Commit fd4ced10bf7d7367e72c77d209e66a5ba3b5f707
1 parent
66e89085
Exists in
master
and in
29 other branches
Makes it possible to order fields by dragdrop
Showing
13 changed files
with
200 additions
and
26 deletions
Show diff stats
plugins/custom_forms/controllers/custom_forms_plugin_myprofile_controller.rb
... | ... | @@ -19,6 +19,8 @@ class CustomFormsPluginMyprofileController < MyProfileController |
19 | 19 | params[:form][:profile_id] = profile.id |
20 | 20 | @form = CustomFormsPlugin::Form.new(params[:form]) |
21 | 21 | |
22 | + normalize_positions(@form) | |
23 | + | |
22 | 24 | respond_to do |format| |
23 | 25 | if @form.save |
24 | 26 | flash[:notice] = _("Custom form #{@form.name} was successfully created.") |
... | ... | @@ -35,9 +37,12 @@ class CustomFormsPluginMyprofileController < MyProfileController |
35 | 37 | |
36 | 38 | def update |
37 | 39 | @form = CustomFormsPlugin::Form.find(params[:id]) |
40 | + @form.attributes = params[:form] | |
41 | + | |
42 | + normalize_positions(@form) | |
38 | 43 | |
39 | 44 | respond_to do |format| |
40 | - if @form.update_attributes(params[:form]) | |
45 | + if @form.save | |
41 | 46 | flash[:notice] = _("Custom form #{@form.name} was successfully updated.") |
42 | 47 | format.html { redirect_to(:action=>'index') } |
43 | 48 | else |
... | ... | @@ -84,4 +89,21 @@ class CustomFormsPluginMyprofileController < MyProfileController |
84 | 89 | @form = @submission.form |
85 | 90 | end |
86 | 91 | |
92 | + private | |
93 | + | |
94 | + def normalize_positions(form) | |
95 | + counter = 0 | |
96 | + form.fields.sort_by{ |f| f.position.to_i }.each do |field| | |
97 | + field.position = counter | |
98 | + counter += 1 | |
99 | + end | |
100 | + form.fields.each do |field| | |
101 | + counter = 0 | |
102 | + field.alternatives.sort_by{ |alt| alt.position.to_i }.each do |alt| | |
103 | + alt.position = counter | |
104 | + counter += 1 | |
105 | + end | |
106 | + end | |
107 | + end | |
108 | + | |
87 | 109 | end | ... | ... |
plugins/custom_forms/db/migrate/20131107050913_add_position_to_field_and_alternatives.rb
0 → 100644
... | ... | @@ -0,0 +1,31 @@ |
1 | +class AddPositionToFieldAndAlternatives < ActiveRecord::Migration | |
2 | + def self.up | |
3 | + change_table :custom_forms_plugin_fields do |t| | |
4 | + t.integer :position, :default => 0 | |
5 | + end | |
6 | + | |
7 | + change_table :custom_forms_plugin_alternatives do |t| | |
8 | + t.integer :position, :default => 0 | |
9 | + end | |
10 | + | |
11 | + CustomFormsPlugin::Field.find_each do |f| | |
12 | + f.position = f.id | |
13 | + f.save! | |
14 | + end | |
15 | + | |
16 | + CustomFormsPlugin::Alternative.find_each do |f| | |
17 | + f.position = f.id | |
18 | + f.save! | |
19 | + end | |
20 | + end | |
21 | + | |
22 | + def self.down | |
23 | + change_table :custom_forms_plugin_fields do |t| | |
24 | + t.remove :position | |
25 | + end | |
26 | + | |
27 | + change_table :custom_forms_plugin_alternatives do |t| | |
28 | + t.remove :position | |
29 | + end | |
30 | + end | |
31 | +end | ... | ... |
plugins/custom_forms/lib/custom_forms_plugin/field.rb
... | ... | @@ -6,7 +6,7 @@ class CustomFormsPlugin::Field < ActiveRecord::Base |
6 | 6 | belongs_to :form, :class_name => 'CustomFormsPlugin::Form' |
7 | 7 | has_many :answers, :class_name => 'CustomFormsPlugin::Answer' |
8 | 8 | |
9 | - has_many :alternatives, :class_name => 'CustomFormsPlugin::Alternative' | |
9 | + has_many :alternatives, :order => 'position', :class_name => 'CustomFormsPlugin::Alternative' | |
10 | 10 | accepts_nested_attributes_for :alternatives, :allow_destroy => true |
11 | 11 | |
12 | 12 | before_validation do |field| | ... | ... |
plugins/custom_forms/lib/custom_forms_plugin/form.rb
1 | 1 | class CustomFormsPlugin::Form < Noosfero::Plugin::ActiveRecord |
2 | 2 | belongs_to :profile |
3 | 3 | |
4 | - has_many :fields, :class_name => 'CustomFormsPlugin::Field', :dependent => :destroy | |
4 | + has_many :fields, :order => 'position', :class_name => 'CustomFormsPlugin::Field', :dependent => :destroy | |
5 | 5 | accepts_nested_attributes_for :fields, :allow_destroy => true |
6 | 6 | |
7 | 7 | has_many :submissions, :class_name => 'CustomFormsPlugin::Submission' | ... | ... |
plugins/custom_forms/public/field.js
1 | +var fixHelperSortable = function(e, tr) { | |
2 | + tr.children().each(function() { | |
3 | + jQuery(this).width(jQuery(this).width()); | |
4 | + }); | |
5 | + return tr; | |
6 | +}; | |
7 | + | |
8 | +var updatePosition = function(e, ui) { | |
9 | + var tag = ui.item[0].tagName.toLowerCase(); | |
10 | + var count = ui.item.prevAll(tag).eq(0).find('input').filter(function() {return /_position/.test(this.id); }).val(); | |
11 | + count = count ? ++count : 0; | |
12 | + | |
13 | + ui.item.find('input').filter(function() {return /_position/.test(this.id); }).eq(0).val(count); | |
14 | + | |
15 | + for (i = 0; i < ui.item.nextAll(tag).length; i++) { | |
16 | + count++; | |
17 | + ui.item.nextAll(tag).eq(i).find('input').filter(function() {return /_position/.test(this.id); }).val(count); | |
18 | + } | |
19 | +} | |
20 | + | |
21 | +jQuery('tbody.field-list').sortable({ | |
22 | + helper: fixHelperSortable, | |
23 | + update: updatePosition | |
24 | +}); | |
25 | + | |
26 | +jQuery("ul.field-list").sortable({ | |
27 | + placeholder: 'ui-state-highlight', | |
28 | + axis: 'y', | |
29 | + opacity: 0.8, | |
30 | + cursor: 'move', | |
31 | + tolerance: 'pointer', | |
32 | + forcePlaceholderSize: true, | |
33 | + update: updatePosition | |
34 | +}); | |
35 | + | |
36 | +jQuery("ul.field-list li").disableSelection(); | |
37 | + | |
1 | 38 | var customFormsPlugin = { |
2 | 39 | removeFieldBox: function (button, confirmMsg) { |
3 | 40 | if (confirm(confirmMsg)) { |
... | ... | @@ -20,11 +57,17 @@ var customFormsPlugin = { |
20 | 57 | addFields: function (button, association, content) { |
21 | 58 | var new_id = new Date().getTime(); |
22 | 59 | var regexp = new RegExp("new_" + association, "g"); |
23 | - jQuery(content.replace(regexp, new_id)).insertBefore(jQuery(button).closest('.addition-buttons')).hide().slideDown(); | |
60 | + content = content.replace(regexp, new_id); | |
24 | 61 | |
25 | 62 | if(association == 'alternatives') { |
63 | + jQuery(content).appendTo(jQuery(button).closest('tfoot').next('tbody.field-list')).hide().slideDown(); | |
26 | 64 | jQuery(button).closest('table').find('tr:first').show(); |
65 | + jQuery(button).closest('tfoot').next('tbody.field-list').sortable({ helper: fixHelperSortable, update: updatePosition}); | |
66 | + } else { | |
67 | + jQuery('<li>').append(jQuery(content)).appendTo(jQuery(button).parent().prev('ul.field-list')).hide().slideDown(); | |
27 | 68 | } |
69 | + | |
70 | + jQuery('input').filter(function () { return new RegExp(new_id + "_position", "g").test(this.id); }).val(new_id); | |
28 | 71 | }, |
29 | 72 | |
30 | 73 | checkHeaderDisplay: function(table) { | ... | ... |
plugins/custom_forms/public/style.css
... | ... | @@ -4,7 +4,7 @@ |
4 | 4 | } |
5 | 5 | |
6 | 6 | .action-table { |
7 | - width: 100%; | |
7 | + width: 100%; | |
8 | 8 | overflow: hidden; |
9 | 9 | } |
10 | 10 | |
... | ... | @@ -13,10 +13,6 @@ |
13 | 13 | text-align: center; |
14 | 14 | } |
15 | 15 | |
16 | -.action-table td{ | |
17 | - cursor: move; | |
18 | -} | |
19 | - | |
20 | 16 | .action-table .actions{ |
21 | 17 | white-space: nowrap; |
22 | 18 | } |
... | ... | @@ -55,3 +51,22 @@ |
55 | 51 | .field-text-default { |
56 | 52 | margin-top: 10px; |
57 | 53 | } |
54 | + | |
55 | +.field-list { | |
56 | + list-style-type: none; | |
57 | + margin: 0px; | |
58 | + padding: 0px; | |
59 | + cursor: move; | |
60 | +} | |
61 | + | |
62 | +.field-list label, .field-list legend { | |
63 | + cursor: move; | |
64 | +} | |
65 | + | |
66 | +ul.field-list > li > fieldset:hover { | |
67 | + border: 2px dotted #BBB; | |
68 | +} | |
69 | + | |
70 | +tr.addition-buttons { | |
71 | + cursor: auto; | |
72 | +} | ... | ... |
plugins/custom_forms/test/functional/custom_forms_plugin_myprofile_controller_test.rb
... | ... | @@ -76,8 +76,8 @@ class CustomFormsPluginMyprofileControllerTest < ActionController::TestCase |
76 | 76 | assert_equal 'Cool form', form.description |
77 | 77 | assert_equal 2, form.fields.count |
78 | 78 | |
79 | - f1 = form.fields.first | |
80 | - f2 = form.fields.last | |
79 | + f1 = form.fields[0] | |
80 | + f2 = form.fields[1] | |
81 | 81 | |
82 | 82 | assert_equal 'Name', f1.name |
83 | 83 | assert_equal 'Jack', f1.default_value |
... | ... | @@ -89,15 +89,15 @@ class CustomFormsPluginMyprofileControllerTest < ActionController::TestCase |
89 | 89 | assert f2.kind_of?(CustomFormsPlugin::SelectField) |
90 | 90 | end |
91 | 91 | |
92 | - should 'create fields in the order they are sent' do | |
92 | + should 'create fields in the order they are sent when no position defined' do | |
93 | 93 | format = '%Y-%m-%d %H:%M' |
94 | 94 | num_fields = 10 |
95 | 95 | begining = Time.now.strftime(format) |
96 | 96 | ending = (Time.now + 1.day).strftime(format) |
97 | 97 | fields = {} |
98 | 98 | num_fields.times do |i| |
99 | - fields[i.to_s] = { | |
100 | - :name => i.to_s, | |
99 | + fields[i] = { | |
100 | + :name => (10-i).to_s, | |
101 | 101 | :default_value => '', |
102 | 102 | :type => 'CustomFormsPlugin::TextField' |
103 | 103 | } |
... | ... | @@ -115,13 +115,47 @@ class CustomFormsPluginMyprofileControllerTest < ActionController::TestCase |
115 | 115 | end |
116 | 116 | form = CustomFormsPlugin::Form.find_by_name('My Form') |
117 | 117 | assert_equal num_fields, form.fields.count |
118 | - lst = -1 | |
119 | - form.fields.find_each do |f| | |
120 | - assert f.name.to_i > lst | |
121 | - lst = f.name.to_i | |
118 | + lst = 10 | |
119 | + form.fields.each do |f| | |
120 | + assert f.name.to_i == lst | |
121 | + lst = lst - 1 | |
122 | 122 | end |
123 | 123 | end |
124 | 124 | |
125 | + should 'create fields in any position size' do | |
126 | + format = '%Y-%m-%d %H:%M' | |
127 | + begining = Time.now.strftime(format) | |
128 | + ending = (Time.now + 1.day).strftime(format) | |
129 | + fields = {} | |
130 | + fields['0'] = { | |
131 | + :name => '0', | |
132 | + :default_value => '', | |
133 | + :type => 'CustomFormsPlugin::TextField', | |
134 | + :position => '999999999999' | |
135 | + } | |
136 | + fields['1'] = { | |
137 | + :name => '1', | |
138 | + :default_value => '', | |
139 | + :type => 'CustomFormsPlugin::TextField', | |
140 | + :position => '1' | |
141 | + } | |
142 | + assert_difference CustomFormsPlugin::Form, :count, 1 do | |
143 | + post :create, :profile => profile.identifier, | |
144 | + :form => { | |
145 | + :name => 'My Form', | |
146 | + :access => 'logged', | |
147 | + :begining => begining, | |
148 | + :ending => ending, | |
149 | + :description => 'Cool form', | |
150 | + :fields_attributes => fields | |
151 | + } | |
152 | + end | |
153 | + form = CustomFormsPlugin::Form.find_by_name('My Form') | |
154 | + assert_equal 2, form.fields.count | |
155 | + assert form.fields.first.name == "1" | |
156 | + assert form.fields.last.name == "0" | |
157 | + end | |
158 | + | |
125 | 159 | should 'edit a form' do |
126 | 160 | form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software') |
127 | 161 | format = '%Y-%m-%d %H:%M' | ... | ... |
plugins/custom_forms/test/unit/custom_forms_plugin/field_test.rb
... | ... | @@ -32,5 +32,13 @@ class CustomFormsPlugin::FieldTest < ActiveSupport::TestCase |
32 | 32 | end |
33 | 33 | assert_equal form.fields, [license_field] |
34 | 34 | end |
35 | + | |
36 | + should 'sort alternatives by position' do | |
37 | + field = CustomFormsPlugin::Field.create!(:name => 'field001') | |
38 | + second = CustomFormsPlugin::Alternative.create!(:label => 'second', :field => field, :position => 2) | |
39 | + first = CustomFormsPlugin::Alternative.create!(:label => 'first', :field => field, :position => 1) | |
40 | + | |
41 | + assert_equal field.alternatives, [first, second] | |
42 | + end | |
35 | 43 | end |
36 | 44 | ... | ... |
plugins/custom_forms/test/unit/custom_forms_plugin/form_test.rb
... | ... | @@ -180,4 +180,12 @@ class CustomFormsPlugin::FormTest < ActiveSupport::TestCase |
180 | 180 | end |
181 | 181 | end |
182 | 182 | |
183 | + should 'sort fields by position' do | |
184 | + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile)) | |
185 | + license_field = CustomFormsPlugin::Field.create!(:name => 'License', :form => form, :position => 2) | |
186 | + url_field = CustomFormsPlugin::Field.create!(:name => 'URL', :form => form, :position => 0) | |
187 | + | |
188 | + assert_equal form.fields, [url_field, license_field] | |
189 | + end | |
190 | + | |
183 | 191 | end | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/_field.html.erb
... | ... | @@ -9,6 +9,8 @@ |
9 | 9 | <%= f.check_box :mandatory %> |
10 | 10 | <%= f.label :mandatory, _('Mandatory') %> |
11 | 11 | |
12 | + <%= f.hidden_field(:position) %> | |
13 | + | |
12 | 14 | <%= f.hidden_field :_destroy, :class => 'destroy-field' %> |
13 | 15 | <%= button_to_function :delete, _('Remove field'), "customFormsPlugin.removeFieldBox(this, #{_('Are you sure you want to remove this field?').to_json})" %> |
14 | 16 | <%= yield %> | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/_form.html.erb
... | ... | @@ -17,9 +17,13 @@ |
17 | 17 | |
18 | 18 | <h2><%= _('Fields') %></h2> |
19 | 19 | |
20 | -<% f.fields_for :fields do |builder| %> | |
21 | - <%= render partial_for_class(builder.object.class), :f => builder %> | |
22 | -<% end %> | |
20 | +<ul class='field-list'> | |
21 | + <% f.fields_for :fields do |builder| %> | |
22 | + <li> | |
23 | + <%= render partial_for_class(builder.object.class), :f => builder %> | |
24 | + </li> | |
25 | + <% end %> | |
26 | +</ul> | |
23 | 27 | |
24 | 28 | <div class="addition-buttons"> |
25 | 29 | <%= button(:add, _('Add a new text field'), '#', :onclick => "customFormsPlugin.addFields(this, 'fields', #{html_for_field(f, :fields, CustomFormsPlugin::TextField).to_json}); return false")%> | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/custom_forms_plugin/_alternative.html.erb
... | ... | @@ -3,8 +3,10 @@ |
3 | 3 | |
4 | 4 | <td> <%= f.check_box(:selected_by_default) %> </td> |
5 | 5 | |
6 | + <%= f.hidden_field(:position) %> | |
7 | + | |
6 | 8 | <td> |
7 | 9 | <%= f.hidden_field :_destroy, :class => 'destroy-field' %> |
8 | 10 | <%= button_to_function_without_text :remove, _('Remove alternative'), "customFormsPlugin.removeAlternative(this, #{_('Are you sure you want to remove this alternative?').to_json})", :class => 'remove-field', :title => _('Remove alternative') %> |
9 | 11 | </td> |
10 | -</div> | |
12 | +</tr> | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/custom_forms_plugin/_select_field.html.erb
... | ... | @@ -12,20 +12,25 @@ |
12 | 12 | </div> |
13 | 13 | |
14 | 14 | <table> |
15 | + <thead> | |
15 | 16 | <tr <%='style="display:none"' if f.object.alternatives.empty? %>> |
16 | 17 | <th><%= _('Alternative') %></th> |
17 | 18 | <th><%= _('Preselected') %></th> |
18 | 19 | <th><%= _('Remove') %></th> |
19 | 20 | </tr> |
20 | - <% f.fields_for :alternatives do |builder| %> | |
21 | - <%= render partial_for_class(builder.object.class), :f => builder %> | |
22 | - <% end %> | |
21 | + </thead> | |
22 | + <tfoot> | |
23 | 23 | <tr class="addition-buttons"> |
24 | 24 | <td colspan="3"> |
25 | 25 | <%= button(:add, _('Add a new alternative'), '#', :onclick => "customFormsPlugin.addFields(this, 'alternatives', #{html_for_field(f, :alternatives, CustomFormsPlugin::Alternative).to_json}); return false") %> |
26 | 26 | </td> |
27 | 27 | </tr> |
28 | + </tfoot> | |
29 | + <tbody class='field-list'> | |
30 | + <% f.fields_for :alternatives do |builder| %> | |
31 | + <%= render partial_for_class(builder.object.class), :f => builder %> | |
32 | + <% end %> | |
33 | + </tbody> | |
28 | 34 | </table> |
29 | 35 | |
30 | - | |
31 | 36 | <% end %> | ... | ... |