module Cms module MenuHelper # Renders a menu. There are two options, neither are required: # # ==== Options # * :items - The items which should appear in the menu. This defaults to calling # menu_items which generates items automatically based on the current page. But you can use # this option to pass in a custom menu structure. # * :partial - The partial used to render the menu. By default this is "partials/menu", # which can be customised through the CMS. The partial gets a local variable items. # # ==== Structure of items # # The items should be an array of hashes, in a tree. Each hash can have the following keys (name # and url are required, others are optional): # # * :name - The name which appears in the menu # * :url - The URL to link to # * :id - The id for the menu item # * :selected - Boolean value to indicate whether the menu item is the current page # * :target - The target attribute for the link # * :children - An array of hashes containing the child menu items. This is where the # tree structure comes in. def render_menu(options = {}) options[:items] ||= menu_items options[:partial] ||= "partials/menu" render :partial => options[:partial], :locals => { :items => options[:items] } end # This will render generate an array-of-hashes tree structure based on the page, which can be # passed to render_menu in order to generate a menu. # # With no options passed, it will generate a structure that includes all the child sections of # the root and then it will include the path of decendent sections all the way to the current # page. # # Hidden pages will not be included, but if the first page in a Section is hidden, it will be # used as the URL for that Section. This is commonly done to have a page for a Section and avoid # having duplicates in the navigation. # # You can change the behavior with the following options, all of these are optional: # # ==== Options # * :page - What page should be used as the current page. If this value is omitted, # the value in @page will be used. # * :path - This will be used to look up a section and that section will used to # generate the menu structure. The current page will still be the value of the page option or # @page. Note that this is the path to a section, not a path to a page. # * :from_top - How many below levels from the root the tree should start at. # All sections at this level will be shown. The default is 0, which means show all # nodes that are direct children of the root # * :depth - How many levels deep should the tree go, relative to from_top. # If no value is supplied, the tree will go all the way down to the current page. # If a value is supplied, the tree will be that many levels underneath from_top deep. # * :limit - Limits the number of top-level elements that will be included in the list # * :show_all_siblings - Passing true for this option will make all sibilings appear in # the tree. The default is false, in which case only the siblings of nodes within the open # path will appear. # # ==== Examples # # Assume you have the structure the NFL, which is NFL > Conference > Division > Team, # with teams being a Page, everything else a Section. Also, assume we are on the # Baltimore Ravens page. If you're not a footbal fan, see http://sports.yahoo.com/nfl/teams # # menu_items # # => [ # { :id => "section_2", :url => "/buf", :name => "AFC", :children => [ # { :id => "section_3", :url => "/buf", :name => "East" }, # { :id => "section_4", :url => "/bal", :name => "North", :children => [ # { :id => "page_5", :selected => true, :url => "/bal", :name => "Baltimore Ravens" }, # { :id => "page_6", :url => "/cin", :name => "Cincinnati Bengals" }, # { :id => "page_7", :url => "/cle", :name => "Cleveland Browns" }, # { :id => "page_8", :url => "/pit", :name => "Pittsburgh Steelers" } # ] }, # { :id => "section_9", :url => "/hou", :name => "South" }, # { :id => "section_10}", :url => "/den", :name => "West" } # ] }, # { :id => "section_11", :url => "/dal", :name => "NFC" } # ] # # menu_items(:depth => 2, :show_all_siblings => true) # # => [ # { :id => "section_2", :url => "/buf", :name => "AFC", :children => [ # { :id => "section_3", :url => "/buf", :name => "East" }, # { :id => "section_4", :url => "/bal", :name => "North" }, # { :id => "section_5", :url => "/hou", :name => "South" }, # { :id => "section_6", :url => "/den", :name => "West" } # ] }, # { :id => "section_7", :url => "/dal", :name => "NFC", :children => [ # { :id => "section_8", :url => "/dal", :name => "East" }, # { :id => "section_9", :url => "/chi", :name => "North" }, # { :id => "section_10", :url => "/atl", :name => "South" }, # { :id => "section_11", :url => "/ari", :name => "West" } # ] } # ] def menu_items(options = {}) # Intialize parameters selected_page = options[:page] || @page return nil unless selected_page # Path to the section if options.has_key?(:path) section_for_path = Section.find_by_path(options[:path]) raise "Could not find section for path '#{options[:path]}'" unless section_for_path ancestors = section_for_path.ancestors(:include_self => true) else ancestors = selected_page.ancestors end if options.has_key?(:from_top) ancestors = ancestors[options[:from_top].to_i..-1] || [] end depth = options.has_key?(:depth) ? options[:depth].to_i : 1.0/0 show_all_siblings = options[:show_all_siblings] || false # We are defining a recursive lambda that takes the top-level sections fn = lambda do |section_nodes, current_depth| section_nodes.map do |section_node| node = section_node.node item = {} item[:selected] = true if selected_page == node item[:id] = "#{section_node.node_type.underscore}_#{section_node.node_id}" # If we are showing a section item, we want to use the path for the first page page = section_node.section? ? node.first_page_or_link : node item[:url] = page && page.path || '#' item[:name] = node.name item[:target] = "_blank" if page.respond_to?(:new_window?) && page.new_window? # Now if this is a section, we do the child nodes, # but only if the show_all_siblings parameter is true, # or if this section is one of the current page's ancestors # and also if the current depth is less than the target depth if section_node.section? && current_depth < depth && (show_all_siblings || ancestors.include?(node)) && !node.visible_child_nodes.empty? item[:children] = fn.call(node.visible_child_nodes, current_depth + 1) end item end end if ancestors.empty? [] else fn.call(ancestors.first.visible_child_nodes(:limit => options[:limit]), 1) end end end end