Class: Stipa::ViewContext

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

Overview

The context object that becomes ‘self` inside every template. Includes helpers for rendering, HTML escaping, and Vue integration.

Instance Method Summary collapse

Constructor Details

#initialize(engine) ⇒ ViewContext

Returns a new instance of ViewContext.



83
84
85
86
# File 'lib/stipa/template.rb', line 83

def initialize(engine)
  @engine = engine
  @_blocks = {}
end

Instance Method Details

#_bindingObject

Internal: expose binding for ERB.



194
195
196
# File 'lib/stipa/template.rb', line 194

def _binding
  binding
end

#_set_content(html) ⇒ Object

Internal: store the inner-page HTML for the layout’s ‘yield`.



189
190
191
# File 'lib/stipa/template.rb', line 189

def _set_content(html)
  @_layout_content = html
end

#_set_local(name, value) ⇒ Object

Internal: inject a local variable as a method on this context.



184
185
186
# File 'lib/stipa/template.rb', line 184

def _set_local(name, value)
  define_singleton_method(name) { value }
end

#contentObject Also known as: yield_content

Called inside a layout template to render the inner page content.

Use one of these in your layout:

<body><%= content %></body>
<body><%= yield_content %></body>

(Plain ERB ‘yield` is a Ruby keyword and cannot be used here.)



205
206
207
# File 'lib/stipa/template.rb', line 205

def content
  @_layout_content
end

#content_for(section = nil, &block) ⇒ Object

Named content blocks — store content from a page, render in the layout.

Page: <% content_for :title do %>Home<% end %> Layout: <title><%= content_for(:title) %></title>



214
215
216
217
# File 'lib/stipa/template.rb', line 214

def content_for(section = nil, &block)
  return @_blocks[section] if section && !block
  @_blocks[section] = block.call if section && block
end

#h(value) ⇒ Object Also known as: escape_html

HTML-escape a value. Use this in attributes or when interpolating untrusted user input inside text content.

<div class="<%= escape_html(user.css_class) %>">

Or use the alias:

<div class="<%= h(user.css_class) %>">


106
107
108
# File 'lib/stipa/template.rb', line 106

def h(value)
  ERB::Util.html_escape(value.to_s)
end

#javascript_tag(*srcs, type: nil, **attrs) ⇒ Object

Include one or more JavaScript files.

<%= javascript_tag '/app.js' %>
<%= javascript_tag '/a.js', '/b.js', type: 'module' %>


165
166
167
168
169
# File 'lib/stipa/template.rb', line 165

def javascript_tag(*srcs, type: nil, **attrs)
  extra     = attrs.map { |k, v| %( #{k}="#{h(v)}") }.join
  type_attr = type ? %( type="#{h(type)}") : ''
  srcs.map { |src| %(<script src="#{h(src)}"#{type_attr}#{extra}></script>) }.join("\n")
end

#render(partial, locals: {}) ⇒ Object

Render a partial from within a template. Partials follow the Rails convention of a leading underscore on disk, but you refer to them without it.

<%= render 'sidebar' %>                         → views/_sidebar.html.erb
<%= render 'users/row', locals: { u: user } %> → views/users/_row.html.erb


225
226
227
228
229
230
231
232
# File 'lib/stipa/template.rb', line 225

def render(partial, locals: {})
  name = if partial.include?('/')
           partial.sub(%r{([^/]+)\z}, '_\1')
         else
           "_#{partial}"
         end
  @engine.render(name, locals: locals, layout: false)
end

#render_template(name, locals: {}) ⇒ Object

Render a template (same engine, layout: false by default). Useful for partials; pass layout: :default if you want to wrap it.



94
95
96
# File 'lib/stipa/template.rb', line 94

def render_template(name, locals: {})
  @engine.render(name, locals: locals, layout: false)
end

#stipa_vue_bootstrap(src: '/stipa-vue.js') ⇒ Object

Include the Stīpa Vue bootstrapper. This script auto-discovers vue_component mount points and mounts them. Must appear AFTER vue_script and AFTER any component registrations.

<%= stipa_vue_bootstrap %>


158
159
160
# File 'lib/stipa/template.rb', line 158

def stipa_vue_bootstrap(src: '/stipa-vue.js')
  %(<script type="module" src="#{src}"></script>)
end

#stylesheet_tag(*hrefs, **attrs) ⇒ Object

Include one or more stylesheets.

<%= stylesheet_tag '/app.css' %>
<%= stylesheet_tag '/reset.css', '/app.css' %>


174
175
176
177
# File 'lib/stipa/template.rb', line 174

def stylesheet_tag(*hrefs, **attrs)
  extra = attrs.map { |k, v| %( #{k}="#{h(v)}") }.join
  hrefs.map { |href| %(<link rel="stylesheet" href="#{h(href)}"#{extra}>) }.join("\n")
end

#vue_component(name, props: {}, tag: 'div', **html_attrs) ⇒ Object

Emit a Vue 3 component mount point.

The Stīpa Vue bootstrapper (stipa-vue.js) picks up all elements with data-vue-component and mounts the corresponding registered component, passing data-props as the component’s initial props.

ERB:

<%= vue_component("Counter", props: { initial: 5 }) %>
<%= vue_component("SearchBox", props: { q: params[:q] }, class: "mt-4") %>

Rendered HTML:

<div data-vue-component="Counter" data-props="{&quot;initial&quot;:5}"></div>

Options:

props:  Hash passed as JSON to the component (default {})
tag:    HTML wrapper element (default 'div')
Any other keyword args become HTML attributes on the wrapper element.


132
133
134
135
136
137
# File 'lib/stipa/template.rb', line 132

def vue_component(name, props: {}, tag: 'div', **html_attrs)
  attr_parts = html_attrs.map { |k, v| %(#{k}="#{h(v)}") }
  attr_str   = attr_parts.empty? ? '' : " #{attr_parts.join(' ')}"
  props_json = h(props.to_json)
  %(<#{tag} data-vue-component="#{h(name)}" data-props="#{props_json}"#{attr_str}></#{tag}>)
end

#vue_script(src: nil, version: '3', dev: false) ⇒ Object

Include Vue 3 from a CDN (or a local path you serve).

<%= vue_script %>                          → unpkg, production build
<%= vue_script(version: '3.4.21') %>       → pin a specific version
<%= vue_script(dev: true) %>               → development build (warnings)
<%= vue_script(src: '/vendor/vue.js') %>   → self-hosted


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

def vue_script(src: nil, version: '3', dev: false)
  unless src
    build = dev ? 'vue.global.js' : 'vue.global.prod.js'
    src   = "https://unpkg.com/vue@#{version}/dist/#{build}"
  end
  %(<script src="#{src}"></script>)
end