module SimpleNavigation def self.explicit_navigation_args adapter.controller.instance_variable_get(:'@sn_current_navigation_args') end # Reads the current navigation for the specified level from the controller. # Returns nil if there is no current navigation set for level. def self.current_navigation_for(level) adapter.controller.instance_variable_get(:"@sn_current_navigation_#{level}") end # If any navigation has been explicitely set in the controller this method # evaluates the specified args set in the controller and sets the correct # instance variable in the controller. def self.handle_explicit_navigation return unless explicit_navigation_args level, navigation = parse_explicit_navigation_args navigation_level = :"@sn_current_navigation_#{level}" adapter.controller.instance_variable_set(navigation_level, navigation) end def self.parse_explicit_navigation_args args = if explicit_navigation_args.empty? [{}] else explicit_navigation_args end indexed_args = args_indexed_by_level(args) deepest = deepest_level_and_item(indexed_args) if deepest.first.zero? fail ArgumentError, 'Invalid level specified or item key not found' end deepest end private def self.deepest_level_and_item(navigation_args) navigation_args.map { |k, v| [k.to_s[/level_(\d)/, 1].to_i, v] } .max end def self.args_indexed_by_level(navigation_args) if navigation_args.first.is_a?(Hash) navigation_args.first elsif navigation_args.size == 1 level = primary_navigation.level_for_item(navigation_args.first) level ? { :"level_#{level}" => navigation_args.first } : {} else navigation_args.each_with_index .with_object({}) do |(arg, i), h| h[:"level_#{i + 1}"] = arg end end end # Adds methods for explicitely setting the current 'active' navigation to # the controllers. # Since version 2.0.0 the simple_navigation plugin determines the active # navigation based on the current url by default (auto highlighting), # so explicitely defining the active navigation in the controllers is only # needed for edge cases where automatic highlighting does not work. # # On the controller class level, use the navigation method to set the # active navigation for all actions in the controller. # Let's assume that we have a primary navigation item :account which in turn # has a sub navigation item :settings. # # ==== Examples # class AccountController << ActionController # navigation :account # ... # end # # class AccountSettingsController << ActionController # navigation :settings # ... # end # # The first example sets the current primary navigation to :account for all # actions. No active sub_navigation. # The second example sets the current sub navigation to :settings and since # it is a child of :account the current primary navigation is set to :account. # # On the controller instance level, use the current_navigation method # to define the active navigation for a specific action. # The navigation item that is set in current_navigation overrides the # one defined on the controller class level (see navigation method). # Thus if you have an :account primary item with a :special # sub navigation item: # # ==== Example # class AccountController << ActionController # navigation :account # # def your_special_action # ... # current_navigation :special # end # end # # The code above still sets the active primary navigation to :account for all # actions, but sets the sub_navigation to :account -> :special for # 'your_special_action'. # # Note 1: As you can see above you just have to set the navigation item of # your 'deepest' navigation level as active and all its parents are # marked as active, too. # # Note 2: The specified symbols must match the keys for your navigation # items in your config/navigation.rb file. module ControllerMethods def self.included(base) #:nodoc: base.class_eval do extend ClassMethods include InstanceMethods end end module ClassMethods # Sets the active navigation for all actions in this controller. # # The specified symbol must match the keys for your navigation items # in your config/navigation.rb file. def navigation(*args) class_eval do define_method :sn_set_navigation do current_navigation(*args) end protected :sn_set_navigation before_filter :sn_set_navigation end end end module InstanceMethods # Sets the active navigation. Call this method in any action to override # the controller-wide active navigation specified by navigation. # # The specified symbol must match the keys for your navigation items in # your config/navigation.rb file. def current_navigation(*args) @sn_current_navigation_args = args end end end class Item def selected_by_config? key == SimpleNavigation.current_navigation_for(container.level) end end class ItemContainer def selected_item self[SimpleNavigation.current_navigation_for(level)] || items.find(&:selected?) end end end ActionController::Base.send(:include, SimpleNavigation::ControllerMethods)