Project

General

Profile

Feature #1189 » custom_field_multiple_values.diff

Sven Eisenschmidt, 2011-09-12 16:21

View differences:

test/unit/custom_value_test.rb (Arbeitskopie)
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?
app/helpers/custom_fields_helper.rb (Arbeitskopie)
49 49
      blank_option = custom_field.is_required? ?
50 50
                       (custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') : 
51 51
                       '<option></option>'
52
      select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), :id => field_id)
52
      #select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), :id => field_id)
53
      multi_image = custom_field.allow_multi ?
54
                    link_to_function(image_tag('bullet_toggle_plus.png'), "toggle_multi_custom('#{custom_field.id}');", :style => "vertical-align: bottom;") :
55
                    ''
56
      multiple = custom_field.allow_multi && custom_value.value.is_a?(Array) && custom_value.value.length > 1
57
      select_name = custom_field.allow_multi ? "#{name}[custom_multi_values][#{custom_field.id}][]" : field_name
58
      select_tag(select_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id, :multiple => multiple) + multi_image
53 59
    else
54 60
      text_field_tag(field_name, custom_value.value, :id => field_id)
55 61
    end
......
92 98
  # Return a string used to display a custom value
93 99
  def show_value(custom_value)
94 100
    return "" unless custom_value
95
    format_value(custom_value.value, custom_value.custom_field.field_format)
101
    format_value((custom_value.value.is_a?(Array) ? custom_value.value.join("\n") : custom_value.value), custom_value.custom_field.field_format)
96 102
  end
97 103
  
98 104
  # Return a string used to display a custom value
app/models/custom_field.rb (Arbeitskopie)
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/issue.rb (Arbeitskopie)
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 (Arbeitskopie)
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/models/custom_value.rb (Arbeitskopie)
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/controllers/issues_controller.rb (Arbeitskopie)
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/views/custom_fields/_form.rhtml (Arbeitskopie)
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>
app/views/projects/show.rhtml (Arbeitskopie)
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 %>
app/views/issues/_form_custom_fields.rhtml (Arbeitskopie)
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 %>
db/migrate/20100512172200_add_custom_fields_multi.rb (Revision 0)
1
class AddCustomFieldsMulti < ActiveRecord::Migration
2
  def self.up
3
    add_column :custom_fields, :allow_multi, :boolean, :default => false
4
  end
5

  
6
  def self.down
7
    remove_column :custom_fields, :allow_multi
8
  end
9
end
vendor/plugins/acts_as_customizable/lib/acts_as_customizable.rb (Arbeitskopie)
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 93
          self.custom_values = custom_field_values
75 94
        end
76 95
        
77 96
        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) }
97
          @custom_field_values ||= available_custom_fields.collect do |x|
98
            if x.allow_multi
99
              CustomValue # otherwise Rails doesn't know the CustomValuesCollection class
100
              CustomValuesCollection.new x, custom_values.select{ |v| v.custom_field == x }
101
            else
102
              custom_values.detect { |v| v.custom_field == x } || custom_values.build(:custom_field => x, :value => nil)
103
            end
104
          end
79 105
        end
80 106
        
81
        def visible_custom_field_values
82
          custom_field_values.select(&:visible?)
107
        def custom_multi_values=(values)
108
          values.each do |key, value|
109
            value.delete_if {|v| v.to_s == ""} if value.length > 1
110
          end
111
          
112
          @old_custom_values ||= custom_values.select{ |x| x.custom_field.allow_multi }
113
          @custom_field_values_changed = true
114
          values = values.stringify_keys
115
          values.each do |key, value|
116
            custom_value = custom_field_values.detect{ |c| c.custom_field.id == key.to_i && c.allow_multi }
117
            value.each do |v|
118
              old = @old_custom_values.detect{ |u| u.custom_field == custom_value.custom_field && u.value == v }
119
              if old.blank?
120
                custom_value << custom_values.build(:custom_field => custom_value.custom_field, :value => v)
121
              else
122
                custom_value << old unless custom_value.include?(old)
123
                @old_custom_values.delete old
124
              end
125
            end if values.is_a?(Hash) && custom_value != nil && values.has_key?(custom_value.custom_field.id.to_s)
126
          end
127
          #delete old normal values
128
          @custom_field_values.each { |c| c.delete_if{ |x| @old_custom_values.include?(x) } if c.is_a?(CustomValuesCollection) }
129
          @custom_values.delete_if { |c| @old_custom_values.include?(c) }
83 130
        end
84 131
        
85 132
        def custom_field_values_changed?
......
93 140
        
94 141
        def save_custom_field_values
95 142
          custom_field_values.each(&:save)
143
		  @old_custom_values.each(&:destroy) unless @old_custom_values.blank?
96 144
          @custom_field_values_changed = false
97 145
          @custom_field_values = nil
98 146
        end
......
109 157
      end
110 158
    end
111 159
  end
112
end
160
end
config/locales/en.yml (Arbeitskopie)
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
config/locales/de.yml (Arbeitskopie)
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
(5-5/8)