##// END OF EJS Templates
Add support for unattached menus (generated dynamically)...
Eric Davis -
r2977:b0999e3764f0
parent child
Show More
@@ -95,6 +95,9 Tree::TreeNode.send(:include, TreeNodePatch)
95
95
96 module Redmine
96 module Redmine
97 module MenuManager
97 module MenuManager
98 class MenuError < StandardError #:nodoc:
99 end
100
98 module MenuController
101 module MenuController
99 def self.included(base)
102 def self.included(base)
100 base.extend(ClassMethods)
103 base.extend(ClassMethods)
@@ -164,27 +167,71 module Redmine
164 end
167 end
165
168
166 def render_menu_node(node, project=nil)
169 def render_menu_node(node, project=nil)
170 if node.hasChildren? || !node.child_menus.nil?
171 return render_menu_node_with_children(node, project)
172 else
173 caption, url, selected = extract_node_details(node, project)
174 return content_tag('li',
175 render_single_menu_node(node, caption, url, selected))
176 end
177 end
178
179 def render_menu_node_with_children(node, project=nil)
167 caption, url, selected = extract_node_details(node, project)
180 caption, url, selected = extract_node_details(node, project)
168 if node.hasChildren?
181
169 html = []
182 html = returning [] do |html|
170 html << '<li>'
183 html << '<li>'
171 html << render_single_menu_node(node, caption, url, selected) # parent
184 # Parent
172 html << ' <ul>'
185 html << render_single_menu_node(node, caption, url, selected)
173 node.children.each do |child|
186
174 html << render_menu_node(child, project)
187 # Standard children
188 standard_children_list = returning "" do |child_html|
189 node.children.each do |child|
190 child_html << render_menu_node(child, project)
191 end
175 end
192 end
176 html << ' </ul>'
193
194 html << content_tag(:ul, standard_children_list, :class => 'menu-children') unless standard_children_list.empty?
195
196 # Unattached children
197 unattached_children_list = render_unattached_children_menu(node, project)
198 html << content_tag(:ul, unattached_children_list, :class => 'menu-children unattached') unless unattached_children_list.blank?
199
177 html << '</li>'
200 html << '</li>'
178 return html.join("\n")
201 end
179 else
202 return html.join("\n")
180 return content_tag('li',
203 end
181 render_single_menu_node(node, caption, url, selected))
204
205 # Returns a list of unattached children menu items
206 def render_unattached_children_menu(node, project)
207 return nil unless node.child_menus
208
209 returning "" do |child_html|
210 unattached_children = node.child_menus.call(project)
211 # Tree nodes support #each so we need to do object detection
212 if unattached_children.is_a? Array
213 unattached_children.each do |child|
214 child_html << content_tag(:li, render_unattached_menu_item(child, project))
215 end
216 else
217 raise MenuError, ":child_menus must be an array of MenuItems"
218 end
182 end
219 end
183 end
220 end
184
221
185 def render_single_menu_node(item, caption, url, selected)
222 def render_single_menu_node(item, caption, url, selected)
186 link_to(h(caption), url, item.html_options(:selected => selected))
223 link_to(h(caption), url, item.html_options(:selected => selected))
187 end
224 end
225
226 def render_unattached_menu_item(menu_item, project)
227 raise MenuError, ":child_menus must be an array of MenuItems" unless menu_item.is_a? MenuItem
228
229 if User.current.allowed_to?(menu_item.url, project)
230 link_to(h(menu_item.caption),
231 menu_item.url,
232 menu_item.html_options)
233 end
234 end
188
235
189 def menu_items_for(menu, project=nil)
236 def menu_items_for(menu, project=nil)
190 items = []
237 items = []
@@ -336,12 +383,13 module Redmine
336
383
337 class MenuItem < Tree::TreeNode
384 class MenuItem < Tree::TreeNode
338 include Redmine::I18n
385 include Redmine::I18n
339 attr_reader :name, :url, :param, :condition, :parent_menu
386 attr_reader :name, :url, :param, :condition, :parent_menu, :child_menus
340
387
341 def initialize(name, url, options)
388 def initialize(name, url, options)
342 raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
389 raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
343 raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
390 raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
344 raise ArgumentError, "Cannot set the :parent_menu to be the same as this item" if options[:parent_menu] == name.to_sym
391 raise ArgumentError, "Cannot set the :parent_menu to be the same as this item" if options[:parent_menu] == name.to_sym
392 raise ArgumentError, "Invalid option :child_menus for menu item '#{name}'" if options[:child_menus] && !options[:child_menus].respond_to?(:call)
345 @name = name
393 @name = name
346 @url = url
394 @url = url
347 @condition = options[:if]
395 @condition = options[:if]
@@ -351,6 +399,7 module Redmine
351 # Adds a unique class to each menu item based on its name
399 # Adds a unique class to each menu item based on its name
352 @html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
400 @html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
353 @parent_menu = options[:parent_menu]
401 @parent_menu = options[:parent_menu]
402 @child_menus = options[:child_menus]
354 super @name.to_sym
403 super @name.to_sym
355 end
404 end
356
405
@@ -101,6 +101,107 class Redmine::MenuManager::MenuHelperTest < HelperTestCase
101
101
102 end
102 end
103
103
104 def test_render_menu_node_with_child_menus
105 User.current = User.find(2)
106
107 parent_node = Redmine::MenuManager::MenuItem.new(:parent_node,
108 '/test',
109 {
110 :child_menus => Proc.new {|p|
111 child_menus = []
112 3.times do |time|
113 child_menus << Redmine::MenuManager::MenuItem.new("test_child_#{time}",
114 {:controller => 'issues', :action => 'index'},
115 {})
116 end
117 child_menus
118 }
119 })
120 @response.body = render_menu_node(parent_node, Project.find(1))
121
122 assert_select("li") do
123 assert_select("a.parent-node", "Parent node")
124 assert_select("ul") do
125 assert_select("li a.test-child-0", "Test child 0")
126 assert_select("li a.test-child-1", "Test child 1")
127 assert_select("li a.test-child-2", "Test child 2")
128 end
129 end
130 end
131
132 def test_render_menu_node_with_nested_items_and_child_menus
133 User.current = User.find(2)
134
135 parent_node = Redmine::MenuManager::MenuItem.new(:parent_node,
136 '/test',
137 {
138 :child_menus => Proc.new {|p|
139 child_menus = []
140 3.times do |time|
141 child_menus << Redmine::MenuManager::MenuItem.new("test_child_#{time}", {:controller => 'issues', :action => 'index'}, {})
142 end
143 child_menus
144 }
145 })
146
147 parent_node << Redmine::MenuManager::MenuItem.new(:child_node,
148 '/test',
149 {
150 :child_menus => Proc.new {|p|
151 child_menus = []
152 6.times do |time|
153 child_menus << Redmine::MenuManager::MenuItem.new("test_dynamic_child_#{time}", {:controller => 'issues', :action => 'index'}, {})
154 end
155 child_menus
156 }
157 })
158
159 @response.body = render_menu_node(parent_node, Project.find(1))
160
161 assert_select("li") do
162 assert_select("a.parent-node", "Parent node")
163 assert_select("ul") do
164 assert_select("li a.child-node", "Child node")
165 assert_select("ul") do
166 assert_select("li a.test-dynamic-child-0", "Test dynamic child 0")
167 assert_select("li a.test-dynamic-child-1", "Test dynamic child 1")
168 assert_select("li a.test-dynamic-child-2", "Test dynamic child 2")
169 assert_select("li a.test-dynamic-child-3", "Test dynamic child 3")
170 assert_select("li a.test-dynamic-child-4", "Test dynamic child 4")
171 assert_select("li a.test-dynamic-child-5", "Test dynamic child 5")
172 end
173 assert_select("li a.test-child-0", "Test child 0")
174 assert_select("li a.test-child-1", "Test child 1")
175 assert_select("li a.test-child-2", "Test child 2")
176 end
177 end
178 end
179
180 def test_render_menu_node_with_child_menus_without_an_array
181 parent_node = Redmine::MenuManager::MenuItem.new(:parent_node,
182 '/test',
183 {
184 :child_menus => Proc.new {|p| Redmine::MenuManager::MenuItem.new("test_child", "/testing", {})}
185 })
186
187 assert_raises Redmine::MenuManager::MenuError, ":child_menus must be an array of MenuItems" do
188 @response.body = render_menu_node(parent_node, Project.find(1))
189 end
190 end
191
192 def test_render_menu_node_with_incorrect_child_menus
193 parent_node = Redmine::MenuManager::MenuItem.new(:parent_node,
194 '/test',
195 {
196 :child_menus => Proc.new {|p| ["a string"] }
197 })
198
199 assert_raises Redmine::MenuManager::MenuError, ":child_menus must be an array of MenuItems" do
200 @response.body = render_menu_node(parent_node, Project.find(1))
201 end
202
203 end
204
104 def test_menu_items_for_should_yield_all_items_if_passed_a_block
205 def test_menu_items_for_should_yield_all_items_if_passed_a_block
105 menu_name = :test_menu_items_for_should_yield_all_items_if_passed_a_block
206 menu_name = :test_menu_items_for_should_yield_all_items_if_passed_a_block
106 Redmine::MenuManager.map menu_name do |menu|
207 Redmine::MenuManager.map menu_name do |menu|
@@ -92,6 +92,20 class Redmine::MenuManager::MenuItemTest < Test::Unit::TestCase
92 })
92 })
93 end
93 end
94
94
95 def test_new_menu_item_should_require_a_proc_to_use_the_child_menus_option
96 assert_raises ArgumentError do
97 Redmine::MenuManager::MenuItem.new(:test_error, '/test',
98 {
99 :child_menus => ['not_a_proc']
100 })
101 end
102
103 assert Redmine::MenuManager::MenuItem.new(:test_good_child_menus, '/test',
104 {
105 :child_menus => Proc.new{}
106 })
107 end
108
95 def test_new_should_not_allow_setting_the_parent_menu_item_to_the_current_item
109 def test_new_should_not_allow_setting_the_parent_menu_item_to_the_current_item
96 assert_raises ArgumentError do
110 assert_raises ArgumentError do
97 Redmine::MenuManager::MenuItem.new(:test_error, '/test', { :parent_menu => :test_error })
111 Redmine::MenuManager::MenuItem.new(:test_error, '/test', { :parent_menu => :test_error })
@@ -25,4 +25,8 class Redmine::MenuManagerTest < Test::Unit::TestCase
25 context "MenuManager#items" do
25 context "MenuManager#items" do
26 should "be tested"
26 should "be tested"
27 end
27 end
28
29 should "be tested" do
30 assert true
31 end
28 end
32 end
General Comments 0
You need to be logged in to leave comments. Login now