Browse files

Added feature to render page content within the context of a vew (tem…

…plate)

Added render to page, which takes a view and renders all subsequent tags in that view. This feature allows you to create Tags that will render partials or templates in the context of the controller. That is with access to all helpers and instance variables of the controller.
  • Loading branch information...
1 parent 605a45f commit 10c554dd4473d35f4c10c35d23eb8fb72dc25f6e @polar committed Jun 5, 2012
View
7 app/controllers/cms_admin/preview_controller.rb
@@ -10,7 +10,12 @@ def preview
@cms_site = @page.site
@cms_layout = @page.layout
@cms_page = @page
- render :inline => @page.content(true), :layout => layout
+ begin
+ render :inline => @page.content(true), :layout => layout
+ rescue => boom
+ render :text => "Page cannot be previewed due to tags that need page context."
+ end
+
end
protected
View
4 app/controllers/cms_content_controller.rb
@@ -1,5 +1,7 @@
class CmsContentController < CmsBaseController
-
+
+ helper ComfortableMexicanSofa.config.preview_helpers
+
# Authentication module must have #authenticate method
include ComfortableMexicanSofa.config.public_auth.to_s.constantize
View
27 lib/comfortable_mexican_sofa/backend/active_record/models/page.rb
@@ -186,6 +186,33 @@ def content(force_reload = false)
end
end
+ # This method renders the content of the page within the ActionView. It has access
+ # to all controller variables, routes, and helpers, which allows the tags to render
+ # in that context. You may render directly in an erb file like so:
+ # <%= page.render(self, :status => 200) %>
+ # If the page layout has an app_layout content will be rendered in that layout.
+ # Note, that in this situation, if you set a layout in your controller, it will render
+ # this content within that layout. You should rendering the page with an empty layout.
+ #
+ def render(view, options)
+ self.tags = [] # resetting
+ if layout
+ app_layout = (layout.app_layout.blank? || view.request.xhr?) ? false : layout.app_layout
+
+ content = ComfortableMexicanSofa::Tag.render_in_view(
+ self,
+ view,
+ ComfortableMexicanSofa::Tag.sanitize_irb(layout.merged_content)
+ )
+ options.merge!({ :inline => content,
+ :layout => "layouts/#{app_layout}"
+ })
+ view.render options
+ else
+ ''
+ end
+ end
+
def content=(value)
self.content_cache = value
self.content_dirty = false
View
29 lib/comfortable_mexican_sofa/backend/mongo_mapper/models/page.rb
@@ -161,7 +161,7 @@ def self.options_for_select(site, page = nil, current_page = nil, depth = 0, exc
return [] if (current_page ||= site.pages.roots.first) == page && exclude_self || !current_page
out = []
out << [ "#{spacer*depth}#{current_page.label}", current_page.id ] unless current_page == page
- current_page.children.each do |child|
+ current_page.children.all.each do |child|
out += options_for_select(site, page, child, depth + 1, exclude_self, spacer)
end
return out.compact
@@ -187,6 +187,33 @@ def content(force_reload = false)
end
end
+ # This method renders the content of the page within the ActionView. It has access
+ # to all controller variables, routes, and helpers, which allows the tags to render
+ # in that context. You may render directly in an erb file like so:
+ # <%= page.render(self, :status => 200) %>
+ # If the page layout has an app_layout content will be rendered in that layout.
+ # Note, that in this situation, if you set a layout in your controller, it will render
+ # this content within that layout. You should rendering the page with an empty layout.
+ #
+ def render(view, options)
+ self.tags = [] # resetting
+ if layout
+ app_layout = (layout.app_layout.blank? || view.request.xhr?) ? false : layout.app_layout
+
+ content = ComfortableMexicanSofa::Tag.render_in_view(
+ self,
+ view,
+ ComfortableMexicanSofa::Tag.sanitize_irb(layout.merged_content)
+ )
+ options.merge!({ :inline => content,
+ :layout => "layouts/#{app_layout}"
+ })
+ view.render options
+ else
+ ''
+ end
+ end
+
def content=(value)
@content_cache = value
@content_dirty = false
View
48 lib/comfortable_mexican_sofa/tag.rb
@@ -48,7 +48,8 @@ def initialize_tag(page, tag_signature)
end
module InstanceMethods
-
+ # ActionView is the context of the rendering the tag in render_in_view.
+ attr_accessor :view
# String identifier of the tag
def id
"#{self.class.to_s.demodulize.underscore}_#{self.identifier}"
@@ -76,8 +77,12 @@ def content
# Content that is used during page rendering. Outputting existing content
# as a default.
def render
- ignore = [ComfortableMexicanSofa::Tag::Partial, ComfortableMexicanSofa::Tag::Helper].member?(self.class)
- ComfortableMexicanSofa::Tag.sanitize_irb(content, ignore)
+ if view
+ view.render :inline => content
+ else
+ ignore = [ComfortableMexicanSofa::Tag::Partial, ComfortableMexicanSofa::Tag::Helper].member?(self.class)
+ ComfortableMexicanSofa::Tag.sanitize_irb(content, ignore)
+ end
end
# Find or initialize Cms::Block object
@@ -105,27 +110,58 @@ def self.initialize_tag(page, tag_signature)
tag_classes.find{ |c| tag_instance = c.initialize_tag(page, tag_signature) }
tag_instance
end
-
+
# Scanning provided content and splitting it into [tag, text] tuples.
# Tags are processed further and their content is expanded in the same way.
# Tags are defined in the parent tags are ignored and not rendered.
def self.process_content(page, content = '', parent_tag = nil)
tokens = content.to_s.scan(TOKENIZER_REGEX)
tokens.collect do |tag_signature, text|
if tag_signature
+ begin
+ if tag = self.initialize_tag(page, tag_signature)
+ tag.parent = parent_tag if parent_tag
+ if tag.ancestors.select{|a| a.id == tag.id}.blank?
+ page.tags << tag
+ self.process_content(page, tag.render, tag)
+ end
+ end
+ rescue => boom
+ # This probably is caused by a tag that needs a view down
+ # somewhere, but this will cause the tests to work.
+ logger.detailed_error(boom)
+ text
+ end
+
+ else
+ text
+ end
+ end.join('')
+ end
+
+
+ # Scanning provided content and splitting it into [tag, text] tuples.
+ # Tags are processed further and their content is expanded in the same way.
+ # Tags are defined in the parent tags are ignored and not rendered.
+ # Tags are rendered in the context of the given ActionView.
+ def self.render_in_view(page, view, content = '', parent_tag = nil)
+ tokens = content.to_s.scan(TOKENIZER_REGEX)
+ tokens.collect do |tag_signature, text|
+ if tag_signature
if tag = self.initialize_tag(page, tag_signature)
+ tag.view = view
tag.parent = parent_tag if parent_tag
if tag.ancestors.select{|a| a.id == tag.id}.blank?
page.tags << tag
- self.process_content(page, tag.render, tag)
+ self.render_in_view(page, view, tag.render, tag)
end
end
else
text
end
end.join('')
end
-
+
# Cleaning content from possible irb stuff. Partial and Helper tags are OK.
def self.sanitize_irb(content, ignore = false)
if ComfortableMexicanSofa.config.allow_irb || ignore
View
48 test/functional/trials_controller_test.rb
@@ -0,0 +1,48 @@
+require File.expand_path('../test_helper', File.dirname(__FILE__))
+require File.expand_path("../test_app/config/application", File.dirname(__FILE__))
+
+class TrialsControllerTest < ActionController::TestCase
+
+ # Tests the basic functionality of the application
+ def test_basic_functionality
+ @routes = Rails.application.routes
+ get :show
+ assert assigns(:trial)
+ assert_equal "world!", assigns(:trial).hello
+ assert_response 200
+ assert_equal assigns(:trial).hello, response.body
+ end
+
+ # Tests the rendering of the controller view context through the page pointed to by the index template
+ def test_index_renders_partial_index_via_cms_page
+ @routes = Rails.application.routes
+
+ site = cms_sites(:default)
+
+ # This layout renders the "content" block of a page as is.
+ layout = site.layouts.create!(
+ :identifier => "test",
+ :label => "Test Layout",
+ :app_layout => "application",
+ :content => "{{ cms:page:content }}"
+ )
+
+ # This page renders the TrailTag.
+ # The content block renders partial "trails/_index" through the TrialTag
+ site.pages.create!(
+ :slug => "trials",
+ :layout => layout,
+ :blocks_attributes => [
+ { :identifier => 'content',
+ :content => '{{ cms:trial }}' }
+ ])
+
+ get :index
+ assert assigns(:site)
+ assert_equal site, assigns(:site)
+ assert assigns(:trials)
+ assert_response 200
+ response_string = assigns(:trials).collect {|t| t.hello}.join("")
+ assert_equal response_string, response.body
+ end
+end
View
14 test/test_app/config/application.rb
@@ -0,0 +1,14 @@
+[
+ "test_app/controllers/trials_controller",
+ "test_app/models/trial",
+ "test_app/lib/tags/trial_tag"
+].each do |f|
+ require f
+end
+
+ActionController::Base.prepend_view_path "test/test_app/views"
+
+Rails.application.routes.draw do
+ resources :trials
+ mount ComfortableMexicanSofa::Engine, :at => "/"
+end
View
13 test/test_app/controllers/trials_controller.rb
@@ -0,0 +1,13 @@
+class TrialsController < ActionController::Base
+
+ def index
+ @site = Cms::Site.find_by_identifier("default-site")
+ @trials = []
+ @trials << Trial.new(:hello => "Hello ")
+ @trials << Trial.new(:hello => "World!")
+ end
+
+ def show
+ @trial = Trial.new(:hello => "world!")
+ end
+end
View
28 test/test_app/lib/tags/trial_tag.rb
@@ -0,0 +1,28 @@
+class TrialTag
+ include ComfortableMexicanSofa::Tag
+
+ # We use the identifier differently here that in the other CMS Tags, Those tags
+ # creates a block with the identifier matching by this expression. It does that
+ # by magic calling "block". The "block" method does not seem to get called
+ # anywhere else in CMS, other than tests, so it's probably safe to do this.
+ # As long as we don't cause the 'block' method to be called then a page block
+ # will not get created with the matched identifier.
+ def self.regex_tag_signature(identifier = nil)
+ identifier ||= /[\w\/\-]+/
+ # Need to make sure that the identifier is match[1] using (?:xxx) to avoid capture.
+ /\{\{\s*cms:trial(?::(#{identifier}))?\s*\}\}/
+ end
+
+ def content
+ case identifier
+ when "name"
+ @trail.name
+ when "page"
+ "<%= render :partial => 'trials/index' %>"
+ when nil
+ "<%= render :partial => 'trials/index' %>"
+ else
+ "<%= render :partial => 'trials/#{identifier}' %>"
+ end
+ end
+end
View
11 test/test_app/models/trial.rb
@@ -0,0 +1,11 @@
+class Trial
+ extend ActiveModel::Serialization
+ def hello
+ @attributes[:hello]
+ end
+
+ attr_accessor :attributes
+ def initialize(attributes)
+ @attributes = attributes
+ end
+end
View
1 test/test_app/views/layouts/application.html.erb
@@ -0,0 +1 @@
+<%= yield %>
View
5 test/test_app/views/trials/_index.html.erb
@@ -0,0 +1,5 @@
+<%
+ # This is a partial that will be rendered by the
+ # {{ cms:test }} tag
+%>
+<%= (@trials.collect {|t| t.hello}).join("") %>
View
10 test/test_app/views/trials/index.html.erb
@@ -0,0 +1,10 @@
+<%=
+ begin
+ site = @site
+ page = site.pages.find_by_full_path("/trials")
+ page.render(self, :status => 200, :content_type => 'text/html')
+ rescue Exception => boom
+ logger.detailed_error(boom)
+ render :text => I18n.t('cms.content.page_not_found'), :status => 404
+ end
+%>
View
1 test/test_app/views/trials/show.html.erb
@@ -0,0 +1 @@
+<%= @trial.hello %>

0 comments on commit 10c554d

Please sign in to comment.