Commit fd4ced10bf7d7367e72c77d209e66a5ba3b5f707

Authored by Larissa Reis
1 parent 66e89085

Makes it possible to order fields by dragdrop

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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 %>
... ...