module RailsConnector
# This module contains helpers that can be used to build markers for the preview.
#
# The helpers will not render anyhing if not in editor mode!
# See RailsConnector::Configuration for information on modes.
#
# All helpers have the following options, unknown options are passed
# to the HTML element which is being created:
#
# [:context] the context object that will be shown in the preview after editing
# [:size] the symbolic size of the target window to open
# [:target] the target frame in which the edit window is to be opened.
#
# You can specify the size of the edit window by means of the :size option:
#
# ['n'] normal browser window
# ['udl'] undecorated large browser window
# ['uds'] undecorated small browser window
# ['udt'] undecorated tiny browser window
#
# The :target option becomes the target attribute of the tag.
module MarkerHelper
# Renders an edit marker for a named attribute of an active object in the preview.
# obj is the Obj to edit, attr the attribute as a Symbol or String.
# The helper will render nothing if the object is inactive! The :context defaults to obj.
#
# The following code will render an edit marker for the title attribute of the current object:
#
# <%= edit_marker @obj, :title %>
#
# By passing a block, the evaluated block will be included in the marker:
# <%= edit_marker @obj, :title do |obj, attr|
# display_value obj[attr]
# end %>
#
# The last form is the default of DisplayHelper::display_field without the option :marker => false.
# When the blob evaluates to nil, an empty string or array, the markup of the option :default_value
# will be rendered.
#
# Hint: You should not render edit markers within elements that are hidden via CSS (display:none),
# otherwise they cannot be displayed correctly.
def edit_marker(obj, attr, options = {}, &block)
if Configuration.editor_interface_enabled? && !session[:hide_editmarkers]
context = (options.delete :context) || @obj
default_value = options.delete(:default_value)
if block_given?
content = block.call(obj, attr)
content = default_value || raw(" ") if content.blank?
else
content = attr.to_s.humanize
content = obj.name + ": " + content if obj != context
end
marker_id = store_marker_definition(
:obj_id => obj.id,
:attribute => format_attribute_name_for_gui(attr),
:context_id => context ? context.id : nil,
:size => options.delete(:size),
:target => options.delete(:target),
:memberships => required_memberships_for_editing(obj, attr)
)
store_edit_marker_markup(content_tag(:a, "",
:class => "nps_edit_marker nps_marker_id_#{marker_id}"))
tag_type = options.delete(:tag) || :span
content_tag(
tag_type,
content.to_s,
options.merge(:class => "#{options[:class]}", :id => "nps_marker_id_#{marker_id}")
)
else
block_given? ? block.call(obj, attr) : nil
end
end
# Renders a link which the editor can use to toggle on/off the editmarkers shown
# in the preview.
# This link will only be shown if the Rails Connector is in editor mode.
# value the link text or e.g. an image_tag
def toggle_edit_marker_link(value)
if Configuration.editor_interface_enabled?
link_to value, toggle_markers_url(@obj)
end
end
# Renders a marker for an action to be performed with any number of objects.
# action the attribute as a Symbol or String.
# objs is an Array containing a single, none or more than one Obj.
# The helper has additional options:
# [:permissions] is an Array of permissions the current user needs to use
# the marker: :read, :write, :root, :create_children - defaults to [:read].
# This option is available for a single Obj passed to objs.
# [:params] a hash of parameters to be passed to the action.
#
# The following code will render an action marker for the release action of the current object:
#
# <%= action_marker :workflow_release, [@obj] %>
def action_marker(action, objs, options = {}, &block)
return unless Configuration.editor_interface_enabled?
context = (options.delete :context) || @obj
size = options.delete(:size)
target = options.delete(:target) || "_blank"
params = options.delete(:params)
permissions = options.delete(:permissions) || [:read]
if objs.size == 1
memberships = required_memberships_for_permissions(objs.first, permissions)
else
memberships = []
end
marker_id = store_marker_definition(
:memberships => memberships
)
link_to(
block_given? ? block.call : action.to_s.humanize,
uri_for_action_marker(action, objs, context, size, target, params),
options.merge(:class => ["nps_action_marker", "nps_marker_id_#{marker_id}", options[:class]].compact.join(' '))
)
end
# Generates an edit marker link.
#
# [name] the link caption.
# [obj] the object for which the edit dialog will be generated.
# [attr] the attribute for which the edit dialog will be generated.
# [options] options for the edit dialog (see +edit_marker+).
def link_to_edit(name, obj, attr, options = {})
if Configuration.editor_interface_enabled?
context = (options.delete :context) || @obj
size = options.delete(:size)
target = options.delete(:target) || "_blank"
link_to_function(name.to_s,
"inline_editing.openEditDialog('#{obj.id}', '#{attr}', '#{context.id || 'null'}', '#{size || 'null'}', '#{target || 'null'}')"
)
end
end
# Renders a folding menu which can be used to insert various edit actions or
# informations.
#
# [left] the left offset.
# [top] the top offset.
# [&block] the content which will be displayed in the menu.
#
# To generate a menu you have to create one using the +marker_menu+ helper.
# Than you have to mark the target for which the menu should be displayed
# using the +marker_menu_target+ helper.
#
# Usage:
# <%= marker_menu do %>
# Menu
# <% end %>
#
# <%= marker_menu_target(:div) do %>
# Content
# <% end %>
def marker_menu(left=0, top=0, &block)
self.current_marker_menu_id = store_marker_definition(
:offset_left => left,
:offset_top => top
)
store_marker_markup(content_tag(
:div,
marker_menu_markup(capture(&block)),
:class => "nps_marker_menu nps_marker_id_#{current_marker_menu_id}",
:id => "nps_marker_menu_#{current_marker_menu_id}"
))
nil
end
# Marks the given content to display the previous defined marker menu.
#
# [tag_name] the tag in which the content will be displayed.
# [options] the options for the tag.
# [&block] the content which will be displayed with the menu.
#
# See +marker_menu+ for an example.
def marker_menu_target(tag_name, options = {}, &block)
content_tag(
tag_name,
capture(&block),
options.merge(:class => "#{marker_menu_target_class} #{options[:class]}")
)
end
# Returns the css class used for the target element where the marker menu
# should be displayed.
#
# Will be used automatically if you use the helpers +marker_menu+ and
# +marker_menu_target+. But if you want to set a marker menu manually to
# an element (e.g. an image) you need to supply this css class.
def marker_menu_target_class
"nps_marker_menu_target nps_marker_menu_target_#{current_marker_menu_id}" if Configuration.editor_interface_enabled?
end
# Renders the necessary JavaScript and CSS includes to use the edit markers.
def include_edit_marker_support # :nodoc:
if Configuration.editor_interface_enabled?
raw <<-EOF
#{stylesheet_link_tag :editmarker}
#{javascript_include_tag :editmarker}
#{content_tag(:script, :type => 'text/javascript') { 'inline_editing.init();' }}
EOF
end
end
# Renders the necessary marker code to use edit markers and marker menus.
def render_marker_code # :nodoc:
return unless Configuration.editor_interface_enabled?
html = raw("#{render_marker_definitions}\n#{render_marker_menus}\n#{render_edit_markers}")
reset_marker_code
html
end
# This helper method can be used in +marker_menu+ to generate a list with
# actions using +edit_item+ or +action_item+. For each action a icon and
# title will be shown.
#
# Usage:
# <%= marker_menu do %>
# <%= iconlist do %>
# <%= edit_item("edit title", "css_class", @obj, :image) %>
# <%= action_item("edit image", "css_class", [@obj], :editImage) %>
# <% end %>
# <% end %>
def iconlist(&block)
content_tag(:ul, capture(&block)) if block_given?
end
# Creates an edit marker item for the list generated by +iconlist+.
#
# [title] the title which will be displayed.
# [css_class] the css-class can be used to display a different icon.
# [obj] the object for which the edit dialog will be generated.
# [attribute] the attribute for which the edit dialog will be generated.
# [options] options that will be delegated to the +link_to_edit+ helper.
def edit_item(title, css_class, obj, attribute, options = {})
edit_link = link_to_edit(content_tag(:span, title, :class => css_class), obj, attribute, options)
content_tag(:li, edit_link)
end
# Creates an action marker item for the list generated by +iconlist+.
#
# [title] the title which will be displayed.
# [css_class] the css-class can be used to display a different icon.
# [objs] the array of objects for which the action dialog will be generated.
# [action] the action for which the action dialog will be generated.
# [options] options that will be delegated to the +action_marker+ helper.
def action_item(title, css_class, objs, action, options = {})
action_link = action_marker(action, objs, options) { content_tag(:span, title, :class => css_class) }
content_tag(:li, action_link)
end
private
attr_accessor :current_marker_menu_id
def current_marker_menu_id
@current_marker_menu_id or raise "Tried to create a marker menu target before a marker menu was created!"
end
JS_HEADER = <
//
ENDOFSTRING
def store_marker_definition(definition)
@marker_definitions ||= {}
marker_id = random_marker_id
@marker_definitions[marker_id] = definition
marker_id
end
def random_marker_id
rand(1_000_000_000)
end
def render_marker_definitions
return unless @marker_definitions.present?
"#{JS_HEADER}inline_editing.storeMarkerDefinitions(#{@marker_definitions.to_json});" +
"#{JS_FOOTER}"
end
def store_marker_markup(markup)
@marker_markup ||= []
@marker_markup << markup
end
def store_edit_marker_markup(markup)
@edit_marker_markup ||= []
@edit_marker_markup << markup
end
def reset_marker_code
@marker_definitions = @marker_markup = @edit_marker_markup = nil
end
def render_marker_menus
(@marker_markup || []).join
end
def render_edit_markers
(@edit_marker_markup || []).join
end
def required_memberships_for_editing(obj, attr)
permissions = [:read]
if [:name, :obj_class, :workflow, :suppressexport, :permalink].include?(attr.to_sym)
permissions << :root
else
permissions << :write
end
required_memberships_for_permissions(obj, permissions)
end
def required_memberships_for_permissions(obj, permissions)
permissions.map { |perm| obj.permissions.__send__(perm) }
end
def uri_for_action_marker(action, objects, context = nil, size = nil, target = nil, params = nil) #:nodoc:
action = action.to_s
objectIds = objects.collect(&:id).to_json
contextId = context.nil? ? 'null' : context.id
size = size.nil? ? 'null' : "'#{size}'"
target = target.nil? ? 'null' : "'#{target}'"
params = params.blank? ? 'null' : "'#{escape_javascript(params.to_json.gsub('"','%22'))}'"
"javascript:parent.openActionDialog('#{action}',#{objectIds},#{contextId},#{params},#{size},#{target})"
end
def format_attribute_name_for_gui(attr)
attr = attr.to_s
attr = 'blob' if attr == 'body'
attr = attr.camelize(:lower) if %w{ valid_from valid_until obj_class }.include? attr
attr
end
def marker_menu_markup(content)
raw %Q{
}
end
end
end