navigatrix 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,12 @@
1
+ module Navigatrix::Rendering::Strategies
2
+ module Bootstrap
3
+ class Tabs < List
4
+
5
+ private
6
+
7
+ def html_attributes
8
+ super.merge_attribute(:class, "nav nav-tabs")
9
+ end
10
+ end
11
+ end
12
+ 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
@@ -0,0 +1,3 @@
1
+ module Navigatrix
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ module Navigatrix
2
+ module ViewHelpers
3
+ def render_navigation(configuration, options = {})
4
+ Navigatrix::Renderer.new(configuration, options.merge({:render_context => self})).render
5
+ end
6
+ end
7
+ end
@@ -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
@@ -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