module WrapIt # # Sections is a smart way to make complex components with inheritance. # # Sections is just array of named HTML markup pieces. You can place any # section before or after another at class level and change their content # at instance level. # # Each component have three stages. First is initialization, then sections # capturing and rendering. You can change any sections content until # rendering stage begins. Finally, renderer joins all sections in order, # that they have at render time. # # {WrapIt::Base} provides following sections: main section is `:content`. # All text from block captured there. `:render_arguments` and `:render_block` # also provided, so arguments and block passed to render method captured # here. # # Access to sections at instance level performed throw hash-like getter and # setter ([] and []=) of self. # # With this functionality you can easy organize you inheritance, so any # descendant can change sections order or any section content without # changes to unrelated sections. # # @example sections usage # class IconedButton < WrapIt::Base # include TextContainer # html_class 'btn' # section :icon # place :icon, before: :content # # after_capture do # self[:icon] = html_safe('') # end # end # # class RightIconedButton < IconedButton # place :icon, after: :content # end # # b1 = IconedButton.new(template, 'text') # b2 = RightIconedButton.new(template, 'text') # b1.render # => '
text
' # b2.render # => '
text
' # # @author Alexey Ovchinnikov # module Sections # Documentation includes # @!parse extend Sections::ClassMethods # module implementation extend DerivedAttributes # def self.included(base) base == Base || fail( TypeError, "#{self.class.name} can be included only into WrapIt::Base" ) base.extend ClassMethods end # # Retrieves specified section content # @param name [Symbol] section name # # @return [String] section content def [](name) @section_names ||= self.class.sections return nil unless @section_names.include?(name) @sections ||= {} @sections[name] ||= empty_html end # # Sets specified section content # @param name [Symbol] section name # @param value [String] content # # @return [String] section content def []=(name, value) @section_names ||= self.class.sections return unless @section_names.include?(name) @sections ||= {} @sections[name] = value end # # {Sections} class methods # module ClassMethods # # Retrieves all sections, including ancestors # # @return [Array] array of sections def sections collect_derived(:@sections) end # # Defines new section or sections. Places its to end of section list # # @overload section([name, ...]) # @param name [Symbol, String] section name # # @return [void] def section(*args) @sections ||= [] args.flatten.each do |name| name.is_a?(String) && name = name.to_sym next unless name.is_a?(Symbol) next if (sections + [:begin, :end]).include?(name) @sections << name placement << name unless placement.include?(name) place name, before: :end end end # # Retrieves section names in current order # # @return [Array] ordered sections array def placement @placement ||= if self == Base sections.clone else parent = ancestors[1..-1].find { |a| a.respond_to?(:placement) } parent.nil? ? sections.clone : parent.placement.clone end end # # Places specific section in specified place # # @overload place(src, to) # @param src [Symbol] section name to place # @param to [Hash] single key-value hash. Key can be `:before` or # `after`, value can be `:begin`, `:end` or section name # # @overload place(src, at, dst) # @param src [Symbol] section name to place # @param at [Symbol] can be `:before` or `:after` # @param dst [Symbol] can be `:begin`, `:end` or section name # # @return [void] def place(src, at, dst = nil) if dst == nil && at.is_a?(Hash) && at.keys.size == 1 dst = at.values[0] at = at.keys[0] end return unless placement.include?(src) && (dst == :begin || dst == :end || placement.include?(dst)) && (at == :before || at == :after) item = placement.delete_at(placement.index(src)) case dst when :begin then placement.unshift(item) when :end then placement.push(item) else x = at == :before ? 0 : 1 placement.insert(placement.index(dst) + x, item) end end end end end