# frozen_string_literal: true require 'sinatra/base' require 'sinatra/capture' module Sinatra # = Sinatra::ContentFor # # Sinatra::ContentFor is a set of helpers that allows you to capture # blocks inside views to be rendered later during the request. The most # common use is to populate different parts of your layout from your view. # # The currently supported engines are: Erb, Erubi, Haml and Slim. # # == Usage # # You call +content_for+, generally from a view, to capture a block of markup # giving it an identifier: # # # index.erb # <% content_for :some_key do %> # ... # <% end %> # # Then, you call +yield_content+ with that identifier, generally from a # layout, to render the captured block: # # # layout.erb # <%= yield_content :some_key %> # # If you have provided +yield_content+ with a block and no content for the # specified key is found, it will render the results of the block provided # to yield_content. # # # layout.erb # <% yield_content :some_key_with_no_content do %> # ... # <% end %> # # === Classic Application # # To use the helpers in a classic application all you need to do is require # them: # # require "sinatra" # require "sinatra/content_for" # # # Your classic application code goes here... # # === Modular Application # # To use the helpers in a modular application you need to require them, and # then, tell the application you will use them: # # require "sinatra/base" # require "sinatra/content_for" # # class MyApp < Sinatra::Base # helpers Sinatra::ContentFor # # # The rest of your modular application code goes here... # end # # == And How Is This Useful? # # For example, some of your views might need a few javascript tags and # stylesheets, but you don't want to force this files in all your pages. # Then you can put <%= yield_content :scripts_and_styles %> on your # layout, inside the tag, and each view can call content_for # setting the appropriate set of tags that should be added to the layout. # # == Limitations # # Due to the rendering process limitation using <%= yield_content %> # from within nested templates do not work above the <%= yield %> statement. # For more details https://github.com/sinatra/sinatra-contrib/issues/140#issuecomment-48831668 # # # app.rb # get '/' do # erb :body, :layout => :layout do # erb :foobar # end # end # # # foobar.erb # <% content_for :one do %> # # <% end %> # <% content_for :two do %> # # <% end %> # # Using <%= yield_content %> before <%= yield %> will cause only the second # alert to display: # # # body.erb # # Display only second alert # <%= yield_content :one %> # <%= yield %> # <%= yield_content :two %> # # # body.erb # # Display both alerts # <%= yield %> # <%= yield_content :one %> # <%= yield_content :two %> # module ContentFor include Capture # Capture a block of content to be rendered later. For example: # # <% content_for :head do %> # # <% end %> # # You can also pass an immediate value instead of a block: # # <% content_for :title, "foo" %> # # You can call +content_for+ multiple times with the same key # (in the example +:head+), and when you render the blocks for # that key all of them will be rendered, in the same order you # captured them. # # Your blocks can also receive values, which are passed to them # by yield_content def content_for(key, value = nil, options = {}, &block) block ||= proc { |*| value } clear_content_for(key) if options[:flush] content_blocks[key.to_sym] << capture_later(&block) end # Check if a block of content with the given key was defined. For # example: # # <% content_for :head do %> # # <% end %> # # <% if content_for? :head %> # content "head" was defined. # <% end %> def content_for?(key) content_blocks[key.to_sym].any? end # Unset a named block of content. For example: # # <% clear_content_for :head %> def clear_content_for(key) content_blocks.delete(key.to_sym) if content_for?(key) end # Render the captured blocks for a given key. For example: # # # Example # <%= yield_content :head %> # # # Would render everything you declared with content_for # :head before closing the tag. # # You can also pass values to the content blocks by passing them # as arguments after the key: # # <%= yield_content :head, 1, 2 %> # # Would pass 1 and 2 to all the blocks registered # for :head. def yield_content(key, *args, &block) if block_given? && !content_for?(key) haml? && Tilt[:haml] == Tilt::HamlTemplate && respond_to?(:capture_haml) ? capture_haml(*args, &block) : yield(*args) else content = content_blocks[key.to_sym].map { |b| capture(*args, &b) } content.join.tap do |c| if block_given? && (erb? || erubi?) @_out_buf << c end end end end private def content_blocks @content_blocks ||= Hash.new { |h, k| h[k] = [] } end end helpers ContentFor end