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,6 +19,8 @@ class CustomFormsPluginMyprofileController < MyProfileController | ||
19 | params[:form][:profile_id] = profile.id | 19 | params[:form][:profile_id] = profile.id |
20 | @form = CustomFormsPlugin::Form.new(params[:form]) | 20 | @form = CustomFormsPlugin::Form.new(params[:form]) |
21 | 21 | ||
22 | + normalize_positions(@form) | ||
23 | + | ||
22 | respond_to do |format| | 24 | respond_to do |format| |
23 | if @form.save | 25 | if @form.save |
24 | flash[:notice] = _("Custom form #{@form.name} was successfully created.") | 26 | flash[:notice] = _("Custom form #{@form.name} was successfully created.") |
@@ -35,9 +37,12 @@ class CustomFormsPluginMyprofileController < MyProfileController | @@ -35,9 +37,12 @@ class CustomFormsPluginMyprofileController < MyProfileController | ||
35 | 37 | ||
36 | def update | 38 | def update |
37 | @form = CustomFormsPlugin::Form.find(params[:id]) | 39 | @form = CustomFormsPlugin::Form.find(params[:id]) |
40 | + @form.attributes = params[:form] | ||
41 | + | ||
42 | + normalize_positions(@form) | ||
38 | 43 | ||
39 | respond_to do |format| | 44 | respond_to do |format| |
40 | - if @form.update_attributes(params[:form]) | 45 | + if @form.save |
41 | flash[:notice] = _("Custom form #{@form.name} was successfully updated.") | 46 | flash[:notice] = _("Custom form #{@form.name} was successfully updated.") |
42 | format.html { redirect_to(:action=>'index') } | 47 | format.html { redirect_to(:action=>'index') } |
43 | else | 48 | else |
@@ -84,4 +89,21 @@ class CustomFormsPluginMyprofileController < MyProfileController | @@ -84,4 +89,21 @@ class CustomFormsPluginMyprofileController < MyProfileController | ||
84 | @form = @submission.form | 89 | @form = @submission.form |
85 | end | 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 | end | 109 | end |
plugins/custom_forms/db/migrate/20131107050913_add_position_to_field_and_alternatives.rb
0 → 100644
@@ -0,0 +1,31 @@ | @@ -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,7 +6,7 @@ class CustomFormsPlugin::Field < ActiveRecord::Base | ||
6 | belongs_to :form, :class_name => 'CustomFormsPlugin::Form' | 6 | belongs_to :form, :class_name => 'CustomFormsPlugin::Form' |
7 | has_many :answers, :class_name => 'CustomFormsPlugin::Answer' | 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 | accepts_nested_attributes_for :alternatives, :allow_destroy => true | 10 | accepts_nested_attributes_for :alternatives, :allow_destroy => true |
11 | 11 | ||
12 | before_validation do |field| | 12 | before_validation do |field| |
plugins/custom_forms/lib/custom_forms_plugin/form.rb
1 | class CustomFormsPlugin::Form < Noosfero::Plugin::ActiveRecord | 1 | class CustomFormsPlugin::Form < Noosfero::Plugin::ActiveRecord |
2 | belongs_to :profile | 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 | accepts_nested_attributes_for :fields, :allow_destroy => true | 5 | accepts_nested_attributes_for :fields, :allow_destroy => true |
6 | 6 | ||
7 | has_many :submissions, :class_name => 'CustomFormsPlugin::Submission' | 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 | var customFormsPlugin = { | 38 | var customFormsPlugin = { |
2 | removeFieldBox: function (button, confirmMsg) { | 39 | removeFieldBox: function (button, confirmMsg) { |
3 | if (confirm(confirmMsg)) { | 40 | if (confirm(confirmMsg)) { |
@@ -20,11 +57,17 @@ var customFormsPlugin = { | @@ -20,11 +57,17 @@ var customFormsPlugin = { | ||
20 | addFields: function (button, association, content) { | 57 | addFields: function (button, association, content) { |
21 | var new_id = new Date().getTime(); | 58 | var new_id = new Date().getTime(); |
22 | var regexp = new RegExp("new_" + association, "g"); | 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 | if(association == 'alternatives') { | 62 | if(association == 'alternatives') { |
63 | + jQuery(content).appendTo(jQuery(button).closest('tfoot').next('tbody.field-list')).hide().slideDown(); | ||
26 | jQuery(button).closest('table').find('tr:first').show(); | 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 | checkHeaderDisplay: function(table) { | 73 | checkHeaderDisplay: function(table) { |
plugins/custom_forms/public/style.css
@@ -4,7 +4,7 @@ | @@ -4,7 +4,7 @@ | ||
4 | } | 4 | } |
5 | 5 | ||
6 | .action-table { | 6 | .action-table { |
7 | - width: 100%; | 7 | + width: 100%; |
8 | overflow: hidden; | 8 | overflow: hidden; |
9 | } | 9 | } |
10 | 10 | ||
@@ -13,10 +13,6 @@ | @@ -13,10 +13,6 @@ | ||
13 | text-align: center; | 13 | text-align: center; |
14 | } | 14 | } |
15 | 15 | ||
16 | -.action-table td{ | ||
17 | - cursor: move; | ||
18 | -} | ||
19 | - | ||
20 | .action-table .actions{ | 16 | .action-table .actions{ |
21 | white-space: nowrap; | 17 | white-space: nowrap; |
22 | } | 18 | } |
@@ -55,3 +51,22 @@ | @@ -55,3 +51,22 @@ | ||
55 | .field-text-default { | 51 | .field-text-default { |
56 | margin-top: 10px; | 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,8 +76,8 @@ class CustomFormsPluginMyprofileControllerTest < ActionController::TestCase | ||
76 | assert_equal 'Cool form', form.description | 76 | assert_equal 'Cool form', form.description |
77 | assert_equal 2, form.fields.count | 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 | assert_equal 'Name', f1.name | 82 | assert_equal 'Name', f1.name |
83 | assert_equal 'Jack', f1.default_value | 83 | assert_equal 'Jack', f1.default_value |
@@ -89,15 +89,15 @@ class CustomFormsPluginMyprofileControllerTest < ActionController::TestCase | @@ -89,15 +89,15 @@ class CustomFormsPluginMyprofileControllerTest < ActionController::TestCase | ||
89 | assert f2.kind_of?(CustomFormsPlugin::SelectField) | 89 | assert f2.kind_of?(CustomFormsPlugin::SelectField) |
90 | end | 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 | format = '%Y-%m-%d %H:%M' | 93 | format = '%Y-%m-%d %H:%M' |
94 | num_fields = 10 | 94 | num_fields = 10 |
95 | begining = Time.now.strftime(format) | 95 | begining = Time.now.strftime(format) |
96 | ending = (Time.now + 1.day).strftime(format) | 96 | ending = (Time.now + 1.day).strftime(format) |
97 | fields = {} | 97 | fields = {} |
98 | num_fields.times do |i| | 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 | :default_value => '', | 101 | :default_value => '', |
102 | :type => 'CustomFormsPlugin::TextField' | 102 | :type => 'CustomFormsPlugin::TextField' |
103 | } | 103 | } |
@@ -115,13 +115,47 @@ class CustomFormsPluginMyprofileControllerTest < ActionController::TestCase | @@ -115,13 +115,47 @@ class CustomFormsPluginMyprofileControllerTest < ActionController::TestCase | ||
115 | end | 115 | end |
116 | form = CustomFormsPlugin::Form.find_by_name('My Form') | 116 | form = CustomFormsPlugin::Form.find_by_name('My Form') |
117 | assert_equal num_fields, form.fields.count | 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 | end | 122 | end |
123 | end | 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 | should 'edit a form' do | 159 | should 'edit a form' do |
126 | form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software') | 160 | form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software') |
127 | format = '%Y-%m-%d %H:%M' | 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,5 +32,13 @@ class CustomFormsPlugin::FieldTest < ActiveSupport::TestCase | ||
32 | end | 32 | end |
33 | assert_equal form.fields, [license_field] | 33 | assert_equal form.fields, [license_field] |
34 | end | 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 | end | 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,4 +180,12 @@ class CustomFormsPlugin::FormTest < ActiveSupport::TestCase | ||
180 | end | 180 | end |
181 | end | 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 | end | 191 | end |
plugins/custom_forms/views/custom_forms_plugin_myprofile/_field.html.erb
@@ -9,6 +9,8 @@ | @@ -9,6 +9,8 @@ | ||
9 | <%= f.check_box :mandatory %> | 9 | <%= f.check_box :mandatory %> |
10 | <%= f.label :mandatory, _('Mandatory') %> | 10 | <%= f.label :mandatory, _('Mandatory') %> |
11 | 11 | ||
12 | + <%= f.hidden_field(:position) %> | ||
13 | + | ||
12 | <%= f.hidden_field :_destroy, :class => 'destroy-field' %> | 14 | <%= f.hidden_field :_destroy, :class => 'destroy-field' %> |
13 | <%= button_to_function :delete, _('Remove field'), "customFormsPlugin.removeFieldBox(this, #{_('Are you sure you want to remove this field?').to_json})" %> | 15 | <%= button_to_function :delete, _('Remove field'), "customFormsPlugin.removeFieldBox(this, #{_('Are you sure you want to remove this field?').to_json})" %> |
14 | <%= yield %> | 16 | <%= yield %> |
plugins/custom_forms/views/custom_forms_plugin_myprofile/_form.html.erb
@@ -17,9 +17,13 @@ | @@ -17,9 +17,13 @@ | ||
17 | 17 | ||
18 | <h2><%= _('Fields') %></h2> | 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 | <div class="addition-buttons"> | 28 | <div class="addition-buttons"> |
25 | <%= button(:add, _('Add a new text field'), '#', :onclick => "customFormsPlugin.addFields(this, 'fields', #{html_for_field(f, :fields, CustomFormsPlugin::TextField).to_json}); return false")%> | 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,8 +3,10 @@ | ||
3 | 3 | ||
4 | <td> <%= f.check_box(:selected_by_default) %> </td> | 4 | <td> <%= f.check_box(:selected_by_default) %> </td> |
5 | 5 | ||
6 | + <%= f.hidden_field(:position) %> | ||
7 | + | ||
6 | <td> | 8 | <td> |
7 | <%= f.hidden_field :_destroy, :class => 'destroy-field' %> | 9 | <%= f.hidden_field :_destroy, :class => 'destroy-field' %> |
8 | <%= 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') %> | 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 | </td> | 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,20 +12,25 @@ | ||
12 | </div> | 12 | </div> |
13 | 13 | ||
14 | <table> | 14 | <table> |
15 | + <thead> | ||
15 | <tr <%='style="display:none"' if f.object.alternatives.empty? %>> | 16 | <tr <%='style="display:none"' if f.object.alternatives.empty? %>> |
16 | <th><%= _('Alternative') %></th> | 17 | <th><%= _('Alternative') %></th> |
17 | <th><%= _('Preselected') %></th> | 18 | <th><%= _('Preselected') %></th> |
18 | <th><%= _('Remove') %></th> | 19 | <th><%= _('Remove') %></th> |
19 | </tr> | 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 | <tr class="addition-buttons"> | 23 | <tr class="addition-buttons"> |
24 | <td colspan="3"> | 24 | <td colspan="3"> |
25 | <%= button(:add, _('Add a new alternative'), '#', :onclick => "customFormsPlugin.addFields(this, 'alternatives', #{html_for_field(f, :alternatives, CustomFormsPlugin::Alternative).to_json}); return false") %> | 25 | <%= button(:add, _('Add a new alternative'), '#', :onclick => "customFormsPlugin.addFields(this, 'alternatives', #{html_for_field(f, :alternatives, CustomFormsPlugin::Alternative).to_json}); return false") %> |
26 | </td> | 26 | </td> |
27 | </tr> | 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 | </table> | 34 | </table> |
29 | 35 | ||
30 | - | ||
31 | <% end %> | 36 | <% end %> |