@@ -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 |
|
|
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 }) |
General Comments 0
You need to be logged in to leave comments.
Login now