liquid-rails icon indicating copy to clipboard operation
liquid-rails copied to clipboard

Layout Tag Implementation

Open RtypeStudios opened this issue 6 years ago • 0 comments

Hi Chamnap,

Thank you for making this library. I have been working with the code and have a working prototype for implementing the layout tag similar to what shopify has: https://help.shopify.com/en/themes/liquid/tags/theme-tags#layout (I doubt my implementation is the same)

My method was this:

  1. Created a simple layout tag:
class Liquid::Tags::LayoutTag < Liquid::Tag

  def initialize(tag_name, markup, tokens)
    super
    @template = markup.delete("\"").strip
  end

  def render(context)
    context.environments.first["layout_override_filename"] = @template
    ""
  end

end
  1. Then extended the view class:
  class TemplateHandler

      def self.call(template)
        "Liquid::Handlers::TemplateHandler.new(self).render(#{template.source.inspect}, local_assigns)"
      end

      def initialize(view)
        @view       = view
        @controller = @view.controller
        @helper     = ActionController::Base.helpers
      end

      def render(template, local_assigns={})

        assigns = if @controller.respond_to?(:liquid_assigns, true)
          @controller.send(:liquid_assigns)
        else
          @view.assigns
        end

        # check if there is a layout override
        layout_override_filename = assigns["layout_override_filename"]

        # if we are processing the layout and there is an override, replace the template with the override content.
        if @view.content_for?(:layout) && !layout_override_filename.nil?
          Rails.logger.info "Rendering layout override from #{layout_override_filename}"
          template = read_template_from_file_system(registers, layout_override_filename)
        end

        assigns['content_for_layout'] = @view.content_for(:layout) if @view.content_for?(:layout)
        assigns.merge!(local_assigns.stringify_keys)

        liquid      = Liquid::Template.parse(template)
        liquid.send(render_method, assigns, filters: filters, registers: registers).html_safe
      end

      def filters
        if @controller.respond_to?(:liquid_filters, true)
          @controller.send(:liquid_filters)
        else
          [@controller._helpers]
        end
      end

      def registers
        {
          view: @view,
          controller: @controller,
          helper: @helper,
          file_system: Liquid::Handlers::FileSystem.new(@view)
        }
      end

      def compilable?
        false
      end

      def render_method
        (::Rails.env.development? || ::Rails.env.test?) ? :render! : :render
      end

      def read_template_from_file_system(registers, name)
        file_system = registers[:file_system] || Liquid::Template.file_system
        file_system.read_template_file(name)
      end

    end

The template handler then replaces the view with the select template when rendering occurs. This was really just a proof of concept. If I clean this up and submit it as a pull request would that be useful to you or others do you think?

Any suggestions on improvements? Liquid is very new to me and I had troubles finding a good way to pass the template override through and ended up using the environment.

RtypeStudios avatar Jul 23 '18 08:07 RtypeStudios