jordanyeo-simple-navigation 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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