crumby 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +115 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +87 -0
- data/Rakefile +39 -0
- data/VERSION +1 -0
- data/crumby.gemspec +77 -0
- data/lib/crumby.rb +41 -0
- data/lib/crumby/entry.rb +26 -0
- data/lib/crumby/helper.rb +6 -0
- data/lib/crumby/helper/controller.rb +53 -0
- data/lib/crumby/helper/view.rb +20 -0
- data/lib/crumby/renderer.rb +11 -0
- data/lib/crumby/renderer/base.rb +41 -0
- data/lib/crumby/renderer/haml.rb +39 -0
- data/lib/crumby/trail.rb +121 -0
- data/spec/crumby/entry_spec.rb +43 -0
- data/spec/crumby/helper_spec.rb +168 -0
- data/spec/crumby/renderer/base_spec.rb +36 -0
- data/spec/crumby/trail_spec.rb +207 -0
- data/spec/spec_helper.rb +26 -0
- metadata +169 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Crumby::Helper
|
3
|
+
# view helper
|
4
|
+
module ViewHelper
|
5
|
+
# render breadcrumb
|
6
|
+
# @see Crumby::Trail#render
|
7
|
+
# @see Crumby::Renderer::Base
|
8
|
+
# @overload crumby(options)
|
9
|
+
# @param [Hash] options
|
10
|
+
# @param [Hash] options passthrough to renderer
|
11
|
+
# @overload crumby(scope, options)
|
12
|
+
# @param [Symbol, String] scope scope of breadcrumb
|
13
|
+
# @param [Hash] options passthrough to renderer
|
14
|
+
def crumby(*args)
|
15
|
+
options = args.extract_options!
|
16
|
+
scope = args.first || :default
|
17
|
+
crumby_trail(scope).render(self, options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Crumby::Renderer
|
3
|
+
# base for renderer
|
4
|
+
# @abstract
|
5
|
+
class Base
|
6
|
+
attr_reader :view, :trail, :options
|
7
|
+
|
8
|
+
def initialize(trail, view, options)
|
9
|
+
@trail = trail
|
10
|
+
@view = view
|
11
|
+
@options = default_options.merge options
|
12
|
+
end
|
13
|
+
|
14
|
+
# render trail
|
15
|
+
# @return [String] rendered trail
|
16
|
+
def render
|
17
|
+
render_list do
|
18
|
+
trail.entries.each do |entry|
|
19
|
+
render_entry(entry)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# empty default options
|
25
|
+
# @abstract
|
26
|
+
def default_options
|
27
|
+
{}
|
28
|
+
end
|
29
|
+
|
30
|
+
# @abstract
|
31
|
+
def render_list(&block)
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
# @abstract
|
36
|
+
def render_entry(entry)
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Crumby::Renderer
|
3
|
+
# haml renderer
|
4
|
+
class Haml < Base
|
5
|
+
|
6
|
+
# @return [Hash] default options for this renderer
|
7
|
+
def default_options
|
8
|
+
{
|
9
|
+
divider: "/",
|
10
|
+
link_last: false,
|
11
|
+
link_first: true
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
# render list by block
|
16
|
+
# the block call render_entry for each entry
|
17
|
+
def render_list(&block)
|
18
|
+
view.capture_haml do
|
19
|
+
view.haml_tag :ul, class: "breadcrumb" do
|
20
|
+
yield
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# render entry
|
26
|
+
# @param [Crumby::Entry] entry that will be rendered
|
27
|
+
def render_entry(entry)
|
28
|
+
view.haml_tag :li, class: (entry.last? ? 'active' : nil) do
|
29
|
+
if entry.route.nil? or (entry.last? and not options[:link_last]) or (entry.first? and not options[:link_first])
|
30
|
+
view.haml_concat entry.label
|
31
|
+
else
|
32
|
+
view.haml_concat view.link_to(entry.label, entry.route)
|
33
|
+
end
|
34
|
+
view.haml_tag "span.divider", options[:divider] unless entry.last?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/lib/crumby/trail.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Crumby
|
3
|
+
|
4
|
+
# it represent on breadcrumb trail
|
5
|
+
class Trail
|
6
|
+
attr_reader :entries
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@entries = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns total entries
|
13
|
+
# @return [Fixnum] total entries
|
14
|
+
def count
|
15
|
+
entries.count
|
16
|
+
end
|
17
|
+
|
18
|
+
# add a new entry
|
19
|
+
# @example
|
20
|
+
# add :books
|
21
|
+
# add @book
|
22
|
+
# add [:admin, @book]
|
23
|
+
# add "Books", :admin_books
|
24
|
+
# add "Books", [:admin,:books]
|
25
|
+
# add "Book", [:admin, @book]
|
26
|
+
# add "Google", "http://google.de"
|
27
|
+
# @overload render(combined)
|
28
|
+
# @param [Object] combined
|
29
|
+
# @overload render(label, route)
|
30
|
+
# @param [String] label the label passthrough to link_to
|
31
|
+
# @param [Symbol, Array, String] route route passthrough to link_to
|
32
|
+
#
|
33
|
+
# @return [Crumby::Entry] builded entry
|
34
|
+
def add(*args)
|
35
|
+
# extract options
|
36
|
+
options = args.extract_options!
|
37
|
+
|
38
|
+
# call without any arguments
|
39
|
+
raise ArgumentError, "Need arguments." if args.empty?
|
40
|
+
|
41
|
+
# process arguments
|
42
|
+
if args.count == 1
|
43
|
+
value = args.first
|
44
|
+
if value.is_a? String
|
45
|
+
label = value
|
46
|
+
elsif value.is_a? Symbol
|
47
|
+
label = value.to_s.humanize
|
48
|
+
route = value
|
49
|
+
elsif value.respond_to? :model_name
|
50
|
+
label = value.model_name.human
|
51
|
+
route = value
|
52
|
+
elsif value.kind_of? Array
|
53
|
+
if value.last.respond_to? :model_name
|
54
|
+
label = value.last.model_name.human
|
55
|
+
else
|
56
|
+
label = value.last.to_s.humanize
|
57
|
+
end
|
58
|
+
route = value
|
59
|
+
else
|
60
|
+
label = value.to_s.humanize
|
61
|
+
end
|
62
|
+
else
|
63
|
+
label = args.first
|
64
|
+
route = args.second
|
65
|
+
end
|
66
|
+
|
67
|
+
entry = Entry.new(self, count, label, route, options)
|
68
|
+
@entries << entry
|
69
|
+
entry
|
70
|
+
end
|
71
|
+
|
72
|
+
# render the trail by a renderer
|
73
|
+
# @overload render(view, options)
|
74
|
+
# @param [ActionView::Base] view
|
75
|
+
# @param [Hash] options passthrough to renderer
|
76
|
+
# @option options [Class] :renderer overwrite default renderer
|
77
|
+
#
|
78
|
+
# @return [String] rendered trail
|
79
|
+
def render(*args)
|
80
|
+
options = args.extract_options!
|
81
|
+
renderer_class = options[:renderer] || Crumby::Renderer.default_renderer
|
82
|
+
raise ArgumentError if not renderer_class.class == Class or not renderer_class.ancestors.include? Crumby::Renderer::Base
|
83
|
+
view = args.first
|
84
|
+
renderer_class.new(self, view, options).render
|
85
|
+
end
|
86
|
+
|
87
|
+
# build a title of trail e.g. for page title
|
88
|
+
# @example
|
89
|
+
# title
|
90
|
+
# title "The Site"
|
91
|
+
# title "The Site", divider: " - "
|
92
|
+
# @overload title(suffix, options)
|
93
|
+
# @param [String] suffix last item.
|
94
|
+
# @param [Hash] options
|
95
|
+
# @option options [String] :divider The divider. default is " » "
|
96
|
+
# @option options [Boolean] :reverse reverse the title building. default is true
|
97
|
+
# @option options [Boolean] :skip_first remove first entry. it is usefull
|
98
|
+
#
|
99
|
+
# @return [String] build title. e.g New Book » Books » Admin
|
100
|
+
def title(*args)
|
101
|
+
options = args.extract_options!
|
102
|
+
suffix = args.first
|
103
|
+
|
104
|
+
default_options = {
|
105
|
+
divider: " » ",
|
106
|
+
reverse: true,
|
107
|
+
skip_first: true
|
108
|
+
}
|
109
|
+
options = default_options.merge args.extract_options!
|
110
|
+
|
111
|
+
title_entries = entries
|
112
|
+
title_entries = title_entries[1..-1] if options[:skip_first]
|
113
|
+
|
114
|
+
title = title_entries.reverse.collect{ |e| e[:label] }
|
115
|
+
title += [suffix] if suffix.present?
|
116
|
+
title.join(options[:divider])
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
3
|
+
|
4
|
+
describe Crumby::Entry do
|
5
|
+
|
6
|
+
context "#new" do
|
7
|
+
let(:entry_label) { "TestLabel" }
|
8
|
+
let(:entry_route) { :test }
|
9
|
+
let(:entry_options) { { test: true } }
|
10
|
+
let(:trail) { stub :trail }
|
11
|
+
|
12
|
+
subject { Crumby::Entry.new(trail, 1, entry_label, entry_route, entry_options) }
|
13
|
+
|
14
|
+
its(:trail) { should equal trail }
|
15
|
+
its(:position) { should eq 1 }
|
16
|
+
its(:label) { should eq entry_label }
|
17
|
+
its(:route) { should eq entry_route }
|
18
|
+
its(:options) { should eq entry_options }
|
19
|
+
end
|
20
|
+
|
21
|
+
context "on trail with 10 breadcrumb" do
|
22
|
+
let(:trail) { trail = stub :trail, count: 10 }
|
23
|
+
|
24
|
+
context "any breadcrumb" do
|
25
|
+
subject { Crumby::Entry.new trail, 0, "Test" }
|
26
|
+
its(:total) { should eq 10 }
|
27
|
+
end
|
28
|
+
|
29
|
+
context "first breadcrumb" do
|
30
|
+
subject { Crumby::Entry.new trail, 0, "Test" }
|
31
|
+
its(:first?) { should be_true }
|
32
|
+
its(:last?) { should_not be_true }
|
33
|
+
end
|
34
|
+
|
35
|
+
context "last breadcrumb" do
|
36
|
+
subject { Crumby::Entry.new trail, 9, "Test" }
|
37
|
+
its(:first?) { should_not be_true }
|
38
|
+
its(:last?) { should be_true }
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
3
|
+
require "active_support/all"
|
4
|
+
|
5
|
+
# class DummyController
|
6
|
+
# include Crumby::Helper
|
7
|
+
# end
|
8
|
+
|
9
|
+
# describe Crumby::Helper do
|
10
|
+
# let(:controller) { DummyController.new }
|
11
|
+
# let(:options) { { the_options: true, the_options2: true } }
|
12
|
+
|
13
|
+
# describe "#crumby_trail" do
|
14
|
+
|
15
|
+
# let! (:default_trail) { controller.crumby_trail(:default) }
|
16
|
+
# let! (:different_trail) { controller.crumby_trail(:different) }
|
17
|
+
|
18
|
+
# it "should match default scope with \":default\"" do
|
19
|
+
# controller.crumby_trail(:default).should equal default_trail
|
20
|
+
# end
|
21
|
+
|
22
|
+
# it "should match default scope with \"default\"" do
|
23
|
+
# controller.crumby_trail("default").should equal default_trail
|
24
|
+
# end
|
25
|
+
|
26
|
+
# it "should match diffrent scope with \":diffrent\"" do
|
27
|
+
# controller.crumby_trail(:different).should equal different_trail
|
28
|
+
# end
|
29
|
+
|
30
|
+
# it "should not match default or diffrent scope with \":other\"" do
|
31
|
+
# controller.crumby_trail(:other).should_not equal default_trail
|
32
|
+
# controller.crumby_trail(:other).should_not equal different_trail
|
33
|
+
# end
|
34
|
+
|
35
|
+
# end
|
36
|
+
|
37
|
+
# describe "#add_crumby" do
|
38
|
+
|
39
|
+
# let(:label) { "Name" }
|
40
|
+
# let(:route) { :route }
|
41
|
+
|
42
|
+
# subject { controller.crumby_trail }
|
43
|
+
|
44
|
+
# it "should receive all arguments" do
|
45
|
+
# controller.crumby_trail.should_receive(:add).with(label, route, options)
|
46
|
+
# controller.add_crumby(label, route, options)
|
47
|
+
# end
|
48
|
+
|
49
|
+
# context "with a diffrent scope" do
|
50
|
+
# let(:scope) { :a_different }
|
51
|
+
# subject { controller.crumby_trail(scope) }
|
52
|
+
|
53
|
+
# it "should receive all arguments" do
|
54
|
+
# subject.should_receive(:add).with(label, route, kind_of(Hash))
|
55
|
+
# controller.add_crumby(label, route, scope: scope)
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
|
60
|
+
# describe "#crumby_title" do
|
61
|
+
# let (:trail) { stub :trail, title: stub }
|
62
|
+
|
63
|
+
# before { controller.stub(:crumby_trail).and_return(trail) }
|
64
|
+
|
65
|
+
# context "with default scope" do
|
66
|
+
# context "without suffix" do
|
67
|
+
# after { controller.crumby_title }
|
68
|
+
|
69
|
+
# it "should load default trail" do
|
70
|
+
# controller.should_receive(:crumby_trail).with(:default)
|
71
|
+
# end
|
72
|
+
|
73
|
+
# it "should load title without suffix" do
|
74
|
+
# trail.should_receive(:title).with(kind_of(Hash))
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
|
78
|
+
# context "with suffix" do
|
79
|
+
# let(:suffix) { "test" }
|
80
|
+
# after { controller.crumby_title suffix }
|
81
|
+
|
82
|
+
# it "should load title with suffix" do
|
83
|
+
# trail.should_receive(:title).with(suffix, kind_of(Hash))
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
|
87
|
+
# context "with options" do
|
88
|
+
# after { controller.crumby_title options }
|
89
|
+
|
90
|
+
# it "should load title with options" do
|
91
|
+
# trail.should_receive(:title).with(options)
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
|
95
|
+
|
96
|
+
# end
|
97
|
+
|
98
|
+
# context "with different scope" do
|
99
|
+
# after { controller.crumby_title scope: :test}
|
100
|
+
# it "should load different trail" do
|
101
|
+
# controller.should_receive(:crumby_trail).with(:test)
|
102
|
+
# end
|
103
|
+
# end
|
104
|
+
|
105
|
+
# # it "should call title on trail" do
|
106
|
+
# # controller.crumby_trail.should_receive(:title).with(options)
|
107
|
+
# # controller.crumby_title options
|
108
|
+
# # end
|
109
|
+
|
110
|
+
# # context "with a diffrent scope" do
|
111
|
+
# # let(:scope) { :a_different }
|
112
|
+
# # subject { controller.crumby_trail(scope) }
|
113
|
+
|
114
|
+
# # it "should call title on trail" do
|
115
|
+
# # subject.should_receive(:title).with(any_args, kind_of(Hash))
|
116
|
+
# # controller.crumby_title(scope, {})
|
117
|
+
# # end
|
118
|
+
# # end
|
119
|
+
|
120
|
+
# end
|
121
|
+
|
122
|
+
# describe "#crumby" do
|
123
|
+
# let (:renderer) { stub :renderer, render: stub }
|
124
|
+
# let (:trail) { stub :trail, renderer: renderer }
|
125
|
+
# before { controller.stub(:crumby_trail).and_return(trail) }
|
126
|
+
|
127
|
+
# context "with default scope" do
|
128
|
+
# after { controller.crumby }
|
129
|
+
|
130
|
+
# it "should load crumb" do
|
131
|
+
# controller.should_receive(:crumby_trail).with(:default)
|
132
|
+
# end
|
133
|
+
# end
|
134
|
+
|
135
|
+
# context "with :test scope" do
|
136
|
+
# after { controller.crumby :test }
|
137
|
+
|
138
|
+
# it "should load crumb" do
|
139
|
+
# controller.should_receive(:crumby_trail).with(:test)
|
140
|
+
# end
|
141
|
+
# end
|
142
|
+
|
143
|
+
# context "with default renderer" do
|
144
|
+
# after { controller.crumby }
|
145
|
+
|
146
|
+
# it "should call renderer" do
|
147
|
+
# trail.should_receive(:renderer).with(nil)
|
148
|
+
# end
|
149
|
+
|
150
|
+
# it "should call render on renderer with options" do
|
151
|
+
# renderer.should_receive(:render).with(kind_of Hash)
|
152
|
+
# end
|
153
|
+
# end
|
154
|
+
|
155
|
+
# context "with custom renderer" do
|
156
|
+
# after { controller.crumby renderer: renderer, option: true }
|
157
|
+
|
158
|
+
# it "should call renderer" do
|
159
|
+
# trail.should_receive(:renderer).with(renderer)
|
160
|
+
# end
|
161
|
+
|
162
|
+
# it "should call render on renderer with options" do
|
163
|
+
# renderer.should_receive(:render).with(hash_including(option: true))
|
164
|
+
# end
|
165
|
+
# end
|
166
|
+
# end
|
167
|
+
|
168
|
+
# end
|