navigatrix 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 28321e32d4430f1d16823c1a3d23121d25a1d2a3
4
+ data.tar.gz: 8c466c5479a7330322bb2989a49d11621e13f81a
5
+ SHA512:
6
+ metadata.gz: d7c652b8c6d1e5ddbda0e997b1829307828d2951ae775a680c3407d1c2ff040e2fa444375a0e2facfddfa43d83a6092aa5d993b77a01af0007f758559accbebf
7
+ data.tar.gz: 0eeb7a60fc49e5f7ce8a2e8c683bda0d10716694cb09f65a10d0dcf006310fd9f9245cddd599c87ecc9577fb994489f2c38dccb72ce705501ba979dd13c20568
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - jruby-19mode # JRuby in 1.9 mode
5
+ script: bundle exec rspec spec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in navigatrix.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ben Eddy
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,144 @@
1
+ # Navigatrix
2
+ [![Build Status](https://api.travis-ci.org/foraker/navigatrix.png?branch=master)](http://travis-ci.org/foraker/navigatrix)
3
+ [![Code Climate](https://codeclimate.com/github/foraker/navigatrix.png)](https://codeclimate.com/github/foraker/navigatrix)
4
+
5
+ Navigation generation for Rails and Sinatra.
6
+
7
+ ## Installation
8
+ Add `gem "navigatrix", github: "foraker/navigatrix"` to your Gemfile and run `bundle install`.
9
+
10
+ ### The Simplest Possible Navigation
11
+ ```ERB
12
+ <%= render_navigation({
13
+ "Home" => "/",
14
+ "About Us" => "/about-us",
15
+ "Blog" => "/blog",
16
+ "Contact" => "/contact"
17
+ }) %>
18
+ ```
19
+ Assuming we're on the "/about-us" path, the resulting HTML will look like this:
20
+ ```HTML
21
+ <ul>
22
+ <li><a href="/">Home</a></li>
23
+ <li class="active">About Us</li>
24
+ <li><a href="/blog">Blog</a></li>
25
+ <li><a href="/contact">Contact</a></li>
26
+ </ul>
27
+ ```
28
+
29
+ ### A More Sophisticated Configuration
30
+ ```ERB
31
+ <%= render_navigation({
32
+ "Home" => "/",
33
+ "Users" => {
34
+ :path => "/users",
35
+ :active_states => [
36
+ {:path => "/my_account"},
37
+ {:path => /\/users\/\d*/},
38
+ ]
39
+ },
40
+ "Sign In" => {
41
+ :path => "/sign_in",
42
+ :render? => !user_signed_in?
43
+ },
44
+ "Sign Out" => {
45
+ :path => "/sign_out",
46
+ :render? => user_signed_in?
47
+ }
48
+ }, {
49
+ :active_class => "active-nav-item",
50
+ :html_attributes => {
51
+ :class => "nav"
52
+ }
53
+ }) %>
54
+ ```
55
+
56
+ Assuming we're on the "/users/1" path, and a User is signed in, the resulting HTML will look like this:
57
+ ```HTML
58
+ <ul class="nav">
59
+ <li><a href="/">Home</a></li>
60
+ <li class="active-nav-item">Users</li>
61
+ <li><a href="/sign_out">Sign Out</a></li>
62
+ </ul>
63
+ ```
64
+ The "Users" item is active and not linked because the path "/users/1" matches the pattern `/\/users\/\d*/`. The "Home" item is linked because we are not on the path "/".
65
+
66
+
67
+ ## List Item Configuration Options
68
+ ##### `:path`
69
+ Specifies where the navigation item should link to.
70
+
71
+ ##### `:active_states`
72
+ An array of state specification hashes used to control when a list item is considered "active". By default, when an item is active, it receives an HTML class of "active". For example:
73
+ ```Ruby
74
+ active_states: [
75
+ {controller: "users", actions: ["index"]},
76
+ {path: "/my-account"}
77
+ ]
78
+ ```
79
+ The first state specification dictates that the item will be active when the current controller is the `UsersController` and the current controller action is `index`. If we wanted the item to be active for any `UsersController` action, the specification should be `{controller: "users"}`.
80
+
81
+ The second state specification dictates that the item will be active with the current path is "/my-account". `:path` can be a string or regular expression.
82
+
83
+ ##### `:unlinked_states`
84
+ Also an array of state specification hashes (like `:active_states`). The state specifications determine if the item should be linked. By default, the item is unlinked if the current path is the same as the item path. The `:unlinked_states` option can be used to override this behavior.
85
+
86
+ ##### `:html_attributes`
87
+ HTML attributes added to an item.
88
+ ```Ruby
89
+ "Item 3" => {
90
+ :path => "/item_path"
91
+ :html_attributes => {:id => "nav-3"}
92
+ }
93
+ ```
94
+ Results in the following HTML.
95
+ ```HTML
96
+ <li id="nav-3"><a href="item_path">Item 3</a></li>
97
+ ```
98
+
99
+ ##### `:children`
100
+ Used for creating nested navigations. The `:children` should contain a navigation configuration.
101
+ ```Ruby
102
+ "Parent" => {
103
+ :path => "/parent_path"
104
+ :children => {
105
+ "Child 1" => "/child_1_path",
106
+ "Child 2" => {
107
+ :path => "/child_2_path",
108
+ :children => {
109
+ "Grandchild" => "/grandchild_path"
110
+ }
111
+ }
112
+ }
113
+ }
114
+ ```
115
+ Results in the following HTML.
116
+ ```HTML
117
+ <ul>
118
+ <li>
119
+ <a href="/parent_path">Parent</a>
120
+ <ul>
121
+ <li><a href="/child_1_path">Child 1</a></li>
122
+ <li>
123
+ <a href="/child_2_path">Child 2</a>
124
+ <ul>
125
+ <li href="/grandchild_path">Grandchild</li>
126
+ </ul>
127
+ </li>
128
+ </ul>
129
+ </li>
130
+ </ul>
131
+ ```
132
+
133
+ ##### `:render?`
134
+ Determines if the navigation item is rendered.
135
+
136
+ ## List Configuration Options
137
+ ##### `:html_attributes`
138
+ HTML attributes added to an the list.
139
+
140
+ ##### `:active_class`
141
+ Determines which HTML class is applied to list items when the item is active.
142
+
143
+ ##### `:inactive_class`
144
+ Determines which HTML class is applied to list items when the item is *not* active.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,9 @@
1
+ require "navigatrix/version"
2
+ require "navigatrix/item"
3
+ require "navigatrix/item_collection"
4
+ require "navigatrix/renderer"
5
+ require 'navigatrix/integration/rails' if defined?(Rails)
6
+ require 'navigatrix/integration/sinatra' if defined?(Sinatra)
7
+
8
+ module Navigatrix
9
+ end
@@ -0,0 +1,51 @@
1
+ require "active_support/core_ext"
2
+
3
+ module Navigatrix
4
+ module Configuration
5
+ def self.wrap(wrapped)
6
+ wrapped.is_a?(Hash) ? AdvancedConfig.new(wrapped) : BasicConfig.new(wrapped)
7
+ end
8
+
9
+ class BasicConfig < Struct.new(:path)
10
+ def active_states
11
+ [{path: path}]
12
+ end
13
+
14
+ def unlinked_states
15
+ active_states
16
+ end
17
+
18
+ def children
19
+ {}
20
+ end
21
+
22
+ def html_attributes
23
+ {}
24
+ end
25
+
26
+ def render?
27
+ true
28
+ end
29
+ end
30
+
31
+ class AdvancedConfig < OpenStruct
32
+ DEFAULTS = {
33
+ :html_attributes => {},
34
+ :render? => true,
35
+ :children => {}
36
+ }
37
+
38
+ def initialize(raw_config)
39
+ super(DEFAULTS.merge(raw_config) || {})
40
+ end
41
+
42
+ def active_states
43
+ Array.wrap(super)
44
+ end
45
+
46
+ def unlinked_states
47
+ Array.wrap(super)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,9 @@
1
+ require "navigatrix/view_helpers"
2
+
3
+ module Navigatrix
4
+ class Railtie < ::Rails::Railtie
5
+ initializer "navigatrix.view_helpers" do
6
+ ActionView::Base.send :include, Navigatrix::ViewHelpers
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ require "navigatrix/view_helpers"
2
+
3
+ ::Sinatra::Application.send(:helpers, Navigatrix::ViewHelpers)
@@ -0,0 +1,109 @@
1
+ require "ostruct"
2
+ require 'forwardable'
3
+ require "navigatrix/configuration"
4
+
5
+ module Navigatrix
6
+ class Item < Struct.new(:name, :config, :context)
7
+ extend Forwardable
8
+ delegate :current_path => :context
9
+ delegate [:render?, :html_attributes] => :config
10
+
11
+ def active?
12
+ applicable_active_state? ||
13
+ active_children? ||
14
+ unlinked?
15
+ end
16
+
17
+ def linked?
18
+ !unlinked?
19
+ end
20
+
21
+ def unlinked?
22
+ unlinked_states_specified? ? unlinked_states.any?(&:applicable?) : currently_on_path?
23
+ end
24
+
25
+ def has_children?
26
+ children.any?
27
+ end
28
+
29
+ def children
30
+ @children ||= ItemCollection.new(config.children, context).items
31
+ end
32
+
33
+ def path
34
+ @path ||= Path.new(config.path).call(context).to_s
35
+ end
36
+
37
+ private
38
+
39
+ def active_children?
40
+ children.any?(&:active?)
41
+ end
42
+
43
+ def applicable_active_state?
44
+ active_states.any?(&:applicable?)
45
+ end
46
+
47
+ def currently_on_path?
48
+ current_path == path
49
+ end
50
+
51
+ def unlinked_states_specified?
52
+ unlinked_states.any?
53
+ end
54
+
55
+ def active_states
56
+ @active_states ||= config.active_states.map { |active_state| State.new(active_state, context) }
57
+ end
58
+
59
+ def unlinked_states
60
+ @unlinked_states ||= config.unlinked_states.map { |unlinked_state| State.new(unlinked_state, context) }
61
+ end
62
+
63
+ def config
64
+ @config ||= Configuration.wrap(super)
65
+ end
66
+
67
+ class Path < Struct.new(:source)
68
+ def call(context)
69
+ source.respond_to?(:call) ? source.call(context) : source
70
+ end
71
+ end
72
+
73
+ class State < OpenStruct
74
+ extend Forwardable
75
+ delegate [:current_path, :controller_name, :action_name] => :context
76
+
77
+ attr_reader :context
78
+
79
+ def initialize(config, context)
80
+ @context = context
81
+ super(config)
82
+ end
83
+
84
+ def applicable?
85
+ path ? path_matches? : (controller_matches? && action_matches?)
86
+ end
87
+
88
+ def controller_matches?
89
+ match?(controller, controller_name)
90
+ end
91
+
92
+ def action_matches?
93
+ actions.empty? || actions.include?(action_name)
94
+ end
95
+
96
+ def path_matches?
97
+ match?(path, current_path)
98
+ end
99
+
100
+ def match?(pattern, target)
101
+ pattern.is_a?(Regexp) ? target[pattern] : pattern == target
102
+ end
103
+
104
+ def actions
105
+ Array.wrap(super)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,10 @@
1
+ module Navigatrix
2
+ class ItemCollection < Struct.new(:configuration, :context)
3
+ extend Forwardable
4
+ def_delegator :items, :each
5
+
6
+ def items
7
+ configuration.map { |name, config| Item.new(name, config, context) }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,41 @@
1
+ require "navigatrix/rendering/context"
2
+ require "navigatrix/rendering/strategies/list"
3
+ require "navigatrix/rendering/strategies/bootstrap/navbar"
4
+ require "navigatrix/rendering/strategies/bootstrap/tabs"
5
+
6
+ module Navigatrix
7
+ class Renderer
8
+ attr_reader :configuration, :strategy, :render_context, :render_options
9
+
10
+ REGISTERED_STRATEGIES = {
11
+ :unordered_list => Rendering::Strategies::List,
12
+ :bootstrap_navbar => Rendering::Strategies::Bootstrap::Navbar,
13
+ :bootstrap_tabs => Rendering::Strategies::Bootstrap::Tabs
14
+ }
15
+
16
+ class MissingStrategy < NameError ; end
17
+
18
+ def initialize(configuration, options)
19
+ @configuration = configuration
20
+ @strategy = find_strategy(options.delete(:strategy))
21
+ @render_context = Rendering::Context.new(options.delete(:render_context))
22
+ @render_options = options
23
+ end
24
+
25
+ def render
26
+ strategy.new(item_collection.items, render_options).render
27
+ end
28
+
29
+ private
30
+
31
+ def item_collection
32
+ ItemCollection.new(configuration, render_context)
33
+ end
34
+
35
+ def find_strategy(strategy_or_name)
36
+ return strategy_or_name if strategy_or_name.is_a?(Class)
37
+ strategy_or_name ||= :unordered_list
38
+ REGISTERED_STRATEGIES[strategy_or_name] || raise(MissingStrategy, "can't find strategy #{strategy_name}")
39
+ end
40
+ end
41
+ end