##// END OF EJS Templates
Hide the main menu div if there isn't any items for it. #3259...
Eric Davis -
r3425:7514e12d331b
parent child
Show More
@@ -1,74 +1,76
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3 <head>
3 <head>
4 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
4 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
5 <title><%=h html_title %></title>
5 <title><%=h html_title %></title>
6 <meta name="description" content="<%= Redmine::Info.app_name %>" />
6 <meta name="description" content="<%= Redmine::Info.app_name %>" />
7 <meta name="keywords" content="issue,bug,tracker" />
7 <meta name="keywords" content="issue,bug,tracker" />
8 <%= stylesheet_link_tag 'application', :media => 'all' %>
8 <%= stylesheet_link_tag 'application', :media => 'all' %>
9 <%= javascript_include_tag :defaults %>
9 <%= javascript_include_tag :defaults %>
10 <%= heads_for_wiki_formatter %>
10 <%= heads_for_wiki_formatter %>
11 <!--[if IE]>
11 <!--[if IE]>
12 <style type="text/css">
12 <style type="text/css">
13 * html body{ width: expression( document.documentElement.clientWidth < 900 ? '900px' : '100%' ); }
13 * html body{ width: expression( document.documentElement.clientWidth < 900 ? '900px' : '100%' ); }
14 body {behavior: url(<%= stylesheet_path "csshover.htc" %>);}
14 body {behavior: url(<%= stylesheet_path "csshover.htc" %>);}
15 </style>
15 </style>
16 <![endif]-->
16 <![endif]-->
17 <%= call_hook :view_layouts_base_html_head %>
17 <%= call_hook :view_layouts_base_html_head %>
18 <!-- page specific tags -->
18 <!-- page specific tags -->
19 <%= yield :header_tags -%>
19 <%= yield :header_tags -%>
20 </head>
20 </head>
21 <body>
21 <body>
22 <div id="wrapper">
22 <div id="wrapper">
23 <div id="wrapper2">
23 <div id="wrapper2">
24 <div id="top-menu">
24 <div id="top-menu">
25 <div id="account">
25 <div id="account">
26 <%= render_menu :account_menu -%>
26 <%= render_menu :account_menu -%>
27 </div>
27 </div>
28 <%= content_tag('div', "#{l(:label_logged_as)} #{link_to_user(User.current, :format => :username)}", :id => 'loggedas') if User.current.logged? %>
28 <%= content_tag('div', "#{l(:label_logged_as)} #{link_to_user(User.current, :format => :username)}", :id => 'loggedas') if User.current.logged? %>
29 <%= render_menu :top_menu -%>
29 <%= render_menu :top_menu -%>
30 </div>
30 </div>
31
31
32 <div id="header">
32 <div id="header">
33 <div id="quick-search">
33 <div id="quick-search">
34 <% form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %>
34 <% form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %>
35 <%= hidden_field_tag(controller.default_search_scope, 1, :id => nil) if controller.default_search_scope %>
35 <%= hidden_field_tag(controller.default_search_scope, 1, :id => nil) if controller.default_search_scope %>
36 <%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project}, :accesskey => accesskey(:search) %>:
36 <%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project}, :accesskey => accesskey(:search) %>:
37 <%= text_field_tag 'q', @question, :size => 20, :class => 'small', :accesskey => accesskey(:quick_search) %>
37 <%= text_field_tag 'q', @question, :size => 20, :class => 'small', :accesskey => accesskey(:quick_search) %>
38 <% end %>
38 <% end %>
39 <%= render_project_jump_box %>
39 <%= render_project_jump_box %>
40 </div>
40 </div>
41
41
42 <h1><%= page_header_title %></h1>
42 <h1><%= page_header_title %></h1>
43
43
44 <% if display_main_menu?(@project) %>
44 <div id="main-menu">
45 <div id="main-menu">
45 <%= render_main_menu(@project) %>
46 <%= render_main_menu(@project) %>
46 </div>
47 </div>
48 <% end %>
47 </div>
49 </div>
48
50
49 <%= tag('div', {:id => 'main', :class => (has_content?(:sidebar) ? '' : 'nosidebar')}, true) %>
51 <%= tag('div', {:id => 'main', :class => (has_content?(:sidebar) ? '' : 'nosidebar')}, true) %>
50 <div id="sidebar">
52 <div id="sidebar">
51 <%= yield :sidebar %>
53 <%= yield :sidebar %>
52 <%= call_hook :view_layouts_base_sidebar %>
54 <%= call_hook :view_layouts_base_sidebar %>
53 </div>
55 </div>
54
56
55 <div id="content">
57 <div id="content">
56 <%= render_flash_messages %>
58 <%= render_flash_messages %>
57 <%= yield %>
59 <%= yield %>
58 <%= call_hook :view_layouts_base_content %>
60 <%= call_hook :view_layouts_base_content %>
59 <div style="clear:both;"></div>
61 <div style="clear:both;"></div>
60 </div>
62 </div>
61 </div>
63 </div>
62
64
63 <div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
65 <div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
64
66
65 <div id="footer">
67 <div id="footer">
66 <div class="bgl"><div class="bgr">
68 <div class="bgl"><div class="bgr">
67 Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> &copy; 2006-2010 Jean-Philippe Lang
69 Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> &copy; 2006-2010 Jean-Philippe Lang
68 </div></div>
70 </div></div>
69 </div>
71 </div>
70 </div>
72 </div>
71 </div>
73 </div>
72 <%= call_hook :view_layouts_base_body_bottom %>
74 <%= call_hook :view_layouts_base_body_bottom %>
73 </body>
75 </body>
74 </html>
76 </html>
@@ -1,443 +1,448
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'tree' # gem install rubytree
18 require 'tree' # gem install rubytree
19
19
20 # Monkey patch the TreeNode to add on a few more methods :nodoc:
20 # Monkey patch the TreeNode to add on a few more methods :nodoc:
21 module TreeNodePatch
21 module TreeNodePatch
22 def self.included(base)
22 def self.included(base)
23 base.class_eval do
23 base.class_eval do
24 attr_reader :last_items_count
24 attr_reader :last_items_count
25
25
26 alias :old_initilize :initialize
26 alias :old_initilize :initialize
27 def initialize(name, content = nil)
27 def initialize(name, content = nil)
28 old_initilize(name, content)
28 old_initilize(name, content)
29 @last_items_count = 0
29 @last_items_count = 0
30 extend(InstanceMethods)
30 extend(InstanceMethods)
31 end
31 end
32 end
32 end
33 end
33 end
34
34
35 module InstanceMethods
35 module InstanceMethods
36 # Adds the specified child node to the receiver node. The child node's
36 # Adds the specified child node to the receiver node. The child node's
37 # parent is set to be the receiver. The child is added as the first child in
37 # parent is set to be the receiver. The child is added as the first child in
38 # the current list of children for the receiver node.
38 # the current list of children for the receiver node.
39 def prepend(child)
39 def prepend(child)
40 raise "Child already added" if @childrenHash.has_key?(child.name)
40 raise "Child already added" if @childrenHash.has_key?(child.name)
41
41
42 @childrenHash[child.name] = child
42 @childrenHash[child.name] = child
43 @children = [child] + @children
43 @children = [child] + @children
44 child.parent = self
44 child.parent = self
45 return child
45 return child
46
46
47 end
47 end
48
48
49 # Adds the specified child node to the receiver node. The child node's
49 # Adds the specified child node to the receiver node. The child node's
50 # parent is set to be the receiver. The child is added at the position
50 # parent is set to be the receiver. The child is added at the position
51 # into the current list of children for the receiver node.
51 # into the current list of children for the receiver node.
52 def add_at(child, position)
52 def add_at(child, position)
53 raise "Child already added" if @childrenHash.has_key?(child.name)
53 raise "Child already added" if @childrenHash.has_key?(child.name)
54
54
55 @childrenHash[child.name] = child
55 @childrenHash[child.name] = child
56 @children = @children.insert(position, child)
56 @children = @children.insert(position, child)
57 child.parent = self
57 child.parent = self
58 return child
58 return child
59
59
60 end
60 end
61
61
62 def add_last(child)
62 def add_last(child)
63 raise "Child already added" if @childrenHash.has_key?(child.name)
63 raise "Child already added" if @childrenHash.has_key?(child.name)
64
64
65 @childrenHash[child.name] = child
65 @childrenHash[child.name] = child
66 @children << child
66 @children << child
67 @last_items_count += 1
67 @last_items_count += 1
68 child.parent = self
68 child.parent = self
69 return child
69 return child
70
70
71 end
71 end
72
72
73 # Adds the specified child node to the receiver node. The child node's
73 # Adds the specified child node to the receiver node. The child node's
74 # parent is set to be the receiver. The child is added as the last child in
74 # parent is set to be the receiver. The child is added as the last child in
75 # the current list of children for the receiver node.
75 # the current list of children for the receiver node.
76 def add(child)
76 def add(child)
77 raise "Child already added" if @childrenHash.has_key?(child.name)
77 raise "Child already added" if @childrenHash.has_key?(child.name)
78
78
79 @childrenHash[child.name] = child
79 @childrenHash[child.name] = child
80 position = @children.size - @last_items_count
80 position = @children.size - @last_items_count
81 @children.insert(position, child)
81 @children.insert(position, child)
82 child.parent = self
82 child.parent = self
83 return child
83 return child
84
84
85 end
85 end
86
86
87 # Wrapp remove! making sure to decrement the last_items counter if
87 # Wrapp remove! making sure to decrement the last_items counter if
88 # the removed child was a last item
88 # the removed child was a last item
89 def remove!(child)
89 def remove!(child)
90 @last_items_count -= +1 if child && child.last
90 @last_items_count -= +1 if child && child.last
91 super
91 super
92 end
92 end
93
93
94
94
95 # Will return the position (zero-based) of the current child in
95 # Will return the position (zero-based) of the current child in
96 # it's parent
96 # it's parent
97 def position
97 def position
98 self.parent.children.index(self)
98 self.parent.children.index(self)
99 end
99 end
100 end
100 end
101 end
101 end
102 Tree::TreeNode.send(:include, TreeNodePatch)
102 Tree::TreeNode.send(:include, TreeNodePatch)
103
103
104 module Redmine
104 module Redmine
105 module MenuManager
105 module MenuManager
106 class MenuError < StandardError #:nodoc:
106 class MenuError < StandardError #:nodoc:
107 end
107 end
108
108
109 module MenuController
109 module MenuController
110 def self.included(base)
110 def self.included(base)
111 base.extend(ClassMethods)
111 base.extend(ClassMethods)
112 end
112 end
113
113
114 module ClassMethods
114 module ClassMethods
115 @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}}
115 @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}}
116 mattr_accessor :menu_items
116 mattr_accessor :menu_items
117
117
118 # Set the menu item name for a controller or specific actions
118 # Set the menu item name for a controller or specific actions
119 # Examples:
119 # Examples:
120 # * menu_item :tickets # => sets the menu name to :tickets for the whole controller
120 # * menu_item :tickets # => sets the menu name to :tickets for the whole controller
121 # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only
121 # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only
122 # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only
122 # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only
123 #
123 #
124 # The default menu item name for a controller is controller_name by default
124 # The default menu item name for a controller is controller_name by default
125 # Eg. the default menu item name for ProjectsController is :projects
125 # Eg. the default menu item name for ProjectsController is :projects
126 def menu_item(id, options = {})
126 def menu_item(id, options = {})
127 if actions = options[:only]
127 if actions = options[:only]
128 actions = [] << actions unless actions.is_a?(Array)
128 actions = [] << actions unless actions.is_a?(Array)
129 actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id}
129 actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id}
130 else
130 else
131 menu_items[controller_name.to_sym][:default] = id
131 menu_items[controller_name.to_sym][:default] = id
132 end
132 end
133 end
133 end
134 end
134 end
135
135
136 def menu_items
136 def menu_items
137 self.class.menu_items
137 self.class.menu_items
138 end
138 end
139
139
140 # Returns the menu item name according to the current action
140 # Returns the menu item name according to the current action
141 def current_menu_item
141 def current_menu_item
142 @current_menu_item ||= menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
142 @current_menu_item ||= menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
143 menu_items[controller_name.to_sym][:default]
143 menu_items[controller_name.to_sym][:default]
144 end
144 end
145
145
146 # Redirects user to the menu item of the given project
146 # Redirects user to the menu item of the given project
147 # Returns false if user is not authorized
147 # Returns false if user is not authorized
148 def redirect_to_project_menu_item(project, name)
148 def redirect_to_project_menu_item(project, name)
149 item = Redmine::MenuManager.items(:project_menu).detect {|i| i.name.to_s == name.to_s}
149 item = Redmine::MenuManager.items(:project_menu).detect {|i| i.name.to_s == name.to_s}
150 if item && User.current.allowed_to?(item.url, project) && (item.condition.nil? || item.condition.call(project))
150 if item && User.current.allowed_to?(item.url, project) && (item.condition.nil? || item.condition.call(project))
151 redirect_to({item.param => project}.merge(item.url))
151 redirect_to({item.param => project}.merge(item.url))
152 return true
152 return true
153 end
153 end
154 false
154 false
155 end
155 end
156 end
156 end
157
157
158 module MenuHelper
158 module MenuHelper
159 # Returns the current menu item name
159 # Returns the current menu item name
160 def current_menu_item
160 def current_menu_item
161 @controller.current_menu_item
161 @controller.current_menu_item
162 end
162 end
163
163
164 # Renders the application main menu
164 # Renders the application main menu
165 def render_main_menu(project)
165 def render_main_menu(project)
166 render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project)
166 render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project)
167 end
167 end
168
168
169 def display_main_menu?(project)
170 menu_name = project && !project.new_record? ? :project_menu : :application_menu
171 Redmine::MenuManager.items(menu_name).size > 1 # 1 element is the root
172 end
173
169 def render_menu(menu, project=nil)
174 def render_menu(menu, project=nil)
170 links = []
175 links = []
171 menu_items_for(menu, project) do |node|
176 menu_items_for(menu, project) do |node|
172 links << render_menu_node(node, project)
177 links << render_menu_node(node, project)
173 end
178 end
174 links.empty? ? nil : content_tag('ul', links.join("\n"))
179 links.empty? ? nil : content_tag('ul', links.join("\n"))
175 end
180 end
176
181
177 def render_menu_node(node, project=nil)
182 def render_menu_node(node, project=nil)
178 if node.hasChildren? || !node.child_menus.nil?
183 if node.hasChildren? || !node.child_menus.nil?
179 return render_menu_node_with_children(node, project)
184 return render_menu_node_with_children(node, project)
180 else
185 else
181 caption, url, selected = extract_node_details(node, project)
186 caption, url, selected = extract_node_details(node, project)
182 return content_tag('li',
187 return content_tag('li',
183 render_single_menu_node(node, caption, url, selected))
188 render_single_menu_node(node, caption, url, selected))
184 end
189 end
185 end
190 end
186
191
187 def render_menu_node_with_children(node, project=nil)
192 def render_menu_node_with_children(node, project=nil)
188 caption, url, selected = extract_node_details(node, project)
193 caption, url, selected = extract_node_details(node, project)
189
194
190 html = returning [] do |html|
195 html = returning [] do |html|
191 html << '<li>'
196 html << '<li>'
192 # Parent
197 # Parent
193 html << render_single_menu_node(node, caption, url, selected)
198 html << render_single_menu_node(node, caption, url, selected)
194
199
195 # Standard children
200 # Standard children
196 standard_children_list = returning "" do |child_html|
201 standard_children_list = returning "" do |child_html|
197 node.children.each do |child|
202 node.children.each do |child|
198 child_html << render_menu_node(child, project)
203 child_html << render_menu_node(child, project)
199 end
204 end
200 end
205 end
201
206
202 html << content_tag(:ul, standard_children_list, :class => 'menu-children') unless standard_children_list.empty?
207 html << content_tag(:ul, standard_children_list, :class => 'menu-children') unless standard_children_list.empty?
203
208
204 # Unattached children
209 # Unattached children
205 unattached_children_list = render_unattached_children_menu(node, project)
210 unattached_children_list = render_unattached_children_menu(node, project)
206 html << content_tag(:ul, unattached_children_list, :class => 'menu-children unattached') unless unattached_children_list.blank?
211 html << content_tag(:ul, unattached_children_list, :class => 'menu-children unattached') unless unattached_children_list.blank?
207
212
208 html << '</li>'
213 html << '</li>'
209 end
214 end
210 return html.join("\n")
215 return html.join("\n")
211 end
216 end
212
217
213 # Returns a list of unattached children menu items
218 # Returns a list of unattached children menu items
214 def render_unattached_children_menu(node, project)
219 def render_unattached_children_menu(node, project)
215 return nil unless node.child_menus
220 return nil unless node.child_menus
216
221
217 returning "" do |child_html|
222 returning "" do |child_html|
218 unattached_children = node.child_menus.call(project)
223 unattached_children = node.child_menus.call(project)
219 # Tree nodes support #each so we need to do object detection
224 # Tree nodes support #each so we need to do object detection
220 if unattached_children.is_a? Array
225 if unattached_children.is_a? Array
221 unattached_children.each do |child|
226 unattached_children.each do |child|
222 child_html << content_tag(:li, render_unattached_menu_item(child, project))
227 child_html << content_tag(:li, render_unattached_menu_item(child, project))
223 end
228 end
224 else
229 else
225 raise MenuError, ":child_menus must be an array of MenuItems"
230 raise MenuError, ":child_menus must be an array of MenuItems"
226 end
231 end
227 end
232 end
228 end
233 end
229
234
230 def render_single_menu_node(item, caption, url, selected)
235 def render_single_menu_node(item, caption, url, selected)
231 link_to(h(caption), url, item.html_options(:selected => selected))
236 link_to(h(caption), url, item.html_options(:selected => selected))
232 end
237 end
233
238
234 def render_unattached_menu_item(menu_item, project)
239 def render_unattached_menu_item(menu_item, project)
235 raise MenuError, ":child_menus must be an array of MenuItems" unless menu_item.is_a? MenuItem
240 raise MenuError, ":child_menus must be an array of MenuItems" unless menu_item.is_a? MenuItem
236
241
237 if User.current.allowed_to?(menu_item.url, project)
242 if User.current.allowed_to?(menu_item.url, project)
238 link_to(h(menu_item.caption),
243 link_to(h(menu_item.caption),
239 menu_item.url,
244 menu_item.url,
240 menu_item.html_options)
245 menu_item.html_options)
241 end
246 end
242 end
247 end
243
248
244 def menu_items_for(menu, project=nil)
249 def menu_items_for(menu, project=nil)
245 items = []
250 items = []
246 Redmine::MenuManager.items(menu).root.children.each do |node|
251 Redmine::MenuManager.items(menu).root.children.each do |node|
247 if allowed_node?(node, User.current, project)
252 if allowed_node?(node, User.current, project)
248 if block_given?
253 if block_given?
249 yield node
254 yield node
250 else
255 else
251 items << node # TODO: not used?
256 items << node # TODO: not used?
252 end
257 end
253 end
258 end
254 end
259 end
255 return block_given? ? nil : items
260 return block_given? ? nil : items
256 end
261 end
257
262
258 def extract_node_details(node, project=nil)
263 def extract_node_details(node, project=nil)
259 item = node
264 item = node
260 url = case item.url
265 url = case item.url
261 when Hash
266 when Hash
262 project.nil? ? item.url : {item.param => project}.merge(item.url)
267 project.nil? ? item.url : {item.param => project}.merge(item.url)
263 when Symbol
268 when Symbol
264 send(item.url)
269 send(item.url)
265 else
270 else
266 item.url
271 item.url
267 end
272 end
268 caption = item.caption(project)
273 caption = item.caption(project)
269 return [caption, url, (current_menu_item == item.name)]
274 return [caption, url, (current_menu_item == item.name)]
270 end
275 end
271
276
272 # Checks if a user is allowed to access the menu item by:
277 # Checks if a user is allowed to access the menu item by:
273 #
278 #
274 # * Checking the conditions of the item
279 # * Checking the conditions of the item
275 # * Checking the url target (project only)
280 # * Checking the url target (project only)
276 def allowed_node?(node, user, project)
281 def allowed_node?(node, user, project)
277 if node.condition && !node.condition.call(project)
282 if node.condition && !node.condition.call(project)
278 # Condition that doesn't pass
283 # Condition that doesn't pass
279 return false
284 return false
280 end
285 end
281
286
282 if project
287 if project
283 return user && user.allowed_to?(node.url, project)
288 return user && user.allowed_to?(node.url, project)
284 else
289 else
285 # outside a project, all menu items allowed
290 # outside a project, all menu items allowed
286 return true
291 return true
287 end
292 end
288 end
293 end
289 end
294 end
290
295
291 class << self
296 class << self
292 def map(menu_name)
297 def map(menu_name)
293 @items ||= {}
298 @items ||= {}
294 mapper = Mapper.new(menu_name.to_sym, @items)
299 mapper = Mapper.new(menu_name.to_sym, @items)
295 if block_given?
300 if block_given?
296 yield mapper
301 yield mapper
297 else
302 else
298 mapper
303 mapper
299 end
304 end
300 end
305 end
301
306
302 def items(menu_name)
307 def items(menu_name)
303 @items[menu_name.to_sym] || Tree::TreeNode.new(:root, {})
308 @items[menu_name.to_sym] || Tree::TreeNode.new(:root, {})
304 end
309 end
305 end
310 end
306
311
307 class Mapper
312 class Mapper
308 def initialize(menu, items)
313 def initialize(menu, items)
309 items[menu] ||= Tree::TreeNode.new(:root, {})
314 items[menu] ||= Tree::TreeNode.new(:root, {})
310 @menu = menu
315 @menu = menu
311 @menu_items = items[menu]
316 @menu_items = items[menu]
312 end
317 end
313
318
314 @@last_items_count = Hash.new {|h,k| h[k] = 0}
319 @@last_items_count = Hash.new {|h,k| h[k] = 0}
315
320
316 # Adds an item at the end of the menu. Available options:
321 # Adds an item at the end of the menu. Available options:
317 # * param: the parameter name that is used for the project id (default is :id)
322 # * param: the parameter name that is used for the project id (default is :id)
318 # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true
323 # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true
319 # * caption that can be:
324 # * caption that can be:
320 # * a localized string Symbol
325 # * a localized string Symbol
321 # * a String
326 # * a String
322 # * a Proc that can take the project as argument
327 # * a Proc that can take the project as argument
323 # * before, after: specify where the menu item should be inserted (eg. :after => :activity)
328 # * before, after: specify where the menu item should be inserted (eg. :after => :activity)
324 # * parent: menu item will be added as a child of another named menu (eg. :parent => :issues)
329 # * parent: menu item will be added as a child of another named menu (eg. :parent => :issues)
325 # * children: a Proc that is called before rendering the item. The Proc should return an array of MenuItems, which will be added as children to this item.
330 # * children: a Proc that is called before rendering the item. The Proc should return an array of MenuItems, which will be added as children to this item.
326 # eg. :children => Proc.new {|project| [Redmine::MenuManager::MenuItem.new(...)] }
331 # eg. :children => Proc.new {|project| [Redmine::MenuManager::MenuItem.new(...)] }
327 # * last: menu item will stay at the end (eg. :last => true)
332 # * last: menu item will stay at the end (eg. :last => true)
328 # * html_options: a hash of html options that are passed to link_to
333 # * html_options: a hash of html options that are passed to link_to
329 def push(name, url, options={})
334 def push(name, url, options={})
330 options = options.dup
335 options = options.dup
331
336
332 if options[:parent]
337 if options[:parent]
333 subtree = self.find(options[:parent])
338 subtree = self.find(options[:parent])
334 if subtree
339 if subtree
335 target_root = subtree
340 target_root = subtree
336 else
341 else
337 target_root = @menu_items.root
342 target_root = @menu_items.root
338 end
343 end
339
344
340 else
345 else
341 target_root = @menu_items.root
346 target_root = @menu_items.root
342 end
347 end
343
348
344 # menu item position
349 # menu item position
345 if first = options.delete(:first)
350 if first = options.delete(:first)
346 target_root.prepend(MenuItem.new(name, url, options))
351 target_root.prepend(MenuItem.new(name, url, options))
347 elsif before = options.delete(:before)
352 elsif before = options.delete(:before)
348
353
349 if exists?(before)
354 if exists?(before)
350 target_root.add_at(MenuItem.new(name, url, options), position_of(before))
355 target_root.add_at(MenuItem.new(name, url, options), position_of(before))
351 else
356 else
352 target_root.add(MenuItem.new(name, url, options))
357 target_root.add(MenuItem.new(name, url, options))
353 end
358 end
354
359
355 elsif after = options.delete(:after)
360 elsif after = options.delete(:after)
356
361
357 if exists?(after)
362 if exists?(after)
358 target_root.add_at(MenuItem.new(name, url, options), position_of(after) + 1)
363 target_root.add_at(MenuItem.new(name, url, options), position_of(after) + 1)
359 else
364 else
360 target_root.add(MenuItem.new(name, url, options))
365 target_root.add(MenuItem.new(name, url, options))
361 end
366 end
362
367
363 elsif options[:last] # don't delete, needs to be stored
368 elsif options[:last] # don't delete, needs to be stored
364 target_root.add_last(MenuItem.new(name, url, options))
369 target_root.add_last(MenuItem.new(name, url, options))
365 else
370 else
366 target_root.add(MenuItem.new(name, url, options))
371 target_root.add(MenuItem.new(name, url, options))
367 end
372 end
368 end
373 end
369
374
370 # Removes a menu item
375 # Removes a menu item
371 def delete(name)
376 def delete(name)
372 if found = self.find(name)
377 if found = self.find(name)
373 @menu_items.remove!(found)
378 @menu_items.remove!(found)
374 end
379 end
375 end
380 end
376
381
377 # Checks if a menu item exists
382 # Checks if a menu item exists
378 def exists?(name)
383 def exists?(name)
379 @menu_items.any? {|node| node.name == name}
384 @menu_items.any? {|node| node.name == name}
380 end
385 end
381
386
382 def find(name)
387 def find(name)
383 @menu_items.find {|node| node.name == name}
388 @menu_items.find {|node| node.name == name}
384 end
389 end
385
390
386 def position_of(name)
391 def position_of(name)
387 @menu_items.each do |node|
392 @menu_items.each do |node|
388 if node.name == name
393 if node.name == name
389 return node.position
394 return node.position
390 end
395 end
391 end
396 end
392 end
397 end
393 end
398 end
394
399
395 class MenuItem < Tree::TreeNode
400 class MenuItem < Tree::TreeNode
396 include Redmine::I18n
401 include Redmine::I18n
397 attr_reader :name, :url, :param, :condition, :parent, :child_menus, :last
402 attr_reader :name, :url, :param, :condition, :parent, :child_menus, :last
398
403
399 def initialize(name, url, options)
404 def initialize(name, url, options)
400 raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
405 raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
401 raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
406 raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
402 raise ArgumentError, "Cannot set the :parent to be the same as this item" if options[:parent] == name.to_sym
407 raise ArgumentError, "Cannot set the :parent to be the same as this item" if options[:parent] == name.to_sym
403 raise ArgumentError, "Invalid option :children for menu item '#{name}'" if options[:children] && !options[:children].respond_to?(:call)
408 raise ArgumentError, "Invalid option :children for menu item '#{name}'" if options[:children] && !options[:children].respond_to?(:call)
404 @name = name
409 @name = name
405 @url = url
410 @url = url
406 @condition = options[:if]
411 @condition = options[:if]
407 @param = options[:param] || :id
412 @param = options[:param] || :id
408 @caption = options[:caption]
413 @caption = options[:caption]
409 @html_options = options[:html] || {}
414 @html_options = options[:html] || {}
410 # Adds a unique class to each menu item based on its name
415 # Adds a unique class to each menu item based on its name
411 @html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
416 @html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
412 @parent = options[:parent]
417 @parent = options[:parent]
413 @child_menus = options[:children]
418 @child_menus = options[:children]
414 @last = options[:last] || false
419 @last = options[:last] || false
415 super @name.to_sym
420 super @name.to_sym
416 end
421 end
417
422
418 def caption(project=nil)
423 def caption(project=nil)
419 if @caption.is_a?(Proc)
424 if @caption.is_a?(Proc)
420 c = @caption.call(project).to_s
425 c = @caption.call(project).to_s
421 c = @name.to_s.humanize if c.blank?
426 c = @name.to_s.humanize if c.blank?
422 c
427 c
423 else
428 else
424 if @caption.nil?
429 if @caption.nil?
425 l_or_humanize(name, :prefix => 'label_')
430 l_or_humanize(name, :prefix => 'label_')
426 else
431 else
427 @caption.is_a?(Symbol) ? l(@caption) : @caption
432 @caption.is_a?(Symbol) ? l(@caption) : @caption
428 end
433 end
429 end
434 end
430 end
435 end
431
436
432 def html_options(options={})
437 def html_options(options={})
433 if options[:selected]
438 if options[:selected]
434 o = @html_options.dup
439 o = @html_options.dup
435 o[:class] += ' selected'
440 o[:class] += ' selected'
436 o
441 o
437 else
442 else
438 @html_options
443 @html_options
439 end
444 end
440 end
445 end
441 end
446 end
442 end
447 end
443 end
448 end
General Comments 0
You need to be logged in to leave comments. Login now