Class: Philiprehberger::HtmlBuilder::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/philiprehberger/html_builder/builder.rb

Overview

DSL-based HTML builder that creates a tree of nodes

Constant Summary collapse

STANDARD_TAGS =
%i[
  a abbr address article aside audio b bdi bdo blockquote body button
  canvas caption cite code colgroup data datalist dd del details dfn
  dialog div dl dt em fieldset figcaption figure footer form
  h1 h2 h3 h4 h5 h6 head header hgroup html i iframe ins kbd label
  legend li main map mark menu meter nav noscript object ol optgroup
  option output p picture pre progress q rp rt ruby s samp script
  section select slot small span strong style sub summary sup table
  tbody td template textarea tfoot th thead time title tr u ul var video
].freeze
VOID_TAGS =
%i[area base br col embed hr img input link meta param source track wbr].freeze
ALL_TAGS =
(STANDARD_TAGS + VOID_TAGS).freeze

Instance Method Summary collapse

Constructor Details

#initializeBuilder

Returns a new instance of Builder.



22
23
24
25
26
27
# File 'lib/philiprehberger/html_builder/builder.rb', line 22

def initialize
  @root_children = []
  @stack = []
  @components = {}
  @cache_store = {}
end

Instance Method Details

#cache(key) { ... } ⇒ void

This method returns an undefined value.

Cache a rendered block result by key

On the first call with a given key, the block is executed, its rendered HTML is stored, and a raw node is appended. On subsequent calls with the same key, the cached HTML is appended without re-executing the block.

Parameters:

  • key (Object)

    the cache key

Yields:

  • the block to render and cache

Raises:



277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/philiprehberger/html_builder/builder.rb', line 277

def cache(key, &block)
  raise Error, 'a block is required for cache' unless block

  if @cache_store.key?(key)
    raw(@cache_store[key])
  else
    nested = Builder.new
    nested.instance_eval(&block)
    html = nested.to_html
    @cache_store[key] = html
    raw(html)
  end
end

#class_names(*args) ⇒ String

Build a space-joined CSS class string from mixed arguments

Strings are included as-is. Hash keys are included when their value is truthy.

Parameters:

  • args (Array<String, Hash>)

    class names and conditional hashes

Returns:

  • (String)

    space-joined class string



255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/philiprehberger/html_builder/builder.rb', line 255

def class_names(*args)
  result = []
  args.each do |arg|
    case arg
    when Hash
      arg.each { |key, val| result << key.to_s if val }
    else
      result << arg.to_s
    end
  end
  result.join(' ')
end

#define_component(name) { ... } ⇒ void

This method returns an undefined value.

Define a reusable named component

Parameters:

  • name (Symbol, String)

    the component name

Yields:

  • the block that defines the component’s HTML

Raises:



122
123
124
125
126
# File 'lib/philiprehberger/html_builder/builder.rb', line 122

def define_component(name, &block)
  raise Error, 'a block is required for define_component' unless block

  @components[name.to_sym] = block
end

#doctypevoid

This method returns an undefined value.

Emit an HTML5 doctype declaration (‘<!DOCTYPE html>`)

Has no children and no attributes. In pretty mode the declaration is rendered on its own line at the current indentation.



89
90
91
# File 'lib/philiprehberger/html_builder/builder.rb', line 89

def doctype
  current_children << DoctypeNode.new
end

#field(name, label_text: nil, type: 'text', **attrs) ⇒ void

This method returns an undefined value.

Form builder helper: builds a label + input pair

Parameters:

  • name (String, Symbol)

    the field name

  • label_text (String) (defaults to: nil)

    the label text

  • type (String) (defaults to: 'text')

    the input type (default “text”)

  • attrs (Hash)

    additional input attributes



162
163
164
165
166
167
# File 'lib/philiprehberger/html_builder/builder.rb', line 162

def field(name, label_text: nil, type: 'text', **attrs)
  field_id = attrs.delete(:id) || name.to_s.tr('_', '-')
  label_str = label_text || name.to_s.gsub('_', ' ').split.map(&:capitalize).join(' ')
  label label_str, for: field_id
  input(type: type, name: name.to_s, id: field_id, **attrs)
end

#form_for(action, method_type: 'post', **attrs) { ... } ⇒ Node

Form builder helper: builds a form tag with common defaults

Parameters:

  • action (String)

    the form action URL

  • method_type (String) (defaults to: 'post')

    the HTTP method (default “post”)

  • attrs (Hash)

    additional attributes

Yields:

  • the form contents

Returns:



151
152
153
# File 'lib/philiprehberger/html_builder/builder.rb', line 151

def form_for(action, method_type: 'post', **attrs, &block)
  form(action: action, method: method_type, **attrs, &block)
end

#hidden_field(name, value) ⇒ Node

Form builder helper: generate a hidden input field

Parameters:

  • name (String, Symbol)

    the input name attribute

  • value (String, Symbol)

    the input value attribute

Returns:



216
217
218
# File 'lib/philiprehberger/html_builder/builder.rb', line 216

def hidden_field(name, value)
  input(type: 'hidden', name: name.to_s, value: value.to_s)
end

#list(items, ordered: false, **attrs) {|item| ... } ⇒ Node

Build a list (ul or ol) from an array of items

Parameters:

  • items (Array)

    the list items

  • ordered (Boolean) (defaults to: false)

    use ol instead of ul (default false)

  • attrs (Hash)

    additional attributes for the list element

Yields:

  • (item)

    optional block for custom rendering of each item

Returns:



236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/philiprehberger/html_builder/builder.rb', line 236

def list(items, ordered: false, **attrs, &block)
  tag_name = ordered ? :ol : :ul
  send(tag_name, **attrs) do
    items.each do |item|
      if block
        li { block.call(item) }
      else
        li item.to_s
      end
    end
  end
end

#raw(html) ⇒ void

This method returns an undefined value.

Add raw HTML content without escaping

Parameters:

  • html (String)

    the raw HTML string



78
79
80
81
# File 'lib/philiprehberger/html_builder/builder.rb', line 78

def raw(html)
  node = RawNode.new(html)
  current_children << node
end

#render_if(condition) { ... } ⇒ void

This method returns an undefined value.

Conditionally render a block if the condition is truthy

Parameters:

  • condition (Object)

    the condition to evaluate

Yields:

  • the block to render if condition is truthy

Raises:



98
99
100
101
102
103
# File 'lib/philiprehberger/html_builder/builder.rb', line 98

def render_if(condition, &block)
  return unless condition
  raise Error, 'a block is required for render_if' unless block

  instance_eval(&block)
end

#render_unless(condition) { ... } ⇒ void

This method returns an undefined value.

Conditionally render a block if the condition is falsy

Parameters:

  • condition (Object)

    the condition to evaluate

Yields:

  • the block to render if condition is falsy

Raises:



110
111
112
113
114
115
# File 'lib/philiprehberger/html_builder/builder.rb', line 110

def render_unless(condition, &block)
  return if condition
  raise Error, 'a block is required for render_unless' unless block

  instance_eval(&block)
end

#select_field(name, options_list, label_text: nil, selected: nil, **attrs) ⇒ void

This method returns an undefined value.

Form builder helper: builds a label + select with options

Parameters:

  • name (String, Symbol)

    the field name

  • options_list (Array<Array, String>)

    list of [text, value] pairs or plain strings

  • label_text (String) (defaults to: nil)

    the label text

  • selected (String, nil) (defaults to: nil)

    the selected value

  • attrs (Hash)

    additional select attributes



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/philiprehberger/html_builder/builder.rb', line 177

def select_field(name, options_list, label_text: nil, selected: nil, **attrs)
  field_id = attrs.delete(:id) || name.to_s.tr('_', '-')
  label_str = label_text || name.to_s.gsub('_', ' ').split.map(&:capitalize).join(' ')
  label label_str, for: field_id
  select(name: name.to_s, id: field_id, **attrs) do
    options_list.each do |opt|
      if opt.is_a?(Array)
        opt_text, opt_value = opt
        option_attrs = { value: opt_value.to_s }
        option_attrs[:selected] = true if opt_value.to_s == selected.to_s
        option opt_text, **option_attrs
      else
        option_attrs = { value: opt.to_s }
        option_attrs[:selected] = true if opt.to_s == selected.to_s
        option opt.to_s, **option_attrs
      end
    end
  end
end

#submit(text = 'Submit', **attrs) ⇒ Node

Form builder helper: generate a submit button

Parameters:

  • text (String) (defaults to: 'Submit')

    the button text (default “Submit”)

  • attrs (Hash)

    additional button attributes

Returns:



225
226
227
# File 'lib/philiprehberger/html_builder/builder.rb', line 225

def submit(text = 'Submit', **attrs)
  button(text, type: 'submit', **attrs)
end

#text(content) ⇒ void

This method returns an undefined value.

Add raw text content to the current context

Parameters:

  • content (String)

    the text content (will be escaped)



70
71
72
# File 'lib/philiprehberger/html_builder/builder.rb', line 70

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

#textarea_field(name, content = nil, label_text: nil, **attrs) ⇒ void

This method returns an undefined value.

Form builder helper: builds a label + textarea

Parameters:

  • name (String, Symbol)

    the field name

  • content (String, nil) (defaults to: nil)

    the textarea content

  • label_text (String) (defaults to: nil)

    the label text

  • attrs (Hash)

    additional textarea attributes



204
205
206
207
208
209
# File 'lib/philiprehberger/html_builder/builder.rb', line 204

def textarea_field(name, content = nil, label_text: nil, **attrs)
  field_id = attrs.delete(:id) || name.to_s.tr('_', '-')
  label_str = label_text || name.to_s.gsub('_', ' ').split.map(&:capitalize).join(' ')
  label label_str, for: field_id
  textarea(content, name: name.to_s, id: field_id, **attrs)
end

#to_htmlString

Render all root-level nodes to HTML (minified)

Returns:

  • (String)

    the rendered HTML



32
33
34
# File 'lib/philiprehberger/html_builder/builder.rb', line 32

def to_html
  @root_children.map { |c| c.respond_to?(:to_html) ? c.to_html : Escape.html(c.to_s) }.join
end

#to_pretty_html(indent_size: 2) ⇒ String

Render all root-level nodes to pretty-printed HTML with indentation

Parameters:

  • indent_size (Integer) (defaults to: 2)

    number of spaces per indent level (default 2)

Returns:

  • (String)

    the pretty-printed HTML



40
41
42
43
44
45
46
47
48
# File 'lib/philiprehberger/html_builder/builder.rb', line 40

def to_pretty_html(indent_size: 2)
  @root_children.map do |c|
    if c.respond_to?(:to_html)
      c.to_html(indent: 0, indent_size: indent_size)
    else
      Escape.html(c.to_s)
    end
  end.join("\n")
end

#use_component(name, **locals) ⇒ void

This method returns an undefined value.

Render a previously defined component

Parameters:

  • name (Symbol, String)

    the component name

  • locals (Hash)

    local variables passed to the component block

Raises:



133
134
135
136
137
138
139
140
141
142
# File 'lib/philiprehberger/html_builder/builder.rb', line 133

def use_component(name, **locals)
  block = @components[name.to_sym]
  raise Error, "undefined component: #{name}" unless block

  if block.arity.zero? || (block.arity.negative? && locals.empty?)
    instance_eval(&block)
  else
    instance_exec(locals, &block)
  end
end