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{MENU } end end end