Class: Papercraft::Template

Inherits:
Proc
  • Object
show all
Defined in:
lib/papercraft/template.rb

Overview

Template represents a distinct, reusable HTML template. A template can include other templates, and also be nested inside other templates.

Since in Papercraft HTML is expressed using blocks (or procs,) the Template class is simply a special kind of Proc, which has some enhanced capabilities, allowing it to be easily composed in a variety of ways.

Templates are usually created using the class methods ‘html`, `xml` or `json`, for HTML, XML or JSON templates, respectively:

greeter = Papercraft.html { |name| h1 "Hello, #{name}!" }
greeter.render('world') #=> "<h1>Hello, world!</h1>"

Templates can also be created using the normal constructor:

greeter = Papercraft::Template.new(mode: :html) { |name| h1 "Hello, #{name}!" }
greeter.render('world') #=> "<h1>Hello, world!</h1>"

The different methods for creating templates can also take a custom MIME type, by passing a ‘mime_type` named argument:

json = Papercraft.json(mime_type: 'application/feed+json') { ... }

In the template block, HTML elements are created by simply calling unqualified methods:

page_layout = Papercraft.html {
  html5 {
    head {
      title 'foo'
    }
    body {
      h1 "Hello, world!"
    }
  }
}

Papercraft templates can take explicit parameters in order to render dynamic content. This can be in the form of regular or named parameters. The ‘greeter` template shown above takes a single `name` parameter. Here’s how a anchor template could be implemented with named parameters:

anchor = Papercraft.html { |uri: , text: | a(text, href: uri) }

The above template could later be rendered by passing the needed arguments:

anchor.render(uri: 'https://example.com', text: 'Example')

## Template Composition

A template can be included in another template using the ‘emit` method:

links = Papercraft.html {
  emit anchor, uri: '/posts',   text: 'Posts'
  emit anchor, uri: '/archive', text: 'Archive'
  emit anchor, uri: '/about',   text: 'About'
}

Another way of composing templates is to pass the templates themselves as parameters:

links = Papercraft.html { |anchors|
  anchors.each { |a| emit a }
}
links.render([
  anchor.apply(uri: '/posts', text: 'Posts'),
  anchor.apply(uri: '/archive', text: 'Archive'),
  anchor.apply(uri: '/about', text: 'About')
])

The ‘#apply` method creates a new template, applying the given parameters such that the template can be rendered without parameters:

links_with_anchors = links.apply([
  anchor.apply(uri: '/posts', text: 'Posts'),
  anchor.apply(uri: '/archive', text: 'Archive'),
  anchor.apply(uri: '/about', text: 'About')
])
links_with_anchors.render

Constant Summary collapse

STOCK_MIME_TYPE =
{
  html: 'text/html',
  xml:  'application/xml',
  json: 'application/json'
}.freeze
H_EMPTY =
{}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mode: :html, mime_type: nil, &block) ⇒ Template

Initializes a template with the given block. The rendering mode (HTML or XML) can be passed in the ‘mode:` parameter. If `mode:` is not specified, the template defaults to HTML.

Parameters:

  • mode (:html, :xml) (defaults to: :html)

    rendering mode

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

    the template’s mime type (nil for default)

  • block (Proc)

    nested HTML block



105
106
107
108
109
# File 'lib/papercraft/template.rb', line 105

def initialize(mode: :html, mime_type: nil, &block)
  @mode = mode
  @mime_type = mime_type || STOCK_MIME_TYPE[mode]
  super(&block)
end

Instance Attribute Details

#modeObject

Determines the rendering mode: ‘:html` or `:xml`.



90
91
92
# File 'lib/papercraft/template.rb', line 90

def mode
  @mode
end

Instance Method Details

#apply(*a, **b, &block) ⇒ Papercraft::Template

Creates a new template, applying the given parameters and or block to the current one. Application is one of the principal methods of composing templates, particularly when passing inner templates as blocks:

article_wrapper = Papercraft.html {
  article {
    emit_yield
  }
}
wrapped_article = article_wrapper.apply {
  h1 'Article title'
}
wrapped_article.render #=> "<article><h1>Article title</h1></article>"

Parameters:

  • *a (<any>)

    normal parameters

  • **b (Hash)

    named parameters

Returns:



171
172
173
174
175
176
177
# File 'lib/papercraft/template.rb', line 171

def apply(*a, **b, &block)
  template = self
  Template.new(mode: @mode, mime_type: @mime_type, &proc do |*x, **y|
    push_emit_yield_block(block) if block
    instance_exec(*a, *x, **b, **y, &template)
  end)
end

#mime_typeString

Returns the template’s associated MIME type.

Returns:

  • (String)

    MIME type



199
200
201
# File 'lib/papercraft/template.rb', line 199

def mime_type
  @mime_type
end

#render(*a, **b, &block) ⇒ String

Renders the template with the given parameters and or block, and returns the string result.

Parameters:

  • *params (any)

    unnamed parameters

  • **named_params (any)

    named parameters

Returns:

  • (String)

    rendered string



119
120
121
122
123
124
125
126
# File 'lib/papercraft/template.rb', line 119

def render(*a, **b, &block)
  template = self
  Renderer.verify_proc_parameters(template, a, b)
  renderer_class.new do
    push_emit_yield_block(block) if block
    instance_exec(*a, **b, &template)
  end.to_s
end

#render_fragment(name, *a, **b, &block) ⇒ String

Renders a template fragment. Any given parameters are passed to the template just like with #render. See also / HTMX template fragments.

form = Papercraft.html { |action|
  h1 'Hello'
  fragment(:buttons) {
    button action
    button 'Cancel'
  }
}
form.render_fragment(:buttons, 'foo') #=> "<button>foo</button><button>Cancel</buttons>"

Parameters:

  • name (Symbol, String)

    fragment name

  • *params (any)

    unnamed parameters

  • **named_params (any)

    named parameters

Returns:

  • (String)

    rendered string



145
146
147
148
149
150
151
152
# File 'lib/papercraft/template.rb', line 145

def render_fragment(name, *a, **b, &block)
  template = self
  Renderer.verify_proc_parameters(template, a, b)
  renderer_class.new(name) do
    push_emit_yield_block(block) if block
    instance_exec(*a, **b, &template)
  end.to_s
end

#renderer_classPapercraft::Renderer

Returns the Renderer class used for rendering the templates, according to the template’s mode.

Returns:



183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/papercraft/template.rb', line 183

def renderer_class
  case @mode
  when :html
    HTMLRenderer
  when :xml
    XMLRenderer
  when :json
    JSONRenderer
  else
    raise "Invalid mode #{@mode.inspect}"
  end
end