navigation 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ .bundle
3
+ vendor/bundle
4
+ Gemfile.lock
5
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem "rake"
7
+ gem "turn"
8
+ gem "mocha"
9
+ gem "minitest"
10
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Ryan Heath
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,158 @@
1
+ h1. Navigation Plugin for Rails
2
+
3
+ This is an experiment with a different way to create navigation menus in Rails. It supports navigation at the controller level, as well as the action level (for both nested or non-nested menus).
4
+
5
+ h2. Initialization
6
+
7
+ Probably the most fundamental difference with this plugin and most navigation plugins is this one requires the menus to be pre-defined before rendering. Here's an example of a typical menu definition (in @config/initializers/navigation.rb@):
8
+
9
+ <pre><code>RPH::Navigation::Builder.config do |navigation|
10
+ navigation.define :primary do |menu|
11
+ menu.item :home, :text => 'Welcome'
12
+ menu.item :about, :path => :about_us_path
13
+ menu.item :contact, :text => 'Contact Us'
14
+ end
15
+ end
16
+ </code></pre>
17
+
18
+ A caveat to this approach is that you don't have access to the named route helpers in an initializer, so you have to pass them as either a symbol or a string and they'll be eval'd at the right time. If you don't pass a route (i.e. the @:path@ option), it will try to do @"#{menu_item_name}_path"@. So the "home" item in the above example would get a path of "home_path" since there was no @:path@ option. And if "home_path" doesn't exist, the fallback is "root_path".
19
+
20
+ h2. Rendering the Menu
21
+
22
+ Once you have your menu(s) defined, it's pretty easy to render it:
23
+
24
+ <pre><code><%= navigation :primary %></code></pre>
25
+
26
+ You just have to pass the key of the name you gave it when it was defined. This is good for a number of reasons, but an added bonus is the ability to define and render different menus depending on who is logged in, for example.
27
+
28
+ <pre><code><%= navigation current_user.menu %></code></pre>
29
+
30
+ All of the logic related to roles/permissions could be abstracted into a @menu()@ method. Or if you're funny about sticking that logic in a model, you could call a helper or something. But you get the idea: key-based menu identification.
31
+
32
+ h2. Tips & Advanced Features
33
+
34
+ There are some additional things that you may want to know about this plugin, so keep reading if you're not satisfied yet.
35
+
36
+ h3. Passing a controller instance...
37
+
38
+ When I'm writing code to determine the "current tab" stuff, I basically just want this: when any action inside of the @HomeController@ gets rendered, highlight the "home" tab as the current. So why not allow the ability to pass the controller instance in the menu definition?
39
+
40
+ <pre><code>RPH::Navigation::Builder.config do |navigation|
41
+ navigation.define :primary do |menu|
42
+ menu.item HomeController, :text => 'Welcome'
43
+ menu.item AboutController, :path => :about_us_path
44
+ menu.item ContactController, :text => 'Contact Us'
45
+ end
46
+ end
47
+ </code></pre>
48
+
49
+ No more guessing games and no more @current_tab :home@ in your controllers.
50
+
51
+ h3. Rule-based navigation menus...
52
+
53
+ Sometimes you only want to show a menu based on a some condition. It happens. Just pass an @:if@ condition to the menu definition:
54
+
55
+ <pre><code>RPH::Navigation::Builder.config do |navigation|
56
+ navigation.define :primary, :if => Proc.new { |view| view.allowed_to_show_menu? } do |menu|
57
+ # ...
58
+ end
59
+ end
60
+ </code></pre>
61
+
62
+ You will automatically be handed a reference to the template/view so you can call any method that @ActionView@ is aware of (Note: if you need the controller, just use @view.controller@).
63
+
64
+ h3. Rule-based navigation menu items...
65
+
66
+ And sometimes you always want the menu present, but one or two of the tabs should only show up for certain reasons. Well, the @:if@ option works exactly the same for menu items as well:
67
+
68
+ <pre><code>RPH::Navigation::Builder.config do |navigation|
69
+ navigation.define :primary do |menu|
70
+ menu.item :home, :text => 'Welcome'
71
+ menu.item :about, :path => :about_us_path
72
+ menu.item :admin, :if => Proc.new { |view| view.logged_in_as_admin? }
73
+ end
74
+ end
75
+ </code></pre>
76
+
77
+ h3. Sub-menu navigation...
78
+
79
+ As we all know, a controller is made up of actions. And sometimes you want to not only provide navigation around your controllers, but also around the actions within a controller. Well, it's a pretty simple concept and now has a pretty simple solution. Just pass a block to the menu item:
80
+
81
+ <pre><code>RPH::Navigation::Builder.config do |navigation|
82
+ navigation.define :primary do |menu|
83
+ menu.item :home, :text => 'Welcome' do |sub_menu|
84
+ sub_menu.item :index, :text => 'Dashboard', :path => :home_path
85
+ sub_menu.item :friends, :path => :friends_path
86
+ end
87
+ menu.item :about, :path => :about_us_path
88
+ menu.item :contact, :text => 'Contact Us'
89
+ end
90
+ end
91
+ </code></pre>
92
+
93
+ If you pass a block to any of the menu items, the plugin will automatically know that this is a sub-menu and will render a completely new menu underneath of the parent menu item. And it's "current tab" will be based on the @action_name@ instead of the @controller_name@. Remember, this is if you want a *nested* action-level menu.
94
+
95
+ h3. Action-level navigation...
96
+
97
+ If you want to build a separate menu for the actions of a controller, but you _don't_ want to nest it under a parent menu item, you can tell the builder that during the definition by passing @:action_menu => true@:
98
+
99
+ <pre><code>RPH::Navigation::Builder.config do |navigation|
100
+ navigation.define :users, :action_menu => true do |menu|
101
+ # ...
102
+ end
103
+ end
104
+ </code></pre>
105
+
106
+ h3. Multiple menus...
107
+
108
+ This isn't a _feature_ per se, but one of the great outcomes of this experiment is the ability to keep all of your menu definitions in a single ruby file and in a single builder...
109
+
110
+ <pre><code>RPH::Navigation::Builder.config do |navigation|
111
+ navigation.define :public do |menu|
112
+ # ...
113
+ end
114
+
115
+ navigation.define :authenticated do |menu|
116
+ # ...
117
+ end
118
+
119
+ navigation.define :administrator do |menu|
120
+ # ...
121
+ end
122
+ end
123
+ </code></pre>
124
+
125
+ Having key-based menu identification works out really well for additional menu logic and keeping your views clean.
126
+
127
+ h3. Helper-based custom menu text
128
+
129
+ Sometimes the text on one of your menu items depend on some conditions. Like, for instance, maybe an inbox link might also show the number of new messages? You could define a helper like so:
130
+
131
+ <pre><code>def inbox_menu_text
132
+ ["Inbox", content_tag(:span, current_user.messages.unread.size]].join(" ")
133
+ end
134
+ </code></pre>
135
+
136
+ Then you can refer to that helper in the navigation definition using a Proc.
137
+
138
+ <pre><code>navigation.define :authenticated do |menu|
139
+ menu.item :messages, :text => Proc.new { |view| view.inbox_menu_text }
140
+ end
141
+ </code></pre>
142
+
143
+ This works for both controller and action-based menus.
144
+
145
+ h3. Manually overriding current sections
146
+
147
+ This doesn't work for action-based menus, but in controllers you can override the current section via the @current_section()@ method. For example:
148
+
149
+ <pre><code>class Admin::UsersController < ActionController::Base
150
+ current_section :administrator
151
+ end
152
+ </code></pre>
153
+
154
+ This is really only useful for controllers that don't have an explicit menu item, but you would still like one of the sections to be highlighted.
155
+
156
+ h3. That's all!
157
+
158
+ Feedback is welcome, and as always, feel free to fork and improve :-)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/navigation.rb ADDED
@@ -0,0 +1,42 @@
1
+ require "navigation/error"
2
+ require "navigation/base"
3
+ require "navigation/navigator"
4
+ require "navigation/nested_menu"
5
+ require "navigation/menu"
6
+ require "navigation/builder"
7
+
8
+ module Navigation
9
+ MENUS, SUBMENU = ActiveSupport::OrderedHash.new, :nested_navigation
10
+
11
+ module ControllerMethods
12
+ def current_section(section = nil)
13
+ self._current_section = section if section
14
+ self._current_section
15
+ end
16
+ end
17
+
18
+ module Helpers
19
+ # renders the navigation identified by
20
+ # the passed in name/key
21
+ #
22
+ # Ex:
23
+ # <%= navigation :primary %>
24
+ #
25
+ def navigation(key, options = {})
26
+ options.merge!(:view => self)
27
+ navigator = Navigator.new(key, options)
28
+ if navigator.allowed?
29
+ content_tag(:ul, navigator.links.join("\n").html_safe, options.merge!(:class => navigator.css_class))
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ ActionController::Base.class_eval do
36
+ extend Navigation::ControllerMethods
37
+ class_attribute :_current_section
38
+ end
39
+
40
+ ActionView::Base.class_eval do
41
+ include Navigation::Helpers
42
+ end
@@ -0,0 +1,11 @@
1
+ module Navigation
2
+ # superclass for all navigation classes
3
+ # (put common methods in here)
4
+ class Base
5
+ # keep everything in the same format
6
+ # for setting/getting hash values
7
+ def normalize(val)
8
+ val.to_s.underscore.sub(/_controller$/, '')
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ module Navigation
2
+ # responsible for the initial menu building/configuration
3
+ #
4
+ # Ex (config/initializers/navigation.rb):
5
+ # RPH::Navigation::Builder.config do |navigation|
6
+ # navigation.define :primary do |menu|
7
+ # ...
8
+ # end
9
+ # end
10
+ #
11
+ class Builder < Base
12
+ def self.config
13
+ # gives a Builder instance to the block
14
+ yield self.new
15
+ end
16
+
17
+ # accepts a name/key, and yields
18
+ # a Menu instance to the block
19
+ def define(*args)
20
+ name, options = args.first, args.extract_options!
21
+
22
+ raise InvalidMenuDefinition, InvalidMenuDefinition.message if name.blank?
23
+ yield Menu.new(normalize(name), options)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ module Navigation
2
+ # generic error superclass for convenient message setting
3
+ class Error < RuntimeError
4
+ def self.message(msg = nil)
5
+ msg.nil? ? @message : self.message = msg
6
+ end
7
+
8
+ def self.message=(msg)
9
+ @message = msg
10
+ end
11
+ end
12
+
13
+ # raised if the navigation() helper is called without any defined menus
14
+ class NoMenuDefinitions < Error
15
+ message "There are no menus defined (menus should be defined in config/initializers/navigation.rb)"
16
+ end
17
+
18
+ # raised if a menu definition is attempted, but the menu name/key is blank
19
+ class InvalidMenuDefinition < Error
20
+ message "You must pass a name when defining a navigation menu"
21
+ end
22
+
23
+ # raised if the navigation() helper is called, but the menu name/key is blank
24
+ class BlankMenuIdentifier < Error
25
+ message "You must pass an identifier (the name used when defining the navigation) to the navigation() helper"
26
+ end
27
+
28
+ # raised if the navigation() helper is called, but the menu name/key doesn't exist
29
+ class InvalidMenuIdentifier < Error
30
+ message "There is no menu defined for that identifier"
31
+ end
32
+
33
+ # raised if a block is passed to a nested menu item when being defined
34
+ class InvalidBlock < Error
35
+ message "You cannot pass a block to a nested menu item"
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ module Navigation
2
+ # handles adding items
3
+ # to a specific menu
4
+ class Menu < Base
5
+ def initialize(name, options = {})
6
+ @name = name
7
+ (MENUS[@name] = ActiveSupport::OrderedHash.new).merge!(
8
+ :action_menu => options[:action_menu], :if => options[:if])
9
+ end
10
+
11
+ def item(key, options = {}, &block)
12
+ key = normalize(key)
13
+
14
+ MENUS[@name].merge!(key => options)
15
+
16
+ # if a block is given to this method, it is
17
+ # assumed that there will be nested navigation,
18
+ # so yield a NestedMenu instance back to the block
19
+ yield NestedMenu.new(@name, key) if block_given?
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,111 @@
1
+ module Navigation
2
+ class Navigator < Base
3
+ attr_reader :name, :view
4
+
5
+ def initialize(name, options)
6
+ # must have menu definitions at this point
7
+ raise NoMenuDefinitions, NoMenuDefinitions.message if MENUS.blank?
8
+ # since multiple menus are supported, must have the name of the desired menu
9
+ raise BlankMenuIdentifier, BlankMenuIdentifier.message if name.blank?
10
+ # menu name/key must exist in the current menu definitions
11
+ raise InvalidMenuIdentifier, InvalidMenuIdentifier.message unless valid?(name)
12
+
13
+ # need a reference of the view/view
14
+ # for building out the HTML
15
+ @view = options.delete(:view)
16
+
17
+ # initialization
18
+ @name, @options = normalize(name), options
19
+
20
+ # support building separate menus for actions,
21
+ # without forcing it to be nested
22
+ @action_menu = MENUS[@name][:action_menu]
23
+
24
+ # menus can be shown/hidden based on
25
+ # conditions by passing a Proc object
26
+ # Ex:
27
+ # navigation.define :primary, :if => Proc.new { |c| c.logged_in? } do |menu|
28
+ # ...
29
+ # end
30
+ @proc = MENUS[@name][:if]
31
+ end
32
+
33
+ # the css class of the
34
+ # parent <ul> element
35
+ def css_class
36
+ @options[:class] || 'navigation'
37
+ end
38
+
39
+ # returns the list items, all
40
+ # properly nested (if needed)
41
+ def links
42
+ construct_html(self.items)
43
+ end
44
+
45
+ def allowed?
46
+ execute_proc @proc
47
+ end
48
+
49
+ protected
50
+ # calls a proc and hands it
51
+ # the view/template instance
52
+ def execute_proc(proc)
53
+ return true unless proc.is_a?(Proc)
54
+ proc.call(self.view)
55
+ end
56
+
57
+ # convenience method
58
+ def items
59
+ MENUS[@name].except(:action_menu, :if)
60
+ end
61
+
62
+ # checks to make sure the menu
63
+ # name/key actually exists
64
+ def valid?(key)
65
+ MENUS.keys.map(&:to_sym).include?(key.to_sym)
66
+ end
67
+
68
+ # builds the HTML from the MENUS hash
69
+ # (does a recursive call for nested menus)
70
+ def construct_html(menu, nested = false)
71
+ return if menu.blank?
72
+
73
+ links = menu.inject([]) do |items, (item, opts)|
74
+ next(items) unless execute_proc(opts[:if])
75
+
76
+ text, path, attrs = self.disect(item, opts)
77
+ subnav = construct_html(menu[item][SUBMENU], true).to_s
78
+ attrs.merge!(:class => [attrs[:class], self.current_css(item, nested)].compact.join(' '))
79
+ items << self.view.content_tag(:li, self.view.link_to(text, path) + subnav, attrs)
80
+ end
81
+
82
+ nested ? self.view.content_tag(:ul, links, :class => 'sub-navigation') : links
83
+ end
84
+
85
+ # determines if the menu item matches the current section
86
+ # (considers both levels: controller and action)
87
+ def current_css(item, nested = false)
88
+ name = if (nested || @action_menu)
89
+ self.view.action_name
90
+ else
91
+ (controller_override = self.view.controller.class.current_section).blank? ?
92
+ self.view.controller_name : controller_override
93
+ end
94
+
95
+ 'current' if normalize(name) == normalize(item)
96
+ end
97
+
98
+ # pulls out the goodies for use
99
+ # in the HTML construction
100
+ def disect(item, opts)
101
+ opts = HashWithIndifferentAccess.new(opts)#.symbolize_keys!
102
+
103
+ path = self.view.send(opts.delete(:path) || "#{item.to_s.underscore}_path" || :root_path)
104
+ text = opts.delete(:text) || item.to_s.titleize
105
+ text = execute_proc(text) if text.is_a?(Proc)
106
+ attrs = opts.except(SUBMENU, :if)
107
+
108
+ [text, path, attrs]
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,19 @@
1
+ module Navigation
2
+ # handles adding nested menu items
3
+ # to a parent menu item
4
+ class NestedMenu < Base
5
+ def initialize(menu, parent)
6
+ @menu, @parent = menu, parent
7
+
8
+ # the entire nested menu is stored as a nested ordered hash
9
+ # since we want multiple items in the same hash, only initialize once
10
+ MENUS[@menu][@parent][SUBMENU] = ActiveSupport::OrderedHash.new unless MENUS[@menu][@parent].has_key?(SUBMENU)
11
+ end
12
+
13
+ def item(key, options = {})
14
+ # only support one level of nesting (the action-level)
15
+ raise InvalkeyBlock, InvalkeyBlock.message if block_given?
16
+ MENUS[@menu][@parent][SUBMENU].merge!(normalize(key) => options)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module Navigation
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'navigation/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "navigation"
8
+ gem.version = Navigation::VERSION
9
+ gem.authors = ["Ryan Heath"]
10
+ gem.email = ["ryan@rpheath.com"]
11
+ gem.description = %q{Navigation helpers for building menus in Rails}
12
+ gem.summary = %q{Navigation helpers for building menus in Rails}
13
+ gem.homepage = "http://github.com/rpheath/navigation"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test)/})
18
+ gem.require_paths = ["lib"]
19
+ end
@@ -0,0 +1,135 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ Nav = Class.new(Navigation::Navigator)
4
+
5
+ class NavigationTest < ActiveSupport::TestCase
6
+ def setup
7
+ @view = ActionView::Base.new
8
+ @basic = Nav.new(:basic, { :view => @view })
9
+ @menu_with_proc = Nav.new(:menu_with_proc, { :view => @view })
10
+ @custom_menu = Nav.new(:basic, { :view => @view, :class => "navigator" })
11
+ @menu_with_submenu = Nav.new(:menu_with_submenu, { :view => @view })
12
+ end
13
+
14
+ context "default behavior" do
15
+ test "should be basic navigation" do
16
+ assert_equal @basic.name, "basic"
17
+ end
18
+
19
+ test "should have access to the view" do
20
+ assert_equal @basic.view, @view
21
+ end
22
+
23
+ test "should have a 'navigation' css class" do
24
+ assert_equal @basic.css_class, "navigation"
25
+ end
26
+
27
+ test "should be 'allowed' if no Proc is specified" do
28
+ assert @basic.allowed?
29
+ end
30
+
31
+ test "menu items should be wrapped in an OrderedHash" do
32
+ assert @basic.send(:items).is_a?(ActiveSupport::OrderedHash)
33
+ end
34
+
35
+ test "should have appropriate items (and in order)" do
36
+ items = ActiveSupport::OrderedHash.new
37
+ %w(home features events).each do |menu_item|
38
+ items[menu_item] = {}
39
+ end
40
+
41
+ assert_equal @basic.send(:items), items
42
+ end
43
+ end
44
+
45
+ context "custom menu options" do
46
+ test "should have a custom CSS class" do
47
+ assert_equal @custom_menu.css_class, "navigator"
48
+ end
49
+ end
50
+
51
+ context "menu validation" do
52
+ test "should be a valid menu" do
53
+ assert @basic.send(:valid?, :basic)
54
+ end
55
+
56
+ test "should not be a valid menu" do
57
+ assert !@basic.send(:valid?, :invalid_menu)
58
+ end
59
+ end
60
+
61
+ context "menu with Proc dependency" do
62
+ test "should be a valid menu" do
63
+ assert @menu_with_proc.send(:valid?, :menu_with_proc)
64
+ end
65
+
66
+ test "should show the menu" do
67
+ @view.instance_eval do
68
+ def show_menu?
69
+ true
70
+ end
71
+ end
72
+
73
+ assert @menu_with_proc.allowed?
74
+ end
75
+
76
+ test "should NOT show the menu" do
77
+ @view.instance_eval do
78
+ def show_menu?
79
+ false
80
+ end
81
+ end
82
+
83
+ assert !@menu_with_proc.allowed?
84
+ end
85
+ end
86
+
87
+ context "disecting menu items" do
88
+ test "should return an array with 3 items" do
89
+ result = @basic.send(:disect, 'home', {})
90
+ assert result.is_a?(Array)
91
+ assert_equal 3, result.size
92
+ end
93
+
94
+ test "should return a disected menu item" do
95
+ result = @basic.send(:disect, 'home', {})
96
+ assert_equal ["Home", "/home", {}], result
97
+ end
98
+
99
+ test "should return a disected menu item with custom path and options" do
100
+ result = @basic.send(:disect, 'home',
101
+ { :path => "custom_home_path", :class => 'custom', :text => "Custom Home" })
102
+ assert_equal ["Custom Home", "/path/to/home", { "class" => "custom" }], result
103
+ end
104
+
105
+ test "should return a disected menu item disregarding SUBMENU and :if options" do
106
+ result = @basic.send(:disect, 'home',
107
+ { RPH::Navigation::SUBMENU => "whatever", :if => Proc.new {} })
108
+ assert_equal ["Home", "/home", {}], result
109
+ end
110
+ end
111
+
112
+ context "constructing the HTML" do
113
+ test "should return the proper HTML links" do
114
+ @view.controller = HomeController.new
115
+ assert_equal @basic.links, [
116
+ "<li class=\"current\"><a href=\"/home\">Home</a></li>",
117
+ "<li class=\"\"><a href=\"/features\">Features</a></li>",
118
+ "<li class=\"\"><a href=\"/events\">Events</a></li>"
119
+ ]
120
+ end
121
+
122
+ test "should be current tab on the features link" do
123
+ @view.controller = FeaturesController.new
124
+ assert_equal @basic.links[1], "<li class=\"current\"><a href=\"/features\">Features</a></li>"
125
+ end
126
+
127
+ test "should return a nested submenu under the Home link" do
128
+ @view.controller = HomeController.new
129
+ assert_equal @menu_with_submenu.links, [
130
+ "<li class=\"current\"><a href=\"/home\">Home</a><ul class=\"sub-navigation\"><li class=\"current\"><a href=\"/\">Index</a></li></ul></li>",
131
+ "<li class=\"\"><a href=\"/features\">Features</a></li>"
132
+ ]
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,71 @@
1
+ require 'test/unit'
2
+ require 'action_view'
3
+ require 'active_support'
4
+ require 'active_support/test_case'
5
+ require 'context'
6
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'navigation')
7
+
8
+ class HomeController
9
+ def controller_name
10
+ 'home'
11
+ end
12
+
13
+ def action_name
14
+ 'index'
15
+ end
16
+ end
17
+
18
+ class FeaturesController
19
+ def controller_name
20
+ 'features'
21
+ end
22
+ end
23
+
24
+ class ActionView::Base
25
+ def root_path
26
+ '/'
27
+ end
28
+
29
+ def home_path
30
+ '/home'
31
+ end
32
+
33
+ def custom_home_path
34
+ '/path/to/home'
35
+ end
36
+
37
+ def features_path
38
+ '/features'
39
+ end
40
+
41
+ def events_path
42
+ '/events'
43
+ end
44
+
45
+ def show_menu?
46
+ true
47
+ end
48
+ end
49
+
50
+ Navigation::Builder.config do |navigation|
51
+ navigation.define :basic do |menu|
52
+ menu.item :home
53
+ menu.item :features
54
+ menu.item :events
55
+ end
56
+ end
57
+
58
+ Navigation::Builder.config do |navigation|
59
+ navigation.define :menu_with_proc, :if => Proc.new { |view| view.show_menu? } do |menu|
60
+ menu.item :home
61
+ end
62
+ end
63
+
64
+ Navigation::Builder.config do |navigation|
65
+ navigation.define :menu_with_submenu do |menu|
66
+ menu.item :home do |sub|
67
+ sub.item :index, :path => :root_path
68
+ end
69
+ menu.item :features
70
+ end
71
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: navigation
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Ryan Heath
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2014-10-16 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Navigation helpers for building menus in Rails
23
+ email:
24
+ - ryan@rpheath.com
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files: []
30
+
31
+ files:
32
+ - .gitignore
33
+ - Gemfile
34
+ - LICENSE
35
+ - README.textile
36
+ - Rakefile
37
+ - lib/navigation.rb
38
+ - lib/navigation/base.rb
39
+ - lib/navigation/builder.rb
40
+ - lib/navigation/error.rb
41
+ - lib/navigation/menu.rb
42
+ - lib/navigation/navigator.rb
43
+ - lib/navigation/nested_menu.rb
44
+ - lib/navigation/version.rb
45
+ - navigation.gemspec
46
+ - test/navigation_test.rb
47
+ - test/test_helper.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/rpheath/navigation
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ requirements: []
76
+
77
+ rubyforge_project:
78
+ rubygems_version: 1.6.2
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: Navigation helpers for building menus in Rails
82
+ test_files:
83
+ - test/navigation_test.rb
84
+ - test/test_helper.rb