Project

General

Profile

Feature #1189 » multi_values_for_r6402.diff

mult_values for revision r6402 - Tag 1.2.1-stable - Matteo Giaccone, 2011-08-05 18:25

View differences:

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
......
285 286
    @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
286 287
    @time_entry.attributes = params[:time_entry]
287 288

  
289
    @custom_values = @issue.custom_field_values
288 290
    @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
289 291
    @issue.init_journal(User.current, @notes)
290 292
    @issue.safe_attributes = params[:issue]
app/helpers/custom_fields_helper.rb
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
      multi_image = custom_field.allow_multi ?
53
                    link_to_function(image_tag('bullet_toggle_plus.png'), "toggle_multi_custom('#{custom_field.id}');", :style => "vertical-align: bottom;") :
54
                    ''
55
      multiple = custom_field.allow_multi && custom_value.value.is_a?(Array) && custom_value.value.length > 1
56
      select_name = custom_field.allow_multi ? "#{name}[custom_multi_values][#{custom_field.id}][]" : field_name
57
      select_tag(select_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id, :multiple => multiple) + multi_image
53 58
    else
54 59
      text_field_tag(field_name, custom_value.value, :id => field_id)
55 60
    end
......
92 97
  # Return a string used to display a custom value
93 98
  def show_value(custom_value)
94 99
    return "" unless custom_value
95
    format_value(custom_value.value, custom_value.custom_field.field_format)
100
    format_value((custom_value.value.is_a?(Array) ? custom_value.value.join("\n") : custom_value.value), custom_value.custom_field.field_format)
96 101
  end
97 102
  
98 103
  # 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
  attr_accessor :custom_field
75
  
76
  def initialize(custom_field, custom_values=[])
77
    @custom_field = custom_field if custom_field.is_a?(CustomField)    
78
    custom_values.map{ |x| self << x }
79
    self
80
  end
81

  
82
  def value
83
    self.uniq.map(&:value).delete_if {|x| x.blank?}
84
  end
85

  
86
  def value=(new_value)
87
    self.delete_if{ |x| true }
88
    new_value.map{ |x| self << x }
89
  end
90
  
91
  def save
92
    self.compact.each(&:save)
93
  end
94

  
95
  def valid?
96
    self.inject(true){ |bool,v| bool && v.valid? }
97
  end
98

  
99
  def validate
100
    self.uniq.map(&:validate)
101
  end
102

  
103
  def custom_field_id
104
    @custom_field.id
105
  end
106

  
107
  def method_missing(symbol, *args)
108
    if @custom_field.respond_to?(symbol)
109
      @custom_field.send(symbol, *args)
110
    elsif self.first && self.first.respond_to?(symbol)
111
      self.first.send(symbol, *args)
112
    else
113
      super
114
    end
115
  end
116
end
app/models/issue.rb
264 264
    'due_date',
265 265
    'done_ratio',
266 266
    'estimated_hours',
267
    'custom_multi_values',
267 268
    'custom_field_values',
268 269
    'custom_fields',
269 270
    'lock_version',
......
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
    custom_field_values.each {|c| @custom_values_before_change.store c.custom_field.id, c.value }
397 398
    # Make sure updated_on is updated when adding a note.
398 399
    updated_on_will_change!
399 400
    @current_journal
......
886 887
                                                      :value => send(c))
887 888
      }
888 889
      # custom fields changes
889
      custom_values.each {|c|
890
      custom_field_values.each {|c|
890 891
        next if (@custom_values_before_change[c.custom_field_id]==c.value ||
891 892
                  (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
892 893
        @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":
......
53 58
      Element.show(p_regexp.parentNode);
54 59
      if (p_searchable) Element.show(p_searchable.parentNode);
55 60
      Element.hide(p_values.parentNode);
61
      Element.hide(p_multi.parentNode);
56 62
      break;
57 63
  }
58 64
}
......
91 97
    <p><%= f.check_box :is_for_all %></p>
92 98
    <p><%= f.check_box :is_filter %></p>
93 99
    <p><%= f.check_box :searchable %></p>
100
    <p><%= f.check_box :allow_multi %></p>
94 101
    
95 102
<% when "UserCustomField" %>
96 103
    <p><%= f.check_box :is_required %></p>
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 %>
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
db/migrate/20100512172200_add_custom_fields_multi.rb
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
test/unit/custom_value_test.rb
78 78
    assert v.valid?
79 79
  end
80 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
103

  
81 104
  def test_int_field_validation
82 105
    f = CustomField.new(:field_format => 'int')
83 106
    v = CustomValue.new(:custom_field => f, :value => '')
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 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
(4-4/8)