jordanyeo-simple-navigation 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/CHANGELOG +265 -0
  2. data/Gemfile +17 -0
  3. data/README +22 -0
  4. data/Rakefile +47 -0
  5. data/VERSION +1 -0
  6. data/generators/navigation_config/USAGE +1 -0
  7. data/generators/navigation_config/navigation_config_generator.rb +8 -0
  8. data/generators/navigation_config/templates/config/navigation.rb +76 -0
  9. data/lib/generators/navigation_config/navigation_config_generator.rb +12 -0
  10. data/lib/simple-navigation.rb +1 -0
  11. data/lib/simple_navigation.rb +167 -0
  12. data/lib/simple_navigation/adapters.rb +10 -0
  13. data/lib/simple_navigation/adapters/base.rb +37 -0
  14. data/lib/simple_navigation/adapters/nanoc.rb +45 -0
  15. data/lib/simple_navigation/adapters/padrino.rb +20 -0
  16. data/lib/simple_navigation/adapters/rails.rb +93 -0
  17. data/lib/simple_navigation/adapters/sinatra.rb +69 -0
  18. data/lib/simple_navigation/core.rb +5 -0
  19. data/lib/simple_navigation/core/configuration.rb +72 -0
  20. data/lib/simple_navigation/core/item.rb +144 -0
  21. data/lib/simple_navigation/core/item_adapter.rb +63 -0
  22. data/lib/simple_navigation/core/item_container.rb +147 -0
  23. data/lib/simple_navigation/core/items_provider.rb +35 -0
  24. data/lib/simple_navigation/rails_controller_methods.rb +144 -0
  25. data/lib/simple_navigation/rendering.rb +12 -0
  26. data/lib/simple_navigation/rendering/helpers.rb +123 -0
  27. data/lib/simple_navigation/rendering/renderer/base.rb +107 -0
  28. data/lib/simple_navigation/rendering/renderer/breadcrumbs.rb +59 -0
  29. data/lib/simple_navigation/rendering/renderer/json.rb +29 -0
  30. data/lib/simple_navigation/rendering/renderer/links.rb +32 -0
  31. data/lib/simple_navigation/rendering/renderer/list.rb +29 -0
  32. data/lib/simple_navigation/rendering/renderer/text.rb +26 -0
  33. data/rails/init.rb +1 -0
  34. data/spec/lib/simple_navigation/adapters/padrino_spec.rb +31 -0
  35. data/spec/lib/simple_navigation/adapters/rails_spec.rb +287 -0
  36. data/spec/lib/simple_navigation/adapters/sinatra_spec.rb +80 -0
  37. data/spec/lib/simple_navigation/core/configuration_spec.rb +128 -0
  38. data/spec/lib/simple_navigation/core/item_adapter_spec.rb +212 -0
  39. data/spec/lib/simple_navigation/core/item_container_spec.rb +451 -0
  40. data/spec/lib/simple_navigation/core/item_spec.rb +566 -0
  41. data/spec/lib/simple_navigation/core/items_provider_spec.rb +60 -0
  42. data/spec/lib/simple_navigation/rails_controller_methods_spec.rb +249 -0
  43. data/spec/lib/simple_navigation/rendering/helpers_spec.rb +276 -0
  44. data/spec/lib/simple_navigation/rendering/renderer/base_spec.rb +199 -0
  45. data/spec/lib/simple_navigation/rendering/renderer/breadcrumbs_spec.rb +101 -0
  46. data/spec/lib/simple_navigation/rendering/renderer/json_spec.rb +48 -0
  47. data/spec/lib/simple_navigation/rendering/renderer/links_spec.rb +64 -0
  48. data/spec/lib/simple_navigation/rendering/renderer/list_spec.rb +211 -0
  49. data/spec/lib/simple_navigation/rendering/renderer/text_spec.rb +41 -0
  50. data/spec/lib/simple_navigation_spec.rb +307 -0
  51. data/spec/spec_helper.rb +108 -0
  52. metadata +199 -0
@@ -0,0 +1,5 @@
1
+ require 'simple_navigation/core/configuration'
2
+ require 'simple_navigation/core/item_adapter'
3
+ require 'simple_navigation/core/item'
4
+ require 'simple_navigation/core/item_container'
5
+ require 'simple_navigation/core/items_provider'
@@ -0,0 +1,72 @@
1
+ require 'singleton'
2
+
3
+ module SimpleNavigation
4
+
5
+ # Responsible for evaluating and handling the config/navigation.rb file.
6
+ class Configuration
7
+ include Singleton
8
+
9
+ attr_accessor :renderer, :selected_class, :active_leaf_class, :autogenerate_item_ids, :id_generator, :auto_highlight, :name_generator
10
+ attr_reader :primary_navigation
11
+
12
+ class << self
13
+
14
+ # Evals the config_file for the given navigation_context
15
+ def eval_config(navigation_context = :default)
16
+ SimpleNavigation.context_for_eval.instance_eval(SimpleNavigation.config_files[navigation_context])
17
+ end
18
+
19
+ # Starts processing the configuration
20
+ def run(&block)
21
+ block.call Configuration.instance
22
+ end
23
+
24
+ end #class << self
25
+
26
+ # Sets the config's default-settings
27
+ def initialize
28
+ @renderer = SimpleNavigation.default_renderer || SimpleNavigation::Renderer::List
29
+ @selected_class = 'selected'
30
+ @active_leaf_class = 'simple-navigation-active-leaf'
31
+ @autogenerate_item_ids = true
32
+ @id_generator = Proc.new {|id| id.to_s }
33
+ @name_generator = Proc.new {|name| name}
34
+ @auto_highlight = true
35
+ end
36
+
37
+ # This is the main method for specifying the navigation items. It can be used in two ways:
38
+ #
39
+ # 1. Declaratively specify your items in the config/navigation.rb file using a block. It then yields an SimpleNavigation::ItemContainer for adding navigation items.
40
+ # 1. Directly provide your items to the method (e.g. when loading your items from the database).
41
+ #
42
+ # ==== Example for block style (configuration file)
43
+ # config.items do |primary|
44
+ # primary.item :my_item, 'My item', my_item_path
45
+ # ...
46
+ # end
47
+ #
48
+ # ==== To consider when directly providing items
49
+ # items_provider should be:
50
+ # * a methodname (as symbol) that returns your items. The method needs to be available in the view (i.e. a helper method)
51
+ # * an object that responds to :items
52
+ # * an enumerable containing your items
53
+ # The items you specify have to fullfill certain requirements. See SimpleNavigation::ItemAdapter for more details.
54
+ #
55
+ def items(items_provider=nil, &block)
56
+ raise 'please specify either items_provider or block, but not both' if (items_provider && block) || (items_provider.nil? && block.nil?)
57
+ @primary_navigation = ItemContainer.new
58
+ if block
59
+ block.call @primary_navigation
60
+ else
61
+ @primary_navigation.items = SimpleNavigation::ItemsProvider.new(items_provider).items
62
+ end
63
+ end
64
+
65
+ # Returns true if the config_file has already been evaluated.
66
+ def loaded?
67
+ !@primary_navigation.nil?
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,144 @@
1
+ module SimpleNavigation
2
+
3
+ # Represents an item in your navigation. Gets generated by the item method in the config-file.
4
+ class Item
5
+ attr_reader :key, :url, :sub_navigation, :method, :highlights_on
6
+ attr_writer :html_options
7
+
8
+ # see ItemContainer#item
9
+ #
10
+ # The subnavigation (if any) is either provided by a block or passed in directly as <tt>items</tt>
11
+ def initialize(container, key, name, url_or_options = {}, options_or_nil={}, items=nil, &sub_nav_block)
12
+ @container = container
13
+ options = setup_url_and_options(url_or_options, options_or_nil)
14
+ @container.dom_class = options.delete(:container_class) if options[:container_class]
15
+ @container.dom_id = options.delete(:container_id) if options[:container_id]
16
+ @container.selected_class = options.delete(:selected_class) if options[:selected_class]
17
+ @key = key
18
+ @method = options.delete(:method)
19
+ @name = name
20
+ if sub_nav_block || items
21
+ @sub_navigation = ItemContainer.new(@container.level + 1)
22
+ sub_nav_block.call @sub_navigation if sub_nav_block
23
+ @sub_navigation.items = items if items
24
+ end
25
+ end
26
+
27
+ # Returns the item's name. If option :apply_generator is set to true (default),
28
+ # the name will be passed to the name_generator specified in the configuration.
29
+ #
30
+ def name(options = {})
31
+ options.reverse_merge!(:apply_generator => true)
32
+ if (options[:apply_generator])
33
+ SimpleNavigation.config.name_generator.call(@name)
34
+ else
35
+ @name
36
+ end
37
+ end
38
+
39
+ # Returns true if this navigation item should be rendered as 'selected'.
40
+ # An item is selected if
41
+ #
42
+ # * it has been explicitly selected in a controller or
43
+ # * it has a subnavigation and one of its subnavigation items is selected or
44
+ # * its url matches the url of the current request (auto highlighting)
45
+ #
46
+ def selected?
47
+ @selected = @selected || selected_by_config? || selected_by_subnav? || selected_by_condition?
48
+ end
49
+
50
+ # Returns the html-options hash for the item, i.e. the options specified for this item in the config-file.
51
+ # It also adds the 'selected' class to the list of classes if necessary.
52
+ def html_options
53
+ default_options = self.autogenerate_item_ids? ? {:id => autogenerated_item_id} : {}
54
+ options = default_options.merge(@html_options)
55
+ options[:class] = [@html_options[:class], self.selected_class, self.active_leaf_class].flatten.compact.join(' ')
56
+ options.delete(:class) if options[:class].nil? || options[:class] == ''
57
+ options
58
+ end
59
+
60
+ # Returns the configured active_leaf_class if the item is the selected leaf,
61
+ # nil otherwise
62
+ def active_leaf_class
63
+ !selected_by_subnav? && selected_by_condition? ? SimpleNavigation.config.active_leaf_class : nil
64
+ end
65
+
66
+ # Returns the configured selected_class if the item is selected,
67
+ # nil otherwise
68
+ def selected_class
69
+ selected? ? (@container.selected_class || SimpleNavigation.config.selected_class) : nil
70
+ end
71
+
72
+ protected
73
+
74
+ # Returns true if item has a subnavigation and the sub_navigation is selected
75
+ def selected_by_subnav?
76
+ sub_navigation && sub_navigation.selected?
77
+ end
78
+
79
+ def selected_by_config?
80
+ false
81
+ end
82
+
83
+ # Returns true if the item's url matches the request's current url.
84
+ def selected_by_condition?
85
+ if highlights_on
86
+ case highlights_on
87
+ when Regexp
88
+ SimpleNavigation.request_uri =~ highlights_on
89
+ when Proc
90
+ highlights_on.call
91
+ when :subpath
92
+ !!(SimpleNavigation.request_uri =~ /^#{Regexp.escape url_without_anchor}(\/|$|\?)/i)
93
+ else
94
+ raise ArgumentError, ':highlights_on must be a Regexp, Proc or :subpath'
95
+ end
96
+ elsif auto_highlight?
97
+ !!(root_path_match? || (url_without_anchor && SimpleNavigation.current_page?(url_without_anchor)))
98
+ else
99
+ false
100
+ end
101
+ end
102
+
103
+ # Returns true if both the item's url and the request's url are root_path
104
+ def root_path_match?
105
+ url == '/' && SimpleNavigation.request_path == '/'
106
+ end
107
+
108
+ # Returns true if the item's id should be added to the rendered output.
109
+ def autogenerate_item_ids?
110
+ SimpleNavigation.config.autogenerate_item_ids
111
+ end
112
+
113
+ # Returns the item's id which is added to the rendered output.
114
+ def autogenerated_item_id
115
+ SimpleNavigation.config.id_generator.call(key)
116
+ end
117
+
118
+ # Return true if auto_highlight is on for this item.
119
+ def auto_highlight?
120
+ SimpleNavigation.config.auto_highlight && @container.auto_highlight
121
+ end
122
+
123
+ def url_without_anchor
124
+ url && url.split('#').first
125
+ end
126
+
127
+ private
128
+ def setup_url_and_options(url_or_options, options_or_nil)
129
+ options = options_or_nil
130
+ url = url_or_options
131
+ case url_or_options
132
+ when Hash
133
+ # url_or_options is options (there is no url)
134
+ options = url_or_options
135
+ when Proc
136
+ @url = url.call
137
+ else
138
+ @url = url
139
+ end
140
+ @highlights_on = options.delete(:highlights_on)
141
+ @html_options = options
142
+ end
143
+ end
144
+ 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.is_a?(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,147 @@
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, :selected_class
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.) <tt>url</tt> is optional - items without URLs should not be rendered as links.
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_or_options = {}, options_or_nil = {}, &block)
38
+ options = url_or_options.is_a?(Hash) ? url_or_options : options_or_nil
39
+ (@items << SimpleNavigation::Item.new(self, key, name, url_or_options, options_or_nil, nil, &block)) if should_add_item?(options)
40
+ end
41
+
42
+ def items=(items)
43
+ items.each do |item|
44
+ item = SimpleNavigation::ItemAdapter.new(item)
45
+ (@items << item.to_simple_navigation_item(self)) if should_add_item?(item.options)
46
+ end
47
+ end
48
+
49
+ # Returns the Item with the specified key, nil otherwise.
50
+ #
51
+ def [](navi_key)
52
+ items.find {|i| i.key == navi_key}
53
+ end
54
+
55
+ # Returns the level of the item specified by navi_key.
56
+ # Recursively works its way down the item's sub_navigations if the desired item is not found directly in this container's items.
57
+ # Returns nil item cannot be found.
58
+ #
59
+ def level_for_item(navi_key)
60
+ my_item = self[navi_key]
61
+ return self.level if my_item
62
+ items.each do |i|
63
+ if i.sub_navigation
64
+ level = i.sub_navigation.level_for_item(navi_key)
65
+ return level unless level.nil?
66
+ end
67
+ end
68
+ return nil
69
+ end
70
+
71
+ # Renders the items in this ItemContainer using the configured renderer.
72
+ #
73
+ # The options are the same as in the view's render_navigation call (they get passed on)
74
+ def render(options={})
75
+ renderer_instance = if options[:renderer]
76
+ if options[:renderer].instance_of?(Symbol) && SimpleNavigation.registered_renderers.key?(options[:renderer])
77
+ SimpleNavigation.registered_renderers[options[:renderer]].new(options)
78
+ else
79
+ options[:renderer].new(options)
80
+ end
81
+ else
82
+ self.renderer.new(options)
83
+ end
84
+ renderer_instance.render(self)
85
+ end
86
+
87
+ # Returns true if any of this container's items is selected.
88
+ #
89
+ def selected?
90
+ items.any? {|i| i.selected?}
91
+ end
92
+
93
+ # Returns the currently selected item, nil if no item is selected.
94
+ #
95
+ def selected_item
96
+ items.find {|i| i.selected?}
97
+ end
98
+
99
+ # Returns the active item_container for the specified level
100
+ # (recursively looks up items in selected sub_navigation if level is deeper than this container's level).
101
+ #
102
+ def active_item_container_for(desired_level)
103
+ return self if self.level == desired_level
104
+ return nil unless selected_sub_navigation?
105
+ return selected_item.sub_navigation.active_item_container_for(desired_level)
106
+ end
107
+
108
+ # Returns the deepest possible active item_container.
109
+ # (recursively searches in the sub_navigation if this container has a selected sub_navigation).
110
+ def active_leaf_container
111
+ if selected_sub_navigation?
112
+ selected_item.sub_navigation.active_leaf_container
113
+ else
114
+ self
115
+ end
116
+ end
117
+
118
+ # Returns true if there are no items defined for this container.
119
+ def empty?
120
+ items.empty?
121
+ end
122
+
123
+ private
124
+
125
+ def selected_sub_navigation?
126
+ !!(selected_item && selected_item.sub_navigation)
127
+ end
128
+
129
+ # partially borrowed from ActionSupport::Callbacks
130
+ def should_add_item?(options) #:nodoc:
131
+ [options.delete(:if)].flatten.compact.all? { |m| evaluate_method(m) } &&
132
+ ![options.delete(:unless)].flatten.compact.any? { |m| evaluate_method(m) }
133
+ end
134
+
135
+ # partially borrowed from ActionSupport::Callbacks
136
+ def evaluate_method(method) #:nodoc:
137
+ case method
138
+ when Proc, Method
139
+ method.call
140
+ else
141
+ raise ArgumentError, ":if or :unless must be procs or lambdas"
142
+ end
143
+ end
144
+
145
+ end
146
+
147
+ 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