module SimpleNavigation
# Holds the Items for a navigation 'level'.
class ItemContainer
attr_reader :items, :level
attr_accessor :renderer, :dom_id, :dom_class, :auto_highlight
def initialize(level=1) #:nodoc:
@level = level
@items = []
@renderer = SimpleNavigation.config.renderer
@auto_highlight = true
end
# Creates a new navigation item.
#
# The key is a symbol which uniquely defines your navigation item in the scope of the primary_navigation or the sub_navigation.
#
# The name will be displayed in the rendered navigation. This can also be a call to your I18n-framework.
#
# The url is the address that the generated item points to. You can also use url_helpers (named routes, restful routes helper, url_for etc.)
#
# The options can be used to specify the following things:
# * html_attributes - will be included in the rendered navigation item (e.g. id, class etc.)
# * :if - Specifies a proc to call to determine if the item should
# be rendered (e.g. :if => Proc.new { current_user.admin? }). The
# proc should evaluate to a true or false value and is evaluated in the context of the view.
# * :unless - Specifies a proc to call to determine if the item should not
# be rendered (e.g. :unless => Proc.new { current_user.admin? }). The
# proc should evaluate to a true or false value and is evaluated in the context of the view.
#
# The block - if specified - will hold the item's sub_navigation.
def item(key, name, url, options={}, &block)
(@items << SimpleNavigation::Item.new(self, key, name, url, options, block)) if should_add_item?(options)
end
# Returns the Item with the specified key, nil otherwise.
#
def [](navi_key)
items.find {|i| i.key == navi_key}
end
# Returns the level of the item specified by navi_key.
# Recursively works its way down the item's sub_navigations if the desired item is not found directly in this container's items.
# Returns nil item cannot be found.
#
def level_for_item(navi_key)
my_item = self[navi_key]
return self.level if my_item
items.each do |i|
if i.sub_navigation
level = i.sub_navigation.level_for_item(navi_key)
return level unless level.nil?
end
end
return nil
end
# Renders the items in this ItemContainer using the configured renderer.
#
# Set include_sub_navigation to true if you want to nest the sub_navigation into the active parent_navigation
def render(include_sub_navigation=false, options={})
self.renderer.new.render(self, include_sub_navigation, options)
end
# Returns true if any of this container's items is selected.
#
def selected?
items.any? {|i| i.selected?}
end
# Returns the currently selected item, nil if no item is selected.
#
def selected_item
self[current_explicit_navigation] || items.find {|i| i.selected?}
end
# Returns the current navigation that has been explicitely defined in the controller for this container's level.
# Returns nil if no explicit current navigation has been set.
#
def current_explicit_navigation
SimpleNavigation.current_navigation_for(level)
end
# Returns the active item_container for the specified level
# (recursively looks up items in selected sub_navigation if level is deeper than this container's level).
#
def active_item_container_for(desired_level)
return self if self.level == desired_level
return nil unless selected_sub_navigation?
return selected_item.sub_navigation.active_item_container_for(desired_level)
end
private
def selected_sub_navigation?
!!(selected_item && selected_item.sub_navigation)
end
# partially borrowed from ActionSupport::Callbacks
def should_add_item?(options) #:nodoc:
[options.delete(:if)].flatten.compact.all? { |m| evaluate_method(m) } &&
![options.delete(:unless)].flatten.compact.any? { |m| evaluate_method(m) }
end
# partially borrowed from ActionSupport::Callbacks
def evaluate_method(method) #:nodoc:
case method
when Proc, Method
method.call
else
raise ArgumentError, ":if or :unless must be procs or lambdas"
end
end
end
end