navtastic 0.0.1 → 0.1.0

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