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,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