navtastic 0.0.1 → 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.
@@ -1,6 +1,16 @@
1
1
  module Navtastic
2
2
  # Stores items generated by a definition block
3
3
  class Menu
4
+ # Configuration settings per menu
5
+ class Configuration
6
+ # @return [String,nil] a url to prepend every item url with
7
+ attr_accessor :base_url
8
+
9
+ def initialize
10
+ @base_url = nil
11
+ end
12
+ end
13
+
4
14
  include Enumerable
5
15
 
6
16
  # @return [Array<Item>] the items in this menu
@@ -9,15 +19,18 @@ module Navtastic
9
19
  # @return [Menu,nil] this parent of this menu
10
20
  attr_reader :parent
11
21
 
22
+ # @return [Menu::Configuration] the configuration for this menu
23
+ attr_reader :config
24
+
12
25
  # Create a new empty menu
13
26
  #
14
- # @param root [Menu] the root menu of this is a submenu
27
+ # @param parent [Menu] the parent menu of this is submenu
15
28
  def initialize(parent = nil)
16
29
  @parent = parent
17
30
 
31
+ @config = Menu::Configuration.new
18
32
  @current_item = nil
19
33
  @items = []
20
- @items_by_url = {}
21
34
  end
22
35
 
23
36
  # @return [true] if this menu is the root menu
@@ -43,14 +56,24 @@ module Navtastic
43
56
  #
44
57
  # @param name [String]the name to display in the menu
45
58
  # @param url [String] the url to link to, if the item is a link
59
+ # @param options [Hash] extra confiration options
46
60
  #
47
61
  # @yield [submenu] block to generate a sub menu
48
62
  # @yieldparam submenu [Menu] the menu to be initialized
49
- def item(name, url = nil)
50
- item = Item.new(self, name, url)
63
+ def item(name, url = nil, options = {})
64
+ # If only options were given and no url, move options to the right place
65
+ if url.is_a?(Hash) && options.empty?
66
+ options = url
67
+ url = nil
68
+ end
69
+
70
+ item = Item.new(self, name, url, options)
51
71
 
52
72
  if block_given?
53
73
  submenu = Menu.new(self)
74
+
75
+ submenu.config.base_url = url if options[:base_url] && url
76
+
54
77
  yield submenu
55
78
  item.submenu = submenu
56
79
  end
@@ -74,7 +97,7 @@ module Navtastic
74
97
  # @return [Item] if an item with that url exists
75
98
  # @return [nil] if the item doens't exist
76
99
  def [](url)
77
- @items_by_url[url]
100
+ items_by_url[url]
78
101
  end
79
102
 
80
103
  # Sets the current active item by url
@@ -87,7 +110,7 @@ module Navtastic
87
110
  return if current_url.nil?
88
111
 
89
112
  # Sort urls from longest to shortest and find the first matching substring
90
- matching_item = @items_by_url
113
+ matching_item = items_by_url
91
114
  .sort_by { |url, _item| -url.length }.to_h
92
115
  .find { |url, _item| current_url.start_with? url }
93
116
 
@@ -106,6 +129,17 @@ module Navtastic
106
129
  end
107
130
  end
108
131
 
132
+ # The base url of this menu, including all parent base urls
133
+ #
134
+ # @return [String]
135
+ def base_url
136
+ base_url = config.base_url.to_s
137
+ base_url.prepend Navtastic.configuration.base_url.to_s if root?
138
+ base_url.prepend @parent.base_url.to_s unless root?
139
+ base_url = nil if base_url.empty?
140
+ base_url
141
+ end
142
+
109
143
  protected
110
144
 
111
145
  # Register a newly added item
@@ -114,14 +148,23 @@ module Navtastic
114
148
  def register_item(item)
115
149
  return unless item.url
116
150
 
117
- @items_by_url[item.url] = item
118
-
119
151
  if root?
120
152
  # The first item with a url is the default current item
121
- @current_item = item if @current_item.nil? && item.url
153
+ @current_item = item if @current_item.nil?
122
154
  else
123
155
  @parent.register_item(item)
124
156
  end
125
157
  end
158
+
159
+ def items_by_url
160
+ indexed_items = {}
161
+
162
+ @items.each do |item|
163
+ indexed_items[item.url] = item if item.url?
164
+ indexed_items.merge!(item.submenu.items_by_url) if item.submenu?
165
+ end
166
+
167
+ indexed_items
168
+ end
126
169
  end
127
170
  end
@@ -1,7 +1,9 @@
1
1
  require 'arbre'
2
2
 
3
3
  module Navtastic
4
- # Generate HTML based on a menu
4
+ # Generate HTML based on a menu.
5
+ #
6
+ # This base renderer only generates a structure and no css classes.
5
7
  #
6
8
  # The actual HTML generation is done using the
7
9
  # [Arbre](https://github.com/activeadmin/arbre) gem.
@@ -11,39 +13,32 @@ module Navtastic
11
13
  # Create a new renderer
12
14
  #
13
15
  # @param menu [Menu]
16
+ # @param options [Hash]
14
17
  #
15
- # @return [Renderer]
16
- def self.render(menu)
17
- new(menu: menu) do
18
- menu(menu)
18
+ # @return [Self]
19
+ def self.render(menu, options = {})
20
+ new(root: menu, options: options) do
21
+ render_menu(root)
19
22
  end
20
23
  end
21
24
 
22
- # Starting a new root menu or submenu (e.g. `<ul>` tag)
25
+ # Start a new root menu or submenu (e.g. `<ul>` tag)
23
26
  #
24
27
  # @param menu [Menu]
25
28
  # @return [Arbre::HTML::Tag]
26
- def menu(menu)
27
- ul do
28
- menu.each do |item|
29
- item_container item
30
- end
31
- end
29
+ def menu_tag(menu) # rubocop:disable Lint/UnusedMethodArgument
30
+ ul { yield }
32
31
  end
33
32
 
34
33
  # The container for every menu item (e.g. `<li>` tags)
35
34
  #
36
35
  # @param item [Item]
37
36
  # @return [Arbre::HTML::Tag]
38
- def item_container(item)
39
- li(class: css_classes_string(item, :item_container)) do
40
- item_content item
41
-
42
- menu(item.submenu) if item.submenu?
43
- end
37
+ def item_tag(item) # rubocop:disable Lint/UnusedMethodArgument
38
+ li { yield }
44
39
  end
45
40
 
46
- # The item itself (e.g. `<a>` tag for links)
41
+ # The item itself (e.g. `<a>` tag for links or `<span>` for text)
47
42
  #
48
43
  # @param item [Item]
49
44
  # @return [Arbre::HTML::Tag]
@@ -51,37 +46,65 @@ module Navtastic
51
46
  if item.url
52
47
  a(href: item.url) { item.name }
53
48
  else
54
- span item.name
49
+ span { item.name }
55
50
  end
56
51
  end
57
52
 
58
- # Decide which css classes are needed for this item
53
+ # Check if a submenu should be displayed inside the item container of after
54
+ # it.
59
55
  #
60
- # For example, the {#item_container} uses this to retrieve the css class for
61
- # the current active item.
56
+ # Defaults to `true`.
62
57
  #
63
- # @param item [Item] the current item that is rendered
64
- # @param context [Symbol] which method is asking for the css classes
58
+ # @param item [Item]
59
+ # @return [bool]
60
+ def menu_inside_container?(item) # rubocop:disable Lint/UnusedMethodArgument
61
+ true
62
+ end
63
+
64
+ private
65
+
66
+ # Render the menu structure
65
67
  #
66
- # @return [Array<String>] list of css classes to apply to the HTML element
67
- def css_classes(item, context)
68
- classes = []
68
+ # @param menu [Menu]
69
+ # @return [Arbre::HTML::Tag]
70
+ def render_menu(menu)
71
+ menu_tag(menu) do
72
+ menu.each do |item|
73
+ render_item(item)
74
+ end
75
+ end
76
+ end
69
77
 
70
- case context
71
- when :item_container
72
- classes << 'current' if item.current?
78
+ # Render the item structure
79
+ #
80
+ # @param item [Item]
81
+ # @return [Arbre::HTML::Tag]
82
+ def render_item(item)
83
+ element = item_tag(item) do
84
+ render_item_content(item)
73
85
  end
74
86
 
75
- classes
87
+ # Add custom css classes to the element
88
+ element.class_list << item.options[:class] if item.options[:class]
89
+
90
+ return unless item.submenu? && !menu_inside_container?(item)
91
+ render_menu(item.submenu)
76
92
  end
77
93
 
78
- # Same as {css_classes} method, but joins classes together in a string
94
+ # Render the item content
79
95
  #
80
- # @see css_classes
81
- #
82
- # @return [String]
83
- def css_classes_string(item, context)
84
- css_classes(item, context).join ' '
96
+ # @param item [Item]
97
+ # @return [Arbre::HTML::Tag]
98
+ def render_item_content(item)
99
+ element = item_content(item)
100
+
101
+ # Add custom css classes to the element
102
+ if item.options[:content_class] && element.respond_to?(:class_list)
103
+ element.class_list << item.options[:content_class]
104
+ end
105
+
106
+ return unless item.submenu? && menu_inside_container?(item)
107
+ render_menu(item.submenu)
85
108
  end
86
109
  end
87
110
  end
@@ -0,0 +1,35 @@
1
+ require 'arbre'
2
+
3
+ module Navtastic
4
+ class Renderer
5
+ # This renderer adds css classes and structure for the bootstrap 4
6
+ # framework
7
+ class Bootstrap4 < Navtastic::Renderer
8
+ def menu_tag(_menu)
9
+ class_list = ['nav']
10
+ class_list << 'flex-column' if vertical?
11
+
12
+ ul(class: class_list.join(' ')) { yield }
13
+ end
14
+
15
+ def item_tag(item)
16
+ element = super(item)
17
+ element.class_list << 'nav-item'
18
+ element
19
+ end
20
+
21
+ def item_content(item)
22
+ element = super(item)
23
+ element.class_list << 'nav-link'
24
+ element.class_list << 'active' if item.current?
25
+ element
26
+ end
27
+
28
+ private
29
+
30
+ def vertical?
31
+ true
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,44 @@
1
+ require 'arbre'
2
+
3
+ module Navtastic
4
+ class Renderer
5
+ # This renderer adds css classes and structure for the bulma.io framework
6
+ # @see file:README.md#Bulma_Configuration documentation on bulma renderer
7
+ # options
8
+ class Bulma < Navtastic::Renderer
9
+ def submenu_inside_container?(item)
10
+ !(headers? && item.menu.root?)
11
+ end
12
+
13
+ def menu_tag(menu)
14
+ if headers? && menu.root?
15
+ nav(class: 'menu') { yield }
16
+ else
17
+ ul(class: 'menu-list') { yield }
18
+ end
19
+ end
20
+
21
+ def item_tag(item)
22
+ if headers? && item.menu.root?
23
+ para(class: 'menu-label') { yield }
24
+ else
25
+ li { yield }
26
+ end
27
+ end
28
+
29
+ def item_content(item)
30
+ element = super(item)
31
+
32
+ element.class_list << 'is-active' if item.current?
33
+
34
+ element
35
+ end
36
+
37
+ private
38
+
39
+ def headers?
40
+ options[:headers]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,58 @@
1
+ require 'arbre'
2
+
3
+ module Navtastic
4
+ class Renderer
5
+ # This renderer adds css classes and structure for the foundation 6
6
+ # framework
7
+ # @see file:README.md#Foundation_Configuration documentation on foundation
8
+ # renderer options
9
+ class Foundation6 < Navtastic::Renderer
10
+ def menu_tag(menu)
11
+ class_list = ['menu']
12
+ class_list << 'vertical' if vertical?
13
+ class_list << 'nested' unless menu.root?
14
+
15
+ list = ul(class: class_list.join(' ')) { yield }
16
+
17
+ if drilldown? && menu.root?
18
+ list.class_list << 'drilldown'
19
+ list.set_attribute('data-drilldown', true)
20
+ end
21
+
22
+ list
23
+ end
24
+
25
+ def item_tag(item)
26
+ element = super(item)
27
+ element.class_list << 'is-active' if item.current?
28
+ element
29
+ end
30
+
31
+ def item_content(item)
32
+ element = if item.url?
33
+ a(href: item.url) { item.name }
34
+ elsif drilldown?
35
+ a(href: '#') { item.name }
36
+ else
37
+ span(class: 'menu-text') { item.name }
38
+ end
39
+
40
+ if drilldown? && item.active? && options[:active_class]
41
+ element.class_list << options[:active_class]
42
+ end
43
+
44
+ element
45
+ end
46
+
47
+ private
48
+
49
+ def vertical?
50
+ true
51
+ end
52
+
53
+ def drilldown?
54
+ options[:style] == :drilldown
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,18 @@
1
+ require 'arbre'
2
+
3
+ module Navtastic
4
+ class Renderer
5
+ # This renderer only adds a `current` css class to the current item
6
+ class Simple < Navtastic::Renderer
7
+ def item_tag(item)
8
+ li(class: current_css_class(item)) { yield }
9
+ end
10
+
11
+ private
12
+
13
+ def current_css_class(item)
14
+ item.current? ? 'current' : nil
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module Navtastic
2
- VERSION = '0.0.1'.freeze
2
+ VERSION = '0.1.0'.freeze
3
3
  end
@@ -1,29 +1,16 @@
1
- <%
2
- Navtastic.define :main_menu do |menu|
3
- menu.item "Home", '/' do |submenu|
4
- submenu.item "Posts", '/posts'
5
- submenu.item "About", '/about'
6
- end
7
-
8
- menu.item "Settings" do |submenu|
9
- submenu.item "General", '/settings'
10
- submenu.item "Profile", '/settings/profile'
11
- end
12
- end
13
- %>
14
-
15
1
  <html>
16
2
  <head>
17
3
  <title>Navtastic Demo Server</title>
18
- <style type="text/css">
19
- .current { font-weight: bold }
20
- .current ul { font-weight: normal }
21
- </style>
22
4
  </head>
23
5
 
24
6
  <body>
25
- <%= Navtastic.render :main_menu, current_url %>
7
+ <h1>Renderers:</h1>
26
8
 
27
- <pre>current_url: <%= current_url %></pre>
9
+ <ul>
10
+ <li><a href="simple">Simple</a></li>
11
+ <li><a href="bootstrap4">Bootstrap4</a></li>
12
+ <li><a href="bulma">Bulma</a></li>
13
+ <li><a href="foundation6">Foundation6</a></li>
14
+ </ul>
28
15
  </body>
29
16
  </html>