Module: ComponentHelper

Defined in:
app/helpers/component_helper.rb

Overview

The Registry of Names

All components available to views are declared here. There is no method_missing sorcery, no dynamic resolution, no convention-based guessing. If a component is not in the registry, it does not exist.

To create a new component: add one line to COMPONENT_MAP and create the class.

See: specs/the-holy-view-spec.md, Article 3

Constant Summary collapse

COMPONENT_MAP =
{
  # Layout Primitives
  VStack:      "VStackComponent",
  HStack:      "HStackComponent",
  Column:      "ColumnComponent",
  Row:         "RowComponent",
  Pusher:      "PusherComponent",
  Overlay:     "OverlayComponent",
  LinkTo:      "LinkToComponent",
  SubHeader:   "SubHeaderComponent",

  # Globals
  Reset:       "ResetComponent",
  Site:        "SiteComponent",
  Wrapper:     "WrapperComponent",
  Template:    "TemplateComponent",
  BackButton:  "BackButtonComponent",

  # Elements
  Button:      "ButtonComponent",
  Paragraph:   "ParagraphComponent",
  ButtonTo:    "ButtonToComponent",
  Container:   "ContainerComponent",
  Divider:     "DividerComponent",
  Emoji:       "EmojiComponent",
  Flag:        "FlagComponent",
  Header:      "HeaderComponent",
  Icon:        "IconComponent",
  Image:       "ImageComponent",
  Input:       "InputComponent",
  Tag:         "TagComponent",
  TagGroup:    "TagGroupComponent",
  List:            "ListComponent",
  ListItem:        "ListItemComponent",
  ListContent:     "ListContentComponent",
  ListHeader:      "ListHeaderComponent",
  ListDescription: "ListDescriptionComponent",
  Loader:      "LoaderComponent",
  Placeholder: "PlaceholderComponent",
  Rail:        "RailComponent",
  Reveal:      "RevealComponent",
  Segment:      "SegmentComponent",
  SegmentGroup: "SegmentGroupComponent",
  Step:        "StepComponent",
  StepGroup:   "StepGroupComponent",

  Text:        "TextComponent",
  ButtonGroup: "ButtonGroupComponent",

  # Collections
  Breadcrumb:  "BreadcrumbComponent",
  Field:       "FieldComponent",
  Form:        "FormComponent",
  Grid:        "GridComponent",
  Menu:        "MenuComponent",
  MenuItem:    "MenuItemComponent",
  SubMenu:     "SubMenuComponent",
  Message:     "MessageComponent",
  Table:       "TableComponent",
  TableRow:    "TableRowComponent",
  TableCell:   "TableCellComponent",

  # Views
  Ad:          "AdComponent",
  ItemGroup:   "ItemGroupComponent",
  Card:        "CardComponent",
  Comment:           "CommentComponent",
  CommentGroup:      "CommentGroupComponent",
  CommentReplyGroup: "CommentReplyGroupComponent",
  CommentReply:      "CommentReplyComponent",
  Feed:        "FeedComponent",
  FeedItem:    "FeedItemComponent",
  Item:        "ItemComponent",
  Statistic:   "StatisticComponent",

  # Modules
  Accordion:     "AccordionComponent",
  AccordionItem: "AccordionItemComponent",
  SubAccordion:  "SubAccordionComponent",
  Calendar:      "CalendarComponent",

  Dimmer:      "DimmerComponent",
  Dropdown:    "DropdownComponent",
  Embed:       "EmbedComponent",
  Flyout:      "FlyoutComponent",
  Modal:       "ModalComponent",
  Nag:         "NagComponent",
  Popup:       "PopupComponent",
  Progress:    "ProgressComponent",
  Slider:      "SliderComponent",
  Rating:      "RatingComponent",
  Search:      "SearchComponent",
  Shape:       "ShapeComponent",
  Sidebar:     "SidebarComponent",
  Sticky:      "StickyComponent",
  Tab:         "TabComponent",
  TabGroup:    "TabGroupComponent",
  Toast:       "ToastComponent",
  Transition:  "TransitionComponent",

  # Behaviors
  Api:         "ApiComponent",
  State:       "StateComponent",
  Visibility:  "VisibilityComponent",

  # Blocks
  ResourceListBlock: "ResourceListBlock"
}.freeze

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, **kwargs, &block) ⇒ Object

PascalCase method calls that aren’t in COMPONENT_MAP are forwarded to the current form builder as underscored method names. e.g. TextField(:label, placeholder: “Name”) -> f.text_field(:label, placeholder: “Name”)

EmojiField(:icon)                      -> f.emoji_field(:icon)
Select(:role, [["Admin","admin"]])      -> f.select(:role, [["Admin","admin"]])


176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'app/helpers/component_helper.rb', line 176

def method_missing(method_name, *args, **kwargs, &block)
  if method_name =~ /\A[A-Z]/ && @_form_builder
    underscored = method_name.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
    if @_form_builder.respond_to?(underscored)
      output_buffer << @_form_builder.public_send(underscored, *args, **kwargs, &block)
      return
    end
  end

  if method_name =~ /\A[A-Z]/
    tag_name = method_name.to_s.underscore.gsub("_", "-")
    output_buffer << tag.public_send(tag_name, *args, **kwargs, &block)
    return
  end

  super
end

Instance Method Details

#ContentFor(*args, &block) ⇒ Object



165
166
167
168
169
# File 'app/helpers/component_helper.rb', line 165

def ContentFor(*args, &block)
  value = content_for(*args, &block)
  output_buffer << value if value.present?
  value
end

#CspMetaTagObject



161
162
163
# File 'app/helpers/component_helper.rb', line 161

def CspMetaTag
  output_buffer << csp_meta_tag
end

#CsrfMetaTagsObject



157
158
159
# File 'app/helpers/component_helper.rb', line 157

def CsrfMetaTags
  output_buffer << csrf_meta_tags
end

#DocType(type = :html) ⇒ Object



145
146
147
# File 'app/helpers/component_helper.rb', line 145

def DocType(type = :html)
  output_buffer << "<!DOCTYPE #{type}>".html_safe
end

#JavascriptImportmapObject



153
154
155
# File 'app/helpers/component_helper.rb', line 153

def JavascriptImportmap
  output_buffer << javascript_importmap_tags
end

#NbSpaceObject



137
138
139
# File 'app/helpers/component_helper.rb', line 137

def NbSpace
  output_buffer << "&nbsp;".html_safe
end

#Partial(*args, **kwargs, &block) ⇒ Object



141
142
143
# File 'app/helpers/component_helper.rb', line 141

def Partial(*args, **kwargs, &block)
  output_buffer << render(*args, **kwargs, &block)
end

#respond_to_missing?(method_name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


194
195
196
197
198
199
200
201
202
203
# File 'app/helpers/component_helper.rb', line 194

def respond_to_missing?(method_name, include_private = false)
  if method_name =~ /\A[A-Z]/ && @_form_builder
    underscored = method_name.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
    return true if @_form_builder.respond_to?(underscored)
  end

  return true if method_name =~ /\A[A-Z]/

  super
end

#Style(css = nil, &block) ⇒ Object



129
130
131
# File 'app/helpers/component_helper.rb', line 129

def Style(css = nil, &block)
  output_buffer << render("StyleComponent".constantize.new(css), &block)
end


149
150
151
# File 'app/helpers/component_helper.rb', line 149

def StylesheetLink(*args)
  output_buffer << stylesheet_link_tag(*args)
end

#text(content) ⇒ Object



133
134
135
# File 'app/helpers/component_helper.rb', line 133

def text(content)
  output_buffer << content.to_s
end