xebec 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +5 -0
  2. data/README.md +105 -0
  3. data/Rakefile +6 -0
  4. data/VERSION +1 -0
  5. data/developer_tasks/doc.rake +29 -0
  6. data/developer_tasks/gem.rake +34 -0
  7. data/developer_tasks/test.rake +20 -0
  8. data/doc/example_app/Gemfile +2 -0
  9. data/doc/example_app/README.md +9 -0
  10. data/doc/example_app/app/controllers/application_controller.rb +22 -0
  11. data/doc/example_app/app/controllers/pages_controller.rb +7 -0
  12. data/doc/example_app/app/controllers/projects_controller.rb +49 -0
  13. data/doc/example_app/app/views/layouts/_site_nav_bar.html.erb +16 -0
  14. data/doc/example_app/app/views/layouts/application.html.erb +15 -0
  15. data/doc/example_app/app/views/pages/about_us.html.erb +1 -0
  16. data/doc/example_app/app/views/pages/faq.html.erb +1 -0
  17. data/doc/example_app/app/views/pages/feedback.html.erb +1 -0
  18. data/doc/example_app/app/views/pages/home.html.erb +1 -0
  19. data/doc/example_app/app/views/pages/privacy_policy.html.erb +1 -0
  20. data/doc/example_app/app/views/projects/budget.html.erb +1 -0
  21. data/doc/example_app/app/views/projects/edit.html.erb +1 -0
  22. data/doc/example_app/app/views/projects/history.html.erb +1 -0
  23. data/doc/example_app/app/views/projects/index.html.erb +1 -0
  24. data/doc/example_app/app/views/projects/show.html.erb +1 -0
  25. data/doc/example_app/config/locales/en.yml +7 -0
  26. data/doc/example_app/config/routes.rb +10 -0
  27. data/init.rb +7 -0
  28. data/lib/xebec.rb +6 -0
  29. data/lib/xebec/controller_support.rb +77 -0
  30. data/lib/xebec/has_nav_bars.rb +31 -0
  31. data/lib/xebec/nav_bar.rb +50 -0
  32. data/lib/xebec/nav_bar_helper.rb +61 -0
  33. data/lib/xebec/nav_bar_proxy.rb +88 -0
  34. data/lib/xebec/nav_item.rb +21 -0
  35. data/rails/init.rb +3 -0
  36. data/tasks/README.md +3 -0
  37. data/test/controller_support_test.rb +47 -0
  38. data/test/nav_bar_helper_test.rb +62 -0
  39. data/test/nav_bar_proxy_test.rb +136 -0
  40. data/test/nav_bar_test.rb +49 -0
  41. data/test/test_helper.rb +49 -0
  42. metadata +133 -0
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ */.DS_Store
3
+ doc/rdoc
4
+ .yardoc
5
+ pkg
@@ -0,0 +1,105 @@
1
+ ## Xebec ##
2
+
3
+ Xebec is a Gem and Rails plugin that helps you build navigation for your website.
4
+ Tired of building custom navigation code for each site?
5
+
6
+ Have you seen Ryan Heath's [Navigation Helper](http://github.com/rpheath/navigation_helper)? It's very usable, but it only does one navigation bar. I often have sites where there are many, e.g.
7
+
8
+ +-----------------------------------------------------------+
9
+ | MyApp | Home | Sign Out | FAQ | | # "site" nav bar
10
+ |===========================================================|
11
+ | | *Projects* | Friends | | # "area" nav bar
12
+ |===========================================================|
13
+ | | Project Overview | *Budget* | History | | # "tabs" nav bar
14
+ | |
15
+ | |
16
+ | Project "Foo" Budget |
17
+ | ==================== |
18
+ | ... |
19
+ +-----------------------------------------------------------+
20
+
21
+ Each navigation bar has dynamic content. The *site* one will often have a "Sign In" and "Sign Up" link when no user is signed-in, but will have a "Sign Out" link otherwise. The *area* bar will often change based on the permissions/roles of the person signed in. The *tabs* bar will certainly depend on the area and the permissions/roles, and possibly also the features of the item being viewed. (Do all projects have budgets? If the "Budget" link goes to "./budget" then it doesn't have to be generated for each project, but it might not be so easy in other cases.)
22
+
23
+ (Of course, these *site*, *area*, and *tabs* aren't the only possibilities, merely a common pattern.)
24
+
25
+ Another common pattern is to differentiate the currently-selected navigation item in each applicable bar. (The asterisks in the above display.) In the case above, it probably makes sense to leave the "Projects" *area* item as a link when browsing a specific project so the user may easily return to the projects listing page. On the other hand, the currently active *tabs* item probably doesn't need to be a link. Clicking that would be the equivalent of a refresh.
26
+
27
+ Thus, Xebec.
28
+
29
+ ### Installing Xebec in a Rails Application ###
30
+
31
+ #### Add the Gem Dependency ####
32
+
33
+ If you're using Bundler, add this to your `Gemfile`:
34
+
35
+ gem "xebec"
36
+
37
+ If not, add this to `config/environment.rb`:
38
+
39
+ config.gem 'xebec'
40
+
41
+ #### Add the Helpers ####
42
+
43
+ Add this to your `ApplicationController`:
44
+
45
+ class ApplicationController < ActionController::Base
46
+ helper Xebec::NavBarHelper
47
+ end
48
+
49
+ ### Rendering a Navigation Bar ###
50
+
51
+ To render a navigation bar, render `nav_bar` in your view, passing the name of the bar:
52
+
53
+ <%= nav_bar :site %>
54
+
55
+ If you want to render the navigation bar only if it contains any items, use `nav_bar_unless_empty`:
56
+
57
+ <%= nav_bar_unless_empty :area >
58
+
59
+ If you only have one navigation bar on your site, you can leave off the name, like so:
60
+
61
+ <%= nav_bar %>
62
+
63
+ Xebec will assign this navigation bar the name `:default` in case you need to refer to it elsewhere, but you probably won't.
64
+
65
+ ### Populating a Navigation Bar ###
66
+
67
+ To add items to your navigation bar in a view, call `nav_bar` with a block containing any number of `nav_item` calls:
68
+
69
+ <%
70
+ nav_bar :site do |nb|
71
+ nb.nav_item :home, root_path
72
+ nb.nav_item :sign_in unless signed_in? # assumes sign_in_path
73
+ end
74
+ %>
75
+
76
+ ### Highlighting the "Current" Element of a Navigation Bar ###
77
+
78
+ The `nav_bar` helper method will add the "current" class to the currently-selected item of each navigation bar. Highlighting the item requires just some basic CSS:
79
+
80
+ ul.navbar { color: green; }
81
+ ul.navbar li.current { color: purple; }
82
+
83
+ Each rendered navigation bar will include its name as a CSS class if you want to style them differently:
84
+
85
+ ul.navbar.site { color: green; }
86
+ ul.navbar.area { color: white; background-color: green; }
87
+
88
+ ### Setting the "Current" Element of a Navigation Bar ###
89
+
90
+ A navigation bar will automatically set an item as selected if its URL matches the current page URL. That will work for pages like "FAQ" above, but what if you want "Projects" to be highlighted not only for the projects list page but also for any page in the "Projects" area, such as an individual project's "Budget" tab? You can manually set the *current* item in a navigation bar like so:
91
+
92
+ <% nav_bar(:area).current = :projects %>
93
+
94
+ ### Example Application ###
95
+
96
+ To see the full range of features that Xebec supports, including internationalization and `before_filter`s for your controllers, check out the example application in `doc/example_app/`.
97
+
98
+ ## What's a *xebec*? ##
99
+
100
+ Apple's dictionary provides the following entry:
101
+
102
+ > **xe‧bec** |ˈzēˌbek| (also **ze‧bec**)
103
+ noun historical
104
+ a small three-masted Mediterranean sailing ship with lateen and sometimes square sails.
105
+ ORIGIN mid 18th cent.: alteration (influenced by Spanish ***xabeque***) of French ***chebec***, via Italian from Arabic ***šabbāk***.
@@ -0,0 +1,6 @@
1
+ require 'rake'
2
+
3
+ Dir['developer_tasks/*.rake'].each { |f| load(f) }
4
+
5
+ desc "Default: run all tests"
6
+ task :default => ['test']
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,29 @@
1
+ desc "Generate RDoc"
2
+ task :doc => ['doc:generate']
3
+
4
+ namespace :doc do
5
+ project_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
6
+ doc_destination = File.join(project_root, 'doc', 'rdoc')
7
+
8
+ begin
9
+ require 'yard'
10
+ require 'yard/rake/yardoc_task'
11
+
12
+ YARD::Rake::YardocTask.new(:generate) do |yt|
13
+ yt.files = Dir.glob(File.join(project_root, 'lib', '**', '*.rb')) +
14
+ [ File.join(project_root, 'README.md') ]
15
+ yt.options = ['--output-dir', doc_destination, '--readme', 'README.md']
16
+ end
17
+ rescue LoadError
18
+ desc "Generate YARD Documentation"
19
+ task :generate do
20
+ abort "Please install the YARD gem to generate rdoc."
21
+ end
22
+ end
23
+
24
+ desc "Remove generated documenation"
25
+ task :clean do
26
+ rm_r doc_dir if File.exists?(doc_destination)
27
+ end
28
+
29
+ end
@@ -0,0 +1,34 @@
1
+ namespace :gem do
2
+ begin
3
+ require 'jeweler'
4
+ Jeweler::Tasks.new do |gemspec|
5
+ gemspec.name = "xebec"
6
+ gemspec.summary = "Navigation helpers"
7
+ gemspec.description = "Helpers for generating navigation bars"
8
+ gemspec.email = "james.a.rosen@gmail.com"
9
+ gemspec.homepage = "http://github.com/jamesarosen/xebec"
10
+ gemspec.authors = ["James Rosen"]
11
+ gemspec.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Xebec Documentation", "--charset", "utf-8"]
12
+ gemspec.platform = Gem::Platform::RUBY
13
+ gemspec.add_development_dependency 'shoulda', '~> 2.10.3'
14
+ gemspec.add_development_dependency 'mocha', '~> 0.9.8'
15
+ gemspec.add_development_dependency 'redgreen', '~> 1.2.2'
16
+ end
17
+ rescue LoadError
18
+ puts "Jeweler not available. Install it with [sudo] gem install jeweler -s http://gemscutter.org"
19
+ end
20
+
21
+ task :push do
22
+ command = ('gem push')
23
+ command << " -p $#{ENV['http_proxy']}" if ENV['http_proxy']
24
+ command << " #{latest_gem}"
25
+ puts "Pushing gem..."
26
+ IO.popen(command) { |io| io.each { |line| puts ' ' + line } }
27
+ end
28
+
29
+ def latest_gem
30
+ result = File.expand_path(Dir.glob(File.join(File.dirname(__FILE__), '..', 'pkg', '*.gem')).sort.last)
31
+ abort "No gems found in pkg/. Did you run gem:build?" if result.nil?
32
+ result
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ require 'rake/testtask'
2
+
3
+ project_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
4
+
5
+ lib_directories = FileList.new do |fl|
6
+ fl.include "#{project_root}/lib"
7
+ fl.include "#{project_root}/test/lib"
8
+ end
9
+
10
+ test_files = FileList.new do |fl|
11
+ fl.include "#{project_root}/test/**/*_test.rb"
12
+ fl.exclude "#{project_root}/test/test_helper.rb"
13
+ fl.exclude "#{project_root}/test/lib/**/*.rb"
14
+ end
15
+
16
+ Rake::TestTask.new(:test) do |t|
17
+ t.libs = lib_directories
18
+ t.test_files = test_files
19
+ t.verbose = true
20
+ end
@@ -0,0 +1,2 @@
1
+ source :gemcutter
2
+ gem 'xebec'
@@ -0,0 +1,9 @@
1
+ This is an example Rails application that shows off the features of Xebec. Things to check out:
2
+
3
+ * declaring the Xebec dependency in your [Gemfile](./Gemfile)
4
+ * adding the Xebec helpers in your [ApplicationController](./app/controllers/application_controller.rb)
5
+ * how to render navigation bars in your [layout](./app/views/layouts/application.html.erb)
6
+ * how to declare and populate a navigation bar in a [view](./app/views/layouts/_site_nav_bar.html.erb)
7
+ * how to declare and populate a navigation bar in a [controller](./app/controllers/application_controller.rb)
8
+ * how to declare and populate a navigation bar in an [action](./app/controllers/projects_controller.rb)
9
+ * how to change the text that is displayed for a navigation item using [i18n](./config/locales/en.yml)
@@ -0,0 +1,22 @@
1
+ class ApplicationController < ActionController::Base
2
+
3
+ # Add Xebec's view helper methods:
4
+ helper Xebec::NavBarHelper
5
+
6
+ # If you prefer to declare your navigation in your
7
+ # controllers, start by including Xebec's controller support:
8
+ include Xebec::ControllerSupport
9
+
10
+ # then declare and populate some navigation bars:
11
+ nav_bar :area do |nb|
12
+ nb.nav_item :projects # assumes projects_path
13
+ end
14
+
15
+ nav_bar :footer do |nb|
16
+ nb.nav_item :about_us, page_path(:about_us)
17
+ nb.nav_item :faq, page_path(:faq)
18
+ nb.nav_item :feedback, page_path(:feedback)
19
+ nb.nav_item :privacy_policy, page_path(:privacy_policy)
20
+ end
21
+
22
+ end
@@ -0,0 +1,7 @@
1
+ class PagesController < ApplicationController
2
+
3
+ def show
4
+ render :action => params[:page]
5
+ end
6
+
7
+ end
@@ -0,0 +1,49 @@
1
+ class ProjectsController < ApplicationController
2
+
3
+ # set the selected item for the :area navigation bar
4
+ # for all actions in this controller:
5
+ nav_bar :area { |nb| nb.selected = :projects }
6
+
7
+ def index
8
+ params[:sort] ||= 'recent'
9
+ # set up tabs for the project-list view:
10
+ nav_bar(:tabs) do |nb|
11
+ nb.nav_item :by_alpha, projects_path(:sort => 'by_alpha')
12
+ nb.nav_item :recent, projects_path(:sort => 'recent')
13
+ nb.selected = params[:sort]
14
+ end
15
+ @projects = Project.ordered_by(params[:sort])
16
+ end
17
+
18
+ def show(selected_tab = :overview)
19
+ @project = Project.find(params[:id])
20
+ prepare_project_tabs selected_tab
21
+ end
22
+
23
+ def budget
24
+ show :budget
25
+ end
26
+
27
+ def history
28
+ show :history
29
+ end
30
+
31
+ def edit
32
+ show :edit
33
+ end
34
+
35
+ protected
36
+
37
+ # Extract navigation bar setup that is common to
38
+ # multiple methods:
39
+ def prepare_project_tabs(selected)
40
+ nav_bar(:tabs) do |nb|
41
+ nb.nav_item :overview, project_path(@project)
42
+ nb.nav_item :budget, budget_project_path(@project)
43
+ nb.nav_item :history, history_project_path(@project)
44
+ nb.nav_item :edit, edit_project_path(@project)
45
+ nb.selected = selected
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,16 @@
1
+ <%=
2
+ # If you want to keep your navigation code in your views, you might
3
+ # want to use partials like this one. Notice that this tag is an
4
+ # "output" tag (%= as opposed to %). That means that the
5
+ # navigation bar will be rendered as an HTML list here. If you want to
6
+ # modify a navigation bar in a view without rendering it, use a
7
+ # non-outputting tag.
8
+ nav_bar :site do |nb|
9
+ nb.nav_item :home, root_path
10
+ nb.nav_item :sign_in unless signed_in?
11
+ nb.nav_item :sign_up unless signed_in?
12
+ nb.nav_item :sign_out if signed_in?
13
+ nb.nav_item :profile, edit_user_path(current_user) if signed_in?
14
+ nb.nav_item :company, 'http://example.com'
15
+ end
16
+ %>
@@ -0,0 +1,15 @@
1
+ <body>
2
+ <div id='header'>
3
+ <h1>My Site</h1>
4
+ <%= render :partial => '/layouts/site_nav_bar' %>
5
+ </div>
6
+ <%= nav_bar_unless_empty :area %>
7
+ <div id='content'>
8
+ <%= nav_bar_unless_empty :tabs %>
9
+ <%= yield %>
10
+ </div>
11
+ <div id='footer'>
12
+ &copy; 2293 My Happy Company
13
+ <%= nav_bar :footer %>
14
+ </div>
15
+ </body>
@@ -0,0 +1 @@
1
+ <h2>About Us</h2>
@@ -0,0 +1 @@
1
+ <h2>Frequently Asked Questions</h2>
@@ -0,0 +1 @@
1
+ <h2>Feedback</h2>
@@ -0,0 +1 @@
1
+ <h2>Welcome!</h2>
@@ -0,0 +1 @@
1
+ <h2>Privacy Policy</h2>
@@ -0,0 +1 @@
1
+ <h2>Project Budget</h2>
@@ -0,0 +1 @@
1
+ <h2>Edit Project</h2>
@@ -0,0 +1 @@
1
+ <h2>Project History</h2>
@@ -0,0 +1 @@
1
+ <h2>Projects</h2>
@@ -0,0 +1 @@
1
+ <h2>Project Overview</h2>
@@ -0,0 +1,7 @@
1
+ en:
2
+ navbar:
3
+ site:
4
+ profile: My Profile
5
+ company: Spiffy Labs
6
+ footer:
7
+ faq: <abbr title="Frequently Asked Questions">FAQ</abbr>
@@ -0,0 +1,10 @@
1
+ ExampleApp::Application.routes.draw do |map|
2
+ root 'pages#show', :page => :home
3
+ resources :projects do
4
+ member do
5
+ get :budget
6
+ get :history
7
+ end
8
+ end
9
+ get '/pages/:page' => 'pages#show', :as => :page
10
+ end
data/init.rb ADDED
@@ -0,0 +1,7 @@
1
+ # Rails uses rails/init.rb, not this file. Put any Rails-specific
2
+ # initialization in that file.
3
+
4
+ lib_path = File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
5
+ $: << lib_path unless $:.include?(lib_path)
6
+
7
+ require 'xebec'
@@ -0,0 +1,6 @@
1
+ module Xebec
2
+
3
+ autoload :NavBarHelper, 'xebec/nav_bar_helper'
4
+ autoload :ControllerSupport, 'xebec/controller_support'
5
+
6
+ end
@@ -0,0 +1,77 @@
1
+ require 'xebec/has_nav_bars'
2
+
3
+ module Xebec
4
+
5
+ # Include this module in Rails controllers if you want to declare
6
+ # navigation bars in your controllers instead of or in addition to
7
+ # in your views.
8
+ module ControllerSupport
9
+
10
+ def self.included(base)
11
+ base.extend Xebec::ControllerSupport::ClassMethods
12
+ base.send :include, Xebec::ControllerSupport::InstanceMethods
13
+ base.send :include, Xebec::HasNavBars
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ # Declare and populate a navigation bar. This method
19
+ # is a shorthand for creating a +before_filter+ that looks
20
+ # up and populates a navigation bar.
21
+ #
22
+ # @param [String, Symbol] name the name of the navigation bar;
23
+ # optional
24
+ #
25
+ # @param [Hash] options the options for the +before_filter+;
26
+ # optional.
27
+ #
28
+ # @yield [Xebec::NavBar] nav_bar the navigation bar -- NB: does
29
+ # NOT yield at call-time, but at action
30
+ # run-time, as a before filter. The block
31
+ # is evaluated in the scope of the controller
32
+ # instance.
33
+ #
34
+ # @return [nil]
35
+ #
36
+ # @example
37
+ # nav_bar :tabs, :only => [:index, :sent, :received, :new] do |nb|
38
+ # nb.nav_item :received, received_messages_path(current_user)
39
+ # nb.nav_item :sent, sent_messages_path(current_user)
40
+ # nb.nav_item :new, new_message_path
41
+ # end
42
+ def nav_bar(name = Xebec::NavBar::DEFAULT_NAME, options = {}, &block)
43
+ append_before_filter options do
44
+ nav_bar name, &block
45
+ end
46
+ nil
47
+ end
48
+
49
+ end
50
+
51
+ module InstanceMethods
52
+
53
+ # Declare and populate a navigation bar.
54
+ #
55
+ # @param [String, Symbol] name the name of the navigation bar
56
+ #
57
+ # @yield [Xebec::NavBar] nav_bar the navigation bar. The block
58
+ # is evaluated in the scope of the
59
+ # controller instance.
60
+ #
61
+ # @return [Xebec::NavBar]
62
+ #
63
+ # @example
64
+ # nav_bar :tabs do |nb|
65
+ # nb.nav_item :overview, @project
66
+ # nb.nav_item :budget, project_budget_path(@project)
67
+ # nb.nav_item :edit, edit_project_path(@project)
68
+ # end
69
+ def nav_bar(name = Xebec::NavBar::DEFAULT_NAME, &block)
70
+ look_up_nav_bar_and_eval name, &block
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,31 @@
1
+ require 'xebec/nav_bar'
2
+
3
+ module Xebec
4
+
5
+ # A supporting mixin for NavBarHelper and ControllerSupport.
6
+ # Looks up navigation bars by name.
7
+ module HasNavBars #:nodoc:
8
+
9
+ protected
10
+
11
+ # Looks up the named nav bar, creates it if it
12
+ # doesn't exist, and evaluates the the block, if
13
+ # given, in the scope of +self+, yielding the nav bar.
14
+ def look_up_nav_bar_and_eval(name = nil, &block)
15
+ name ||= Xebec::NavBar::DEFAULT_NAME
16
+ look_up_nav_bar(name).tap do |bar|
17
+ block.bind(self).call(bar) if block_given?
18
+ end
19
+ end
20
+
21
+ def look_up_nav_bar(name)
22
+ nav_bars[name] ||= NavBar.new(name)
23
+ end
24
+
25
+ def nav_bars
26
+ @nav_bars ||= {}
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,50 @@
1
+ require 'xebec/nav_item'
2
+
3
+ module Xebec
4
+
5
+ class NavBar
6
+
7
+ DEFAULT_NAME = :default
8
+
9
+ attr_reader :name
10
+ attr_reader :items
11
+ attr_accessor :current
12
+
13
+ # Create a new NavBar object.
14
+ #
15
+ # @param [String] name the name of the navigation bar; defaults to :default
16
+ def initialize(name = nil)
17
+ @name = name || DEFAULT_NAME
18
+ @items = []
19
+ @current = nil
20
+ end
21
+
22
+ # Add a navigation item to this bar.
23
+ #
24
+ # @param [String, Symbol] name the name of the item
25
+ # @param [String, Proc] href the URL of the item; optional
26
+ #
27
+ # To customize the link text, set the internationalization key
28
+ # <tt>navbar.{{nav bar name}}.{{nav item name}}</tt>.
29
+ #
30
+ # @see Xebec::NavBarHelper#nav_bar
31
+ # @see Xebec::NavBarProxy#to_s
32
+ def nav_item(name, href = nil)
33
+ items << Xebec::NavItem.new(name, href)
34
+ end
35
+
36
+ def empty?
37
+ items.empty?
38
+ end
39
+
40
+ def to_s
41
+ "<NavBar #{name}>"
42
+ end
43
+
44
+ def inspect
45
+ to_s
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,61 @@
1
+ require 'xebec/nav_bar'
2
+ require 'xebec/nav_bar_proxy'
3
+ require 'xebec/has_nav_bars'
4
+
5
+ module Xebec
6
+
7
+ module NavBarHelper
8
+
9
+ include Xebec::HasNavBars
10
+
11
+ # If called in an output expression ("<%= navbar %>" in ERB
12
+ # or "=navbar" in HAML), renders the navigation bar.
13
+ #
14
+ # @example
15
+ # <%= navbar :tabs%>
16
+ # # => <ul class="navbar tabs">...</ul>
17
+ #
18
+ # @see Xebec::NavBarProxy#to_s
19
+ #
20
+ # If called with a block, yields the underlying NavBar for
21
+ # modification.
22
+ #
23
+ # @example
24
+ # <% navbar do |nb|
25
+ # nb.nav_item :home
26
+ # nb.nav_item :faq, pages_path(:page => :faq)
27
+ # end %>
28
+ #
29
+ # @see Xebec::NavBar#nav_item
30
+ # @see Xebec::HasNavBars#nav_bar
31
+ #
32
+ # @return [Xebec::NavBarProxy]
33
+ def nav_bar(name = nil, &block)
34
+ look_up_nav_bar_and_eval name, &block
35
+ end
36
+
37
+ # Renders a navigation bar if and only if it contains any
38
+ # navigation items. Unlike +nav_bar+, this method does not
39
+ # accept a block.
40
+ #
41
+ # @return [String, Xebec::NavBarProxy]
42
+ def nav_bar_unless_empty(name = nil)
43
+ bar = look_up_nav_bar name
44
+ bar.empty? ? '' : bar
45
+ end
46
+
47
+ protected
48
+
49
+ # Override HasNavBars#look_up_nav_bar to replace with a
50
+ # proxy if necessary.
51
+ def look_up_nav_bar(name)
52
+ bar = super(name)
53
+ if bar.kind_of?(Xebec::NavBar)
54
+ bar = nav_bars[bar.name] = NavBarProxy.new(bar, self)
55
+ end
56
+ bar
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,88 @@
1
+ require 'xebec/nav_bar'
2
+
3
+ module Xebec
4
+
5
+ # A proxy for a Xebec::NavBar that knows how to turn the NavBar
6
+ # into an HTML list using ActionView helper methods.
7
+ class NavBarProxy
8
+
9
+ # Create a new NavBar proxy object. The proxy will pass all
10
+ # methods on to the NavBar except for +to_s+, which will
11
+ # render the NavBar as an HTML list.
12
+ #
13
+ # @param [Xebec::NavBar] bar the navigation bar to proxy
14
+ # @param [#tag AND #content_tag AND #link_to] helper the ActionView helper
15
+ def initialize(bar, helper)
16
+ raise ArgumentError.new("#{bar || '<nil>'} is not a NavBar") unless bar.kind_of?(NavBar)
17
+ raise ArgumentError.new("#{helper || '<nil>'} does not seem to be a view helper") unless
18
+ helper.respond_to?(:content_tag) && helper.respond_to?(:link_to_unless_current)
19
+ @bar, @helper = bar, helper
20
+ end
21
+
22
+ # @return [String] the proxied navigation bar as an HTML list.
23
+ #
24
+ # The HREF for each navigation item is generated as follows:
25
+ #
26
+ # * if the item was declared with a link
27
+ # (e.g. <tt>nav_item :faq, page_path(:page => :faq)</tt>),
28
+ # use that link
29
+ # * else, try to use the route named after the navigation item
30
+ # (e.g. <tt>nav_item :home</tt> uses <tt>home_path</tt>)
31
+ #
32
+ # The link text for each navigation is generated as follows:
33
+ #
34
+ # * if the internationalization key
35
+ # <tt>navbar.{{nav bar name}}.{{nav item name}}</tt> is
36
+ # defined, use that value
37
+ # * else, use <tt>nav_item.name.titleize</tt>
38
+ def to_s
39
+ klass = "navbar #{bar.name}"
40
+ if bar.empty?
41
+ helper.tag(:ul, { :class => klass }, false)
42
+ else
43
+ helper.content_tag :ul, { :class => klass } do
44
+ bar.items.map do |item|
45
+ render_nav_item item
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def respond_to?(sym)
52
+ return true if bar.respond_to?(sym)
53
+ super
54
+ end
55
+
56
+ def method_missing(sym, *args, &block)
57
+ return bar.send(sym, *args, &block) if bar.respond_to?(sym)
58
+ super
59
+ end
60
+
61
+ protected
62
+
63
+ attr_reader :bar, :helper
64
+
65
+ def render_nav_item(item)
66
+ text = text_for_nav_item item
67
+ href = href_for_nav_item item
68
+ klass = is_current_nav_item?(item, href) ? 'current' : ''
69
+ helper.content_tag :li, :class => klass do
70
+ helper.link_to_unless_current text, href
71
+ end
72
+ end
73
+
74
+ def text_for_nav_item(item)
75
+ I18n.t "navbar.#{bar.name}.#{item.name}", :default => item.name.to_s.titleize
76
+ end
77
+
78
+ def href_for_nav_item(item)
79
+ item.href or helper.send("#{item.name}_path")
80
+ end
81
+
82
+ def is_current_nav_item?(item, href)
83
+ bar.current == item.name || bar.current.blank? && helper.current_page?(href)
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,21 @@
1
+ module Xebec
2
+
3
+ class NavItem
4
+
5
+ attr_reader :name, :href
6
+
7
+ # Create a new navigation item.
8
+ #
9
+ # @param [String, Symbol] name the name of the item
10
+ # @param [String, Hash, ActiveRecord::Base] href whither the navigation item links;
11
+ # defaults to the named route, "#{name}_path"
12
+ #
13
+ # @see ActionView::Helpers::UrlHelper#url_for
14
+ def initialize(name, href = nil)
15
+ raise ArgumentError.new("#{name || '<nil>'} is not a valid name for a navigation item") if name.blank?
16
+ @name, @href = name, href
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,3 @@
1
+ load File.join(File.dirname(__FILE__), '..', 'init.rb')
2
+
3
+ # do any Rails-specific initialization here:
@@ -0,0 +1,3 @@
1
+ This directory holds tasks that users of the gem will use. If a user adds this gem to a Rails project, Rails will automatically load all tasks in this directory.
2
+
3
+ Tasks for the development of this gem go in the `developer_tasks` (a sibling directory to this one).
@@ -0,0 +1,47 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+ require 'xebec'
3
+
4
+ class ControllerSupportTest < Test::Unit::TestCase
5
+
6
+ def setup
7
+ @controller_class = Class.new(ActionController::Base).tap do |c|
8
+ c.instance_eval do
9
+ include Xebec::ControllerSupport
10
+ def public_nav_bar(*args, &block)
11
+ nav_bar(*args, &block)
12
+ end
13
+ end
14
+ end
15
+ @controller = @controller_class.new
16
+ end
17
+
18
+ def call_before_filters
19
+ @controller_class.before_filters.each do |filter|
20
+ filter.bind(@controller).call
21
+ end
22
+ end
23
+
24
+ context 'ControllerSupport::ClassMethods#nav_bar' do
25
+
26
+ should 'append a before_filter that looks up the named navigation bar' do
27
+ @controller_class.public_nav_bar :turtles
28
+ @controller.expects(:nav_bar).with(:turtles)
29
+ call_before_filters
30
+ end
31
+
32
+ should 'append a before_filter with the given options' do
33
+ options = { :only => :show }
34
+ @controller_class.expects(:append_before_filter).with(options)
35
+ @controller_class.public_nav_bar :foo, options
36
+ end
37
+
38
+ should 'append a before_filter that passes the given block to the named nav bar instance' do
39
+ @controller_class.public_nav_bar(:baz) { |nb| nb.nav_item :help }
40
+ bar = @controller.nav_bar :baz
41
+ bar.expects(:nav_item).with(:help)
42
+ call_before_filters
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,62 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+ require 'xebec'
3
+
4
+ class NavBarHelperTest < Test::Unit::TestCase
5
+
6
+ context 'NavBarHelper#nav_bar' do
7
+
8
+ setup do
9
+ @helper = new_nav_bar_helper
10
+ end
11
+
12
+ should 'return a NavBar proxy' do
13
+ assert @helper.nav_bar.kind_of?(Xebec::NavBarProxy)
14
+ end
15
+
16
+ should 'return a NavBar with the given name' do
17
+ assert_equal :snacks, @helper.nav_bar(:snacks).name
18
+ end
19
+
20
+ should 'return the same NavBar for repeated calls with the same name' do
21
+ snacks = @helper.nav_bar(:snacks)
22
+ assert_equal snacks, @helper.nav_bar(:snacks)
23
+ end
24
+
25
+ should "evaluate a block in the helper's scope" do
26
+ @helper.expects(:zoink!)
27
+ @helper.nav_bar do
28
+ zoink!
29
+ end
30
+ end
31
+
32
+ should "yield the NavBar proxy to the given block" do
33
+ bar = @helper.nav_bar
34
+ bar.expects :zoink!
35
+ @helper.nav_bar do |nb|
36
+ nb.zoink!
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ context 'NavBarHelper#nav_bar_unless_empty' do
43
+ setup do
44
+ @helper = new_nav_bar_helper
45
+ @bar = @helper.nav_bar(:pets)
46
+ end
47
+
48
+ context 'with an empty navigation bar' do
49
+ should 'return an empty String' do
50
+ assert @helper.nav_bar_unless_empty(:pets).blank?
51
+ end
52
+ end
53
+
54
+ context 'with a non-empty navigation bar' do
55
+ setup { @bar.nav_item :cats, '/cats' }
56
+ should 'return a navigation bar' do
57
+ assert !@helper.nav_bar_unless_empty(:pets).blank?
58
+ end
59
+ end
60
+ end
61
+
62
+ end
@@ -0,0 +1,136 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+ require 'xebec'
3
+
4
+ class NavBarProxyTest < Test::Unit::TestCase
5
+
6
+ context 'creating a NavBar proxy' do
7
+ should 'require a NavBar' do
8
+ assert_raises(ArgumentError) do
9
+ Xebec::NavBarProxy.new('foobar', new_nav_bar_helper)
10
+ end
11
+ end
12
+ should 'require a NavBarHelper' do
13
+ assert_raises(ArgumentError) do
14
+ Xebec::NavBarProxy.new(Xebec::NavBar.new, 'baz')
15
+ end
16
+ end
17
+ end
18
+
19
+ context 'a NavBar proxy' do
20
+
21
+ setup do
22
+ clear_translations!
23
+ @bar = Xebec::NavBar.new('elephants')
24
+ @helper = new_nav_bar_helper
25
+ @proxy = Xebec::NavBarProxy.new(@bar, @helper)
26
+ end
27
+
28
+ should 'respond to :name' do
29
+ assert @proxy.respond_to?(:name)
30
+ end
31
+
32
+ should "return the NavBar's name when sent :name" do
33
+ assert_equal 'elephants', @proxy.name
34
+ end
35
+
36
+ should 'not respond to a method that the underlying NavBar does not' do
37
+ assert !@proxy.respond_to?(:cromulize)
38
+ end
39
+
40
+ should "render a navigation bar with the class 'navbar' and the bar's name" do
41
+ assert_select_from @proxy.to_s, "ul.navbar.elephants"
42
+ end
43
+
44
+ context 'with an empty NavBar' do
45
+
46
+ should 'render an empty navigation bar' do
47
+ assert_select_from @proxy.to_s, 'ul.navbar', /$^/
48
+ end
49
+
50
+ end
51
+
52
+ context 'with a NavBar that has a navigation item declared as a name' do
53
+ setup do
54
+ @helper.stubs(:foo_path).returns("/foo")
55
+ @bar.nav_item :foo
56
+ end
57
+ should 'render a navigation bar with the appropriate items' do
58
+ assert_select_from @proxy.to_s, 'ul.navbar' do
59
+ assert_select 'li' do
60
+ assert_select 'a[href="/foo"]', 'Foo'
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ context 'with a NavBar that has a navigation item set as current' do
67
+ setup do
68
+ @bar.nav_item :foo, '/foo'
69
+ @bar.current = :foo
70
+ end
71
+ should 'render a navigation bar with the item marked as current' do
72
+ assert_select_from @proxy.to_s, 'ul.navbar' do
73
+ assert_select 'li.current', 'Foo'
74
+ end
75
+ end
76
+ end
77
+
78
+
79
+ context 'with a NavBar that has a navigation item declared as a name and URL' do
80
+ setup do
81
+ @bar.nav_item :foo, 'http://foo.com'
82
+ end
83
+ should 'render a navigation bar with the appropriate items' do
84
+ assert_select_from @proxy.to_s, 'ul.navbar' do
85
+ assert_select 'li' do
86
+ assert_select 'a[href="http://foo.com"]', 'Foo'
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ context 'with a NavBar that has a navigation item that links to the current page' do
93
+ setup do
94
+ @helper.stubs(:current_page?).with('/').returns(true)
95
+ @bar.nav_item :home, '/'
96
+ @bar.nav_item :sign_up, '/sign_up'
97
+ end
98
+ should 'render a non-link navigation item' do
99
+ assert_select_from @proxy.to_s, 'ul.navbar' do
100
+ assert_select 'li', 'Home' do
101
+ assert_select 'a', 0
102
+ end
103
+ end
104
+ end
105
+ should 'render other items as links' do
106
+ assert_select_from @proxy.to_s, 'ul.navbar' do
107
+ assert_select 'li' do
108
+ assert_select 'a[href="/sign_up"]', 'Sign Up'
109
+ end
110
+ end
111
+ end
112
+ should 'add the "current" class to the current item' do
113
+ assert_select_from @proxy.to_s, 'ul.navbar' do
114
+ assert_select 'li.current', 'Home'
115
+ end
116
+ end
117
+ end
118
+
119
+ context "with a NavBar that has a navigation item with an i18n'd title" do
120
+ setup do
121
+ define_translation 'navbar.elephants.foo', 'My Foos'
122
+ @helper.stubs(:foo_path).returns("/foo")
123
+ @bar.nav_item :foo
124
+ end
125
+ should 'render a navigation bar using the internationalized text' do
126
+ assert_select_from @proxy.to_s, 'ul.navbar' do
127
+ assert_select 'li' do
128
+ assert_select 'a', 'My Foos'
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ end
135
+
136
+ end
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+ require 'xebec'
3
+
4
+ class NavBarTest < Test::Unit::TestCase
5
+
6
+ context 'a NavBar' do
7
+
8
+ setup do
9
+ @bar = Xebec::NavBar.new
10
+ end
11
+
12
+ should 'use :default as its name by default' do
13
+ assert_equal :default, @bar.name
14
+ end
15
+
16
+ should 'use a specified name' do
17
+ assert_equal 'fazbot', Xebec::NavBar.new('fazbot').name
18
+ end
19
+
20
+ should 'be empty by default' do
21
+ assert @bar.empty?
22
+ end
23
+
24
+ should 'not have any item specified as current by default' do
25
+ assert @bar.current.blank?
26
+ end
27
+
28
+ context 'with some items' do
29
+ setup do
30
+ @bar.nav_item :foo
31
+ @bar.nav_item :bar
32
+ end
33
+ should 'not be empty' do
34
+ assert !@bar.empty?
35
+ end
36
+ end
37
+
38
+ should 'not allow a nameless item to be added' do
39
+ assert_raises ArgumentError do
40
+ @bar.nav_item nil
41
+ end
42
+ assert_raises ArgumentError do
43
+ @bar.nav_item ''
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,49 @@
1
+ require 'test/unit'
2
+ require 'test/unit/testcase'
3
+ require 'rubygems'
4
+ require 'shoulda'
5
+ require 'mocha'
6
+ require 'activesupport'
7
+ require 'actionpack'
8
+ require 'action_view'
9
+ require 'action_controller'
10
+
11
+ begin
12
+ require 'redgreen/unicode'
13
+ rescue
14
+ require 'redgreen'
15
+ end
16
+
17
+ [['..', 'lib'], ['lib']].each do |lib_dir|
18
+ lib_path = File.expand_path(File.join(File.dirname(__FILE__), *lib_dir))
19
+ $: << lib_path unless $:.include?(lib_path)
20
+ end
21
+
22
+ require 'xebec'
23
+
24
+ Test::Unit::TestCase.class_eval do
25
+ include ActionController::Assertions::SelectorAssertions
26
+
27
+ def assert_select_from(text, *args, &block)
28
+ @selected = HTML::Document.new(text).root.children
29
+ assert_select(*args, &block)
30
+ end
31
+
32
+ def new_nav_bar_helper
33
+ ActionView::Base.new.tap do |helper|
34
+ helper.extend Xebec::NavBarHelper
35
+ helper.stubs(:current_page?).returns(false)
36
+ end
37
+ end
38
+
39
+ def clear_translations!
40
+ I18n.reload!
41
+ end
42
+
43
+ def define_translation(key, value)
44
+ hash = key.to_s.split('.').reverse.inject(value) do |value, key_part|
45
+ { key_part.to_sym => value }
46
+ end
47
+ I18n.backend.send :merge_translations, I18n.locale, hash
48
+ end
49
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xebec
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - James Rosen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-03-04 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 2.10.3
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: mocha
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.8
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: redgreen
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 1.2.2
44
+ version:
45
+ description: Helpers for generating navigation bars
46
+ email: james.a.rosen@gmail.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README.md
53
+ files:
54
+ - .gitignore
55
+ - README.md
56
+ - Rakefile
57
+ - VERSION
58
+ - developer_tasks/doc.rake
59
+ - developer_tasks/gem.rake
60
+ - developer_tasks/test.rake
61
+ - doc/example_app/Gemfile
62
+ - doc/example_app/README.md
63
+ - doc/example_app/app/controllers/application_controller.rb
64
+ - doc/example_app/app/controllers/pages_controller.rb
65
+ - doc/example_app/app/controllers/projects_controller.rb
66
+ - doc/example_app/app/views/layouts/_site_nav_bar.html.erb
67
+ - doc/example_app/app/views/layouts/application.html.erb
68
+ - doc/example_app/app/views/pages/about_us.html.erb
69
+ - doc/example_app/app/views/pages/faq.html.erb
70
+ - doc/example_app/app/views/pages/feedback.html.erb
71
+ - doc/example_app/app/views/pages/home.html.erb
72
+ - doc/example_app/app/views/pages/privacy_policy.html.erb
73
+ - doc/example_app/app/views/projects/budget.html.erb
74
+ - doc/example_app/app/views/projects/edit.html.erb
75
+ - doc/example_app/app/views/projects/history.html.erb
76
+ - doc/example_app/app/views/projects/index.html.erb
77
+ - doc/example_app/app/views/projects/show.html.erb
78
+ - doc/example_app/config/locales/en.yml
79
+ - doc/example_app/config/routes.rb
80
+ - init.rb
81
+ - lib/xebec.rb
82
+ - lib/xebec/controller_support.rb
83
+ - lib/xebec/has_nav_bars.rb
84
+ - lib/xebec/nav_bar.rb
85
+ - lib/xebec/nav_bar_helper.rb
86
+ - lib/xebec/nav_bar_proxy.rb
87
+ - lib/xebec/nav_item.rb
88
+ - rails/init.rb
89
+ - tasks/README.md
90
+ - test/controller_support_test.rb
91
+ - test/nav_bar_helper_test.rb
92
+ - test/nav_bar_proxy_test.rb
93
+ - test/nav_bar_test.rb
94
+ - test/test_helper.rb
95
+ has_rdoc: true
96
+ homepage: http://github.com/jamesarosen/xebec
97
+ licenses: []
98
+
99
+ post_install_message:
100
+ rdoc_options:
101
+ - --line-numbers
102
+ - --inline-source
103
+ - --title
104
+ - Xebec Documentation
105
+ - --charset
106
+ - utf-8
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: "0"
114
+ version:
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: "0"
120
+ version:
121
+ requirements: []
122
+
123
+ rubyforge_project:
124
+ rubygems_version: 1.3.5
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Navigation helpers
128
+ test_files:
129
+ - test/controller_support_test.rb
130
+ - test/nav_bar_helper_test.rb
131
+ - test/nav_bar_proxy_test.rb
132
+ - test/nav_bar_test.rb
133
+ - test/test_helper.rb