├── 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. --------------------------------------------------------------------------------