crumby 1.1.0
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.
- 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
|