simple-navigation-ext 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +177 -0
- data/README +22 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/generators/navigation_config/USAGE +1 -0
- data/generators/navigation_config/navigation_config_generator.rb +8 -0
- data/generators/navigation_config/templates/config/navigation.rb +67 -0
- data/lib/generators/navigation_config/navigation_config_generator.rb +12 -0
- data/lib/simple-navigation.rb +1 -0
- data/lib/simple_navigation/adapters/base.rb +37 -0
- data/lib/simple_navigation/adapters/padrino.rb +20 -0
- data/lib/simple_navigation/adapters/rails.rb +94 -0
- data/lib/simple_navigation/adapters/sinatra.rb +68 -0
- data/lib/simple_navigation/adapters.rb +9 -0
- data/lib/simple_navigation/core/configuration.rb +70 -0
- data/lib/simple_navigation/core/item.rb +117 -0
- data/lib/simple_navigation/core/item_adapter.rb +63 -0
- data/lib/simple_navigation/core/item_container.rb +146 -0
- data/lib/simple_navigation/core/items_provider.rb +35 -0
- data/lib/simple_navigation/core.rb +5 -0
- data/lib/simple_navigation/rails_controller_methods.rb +144 -0
- data/lib/simple_navigation/rendering/helpers.rb +82 -0
- data/lib/simple_navigation/rendering/renderer/base.rb +71 -0
- data/lib/simple_navigation/rendering/renderer/breadcrumbs.rb +37 -0
- data/lib/simple_navigation/rendering/renderer/links.rb +25 -0
- data/lib/simple_navigation/rendering/renderer/list.rb +47 -0
- data/lib/simple_navigation/rendering.rb +10 -0
- data/lib/simple_navigation.rb +162 -0
- data/rails/init.rb +1 -0
- data/spec/lib/simple_navigation/adapters/padrino_spec.rb +29 -0
- data/spec/lib/simple_navigation/adapters/rails_spec.rb +284 -0
- data/spec/lib/simple_navigation/adapters/sinatra_spec.rb +60 -0
- data/spec/lib/simple_navigation/core/configuration_spec.rb +122 -0
- data/spec/lib/simple_navigation/core/item_adapter_spec.rb +209 -0
- data/spec/lib/simple_navigation/core/item_container_spec.rb +396 -0
- data/spec/lib/simple_navigation/core/item_spec.rb +513 -0
- data/spec/lib/simple_navigation/core/items_provider_spec.rb +60 -0
- data/spec/lib/simple_navigation/rails_controller_methods_spec.rb +249 -0
- data/spec/lib/simple_navigation/rendering/helpers_spec.rb +183 -0
- data/spec/lib/simple_navigation/rendering/renderer/base_spec.rb +199 -0
- data/spec/lib/simple_navigation/rendering/renderer/breadcrumbs_spec.rb +58 -0
- data/spec/lib/simple_navigation/rendering/renderer/links_spec.rb +58 -0
- data/spec/lib/simple_navigation/rendering/renderer/list_spec.rb +189 -0
- data/spec/lib/simple_navigation_spec.rb +307 -0
- data/spec/spec_helper.rb +96 -0
- metadata +158 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'ruby-debug'
|
2
|
+
|
3
|
+
module SimpleNavigation
|
4
|
+
|
5
|
+
# Represents an item in your navigation. Gets generated by the item method in the config-file.
|
6
|
+
class Item
|
7
|
+
attr_reader :key, :name, :url, :sub_navigation, :method, :highlights_on, :exclude_highlighting
|
8
|
+
attr_writer :html_options
|
9
|
+
|
10
|
+
# see ItemContainer#item
|
11
|
+
#
|
12
|
+
# The subnavigation (if any) is either provided by a block or passed in directly as <tt>items</tt>
|
13
|
+
def initialize(container, key, name, url, options, items=nil, &sub_nav_block)
|
14
|
+
@container = container
|
15
|
+
@container.dom_class = options.delete(:container_class) if options[:container_class]
|
16
|
+
@container.dom_id = options.delete(:container_id) if options[:container_id]
|
17
|
+
@key = key
|
18
|
+
@method = options.delete(:method)
|
19
|
+
@name = name
|
20
|
+
@url = url.instance_of?(Proc) ? url.call : url
|
21
|
+
@highlights_on = options.delete(:highlights_on)
|
22
|
+
@exclude_highlighting = options.delete(:exclude_highlight_if)
|
23
|
+
@html_options = options
|
24
|
+
if sub_nav_block || items
|
25
|
+
@sub_navigation = ItemContainer.new(@container.level + 1)
|
26
|
+
sub_nav_block.call @sub_navigation if sub_nav_block
|
27
|
+
@sub_navigation.items = items if items
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns true if this navigation item should be rendered as 'selected'.
|
32
|
+
# An item is selected if
|
33
|
+
#
|
34
|
+
# * it has been explicitly selected in a controller or
|
35
|
+
# * it has a subnavigation and one of its subnavigation items is selected or
|
36
|
+
# * its url matches the url of the current request (auto highlighting)
|
37
|
+
#
|
38
|
+
def selected?
|
39
|
+
@selected = (@selected || selected_by_config? || selected_by_subnav? || selected_by_url?)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the html-options hash for the item, i.e. the options specified for this item in the config-file.
|
43
|
+
# It also adds the 'selected' class to the list of classes if necessary.
|
44
|
+
def html_options
|
45
|
+
default_options = self.autogenerate_item_ids? ? {:id => autogenerated_item_id} : {}
|
46
|
+
options = default_options.merge(@html_options)
|
47
|
+
options[:class] = [@html_options[:class], self.selected_class].flatten.compact.join(' ')
|
48
|
+
options.delete(:class) if options[:class].nil? || options[:class] == ''
|
49
|
+
options
|
50
|
+
end
|
51
|
+
|
52
|
+
def item_excluded
|
53
|
+
if exclude_highlighting
|
54
|
+
raise ArgumentError, ':exclude_highlight_if must be a regexp' unless exclude_highlighting.instance_of?(Regexp)
|
55
|
+
SimpleNavigation.request_uri =~ exclude_highlighting
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the configured selected_class if the item is selected, nil otherwise
|
60
|
+
def selected_class
|
61
|
+
if selected?
|
62
|
+
self.item_excluded ? nil : SimpleNavigation.config.selected_class
|
63
|
+
# SimpleNavigation.config.selected_class
|
64
|
+
else
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
# Returns true if item has a subnavigation and the sub_navigation is selected
|
72
|
+
def selected_by_subnav?
|
73
|
+
sub_navigation && sub_navigation.selected?
|
74
|
+
end
|
75
|
+
|
76
|
+
def selected_by_config?
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns true if the item's url matches the request's current url.
|
81
|
+
def selected_by_url?
|
82
|
+
if highlights_on
|
83
|
+
raise ArgumentError, ':highlights_on must be a regexp' unless highlights_on.instance_of?(Regexp)
|
84
|
+
SimpleNavigation.request_uri =~ highlights_on
|
85
|
+
elsif auto_highlight?
|
86
|
+
!!(root_path_match? || SimpleNavigation.current_page?(url_without_anchor))
|
87
|
+
else
|
88
|
+
false
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns true if both the item's url and the request's url are root_path
|
93
|
+
def root_path_match?
|
94
|
+
url == '/' && SimpleNavigation.request_path == '/'
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns true if the item's id should be added to the rendered output.
|
98
|
+
def autogenerate_item_ids?
|
99
|
+
SimpleNavigation.config.autogenerate_item_ids
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns the item's id which is added to the rendered output.
|
103
|
+
def autogenerated_item_id
|
104
|
+
SimpleNavigation.config.id_generator.call(key)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Return true if auto_highlight is on for this item.
|
108
|
+
def auto_highlight?
|
109
|
+
SimpleNavigation.config.auto_highlight && @container.auto_highlight
|
110
|
+
end
|
111
|
+
|
112
|
+
def url_without_anchor
|
113
|
+
url.split('#').first
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module SimpleNavigation
|
4
|
+
|
5
|
+
# This class acts as an adapter to items that are not defined using the DSL in the config/navigation.rb, but directly provided inside the application.
|
6
|
+
# When defining the items that way, every item you provide needs to define the following methods:
|
7
|
+
#
|
8
|
+
# * <tt>key</tt>
|
9
|
+
# * <tt>name</tt>
|
10
|
+
# * <tt>url</tt>
|
11
|
+
#
|
12
|
+
# and optionally
|
13
|
+
#
|
14
|
+
# * <tt>options</tt>
|
15
|
+
# * <tt>items</tt> - if one of your items has a subnavigation it must respond to <tt>items</tt> providing the subnavigation.
|
16
|
+
#
|
17
|
+
# You can also specify your items as a list of hashes. The hashes will be converted to objects automatically.
|
18
|
+
# The hashes representing the items obviously must have the keys :key, :name and :url and optionally the keys :options and :items.
|
19
|
+
#
|
20
|
+
# See SimpleNavigation::ItemContainer#item for the purpose of these methods.
|
21
|
+
class ItemAdapter
|
22
|
+
extend Forwardable
|
23
|
+
|
24
|
+
def_delegators :item, :key, :name, :url
|
25
|
+
|
26
|
+
attr_reader :item
|
27
|
+
|
28
|
+
def initialize(item)
|
29
|
+
@item = item.instance_of?(Hash) ? to_object(item) : item
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the options for this item. If the wrapped item does not implement an options method, an empty hash is returned.
|
33
|
+
def options
|
34
|
+
@item.respond_to?(:options) ? @item.options : {}
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the items (subnavigation) for this item if it responds to :items and the items-collection is not empty. Returns nil otherwise.
|
38
|
+
def items
|
39
|
+
(@item.respond_to?(:items) && !(@item.items.nil? || @item.items.empty?)) ? @item.items : nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# Converts this Item into a SimpleNavigation::Item
|
43
|
+
def to_simple_navigation_item(item_container)
|
44
|
+
SimpleNavigation::Item.new(item_container, key, name, url, options, items)
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
# Converts the specified hash into an object. Each key will be added as method.
|
50
|
+
#
|
51
|
+
def to_object(hash)
|
52
|
+
mod = Module.new do
|
53
|
+
hash.each_pair do |key, value|
|
54
|
+
define_method key do
|
55
|
+
value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
Object.new.extend(mod)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module SimpleNavigation
|
2
|
+
|
3
|
+
# Holds the Items for a navigation 'level'.
|
4
|
+
class ItemContainer
|
5
|
+
|
6
|
+
attr_reader :items, :level
|
7
|
+
attr_accessor :renderer, :dom_id, :dom_class, :auto_highlight
|
8
|
+
|
9
|
+
def initialize(level=1) #:nodoc:
|
10
|
+
@level = level
|
11
|
+
@items = []
|
12
|
+
@renderer = SimpleNavigation.config.renderer
|
13
|
+
@auto_highlight = true
|
14
|
+
end
|
15
|
+
|
16
|
+
# Creates a new navigation item.
|
17
|
+
#
|
18
|
+
# The <tt>key</tt> is a symbol which uniquely defines your navigation item in the scope of the primary_navigation or the sub_navigation.
|
19
|
+
#
|
20
|
+
# The <tt>name</tt> will be displayed in the rendered navigation. This can also be a call to your I18n-framework.
|
21
|
+
#
|
22
|
+
# The <tt>url</tt> is the address that the generated item points to. You can also use url_helpers (named routes, restful routes helper, url_for etc.)
|
23
|
+
#
|
24
|
+
# The <tt>options</tt> can be used to specify the following things:
|
25
|
+
# * <tt>any html_attributes</tt> - will be included in the rendered navigation item (e.g. id, class etc.)
|
26
|
+
# * <tt>:if</tt> - Specifies a proc to call to determine if the item should
|
27
|
+
# be rendered (e.g. <tt>:if => Proc.new { current_user.admin? }</tt>). The
|
28
|
+
# proc should evaluate to a true or false value and is evaluated in the context of the view.
|
29
|
+
# * <tt>:unless</tt> - Specifies a proc to call to determine if the item should not
|
30
|
+
# be rendered (e.g. <tt>:unless => Proc.new { current_user.admin? }</tt>). The
|
31
|
+
# proc should evaluate to a true or false value and is evaluated in the context of the view.
|
32
|
+
# * <tt>:method</tt> - Specifies the http-method for the generated link - default is :get.
|
33
|
+
# * <tt>:highlights_on</tt> - if autohighlighting is turned off and/or you want to explicitly specify
|
34
|
+
# when the item should be highlighted, you can set a regexp which is matched againstthe current URI.
|
35
|
+
#
|
36
|
+
# The <tt>block</tt> - if specified - will hold the item's sub_navigation.
|
37
|
+
def item(key, name, url, options={}, &block)
|
38
|
+
(@items << SimpleNavigation::Item.new(self, key, name, url, options, nil, &block)) if should_add_item?(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def items=(items)
|
42
|
+
items.each do |item|
|
43
|
+
item = SimpleNavigation::ItemAdapter.new(item)
|
44
|
+
(@items << item.to_simple_navigation_item(self)) if should_add_item?(item.options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the Item with the specified key, nil otherwise.
|
49
|
+
#
|
50
|
+
def [](navi_key)
|
51
|
+
items.find {|i| i.key == navi_key}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the level of the item specified by navi_key.
|
55
|
+
# Recursively works its way down the item's sub_navigations if the desired item is not found directly in this container's items.
|
56
|
+
# Returns nil item cannot be found.
|
57
|
+
#
|
58
|
+
def level_for_item(navi_key)
|
59
|
+
my_item = self[navi_key]
|
60
|
+
return self.level if my_item
|
61
|
+
items.each do |i|
|
62
|
+
if i.sub_navigation
|
63
|
+
level = i.sub_navigation.level_for_item(navi_key)
|
64
|
+
return level unless level.nil?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
return nil
|
68
|
+
end
|
69
|
+
|
70
|
+
# Renders the items in this ItemContainer using the configured renderer.
|
71
|
+
#
|
72
|
+
# The options are the same as in the view's render_navigation call (they get passed on)
|
73
|
+
def render(options={})
|
74
|
+
renderer_instance = if options[:renderer]
|
75
|
+
if options[:renderer].instance_of?(Symbol) && SimpleNavigation.registered_renderers.key?(options[:renderer])
|
76
|
+
SimpleNavigation.registered_renderers[options[:renderer]].new(options)
|
77
|
+
else
|
78
|
+
options[:renderer].new(options)
|
79
|
+
end
|
80
|
+
else
|
81
|
+
self.renderer.new(options)
|
82
|
+
end
|
83
|
+
renderer_instance.render(self)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns true if any of this container's items is selected.
|
87
|
+
#
|
88
|
+
def selected?
|
89
|
+
items.any? {|i| i.selected?}
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns the currently selected item, nil if no item is selected.
|
93
|
+
#
|
94
|
+
def selected_item
|
95
|
+
items.find {|i| i.selected?}
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the active item_container for the specified level
|
99
|
+
# (recursively looks up items in selected sub_navigation if level is deeper than this container's level).
|
100
|
+
#
|
101
|
+
def active_item_container_for(desired_level)
|
102
|
+
return self if self.level == desired_level
|
103
|
+
return nil unless selected_sub_navigation?
|
104
|
+
return selected_item.sub_navigation.active_item_container_for(desired_level)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns the deepest possible active item_container.
|
108
|
+
# (recursively searches in the sub_navigation if this container has a selected sub_navigation).
|
109
|
+
def active_leaf_container
|
110
|
+
if selected_sub_navigation?
|
111
|
+
selected_item.sub_navigation.active_leaf_container
|
112
|
+
else
|
113
|
+
self
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns true if there are no items defined for this container.
|
118
|
+
def empty?
|
119
|
+
items.empty?
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def selected_sub_navigation?
|
125
|
+
!!(selected_item && selected_item.sub_navigation)
|
126
|
+
end
|
127
|
+
|
128
|
+
# partially borrowed from ActionSupport::Callbacks
|
129
|
+
def should_add_item?(options) #:nodoc:
|
130
|
+
[options.delete(:if)].flatten.compact.all? { |m| evaluate_method(m) } &&
|
131
|
+
![options.delete(:unless)].flatten.compact.any? { |m| evaluate_method(m) }
|
132
|
+
end
|
133
|
+
|
134
|
+
# partially borrowed from ActionSupport::Callbacks
|
135
|
+
def evaluate_method(method) #:nodoc:
|
136
|
+
case method
|
137
|
+
when Proc, Method
|
138
|
+
method.call
|
139
|
+
else
|
140
|
+
raise ArgumentError, ":if or :unless must be procs or lambdas"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SimpleNavigation
|
2
|
+
|
3
|
+
# Acts as a proxy to navigation items that are passed into the SimpleNavigation::Configuration#items method. It hides the logic
|
4
|
+
# for finding items from the Configuration object.
|
5
|
+
#
|
6
|
+
class ItemsProvider
|
7
|
+
|
8
|
+
attr_reader :provider
|
9
|
+
|
10
|
+
# It accepts the following types of provider:
|
11
|
+
# * methodname as symbol - the specified method should return the relevant items and has to be available in the view (a helper method)
|
12
|
+
# * object that responds to :items
|
13
|
+
# * enumerable object that represents the items
|
14
|
+
#
|
15
|
+
# See SimpleNavigation::ItemAdapter for the requirements that need to be fulfilled by the provided items.
|
16
|
+
#
|
17
|
+
def initialize(provider)
|
18
|
+
@provider = provider
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the navigation items
|
22
|
+
def items
|
23
|
+
if provider.instance_of?(Symbol)
|
24
|
+
SimpleNavigation.context_for_eval.send(provider)
|
25
|
+
elsif provider.respond_to?(:items)
|
26
|
+
provider.items
|
27
|
+
elsif provider.respond_to?(:each)
|
28
|
+
provider
|
29
|
+
else
|
30
|
+
raise "items_provider either must be a symbol specifying the helper-method to call, an object with an items-method defined or an enumerable representing the items"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module SimpleNavigation
|
2
|
+
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def explicit_navigation_args
|
6
|
+
self.adapter.controller.instance_variable_get(:"@sn_current_navigation_args")
|
7
|
+
end
|
8
|
+
|
9
|
+
# Reads the current navigation for the specified level from the controller.
|
10
|
+
# Returns nil if there is no current navigation set for level.
|
11
|
+
def current_navigation_for(level)
|
12
|
+
self.adapter.controller.instance_variable_get(:"@sn_current_navigation_#{level}")
|
13
|
+
end
|
14
|
+
|
15
|
+
# If any navigation has been explicitely set in the controller this method evaluates the specified args set in the controller and sets
|
16
|
+
# the correct instance variable in the controller.
|
17
|
+
def handle_explicit_navigation
|
18
|
+
if SimpleNavigation.explicit_navigation_args
|
19
|
+
level, navigation = parse_explicit_navigation_args
|
20
|
+
self.adapter.controller.instance_variable_set(:"@sn_current_navigation_#{level}", navigation)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# TODO: refactor this ugly thing to make it nice and short
|
25
|
+
def parse_explicit_navigation_args
|
26
|
+
args = SimpleNavigation.explicit_navigation_args
|
27
|
+
args = [Hash.new] if args.empty?
|
28
|
+
if args.first.kind_of? Hash
|
29
|
+
options = args.first
|
30
|
+
else # args is a list of current navigation for several levels
|
31
|
+
options = {}
|
32
|
+
if args.size == 1 #only one navi-key has been specified, try to find out level
|
33
|
+
level = SimpleNavigation.primary_navigation.level_for_item(args.first)
|
34
|
+
options[:"level_#{level}"] = args.first if level
|
35
|
+
else
|
36
|
+
args.each_with_index {|arg, i| options[:"level_#{i + 1}"] = arg}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
#only the deepest level is relevant
|
40
|
+
level = options.inject(0) do |max, kv|
|
41
|
+
kv.first.to_s =~ /level_(\d)/
|
42
|
+
max = $1.to_i if $1.to_i > max
|
43
|
+
max
|
44
|
+
end
|
45
|
+
raise ArgumentError, "Invalid level specified or item key not found" if level == 0
|
46
|
+
[level, options[:"level_#{level}"]]
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# Adds methods for explicitely setting the current 'active' navigation to the controllers.
|
52
|
+
# Since version 2.0.0 the simple_navigation plugin determines the active navigation based on the current url by default (auto highlighting),
|
53
|
+
# so explicitely defining the active navigation in the controllers is only needed for edge cases where automatic highlighting does not work.
|
54
|
+
#
|
55
|
+
# On the controller class level, use the <tt>navigation</tt> method to set the active navigation for all actions in the controller.
|
56
|
+
# Let's assume that we have a primary navigation item :account which in turn has a sub navigation item :settings.
|
57
|
+
#
|
58
|
+
# ==== Examples
|
59
|
+
# class AccountController << ActionController
|
60
|
+
# navigation :account
|
61
|
+
# ...
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# class AccountSettingsController << ActionController
|
65
|
+
# navigation :settings
|
66
|
+
# ...
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# The first example sets the current primary navigation to :account for all actions. No active sub_navigation.
|
70
|
+
# 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.
|
71
|
+
#
|
72
|
+
# On the controller instance level, use the <tt>current_navigation</tt> method to define the active navigation for a specific action.
|
73
|
+
# The navigation item that is set in <tt>current_navigation</tt> overrides the one defined on the controller class level (see <tt>navigation</tt> method).
|
74
|
+
# Thus if you have an :account primary item with a :special sub navigation item:
|
75
|
+
#
|
76
|
+
# ==== Example
|
77
|
+
# class AccountController << ActionController
|
78
|
+
# navigation :account
|
79
|
+
#
|
80
|
+
# def your_special_action
|
81
|
+
# ...
|
82
|
+
# current_navigation :special
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# 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'.
|
87
|
+
#
|
88
|
+
# 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.
|
89
|
+
#
|
90
|
+
# Note 2: The specified symbols must match the keys for your navigation items in your config/navigation.rb file.
|
91
|
+
module ControllerMethods
|
92
|
+
def self.included(base) #:nodoc:
|
93
|
+
base.class_eval do
|
94
|
+
extend ClassMethods
|
95
|
+
include InstanceMethods
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
module ClassMethods
|
100
|
+
# Sets the active navigation for all actions in this controller.
|
101
|
+
#
|
102
|
+
# The specified symbol must match the keys for your navigation items in your config/navigation.rb file.
|
103
|
+
def navigation(*args)
|
104
|
+
self.class_eval do
|
105
|
+
define_method :sn_set_navigation do
|
106
|
+
current_navigation(*args)
|
107
|
+
end
|
108
|
+
protected :sn_set_navigation
|
109
|
+
before_filter :sn_set_navigation
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
module InstanceMethods
|
115
|
+
# Sets the active navigation. Call this method in any action to override the controller-wide active navigation
|
116
|
+
# specified by navigation.
|
117
|
+
#
|
118
|
+
# The specified symbol must match the keys for your navigation items in your config/navigation.rb file.
|
119
|
+
def current_navigation(*args)
|
120
|
+
@sn_current_navigation_args = args
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
class Item
|
127
|
+
|
128
|
+
def selected_by_config?
|
129
|
+
key == SimpleNavigation.current_navigation_for(@container.level)
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
class ItemContainer
|
135
|
+
|
136
|
+
def selected_item
|
137
|
+
self[SimpleNavigation.current_navigation_for(self.level)] || items.find {|i| i.selected?}
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
ActionController::Base.send(:include, SimpleNavigation::ControllerMethods)
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module SimpleNavigation
|
2
|
+
|
3
|
+
# View helpers to render the navigation.
|
4
|
+
#
|
5
|
+
# Use render_navigation as following to render your navigation:
|
6
|
+
# * call <tt>render_navigation</tt> without :level option to render your complete navigation as nested tree.
|
7
|
+
# * call <tt>render_navigation(:level => x)</tt> to render a specific navigation level (e.g. :level => 1 to render your primary navigation, :level => 2 to render the sub navigation and so forth)
|
8
|
+
# * call <tt>render_navigation(:level => 2..3)</tt> to render navigation levels 2 and 3).
|
9
|
+
# For example, you could use render_navigation(:level => 1) to render your primary navigation as tabs and render_navigation(:level => 2..3) to render the rest of the navigation as a tree in a sidebar.
|
10
|
+
#
|
11
|
+
# ==== Examples (using Haml)
|
12
|
+
# #primary_navigation= render_navigation(:level => 1)
|
13
|
+
#
|
14
|
+
# #sub_navigation= render_navigation(:level => 2)
|
15
|
+
#
|
16
|
+
# #nested_navigation= render_navigation
|
17
|
+
#
|
18
|
+
# #top_navigation= render_navigation(:level => 1..2)
|
19
|
+
# #sidebar_navigation= render_navigation(:level => 3)
|
20
|
+
module Helpers
|
21
|
+
|
22
|
+
# Renders the navigation according to the specified options-hash.
|
23
|
+
#
|
24
|
+
# The following options are supported:
|
25
|
+
# * <tt>:level</tt> - defaults to :all which renders the the sub_navigation for an active primary_navigation inside that active primary_navigation item.
|
26
|
+
# Specify a specific level to only render that level of navigation (e.g. :level => 1 for primary_navigation etc...).
|
27
|
+
# Specifiy a Range of levels to render only those specific levels (e.g. :level => 1..2 to render both your first and second levels, maybe you want to render your third level somewhere else on the page)
|
28
|
+
# * <tt>:expand_all</tt> - defaults to false. If set to true the all specified levels will be rendered as a fully expanded tree (always open). This is useful for javascript menus like Superfish.
|
29
|
+
# * <tt>:context</tt> - specifies the context for which you would render the navigation. Defaults to :default which loads the default navigation.rb (i.e. config/navigation.rb).
|
30
|
+
# If you specify a context then the plugin tries to load the configuration file for that context, e.g. if you call <tt>render_navigation(:context => :admin)</tt> the file config/admin_navigation.rb
|
31
|
+
# will be loaded and used for rendering the navigation.
|
32
|
+
# * <tt>:items</tt> - you can specify the items directly (e.g. if items are dynamically generated from database). See SimpleNavigation::ItemsProvider for documentation on what to provide as items.
|
33
|
+
# * <tt>:renderer</tt> - specify the renderer to be used for rendering the navigation. Either provide the Class or a symbol matching a registered renderer. Defaults to :list (html list renderer).
|
34
|
+
def render_navigation(options={})
|
35
|
+
options = apply_defaults(options)
|
36
|
+
load_config(options)
|
37
|
+
active_item_container = SimpleNavigation.active_item_container_for(options[:level])
|
38
|
+
active_item_container.render(options) unless active_item_container.nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the name of the currently active navigation item belonging to the specified level.
|
42
|
+
#
|
43
|
+
# The following options are supported:
|
44
|
+
# * <tt>:level</tt> - defaults to :all which returns the most specific/deepest selected item (the leaf).
|
45
|
+
# Specify a specific level to only look for the selected item in the specified level of navigation (e.g. :level => 1 for primary_navigation etc...).
|
46
|
+
# * <tt>:context</tt> - specifies the context for which you would like to find the active navigation item. Defaults to :default which loads the default navigation.rb (i.e. config/navigation.rb).
|
47
|
+
# If you specify a context then the plugin tries to load the configuration file for that context, e.g. if you call <tt>active_navigation_item_name(:context => :admin)</tt> the file config/admin_navigation.rb
|
48
|
+
# will be loaded and used for searching the active item.
|
49
|
+
# * <tt>:items</tt> - you can specify the items directly (e.g. if items are dynamically generated from database). See SimpleNavigation::ItemsProvider for documentation on what to provide as items.
|
50
|
+
#
|
51
|
+
# Returns an empty string if no active item can be found for the specified options
|
52
|
+
def active_navigation_item_name(options={})
|
53
|
+
options = apply_defaults(options)
|
54
|
+
load_config(options)
|
55
|
+
options[:level] = :leaves if options[:level] == :all
|
56
|
+
active_item_container = SimpleNavigation.active_item_container_for(options[:level])
|
57
|
+
if active_item_container && !active_item_container.selected_item.nil?
|
58
|
+
active_item_container.selected_item.name
|
59
|
+
else
|
60
|
+
''
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def load_config(options)
|
67
|
+
ctx = options.delete(:context)
|
68
|
+
SimpleNavigation.init_adapter_from self
|
69
|
+
SimpleNavigation.load_config(ctx)
|
70
|
+
SimpleNavigation::Configuration.eval_config(ctx)
|
71
|
+
SimpleNavigation.config.items(options[:items]) if options[:items]
|
72
|
+
SimpleNavigation.handle_explicit_navigation if SimpleNavigation.respond_to?(:handle_explicit_navigation)
|
73
|
+
raise "no primary navigation defined, either use a navigation config file or pass items directly to render_navigation" unless SimpleNavigation.primary_navigation
|
74
|
+
end
|
75
|
+
|
76
|
+
def apply_defaults(options)
|
77
|
+
options[:level] = options.delete(:levels) if options[:levels]
|
78
|
+
{:context => :default, :level => :all}.merge(options)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module SimpleNavigation
|
4
|
+
module Renderer
|
5
|
+
|
6
|
+
# This is the base class for all renderers.
|
7
|
+
#
|
8
|
+
# A renderer is responsible for rendering an ItemContainer and its containing items to HTML.
|
9
|
+
class Base
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
attr_reader :options, :adapter
|
13
|
+
|
14
|
+
def_delegators :adapter, :link_to, :content_tag
|
15
|
+
|
16
|
+
def initialize(options) #:nodoc:
|
17
|
+
@options = options
|
18
|
+
@adapter = SimpleNavigation.adapter
|
19
|
+
end
|
20
|
+
|
21
|
+
def expand_all?
|
22
|
+
!!options[:expand_all]
|
23
|
+
end
|
24
|
+
|
25
|
+
def level
|
26
|
+
options[:level] || :all
|
27
|
+
end
|
28
|
+
|
29
|
+
def skip_if_empty?
|
30
|
+
!!options[:skip_if_empty]
|
31
|
+
end
|
32
|
+
|
33
|
+
def include_sub_navigation?(item)
|
34
|
+
consider_sub_navigation?(item) && expand_sub_navigation?(item)
|
35
|
+
end
|
36
|
+
|
37
|
+
def render_sub_navigation_for(item)
|
38
|
+
item.sub_navigation.render(self.options)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Renders the specified ItemContainer to HTML.
|
42
|
+
#
|
43
|
+
# When implementing a renderer, please consider to call include_sub_navigation? to determin
|
44
|
+
# whether an item's sub_navigation should be rendered or not.
|
45
|
+
#
|
46
|
+
def render(item_container)
|
47
|
+
raise 'subclass responsibility'
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def consider_sub_navigation?(item)
|
53
|
+
return false if item.sub_navigation.nil?
|
54
|
+
case level
|
55
|
+
when :all
|
56
|
+
return true
|
57
|
+
when Integer
|
58
|
+
return false
|
59
|
+
when Range
|
60
|
+
return item.sub_navigation.level <= level.max
|
61
|
+
end
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
def expand_sub_navigation?(item)
|
66
|
+
expand_all? || item.selected?
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|