#
# This module provides several helper methods for rendering the CMS contents and enabling the
# in-place editing.
#
# @api public
#
module ScrivitoHelper
include Scrivito::ControllerHelper
#
# Renders a field within the given HTML tag.
#
# This method also renders additional attributes, which are needed for in-place editing.
# These attributes are only rendered when appropriate, i.e. not for a regular visitor.
#
# The helper is similar to (and internally uses)
# http://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-content_tag.
# You can add additional HTML attributes by passing them in +html_options+.
#
# @api public
#
# @note If the param +field_name+ is of type +widget+, then +tag_name+ must be a block tag,
# like +div+ or +h1+. An inline tag like +p+ or +span+ could result in broken HTML output, since
# the widgets are rendered within block tags.
#
# @param tag_name [String, Symbol] Name of the HTML tag (e.g. +:h1+ or +:div+).
# If the param +field_name+ is of type +widget+, then +tag_name+ must be a block tag,
# like +div+ or +h1+. An inline tag like +p+ or +span+ could result in broken HTML output, since
# the widgets are rendered within block tags.
# @param obj_or_widget [Scrivito::BasicObj, Scrivito::BasicWidget] A {Scrivito::BasicObj}
# or {Scrivito::BasicWidget} from which the +field_name+ is read.
# @param field_name [String, Symbol] Which field of the Obj should be rendered.
# @param html_options [Hash] HTML options to be passed to +content_tag+.
#
# @param editing_options [Hash] Additional editing options for widgets (e.g. +:inner_tag+)
# @option editing_options [Symbol] :inner_tag Wraps widgets inside specified tag
# @option editing_options [String, Symbol] :editor Name of the JavaScript editor to be used for
# this field. Normally, the name of the editor to be used for a field is determined by the
# +scrivito.select_editor+ JavaScript API. The option +:editor+ should only be used if setting
# the editor via the JavaScript API is not feasible. Specifying +editor: false+ disables
# in-place editing.
# @option editing_options [Boolean] :disable_margins If +true+, no margins are attached to the
# widgets contained in a +widgetlist+ attribute. In this case, the in-place editing controls of
# widgets may overlap which can be handled by using suitable CSS. Defaults to +false+.
#
# @param block [Proc] Optional block to render inner HTML. If none given the value of attribute
# will be rendered instead. +block+ is not allowed for fields of type +widget+.
#
# @raise ArgumentError if the field behind +field_name+ is of type +widget+ and a +block+ is given.
#
# @return [String] The rendered HTML tag.
#
# @example Renders an
tag containing the text of the +headline+ attribute of +@obj+ and assigns the tag a css class called +very_important+
# scrivito_tag(:h2, @obj, :headline, class: "very_important")
#
# @example Renders an tag containing a truncated +headline+ of the +widget+
# scrivito_tag(:h2, widget, :headline) do
# truncate(widget.headline)
# end
#
# @example Render a download link for a PDF document
# scrivito_tag(:div, @obj, :pdf) do
# if @obj.pdf
# "Download: #{link_to(@obj.pdf.filename, @obj.pdf.url)}"
# elsif scrivito_user
# "Drop PDF here to upload"
# end
# end
#
# @example Render widgetlist inside an +ul+ tag with the individual widgets inside of +li+ tags.
# scrivito_tag(:ul, @obj, :body, {}, inner_tag: :li)
#
def scrivito_tag(tag_name, obj_or_widget, field_name,
html_options = {}, editing_options = {}, &block)
Scrivito::CmsFieldTag.new(self, tag_name, obj_or_widget, editing_options.merge(
widget_render_context: @scrivito_widget_render_context,
field_name: field_name.to_s
)).render(html_options, &block)
end
#
# Renders a navigation ready for in-place editing.
#
# @api public
#
# If a navigation is rendered using this helper method, a special menu is attached to it that lets
# you change the order of the child pages or insert a new child.
#
# For making the child pages sortable, the parent {Scrivito::BasicObj Obj} requires a
# +referencelist+ attribute named +child_order+. When creating a page model using the page
# generator, such an attribute is automatically added to the model.
#
# @param tag_name [String, Symbol] Name of the outer HTML tag (e.g. +:ul+ or +:div+).
# @param obj [Scrivito::BasicObj] The parent {Scrivito::BasicObj Obj}, on which +method_name+ will
# be called in order to fetch the children.
# @param method_name [String, Symbol] Name of the method to be called on the parent
# {Scrivito::BasicObj Obj} in order to fetch the children. Currently, only +toclist+ is
# supported.
# @param options [Hash] Options to be passed to +content_tag+. Use them to add HTML attributes to
# the tag.
#
# @yieldparam [Scrivito::ChildListTag::ObjTag] list An object on which the
# {Scrivito::ChildListTag::ObjTag#tag tag} method should be called once per child to enable
# in-place editing.
# @yieldparam [Scrivito::BasicObj] child Each child of +toclist+.
#
# @return [String] The rendered HTML string.
#
# @see Scrivito::ChildListTag::ObjTag#tag
#
# @example Renders a navigation for an +@obj+ with two children in its +toclist+, whose titles are +"Toys"+ and +"Electronics"+ respectively:
# scrivito_tag_list(:ul, @obj, :toclist, class: "my_list") do |list, child|
# list.tag(:li, class: "my_list_element") do
# link_to(scrivito_path(child)) do
# scrivito_tag(:span, child, :title)
# end
# end
# end
#
# #
# # Basically, the rendered HTML will look like this:
# #
# #
# #
#
def scrivito_tag_list(tag_name, obj, method_name, options = {}, &block)
Scrivito::ChildListTag.new(tag_name, obj, method_name.to_s, self).render(options, &block)
end
#
# Calculates an HTML image tag of an image stored in the CMS for inplace editing.
#
# @api public
#
# @note If you do not specify an HTML +alt+ attribute, the helper method will use
# {Scrivito::BasicObj#alt_description Obj#alt_description} of the target object.
#
# @param [Obj] obj Obj with a +link_list+, +reference+, +link+ or +binary+ attribute
# @param [String, Symbol, Hash] field_name_or_tag_options Name of +link_list+, +reference+, +link+, or +binary+
# attribute, which contains the image or additional HTML attributes for the tag.
# The +field_name+ can be omitted for binary Objs and +blob+ will be used as default.
# @param [Hash] tag_or_editing_options Additional HTML attributes for the tag or
# the editing options if no +field_name+ was given
# @param [Hash] editing_options Additional options for inplace editing
#
# @option editing_options [String] :placeholder ('scrivito/image_placeholder.gif') URL
# or path to image to be displayed if target is missing
#
# @option editing_options [Hash] :transform if set, the displayed image will be transformed using
# the definition in the given hash, see {Scrivito::Binary#transform}.
#
# @example
# scrivito_image_tag(@obj, :my_linklist)
# scrivito_image_tag(@obj, :my_linklist, alt: 'Interesting picture', class: 'my_image')
# scrivito_image_tag(@obj, :my_linklist, {}, placeholder: image_path('my_placeholder.png'))
# scrivito_image_tag(@obj, :my_linklist, {class: 'my_image'}, placeholder: 'http://placehold.it/350x150')
#
# @example Render an image tag for a reference attribute.
# scrivito_image_tag(@obj, :my_reference)
#
# @example Render an image tag for a link attribute.
# scrivito_image_tag(@obj, :my_link)
#
# @example Render an image tag for a binary attribute
# scrivito_image_tag(@obj, :my_binary)
#
# @example Render an image tag for a binary Obj
# scrivito_image_tag(@image)
#
# @example Render an image tag with an on-the-fly calculated thumbnail
# scrivito_image_tag @obj, :my_binary, {}, transform: {width: 50, height: 50}
#
# @return [String] HTML image tag
#
# @raise [ScrivitoError] if +field_name+ is not set and Obj is not binary
#
def scrivito_image_tag(obj, field_name_or_tag_options=nil,
tag_or_editing_options = {}, editing_options = {})
field_name, tag_options, editing_options =
if field_name_or_tag_options.is_a?(Hash)
[nil, field_name_or_tag_options, tag_or_editing_options]
else
[field_name_or_tag_options, tag_or_editing_options, editing_options]
end
if field_name.blank?
if obj.binary?
field_name = :blob
else
raise Scrivito::ScrivitoError,
"when omitting `field_name' you have to pass a binary obj"
end
end
options = Scrivito::ImageTag.new(self).options(obj, field_name,
tag_options.with_indifferent_access, editing_options.with_indifferent_access)
scrivito_tag('img', obj, field_name, options)
end
#
# Renders an attribute value of a CMS model, taking its type into account.
#
# @api public
#
# Links inside +html+ attributes are rendered using the routing of the application. Values from
# +string+ attributes are escaped. For other attribute types, a simple default representation is
# rendered.
#
# @example
# scrivito_value(@obj.title)
#
# @note Content rendered using this method will not be editable in the Scrivito UI. If you want
# in-place editing, use {ScrivitoHelper#scrivito_tag} instead.
#
def scrivito_value(value)
renderer = Scrivito::AttributeValueRenderer.new(self)
case value
when Scrivito::BasicWidget then render(template: value.show_view_path, locals: {widget: value})
when Scrivito::HtmlString then renderer.render_as_html(value)
when String then renderer.render_as_string(value)
when Time then renderer.render_as_date(value)
else value
end
end
#
# Renders a field from the CMS.
#
# @api public
#
# @note Content rendered using this method will not be editable in the Scrivito UI.
# If you want in-place editing, then please use {ScrivitoHelper#scrivito_tag} instead.
#
# @param obj [Scrivito::BasicObj] an +Obj+, whose field should be rendered.
# @param field_name [String, Symbol] the field of +obj+ to be rendered.
#
# @example
# scrivito_field(@obj, :title)
# scrivito_field(@obj, 'headline')
#
def scrivito_field(obj, field_name)
scrivito_value(obj[field_name])
end
#
# Thumbnail helper generates HTML for the page class selection dialog and the widget class selection dialog.
# The generated HTML has appropriate DOM structure and CSS classes, which are compatible with the CSS of the SDK.
# By using this helper you ensure, that the look of your thumbnails fits into the SDK's design.
#
# @api public
#
# @param title [String] title of the thumbnail, e.g. "Content Page" or "Image Widget".
# @param icon [Symbol, String] icon of the thumbnail.
# You can use one of the built-in icons or specify your own HTML.
# There are following built-in icons: +:content+, +:headline+, +:image+, +:scrivito+ and +:text+.
# If the name of the specified icon is unknown, then the default icon (+:scrivito+) will be displayed.
# If you are specifying your own HTML string, then make sure it is HTML safe.
# @param block [Proc] the description of the thumbnail.
#
# @example A simple thumbnail
# scrivito_thumbnail('Content Page') do
# 'A content page.'
# end
#
# @example A thumbnail with a built-in icon
# scrivito_thumbnail('Image Widget', :image) do
# 'An image widget.'
# end
#
# @example A thumbnail with custom icon HTML
# scrivito_thumbnail('Blog', content_tag(:i, '', class: 'thumbnail-blog')) do
# 'A blog post.'
# end
#
def scrivito_thumbnail(title, icon = :scrivito, &block)
if icon.is_a?(Symbol)
icon_code = {
content: '',
headline: '',
image: '',
scrivito: '',
text: '',
}.fetch(icon, '')
icon = content_tag(:i, icon_code.html_safe, class: 'scrivito_icon')
end
content_tag(:div, 'data-scrivito-private-thumbnail-title' => title) do
capture do
concat content_tag(:div, icon, class: 'scrivito_editing_widget_visualization')
concat content_tag(:div, title, class: 'scrivito_editing_widget_title')
if block_given?
concat content_tag(:div, class: 'scrivito_editing_widget_description', &block)
else
concat content_tag(:div, nil, class: 'scrivito_editing_widget_description')
end
end
end
end
#
# Attribute group helper generates HTML for page details dialog and widget details dialog.
# The generated HTML has appropriate DOM structure and CSS classes, which are compatible with the CSS of the SDK.
# By using this helper you ensure, that the look of your attribute groups fits into the SDK's design.
#
# @api public
#
# @param title [String] title of the attribute group.
# @param block [Proc] content of the attribute group.
#
# @example
# scrivito_details_for('Title and Category') do
# concat scrivito_tag(:div, @obj, :title)
# concat scrivito_tag(:div, @obj, :category)
# end
#
# scrivito_details_for do
# scrivito_tag(:div, @obj, :abstract)
# end
#
def scrivito_details_for(title = nil, &block)
content_tag(:div, class: 'scrivito_content_group') do
capture do
if title
concat content_tag(:h3, title)
end
concat capture(&block)
end
end
end
# @!group Details Dialog Size
# Set the size of the page and widget details dialog to +large+.
# @example
# scrivito_large_dialog do
# scrivito_details_for('Title and Category') do
# concat scrivito_tag(:div, @obj, :title)
# concat scrivito_tag(:div, @obj, :category)
# end
# end
# @param block [Proc] Block to render inner HTML.
# @return [String]
# @api public
def scrivito_large_dialog(&block)
Scrivito::DialogSizeHelper.render_dialog_with_size(self, 'large', &block)
end
# Set the size of the page and widget details dialog to +medium+ (default).
# @example
# scrivito_medium_dialog do
# # see scrivito_large_dialog example
# end
# @param block [Proc] Block to render inner HTML.
# @return [String]
# @api public
def scrivito_medium_dialog(&block)
Scrivito::DialogSizeHelper.render_dialog_with_size(self, 'medium', &block)
end
# Set the size of the page and widget details dialog to +small+.
# @example
# scrivito_small_dialog do
# # see scrivito_large_dialog example
# end
# @param block [Proc] Block to render inner HTML.
# @return [String]
# @api public
def scrivito_small_dialog(&block)
Scrivito::DialogSizeHelper.render_dialog_with_size(self, 'small', &block)
end
# @!endgroup
#
# Renders all tags needed in the HTML head.
# @api public
#
def scrivito_head_tags
layout_tags = Scrivito::LayoutTags.new(self)
capture do
concat layout_tags.editing_auth_warning
concat layout_tags.generator_meta_tag
if scrivito_user
concat javascript_include_tag('scrivito_ui_redirect')
end
end
end
#
# Renders all tags needed in the HTML body.
# @api public
#
def scrivito_body_tags
if scrivito_user
Scrivito::LayoutTags.new(self).page_config(@obj)
end
end
{
'cms_image_tag' => 'scrivito_image_tag',
'cms_path' => 'scrivito_path',
'cms_tag' => 'scrivito_tag',
'cms_tag_list' => 'scrivito_tag_list',
'cms_url' => 'scrivito_url',
'display_field' => 'scrivito_field',
'display_value' => 'scrivito_value',
'render_widget' => 'scrivito_value',
'scrivito_after_content_tags' => 'scrivito_body_tags',
'scrivito_header_tags' => 'scrivito_head_tags',
}.each_pair do |old_method_name, new_method_name|
define_method(old_method_name) do |*args|
raise %{
The helper method `#{old_method_name}' has been removed.
Please use `#{new_method_name}' instead.
}
end
end
def inplace_editing_allowed?
raise %{
The helper method `inplace_editing_allowed?' has been removed.
Please use helper method `scrivito_user` instead.
}
end
# @api public
#
# This method wraps Rails' built-in fragment caching to work with content delivered by Scrivito.
# Fragment caching applies to computed parts of views and helps to improve performance. The
# Rails guides provide an excellent introduction to caching if you haven't used it yet.
#
# The +scrivito_cache+ method extends built-in fragment caching so that cached
# parts of a view are automatically recomputed when Scrivito content changes. The
# fragments are only cached for the published content. In editable working copies
# no caching takes place, and the fragments are computed for every request.
#
# @param key [String] a name describing the data to be cached.
# @param options [Hash] a hash that enables further configuration of the fragment
# cache. The options are passed to the +cache+ helper of Rails. See the Rails
# {http://api.rubyonrails.org/classes/ActionView/Helpers/CacheHelper.html#method-i-cache documentation}
# for details.
def scrivito_cache(key, options=nil, &block)
workspace = Scrivito::Workspace.current
cache_if(workspace.published? && !scrivito_user, [workspace.cache_key, key], options, &block)
end
#
# @api public
#
# This helper can be used in the +show.html.*+ template of widgets. It allows you to specify
# attributes (including CSS classes) for the tag enclosing the widget.
#
# @param [Hash] html_options attributes for this tag.
#
# @example It should be wrapped around the entire template like this:
# <%= scrivito_widget_tag class: 'my_class', data-very-funky: true do %>
# <%= scrivito_image_tag widget, :image %>
# <% end %>
#
def scrivito_widget_tag(html_options, &block)
capture(&block).tap do
@scrivito_widget_tag_html_options = html_options
end
end
#
# The +scrivito_backlinks+ helper generates HTML markup for the list of
# {Scrivito::Associations#backlinks backlinks} of a given CMS object.
#
# @api public
#
# The helper is intended to be used on details views. The DOM structure it generates and the CSS
# classes it uses are compatible with the CSS of the SDK. This helper ensures that backlink lists
# fit into the design the SDK applies.
#
# @param obj [Scrivito::BasicObj] an +Obj+ whose backlinks should be rendered.
#
# @example
# scrivito_details_for "Backlinks:" do
# scrivito_backlinks @obj
# end
#
# @see Scrivito::Associations#backlinks
#
def scrivito_backlinks(obj)
content_tag(:div, class: 'scrivito_backlinks_list') do
backlinks = obj.backlinks.to_a
if backlinks.any?
content_tag(:ul) do
capture do
backlinks.each do |o|
concat(content_tag(:li) {
concat(content_tag(:i, nil, class: 'scrivito_icon scrivito_icon_link'))
concat(link_to(o.description_for_editor, scrivito_path(o), target: :_blank))
})
end
end
end
end
end
end
end