require 'hanami/view/rendering/registry' require 'hanami/view/rendering/scope' require 'hanami/view/rendering/null_local' module Hanami module View # Rendering methods # # @since 0.1.0 # # @see Hanami::View::Rendering::InstanceMethods module Rendering # @since 0.1.0 # @api private def self.extended(base) base.class_eval do include InstanceMethods end end module InstanceMethods # Initialize a view # # @param template [Hanami::View::Template] the template to render # @param locals [Hash] a set of objects available during the rendering # process. # # @since 0.1.0 # # @see Hanami::View::Template # # @example # require 'hanami/view' # # class IndexView # include Hanami::View # end # # template = Hanami::View::Template.new('index.html.erb') # view = IndexView.new(template, {article: article}) def initialize(template, **locals) @template = template @locals = locals @scope = Scope.new(self, @locals) end # Render the template by bounding the local scope. # If it uses a layout, it renders the template first and then the # control passes to the layout. # # Override this method for custom rendering policies. # For instance, when a serializer is used and there isn't the need of # a template. # # @return [String] the output of the rendering process # # @raise [Hanami::View::MissingTemplateError] if the template is nil # # @since 0.1.0 # # @see Hanami::View::Layout # # @example with template # require 'hanami/view' # # class IndexView # include Hanami::View # end # # template = Hanami::View::Template.new('index.html.erb') # view = IndexView.new(template, {article: article}) # # view.render # =>

Introducing Hanami::view

... # # @example with template and layout # require 'hanami/view' # # class ApplicationLayout # include Hanami::View::Layout # end # # class IndexView # include Hanami::View # layout :application # end # # template = Hanami::View::Template.new('index.html.erb') # view = IndexView.new(template, {article: article}) # # view.render # => ...

Introducing Hanami::view

... # # @example with custom rendering # require 'hanami/view' # # class IndexView # include Hanami::View # # def render # ArticleSerializer.new(article).render # end # end # # view = IndexView.new(nil, {article: article}) # # view.render # => {title: ...} def render layout.render end # It tries to invoke a method for the view or a local for the given key. # If the lookup fails, it returns a null object. # # @return [Object,Hanami::View::Rendering::NullLocal] the returning value # # @since 0.7.0 # # @example # <% if local(:plan).overdue? %> #

Your plan is overdue.

# <% end %> def local(key) if respond_to?(key) __send__(key) else locals.fetch(key) { NullLocal.new(key) } end end protected # The output of the template rendering process. # # @return [String] the rendering output # # @raise [Hanami::View::MissingTemplateError] if the template is nil # # @api private # @since 0.1.0 def rendered template.render @scope end # The layout. # # @return [Class, Hanami::View::Rendering::NullLayout] # # @see Hanami::View::Layout # @see Hanami::View.layout # @see Hanami::View::Dsl#layout # # @api private # @since 0.1.0 def layout @layout ||= self.class.layout.new(@scope, rendered) end # The template. # # @return [Hanami::View::Template] the template # # @raise [Hanami::View::MissingTemplateError] if the template is nil # # @api private # @since 0.1.0 def template @template or raise MissingTemplateError.new(self.class.template, @scope.format) end # A set of objects available during the rendering process. # # @return [Hash] # # @see Hanami::View#initialize # # @api private # @since 0.1.0 def locals @locals end # Delegates missing methods to the scope. # # @see Hanami::View::Rendering::Scope # # @api private # @since 0.1.0 # # @example # require 'hanami/view' # # class IndexView # include Hanami::View # end # # template = Hanami::View::Template.new('index.html.erb') # view = IndexView.new(template, {article: article}) # # view.article # => # def method_missing(m) @scope.__send__ m end end # Render the given context and locals with the appropriate template. # If there are registered subclasses, it choose the right class, according # to the requested format. # # @param context [Hash] the context for the rendering process # @option context [Symbol] :format the requested format # # @return [String] the output of the rendering process # # @raise [Hanami::View::MissingTemplateError] if it can't find a template # for the given context # # @raise [Hanami::View::MissingFormatError] if the given context doesn't # have the :format key # # @since 0.1.0 # # @see Hanami::View#initialize # @see Hanami::View#render # # @example # require 'hanami/view' # # article = OpenStruct.new(title: 'Hello') # # module Articles # class Show # include Hanami::View # # def title # @title ||= article.title.upcase # end # end # # class JsonShow < Show # format :json # # def title # super.downcase # end # end # end # # Hanami::View.root = '/path/to/templates' # Hanami::View.load! # # Articles::Show.render(format: :html, article: article) # # => renders `articles/show.html.erb` # # Articles::Show.render(format: :json, article: article) # # => renders `articles/show.json.erb` # # Articles::Show.render(format: :xml, article: article) # # => raises Hanami::View::MissingTemplateError def render(context) registry.resolve(context).render end protected # Loading mechanism hook. # # @api private # @since 0.1.0 # # @see Hanami::View.load! def load! super load_registry! end private # The registry that holds all the registered subclasses. # # @api private # @since 0.1.0 # # @see Hanami::View::Rendering::Registry def registry @registry ||= Registry.new(self) end # @api private def load_registry! @registry = nil registry.freeze end end end end