lib/nice_partials/partial.rb in nice_partials-0.1.9 vs lib/nice_partials/partial.rb in nice_partials-0.9.0

- old
+ new

@@ -1,57 +1,111 @@ module NicePartials class Partial + autoload :Content, "nice_partials/partial/content" autoload :Section, "nice_partials/partial/section" autoload :Stack, "nice_partials/partial/stack" delegate_missing_to :@view_context - # <%= render "nice_partial" do |p| %> - # <% p.content_for :title, "Yo" %> - # This content can be accessed through calling `yield`. - # <% end %> - # - # Then in the nice_partial: - # <%= content.content_for :title %> # => "Yo" - # <%= content.output_buffer %> # => "This line is printed to the `output_buffer`." - attr_accessor :output_buffer - - def initialize(view_context) - @view_context = view_context - @contents = Hash.new { |h, k| h[k] = Section.new(@view_context) } + def initialize(view_context, local_assigns = nil) + @view_context, @local_assigns = view_context, local_assigns end def yield(*arguments, &block) if arguments.empty? - output_buffer + @captured_buffer else content_for(*arguments, &block) end end def helpers(&block) - class_eval &block + self.class.class_eval(&block) end + # `translate` is a shorthand to set `content_for` with content that's run through + # the view's `translate`/`t` context. + # + # partial.t :title # => partial.content_for :title, t(".title") + # partial.t title: :section # => partial.content_for :title, t(".section") + # partial.t title: "some.custom.key" # => partial.content_for :title, t("some.custom.key") + # partial.t :description, title: :header # Mixing is supported too. + # + # Note that `partial.t "some.custom.key"` can't derive a `content_for` name, so an explicit + # name must be provided e.g. `partial.t title: "some.custom.key"`. + def translate(*names, **renames) + names.chain(renames).each do |name, key = name| + content_for name, @view_context.t(key.is_a?(String) ? key : ".#{key}") + end + end + alias t translate + + # Allows an inner partial to copy content from an outer partial. + # + # Additionally a hash of keys to rename in the new partial context can be passed. + # + # First, an outer partial gets some content set: + # <% partial.title "Hello there" %> + # <% partial.byline "Somebody" %> + # + # Second, a new partial is rendered, but we want to extract the title, byline content but rename the byline key too: + # <%= render "shared/title" do |cp| %> + # <% cp.content_from partial, :title, byline: :name %> + # <% end %> + # + # # Third, the contents with any renames are accessible in shared/_title.html.erb: + # <%= partial.title %> # => "Hello there" + # <%= partial.name %> # => "Somebody" + def content_from(partial, *names, **renames) + names.chain(renames).each { |key, new_key = key| public_send new_key, partial.public_send(key).to_s } + end + # Similar to Rails' built-in `content_for` except it defers any block execution # and lets you pass arguments into it, like so: # # # Here we store a block with some eventual content… - # <% partial.content_for :title do |tag| - # <%= tag.h1 %> - # <% end %> + # <% partial.title { |tag| tag.h1 } %> # - # # …which is then invoked with some predefined options later. - # <%= partial.content_for :title, tag.with_options(class: "text-bold") %> - def content_for(name, content = nil, *arguments, &block) - @contents[name].content_for(content, *arguments, &block) + # # …which we can then yield into with some predefined options later. + # <%= partial.title.yield tag.with_options(class: "text-bold") %> + def section(name, content = nil, **options, &block) + section_from(name).then { _1.write?(content, **options, &block) ? nil : _1 } end - def content_for?(name) - @contents[name].content? + def section?(name) + @sections&.dig(name).present? end + alias content_for? section? + def content_for(...) + section(...)&.to_s + end + + def slice(*keys) + keys.index_with { content_for _1 } + end + def capture(*arguments, &block) - self.output_buffer = @view_context.capture(*arguments, self, &block) + @captured_buffer = @view_context.capture(*arguments, self, &block) + end + + private + + def section_from(name) + @sections ||= {} and @sections[name] ||= Section.new(@view_context, @local_assigns&.dig(name)) + end + + def method_missing(meth, *arguments, **keywords, &block) + if @view_context.respond_to?(meth) + @view_context.public_send(meth, *arguments, **keywords, &block) + else + define_accessor meth and public_send(meth, *arguments, **keywords, &block) + end + end + + def define_accessor(name) + name = name.to_s.chomp("?").to_sym + self.class.define_method(name) { |content = nil, **options, &block| section(name, content, **options, &block) } + self.class.define_method("#{name}?") { section?(name) } end end end