Project

General

Profile

Feature #1189 » multi_values_for_r3739.diff

Minjie Zhu, 2010-05-17 14:52

View differences:

app/controllers/issues_controller.rb (working copy)
119 119
    @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
120 120
    @priorities = IssuePriority.all
121 121
    @time_entry = TimeEntry.new
122
    @custom_values = @issue.custom_field_values
122 123
    respond_to do |format|
123 124
      format.html { render :template => 'issues/show.rhtml' }
124 125
      format.xml  { render :layout => false }
......
428 429
    @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
429 430
    @time_entry = TimeEntry.new
430 431
    
432
    @custom_values = @issue.custom_field_values
431 433
    @notes = params[:notes]
432 434
    @issue.init_journal(User.current, @notes)
433 435
    # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
app/helpers/custom_fields_helper.rb (working copy)
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, 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 (working copy)
34 34
  def before_validation
35 35
    # make sure these fields are not searchable
36 36
    self.searchable = false if %w(int float date bool).include?(field_format)
37
    # make sure only list field_format have allow_multi option
38
    self.allow_multi = false unless field_format == 'list'
37 39
    true
38 40
  end
39 41
  
app/models/custom_value.rb (working copy)
65 65
    end
66 66
  end
67 67
end
68

  
69
class CustomValuesCollection < Array
70
  attr_accessor :custom_field
71
  
72
  def initialize(custom_field, custom_values=[])
73
    @custom_field = custom_field if custom_field.is_a?(CustomField)    
74
    custom_values.map{ |x| self << x }
75
    self
76
  end
77

  
78
  def value
79
    self.uniq.map(&:value).delete_if {|x| x.blank?}
80
  end
81

  
82
  def value=(new_value)
83
    self.delete_if{ |x| true }
84
    new_value.map{ |x| self << x }
85
  end
86
  
87
  def save
88
    self.compact.each(&:save)
89
  end
90

  
91
  def valid?
92
    self.inject(true){ |bool,v| bool && v.valid? }
93
  end
94

  
95
  def validate
96
    self.uniq.map(&:validate)
97
  end
98

  
99
  def custom_field_id
100
    @custom_field.id
101
  end
102

  
103
  def method_missing(symbol, *args)
104
    if @custom_field.respond_to?(symbol)
105
      @custom_field.send(symbol, *args)
106
    elsif self.first && self.first.respond_to?(symbol)
107
      self.first.send(symbol, *args)
108
    else
109
      super
110
    end
111
  end
112
end
app/models/issue.rb (working copy)
211 211
    due_date
212 212
    done_ratio
213 213
    estimated_hours
214
    custom_multi_values
214 215
    custom_field_values
215 216
    lock_version
216 217
  ) unless const_defined?(:SAFE_ATTRIBUTES)
......
318 319
    @issue_before_change = self.clone
319 320
    @issue_before_change.status = self.status
320 321
    @custom_values_before_change = {}
321
    self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
322
    custom_field_values.each {|c| @custom_values_before_change.store c.custom_field.id, c.value }
322 323
    # Make sure updated_on is updated when adding a note.
323 324
    updated_on_will_change!
324 325
    @current_journal
......
781 782
                                                      :value => send(c)) unless send(c)==@issue_before_change.send(c)
782 783
      }
783 784
      # custom fields changes
784
      custom_values.each {|c|
785
      custom_field_values.each {|c|
785 786
        next if (@custom_values_before_change[c.custom_field_id]==c.value ||
786 787
                  (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
787 788
        @current_journal.details << JournalDetail.new(:property => 'cf', 
app/models/journal_detail.rb (working copy)
19 19
  belongs_to :journal
20 20
  
21 21
  def before_save
22
    self.value = value.join(", ") if value && value.is_a?(Array)
23
    self.old_value = old_value.join(", ") if old_value && old_value.is_a?(Array)
22 24
    self.value = value[0..254] if value && value.is_a?(String)
23 25
    self.old_value = old_value[0..254] if old_value && old_value.is_a?(String)
24 26
  end
app/views/custom_fields/_form.rhtml (working copy)
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);
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);
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);
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);
46
      Element.hide(p_multi.parentNode);
42 47
      break;
43 48
    default:
44 49
      Element.show(p_length.parentNode);
45 50
      Element.show(p_regexp.parentNode);
46 51
      if (p_searchable) Element.show(p_searchable.parentNode);
47 52
      Element.hide(p_values);
53
      Element.hide(p_multi.parentNode);
48 54
      break;
49 55
  }
50 56
}
......
83 89
    <p><%= f.check_box :is_for_all %></p>
84 90
    <p><%= f.check_box :is_filter %></p>
85 91
    <p><%= f.check_box :searchable %></p>
92
    <p><%= f.check_box :allow_multi %></p>
86 93
    
87 94
<% when "UserCustomField" %>
88 95
    <p><%= f.check_box :is_required %></p>
app/views/issues/_form_custom_fields.rhtml (working copy)
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 (working copy)
272 272
  field_column_names: Columns
273 273
  field_time_zone: Time zone
274 274
  field_searchable: Searchable
275
  field_allow_multi: Allow multiple choices
275 276
  field_default_value: Default value
276 277
  field_comments_sorting: Display comments
277 278
  field_parent_title: Parent page
config/locales/fr.yml (working copy)
291 291
  field_column_names: Colonnes
292 292
  field_time_zone: Fuseau horaire
293 293
  field_searchable: Utilisé pour les recherches
294
  field_allow_multi: Permettre les choix multiples
294 295
  field_default_value: Valeur par défaut
295 296
  field_comments_sorting: Afficher les commentaires
296 297
  field_parent_title: Page parent
config/locales/ja.yml (working copy)
303 303
  field_column_names: 項目
304 304
  field_time_zone: タイムゾーン
305 305
  field_searchable: 検索条件に設定可能とする
306
  field_allow_multi: 複数選択可能
306 307
  field_default_value: デフォルト値
307 308
  field_comments_sorting: コメントを表示
308 309
  field_parent_title: 親ページ
config/locales/zh-TW.yml (working copy)
363 363
  field_column_names: 欄位
364 364
  field_time_zone: 時區
365 365
  field_searchable: 可用做搜尋條件
366
  field_allow_multi: 允許多重選擇
366 367
  field_default_value: 預設值
367 368
  field_comments_sorting: 註解排序
368 369
  field_parent_title: 父頁面
config/locales/zh.yml (working copy)
291 291
  field_column_names: 列
292 292
  field_time_zone: 时区
293 293
  field_searchable: 可用作搜索条件
294
  field_allow_multi: 允许多选
294 295
  field_default_value: 默认值
295 296
  field_comments_sorting: 显示注释
296 297
  field_parent_title: 上级页面
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
test/unit/custom_value_test.rb (working copy)
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 (working copy)
54 54
          @custom_field_values_changed = true
55 55
          values = values.stringify_keys
56 56
          custom_field_values.each do |custom_value|
57
            custom_value.value = values[custom_value.custom_field_id.to_s] if values.has_key?(custom_value.custom_field_id.to_s)
57
            if custom_value.is_a? CustomValuesCollection
58
              if custom_value.empty?
59
                values.each do |key, value|
60
                  if (custom_value.custom_field_id == key.to_i && value.is_a?(Array))
61
                    if value.empty?
62
                      custom_value << custom_values.build(:custom_field => custom_value.custom_field, :value => nil)
63
                    else
64
                      value.each do |v|
65
                        custom_value << custom_values.build(:custom_field => custom_value.custom_field, :value => v)
66
                      end
67
                    end
68
                  end
69
                end
70
              end
71
              CustomValue # otherwise Rails doesn't know the CustomValuesCollection class
72
              custom_value = CustomValuesCollection.new custom_value.custom_field, custom_value
73
              #end
74
            else
75
              custom_value.value = values[custom_value.custom_field_id.to_s] if values.has_key?(custom_value.custom_field_id.to_s)
76
            end
58 77
          end if values.is_a?(Hash)
59 78
        end
60 79
        
61 80
        def custom_field_values
62
          @custom_field_values ||= available_custom_fields.collect { |x| custom_values.detect { |v| v.custom_field == x } || custom_values.build(:custom_field => x, :value => nil) }
81
          @custom_field_values ||= available_custom_fields.collect do |x|
82
            if x.allow_multi
83
              CustomValue # otherwise Rails doesn't know the CustomValuesCollection class
84
              CustomValuesCollection.new x, custom_values.select{ |v| v.custom_field == x }
85
            else
86
              custom_values.detect { |v| v.custom_field == x } || custom_values.build(:custom_field => x, :value => nil)
87
            end
88
          end
89
        end
90
        
91
        def custom_multi_values=(values)
92
          values.each do |key, value|
93
            value.delete_if {|v| v.to_s == ""} if value.length > 1
94
          end
95
          
96
          @old_custom_values ||= custom_values.select{ |x| x.custom_field.allow_multi }
97
          @custom_field_values_changed = true
98
          values = values.stringify_keys
99
          values.each do |key, value|
100
            custom_value = custom_field_values.detect{ |c| c.custom_field.id == key.to_i && c.allow_multi }
101
            value.each do |v|
102
              old = @old_custom_values.detect{ |u| u.custom_field == custom_value.custom_field && u.value == v }
103
              if old.blank?
104
                custom_value << custom_values.build(:custom_field => custom_value.custom_field, :value => v)
105
              else
106
                custom_value << old unless custom_value.include?(old)
107
                @old_custom_values.delete old
108
              end
109
            end if values.is_a?(Hash) && custom_value != nil && values.has_key?(custom_value.custom_field.id.to_s)
110
          end
111
          #delete old normal values
112
          @custom_field_values.each { |c| c.delete_if{ |x| @old_custom_values.include?(x) } if c.is_a?(CustomValuesCollection) }
113
          @custom_values.delete_if { |c| @old_custom_values.include?(c) }
63 114
        end
64 115
        
65 116
        def custom_field_values_changed?
......
73 124
        
74 125
        def save_custom_field_values
75 126
          custom_field_values.each(&:save)
127
          @old_custom_values.each(&:destroy) unless @old_custom_values.blank?
76 128
          @custom_field_values_changed = false
77 129
          @custom_field_values = nil
78 130
        end
(1-1/8)