├── VERSION
├── .gitignore
├── rails
└── init.rb
├── init.rb
├── Gemfile
├── generators
└── tiny_navigation
│ ├── USAGE
│ ├── tiny_navigation_generator.rb
│ └── templates
│ └── tiny_navigation.rb
├── test
├── test_helper.rb
└── navigation_test.rb
├── Gemfile.lock
├── MIT-LICENSE
├── lib
├── tiny_navigation
│ ├── controller
│ │ └── base.rb
│ └── data
│ │ ├── config.rb
│ │ ├── navigation.rb
│ │ └── item.rb
└── tiny_navigation.rb
├── Rakefile
├── tiny_navigation.gemspec
├── .specification
└── README.rdoc
/VERSION:
--------------------------------------------------------------------------------
1 | 1.1.1
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.gem
--------------------------------------------------------------------------------
/rails/init.rb:
--------------------------------------------------------------------------------
1 | require "tiny_navigation"
--------------------------------------------------------------------------------
/init.rb:
--------------------------------------------------------------------------------
1 | require File.dirname(__FILE__) + "/rails/init.rb"
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # A sample Gemfile
2 | source "https://rubygems.org"
3 |
4 | gem 'actionpack'
5 | gem 'activesupport'
6 | gem 'rake'
--------------------------------------------------------------------------------
/generators/tiny_navigation/USAGE:
--------------------------------------------------------------------------------
1 | Call:
2 |
3 | rails generate tiny_navigation
4 |
5 | to generate the config/tiny_navigation.rb file.
--------------------------------------------------------------------------------
/generators/tiny_navigation/tiny_navigation_generator.rb:
--------------------------------------------------------------------------------
1 | class TinyNavigationGenerator < Rails::Generator::Base
2 | def manifest
3 | record do |m|
4 | m.file "tiny_navigation.rb", "config/tiny_navigation.rb"
5 | end
6 | end
7 | end
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # require rails stuff
2 | require "rubygems"
3 | require "bundler"
4 |
5 | Bundler.setup
6 | require "test/unit"
7 | require "active_support"
8 |
9 |
10 | # require gem/plugin
11 | require "#{File.dirname(__FILE__)}/../init"
12 |
--------------------------------------------------------------------------------
/generators/tiny_navigation/templates/tiny_navigation.rb:
--------------------------------------------------------------------------------
1 | # Within this file you'll define your navigation data.
2 |
3 | # navigation :main do
4 | # # Menu Item
5 | # item "Foos", :to => "foos#index"
6 | #
7 | # # Menu Item with a sub-menu item
8 | # item "Bars", :to => "bars#index" do
9 | # item "Bazzes", :to => "bazzes#index"
10 | # end
11 | # end
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actionpack (3.2.12)
5 | activemodel (= 3.2.12)
6 | activesupport (= 3.2.12)
7 | builder (~> 3.0.0)
8 | erubis (~> 2.7.0)
9 | journey (~> 1.0.4)
10 | rack (~> 1.4.5)
11 | rack-cache (~> 1.2)
12 | rack-test (~> 0.6.1)
13 | sprockets (~> 2.2.1)
14 | activemodel (3.2.12)
15 | activesupport (= 3.2.12)
16 | builder (~> 3.0.0)
17 | activesupport (3.2.12)
18 | i18n (~> 0.6)
19 | multi_json (~> 1.0)
20 | builder (3.0.4)
21 | erubis (2.7.0)
22 | hike (1.2.1)
23 | i18n (0.6.4)
24 | journey (1.0.4)
25 | multi_json (1.6.1)
26 | rack (1.4.5)
27 | rack-cache (1.2)
28 | rack (>= 0.4)
29 | rack-test (0.6.2)
30 | rack (>= 1.0)
31 | rake (10.0.3)
32 | sprockets (2.2.2)
33 | hike (~> 1.2)
34 | multi_json (~> 1.0)
35 | rack (~> 1.0)
36 | tilt (~> 1.1, != 1.3.0)
37 | tilt (1.3.5)
38 |
39 | PLATFORMS
40 | ruby
41 |
42 | DEPENDENCIES
43 | actionpack
44 | activesupport
45 | rake
46 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010 Coroutine
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.
21 |
--------------------------------------------------------------------------------
/lib/tiny_navigation/controller/base.rb:
--------------------------------------------------------------------------------
1 | module Coroutine #:nodoc:
2 | module TinyNavigation #:nodoc:
3 | module Controller #:nodoc:
4 |
5 | # This module provides the core controller functionality implemented by
6 | # the gem.
7 | #
8 | module Base
9 |
10 | def self.included(base) #:nodoc:
11 | base.send(:include, InstanceMethods)
12 | base.send(:helper_method, :navigation)
13 | end
14 |
15 |
16 | # This module contains instance methods that will be mixed into the extended
17 | # controller.
18 | #
19 | module InstanceMethods
20 | private
21 |
22 | # This method returns a Coroutine::TinyNavigation::Data::Navigation object for
23 | # the supplied navigation name.
24 | #
25 | def navigation(which_navigation)
26 | config = Coroutine::TinyNavigation::Data::Config.new self
27 | config.nav[which_navigation]
28 | end
29 | end
30 |
31 | end
32 |
33 |
34 | end
35 | end
36 | end
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rake'
2 | require 'rake/testtask'
3 | require 'rdoc/task'
4 |
5 | desc 'Default: run unit tests.'
6 | task :default => :test
7 |
8 | desc 'Test the tiny_navigation plugin.'
9 | Rake::TestTask.new(:test) do |t|
10 | t.libs << 'lib'
11 | t.libs << 'test'
12 | t.pattern = 'test/**/*_test.rb'
13 | t.verbose = true
14 | end
15 |
16 | desc 'Generate documentation for the tiny_navigation plugin.'
17 | Rake::RDocTask.new(:rdoc) do |rdoc|
18 | rdoc.rdoc_dir = 'rdoc'
19 | rdoc.title = 'TinyNavigation'
20 | rdoc.options << '--line-numbers' << '--inline-source'
21 | rdoc.rdoc_files.include('README')
22 | rdoc.rdoc_files.include('lib/**/*.rb')
23 | end
24 |
25 | begin
26 | require 'jeweler'
27 | Jeweler::Tasks.new do |gemspec|
28 | gemspec.authors = ["Coroutine", "Tim Lowrimore"]
29 | gemspec.description = "TinyNavigation makes it easy to define site navigation using a small DSL."
30 | gemspec.email = "gems@coroutine.com"
31 | gemspec.homepage = "http://github.com/coroutine/tiny_navigation"
32 | gemspec.name = "tiny_navigation"
33 | gemspec.summary = "TinyNavigation provides an easy-to-use DSL for defining navigation structures."
34 |
35 | gemspec.add_dependency("actionpack", ">= 2.3.4")
36 | gemspec.add_development_dependency("activesupport", ">= 2.3.4")
37 | gemspec.files.include("lib/**/*.rb")
38 | end
39 | Jeweler::GemcutterTasks.new
40 | rescue LoadError
41 | puts "Jeweler not available. Install it with: gem install jeweler"
42 | end
--------------------------------------------------------------------------------
/lib/tiny_navigation/data/config.rb:
--------------------------------------------------------------------------------
1 | module Coroutine #:nodoc:
2 | module TinyNavigation #:nodoc:
3 | module Data #:nodoc:
4 |
5 | # This class represents a configuration object. It is responsible for reading in the
6 | # DSL and converting it to a structured set of navigation objects.
7 | #
8 | class Config #:nodoc:
9 |
10 | attr_reader :nav
11 |
12 | # This method creates a new configuration object. It reads in the configuration
13 | # file and saves the contents to a class variable so it only has to be loaded
14 | # once.
15 | #
16 | # current_controller is a reference to the controller object being extended.
17 | #
18 | # config is the location of the config file to load. Defaults to the generated
19 | # file.
20 | #
21 | def initialize(current_controller, conf=File.join(Rails.root, "config", "tiny_navigation.rb"))
22 | @current_controller = current_controller
23 | @nav = {}
24 |
25 | Config.class_eval { class << self; attr_reader :file end; @file ||= File.read(conf) }
26 |
27 | self.instance_eval(Config.file)
28 | end
29 |
30 |
31 | private
32 |
33 | # This method adds a navigation structure to the application.
34 | #
35 | # name is the key in the navigation hash.
36 | #
37 | def navigation(name, &block)
38 | raise "Navigation names must be unique. You specified '#{name}' twice." if @nav.has_key?(name)
39 | @nav[name] = Navigation.new(name, @current_controller, &block)
40 | end
41 |
42 | end
43 |
44 | end
45 | end
46 | end
--------------------------------------------------------------------------------
/tiny_navigation.gemspec:
--------------------------------------------------------------------------------
1 | # Generated by jeweler
2 | # DO NOT EDIT THIS FILE DIRECTLY
3 | # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4 | # -*- encoding: utf-8 -*-
5 |
6 | Gem::Specification.new do |s|
7 | s.name = %q{tiny_navigation}
8 | s.version = "1.1.1"
9 |
10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11 | s.authors = ["Coroutine", "Tim Lowrimore"]
12 | s.date = %q{2010-10-10}
13 | s.description = %q{TinyNavigation makes it easy to define site navigation using a small DSL.}
14 | s.email = %q{gems@coroutine.com}
15 | s.extra_rdoc_files = [
16 | "README.rdoc"
17 | ]
18 | s.files = [
19 | ".gitignore",
20 | ".specification",
21 | "MIT-LICENSE",
22 | "README.rdoc",
23 | "Rakefile",
24 | "VERSION",
25 | "generators/tiny_navigation/USAGE",
26 | "generators/tiny_navigation/templates/tiny_navigation.rb",
27 | "generators/tiny_navigation/tiny_navigation_generator.rb",
28 | "init.rb",
29 | "lib/tiny_navigation.rb",
30 | "lib/tiny_navigation/controller/base.rb",
31 | "lib/tiny_navigation/data/config.rb",
32 | "lib/tiny_navigation/data/item.rb",
33 | "lib/tiny_navigation/data/navigation.rb",
34 | "rails/init.rb",
35 | "test/navigation_test.rb",
36 | "test/test_helper.rb",
37 | "tiny_navigation.gemspec"
38 | ]
39 | s.homepage = %q{http://github.com/coroutine/tiny_navigation}
40 | s.rdoc_options = ["--charset=UTF-8"]
41 | s.require_paths = ["lib"]
42 | s.rubygems_version = %q{1.3.7}
43 | s.summary = %q{TinyNavigation provides an easy-to-use DSL for defining navigation structures.}
44 | s.test_files = [
45 | "test/navigation_test.rb",
46 | "test/test_helper.rb"
47 | ]
48 |
49 | if s.respond_to? :specification_version then
50 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
51 | s.specification_version = 3
52 |
53 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
54 | s.add_runtime_dependency(%q, [">= 2.3.4"])
55 | s.add_development_dependency(%q, [">= 2.3.4"])
56 | else
57 | s.add_dependency(%q, [">= 2.3.4"])
58 | s.add_dependency(%q, [">= 2.3.4"])
59 | end
60 | else
61 | s.add_dependency(%q, [">= 2.3.4"])
62 | s.add_dependency(%q, [">= 2.3.4"])
63 | end
64 | end
65 |
66 |
--------------------------------------------------------------------------------
/lib/tiny_navigation/data/navigation.rb:
--------------------------------------------------------------------------------
1 | module Coroutine #:nodoc:
2 | module TinyNavigation #:nodoc:
3 | module Data #:nodoc:
4 |
5 | # This class represents a navigation tree. It holds all the data for tree
6 | # and provides a number of convenience methods related to that tree.
7 | #
8 | class Navigation
9 |
10 | attr_reader :name, :items
11 |
12 | # This method creates a new navigation data object.
13 | #
14 | # name is the unique identifer of the navigation.
15 | #
16 | # current_controller the currently loaded controller
17 | #
18 | # The block given to the navigation item is used to define navigation
19 | # items of this navigation object.
20 | #
21 | def initialize(name, current_controller, &block)
22 | @items = []
23 | @name = name
24 | @current_controller = current_controller
25 |
26 | self.instance_eval(&block) if block_given?
27 | end
28 |
29 | # This method returns an array of selected navigation items. The array
30 | # represents a bread-crumb list in that the head of the list represents
31 | # a top-level navigation item, and the tail of the list represents selected
32 | # sub-navigation items.
33 | #
34 | # item is the reference point for the calculation.
35 | #
36 | def selected(item=self)
37 | items = []
38 | item.items.each do |item|
39 | items << item << selected(item) if item.selected?
40 | end
41 | items.flatten
42 | end
43 |
44 | # This method delegates method missing calls to the controller, in case
45 | # the navigation item has user-defined properties.
46 | #
47 | def method_missing(method_name, *args) #:nodoc:
48 | @current_controller.send method_name, *args
49 | end
50 |
51 |
52 | private
53 |
54 | # This method adds a new item to the items collection.
55 | #
56 | # name is the friendly name for the navigation item.
57 | #
58 | # options is a hash containing any user-defined properties.
59 | #
60 | def item(name, options={}, &block)
61 | @items << Item.new(name, @current_controller, options, &block)
62 | end
63 |
64 | end
65 |
66 | end
67 | end
68 | end
--------------------------------------------------------------------------------
/.specification:
--------------------------------------------------------------------------------
1 | --- !ruby/object:Gem::Specification
2 | name: tiny_navigation
3 | version: !ruby/object:Gem::Version
4 | hash: 23
5 | prerelease: false
6 | segments:
7 | - 1
8 | - 0
9 | - 0
10 | version: 1.0.0
11 | platform: ruby
12 | authors:
13 | - Coroutine
14 | - Tim Lowrimore
15 | autorequire:
16 | bindir: bin
17 | cert_chain: []
18 |
19 | date: 2010-10-10 00:00:00 -05:00
20 | default_executable:
21 | dependencies:
22 | - !ruby/object:Gem::Dependency
23 | name: actionpack
24 | prerelease: false
25 | requirement: &id001 !ruby/object:Gem::Requirement
26 | none: false
27 | requirements:
28 | - - ">="
29 | - !ruby/object:Gem::Version
30 | hash: 11
31 | segments:
32 | - 2
33 | - 3
34 | - 4
35 | version: 2.3.4
36 | type: :runtime
37 | version_requirements: *id001
38 | - !ruby/object:Gem::Dependency
39 | name: activesupport
40 | prerelease: false
41 | requirement: &id002 !ruby/object:Gem::Requirement
42 | none: false
43 | requirements:
44 | - - ">="
45 | - !ruby/object:Gem::Version
46 | hash: 11
47 | segments:
48 | - 2
49 | - 3
50 | - 4
51 | version: 2.3.4
52 | type: :development
53 | version_requirements: *id002
54 | description: TinyNavigation makes it easy to define site navigation using a small DSL.
55 | email: gems@coroutine.com
56 | executables: []
57 |
58 | extensions: []
59 |
60 | extra_rdoc_files:
61 | - README.rdoc
62 | files:
63 | - .gitignore
64 | - .specification
65 | - MIT-LICENSE
66 | - README.rdoc
67 | - Rakefile
68 | - VERSION
69 | - generators/tiny_navigation/USAGE
70 | - generators/tiny_navigation/templates/tiny_navigation.rb
71 | - generators/tiny_navigation/tiny_navigation_generator.rb
72 | - init.rb
73 | - lib/tiny_navigation.rb
74 | - lib/tiny_navigation/controller/base.rb
75 | - lib/tiny_navigation/data/config.rb
76 | - lib/tiny_navigation/data/item.rb
77 | - lib/tiny_navigation/data/navigation.rb
78 | - rails/init.rb
79 | - test/navigation_test.rb
80 | - test/test_helper.rb
81 | - tiny_navigation.gemspec
82 | has_rdoc: true
83 | homepage: http://github.com/coroutine/tiny_navigation
84 | licenses: []
85 |
86 | post_install_message:
87 | rdoc_options:
88 | - --charset=UTF-8
89 | require_paths:
90 | - lib
91 | required_ruby_version: !ruby/object:Gem::Requirement
92 | none: false
93 | requirements:
94 | - - ">="
95 | - !ruby/object:Gem::Version
96 | hash: 3
97 | segments:
98 | - 0
99 | version: "0"
100 | required_rubygems_version: !ruby/object:Gem::Requirement
101 | none: false
102 | requirements:
103 | - - ">="
104 | - !ruby/object:Gem::Version
105 | hash: 3
106 | segments:
107 | - 0
108 | version: "0"
109 | requirements: []
110 |
111 | rubyforge_project:
112 | rubygems_version: 1.3.7
113 | signing_key:
114 | specification_version: 3
115 | summary: TinyNavigation provides an easy-to-use DSL for defining navigation structures.
116 | test_files:
117 | - test/navigation_test.rb
118 | - test/test_helper.rb
119 |
120 |
--------------------------------------------------------------------------------
/lib/tiny_navigation/data/item.rb:
--------------------------------------------------------------------------------
1 | module Coroutine #:nodoc:
2 | module TinyNavigation #:nodoc:
3 | module Data #:nodoc:
4 |
5 | # This class represents a navigation item. It holds all the data for item
6 | # and provides a number of convenience methods related to that item.
7 | #
8 | class Item < Navigation
9 |
10 | # This method creates a new instance of a navigation item.
11 | #
12 | # name is the name of the navigation item. The name should
13 | # be used for the label text when rendering the navigation item.
14 | #
15 | # current_controller the currently loaded controller
16 | #
17 | # options provide configuration options and custom properties
18 | # to the nav item. Currently, the only configuration option is
19 | # :to which is used to generate the URL of the navigation item.
20 | # All other options provided to via the options hash will be
21 | # treated as custom properties on the navigation item. These custom
22 | # properties can be accessed as methods on the navigation item.
23 | #
24 | # The block given to the navigation item is used to define sub-navigation
25 | # items of this item.
26 | #
27 | def initialize(name, current_controller, options={}, &block)
28 | super name, current_controller, &block
29 | set_controller_and_action options.delete(:to)
30 |
31 | @selection_scope = options.delete(:selection_scope) || :controller
32 | @extra_options = options
33 | end
34 |
35 | # This method indicates whether the navigation item is currently selected.
36 | # This takes into account any sub-nav items such that a parent item is
37 | # selected if it has a selected child.
38 | #
39 | def selected?
40 | is_controller = @controller_name == @current_controller.controller_name
41 |
42 | case @selection_scope
43 | when :action
44 | is_controller && @action_name == @current_controller.action_name
45 | when :controller
46 | is_controller || @items.any?(&:selected?)
47 | end
48 | end
49 |
50 | # This method returns the URL to which the navigation item points. This
51 | # should be used in a scenario where the navigation item represents a link
52 | # and the URL is the href of that link.
53 | #
54 | def url
55 | @current_controller.url_for :controller => @controller_name, :action => @action_name
56 | end
57 |
58 | # This method uses the extra_options hash takes precendence when looking
59 | # for the called method. Otherwise, we'll let the super-class forward
60 | # the method call to the current controller.
61 | #
62 | def method_missing(method_name, *args) #:nodoc:
63 | if @extra_options.has_key? method_name
64 | @extra_options[method_name]
65 | else
66 | super method_name, *args
67 | end
68 | end
69 |
70 |
71 | private
72 |
73 | # This method converts the :to option value into controller and action
74 | # values.
75 | #
76 | def set_controller_and_action(to)
77 | if to
78 | controller_and_action = to.split "#"
79 | @controller_name = controller_and_action.shift
80 | @action_name = controller_and_action.shift || "index"
81 | end
82 | end
83 | end
84 |
85 | end
86 | end
87 | end
--------------------------------------------------------------------------------
/test/navigation_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 |
4 | class NavigationTest < ActiveSupport::TestCase
5 |
6 | def setup
7 | @controller_class = Struct.new(:controller_name, :action_name)
8 | @current_controller = @controller_class.new("tests", "index")
9 | end
10 |
11 |
12 | #-------------------------------------------------------------------------------
13 | # Definition tests
14 | #-------------------------------------------------------------------------------
15 |
16 | # flat structure
17 | test "can create a navigation with one level of nesting" do
18 | nav = navigation do
19 | item "Foos", :to => "foos#index"
20 | item "Bars", :to => "bars#index"
21 | end
22 |
23 | assert_equal 2, nav.items.length
24 | end
25 |
26 | # nested structure
27 | test "can create a navigation with multiple levels of nesting" do
28 | nav = navigation do
29 | item "Foos", :to => "foos#index"
30 | item "Bars", :to => "bars#index" do
31 | item "Tests", :to => "tests#index"
32 | end
33 | end
34 |
35 | assert_equal 2, nav.items.length
36 | assert_equal 1, nav.items.last.items.length
37 | end
38 |
39 | # with conditions
40 | test "can create a navigation with conditional inclusion" do
41 | nav = navigation do
42 | item "Foos", :to => "foos#index" if 1 == 1
43 | item "Bars", :to => "bars#index" if 1 == 2
44 | item "Tests", :to => "tests#index"
45 | end
46 |
47 | assert_equal 2, nav.items.length
48 | end
49 |
50 | # extra options
51 | test "can create a navigation with user-defined properties on items" do
52 | nav = navigation do
53 | item "Foos", :to => "foos#index", :align => :left
54 | item "Bars", :to => "bars#index", :align => :right
55 | end
56 |
57 | assert_equal 2, nav.items.length
58 | assert_equal :left, nav.items.first.align
59 | assert_equal :right, nav.items.last.align
60 | end
61 |
62 |
63 |
64 | #-------------------------------------------------------------------------------
65 | # Selection tests
66 | #-------------------------------------------------------------------------------
67 |
68 | # flat structure
69 | test "has correct selected item in one level of nesting" do
70 | nav = navigation do
71 | item "Tests", :to => "tests"
72 | end
73 |
74 | assert_equal 1, nav.selected.length
75 | assert_equal "Tests", nav.selected.first.name
76 | end
77 |
78 | # nested structure
79 | test "has correct selected items in multiple levels of nesting" do
80 | nav = navigation do
81 | item "Foos", :to => "foos#index" do
82 | item "Tests", :to => "tests#index"
83 | end
84 | end
85 |
86 | assert_equal 2, nav.selected.length
87 | assert_equal ["Foos", "Tests"], nav.selected.map(&:name)
88 | end
89 |
90 | test "when selection_context is 'action' it does not apply selection to a non-matching item, despite a controller match" do
91 | nav = navigation do
92 | item "Tests", :to => "tests#show", :selection_scope => :action
93 | end
94 |
95 | assert_nil nav.selected.first
96 | end
97 |
98 | test "when selection_context is 'action' it applies selection to a matching item when both controller and action match" do
99 | nav = navigation do
100 | item "Tests", :to => "tests#index", :selection_scope => :action
101 | end
102 |
103 | assert_equal "Tests", nav.selected.first.name
104 | end
105 |
106 | #-------------------------------------------------------------------------------
107 | # Helpers
108 | #-------------------------------------------------------------------------------
109 |
110 | private
111 |
112 | # This method bootstraps a navigation call in lieu of loading a config file.
113 | #
114 | def navigation(name = :main, current_controller = @current_controller, &block)
115 | Coroutine::TinyNavigation::Data::Navigation.new(name, current_controller, &block)
116 | end
117 |
118 | end
119 |
--------------------------------------------------------------------------------
/lib/tiny_navigation.rb:
--------------------------------------------------------------------------------
1 | #------------------------------------------------------------
2 | # setup
3 | #------------------------------------------------------------
4 |
5 | # external gems
6 | require "action_pack"
7 | require "action_controller"
8 |
9 | # add data files
10 | %w(config navigation item).each do |file|
11 | require File.join(File.dirname(__FILE__), "tiny_navigation/data", file)
12 | end
13 |
14 | # add controller files
15 | require File.dirname(__FILE__) + "/tiny_navigation/controller/base"
16 |
17 |
18 | # add extensions to action controller
19 | ::ActionController::Base.send(:include, Coroutine::TinyNavigation::Controller::Base)
20 |
21 |
22 |
23 | #------------------------------------------------------------
24 | # doc namespaces
25 | #------------------------------------------------------------
26 |
27 | module Coroutine #:nodoc:
28 |
29 | # TinyNavigation provides an easy-to-use DSL for defining navigation structures;
30 | # these structures are defined in config/tiny_navigation.rb.
31 | #
32 | # == Here are a few things TinyNavigation WILL do:
33 | #
34 | # * It provides the ability to define and map menu items to resources using the
35 | # convention set forth in the Rails 3 router. For example, to map a menu item
36 | # _Foo_ to the show action of the foos_controller simply do:
37 | #
38 | # navigation :top_tabs do
39 | # item "Foo", :to => "foos#show"
40 | # end
41 | #
42 | # If one were to omit the specified action from the :to option, the
43 | # navigation item's action would default to index.
44 | #
45 | # The URL generated from this mapping can be accessed via the url
46 | # method of the navigation item.
47 | #
48 | # * It provides a selected method for getting the selected menu items of
49 | # a navigational structure. For example, for this definition:
50 | #
51 | # navigation :main do
52 | # item "Foo", :to => "foos#index"
53 | # item "Bar", :to => "bars#index" do
54 | # item "Baz", :to => "bazzs#index"
55 | # end
56 | # end
57 | #
58 | # If the menu item _Foo_ is selected, an array containing that menu item is
59 | # returned. However, if the menu item _Baz_ is selected, an array containing
60 | # the _Bar_ and the _Baz_ menu items. This is useful for generating bread-crumbs
61 | # or simply highlighting both the main nav item and its selected sub-nav item.
62 | #
63 | # * It allows for the declaration of custom attributes on nav items. For instance,
64 | # given the configuration in the previous example, we want to right-align the _Bar_
65 | # navigation item. To do this we could simply add another option to the item:
66 | #
67 | # navigation :main do
68 | # item "Foo", :to => "foos#index", :align => :left
69 | # item "Bar", :to => "bars#index", :align => :right do
70 | # item "Baz", :to => "bazzs#index"
71 | # end
72 | # end
73 | #
74 | # Now, when we render the navigation items we can call right_align on
75 | # the item to get its value:
76 | #
77 | # navigation(:main).each do |item|
78 | # if item.align == :right
79 | # ...
80 | # end
81 | # end
82 | #
83 | # * It delegates controller method calls made from within the config file to the
84 | # current controller. For instance, let's say you're using Ryan Bates' fantastic
85 | # CanCan gem for authorization--which adds a some methods to the controller, namely
86 | # the can? method--and you want to show or hide navigation items based upon
87 | # a user's ability. You can do that! Check it:
88 | #
89 | # navigation :main do
90 | # item("Foo", :to => "foos#index") if can? :read, Foo
91 | # item "Bar", :to => "bars#index" do
92 | # item "Baz", :to => "bazzs#index"
93 | # end
94 | # end
95 | #
96 | # *IMPORTANT* if a custom attribute is defined on an item, as mentioned earlier,
97 | # it will take precedence over a controller attribute of the same name, thus
98 | # hiding access to the controller attribute.
99 | #
100 | # == Here are a couple things that TinyNavigation WILL NOT do:
101 | #
102 | # * TinyNavigation makes no attempt at rendering the navigation. That's up
103 | # to you. You may want to render your nav items into div tags, while
104 | # I may want to use an unordered list. That's fine, go for it.
105 | #
106 | # * TinyNavigation does not provide authorization logic for limiting access to
107 | # navigation items; that's a separate concern. It's easy enough to use
108 | # an authorization gem that does that job quite well, and by allowing for calls
109 | # to the current controller from within config/tiny_navigation.rb you can
110 | # do that.
111 | #
112 | module TinyNavigation
113 |
114 | # This module defines all behavior and logic related to extending controller
115 | # behavior.
116 | #
117 | module Controller
118 | end
119 |
120 | # This module defines all objects that primarily serve to provide data structures
121 | # and access methods.
122 | #
123 | module Data
124 | end
125 |
126 | end
127 | end
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = Tiny Navigation
2 |
3 | TinyNavigation provides an easy-to-use DSL for defining navigation structures.
4 |
5 |
6 |
7 | == Usage
8 |
9 | TinyNavigation's structures are defined in config/tiny_navigation.rb and are accessed
10 | via a single method: navigation. For example, provided the following
11 | configuration:
12 |
13 | navigation :main do
14 | item "Store", :to => "products#index"
15 | item "Blog", :to => "blog#index"
16 | end
17 |
18 | the code for accessing this structure would be:
19 |
20 | navigation :main
21 |
22 | The resulting structure could be used to generate the markup. For example:
23 |
24 | content_tag :ul, :class => :tabs do
25 | (navigation(:main).items.map do |item|
26 | content_tag :li do
27 | link_to item.name, item.url, :class => item.selected? ? :selected : ""
28 | end
29 | end).join("")
30 | end
31 |
32 |
33 |
34 | == What TinyNavigation WILL do:
35 |
36 | * It provides the ability to define and map menu items to resources using the
37 | convention set forth in the Rails 3 router. For example, to map a menu item
38 | _Foo_ to the show action of the foos_controller simply do:
39 |
40 | navigation :top_tabs do
41 | item "Foo", :to => "foos#show"
42 | end
43 |
44 | If one were to omit the specified action from the :to option, the
45 | navigation item's action would default to index.
46 |
47 | The URL generated from this mapping can be accessed via the url
48 | method of the navigation item.
49 |
50 | * It provides a selected method for getting the selected menu items of
51 | a navigational structure. For example, for this definition:
52 |
53 | navigation :main do
54 | item "Foo", :to => "foos#index"
55 | item "Bar", :to => "bars#index" do
56 | item "Baz", :to => "bazzs#index"
57 | end
58 | end
59 |
60 | If the menu item _Foo_ is selected, an array containing that menu item is
61 | returned. However, if the menu item _Baz_ is selected, an array containing
62 | the _Bar_ and the _Baz_ menu items. This is useful for generating bread-crumbs
63 | or simply highlighting both the main nav item and its selected sub-nav item.
64 |
65 | * It allows for the declaration of custom attributes on nav items. For instance,
66 | given the configuration in the previous example, we want to right-align the _Bar_
67 | navigation item. To do this we could simply add another option to the item:
68 |
69 | navigation :main do
70 | item "Foo", :to => "foos#index", :align => :left
71 | item "Bar", :to => "bars#index", :align => :right do
72 | item "Baz", :to => "bazzs#index"
73 | end
74 | end
75 |
76 | Now, when we render the navigation items we can call align on
77 | the item to get its value:
78 |
79 | navigation(:main).items.each do |item|
80 | if item.align == :right
81 | ...
82 | end
83 | end
84 |
85 | * It delegates controller method calls made from within the config file to the
86 | current controller. For instance, let's say you're using Ryan Bates' fantastic
87 | {CanCan}[http://rubygems.org/gems/cancan] gem for authorization--which adds a some
88 | methods to the controller, namely the can? method--and you want to show
89 | or hide navigation items based upon a user's ability. You can do that! Check it:
90 |
91 | navigation :main do
92 | item("Foo", :to => "foos#index") if can? :read, Foo
93 | item "Bar", :to => "bars#index" do
94 | item "Baz", :to => "bazzs#index"
95 | end
96 | end
97 |
98 | *IMPORTANT* if a custom attribute is defined on an item, as mentioned earlier,
99 | it will take precedence over a controller attribute of the same name, thus
100 | hiding access to the controller attribute from within the config file.
101 |
102 |
103 |
104 |
105 | == What TinyNavigation WILL NOT do:
106 |
107 | * TinyNavigation makes no attempt at rendering the navigation. That's up
108 | to you. You may want to render your nav items into div tags, while
109 | I may want to use an unordered list. That's fine, go for it.
110 |
111 | * TinyNavigation does not provide authorization logic for limiting access to
112 | navigation items; that's a separate concern. It's easy enough to use
113 | an authorization gem that does that job quite well, and by allowing for calls
114 | to the current controller from within config/tiny_navigation.rb, you can
115 | do that.
116 |
117 |
118 |
119 | == Helpful Links
120 |
121 | * Repository: http://github.com/coroutine/tiny_navigation
122 | * Gem: http://rubygems.org/gems/tiny_navigation
123 | * Authors: http://coroutine.com
124 |
125 |
126 |
127 | == Installation & Generators (Rails 3)
128 |
129 | Install me from RubyGems.org by adding a gem dependency to your Gemfile. Bundler does
130 | the rest.
131 |
132 | gem "tiny_navigation"
133 |
134 | $ bundle install
135 |
136 | Then generate the required config file.
137 |
138 | $ rails g tiny_navigation
139 |
140 |
141 |
142 | == Installation & Generators (Rails 2)
143 |
144 | Install as a gem from RubyGems.org and add a gem dependency in the appropriate file.
145 |
146 | $ gem install tiny_navigation
147 |
148 | Or install as a plugin.
149 |
150 | $ script/plugin install git://github.com/coroutine/tiny_navigation.git
151 |
152 | Either way, then generate the required config file.
153 |
154 | $ script/generate tiny_navigation
155 |
156 |
157 |
158 | == Gemroll
159 |
160 | Other gems by Coroutine include:
161 |
162 | * {acts_as_current}[http://github.com/coroutine/acts_as_current]
163 | * {acts_as_label}[http://github.com/coroutine/acts_as_label]
164 | * {acts_as_list_with_sti_support}[http://github.com/coroutine/acts_as_list_with_sti_support]
165 | * {delayed_form_observer}[http://github.com/coroutine/delayed_form_observer]
166 | * {kenny_dialoggins}[http://github.com/coroutine/kenny_dialoggins]
167 | * {michael_hintbuble}[http://github.com/coroutine/michael_hintbuble]
168 |
169 |
170 |
171 | == License
172 |
173 | Copyright (c) 2010 {Coroutine LLC}[http://coroutine.com].
174 |
175 | Permission is hereby granted, free of charge, to any person obtaining
176 | a copy of this software and associated documentation files (the
177 | "Software"), to deal in the Software without restriction, including
178 | without limitation the rights to use, copy, modify, merge, publish,
179 | distribute, sublicense, and/or sell copies of the Software, and to
180 | permit persons to whom the Software is furnished to do so, subject to
181 | the following conditions:
182 |
183 | The above copyright notice and this permission notice shall be
184 | included in all copies or substantial portions of the Software.
185 |
186 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
187 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
188 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
189 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
190 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
191 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
192 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------