Patch #4250 » 0002-Add-support-for-unattached-menus-generated-dynamical.patch
lib/redmine/menu_manager.rb | ||
---|---|---|
95 | 95 | |
96 | 96 |
module Redmine |
97 | 97 |
module MenuManager |
98 |
class MenuError < StandardError #:nodoc: |
|
99 |
end |
|
100 |
|
|
98 | 101 |
module MenuController |
99 | 102 |
def self.included(base) |
100 | 103 |
base.extend(ClassMethods) |
... | ... | |
164 | 167 |
end |
165 | 168 | |
166 | 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 | 180 |
caption, url, selected = extract_node_details(node, project) |
168 |
if node.hasChildren? |
|
169 |
html = []
|
|
181 | ||
182 |
html = returning [] do |html|
|
|
170 | 183 |
html << '<li>' |
171 |
html << render_single_menu_node(node, caption, url, selected) # parent |
|
172 |
html << ' <ul>' |
|
173 |
node.children.each do |child| |
|
174 |
html << render_menu_node(child, project) |
|
184 |
# Parent |
|
185 |
html << render_single_menu_node(node, caption, url, selected) |
|
186 | ||
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 | 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 | 200 |
html << '</li>' |
178 |
return html.join("\n") |
|
179 |
else |
|
180 |
return content_tag('li', |
|
181 |
render_single_menu_node(node, caption, url, selected)) |
|
201 |
end |
|
202 |
return html.join("\n") |
|
203 |
end |
|
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 | 219 |
end |
183 | 220 |
end |
184 | 221 | |
185 | 222 |
def render_single_menu_node(item, caption, url, selected) |
186 | 223 |
link_to(h(caption), url, item.html_options(:selected => selected)) |
187 | 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 | 236 |
def menu_items_for(menu, project=nil) |
190 | 237 |
items = [] |
... | ... | |
336 | 383 |
|
337 | 384 |
class MenuItem < Tree::TreeNode |
338 | 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 | 388 |
def initialize(name, url, options) |
342 | 389 |
raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call) |
343 | 390 |
raise ArgumentError, "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash) |
344 | 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 | 393 |
@name = name |
346 | 394 |
@url = url |
347 | 395 |
@condition = options[:if] |
... | ... | |
351 | 399 |
# Adds a unique class to each menu item based on its name |
352 | 400 |
@html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ') |
353 | 401 |
@parent_menu = options[:parent_menu] |
402 |
@child_menus = options[:child_menus] |
|
354 | 403 |
super @name.to_sym |
355 | 404 |
end |
356 | 405 |
|
test/unit/lib/redmine/menu_manager/menu_helper_test.rb | ||
---|---|---|
101 | 101 |
|
102 | 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 | 205 |
def test_menu_items_for_should_yield_all_items_if_passed_a_block |
105 | 206 |
menu_name = :test_menu_items_for_should_yield_all_items_if_passed_a_block |
106 | 207 |
Redmine::MenuManager.map menu_name do |menu| |
test/unit/lib/redmine/menu_manager/menu_item_test.rb | ||
---|---|---|
92 | 92 |
}) |
93 | 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 | 109 |
def test_new_should_not_allow_setting_the_parent_menu_item_to_the_current_item |
96 | 110 |
assert_raises ArgumentError do |
97 | 111 |
Redmine::MenuManager::MenuItem.new(:test_error, '/test', { :parent_menu => :test_error }) |
test/unit/lib/redmine/menu_manager_test.rb | ||
---|---|---|
25 | 25 |
context "MenuManager#items" do |
26 | 26 |
should "be tested" |
27 | 27 |
end |
28 | ||
29 |
should "be tested" do |
|
30 |
assert true |
|
31 |
end |
|
28 | 32 |
end |