Module: Papercraft::Tags

Included in:
HTML, XML
Defined in:
lib/papercraft/tags.rb

Overview

Markup (HTML/XML) extensions

Constant Summary collapse

S_LT =
'<'
S_GT =
'>'
S_LT_SLASH =
'</'
S_SPACE_LT_SLASH =
' </'
S_SLASH_GT =
'/>'
S_GT_LT_SLASH =
'></'
S_SPACE =
' '
S_EQUAL_QUOTE =
'="'
S_QUOTE =
'"'
S_TAG_METHOD_LINE =

The tag method template below is optimized for performance. Do not touch!

__LINE__ + 2
S_TAG_METHOD =
<<~EOF
  S_TAG_%<TAG>s_PRE = %<tag_pre>s
  S_TAG_%<TAG>s_CLOSE = %<tag_close>s

  def %<tag>s(text = nil, _for: nil, **attributes, &block)
    return if @render_fragment && @fragment != @render_fragment

    return _for.each { |*a| %<tag>s(text, **attributes) { block.(*a)} } if _for

    if text.is_a?(Hash) && attributes.empty?
      attributes = text
      text = nil
    end

    @buffer << S_TAG_%<TAG>s_PRE
    emit_attributes(attributes) unless attributes.empty?

    if block
      @buffer << S_GT
      instance_eval(&block)
      @buffer << S_TAG_%<TAG>s_CLOSE
    elsif Proc === text
      @buffer << S_GT
      emit(text)
      @buffer << S_TAG_%<TAG>s_CLOSE
    elsif text
      @buffer << S_GT << escape_text(text.to_s) << S_TAG_%<TAG>s_CLOSE
    else
      @buffer << S_GT << S_TAG_%<TAG>s_CLOSE
    end
  end
EOF
S_VOID_TAG_METHOD_LINE =
__LINE__ + 2
S_VOID_TAG_METHOD =
<<~EOF
  S_TAG_%<TAG>s_PRE = %<tag_pre>s
  S_TAG_%<TAG>s_CLOSE = %<tag_close>s

  def %<tag>s(text = nil, _for: nil, **attributes, &block)
    return if @render_fragment && @fragment != @render_fragment
    
    return _for.each { |*a| %<tag>s(text, **attributes) { block.(*a)} } if _for

    if text.is_a?(Hash) && attributes.empty?
      attributes = text
      text = nil
    end

    @buffer << S_TAG_%<TAG>s_PRE
    emit_attributes(attributes) unless attributes.empty?

    if block
      @buffer << S_GT
      instance_eval(&block)
      @buffer << S_TAG_%<TAG>s_CLOSE
    elsif Proc === text
      @buffer << S_GT
      emit(text)
      @buffer << S_TAG_%<TAG>s_CLOSE
    elsif text
      @buffer << S_GT << escape_text(text.to_s) << S_TAG_%<TAG>s_CLOSE
    else
      @buffer << S_SLASH_GT
    end
  end
EOF
INITIAL_BUFFER_CAPACITY =
8192

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args, **opts, &block) ⇒ void

This method returns an undefined value.

Catches undefined tag method call and handles it by defining the method.

Parameters:

  • sym (Symbol)

    tag or component identifier

  • args (Array)

    method arguments

  • opts (Hash)

    named method arguments



207
208
209
210
211
212
213
214
215
216
# File 'lib/papercraft/tags.rb', line 207

def method_missing(sym, *args, **opts, &block)
  tag = sym.to_s
  if tag =~ /^[A-Z]/ && (Object.const_defined?(tag))
    define_const_tag_method(tag)
  else
    define_tag_method(tag)
  end

  send(sym, *args, **opts, &block)
end

Instance Method Details

#def_tag(tag, &block) ⇒ void

This method returns an undefined value.

Defines a custom tag. This is handy for defining helper or extension methods inside the template body.

Papercraft.html {
  def_tag(:section) { |title, &inner|
    div {
      h1 title
      emit inner
    }
  }

  section('Foo') {
    p 'Bar'
  }
}

Parameters:

  • tag (Symbol, String)

    tag/method name

  • block (Proc)

    method body



249
250
251
# File 'lib/papercraft/tags.rb', line 249

def def_tag(tag, &block)
  self.class.define_method(tag, &block)
end

#defer(&block) ⇒ void

This method returns an undefined value.

Defers the given block to be evaluated later. Deferred evaluation allows Papercraft templates to inject state into sibling components, regardless of the component’s order in the container component. For example, a nested component may set an instance variable used by another component. This is an elegant solution to the problem of setting the XML page’s title, or adding elements to the ‘<head>` section. Here’s how a title can be controlled from a nested component:

layout = Papercraft.html {
  html {
    head {
      defer { title @title }
    }
    body {
      emit_yield
    }
  }
}

html.render {
  @title = 'My super page'
  h1 'content'
}


143
144
145
146
147
148
149
150
151
# File 'lib/papercraft/tags.rb', line 143

def defer(&block)
  if !@parts
    @parts = [@buffer, block]
  else
    @parts << @buffer unless @buffer.empty?
    @parts << block
  end
  @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
end

#extend(ext) ⇒ Object

Extends the template with the provided module or map of modules. When given a module, the template body will be extended with the module, and will have access to all the module’s methods:

module CustomTags
  def label(text)
    span text, class: 'label'
  end
end

Papercraft.html {
  extend CustomTags
  label('foo')
}

When given a hash, each module in the hash is namespaced, and can be accessed using its key:

Papercraft.html {
  extend custom: CustomTags
  custom.label('foo')
}

Parameters:

  • ext (Module, Hash)

    extension module or hash mapping symbols to modules

Returns:

  • (Object)

    self



280
281
282
283
284
285
286
287
288
# File 'lib/papercraft/tags.rb', line 280

def extend(ext)
  if ext.is_a?(Module)
    orig_extend(ext)
  else
    ext.each do |sym, mod|
      define_extension_method(sym, mod)
    end
  end
end

#initialize(render_fragment = nil, &template) ⇒ Object

Initializes a tag renderer.



92
93
94
95
# File 'lib/papercraft/tags.rb', line 92

def initialize(render_fragment = nil, &template)
  @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
  super(render_fragment)
end

#orig_extendObject



253
# File 'lib/papercraft/tags.rb', line 253

alias_method :orig_extend, :extend

#tag(sym, text = nil, _for: nil, **attributes, &block) ⇒ void

This method returns an undefined value.

Emits an XML tag with the given content, properties and optional block. This method is an alternative to emitting XML tags using dynamically created methods. This is particularly useful when using extensions that have method names that clash with XML tags, such as ‘button` or `a`, or when you need to override the behaviour of a particular XML tag.

The following two method calls have the same effect:

button ‘text’, id: ‘button1’ tag :button, ‘text’, id: ‘button1’

Parameters:

  • sym (Symbol, String)

    XML tag

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

    tag content

  • **attributes (Hash)

    tag attributes



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/papercraft/tags.rb', line 169

def tag(sym, text = nil, _for: nil, **attributes, &block)
  return if @render_fragment && @fragment != @render_fragment
    
  return _for.each { |*a| tag(sym, text, **attributes) { block.(*a)} } if _for

  if text.is_a?(Hash) && attributes.empty?
    attributes = text
    text = nil
  end

  tag = tag_repr(sym)

  @buffer << S_LT << tag
  emit_attributes(attributes) unless attributes.empty?

  if block
    @buffer << S_GT
    instance_eval(&block)
    @buffer << S_LT_SLASH << tag << S_GT
  elsif Proc === text
    @buffer << S_GT
    emit(text)
    @buffer << S_LT_SLASH << tag << S_GT
  elsif text
    @buffer << S_GT << escape_text(text.to_s) << S_LT_SLASH << tag << S_GT
  elsif is_void_element_tag?(sym)
    @buffer << S_SLASH_GT
  else
    @buffer << S_GT_LT_SLASH << tag << S_GT
  end
end

#text(data = nil) ⇒ void

This method returns an undefined value.

Emits text into the rendering buffer, escaping any special characters to the respective XML entities.

Parameters:

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

    text



223
224
225
226
227
228
# File 'lib/papercraft/tags.rb', line 223

def text(data = nil)
  return if !data
  return if @render_fragment && @fragment != @render_fragment
    
  @buffer << escape_text(data)
end

#to_sString

Returns the rendered template.

Returns:

  • (String)


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/papercraft/tags.rb', line 100

def to_s
  if @parts
    last = @buffer
    @buffer = String.new(capacity: INITIAL_BUFFER_CAPACITY)
    parts = @parts
    @parts = nil
    parts.each do |p|
      if Proc === p
        render_deferred_proc(&p)
      else
        @buffer << p
      end
    end
    @buffer << last unless last.empty?
  end
  @buffer
end