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.
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
+