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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +144 -0
- data/Rakefile +1 -0
- data/lib/navigatrix.rb +9 -0
- data/lib/navigatrix/configuration.rb +51 -0
- data/lib/navigatrix/integration/rails.rb +9 -0
- data/lib/navigatrix/integration/sinatra.rb +3 -0
- data/lib/navigatrix/item.rb +109 -0
- data/lib/navigatrix/item_collection.rb +10 -0
- data/lib/navigatrix/renderer.rb +41 -0
- data/lib/navigatrix/rendering/context.rb +26 -0
- data/lib/navigatrix/rendering/strategies/bootstrap/navbar.rb +38 -0
- data/lib/navigatrix/rendering/strategies/bootstrap/tabs.rb +12 -0
- data/lib/navigatrix/rendering/strategies/list.rb +100 -0
- data/lib/navigatrix/version.rb +3 -0
- data/lib/navigatrix/view_helpers.rb +7 -0
- data/navigatrix.gemspec +29 -0
- data/spec/item_spec.rb +247 -0
- data/spec/renderer_spec.rb +77 -0
- data/spec/strategies/bootstrap/navbar_spec.rb +44 -0
- data/spec/strategies/bootstrap/tabs_spec.rb +19 -0
- data/spec/strategies/list_spec.rb +11 -0
- data/spec/support/list_rendering_strategy.rb +111 -0
- metadata +174 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module Navigatrix
|
2
|
+
module Rendering
|
3
|
+
class Context < Struct.new(:wrapped)
|
4
|
+
extend Forwardable
|
5
|
+
delegate :request => :wrapped
|
6
|
+
|
7
|
+
def current_path
|
8
|
+
request.env["PATH_INFO"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def controller_name
|
12
|
+
attempt_delegation(:controller_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def action_name
|
16
|
+
attempt_delegation(:action_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def attempt_delegation(method)
|
22
|
+
wrapped.send(method) if wrapped.respond_to?(method)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "navigatrix/rendering/strategies/list"
|
2
|
+
|
3
|
+
module Navigatrix::Rendering::Strategies
|
4
|
+
module Bootstrap
|
5
|
+
class Navbar < List
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def html_attributes
|
10
|
+
super.merge_attribute(:class, "nav")
|
11
|
+
end
|
12
|
+
|
13
|
+
def items
|
14
|
+
super.map { |item| (item.has_children? ? Item : List::Item).new(item, options) }
|
15
|
+
end
|
16
|
+
|
17
|
+
class Item < List::Item
|
18
|
+
private
|
19
|
+
|
20
|
+
def html_attributes
|
21
|
+
super.merge_attribute(:class, "dropdown")
|
22
|
+
end
|
23
|
+
|
24
|
+
def name
|
25
|
+
(super + dropdown_icon).html_safe
|
26
|
+
end
|
27
|
+
|
28
|
+
def nested_list
|
29
|
+
List.new(children, options).render if has_children?
|
30
|
+
end
|
31
|
+
|
32
|
+
def dropdown_icon
|
33
|
+
options[:dropdown_icon] || content_tag(:i, nil, :class => "icon-chevron-down icon-white")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require "action_view"
|
2
|
+
|
3
|
+
module Navigatrix::Rendering::Strategies
|
4
|
+
class List < Struct.new(:items, :options)
|
5
|
+
include ActionView::Helpers::TagHelper
|
6
|
+
include ActionView::Helpers::UrlHelper
|
7
|
+
|
8
|
+
def render
|
9
|
+
content_tag(:ul, render_items, html_attributes)
|
10
|
+
end
|
11
|
+
|
12
|
+
def render_items
|
13
|
+
items.map(&:render).join.html_safe
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def items
|
19
|
+
super.map { |item| item_class.new(item, options) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def item_class
|
23
|
+
Item
|
24
|
+
end
|
25
|
+
|
26
|
+
def html_attributes
|
27
|
+
HTMLAttributes.new(options[:html_attributes])
|
28
|
+
end
|
29
|
+
|
30
|
+
class Item < SimpleDelegator
|
31
|
+
include ActionView::Helpers::TagHelper
|
32
|
+
include ActionView::Helpers::UrlHelper
|
33
|
+
|
34
|
+
attr_reader :options
|
35
|
+
|
36
|
+
def initialize(item, options = {})
|
37
|
+
super(item)
|
38
|
+
@options = options
|
39
|
+
end
|
40
|
+
|
41
|
+
def render
|
42
|
+
content_tag(:li, content, html_attributes) if render?
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def content
|
48
|
+
name_or_link + nested_list.to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
def name_or_link
|
52
|
+
linked? ? link : unlinked_content
|
53
|
+
end
|
54
|
+
|
55
|
+
def nested_list
|
56
|
+
List.new(children, options).render if has_children?
|
57
|
+
end
|
58
|
+
|
59
|
+
def link
|
60
|
+
link_to(name, path)
|
61
|
+
end
|
62
|
+
|
63
|
+
def unlinked_content
|
64
|
+
name
|
65
|
+
end
|
66
|
+
|
67
|
+
def name
|
68
|
+
super.html_safe
|
69
|
+
end
|
70
|
+
|
71
|
+
def html_attributes
|
72
|
+
HTMLAttributes.new(super).merge_attribute(:class, html_class)
|
73
|
+
end
|
74
|
+
|
75
|
+
def html_class
|
76
|
+
active? ? active_class : inactive_class
|
77
|
+
end
|
78
|
+
|
79
|
+
def active_class
|
80
|
+
options[:active_class] || "active"
|
81
|
+
end
|
82
|
+
|
83
|
+
def inactive_class
|
84
|
+
options[:inactive_class]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class HTMLAttributes < HashWithIndifferentAccess
|
89
|
+
def merge_attribute(attribute, value)
|
90
|
+
tap do
|
91
|
+
self[attribute] = attribute_values(attribute).push(value).join(" ")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def attribute_values(attribute)
|
96
|
+
fetch(attribute, "").to_s.split(" ")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/navigatrix.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'navigatrix/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "navigatrix"
|
8
|
+
spec.version = Navigatrix::VERSION
|
9
|
+
spec.authors = ["Ben Eddy"]
|
10
|
+
spec.email = ["bae@foraker.com"]
|
11
|
+
spec.description = %q{Navigation Generation}
|
12
|
+
spec.summary = %q{Navigation generation for Rails and Sinatra}
|
13
|
+
spec.homepage = "https://github.com/foraker/navigatrix"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "i18n"
|
22
|
+
spec.add_dependency "activesupport"
|
23
|
+
spec.add_dependency "actionpack"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec", "~> 2.0"
|
28
|
+
spec.add_development_dependency "capybara"
|
29
|
+
end
|
data/spec/item_spec.rb
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
require File.expand_path("../../lib/navigatrix/item", __FILE__)
|
2
|
+
require File.expand_path("../../lib/navigatrix/item_collection", __FILE__)
|
3
|
+
require File.expand_path("../../lib/navigatrix/configuration", __FILE__)
|
4
|
+
|
5
|
+
module Navigatrix
|
6
|
+
describe Item do
|
7
|
+
let(:context) { double({:current_path => "/"}) }
|
8
|
+
|
9
|
+
it "knows its name" do
|
10
|
+
Item.new("Item 1", {}, context).name.should == "Item 1"
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#path" do
|
14
|
+
it "returns a file paths" do
|
15
|
+
Item.new("Item 1", {"path" => "/a/standard/path"}).path.should == "/a/standard/path"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "evaluates a Proc" do
|
19
|
+
context.stub(:path_method => "/a/path/method")
|
20
|
+
path_proc = Proc.new { |context| context.path_method }
|
21
|
+
item = Item.new("Item 1", {"path" => path_proc}, context)
|
22
|
+
item.path.should == "/a/path/method"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#active?" do
|
27
|
+
it "is true when the navigation is unlinked" do
|
28
|
+
item = new_item({})
|
29
|
+
item.stub(:unlinked? => true)
|
30
|
+
item.should be_active
|
31
|
+
end
|
32
|
+
|
33
|
+
it "is false when the navigation is linked" do
|
34
|
+
item = new_item({})
|
35
|
+
item.stub(:unlinked? => false)
|
36
|
+
item.should_not be_active
|
37
|
+
end
|
38
|
+
|
39
|
+
context "a controller name is specified in the config" do
|
40
|
+
let(:config) { {"active_states" => {"controller" => "controller_1"}} }
|
41
|
+
|
42
|
+
it "is true if the specified controller is the current controller" do
|
43
|
+
context.stub(:controller_name => "controller_1", :action_name => "index")
|
44
|
+
new_item(config).should be_active
|
45
|
+
end
|
46
|
+
|
47
|
+
it "is true regardless of action name" do
|
48
|
+
context.stub(:controller_name => "controller_1", :action_name => "edit")
|
49
|
+
new_item(config).should be_active
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "a controller pattern is specified in the config" do
|
54
|
+
let(:config) { {"active_states" => {"controller" => /^users/}} }
|
55
|
+
|
56
|
+
it "is true if the specified controller is the current controller" do
|
57
|
+
context.stub(:controller_name => "users", :action_name => "index")
|
58
|
+
new_item(config).should be_active
|
59
|
+
end
|
60
|
+
|
61
|
+
it "is true if the specified controller matches the current controller" do
|
62
|
+
context.stub(:controller_name => "users/comments", :action_name => "index")
|
63
|
+
new_item(config).should be_active
|
64
|
+
end
|
65
|
+
|
66
|
+
it "is true regardless of action name" do
|
67
|
+
context.stub(:controller_name => "users", :action_name => "edit")
|
68
|
+
new_item(config).should be_active
|
69
|
+
end
|
70
|
+
|
71
|
+
it "is false if the name does not match" do
|
72
|
+
context.stub(:controller_name => "comments/users", :action_name => "edit")
|
73
|
+
new_item(config).should_not be_active
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "a controller and action are specified in the config" do
|
78
|
+
let(:config) do
|
79
|
+
{"active_states" => {"controller" => "controller_2", "actions" => "index"}}
|
80
|
+
end
|
81
|
+
|
82
|
+
it "is true if the specified controller/action combination is the current controller/action combination" do
|
83
|
+
context.stub(:controller_name => "controller_2", :action_name => "index")
|
84
|
+
new_item(config).should be_active
|
85
|
+
end
|
86
|
+
|
87
|
+
it "is false when the action name mismatches" do
|
88
|
+
context.stub(:controller_name => "controller_2", :action_name => "edit")
|
89
|
+
new_item(config).should_not be_active
|
90
|
+
end
|
91
|
+
|
92
|
+
it "is false when the controller name mismatches" do
|
93
|
+
context.stub(:controller_name => "controller_1", :action_name => "index")
|
94
|
+
new_item(config).should_not be_active
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "multiple active states are specified" do
|
99
|
+
let(:config) do
|
100
|
+
{"active_states" => [
|
101
|
+
{"controller" => "controller_3"},
|
102
|
+
{"controller" => "controller_4", "actions" => "show"},
|
103
|
+
]}
|
104
|
+
end
|
105
|
+
|
106
|
+
it "is true if the first state is applicable" do
|
107
|
+
context.stub(:controller_name => "controller_3", :action_name => "index")
|
108
|
+
new_item(config).should be_active
|
109
|
+
end
|
110
|
+
|
111
|
+
it "is indifferent to action name in the first case" do
|
112
|
+
context.stub(:controller_name => "controller_3", :action_name => "show")
|
113
|
+
new_item(config).should be_active
|
114
|
+
end
|
115
|
+
|
116
|
+
it "is true if the second state is applicable" do
|
117
|
+
context.stub(:controller_name => "controller_4", :action_name => "show")
|
118
|
+
new_item(config).should be_active
|
119
|
+
end
|
120
|
+
|
121
|
+
it "is honors action name specifications" do
|
122
|
+
context.stub(:controller_name => "controller_4", :action_name => "edit")
|
123
|
+
new_item(config).should_not be_active
|
124
|
+
end
|
125
|
+
|
126
|
+
it "is false when neither apply" do
|
127
|
+
context.stub(:controller_name => "controller_5", :action_name => "edit")
|
128
|
+
new_item(config).should_not be_active
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "#unlinked?" do
|
134
|
+
context "no unlinked states are configured" do
|
135
|
+
before { context.stub(:current_path => "/path_1") }
|
136
|
+
|
137
|
+
it "is true when the current_path is the same as the item path" do
|
138
|
+
item = new_item({})
|
139
|
+
item.stub(:path => "/path_1")
|
140
|
+
item.should be_unlinked
|
141
|
+
end
|
142
|
+
|
143
|
+
it "is false when the current_path is not the same the item path" do
|
144
|
+
item = new_item({})
|
145
|
+
item.stub(:path => "/path_2")
|
146
|
+
item.should_not be_unlinked
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context "unlink states are configured" do
|
151
|
+
context "a controller is specified" do
|
152
|
+
let(:config) { {"unlinked_states" => {"controller" => "controller_1"}} }
|
153
|
+
|
154
|
+
it "is true if the specified controller is the current controller" do
|
155
|
+
context.stub(:controller_name => "controller_1", :action_name => "index")
|
156
|
+
new_item(config).should be_unlinked
|
157
|
+
end
|
158
|
+
|
159
|
+
it "is true regardless of action name" do
|
160
|
+
context.stub(:controller_name => "controller_1", :action_name => "edit")
|
161
|
+
new_item(config).should be_unlinked
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context "a controller and action are specified config" do
|
166
|
+
let(:config) do
|
167
|
+
{"unlinked_states" => {"controller" => "controller_2", "actions" => "index"}}
|
168
|
+
end
|
169
|
+
|
170
|
+
it "is true if the specified controller/action combination is the current controller/action combination" do
|
171
|
+
context.stub(:controller_name => "controller_2", :action_name => "index")
|
172
|
+
new_item(config).should be_unlinked
|
173
|
+
end
|
174
|
+
|
175
|
+
it "is false when the action name mismatches" do
|
176
|
+
context.stub(:controller_name => "controller_2", :action_name => "edit")
|
177
|
+
new_item(config).should_not be_unlinked
|
178
|
+
end
|
179
|
+
|
180
|
+
it "is false when the controller name mismatches" do
|
181
|
+
context.stub(:controller_name => "controller_1", :action_name => "index")
|
182
|
+
new_item(config).should_not be_active
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
context "multiple active states are specified" do
|
187
|
+
let(:config) do
|
188
|
+
{"unlinked_states" => [
|
189
|
+
{"controller" => "controller_3"},
|
190
|
+
{"controller" => "controller_4", "actions" => "show"},
|
191
|
+
]}
|
192
|
+
end
|
193
|
+
|
194
|
+
it "is true if the first state is applicable" do
|
195
|
+
context.stub(:controller_name => "controller_3", :action_name => "index")
|
196
|
+
new_item(config).should be_unlinked
|
197
|
+
end
|
198
|
+
|
199
|
+
it "is indifferent to action name in the first case" do
|
200
|
+
context.stub(:controller_name => "controller_3", :action_name => "show")
|
201
|
+
new_item(config).should be_unlinked
|
202
|
+
end
|
203
|
+
|
204
|
+
it "is true if the second state is applicable" do
|
205
|
+
context.stub(:controller_name => "controller_4", :action_name => "show")
|
206
|
+
new_item(config).should be_unlinked
|
207
|
+
end
|
208
|
+
|
209
|
+
it "is honors action name specifications" do
|
210
|
+
context.stub(:controller_name => "controller_4", :action_name => "edit")
|
211
|
+
new_item(config).should_not be_unlinked
|
212
|
+
end
|
213
|
+
|
214
|
+
it "is false when neither apply" do
|
215
|
+
context.stub(:controller_name => "controller_5", :action_name => "edit")
|
216
|
+
new_item(config).should_not be_unlinked
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe "#children" do
|
223
|
+
let(:item) { new_item({"children" => {"Child 1" => {}}}) }
|
224
|
+
let(:child) { item.children.first }
|
225
|
+
|
226
|
+
it "creates the correct number of children" do
|
227
|
+
item.children.count.should == 1
|
228
|
+
end
|
229
|
+
|
230
|
+
it "returns an array of items" do
|
231
|
+
child.should be_instance_of(Item)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "maps its children to items" do
|
235
|
+
child.name.should == "Child 1"
|
236
|
+
end
|
237
|
+
|
238
|
+
it "can handle childless configurations" do
|
239
|
+
expect { new_item({}) }.to_not raise_error
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def new_item(config)
|
246
|
+
Navigatrix::Item.new("", config, context)
|
247
|
+
end
|