waiter 0.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YmQ1YzdlZmU3MDM4ZDgwOWQ0ZmNhMjI5MDcwNGYxZTUwMTE5YzI1MQ==
5
+ data.tar.gz: !binary |-
6
+ ZTg1ZmIzMDc1MmQ4MDQ0MDk1ZDRmZjIwNzViMDVmYmFlYzBjYzhiNQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NTBhMTMxOWQ4ZmNiZWEwZmNkMDkwMDBiZDA3YjIyODNhYTM1M2ZhNDgwYjY2
10
+ OThhODZmYmQ3MDVhMWQzYTY4YWM4NWMwNGFiNzg5MTk4NWM1OTlkODIxMDM3
11
+ ZTAwYjFhMTE1YWU3ZDhjYzE5ZDkyODI1ZjllNjdmMDEyZTY2ZGQ=
12
+ data.tar.gz: !binary |-
13
+ MGVjZTZmMWY4NmY3MTM4NDI0NGQyNjBkMTc1MTFkZGM4OWFkYjU1YTc2MmIz
14
+ MWNkNWYwNjhiMWY5YTRhNGQ2NWNkMzdjMDQzZDA3YzBkN2FlNzEwMzc4OTIz
15
+ NzU4YmZjZTA2YjFhNDVmNWEwMTM2NTJhMzk1ZGIyOTZkMzVjNGY=
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/README.md CHANGED
@@ -24,33 +24,41 @@ Or install it yourself as:
24
24
 
25
25
  To create a new menu:
26
26
 
27
- Waiter::Menu.serve do |menu|
28
- menu.file
29
- menu.edit
30
- menu.view
27
+ Waiter::Menu.serve(:my_menu) do
28
+ file
29
+ edit
30
+ view
31
31
  # ...
32
32
  end
33
33
 
34
- The above defines a menu with three items. By default, each item name (ie. "file", "edit", etc.) corresponds both to the I18n string to use (so `menu.file` will call `I18n.t(:file)`) as well as to the controller to use (`menu.file` will correspond to `FileController`, and the menu item will link to `FileController#index` by default).
34
+ The above defines a menu with three items. By default, each item name (ie. "file", "edit", etc.) corresponds both to the
35
+ I18n string to use (so `file` will call `I18n.t(:file, scope: [:menu, :my_menu])`) as well as to the controller to use
36
+ (`file` will correspond to `FileController`, and the menu item will link to `FileController#index` by default).
35
37
 
36
- Of course, there will be situations where the path may need to be explicitly specified. In this case, the `:controller`
37
- and `:action` options can be specified:
38
+ Of course, there will be situations where the path may need to be explicitly specified. In this case, you can use a path finder:
38
39
 
39
- Waiter::Menu.serve do |menu|
40
- menu.file :controller => 'documents', :action => 'show'
40
+ Waiter::Menu::serve do
41
+ menu.file documents_path
41
42
  end
43
+
44
+ Alternately, the `:controller` and `:action` options can be specified:
42
45
 
43
- In this case the File menu will correspond to `DocumentsController#show`, and will only light up for that action. If
44
- no specific action is given, the menu item will be lit up for all actions within the given controller.
46
+ Waiter::Menu.serve do
47
+ menu.file :controller => :documents, :action => :index
48
+ end
49
+
50
+ In both of the above examples, the File menu will correspond to `DocumentsController#index`, and will be shown as selected
51
+ for any action within that controller.
45
52
 
46
53
  There may also be times where you need multiple controllers to light up the same menu item. This can be achieved by
47
54
  passing an array to the `:controllers` option:
48
55
 
49
- Waiter::Menu.serve do |menu|
50
- menu.file :controller => 'documents', :controllers => ['files', 'print', ... ]
56
+ Waiter::Menu.serve do
57
+ file :controllers => ['files', 'print', ... ]
51
58
  end
52
59
 
53
- This will create a menu that links to `DocumentsController#index`, which will be lit up the user hits any action within the `FilesController`, `PrintController`, as well as the `DocumentsController`.
60
+ This will create a menu that links to `DocumentsController#index`, which will be lit up the user hits any action within
61
+ the `FilesController`, `PrintController`, as well as the `DocumentsController`.
54
62
 
55
63
 
56
64
  ### Submenus
@@ -61,20 +69,18 @@ passed as a block to the root menu item.
61
69
 
62
70
  Submenus automatically assume the controller to use is the controller specified by its root menu item, so a
63
71
  controller does not need to be specified unless it differs. As well, the menu item name is assumed to be used
64
- for the action within that controller, so action does not need to be specified unless it differs from the name.
72
+ for the controller for that item, so it does not need to be specified unless it differs from the name.
65
73
  An id can also be specified if necessary (with the `:id` option).
66
74
 
67
- Waiter::Menu.serve do |menu|
68
- menu.file, :controllers => ['print'] do |file|
69
- file.new # Corresponds to FileController#new
70
- file.open :action => :read # Corresponds to FileController#read
71
- file.save :action => :write # Corresponds to FileController#write
72
- file.print, :controller => 'print' # Corresponds to PrintController#print
75
+ Waiter::Menu.serve do
76
+ file do
77
+ new # Corresponds to FileController#new
78
+ print print_file_path # Corresponds to PrintController#print
79
+ list :action => :all # Corresponds to ListController#all
73
80
  end
74
81
  end
75
82
 
76
- If an action isn't explicitly given, and the menu item name does not correspond to an actual action, the default
77
- action for the controller will be used instead.
83
+ If a path hash is given without an `action` key, the index action will be inferred.
78
84
 
79
85
 
80
86
  ### Menu Options
@@ -82,14 +88,6 @@ action for the controller will be used instead.
82
88
  There are two options that you can use when building your menu. Options can be passed into build when defining
83
89
  a menu or specified once the menu is defined by accessing the menu.options hash.
84
90
 
85
- `string_prefix`: Allows for a prefix for I18n strings to be specified.
86
-
87
- Waiter::Menu.serve(:string_prefix => 'menu_') do |menu|
88
- menu.file
89
- end
90
-
91
- # will use I18n.t(:menu_file) instead of I18n.t(:file)
92
-
93
91
  `selected`: Allows the selected menu item to be overridden, so a specified menu item can be lit up regardless of
94
92
  what the current controller/action is. This can be useful if there is an action that falls under different menu
95
93
  items depending on application context, or if there is a controller that may correspond to multiple menu items.
@@ -123,7 +121,11 @@ the context is self.
123
121
 
124
122
  Waiter::Menu::Drawer.new(menu, context).draw
125
123
 
126
- If a different `Drawer` is desired, `Waiter::Menu::Drawer` can be subclassed and the methods `draw` and `draw_submenu` overridden to provide an alternate format.
124
+ If a different `Drawer` is desired, `Waiter::Menu::Drawer` can be subclassed and the method `draw` overridden to provide an alternate format.
125
+
126
+ Alternately, `Waiter::Menu` responds to `draw` directly:
127
+
128
+ menu.draw(context)
127
129
 
128
130
  ## Contributing
129
131
 
data/Rakefile CHANGED
@@ -1 +1,11 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
9
+ rescue LoadError
10
+ # no rspec available
11
+ end
Binary file
@@ -0,0 +1,129 @@
1
+ /*
2
+ * Menu bar
3
+ */
4
+ .waiter-menu
5
+ {
6
+ width: 100%;
7
+ display: block;
8
+ height: 40px;
9
+ border: 0px;
10
+ background: image-url('waiter/bg.gif') repeat-x left top;
11
+
12
+ div.top-item
13
+ {
14
+ float: left;
15
+ position: relative;
16
+ top: 10px;
17
+ height: 20px;
18
+ min-width: 80px;
19
+ border-right: 1px solid #e6e9ef;
20
+ border-left: 0;
21
+ margin: 0;
22
+ text-align: center;
23
+ /* Fix IE7 z-index bug - See http://brenelz.com/blog/squish-the-internet-explorer-z-index-bug/ */
24
+ z-index: 10501;
25
+ color: #e6e9ef;
26
+ font-family: "Arial";
27
+ font-weight: bold;
28
+ font-size: 14px;
29
+ padding: 5px 15px 5px 10px;
30
+ cursor: default;
31
+
32
+ a
33
+ {
34
+ color: #FFF;
35
+ display: block;
36
+ height: 20px;
37
+ min-width: 80px;
38
+ text-decoration: none;
39
+ }
40
+
41
+ &:hover, &.selected
42
+ {
43
+ color: #FFF;
44
+ background: image-url('waiter/selected-bg.gif') repeat-x left top;
45
+ }
46
+
47
+ $submenu-border-color: #888;
48
+ $submenu-item-border-color: #CCC;
49
+ $submenu-item-height: 20px;
50
+
51
+ ul.submenu
52
+ {
53
+ position: absolute;
54
+ top: 30px;
55
+ left: 0;
56
+ background-color: #ffffff;
57
+ display: none;
58
+ z-index: 10500;
59
+ margin: 0;
60
+ padding: 0;
61
+ border: 2px solid $submenu-border-color;
62
+
63
+ li
64
+ {
65
+ list-style-type: none;
66
+ min-height: $submenu-item-height;
67
+ line-height: $submenu-item-height;
68
+ vertical-align: middle;
69
+ background-color: #e6e9ef;
70
+ border-top: 1px solid $submenu-item-border-color;
71
+ min-width: 150px;
72
+ text-align: left;
73
+ white-space: nowrap;
74
+ color: #000000;
75
+ border-collapse: collapse;
76
+
77
+ font-family: "Arial";
78
+ font-weight: normal;
79
+ font-size: 12px;
80
+ white-space: nowrap;
81
+
82
+ a, a:hover
83
+ {
84
+ color: #000000;
85
+ font-weight: normal;
86
+ padding-left: 8px;
87
+ padding-right: 8px;
88
+ line-height: $submenu-item-height + 12px;
89
+ height: $submenu-item-height + 12px;
90
+ }
91
+
92
+ &:first-child
93
+ {
94
+ border-top: 0;
95
+ }
96
+
97
+ &:hover
98
+ {
99
+ background-color: #ff9900;
100
+ }
101
+
102
+ &.separator
103
+ {
104
+ min-height: 0;
105
+ max-height: 0;
106
+ padding: 0;
107
+ border: 0;
108
+ }
109
+ }
110
+
111
+ li.separator + li
112
+ {
113
+ border-top: 2px solid $submenu-border-color;
114
+ }
115
+
116
+ li.separator + li.separator
117
+ {
118
+ display: none;
119
+ }
120
+ }
121
+
122
+ &:hover ul.submenu
123
+ {
124
+ background-color: #333333;
125
+ /*border:1px solid #333333;*/
126
+ display: block;
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,8 @@
1
+ .top-item{ class: item.selected? ? 'selected' : '' }
2
+ - if item.path
3
+ = link_to item.menu_title, item.path
4
+ - else
5
+ = item.menu_title
6
+
7
+ - if item.submenu?
8
+ = render 'waiter/submenu', parent: item, items: item.submenu.items(true)
@@ -0,0 +1,3 @@
1
+ .waiter-menu{ id: menu.name }
2
+ - menu.items.each do |item|
3
+ = render 'waiter/item', item: item
@@ -0,0 +1,5 @@
1
+ - section.items.each do |subitem|
2
+ = render 'waiter/sub_item', item: item, subitem: subitem
3
+
4
+ - unless last
5
+ %li.separator
@@ -0,0 +1,2 @@
1
+ %li
2
+ = link_to subitem.item_title, subitem.path
@@ -0,0 +1,6 @@
1
+ %ul.submenu
2
+ - items.each.with_index do |subitem, idx|
3
+ - if subitem.section?
4
+ = render 'waiter/section', item: parent, section: subitem, last: idx == items.count - 1
5
+ - else
6
+ = render 'waiter/sub_item', item: parent, subitem: subitem
data/lib/waiter/dsl.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'active_support/core_ext/array/extract_options'
2
+
3
+ module Waiter
4
+ module DSL
5
+ def section(options = {}, &block)
6
+ add_section(options, &block)
7
+ end
8
+
9
+ def method_missing(name, *args, &block)
10
+ return context.send(name, *args, &block) if context.respond_to?(name)
11
+
12
+ path = args.shift
13
+ options = args.extract_options!
14
+
15
+ if path.is_a?(Hash) && !(path.key?(:controller) || path.key?(:action))
16
+ options, path = path, nil
17
+ end
18
+
19
+ options[:controllers] ||= []
20
+ add(name, path, options, &block)
21
+ return nil
22
+ end
23
+ end
24
+ end
@@ -1,63 +1,21 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
1
3
  module Waiter
2
4
  class Menu
3
5
  # Outputs a Menu into HTML
4
6
  class Drawer
5
- # @context is a Controller binding, which allows the ActionView helpers to work in the correct context
6
- delegate :link_to, :content_tag, :params, :to => :@context
7
+ attr_reader :menu, :context
8
+
9
+ delegate :render, :link_to, :content_tag, :params, :to => :context
7
10
 
8
11
  def initialize(menu, context)
12
+ # context is a Controller binding, which allows the ActionView helpers to work in the correct context
9
13
  @context = context
10
14
  @menu = menu
11
15
  end
12
16
 
13
17
  def draw
14
- content_tag :div, :id => "menu" do
15
- out = ActiveSupport::SafeBuffer.new
16
-
17
- @menu.items.each do |(name, menu)|
18
- menu[:controller] = name unless menu[:controller]
19
-
20
- # Allow an :if option which will only display the menu if true
21
- if !menu[:if] or menu[:if].call
22
- # If there is a :selected option, use that instead of trying to determine the selected menu item
23
- selected = menu_selected?(name, menu[:controller], menu[:action] || menu[:actions], menu.delete(:controllers))
24
-
25
- out << content_tag(:span, :class => selected ? "selected" : nil) do
26
- out2 = ActiveSupport::SafeBuffer.new
27
- out2 << link_to(I18n.t(@menu.options[:string_prefix].to_s + name.to_s), { :controller => menu.delete(:controller).to_s.absolutify, :action => (menu.delete(:action) || menu.delete(:actions).andand.first) }.merge(menu))
28
- out2 << draw_submenu(@menu.submenus[name]) if @menu.submenus[name]
29
- out2
30
- end
31
- end
32
- end
33
-
34
- out << '<br class="clear" />'.html_safe
35
- out
36
- end
37
- end
38
-
39
- def draw_submenu(submenu)
40
- content_tag :table, :class => "dropdown", :cellspacing => 0 do
41
-
42
- out = ActiveSupport::SafeBuffer.new
43
-
44
- submenu.items.each do |(name, menu)|
45
- out << content_tag(:tr) do
46
- content_tag :td do
47
- controller = menu.delete(:controller) || submenu.options[:controller]
48
- action =
49
- menu.delete(:action) || begin
50
- controller_class = "#{controller}_controller".classify.constantize
51
- controller_class.action_methods.include?(name.to_s) ? name : nil
52
- end rescue nil || :index
53
-
54
- link_to I18n.t(submenu.options[:string_prefix].to_s + name.to_s), { :controller => controller.to_s.absolutify, :action => action }.merge(menu)
55
- end
56
- end
57
- end
58
-
59
- out
60
- end
18
+ render partial: 'waiter/menu_bar', locals: { menu: menu }
61
19
  end
62
20
 
63
21
  protected
@@ -82,10 +40,4 @@ module Waiter
82
40
  end
83
41
  end
84
42
  end
85
- end
86
-
87
- class String
88
- def absolutify
89
- self.gsub(%r{^(?!/)}, "/")
90
- end
91
43
  end
@@ -0,0 +1,137 @@
1
+ require 'active_support/core_ext/object/try'
2
+ require 'active_support/core_ext/hash/reverse_merge'
3
+ require 'active_support/core_ext/module/delegation'
4
+ require 'active_support/core_ext/object/blank'
5
+
6
+ module Waiter
7
+ class Menu
8
+ class Item
9
+ attr_reader :parent, :name, :path, :submenu
10
+ attr_accessor :options
11
+ delegate :context, to: :parent
12
+
13
+ def initialize(parent, name, path = {}, options = {}, &block)
14
+ @parent = parent
15
+ @name = name
16
+ @path = complete_path_for(path)
17
+ @options = options
18
+
19
+ collect_controllers
20
+
21
+ @options.reverse_merge!(parent.options || {})
22
+
23
+ if block_given?
24
+ @submenu = Menu.new([@parent.name, name].compact, @parent.context, @options.dup)
25
+ @submenu.parent = self
26
+ @submenu.update(&block)
27
+ end
28
+ end
29
+
30
+ def section?
31
+ false
32
+ end
33
+
34
+ def items
35
+ @submenu.try(:items) || []
36
+ end
37
+
38
+ def [](name)
39
+ return nil if items.empty?
40
+ items[name]
41
+ end
42
+
43
+ def submenu?
44
+ submenu.present?
45
+ end
46
+
47
+ def menu_title
48
+ translate(:title)
49
+ end
50
+
51
+ def item_title
52
+ translate(name)
53
+ end
54
+
55
+ def controller
56
+ @controller ||= find_controller
57
+ end
58
+
59
+ def controllers
60
+ options[:controllers]
61
+ end
62
+
63
+ def selected?
64
+ return name == options[:selected] if options[:selected]
65
+
66
+ current_controller = context.params[:controller]
67
+
68
+ if wildcard_controllers.any?
69
+ return true if wildcard_controllers.any? do |c|
70
+ r = Regexp.new(c.sub(%r{/\*$}, '(/*)?').gsub('*', '.*'))
71
+ r =~ current_controller
72
+ end
73
+ end
74
+
75
+ controllers.include?(current_controller)
76
+ end
77
+
78
+ def inspect
79
+ "#<#{self.class.name}:#{'0x%x' % (36971870 << 1)} name=#{name.inspect} options=#{options.inspect} parent=#{parent.inspect} submenu=#{submenu.inspect}>"
80
+ end
81
+
82
+ private
83
+
84
+ def translate(key)
85
+ scope = i18n_scope
86
+ scope.pop if scope.last == key
87
+ I18n.t(key, scope: scope, cascade: true)
88
+ end
89
+
90
+ def i18n_scope
91
+ [:menu, parent.name, name].flatten.compact
92
+ end
93
+
94
+ def complete_path_for(path)
95
+ return if path.nil? && parent.top?
96
+
97
+ case path
98
+ when String
99
+ path
100
+
101
+ when Hash, NilClass
102
+ path ||= {}
103
+ path[:action] ||= :index
104
+ path[:controller] ||= name
105
+ path[:controller] = path[:controller].to_s.gsub(%r{^(?!/)}, '/')
106
+ path
107
+ end
108
+ end
109
+
110
+ def find_controller
111
+ case path
112
+ when Hash
113
+ path[:controller].to_s.gsub(%r{^/}, '')
114
+
115
+ when String
116
+ request = parent.context.request
117
+ route = Rails.application.routes.recognize_path("#{request.protocol}#{request.host}#{path}")
118
+ route ? route[:controller].to_s : nil
119
+ end
120
+
121
+ rescue ActionController::RoutingError
122
+ nil
123
+ end
124
+
125
+ def collect_controllers
126
+ target = parent.top? ? self : parent
127
+ target.options[:controllers] ||= []
128
+ target.options[:controllers] << controller unless controller.blank?
129
+ target.options[:controllers].uniq!
130
+ end
131
+
132
+ def wildcard_controllers
133
+ @wildcard_controllers ||= controllers.select{ |c| c['*'] }
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,19 @@
1
+ module Waiter
2
+ class Menu
3
+ class ItemList < Array
4
+ def include?(other)
5
+ return map(&:name).include?(other) if other.is_a? Symbol
6
+ super
7
+ end
8
+
9
+ def [](index)
10
+ return detect{ |item| item.name == index } if index.is_a? Symbol
11
+ super
12
+ end
13
+
14
+ def names
15
+ map(&:name)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ require 'waiter/menu/item'
2
+
3
+ module Waiter
4
+ class Menu
5
+ class Section < Item
6
+ def initialize(parent, options = {}, &block)
7
+ super(parent, nil, nil, options, &block)
8
+ end
9
+
10
+ def section?
11
+ true
12
+ end
13
+
14
+ private
15
+
16
+ def complete_path_for(*)
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
data/lib/waiter/menu.rb CHANGED
@@ -1,26 +1,68 @@
1
+ require 'waiter/dsl'
2
+ require 'waiter/menu/item_list'
3
+ require 'waiter/menu/item'
4
+ require 'waiter/menu/section'
5
+ require 'waiter/menu/drawer'
6
+
1
7
  module Waiter
2
8
  class Menu
3
- attr_accessor :options, :submenus
9
+ attr_accessor :name, :context, :options, :submenu, :parent
10
+
11
+ include DSL
4
12
 
5
- class << self
6
- def serve(options = {})
7
- yield Builder.new(obj = self.new(options))
8
- obj
13
+ def self.serve(name, context = nil, options = {}, &block)
14
+ new(name, context, options).tap do |menu|
15
+ menu.update(&block)
9
16
  end
10
17
  end
11
18
 
12
- def initialize(options = {})
13
- @menu_items = []
14
- @submenus = {}
19
+ def initialize(name, context, options = {})
20
+ @name = name
21
+ @context = context
22
+ @items = ItemList.new
15
23
  @options = options
16
24
  end
17
25
 
18
- def add(name, args = {})
19
- @menu_items << [name, args]
26
+ def update(&block)
27
+ instance_eval(&block)
28
+ end
29
+
30
+ def draw(context = nil)
31
+ Drawer.new(self, context).draw
32
+ end
33
+
34
+ def add(name, path = {}, options = {}, &block)
35
+ items << Item.new(self, name, path, options, &block)
36
+ end
37
+
38
+ def add_section(options = {}, &block)
39
+ section = Section.new(self, options, &block)
40
+ items << section if section.items.any?
41
+ end
42
+
43
+ def sections
44
+ items.select(&:section?) || []
45
+ end
46
+
47
+ def [](name)
48
+ items[name]
49
+ end
50
+
51
+ def items(sorted = false)
52
+ return @items unless sorted
53
+
54
+ items = @items.dup
55
+ items.sort_by!(&options[:sort].to_sym) if options.fetch(:sort, false)
56
+ items.reverse! if options.fetch(:reverse, false)
57
+ ItemList.new(items)
58
+ end
59
+
60
+ def top?
61
+ parent.nil?
20
62
  end
21
63
 
22
- def items
23
- @menu_items
64
+ def inspect
65
+ "#<#{self.class.name}:#{'0x%x' % (36971870 << 1)} name=#{name.inspect} options=#{options.inspect}>"
24
66
  end
25
67
  end
26
68
  end
@@ -1,3 +1,3 @@
1
1
  module Waiter
2
- VERSION = "0.0.1"
2
+ VERSION = "2.0.0"
3
3
  end