Feature #1189 » custom_field_multiple_values_2.diff
app/controllers/issues_controller.rb | ||
---|---|---|
118 | 118 |
@edit_allowed = User.current.allowed_to?(:edit_issues, @project) |
119 | 119 |
@priorities = IssuePriority.all |
120 | 120 |
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) |
121 |
@custom_values = @issue.custom_field_values |
|
121 | 122 |
respond_to do |format| |
122 | 123 |
format.html { render :template => 'issues/show.rhtml' } |
123 | 124 |
format.api |
... | ... | |
284 | 285 |
@edit_allowed = User.current.allowed_to?(:edit_issues, @project) |
285 | 286 |
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) |
286 | 287 |
@time_entry.attributes = params[:time_entry] |
288 |
@custom_values = @issue.custom_field_values |
|
287 | 289 | |
288 | 290 |
@notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil) |
289 | 291 |
@issue.init_journal(User.current, @notes) |
app/helpers/custom_fields_helper.rb | ||
---|---|---|
61 | 61 |
blank_option = custom_field.is_required? ? |
62 | 62 |
(custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') : |
63 | 63 |
'<option></option>' |
64 |
select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), :id => field_id) |
|
64 |
#select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), :id => field_id) |
|
65 |
multi_image = custom_field.allow_multi ? |
|
66 |
link_to_function(image_tag('bullet_toggle_plus.png'), "toggle_multi_custom('#{custom_field.id}');", :style => "vertical-align: bottom;") : |
|
67 |
'' |
|
68 |
multiple = custom_field.allow_multi && custom_value.value.is_a?(Array) && custom_value.value.length > 1 |
|
69 |
select_name = custom_field.allow_multi ? "#{name}[custom_multi_values][#{custom_field.id}][]" : field_name |
|
70 |
select_tag(select_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id, :multiple => multiple) + multi_image |
|
65 | 71 |
else |
66 | 72 |
text_field_tag(field_name, custom_value.value, :id => field_id) |
67 | 73 |
end |
... | ... | |
104 | 110 |
# Return a string used to display a custom value |
105 | 111 |
def show_value(custom_value) |
106 | 112 |
return "" unless custom_value |
107 |
format_value(custom_value.value, custom_value.custom_field.field_format)
|
|
113 |
format_value((custom_value.value.is_a?(Array) ? custom_value.value.join("\n") : custom_value.value), custom_value.custom_field.field_format)
|
|
108 | 114 |
end |
109 | 115 |
|
110 | 116 |
# Return a string used to display a custom value |
app/models/custom_field.rb | ||
---|---|---|
33 | 33 |
def before_validation |
34 | 34 |
# make sure these fields are not searchable |
35 | 35 |
self.searchable = false if %w(int float date bool).include?(field_format) |
36 |
# make sure only list field_format have allow_multi option |
|
37 |
self.allow_multi = false unless field_format == 'list' |
|
36 | 38 |
true |
37 | 39 |
end |
38 | 40 |
|
app/models/custom_value.rb | ||
---|---|---|
69 | 69 |
end |
70 | 70 |
end |
71 | 71 |
end |
72 | ||
73 |
class CustomValuesCollection < Array |
|
74 | ||
75 |
attr_accessor :custom_field |
|
76 | ||
77 |
def initialize(custom_field, custom_values=[]) |
|
78 |
@custom_field = custom_field if custom_field.is_a?(CustomField) |
|
79 |
custom_values.map{ |x| self << x } |
|
80 |
self |
|
81 |
end |
|
82 | ||
83 |
def value |
|
84 |
self.uniq.map(&:value).delete_if {|x| x.blank?} |
|
85 |
end |
|
86 | ||
87 |
def value=(new_value) |
|
88 |
self.delete_if{ |x| true } |
|
89 |
new_value.map{ |x| self << x } |
|
90 |
end |
|
91 | ||
92 |
def save |
|
93 |
self.compact.each(&:save) |
|
94 |
end |
|
95 | ||
96 |
def valid? |
|
97 |
self.inject(true){ |bool,v| bool && v.valid? } |
|
98 |
end |
|
99 | ||
100 |
def validate |
|
101 |
self.uniq.map(&:validate) |
|
102 |
end |
|
103 | ||
104 |
def custom_field_id |
|
105 |
@custom_field.id |
|
106 |
end |
|
107 | ||
108 |
def method_missing(symbol, *args) |
|
109 |
if @custom_field.respond_to?(symbol) |
|
110 |
@custom_field.send(symbol, *args) |
|
111 |
elsif self.first && self.first.respond_to?(symbol) |
|
112 |
self.first.send(symbol, *args) |
|
113 |
else |
|
114 |
super |
|
115 |
end |
|
116 |
end |
|
117 | ||
118 |
end |
|
119 |
app/models/issue.rb | ||
---|---|---|
266 | 266 |
'estimated_hours', |
267 | 267 |
'custom_field_values', |
268 | 268 |
'custom_fields', |
269 |
'custom_multi_values', |
|
269 | 270 |
'lock_version', |
270 | 271 |
:if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) } |
271 | 272 | |
... | ... | |
393 | 394 |
@issue_before_change = self.clone |
394 | 395 |
@issue_before_change.status = self.status |
395 | 396 |
@custom_values_before_change = {} |
396 |
self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value } |
|
397 |
#self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value } |
|
398 |
custom_field_values.each {|c| @custom_values_before_change.store c.custom_field.id, c.value } |
|
397 | 399 |
# Make sure updated_on is updated when adding a note. |
398 | 400 |
updated_on_will_change! |
399 | 401 |
@current_journal |
... | ... | |
883 | 885 |
@current_journal.details << JournalDetail.new(:property => 'attr', |
884 | 886 |
:prop_key => c, |
885 | 887 |
:old_value => @issue_before_change.send(c), |
886 |
:value => send(c)) |
|
888 |
:value => send(c)) unless send(c)==@issue_before_change.send(c)
|
|
887 | 889 |
} |
888 | 890 |
# custom fields changes |
889 |
custom_values.each {|c| |
|
891 |
custom_field_values.each {|c|
|
|
890 | 892 |
next if (@custom_values_before_change[c.custom_field_id]==c.value || |
891 | 893 |
(@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?)) |
892 | 894 |
@current_journal.details << JournalDetail.new(:property => 'cf', |
app/models/journal_detail.rb | ||
---|---|---|
22 | 22 |
private |
23 | 23 |
|
24 | 24 |
def normalize_values |
25 |
self.value = value.join(", ") if value && value.is_a?(Array) |
|
26 |
self.old_value = old_value.join(", ") if old_value && old_value.is_a?(Array) |
|
25 | 27 |
self.value = normalize(value) |
26 | 28 |
self.old_value = normalize(old_value) |
27 | 29 |
end |
app/views/custom_fields/_form.rhtml | ||
---|---|---|
5 | 5 |
function toggle_custom_field_format() { |
6 | 6 |
format = $("custom_field_field_format"); |
7 | 7 |
p_length = $("custom_field_min_length"); |
8 |
p_multi = $("custom_field_allow_multi"); |
|
8 | 9 |
p_regexp = $("custom_field_regexp"); |
9 | 10 |
p_values = $("custom_field_possible_values"); |
10 | 11 |
p_searchable = $("custom_field_searchable"); |
... | ... | |
19 | 20 |
Element.hide(p_regexp.parentNode); |
20 | 21 |
if (p_searchable) Element.show(p_searchable.parentNode); |
21 | 22 |
Element.show(p_values.parentNode); |
23 |
Element.show(p_multi.parentNode); |
|
22 | 24 |
break; |
23 | 25 |
case "bool": |
24 | 26 |
p_default.setAttribute('type','checkbox'); |
... | ... | |
26 | 28 |
Element.hide(p_regexp.parentNode); |
27 | 29 |
if (p_searchable) Element.hide(p_searchable.parentNode); |
28 | 30 |
Element.hide(p_values.parentNode); |
31 |
Element.hide(p_multi.parentNode); |
|
29 | 32 |
break; |
30 | 33 |
case "date": |
31 | 34 |
Element.hide(p_length.parentNode); |
32 | 35 |
Element.hide(p_regexp.parentNode); |
33 | 36 |
if (p_searchable) Element.hide(p_searchable.parentNode); |
34 | 37 |
Element.hide(p_values.parentNode); |
38 |
Element.hide(p_multi.parentNode); |
|
35 | 39 |
break; |
36 | 40 |
case "float": |
37 | 41 |
case "int": |
... | ... | |
39 | 43 |
Element.show(p_regexp.parentNode); |
40 | 44 |
if (p_searchable) Element.hide(p_searchable.parentNode); |
41 | 45 |
Element.hide(p_values.parentNode); |
46 |
Element.hide(p_multi.parentNode); |
|
42 | 47 |
break; |
43 | 48 |
case "user": |
44 | 49 |
case "version": |
... | ... | |
91 | 96 |
<p><%= f.check_box :is_for_all %></p> |
92 | 97 |
<p><%= f.check_box :is_filter %></p> |
93 | 98 |
<p><%= f.check_box :searchable %></p> |
99 |
<p><%= f.check_box :allow_multi %></p> |
|
94 | 100 |
|
95 | 101 |
<% when "UserCustomField" %> |
96 | 102 |
<p><%= f.check_box :is_required %></p> |
... | ... | |
105 | 111 |
<% when "TimeEntryCustomField" %> |
106 | 112 |
<p><%= f.check_box :is_required %></p> |
107 | 113 | |
114 |
<% when "DmsfFileRevisionCustomField" %> |
|
115 |
<p><%= f.check_box :allow_multi %></p> |
|
116 |
<p><%= f.check_box :is_required %></p> |
|
117 | ||
108 | 118 |
<% else %> |
109 | 119 |
<p><%= f.check_box :is_required %></p> |
110 | 120 |
|
app/views/issues/_form_custom_fields.rhtml | ||
---|---|---|
1 |
<script type="text/javascript"> |
|
2 |
//<![CDATA[ |
|
3 |
function toggle_multi_custom(field) { |
|
4 |
select = $('issue_custom_field_values_' + field); |
|
5 |
if (select.multiple == true) { |
|
6 |
select.multiple = false; |
|
7 |
} else { |
|
8 |
select.multiple = true; |
|
9 |
} |
|
10 |
} |
|
11 |
//]]> |
|
12 |
</script> |
|
1 | 13 |
<div class="splitcontentleft"> |
2 | 14 |
<% i = 0 %> |
3 | 15 |
<% split_on = (@issue.custom_field_values.size / 2.0).ceil - 1 %> |
app/views/projects/show.rhtml | ||
---|---|---|
16 | 16 |
<li><%=l(:label_subproject_plural)%>: |
17 | 17 |
<%= @subprojects.collect{|p| link_to(h(p), :action => 'show', :id => p)}.join(", ") %></li> |
18 | 18 |
<% end %> |
19 |
<% @project.visible_custom_field_values.each do |custom_value| %>
|
|
19 |
<% @project.custom_field_values.each do |custom_value| %> |
|
20 | 20 |
<% if !custom_value.value.blank? %> |
21 | 21 |
<li><%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li> |
22 | 22 |
<% end %> |
config/locales/de.yml | ||
---|---|---|
308 | 308 |
field_time_entries: Logzeit |
309 | 309 |
field_time_zone: Zeitzone |
310 | 310 |
field_searchable: Durchsuchbar |
311 |
field_allow_multi: Erlaube mehrere Werte |
|
311 | 312 |
field_default_value: Standardwert |
312 | 313 |
field_comments_sorting: Kommentare anzeigen |
313 | 314 |
field_parent_title: Übergeordnete Seite |
config/locales/en.yml | ||
---|---|---|
292 | 292 |
field_time_entries: Log time |
293 | 293 |
field_time_zone: Time zone |
294 | 294 |
field_searchable: Searchable |
295 |
field_allow_multi: Allow multiple choices |
|
295 | 296 |
field_default_value: Default value |
296 | 297 |
field_comments_sorting: Display comments |
297 | 298 |
field_parent_title: Parent page |
test/unit/custom_value_test.rb | ||
---|---|---|
75 | 75 |
v.value = 'abc' |
76 | 76 |
assert !v.valid? |
77 | 77 |
v.value = 'value2' |
78 |
assert v.valid? |
|
79 |
end |
|
78 |
assert v.valid? |
|
79 |
end |
|
80 |
|
|
81 |
def test_multi_list_field_validation |
|
82 |
f = CustomField.new(:field_format => 'list', :allow_multi => true, :possible_values => ['value1', 'value2']) |
|
83 |
v = CustomValuesCollection.new(:custom_field => f, :value => []) |
|
84 |
assert v.valid? |
|
85 |
v << CustomValue.new(:custom_field => f, :value => 'value1') |
|
86 |
assert v.valid? |
|
87 |
v << CustomValue.new(:custom_field => f, :value => 'value2') |
|
88 |
assert v.valid? |
|
89 |
v << CustomValue.new(:custom_field => f, :value => 'abc') |
|
90 |
assert !v.valid? |
|
91 |
end |
|
92 |
|
|
93 |
def test_multi_list_field_value |
|
94 |
f = CustomField.new(:field_format => 'list', :allow_multi => true, :possible_values => ['value1', 'value2']) |
|
95 |
v = CustomValuesCollection.new(:custom_field => f, :value => []) |
|
96 |
v << CustomValue.new(:custom_field => f, :value => 'value1') |
|
97 |
c = CustomValue.new(:custom_field => f, :value => 'value2') |
|
98 |
v << c |
|
99 |
assert_equal ['value1', 'value2'], v.value |
|
100 |
v << c |
|
101 |
assert ['value1', 'value2'], v.value |
|
102 |
end |
|
80 | 103 | |
81 |
def test_int_field_validation |
|
104 |
def test_int_field_validation
|
|
82 | 105 |
f = CustomField.new(:field_format => 'int') |
83 | 106 |
v = CustomValue.new(:custom_field => f, :value => '') |
84 | 107 |
assert v.valid? |
vendor/plugins/acts_as_customizable/lib/acts_as_customizable.rb | ||
---|---|---|
69 | 69 |
@custom_field_values_changed = true |
70 | 70 |
values = values.stringify_keys |
71 | 71 |
custom_field_values.each do |custom_value| |
72 |
custom_value.value = values[custom_value.custom_field_id.to_s] if values.has_key?(custom_value.custom_field_id.to_s) |
|
72 |
if custom_value.is_a? CustomValuesCollection |
|
73 |
if custom_value.empty? |
|
74 |
values.each do |key, value| |
|
75 |
if (custom_value.custom_field_id == key.to_i && value.is_a?(Array)) |
|
76 |
if value.empty? |
|
77 |
custom_value << custom_values.build(:custom_field => custom_value.custom_field, :value => nil) |
|
78 |
else |
|
79 |
value.each do |v| |
|
80 |
custom_value << custom_values.build(:custom_field => custom_value.custom_field, :value => v) |
|
81 |
end |
|
82 |
end |
|
83 |
end |
|
84 |
end |
|
85 |
end |
|
86 |
CustomValue # otherwise Rails doesn't know the CustomValuesCollection class |
|
87 |
custom_value = CustomValuesCollection.new custom_value.custom_field, custom_value |
|
88 |
#end |
|
89 |
else |
|
90 |
custom_value.value = values[custom_value.custom_field_id.to_s] if values.has_key?(custom_value.custom_field_id.to_s) |
|
91 |
end |
|
73 | 92 |
end if values.is_a?(Hash) |
74 |
self.custom_values = custom_field_values |
|
93 | ||
94 |
self.custom_values = [] |
|
95 |
custom_field_values.each do |cfv| |
|
96 |
if cfv.is_a?(CustomValuesCollection) |
|
97 |
cfv.each do |cv| |
|
98 |
self.custom_values << cv |
|
99 |
end |
|
100 |
else |
|
101 |
self.custom_values << cfv |
|
102 |
end |
|
103 |
end |
|
104 |
self.custom_values |
|
105 |
#self.custom_values = custom_field_values |
|
75 | 106 |
end |
76 | 107 |
|
77 | 108 |
def custom_field_values |
78 |
@custom_field_values ||= available_custom_fields.collect { |x| custom_values.detect { |v| v.custom_field == x } || custom_values.build(:customized => self, :custom_field => x, :value => nil) } |
|
109 |
@custom_field_values ||= available_custom_fields.collect do |x| |
|
110 |
if x.allow_multi |
|
111 |
CustomValue # otherwise Rails doesn't know the CustomValuesCollection class |
|
112 |
custom_field_values = CustomValuesCollection.new x, custom_values.select{ |v| v.custom_field == x } |
|
113 |
if custom_field_values.blank? |
|
114 |
custom_field_values = custom_values.build(:custom_field => x, :value => nil) |
|
115 |
end |
|
116 |
custom_field_values |
|
117 |
else |
|
118 |
custom_values.detect { |v| v.custom_field == x } || custom_values.build(:custom_field => x, :value => nil) |
|
119 |
end |
|
120 |
end |
|
79 | 121 |
end |
80 | 122 |
|
81 |
def visible_custom_field_values |
|
82 |
custom_field_values.select(&:visible?) |
|
123 |
def custom_multi_values=(values) |
|
124 |
values.each do |key, value| |
|
125 |
value.delete_if {|v| v.to_s == ""} if value.length > 1 |
|
126 |
end |
|
127 |
|
|
128 |
@old_custom_values ||= custom_values.select{ |x| x.custom_field.allow_multi } |
|
129 |
@custom_field_values_changed = true |
|
130 |
values = values.stringify_keys |
|
131 |
values.each do |key, value| |
|
132 |
custom_value = custom_field_values.detect{ |c| c.custom_field.id == key.to_i && c.allow_multi } |
|
133 |
value.each do |v| |
|
134 |
old = @old_custom_values.detect{ |u| u.custom_field == custom_value.custom_field && u.value == v } |
|
135 |
if old.blank? |
|
136 |
custom_value << custom_values.build(:custom_field => custom_value.custom_field, :value => v) |
|
137 |
else |
|
138 |
custom_value << old unless custom_value.include?(old) |
|
139 |
@old_custom_values.delete old |
|
140 |
end |
|
141 |
end if values.is_a?(Hash) && custom_value != nil && values.has_key?(custom_value.custom_field.id.to_s) |
|
142 |
end |
|
143 |
#delete old normal values |
|
144 |
@custom_field_values.each { |c| c.delete_if{ |x| @old_custom_values.include?(x) } if c.is_a?(CustomValuesCollection) } |
|
145 |
@custom_values.delete_if { |c| @old_custom_values.include?(c) } |
|
83 | 146 |
end |
84 | 147 |
|
85 | 148 |
def custom_field_values_changed? |
... | ... | |
93 | 156 |
|
94 | 157 |
def save_custom_field_values |
95 | 158 |
custom_field_values.each(&:save) |
159 |
@old_custom_values.each(&:destroy) unless @old_custom_values.blank? |
|
96 | 160 |
@custom_field_values_changed = false |
97 | 161 |
@custom_field_values = nil |
98 | 162 |
end |
... | ... | |
109 | 173 |
end |
110 | 174 |
end |
111 | 175 |
end |
112 |
end |
|
176 |
end |