Class: Compony::Component
- Inherits:
-
Object
- Object
- Compony::Component
- Defined in:
- lib/compony/component.rb
Direct Known Subclasses
Compony::Components::Buttons::Link, Compony::Components::Destroy, Compony::Components::Form, Compony::Components::Index, Compony::Components::List, Compony::Components::Show, Compony::Components::WithForm
Instance Attribute Summary collapse
- #comp_opts ⇒ Object readonly
-
#content_blocks ⇒ Object
readonly
needed in RequestContext for nesting.
- #parent_comp ⇒ Object readonly
Class Method Summary collapse
-
.comp_name ⇒ Object
Returns the component name.
-
.family_name ⇒ Object
Returns the family name.
-
.setup(&block) ⇒ Object
DSL method.
Instance Method Summary collapse
-
#before_render(name = :main, before: nil, &block) ⇒ Object
DSL method Adds or overrides a before_render block.
-
#content(name = :main, before: nil, &block) ⇒ Object
DSL method Adds or overrides a content block.
-
#exposed_intents(&block) ⇒ Object
DSL method If a block is given: Enters the DSL where exposed intents can be added or removed (use from Component.setup within the component).
-
#id ⇒ Object
Returns an identifier describing this component.
-
#id_path ⇒ Object
Returns the id_path from the root_comp.
-
#id_path_hash ⇒ Object
Returns a hash for the id_path.
-
#initialize(parent_comp = nil, index: 0, **comp_opts) ⇒ Component
constructor
A new instance of Component.
- #inspect ⇒ Object
-
#param_name(unprefixed_param_name) ⇒ Object
Given an unprefixed name of a param, adds the id_path hash Do not overwrite.
-
#path(&block) ⇒ Object
DSL method Overrides how the path to this component should be generated.
-
#remove_content(name) ⇒ Object
DSL method Removes a content block.
-
#remove_content!(name) ⇒ Object
DSL method Removes a content block and fails if the content block was not found.
-
#render(controller, standalone: false, **locals) ⇒ Object
Renders the component using the controller passsed to it and returns it as a string.
-
#resourceful? ⇒ Boolean
Is true for resourceful components.
-
#root_comp ⇒ Object
Returns the current root comp.
-
#root_comp? ⇒ Boolean
Returns whether or not this is the root comp.
-
#sub_comp(**comp_opts) ⇒ Object
Instanciate a component with
selfas a parent.
Constructor Details
#initialize(parent_comp = nil, index: 0, **comp_opts) ⇒ Component
Returns a new instance of Component.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/compony/component.rb', line 33 def initialize(parent_comp = nil, index: 0, **comp_opts) @parent_comp = parent_comp @sub_comps = [] @index = index @comp_opts = comp_opts @before_render_blocks = NaturalOrdering.new @content_blocks = NaturalOrdering.new @actions = NaturalOrdering.new @exposed_intent_blocks = [] @skipped_actions = Set.new @path_block = proc do |model = nil, *args_for_path_helper, standalone_name: nil, **kwargs_for_path_helper| kwargs_for_path_helper.merge!(id: model.id) if model next Rails.application.routes.url_helpers.send( "#{path_helper_name(standalone_name)}_path", *args_for_path_helper, **kwargs_for_path_helper ) end init_standalone init_labelling fail "#{inspect} is missing a call to `setup`." unless setup_blocks&.any? setup_blocks.each do |setup_block| instance_exec(&setup_block) end end |
Instance Attribute Details
#comp_opts ⇒ Object (readonly)
9 10 11 |
# File 'lib/compony/component.rb', line 9 def comp_opts @comp_opts end |
#content_blocks ⇒ Object (readonly)
needed in RequestContext for nesting
10 11 12 |
# File 'lib/compony/component.rb', line 10 def content_blocks @content_blocks end |
#parent_comp ⇒ Object (readonly)
8 9 10 |
# File 'lib/compony/component.rb', line 8 def parent_comp @parent_comp end |
Class Method Details
.comp_name ⇒ Object
Returns the component name
29 30 31 |
# File 'lib/compony/component.rb', line 29 def self.comp_name name.demodulize.underscore end |
.family_name ⇒ Object
Returns the family name
24 25 26 |
# File 'lib/compony/component.rb', line 24 def self.family_name module_parent.to_s.demodulize.underscore end |
.setup(&block) ⇒ Object
DSL method
16 17 18 19 20 21 |
# File 'lib/compony/component.rb', line 16 def self.setup(&block) fail("`setup` expects a block in #{inspect}.") unless block_given? self.setup_blocks ||= [] self.setup_blocks = setup_blocks.dup # This is required to prevent the parent class to see children's setup blocks. setup_blocks << block end |
Instance Method Details
#before_render(name = :main, before: nil, &block) ⇒ Object
DSL method Adds or overrides a before_render block. You can use controller.redirect_to to redirect away and halt the before_render/content chain
141 142 143 144 |
# File 'lib/compony/component.rb', line 141 def before_render(name = :main, before: nil, **, &block) fail("`before_render` expects a block in #{inspect}.") unless block_given? @before_render_blocks.natural_push(name, block, before:, **) end |
#content(name = :main, before: nil, &block) ⇒ Object
DSL method Adds or overrides a content block.
152 153 154 155 156 157 158 |
# File 'lib/compony/component.rb', line 152 def content(name = :main, before: nil, **, &block) # A block is required here, but if this is an override (e.g. to hide another content block), we can tolerate the missing block. if !block_given? && @content_blocks.find { |b| b.name == name }.nil? fail("`content` expects a block in #{inspect}.") end @content_blocks.natural_push(name, block || :missing, before:, **) end |
#exposed_intents(&block) ⇒ Object
DSL method If a block is given: Enters the DSL where exposed intents can be added or removed (use from setup within the component). If no block is given: Builds the declared intents and returns them (use from a RequestContest outside the component).
218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/compony/component.rb', line 218 def exposed_intents(&block) if block_given? # Enter DSL @exposed_intent_blocks << block else # Build the declared intents return @exposed_intents if @exposed_intents @exposed_intents = NaturalOrdering.new @exposed_intent_blocks.each { |block| ManageIntentsDsl.new(@exposed_intents).evaluate(&block) } # alters @exposed_intents return @exposed_intents.map!(&:payload) end end |
#id ⇒ Object
Returns an identifier describing this component. Must be unique among simplings under the same parent_comp. Do not override.
81 82 83 |
# File 'lib/compony/component.rb', line 81 def id "#{family_name}_#{comp_name}_#{@index}" end |
#id_path ⇒ Object
Returns the id_path from the root_comp. Do not overwrite.
87 88 89 90 91 92 93 |
# File 'lib/compony/component.rb', line 87 def id_path if root_comp? id else "#{parent_comp.id_path}/#{id}" end end |
#id_path_hash ⇒ Object
Returns a hash for the id_path. Used for params prefixing. Do not overwrite.
97 98 99 |
# File 'lib/compony/component.rb', line 97 def id_path_hash Digest::SHA1.hexdigest(id_path)[..4] end |
#inspect ⇒ Object
62 63 64 |
# File 'lib/compony/component.rb', line 62 def inspect "#<#{self.class.name}:#{hash}>" end |
#param_name(unprefixed_param_name) ⇒ Object
Given an unprefixed name of a param, adds the id_path hash Do not overwrite.
103 104 105 |
# File 'lib/compony/component.rb', line 103 def param_name(unprefixed_param_name) "#{id_path_hash}_#{unprefixed_param_name}" end |
#path(&block) ⇒ Object
DSL method
Overrides how the path to this component should be generated.
The block will be given the following args: a model (optional), pos. args for the path helper, the kwarg standalone_name and kwargs for the path helper.
The block is expected to return a Rails path. It is not given controller or helpers, instead use: Rails.application.routes.url_helpers.
For an example, refer to the initializer of this class, where the default block is defined.
126 127 128 129 130 131 132 133 |
# File 'lib/compony/component.rb', line 126 def path(*, **, &block) if block_given? # Assignment via DSL @path_block = block else @path_block.call(*, **) end end |
#remove_content(name) ⇒ Object
DSL method Removes a content block. Use this in subclasses if a content block defined in the parent should be removed from the child.
163 164 165 166 167 168 169 170 171 |
# File 'lib/compony/component.rb', line 163 def remove_content(name) # rubocop:disable Naming/PredicateMethod existing_index = @content_blocks.find_index { |el| el.name == name.to_sym } if existing_index.nil? return false else @content_blocks.delete_at(existing_index) return true end end |
#remove_content!(name) ⇒ Object
DSL method Removes a content block and fails if the content block was not found.
176 177 178 |
# File 'lib/compony/component.rb', line 176 def remove_content!(name) remove_content(name) || fail("Content block #{name.inspect} not found for removal in #{inspect}.") end |
#render(controller, standalone: false, **locals) ⇒ Object
Renders the component using the controller passsed to it and returns it as a string. Do not overwrite.
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/compony/component.rb', line 183 def render(controller, standalone: false, **locals) # Call before_render hooks (if any) and backfire instance variables back to the component @before_render_blocks.each do |element| RequestContext.new(self, controller, locals:).evaluate_with_backfire(&element.payload) # Stop if a `before_render` block issued a body (e.g. through redirecting) break unless controller.response_body.nil? end # Render, unless before_render has already issued a body (e.g. through redirecting). if controller.response_body.nil? fail "#{self.class.inspect} must define `content` or set a response body in `before_render`" if @content_blocks.none? return controller.render_to_string( type: :dyny, locals: { content_blocks: @content_blocks, standalone:, component: self, render_locals: locals }, inline: <<~RUBY if Compony.content_before_root_comp_block && standalone Compony::RequestContext.new(component, controller, helpers: self, locals: render_locals).evaluate(&Compony.content_before_root_comp_block) end content_blocks.reject{ |el| el.hidden }.each do |element| # Instanciate and evaluate a fresh RequestContext in order to use the buffer allocated by the ActionView (needed for `concat` calls) Compony::RequestContext.new(component, controller, helpers: self, locals: render_locals).evaluate(&element.payload) end if Compony.content_after_root_comp_block && standalone Compony::RequestContext.new(component, controller, helpers: self, locals: render_locals).evaluate(&Compony.content_after_root_comp_block) end RUBY ) else return nil # Prevent double render errors end end |
#resourceful? ⇒ Boolean
Is true for resourceful components
232 233 234 |
# File 'lib/compony/component.rb', line 232 def resourceful? return false end |
#root_comp ⇒ Object
Returns the current root comp. Do not overwrite.
68 69 70 71 |
# File 'lib/compony/component.rb', line 68 def root_comp return self unless parent_comp return parent_comp.root_comp end |
#root_comp? ⇒ Boolean
Returns whether or not this is the root comp. Do not overwrite.
75 76 77 |
# File 'lib/compony/component.rb', line 75 def root_comp? parent_comp.nil? end |