Class: RSX::Component
- Inherits:
-
Object
- Object
- RSX::Component
- Defined in:
- lib/rsx/component.rb
Overview
Base class for every component defined in a .rsx file.
component Greeting do |name:|
return <p>Hello {name}</p>
end
compiles to a subclass whose rsx_render is the block body. Components are
plain Ruby objects: one instance per render, no inheritance requirements on
the caller, and no framework needed to use them.
Constant Summary collapse
- EMPTY_PROPS =
{}.freeze
Class Attribute Summary collapse
- .rsx_cache_options ⇒ Object
-
.rsx_source_digest ⇒ Object
Returns the value of attribute rsx_source_digest.
-
.rsx_source_path ⇒ Object
Returns the value of attribute rsx_source_path.
Instance Attribute Summary collapse
-
#props ⇒ Object
readonly
Returns the value of attribute props.
Class Method Summary collapse
-
.cache(options = true) ⇒ Object
Enables whole-component caching.
-
.call(**props) ⇒ Object
Public API: Button.call(label: "Save") => SafeString.
- .inspect ⇒ Object
- .render(**props) ⇒ Object
-
.rsx_accepted_props ⇒ Object
The keyword props this component declares.
- .rsx_accepts_extra_props? ⇒ Boolean
- .rsx_cache_key(props, options) ⇒ Object
-
.rsx_call(props = nil, parent = nil) ⇒ Object
Renders the component.
- .rsx_filter_props(props) ⇒ Object
- .rsx_render_parameters ⇒ Object
- .rsx_render_style ⇒ Object
-
.rsx_static! ⇒ Object
Marks a component whose output never varies.
- .rsx_static? ⇒ Boolean
Instance Method Summary collapse
-
#cache(key, expires_in: nil) ⇒ Object
Caches a fragment of markup.
-
#children ⇒ Object
The markup nested inside this component's tag, rendered on demand.
- #children? ⇒ Boolean
-
#helpers ⇒ Object
(also: #view_context)
The nearest non-component render context, i.e.
- #helpers? ⇒ Boolean
-
#initialize(props = nil, parent = nil) ⇒ Component
constructor
A new instance of Component.
- #inspect ⇒ Object
-
#raw(value) ⇒ Object
Escape hatch for trusted HTML built elsewhere.
-
#rsx_parent ⇒ Object
The object that rendered this component: a view context in Rails, the parent component when nested, or nil when rendered directly.
- #rsx_perform ⇒ Object
- #to_s ⇒ Object
-
#use_context(context) ⇒ Object
Reads a value provided by an enclosing <Ctx.Provider>.
Constructor Details
#initialize(props = nil, parent = nil) ⇒ Component
Returns a new instance of Component.
128 129 130 131 |
# File 'lib/rsx/component.rb', line 128 def initialize(props = nil, parent = nil) @props = props || EMPTY_PROPS @rsx_parent = parent end |
Class Attribute Details
.rsx_cache_options ⇒ Object
20 21 22 |
# File 'lib/rsx/component.rb', line 20 def @rsx_cache_options end |
.rsx_source_digest ⇒ Object
Returns the value of attribute rsx_source_digest.
17 18 19 |
# File 'lib/rsx/component.rb', line 17 def rsx_source_digest @rsx_source_digest end |
.rsx_source_path ⇒ Object
Returns the value of attribute rsx_source_path.
17 18 19 |
# File 'lib/rsx/component.rb', line 17 def rsx_source_path @rsx_source_path end |
Instance Attribute Details
#props ⇒ Object (readonly)
Returns the value of attribute props.
126 127 128 |
# File 'lib/rsx/component.rb', line 126 def props @props end |
Class Method Details
.cache(options = true) ⇒ Object
Enables whole-component caching.
cache expires_in: 300
cache key: ->(props) { [props[:user], props[:locale]] }
38 39 40 |
# File 'lib/rsx/component.rb', line 38 def cache( = true) @rsx_cache_options = RSX.() end |
.call(**props) ⇒ Object
Public API: Button.call(label: "Save") => SafeString
57 58 59 |
# File 'lib/rsx/component.rb', line 57 def call(**props) rsx_call(props) end |
.inspect ⇒ Object
121 122 123 |
# File 'lib/rsx/component.rb', line 121 def inspect name || super end |
.render(**props) ⇒ Object
61 62 63 |
# File 'lib/rsx/component.rb', line 61 def render(**props) rsx_call(props) end |
.rsx_accepted_props ⇒ Object
The keyword props this component declares.
94 95 96 97 98 |
# File 'lib/rsx/component.rb', line 94 def rsx_accepted_props @rsx_accepted_props ||= rsx_render_parameters.filter_map do |kind, name| name if %i[key keyreq].include?(kind) end end |
.rsx_accepts_extra_props? ⇒ Boolean
100 101 102 103 104 |
# File 'lib/rsx/component.rb', line 100 def rsx_accepts_extra_props? return @rsx_accepts_extra_props if defined?(@rsx_accepts_extra_props) @rsx_accepts_extra_props = rsx_render_parameters.any? { |kind, _| kind == :keyrest } end |
.rsx_cache_key(props, options) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/rsx/component.rb', line 65 def rsx_cache_key(props, ) custom = [:key] payload = if custom.nil? (props || EMPTY_PROPS).reject { |key, _| key == :children } elsif custom.respond_to?(:arity) && custom.arity.zero? custom.call else custom.call(props || EMPTY_PROPS) end "rsx/#{name || "component"}/#{rsx_source_digest || "0"}/#{RSX.stable_key(payload)}" end |
.rsx_call(props = nil, parent = nil) ⇒ Object
Renders the component. This is the entry point used by compiled markup.
43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/rsx/component.rb', line 43 def rsx_call(props = nil, parent = nil) return (@rsx_static_output ||= new(props, parent).rsx_perform) if rsx_static? = return new(props, parent).rsx_perform unless key = rsx_cache_key(props, ) cached = RSX.cache.fetch(key, expires_in: [:expires_in]) do new(props, parent).rsx_perform.to_s end cached.is_a?(SafeString) ? cached : SafeString.new(cached) end |
.rsx_filter_props(props) ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/rsx/component.rb', line 106 def rsx_filter_props(props) return EMPTY_PROPS if props.nil? || props.empty? return props if rsx_accepts_extra_props? accepted = rsx_accepted_props unknown = props.keys - accepted - [:children] unless unknown.empty? raise PropsError, "#{name} does not accept #{unknown.map(&:inspect).join(", ")}. " \ "Declared props: #{accepted.map(&:inspect).join(", ")}. " \ "Add `**rest` to the component parameters to accept anything else." end accepted.include?(:children) ? props : props.except(:children) end |
.rsx_render_parameters ⇒ Object
87 88 89 90 91 |
# File 'lib/rsx/component.rb', line 87 def rsx_render_parameters instance_method(:rsx_render).parameters rescue NameError [] end |
.rsx_render_style ⇒ Object
79 80 81 82 83 84 85 |
# File 'lib/rsx/component.rb', line 79 def rsx_render_style return @rsx_render_style if defined?(@rsx_render_style) parameters = rsx_render_parameters keyword = parameters.any? { |kind, _| %i[key keyreq keyrest].include?(kind) } @rsx_render_style = keyword ? :keyword : :positional end |
.rsx_static! ⇒ Object
Marks a component whose output never varies. The compiler sets this automatically when a component body is nothing but static markup.
26 27 28 |
# File 'lib/rsx/component.rb', line 26 def rsx_static! @rsx_static = true end |
.rsx_static? ⇒ Boolean
30 31 32 |
# File 'lib/rsx/component.rb', line 30 def rsx_static? @rsx_static ? true : false end |
Instance Method Details
#cache(key, expires_in: nil) ⇒ Object
Caches a fragment of markup.
{cache(["sidebar", user.id], expires_in: 300) do
<nav>...</nav>
end}
193 194 195 196 197 |
# File 'lib/rsx/component.rb', line 193 def cache(key, expires_in: nil) full_key = "rsx/#{self.class.name}/#{self.class.rsx_source_digest || "0"}/#{RSX.stable_key(key)}" cached = RSX.cache.fetch(full_key, expires_in: expires_in) { RSX.child(yield).to_s } cached.is_a?(SafeString) ? cached : SafeString.new(cached) end |
#children ⇒ Object
The markup nested inside this component's tag, rendered on demand.
149 150 151 |
# File 'lib/rsx/component.rb', line 149 def children @props[:children] end |
#children? ⇒ Boolean
153 154 155 156 |
# File 'lib/rsx/component.rb', line 153 def children? child = children !child.nil? && !(child.respond_to?(:empty?) && child.empty?) end |
#helpers ⇒ Object Also known as: view_context
The nearest non-component render context, i.e. the Rails view. Gives access
to url helpers, form builders, t, asset helpers and anything else the
application exposes to templates.
167 168 169 170 171 172 173 174 |
# File 'lib/rsx/component.rb', line 167 def helpers node = @rsx_parent node = node.rsx_parent while node.is_a?(Component) return node unless node.nil? raise Error, "#{self.class.name} has no view context. Render it from a Rails view, " \ "or pass one with RSX.render(#{self.class.name}, context: view)." end |
#helpers? ⇒ Boolean
177 178 179 180 181 |
# File 'lib/rsx/component.rb', line 177 def helpers? node = @rsx_parent node = node.rsx_parent while node.is_a?(Component) !node.nil? end |
#inspect ⇒ Object
208 209 210 |
# File 'lib/rsx/component.rb', line 208 def inspect "#<#{self.class.name} #{@props.inspect}>" end |
#raw(value) ⇒ Object
Escape hatch for trusted HTML built elsewhere.
200 201 202 |
# File 'lib/rsx/component.rb', line 200 def raw(value) RSX.raw(value) end |
#rsx_parent ⇒ Object
The object that rendered this component: a view context in Rails, the parent component when nested, or nil when rendered directly.
160 161 162 |
# File 'lib/rsx/component.rb', line 160 def rsx_parent @rsx_parent end |
#rsx_perform ⇒ Object
133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/rsx/component.rb', line 133 def rsx_perform result = if self.class.rsx_render_style == :keyword rsx_render(**self.class.rsx_filter_props(@props)) else rsx_render(@props) end RSX.child(result) rescue ArgumentError => e raise unless e..start_with?("missing keyword", "unknown keyword", "wrong number of arguments") raise PropsError, "#{self.class.name}: #{e.}. " \ "Declared props: #{self.class.rsx_accepted_props.map(&:inspect).join(", ")}" end |
#to_s ⇒ Object
204 205 206 |
# File 'lib/rsx/component.rb', line 204 def to_s rsx_perform end |
#use_context(context) ⇒ Object
Reads a value provided by an enclosing <Ctx.Provider>.
184 185 186 |
# File 'lib/rsx/component.rb', line 184 def use_context(context) context.value end |