rocket_navigation 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+