rocket_navigation 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +17 -0
- data/CHANGELOG.md +13 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +205 -0
- data/Guardfile +7 -0
- data/LICENSE.txt +23 -0
- data/README.md +154 -0
- data/Rakefile +15 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/rocket_navigation.rb +32 -0
- data/lib/rocket_navigation/configuration.rb +21 -0
- data/lib/rocket_navigation/helpers.rb +120 -0
- data/lib/rocket_navigation/item.rb +114 -0
- data/lib/rocket_navigation/item_container.rb +135 -0
- data/lib/rocket_navigation/railtie.rb +10 -0
- data/lib/rocket_navigation/renderer.rb +13 -0
- data/lib/rocket_navigation/renderer/base.rb +159 -0
- data/lib/rocket_navigation/renderer/breadcrumbs.rb +58 -0
- data/lib/rocket_navigation/renderer/breadcrumbs_on_rails.rb +27 -0
- data/lib/rocket_navigation/renderer/json.rb +29 -0
- data/lib/rocket_navigation/renderer/links.rb +35 -0
- data/lib/rocket_navigation/renderer/list.rb +45 -0
- data/lib/rocket_navigation/renderer/text.rb +21 -0
- data/lib/rocket_navigation/version.rb +3 -0
- data/rocket_navigation.gemspec +38 -0
- metadata +255 -0
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
require 'rdoc/task'
|
9
|
+
|
10
|
+
RDoc::Task.new do |rdoc|
|
11
|
+
rdoc.rdoc_dir = 'rdoc'
|
12
|
+
rdoc.title = 'RocketNavigation'
|
13
|
+
rdoc.options << '--inline-source'
|
14
|
+
rdoc.rdoc_files.include('README.md', 'lib/**/*.rb')
|
15
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rocket_navigation"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require "rocket_navigation/version"
|
2
|
+
|
3
|
+
# cherry picking active_support stuff
|
4
|
+
require 'active_support/core_ext/array'
|
5
|
+
require 'active_support/core_ext/hash'
|
6
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
7
|
+
|
8
|
+
require 'rocket_navigation/configuration'
|
9
|
+
require 'rocket_navigation/item'
|
10
|
+
require 'rocket_navigation/item_container'
|
11
|
+
require 'rocket_navigation/renderer'
|
12
|
+
|
13
|
+
if defined?(Rails)
|
14
|
+
require "rocket_navigation/railtie"
|
15
|
+
end
|
16
|
+
|
17
|
+
module RocketNavigation
|
18
|
+
mattr_accessor :default_renderer
|
19
|
+
mattr_accessor :registered_renderers
|
20
|
+
|
21
|
+
self.default_renderer = :list
|
22
|
+
|
23
|
+
self.registered_renderers = {
|
24
|
+
list: RocketNavigation::Renderer::List,
|
25
|
+
links: RocketNavigation::Renderer::Links,
|
26
|
+
breadcrumbs: RocketNavigation::Renderer::Breadcrumbs,
|
27
|
+
breadcrumbs_on_rails: RocketNavigation::Renderer::BreadcrumbsOnRails,
|
28
|
+
text: RocketNavigation::Renderer::Text,
|
29
|
+
json: RocketNavigation::Renderer::Json
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RocketNavigation
|
2
|
+
def self.configuration
|
3
|
+
@configuration ||= Configuration.new
|
4
|
+
end
|
5
|
+
def self.config
|
6
|
+
@configuration ||= Configuration.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.configure
|
10
|
+
yield configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
class Configuration
|
14
|
+
attr_accessor :renderer
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@renderer = RocketNavigation::Renderer::List
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module RocketNavigation
|
2
|
+
# View helpers to render the navigation.
|
3
|
+
#
|
4
|
+
# Use render_navigation as following to render your navigation:
|
5
|
+
# * call <tt>render_navigation</tt> without :level option to render your
|
6
|
+
# complete navigation as nested tree.
|
7
|
+
# * call <tt>render_navigation(level: x)</tt> to render a specific
|
8
|
+
# navigation level (e.g. level: 1 to render your primary navigation,
|
9
|
+
# level: 2 to render the sub navigation and so forth)
|
10
|
+
# * call <tt>render_navigation(:level => 2..3)</tt> to render navigation
|
11
|
+
# levels 2 and 3).
|
12
|
+
#
|
13
|
+
# For example, you could use render_navigation(level: 1) to render your
|
14
|
+
# primary navigation as tabs and render_navigation(level: 2..3) to render
|
15
|
+
# the rest of the navigation as a tree in a sidebar.
|
16
|
+
#
|
17
|
+
# ==== Examples (using Haml)
|
18
|
+
# #primary_navigation= render_navigation(level: 1)
|
19
|
+
#
|
20
|
+
# #sub_navigation= render_navigation(level: 2)
|
21
|
+
#
|
22
|
+
# #nested_navigation= render_navigation
|
23
|
+
#
|
24
|
+
# #top_navigation= render_navigation(level: 1..2)
|
25
|
+
# #sidebar_navigation= render_navigation(level: 3)
|
26
|
+
module Helpers
|
27
|
+
# Renders the navigation according to the specified options-hash.
|
28
|
+
#
|
29
|
+
# The following options are supported:
|
30
|
+
# * <tt>:level</tt> - defaults to :all which renders the the sub_navigation
|
31
|
+
# for an active primary_navigation inside that active
|
32
|
+
# primary_navigation item.
|
33
|
+
# Specify a specific level to only render that level of navigation
|
34
|
+
# (e.g. level: 1 for primary_navigation, etc).
|
35
|
+
# Specifiy a Range of levels to render only those specific levels
|
36
|
+
# (e.g. level: 1..2 to render both your first and second levels, maybe
|
37
|
+
# you want to render your third level somewhere else on the page)
|
38
|
+
# * <tt>:expand_all</tt> - defaults to false. If set to true the all
|
39
|
+
# specified levels will be rendered as a fully expanded
|
40
|
+
# tree (always open). This is useful for javascript menus like Superfish.
|
41
|
+
# * <tt>:items</tt> - you can specify the items directly (e.g. if items are
|
42
|
+
# dynamically generated from database).
|
43
|
+
# See SimpleNavigation::ItemsProvider for documentation on what to
|
44
|
+
# provide as items.
|
45
|
+
# * <tt>:renderer</tt> - specify the renderer to be used for rendering the
|
46
|
+
# navigation. Either provide the Class or a symbol matching a registered
|
47
|
+
# renderer. Defaults to :list (html list renderer).
|
48
|
+
#
|
49
|
+
# Instead of using the <tt>:items</tt> option, a block can be passed to
|
50
|
+
# specify the items dynamically
|
51
|
+
#
|
52
|
+
# ==== Examples
|
53
|
+
# render_navigation do |menu|
|
54
|
+
# menu.item :posts, "Posts", posts_path
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
def render_navigation(options = {}, &block)
|
58
|
+
container = ItemContainer.new(1, options)
|
59
|
+
container.view_context = view_context
|
60
|
+
if block_given?
|
61
|
+
yield container
|
62
|
+
end
|
63
|
+
container.render(options)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns true or false based on the provided path and condition
|
67
|
+
# Possible condition values are:
|
68
|
+
# Boolean -> true | false
|
69
|
+
# Symbol -> :exclusive | :inclusive
|
70
|
+
# Regex -> /regex/
|
71
|
+
# Controller/Action Pair -> [[:controller], [:action_a, :action_b]]
|
72
|
+
#
|
73
|
+
# Example usage:
|
74
|
+
#
|
75
|
+
# is_active_nav_link?('/root', true)
|
76
|
+
# is_active_nav_link?('/root', :exclusive)
|
77
|
+
# is_active_nav_link?('/root', /^\/root/)
|
78
|
+
# is_active_nav_link?('/root', ['users', ['show', 'edit']])
|
79
|
+
#
|
80
|
+
# Source: https://github.com/comfy/active_link_to/blob/master/lib/active_link_to/active_link_to.rb
|
81
|
+
# Copyright (c) 2009-17 Oleg Khabarov
|
82
|
+
# MIT License
|
83
|
+
def is_active_nav_link?(url, condition = nil)
|
84
|
+
@is_active_link ||= {}
|
85
|
+
@is_active_link[[url, condition]] ||= begin
|
86
|
+
original_url = url
|
87
|
+
url = Addressable::URI::parse(url).path
|
88
|
+
path = request.original_fullpath
|
89
|
+
case condition
|
90
|
+
when :inclusive, nil
|
91
|
+
!path.match(/^#{Regexp.escape(url).chomp('/')}(\/.*|\?.*)?$/).blank?
|
92
|
+
when :exclusive
|
93
|
+
!path.match(/^#{Regexp.escape(url)}\/?(\?.*)?$/).blank?
|
94
|
+
when :exact
|
95
|
+
path == original_url
|
96
|
+
when Proc
|
97
|
+
condition.call(original_url)
|
98
|
+
when Regexp
|
99
|
+
!path.match(condition).blank?
|
100
|
+
when Array
|
101
|
+
controllers = Array.wrap(condition[0])
|
102
|
+
actions = Array.wrap(condition[1])
|
103
|
+
(controllers.blank? || controllers.member?(params[:controller])) &&
|
104
|
+
(actions.blank? || actions.member?(params[:action])) ||
|
105
|
+
controllers.any? do |controller, action|
|
106
|
+
params[:controller] == controller.to_s && params[:action] == action.to_s
|
107
|
+
end
|
108
|
+
when TrueClass
|
109
|
+
true
|
110
|
+
when FalseClass
|
111
|
+
false
|
112
|
+
when Hash
|
113
|
+
condition.all? do |key, value|
|
114
|
+
params[key].to_s == value.to_s
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module RocketNavigation
|
2
|
+
# Represents an item in your navigation.
|
3
|
+
class Item
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
attr_reader :key,
|
7
|
+
:name,
|
8
|
+
:sub_navigation,
|
9
|
+
:url,
|
10
|
+
:options
|
11
|
+
|
12
|
+
def_delegators :container, :view_context
|
13
|
+
def_delegators :view_context, :is_active_nav_link?
|
14
|
+
|
15
|
+
# see ItemContainer#item
|
16
|
+
#
|
17
|
+
# The subnavigation (if any) is either provided by a block or
|
18
|
+
# passed in directly as <tt>items</tt>
|
19
|
+
def initialize(container, key, name, url = nil, opts = {}, &sub_nav_block)
|
20
|
+
self.container = container
|
21
|
+
self.key = key
|
22
|
+
self.name = name.respond_to?(:call) ? name.call : name
|
23
|
+
self.url = url.respond_to?(:call) ? url.call : url
|
24
|
+
self.options = opts
|
25
|
+
|
26
|
+
setup_sub_navigation(options[:items], &sub_nav_block)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the item's name.
|
30
|
+
# If :apply_generator option is set to true (default),
|
31
|
+
# the name will be passed to the name_generator specified
|
32
|
+
# in the configuration.
|
33
|
+
#
|
34
|
+
def name(options = {})
|
35
|
+
@name
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns true if this navigation item should be rendered as 'selected'.
|
39
|
+
# An item is selected if
|
40
|
+
#
|
41
|
+
# * it has a subnavigation and one of its subnavigation items is selected or
|
42
|
+
# * its url matches the url of the current request (auto highlighting)
|
43
|
+
#
|
44
|
+
def selected?
|
45
|
+
@selected ||= selected_by_subnav? || selected_by_condition?
|
46
|
+
end
|
47
|
+
|
48
|
+
def active_branch?
|
49
|
+
@active_branch ||= selected_by_subnav? && !selected_by_condition?
|
50
|
+
end
|
51
|
+
|
52
|
+
def active_leaf?
|
53
|
+
@active_leaf ||= selected_by_condition?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the :highlights_on option as set at initialization
|
57
|
+
def highlights_on
|
58
|
+
@highlights_on ||= options[:highlights_on]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the :method option as set at initialization
|
62
|
+
def method
|
63
|
+
@method ||= options[:method]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns true if item has a subnavigation and
|
67
|
+
# the sub_navigation is selected
|
68
|
+
def selected_by_subnav?
|
69
|
+
sub_navigation && sub_navigation.selected?
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns true if the item's url matches the request's current url.
|
73
|
+
def selected_by_condition?
|
74
|
+
is_active_nav_link?(url, highlights_on)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns true if both the item's url and the request's url are root_path
|
78
|
+
def root_path_match?
|
79
|
+
url == '/'
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
attr_accessor :container,
|
85
|
+
:options
|
86
|
+
|
87
|
+
attr_writer :key,
|
88
|
+
:name,
|
89
|
+
:sub_navigation,
|
90
|
+
:url
|
91
|
+
|
92
|
+
|
93
|
+
def remove_anchors(url_with_anchors)
|
94
|
+
url_with_anchors && url_with_anchors.split('#').first
|
95
|
+
end
|
96
|
+
|
97
|
+
def remove_query_params(url_with_params)
|
98
|
+
url_with_params && url_with_params.split('?').first
|
99
|
+
end
|
100
|
+
|
101
|
+
def setup_sub_navigation(items = nil, &sub_nav_block)
|
102
|
+
return unless sub_nav_block || items
|
103
|
+
|
104
|
+
self.sub_navigation = container.new_child
|
105
|
+
|
106
|
+
if sub_nav_block
|
107
|
+
sub_nav_block.call sub_navigation
|
108
|
+
else
|
109
|
+
sub_navigation.items = items
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module RocketNavigation
|
2
|
+
class ItemContainer
|
3
|
+
attr_accessor :renderer, :view_context, :options
|
4
|
+
attr_reader :items, :level
|
5
|
+
|
6
|
+
attr_accessor :container_html, :item_html, :link_html, :selected_class
|
7
|
+
|
8
|
+
def default_html_options
|
9
|
+
if options[:no_default_classes]
|
10
|
+
@container_html = {}
|
11
|
+
@item_html = {}
|
12
|
+
@link_html = {}
|
13
|
+
@selected_class = {}
|
14
|
+
else
|
15
|
+
@container_html = {class: "nav"}
|
16
|
+
@item_html = {class: 'nav-item'}
|
17
|
+
@link_html = {class: 'nav-link'}
|
18
|
+
@selected_class = {branch: "active-branch", item: "active", link: "active"}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(level = 1, options = {})
|
23
|
+
@level = level
|
24
|
+
@items ||= []
|
25
|
+
@options = options
|
26
|
+
@renderer = RocketNavigation.config.renderer
|
27
|
+
default_html_options
|
28
|
+
end
|
29
|
+
|
30
|
+
def new_child
|
31
|
+
child = ItemContainer.new(level + 1, options)
|
32
|
+
child.view_context = view_context
|
33
|
+
child
|
34
|
+
end
|
35
|
+
|
36
|
+
def item(key, name, url = nil, options = {}, &block)
|
37
|
+
return unless should_add_item?(options)
|
38
|
+
key = url if key.nil?
|
39
|
+
item = Item.new(self, key, name, url, options, &block)
|
40
|
+
add_item item, options
|
41
|
+
end
|
42
|
+
|
43
|
+
def items=(new_items)
|
44
|
+
new_items.each do |item|
|
45
|
+
item_adapter = ItemAdapter.new(item)
|
46
|
+
next unless should_add_item?(item_adapter.options)
|
47
|
+
add_item item_adapter.to_simple_navigation_item(self), item_adapter.options
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the Item with the specified key, nil otherwise.
|
52
|
+
#
|
53
|
+
def [](navi_key)
|
54
|
+
items.find { |item| item.key == navi_key }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the level of the item specified by navi_key.
|
58
|
+
# Recursively works its way down the item's sub_navigations if the desired
|
59
|
+
# item is not found directly in this container's items.
|
60
|
+
# Returns nil if item cannot be found.
|
61
|
+
#
|
62
|
+
def level_for_item(navi_key)
|
63
|
+
return level if self[navi_key]
|
64
|
+
|
65
|
+
items.each do |item|
|
66
|
+
next unless item.sub_navigation
|
67
|
+
level = item.sub_navigation.level_for_item(navi_key)
|
68
|
+
return level if level
|
69
|
+
end
|
70
|
+
return nil
|
71
|
+
end
|
72
|
+
|
73
|
+
# Renders the items in this ItemContainer using the configured renderer.
|
74
|
+
#
|
75
|
+
# The options are the same as in the view's render_navigation call
|
76
|
+
# (they get passed on)
|
77
|
+
def render(options = {})
|
78
|
+
renderer_instance(options).render(self)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns true if any of this container's items is selected.
|
82
|
+
#
|
83
|
+
def selected?
|
84
|
+
items.any?(&:selected?)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the currently selected item, nil if no item is selected.
|
88
|
+
#
|
89
|
+
def selected_item
|
90
|
+
items.find(&:selected?)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns true if there are no items defined for this container.
|
94
|
+
def empty?
|
95
|
+
items.empty?
|
96
|
+
end
|
97
|
+
|
98
|
+
def inspect
|
99
|
+
"#<RocketNavigation::ItemContainer:#{object_id}
|
100
|
+
@renderer=#{@renderer.inspect}
|
101
|
+
@options=#{@options.inspect}
|
102
|
+
@items=#{@items.inspect}
|
103
|
+
@level=#{@level.inspect}
|
104
|
+
@container_html=#{@container_html.inspect}
|
105
|
+
@item_html=#{@item_html.inspect}
|
106
|
+
@link_html=#{@link_html.inspect}
|
107
|
+
@selected_class=#{@selected_class.inspect}
|
108
|
+
@view_context=#{view_context.nil? ? nil : "[rails view context, hidden from inspect]"}
|
109
|
+
>"
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def add_item(item, options)
|
115
|
+
items << item
|
116
|
+
end
|
117
|
+
|
118
|
+
def should_add_item?(options)
|
119
|
+
[options[:if]].flatten.compact.all? { |m| evaluate_method(m) } &&
|
120
|
+
[options[:unless]].flatten.compact.none? { |m| evaluate_method(m) }
|
121
|
+
end
|
122
|
+
|
123
|
+
def renderer_instance(options)
|
124
|
+
return renderer.new(self, options) unless options[:renderer]
|
125
|
+
|
126
|
+
if options[:renderer].is_a?(Symbol)
|
127
|
+
registered_renderer = SimpleNavigation.registered_renderers[options[:renderer]]
|
128
|
+
registered_renderer.new(self, options)
|
129
|
+
else
|
130
|
+
options[:renderer].new(self, options)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|