rocket_navigation 0.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.
- 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
|
+
|