Commit fd4ced10bf7d7367e72c77d209e66a5ba3b5f707
1 parent
66e89085
Exists in
master
and in
8 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 %> | ... | ... |