comparison lib/redmine/menu_manager.rb @ 1115:433d4f72a19b redmine-2.2

Update to Redmine SVN revision 11137 on 2.2-stable branch
author Chris Cannam
date Mon, 07 Jan 2013 12:01:42 +0000
parents cbb26bc654de
children 622f24f53b42 261b3d9a4903
comparison
equal deleted inserted replaced
929:5f33065ddc4b 1115:433d4f72a19b
1 # Redmine - project management software 1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang 2 # Copyright (C) 2006-2012 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.
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
19
20 # Monkey patch the TreeNode to add on a few more methods :nodoc:
21 module TreeNodePatch
22 def self.included(base)
23 base.class_eval do
24 attr_reader :last_items_count
25
26 alias :old_initilize :initialize
27 def initialize(name, content = nil)
28 old_initilize(name, content)
29 @childrenHash ||= {}
30 @last_items_count = 0
31 extend(InstanceMethods)
32 end
33 end
34 end
35
36 module InstanceMethods
37 # Adds the specified child node to the receiver node. The child node's
38 # parent is set to be the receiver. The child is added as the first child in
39 # the current list of children for the receiver node.
40 def prepend(child)
41 raise "Child already added" if @childrenHash.has_key?(child.name)
42
43 @childrenHash[child.name] = child
44 @children = [child] + @children
45 child.parent = self
46 return child
47
48 end
49
50 # Adds the specified child node to the receiver node. The child node's
51 # parent is set to be the receiver. The child is added at the position
52 # into the current list of children for the receiver node.
53 def add_at(child, position)
54 raise "Child already added" if @childrenHash.has_key?(child.name)
55
56 @childrenHash[child.name] = child
57 @children = @children.insert(position, child)
58 child.parent = self
59 return child
60
61 end
62
63 def add_last(child)
64 raise "Child already added" if @childrenHash.has_key?(child.name)
65
66 @childrenHash[child.name] = child
67 @children << child
68 @last_items_count += 1
69 child.parent = self
70 return child
71
72 end
73
74 # Adds the specified child node to the receiver node. The child node's
75 # parent is set to be the receiver. The child is added as the last child in
76 # the current list of children for the receiver node.
77 def add(child)
78 raise "Child already added" if @childrenHash.has_key?(child.name)
79
80 @childrenHash[child.name] = child
81 position = @children.size - @last_items_count
82 @children.insert(position, child)
83 child.parent = self
84 return child
85
86 end
87
88 # Wrapp remove! making sure to decrement the last_items counter if
89 # the removed child was a last item
90 def remove!(child)
91 @last_items_count -= +1 if child && child.last
92 super
93 end
94
95
96 # Will return the position (zero-based) of the current child in
97 # it's parent
98 def position
99 self.parent.children.index(self)
100 end
101 end
102 end
103 Tree::TreeNode.send(:include, TreeNodePatch)
104
105 module Redmine 18 module Redmine
106 module MenuManager 19 module MenuManager
107 class MenuError < StandardError #:nodoc: 20 class MenuError < StandardError #:nodoc:
108 end 21 end
109 22
167 render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project) 80 render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project)
168 end 81 end
169 82
170 def display_main_menu?(project) 83 def display_main_menu?(project)
171 menu_name = project && !project.new_record? ? :project_menu : :application_menu 84 menu_name = project && !project.new_record? ? :project_menu : :application_menu
172 Redmine::MenuManager.items(menu_name).size > 1 # 1 element is the root 85 Redmine::MenuManager.items(menu_name).children.present?
173 end 86 end
174 87
175 def render_menu(menu, project=nil) 88 def render_menu(menu, project=nil)
176 links = [] 89 links = []
177 menu_items_for(menu, project) do |node| 90 menu_items_for(menu, project) do |node|
179 end 92 end
180 links.empty? ? nil : content_tag('ul', links.join("\n").html_safe) 93 links.empty? ? nil : content_tag('ul', links.join("\n").html_safe)
181 end 94 end
182 95
183 def render_menu_node(node, project=nil) 96 def render_menu_node(node, project=nil)
184 if node.hasChildren? || !node.child_menus.nil? 97 if node.children.present? || !node.child_menus.nil?
185 return render_menu_node_with_children(node, project) 98 return render_menu_node_with_children(node, project)
186 else 99 else
187 caption, url, selected = extract_node_details(node, project) 100 caption, url, selected = extract_node_details(node, project)
188 return content_tag('li', 101 return content_tag('li',
189 render_single_menu_node(node, caption, url, selected)) 102 render_single_menu_node(node, caption, url, selected))
197 html << '<li>' 110 html << '<li>'
198 # Parent 111 # Parent
199 html << render_single_menu_node(node, caption, url, selected) 112 html << render_single_menu_node(node, caption, url, selected)
200 113
201 # Standard children 114 # Standard children
202 standard_children_list = "".tap do |child_html| 115 standard_children_list = "".html_safe.tap do |child_html|
203 node.children.each do |child| 116 node.children.each do |child|
204 child_html << render_menu_node(child, project) 117 child_html << render_menu_node(child, project)
205 end 118 end
206 end 119 end
207 120
211 unattached_children_list = render_unattached_children_menu(node, project) 124 unattached_children_list = render_unattached_children_menu(node, project)
212 html << content_tag(:ul, unattached_children_list, :class => 'menu-children unattached') unless unattached_children_list.blank? 125 html << content_tag(:ul, unattached_children_list, :class => 'menu-children unattached') unless unattached_children_list.blank?
213 126
214 html << '</li>' 127 html << '</li>'
215 end 128 end
216 return html.join("\n") 129 return html.join("\n").html_safe
217 end 130 end
218 131
219 # Returns a list of unattached children menu items 132 # Returns a list of unattached children menu items
220 def render_unattached_children_menu(node, project) 133 def render_unattached_children_menu(node, project)
221 return nil unless node.child_menus 134 return nil unless node.child_menus
222 135
223 "".tap do |child_html| 136 "".html_safe.tap do |child_html|
224 unattached_children = node.child_menus.call(project) 137 unattached_children = node.child_menus.call(project)
225 # Tree nodes support #each so we need to do object detection 138 # Tree nodes support #each so we need to do object detection
226 if unattached_children.is_a? Array 139 if unattached_children.is_a? Array
227 unattached_children.each do |child| 140 unattached_children.each do |child|
228 child_html << content_tag(:li, render_unattached_menu_item(child, project)) 141 child_html << content_tag(:li, render_unattached_menu_item(child, project))
304 mapper 217 mapper
305 end 218 end
306 end 219 end
307 220
308 def items(menu_name) 221 def items(menu_name)
309 @items[menu_name.to_sym] || Tree::TreeNode.new(:root, {}) 222 @items[menu_name.to_sym] || MenuNode.new(:root, {})
310 end 223 end
311 end 224 end
312 225
313 class Mapper 226 class Mapper
314 def initialize(menu, items) 227 def initialize(menu, items)
315 items[menu] ||= Tree::TreeNode.new(:root, {}) 228 items[menu] ||= MenuNode.new(:root, {})
316 @menu = menu 229 @menu = menu
317 @menu_items = items[menu] 230 @menu_items = items[menu]
318 end 231 end
319
320 @@last_items_count = Hash.new {|h,k| h[k] = 0}
321 232
322 # Adds an item at the end of the menu. Available options: 233 # Adds an item at the end of the menu. Available options:
323 # * param: the parameter name that is used for the project id (default is :id) 234 # * param: the parameter name that is used for the project id (default is :id)
324 # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true 235 # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true
325 # * caption that can be: 236 # * caption that can be:
396 end 307 end
397 end 308 end
398 end 309 end
399 end 310 end
400 311
401 class MenuItem < Tree::TreeNode 312 class MenuNode
313 include Enumerable
314 attr_accessor :parent
315 attr_reader :last_items_count, :name
316
317 def initialize(name, content = nil)
318 @name = name
319 @children = []
320 @last_items_count = 0
321 end
322
323 def children
324 if block_given?
325 @children.each {|child| yield child}
326 else
327 @children
328 end
329 end
330
331 # Returns the number of descendants + 1
332 def size
333 @children.inject(1) {|sum, node| sum + node.size}
334 end
335
336 def each &block
337 yield self
338 children { |child| child.each(&block) }
339 end
340
341 # Adds a child at first position
342 def prepend(child)
343 add_at(child, 0)
344 end
345
346 # Adds a child at given position
347 def add_at(child, position)
348 raise "Child already added" if find {|node| node.name == child.name}
349
350 @children = @children.insert(position, child)
351 child.parent = self
352 child
353 end
354
355 # Adds a child as last child
356 def add_last(child)
357 add_at(child, -1)
358 @last_items_count += 1
359 child
360 end
361
362 # Adds a child
363 def add(child)
364 position = @children.size - @last_items_count
365 add_at(child, position)
366 end
367 alias :<< :add
368
369 # Removes a child
370 def remove!(child)
371 @children.delete(child)
372 @last_items_count -= +1 if child && child.last
373 child.parent = nil
374 child
375 end
376
377 # Returns the position for this node in it's parent
378 def position
379 self.parent.children.index(self)
380 end
381
382 # Returns the root for this node
383 def root
384 root = self
385 root = root.parent while root.parent
386 root
387 end
388 end
389
390 class MenuItem < MenuNode
402 include Redmine::I18n 391 include Redmine::I18n
403 attr_reader :name, :url, :param, :condition, :parent, :child_menus, :last 392 attr_reader :name, :url, :param, :condition, :parent, :child_menus, :last
404 393
405 def initialize(name, url, options) 394 def initialize(name, url, options)
406 raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call) 395 raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)