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,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,12 @@
1
+ require 'simple_navigation/rendering/helpers'
2
+ require 'simple_navigation/rendering/renderer/base'
3
+
4
+ module SimpleNavigation
5
+ module Renderer
6
+ autoload :List, 'simple_navigation/rendering/renderer/list'
7
+ autoload :Links, 'simple_navigation/rendering/renderer/links'
8
+ autoload :Breadcrumbs, 'simple_navigation/rendering/renderer/breadcrumbs'
9
+ autoload :Text, 'simple_navigation/rendering/renderer/text'
10
+ autoload :Json, 'simple_navigation/rendering/renderer/json'
11
+ end
12
+ end
@@ -0,0 +1,123 @@
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
+ #
35
+ # Instead of using the <tt>:items</tt> option, a block can be passed to specify the items dynamically
36
+ #
37
+ # render_navigation do |menu|
38
+ # menu.item :posts, "Posts", posts_path
39
+ # end
40
+ #
41
+ def render_navigation(options={},&block)
42
+ container = active_navigation_item_container(options,&block)
43
+ container && container.render(options)
44
+ end
45
+
46
+ # Returns the name of the currently active navigation item belonging to the specified level.
47
+ #
48
+ # See Helpers#active_navigation_item for supported options.
49
+ #
50
+ # Returns an empty string if no active item can be found for the specified options
51
+ def active_navigation_item_name(options={})
52
+ active_navigation_item(options,'') { |item| item.name(:apply_generator => false) }
53
+ end
54
+
55
+ # Returns the key of the currently active navigation item belonging to the specified level.
56
+ #
57
+ # See Helpers#active_navigation_item for supported options.
58
+ #
59
+ # Returns <tt>nil</tt> if no active item can be found for the specified options
60
+ def active_navigation_item_key(options={})
61
+ active_navigation_item(options) { |item| item.key }
62
+ end
63
+
64
+ # Returns the currently active navigation item belonging to the specified level.
65
+ #
66
+ # The following options are supported:
67
+ # * <tt>:level</tt> - defaults to :all which returns the most specific/deepest selected item (the leaf).
68
+ # 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...).
69
+ # * <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).
70
+ # 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
71
+ # will be loaded and used for searching the active item.
72
+ # * <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.
73
+ #
74
+ # Returns the supplied <tt>value_for_nil</tt> object (<tt>nil</tt>
75
+ # by default) if no active item can be found for the specified
76
+ # options
77
+ def active_navigation_item(options={},value_for_nil = nil)
78
+ options[:level] = :leaves if options[:level].nil? || options[:level] == :all
79
+ container = active_navigation_item_container(options)
80
+ if container && (item = container.selected_item)
81
+ block_given? ? yield(item) : item
82
+ else
83
+ value_for_nil
84
+ end
85
+ end
86
+
87
+ # Returns the currently active item container belonging to the specified level.
88
+ #
89
+ # The following options are supported:
90
+ # * <tt>:level</tt> - defaults to :all which returns the least specific/shallowest selected item.
91
+ # 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...).
92
+ # * <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).
93
+ # 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
94
+ # will be loaded and used for searching the active item.
95
+ # * <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.
96
+ #
97
+ # Returns <tt>nil</tt> if no active item container can be found
98
+ def active_navigation_item_container(options={},&block)
99
+ options = SimpleNavigation::Helpers::apply_defaults(options)
100
+ SimpleNavigation::Helpers::load_config(options,self,&block)
101
+ container = SimpleNavigation.active_item_container_for(options[:level])
102
+ end
103
+
104
+ class << self
105
+ def load_config(options,includer,&block)
106
+ ctx = options.delete(:context)
107
+ SimpleNavigation.init_adapter_from includer
108
+ SimpleNavigation.load_config(ctx)
109
+ SimpleNavigation::Configuration.eval_config(ctx)
110
+ if block_given? || options[:items]
111
+ SimpleNavigation.config.items(options[:items],&block)
112
+ end
113
+ SimpleNavigation.handle_explicit_navigation if SimpleNavigation.respond_to?(:handle_explicit_navigation)
114
+ raise "no primary navigation defined, either use a navigation config file or pass items directly to render_navigation" unless SimpleNavigation.primary_navigation
115
+ end
116
+
117
+ def apply_defaults(options)
118
+ options[:level] = options.delete(:levels) if options[:levels]
119
+ {:context => :default, :level => :all}.merge(options)
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,107 @@
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
+ # to allow overriding when there is specific logic determining
70
+ # when a link should not be rendered (eg. breadcrumbs renderer
71
+ # does not render the final breadcrumb as a link when instructed
72
+ # not to do so.)
73
+ def suppress_link?(item)
74
+ item.url.nil?
75
+ end
76
+
77
+ # determine and return link or static content depending on
78
+ # item/renderer conditions.
79
+ def tag_for(item)
80
+ if suppress_link?(item)
81
+ content_tag('span', item.name, link_options_for(item).except(:method))
82
+ else
83
+ link_to("<div class=\"menubg\"></div><i class=\"icon-#{item.name.downcase}\"></i><span>" + item.name + '</span>', item.url, options_for(item))
84
+ end
85
+ end
86
+
87
+ # to allow overriding when link options should be special-cased
88
+ # (eg. links renderer uses item options for the a-tag rather
89
+ # than an li-tag).
90
+ def options_for(item)
91
+ link_options_for(item)
92
+ end
93
+
94
+ # Extracts the options relevant for the generated link
95
+ #
96
+ def link_options_for(item)
97
+ special_options = {:method => item.method, :class => item.selected_class}.reject {|k, v| v.nil? }
98
+ link_options = item.html_options[:link]
99
+ return special_options unless link_options
100
+ opts = special_options.merge(link_options)
101
+ opts[:class] = [link_options[:class], item.selected_class].flatten.compact.join(' ')
102
+ opts.delete(:class) if opts[:class].nil? || opts[:class] == ''
103
+ opts
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,59 @@
1
+ module SimpleNavigation
2
+ module Renderer
3
+
4
+ # Renders an ItemContainer as a <div> element and its containing items as <a> elements.
5
+ # It only renders 'selected' elements.
6
+ #
7
+ # By default, the renderer sets the item's key as dom_id for the rendered <a> element unless the config option <tt>autogenerate_item_ids</tt> is set to false.
8
+ # The id can also be explicitely specified by setting the id in the html-options of the 'item' method in the config/navigation.rb file.
9
+ # The ItemContainer's dom_class and dom_id are applied to the surrounding <div> element.
10
+ #
11
+ class Breadcrumbs < SimpleNavigation::Renderer::Base
12
+
13
+ def render(item_container)
14
+ content = a_tags(item_container).join(join_with)
15
+ content_tag(:div,
16
+ prefix_for(content) + content,
17
+ {:id => item_container.dom_id, :class => item_container.dom_class})
18
+ end
19
+
20
+ protected
21
+
22
+ def a_tags(item_container)
23
+ item_container.items.inject([]) do |list, item|
24
+ if item.selected?
25
+ list << tag_for(item)
26
+ if include_sub_navigation?(item)
27
+ list.concat a_tags(item.sub_navigation)
28
+ end
29
+ end
30
+ list
31
+ end
32
+ end
33
+
34
+ def join_with
35
+ @join_with ||= options[:join_with] || " "
36
+ end
37
+
38
+ def suppress_link?(item)
39
+ super || (options[:static_leaf] && item.active_leaf_class)
40
+ end
41
+
42
+ def prefix_for(content)
43
+ content.empty? ? '' : options[:prefix] || ''
44
+ end
45
+
46
+ # Extracts the options relevant for the generated link
47
+ #
48
+ def link_options_for(item)
49
+ if options[:allow_classes_and_ids]
50
+ opts = super
51
+ opts[:id] = "breadcrumb_#{opts[:id]}" if opts[:id]
52
+ opts
53
+ else
54
+ {:method => item.method}.merge(item.html_options.except(:class,:id))
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end