navigatrix 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +14 -6
- data/README.md +108 -7
- data/lib/navigatrix.rb +23 -0
- data/lib/navigatrix/builder.rb +69 -0
- data/lib/navigatrix/configuration.rb +10 -0
- data/lib/navigatrix/item.rb +1 -1
- data/lib/navigatrix/renderer.rb +20 -14
- data/lib/navigatrix/rendering/strategies/bootstrap/navbar.rb +27 -23
- data/lib/navigatrix/rendering/strategies/bootstrap/tabs.rb +10 -6
- data/lib/navigatrix/rendering/strategies/content_helpers.rb +16 -0
- data/lib/navigatrix/rendering/strategies/html_attributes.rb +27 -0
- data/lib/navigatrix/rendering/strategies/item.rb +102 -0
- data/lib/navigatrix/rendering/strategies/list.rb +33 -89
- data/lib/navigatrix/rendering/strategy_factory.rb +28 -0
- data/lib/navigatrix/version.rb +1 -1
- data/spec/integration/documentation_spec.rb +154 -0
- data/spec/item_builder_spec.rb +120 -0
- data/spec/item_spec.rb +1 -3
- data/spec/list_builder_spec.rb +32 -0
- data/spec/renderer_spec.rb +23 -20
- data/spec/{strategies → rendering/strategies}/bootstrap/navbar_spec.rb +2 -4
- data/spec/{strategies → rendering/strategies}/bootstrap/tabs_spec.rb +1 -3
- data/spec/rendering/strategies/list_spec.rb +13 -0
- data/spec/rendering/strategy_factory_spec.rb +58 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/support/list_rendering_strategy.rb +11 -5
- metadata +37 -21
- data/spec/strategies/list_spec.rb +0 -11
@@ -0,0 +1,16 @@
|
|
1
|
+
require "action_view"
|
2
|
+
|
3
|
+
module Navigatrix
|
4
|
+
module Rendering
|
5
|
+
module Strategies
|
6
|
+
module ContentHelpers
|
7
|
+
include ActionView::Helpers::TagHelper
|
8
|
+
include ActionView::Helpers::UrlHelper
|
9
|
+
|
10
|
+
def capture
|
11
|
+
yield
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "active_support/core_ext"
|
2
|
+
|
3
|
+
module Navigatrix
|
4
|
+
module Rendering
|
5
|
+
module Strategies
|
6
|
+
class HTMLAttributes < HashWithIndifferentAccess
|
7
|
+
def merge(other)
|
8
|
+
other.inject(self) do |this, (attribute, value)|
|
9
|
+
this.merge_attribute(attribute, value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def merge_attribute(attribute, value)
|
14
|
+
return self unless value.present?
|
15
|
+
|
16
|
+
tap do
|
17
|
+
self[attribute] = attribute_values(attribute).push(value).join(" ")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def attribute_values(attribute)
|
22
|
+
fetch(attribute, "").to_s.split(" ")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
require "navigatrix/rendering/strategy_factory"
|
4
|
+
require "navigatrix/rendering/strategies/html_attributes"
|
5
|
+
require "navigatrix/rendering/strategies/content_helpers"
|
6
|
+
|
7
|
+
module Navigatrix
|
8
|
+
module Rendering
|
9
|
+
module Strategies
|
10
|
+
class Item < SimpleDelegator
|
11
|
+
include ContentHelpers
|
12
|
+
|
13
|
+
attr_reader :options
|
14
|
+
|
15
|
+
def initialize(item, options = {})
|
16
|
+
super(item)
|
17
|
+
@options = options || {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def render
|
21
|
+
content_tag(:li, content, html_attributes) if render?
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def content
|
27
|
+
name_or_link + render_children
|
28
|
+
end
|
29
|
+
|
30
|
+
def name_or_link
|
31
|
+
linked? ? linked_content : unlinked_content
|
32
|
+
end
|
33
|
+
|
34
|
+
def linked_content
|
35
|
+
link_to(name, path)
|
36
|
+
end
|
37
|
+
|
38
|
+
def unlinked_content
|
39
|
+
name
|
40
|
+
end
|
41
|
+
|
42
|
+
def name
|
43
|
+
super.html_safe
|
44
|
+
end
|
45
|
+
|
46
|
+
def html_attributes
|
47
|
+
universal_attributes
|
48
|
+
.merge(super)
|
49
|
+
.merge_attribute(:class, html_class)
|
50
|
+
end
|
51
|
+
|
52
|
+
def universal_attributes
|
53
|
+
HTMLAttributes.new(options[:html_attributes])
|
54
|
+
end
|
55
|
+
|
56
|
+
def render_children
|
57
|
+
if has_children?
|
58
|
+
children_renderer.new(
|
59
|
+
children,
|
60
|
+
children_list_options,
|
61
|
+
children_item_options
|
62
|
+
).render
|
63
|
+
else
|
64
|
+
""
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def children_options
|
69
|
+
options
|
70
|
+
end
|
71
|
+
|
72
|
+
def html_class
|
73
|
+
active? ? active_class : inactive_class
|
74
|
+
end
|
75
|
+
|
76
|
+
def active_class
|
77
|
+
super.presence || options[:active_class] || "active"
|
78
|
+
end
|
79
|
+
|
80
|
+
def inactive_class
|
81
|
+
super.presence || options[:inactive_class]
|
82
|
+
end
|
83
|
+
|
84
|
+
def children_renderer
|
85
|
+
StrategyFactory.find_list_strategy(strategy_name)
|
86
|
+
end
|
87
|
+
|
88
|
+
def strategy_name
|
89
|
+
children_list_options[:renderer]
|
90
|
+
end
|
91
|
+
|
92
|
+
def children_list_options
|
93
|
+
children_options.fetch(:list, {})
|
94
|
+
end
|
95
|
+
|
96
|
+
def children_item_options
|
97
|
+
children_options.fetch(:item, {})
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -1,99 +1,43 @@
|
|
1
|
-
require "
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
1
|
+
require "navigatrix/rendering/strategy_factory"
|
2
|
+
require "navigatrix/rendering/strategies/content_helpers"
|
3
|
+
require "navigatrix/rendering/strategies/html_attributes"
|
4
|
+
require "navigatrix/rendering/strategies/item"
|
5
|
+
|
6
|
+
module Navigatrix
|
7
|
+
module Rendering
|
8
|
+
module Strategies
|
9
|
+
class List
|
10
|
+
include ContentHelpers
|
11
|
+
|
12
|
+
attr_reader :options, :item_options
|
13
|
+
|
14
|
+
def initialize(items, list_options = {}, item_options = {})
|
15
|
+
@items = items || []
|
16
|
+
@options = list_options
|
17
|
+
@item_options = item_options
|
18
|
+
end
|
70
19
|
|
71
|
-
|
72
|
-
|
73
|
-
|
20
|
+
def render
|
21
|
+
content_tag(:ul, render_items, html_attributes)
|
22
|
+
end
|
74
23
|
|
75
|
-
|
76
|
-
active? ? active_class : inactive_class
|
77
|
-
end
|
24
|
+
private
|
78
25
|
|
79
|
-
|
80
|
-
|
81
|
-
|
26
|
+
def render_items
|
27
|
+
items.map(&:render).join.html_safe
|
28
|
+
end
|
82
29
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
end
|
30
|
+
def items
|
31
|
+
@items.map { |item| item_class.new(item, item_options) }
|
32
|
+
end
|
87
33
|
|
88
|
-
|
89
|
-
|
90
|
-
tap do
|
91
|
-
self[attribute] = attribute_values(attribute).push(value).join(" ")
|
34
|
+
def item_class
|
35
|
+
StrategyFactory.find_item_strategy(item_options[:renderer])
|
92
36
|
end
|
93
|
-
end
|
94
37
|
|
95
|
-
|
96
|
-
|
38
|
+
def html_attributes
|
39
|
+
HTMLAttributes.new(options[:html_attributes])
|
40
|
+
end
|
97
41
|
end
|
98
42
|
end
|
99
43
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Navigatrix
|
2
|
+
module Rendering
|
3
|
+
class StrategyFactory
|
4
|
+
DEFAULT_LIST_STRATEGY = :unordered_list
|
5
|
+
DEFAULT_ITEM_STRATEGY = :item
|
6
|
+
|
7
|
+
class MissingStrategy < NameError ; end
|
8
|
+
|
9
|
+
def self.find_list_strategy(strategy_or_name, strategies = Navigatrix.list_renderers)
|
10
|
+
find_strategy(strategy_or_name, strategies, DEFAULT_LIST_STRATEGY)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.find_item_strategy(strategy_or_name, strategies = Navigatrix.item_renderers)
|
14
|
+
find_strategy(strategy_or_name, strategies, DEFAULT_ITEM_STRATEGY)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.find_strategy(strategy_or_name, strategies, default)
|
18
|
+
return strategy_or_name if strategy_or_name.is_a?(Class)
|
19
|
+
|
20
|
+
strategy_or_name ||= default
|
21
|
+
|
22
|
+
strategies.fetch(strategy_or_name) do
|
23
|
+
raise(MissingStrategy, "can't find strategy #{strategy_or_name}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/navigatrix/version.rb
CHANGED
@@ -0,0 +1,154 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "documentation" do
|
4
|
+
let(:request) do
|
5
|
+
double({
|
6
|
+
env: {}
|
7
|
+
})
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:render_context) do
|
11
|
+
double({
|
12
|
+
request: request
|
13
|
+
})
|
14
|
+
end
|
15
|
+
|
16
|
+
def render_navigation(configuration, options = {})
|
17
|
+
Navigatrix::Renderer.new(configuration, options.merge(render_context: render_context)).render
|
18
|
+
end
|
19
|
+
|
20
|
+
it "renders a basic navigation" do
|
21
|
+
request.stub(env: {
|
22
|
+
"PATH_INFO" => "/about-us"
|
23
|
+
})
|
24
|
+
rendered = render_navigation({
|
25
|
+
"Home" => "/",
|
26
|
+
"About Us" => "/about-us",
|
27
|
+
"Blog" => "/blog",
|
28
|
+
"Contact" => "/contact"
|
29
|
+
})
|
30
|
+
|
31
|
+
desired = <<-HTML
|
32
|
+
<ul>
|
33
|
+
<li><a href="/">Home</a></li>
|
34
|
+
<li class="active">About Us</li>
|
35
|
+
<li><a href="/blog">Blog</a></li>
|
36
|
+
<li><a href="/contact">Contact</a></li>
|
37
|
+
</ul>
|
38
|
+
HTML
|
39
|
+
|
40
|
+
rendered.should match_html(desired)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "renders a more sophisticated nav" do
|
44
|
+
render_context.stub(controller_name: "users")
|
45
|
+
request.stub(env: {
|
46
|
+
"PATH_INFO" => "/users/1"
|
47
|
+
})
|
48
|
+
|
49
|
+
rendered = render_navigation({
|
50
|
+
"Home" => "/",
|
51
|
+
"Users" => {
|
52
|
+
:path => "/users",
|
53
|
+
:active_states => [
|
54
|
+
{:path => "/my_account"},
|
55
|
+
{:path => /\/users\/\d*/},
|
56
|
+
]
|
57
|
+
},
|
58
|
+
"Sign In" => {
|
59
|
+
:path => "/sign_in",
|
60
|
+
:render? => false
|
61
|
+
},
|
62
|
+
"Sign Out" => {
|
63
|
+
:path => "/sign_out",
|
64
|
+
:render? => true
|
65
|
+
}
|
66
|
+
}, {
|
67
|
+
:item => {
|
68
|
+
:active_class => "active-nav-item"
|
69
|
+
},
|
70
|
+
:list => {
|
71
|
+
:html_attributes => {
|
72
|
+
:class => "nav"
|
73
|
+
}
|
74
|
+
}
|
75
|
+
})
|
76
|
+
|
77
|
+
desired = <<-HTML
|
78
|
+
<ul class="nav">
|
79
|
+
<li><a href="/">Home</a></li>
|
80
|
+
<li class="active-nav-item"><a href="/users">Users</a></li>
|
81
|
+
<li><a href="/sign_out">Sign Out</a></li>
|
82
|
+
</ul>
|
83
|
+
HTML
|
84
|
+
|
85
|
+
rendered.should match_html(desired)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "accepts item options" do
|
89
|
+
render_context.stub(controller_name: "users")
|
90
|
+
request.stub(env: {
|
91
|
+
"PATH_INFO" => "/users"
|
92
|
+
})
|
93
|
+
|
94
|
+
rendered = render_navigation({
|
95
|
+
"Users" => "/users",
|
96
|
+
"Clients" => "/clients"
|
97
|
+
}, {
|
98
|
+
item: {
|
99
|
+
:active_class => "custom-active",
|
100
|
+
:inactive_class => "custom-inactive",
|
101
|
+
:html_attributes => {
|
102
|
+
"class" => "nav-item",
|
103
|
+
"data-item" => "nav"
|
104
|
+
}
|
105
|
+
}
|
106
|
+
})
|
107
|
+
|
108
|
+
rendered.should match_html <<-HTML
|
109
|
+
<ul>
|
110
|
+
<li class="nav-item custom-active" data-item="nav">Users</li>
|
111
|
+
<li class="nav-item custom-inactive" data-item="nav"><a href="/clients">Clients</a></li>
|
112
|
+
</ul>
|
113
|
+
HTML
|
114
|
+
end
|
115
|
+
|
116
|
+
it "accepts item options on each item" do
|
117
|
+
render_context.stub(controller_name: "users")
|
118
|
+
request.stub(env: {
|
119
|
+
"PATH_INFO" => "/users"
|
120
|
+
})
|
121
|
+
|
122
|
+
rendered = render_navigation({
|
123
|
+
"Users" => {
|
124
|
+
:path => "/users",
|
125
|
+
:active_class => "custom-active"
|
126
|
+
},
|
127
|
+
"Clients" => {
|
128
|
+
:path => "/clients",
|
129
|
+
:inactive_class => "custom-inactive"
|
130
|
+
}
|
131
|
+
})
|
132
|
+
|
133
|
+
rendered.should match_html <<-HTML
|
134
|
+
<ul>
|
135
|
+
<li class="custom-active">Users</li>
|
136
|
+
<li class="custom-inactive"><a href="/clients">Clients</a></li>
|
137
|
+
</ul>
|
138
|
+
HTML
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
RSpec::Matchers.define :match_html do |expected|
|
143
|
+
match do |actual|
|
144
|
+
clean(expected) == clean(actual)
|
145
|
+
end
|
146
|
+
|
147
|
+
failure_message_for_should do |actual|
|
148
|
+
"expected that #{clean(actual)} to equal #{clean(expected)}"
|
149
|
+
end
|
150
|
+
|
151
|
+
def clean(text)
|
152
|
+
text.strip.gsub("\n", "").gsub(/(?<=\>)\s+?(?=\<)/, "")
|
153
|
+
end
|
154
|
+
end
|