binda 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +42 -26
  3. data/Rakefile +4 -0
  4. data/app/assets/javascripts/binda/application.js +3 -0
  5. data/app/assets/javascripts/binda/components/field_setting_choices.js +5 -5
  6. data/app/assets/javascripts/binda/components/fileupload.js +1 -1
  7. data/app/assets/javascripts/binda/components/form_item_collapsable.js +245 -0
  8. data/app/assets/javascripts/binda/components/form_item_image.js +1 -1
  9. data/app/assets/javascripts/binda/components/login-shader.js +1 -1
  10. data/app/assets/javascripts/binda/components/login_form.js +1 -1
  11. data/app/assets/javascripts/binda/components/sortable.js +21 -30
  12. data/app/assets/javascripts/binda/dist/binda.bundle.js +299 -377
  13. data/app/assets/javascripts/binda/index.js +9 -17
  14. data/app/assets/stylesheets/binda/components/field_setting_choices.scss +5 -3
  15. data/app/assets/stylesheets/binda/components/form_item.scss +23 -70
  16. data/app/assets/stylesheets/binda/components/standard-form.scss +56 -1
  17. data/app/assets/stylesheets/binda/settings/tiny_mce_overrides.scss +8 -0
  18. data/app/controllers/binda/application_controller.rb +6 -0
  19. data/app/controllers/binda/boards_controller.rb +18 -12
  20. data/app/controllers/binda/components_controller.rb +21 -16
  21. data/app/controllers/binda/field_groups_controller.rb +57 -16
  22. data/app/controllers/binda/field_settings_controller.rb +14 -2
  23. data/app/controllers/binda/repeaters_controller.rb +2 -2
  24. data/app/controllers/binda/structures_controller.rb +32 -16
  25. data/app/controllers/concerns/binda/fieldable_helpers.rb +2 -2
  26. data/app/helpers/binda/components_helper.rb +16 -4
  27. data/app/helpers/binda/field_groups_helper.rb +4 -1
  28. data/app/models/binda/asset.rb +1 -3
  29. data/app/models/binda/component.rb +17 -10
  30. data/app/models/binda/date.rb +2 -6
  31. data/app/models/binda/field_group.rb +7 -5
  32. data/app/models/binda/field_setting.rb +69 -8
  33. data/app/models/binda/gallery.rb +2 -3
  34. data/app/models/binda/image.rb +7 -1
  35. data/app/models/binda/relation.rb +2 -5
  36. data/app/models/binda/repeater.rb +10 -14
  37. data/app/models/binda/selection.rb +3 -3
  38. data/app/models/binda/structure.rb +9 -5
  39. data/app/models/binda/text.rb +2 -6
  40. data/app/models/binda/user.rb +10 -0
  41. data/app/models/concerns/binda/field_uniqueness.rb +31 -0
  42. data/app/models/concerns/binda/fieldable_association_helpers.rb +31 -11
  43. data/app/models/concerns/binda/fieldable_associations.rb +2 -3
  44. data/app/models/concerns/binda/fields.rb +21 -0
  45. data/app/views/binda/boards/edit.html.erb +2 -1
  46. data/app/views/binda/components/edit.html.erb +2 -1
  47. data/app/views/binda/field_groups/_form_body.html.erb +4 -42
  48. data/app/views/binda/field_groups/_form_header.html.erb +11 -0
  49. data/app/views/binda/field_groups/_form_item.html.erb +4 -6
  50. data/app/views/binda/field_groups/_form_new_item.html.erb +8 -0
  51. data/app/views/binda/field_groups/_form_section.html.erb +17 -27
  52. data/app/views/binda/field_groups/_form_section_repeater.html.erb +17 -18
  53. data/app/views/binda/field_groups/_form_sidebar.html.erb +13 -0
  54. data/app/views/binda/field_groups/form_item/_form_item_editor.html.erb +9 -5
  55. data/app/views/binda/field_groups/form_item/{_form_item_persisted_editor.html.erb → _form_item_editor_existing_item.html.erb} +1 -4
  56. data/app/views/binda/field_groups/form_item/_form_item_editor_new_item.html.erb +3 -0
  57. data/app/views/binda/field_groups/form_item/_form_item_header.html.erb +23 -24
  58. data/app/views/binda/field_groups/form_section/_form_section_header.html.erb +45 -0
  59. data/app/views/binda/field_groups/form_section/_form_section_list.html.erb +9 -0
  60. data/app/views/binda/fieldable/_form_body.html.erb +6 -1
  61. data/app/views/binda/fieldable/_form_item_date.html.erb +1 -1
  62. data/app/views/binda/fieldable/_form_item_image.html.erb +1 -1
  63. data/app/views/binda/fieldable/_form_item_new_repeater.html.erb +7 -21
  64. data/app/views/binda/fieldable/_form_item_relation.html.erb +7 -7
  65. data/app/views/binda/fieldable/_form_item_repeater.html.erb +9 -99
  66. data/app/views/binda/fieldable/_form_item_selections.html.erb +1 -1
  67. data/app/views/binda/fieldable/_form_item_string.html.erb +5 -12
  68. data/app/views/binda/fieldable/_form_item_text.html.erb +5 -13
  69. data/app/views/binda/fieldable/_form_item_video.html.erb +1 -1
  70. data/app/views/binda/fieldable/_form_section.html.erb +11 -10
  71. data/app/views/binda/fieldable/_form_section_repeater.html.erb +2 -3
  72. data/app/views/binda/fieldable/form_item_repeater/_form_item_repeater_header.html.erb +32 -0
  73. data/app/views/binda/fieldable/form_item_repeater/_form_item_repeater_list.html.erb +19 -0
  74. data/app/views/binda/fieldable/form_item_repeater/_form_item_repeater_list_item.html.erb +42 -0
  75. data/app/views/binda/structures/_form_field_group_item.html.erb +5 -0
  76. data/app/views/binda/structures/_form_new_field_group_item.html.erb +8 -0
  77. data/app/views/binda/structures/_form_section.html.erb +16 -78
  78. data/app/views/binda/structures/_form_sidebar.html.erb +9 -8
  79. data/app/views/binda/structures/form_field_group_item/_form_field_group_item_editor.html.erb +18 -0
  80. data/app/views/binda/structures/form_field_group_item/_form_field_group_item_field_setting_form.html.erb +12 -0
  81. data/app/views/binda/structures/form_field_group_item/_form_field_group_item_field_settings.html.erb +18 -0
  82. data/app/views/binda/structures/form_field_group_item/_form_field_group_item_header.html.erb +29 -0
  83. data/app/views/binda/structures/form_section/_form_section_header.html.erb +26 -0
  84. data/app/views/binda/structures/form_section/_form_section_list.html.erb +8 -0
  85. data/config/initializers/simple_form__fileupload.rb +3 -4
  86. data/config/locales/en.yml +9 -4
  87. data/config/routes.rb +4 -0
  88. data/lib/binda/version.rb +1 -1
  89. data/lib/generators/binda/install/install_generator.rb +17 -6
  90. data/lib/generators/binda/maintenance/maintenance_generator.rb +12 -8
  91. data/lib/generators/binda/setup/setup_generator.rb +23 -27
  92. data/lib/tasks/create_missing_field_instances_task.rake +10 -0
  93. data/lib/tasks/remove_orphan_fields_task.rake +1 -7
  94. data/lib/tasks/user_tasks.rake +1 -7
  95. data/vendor/assets/javascripts/lodash.js +17097 -0
  96. metadata +39 -7
  97. data/app/assets/javascripts/binda/components/form_item.js +0 -113
  98. data/app/assets/javascripts/binda/components/form_item_editor.js +0 -39
  99. data/app/assets/javascripts/binda/components/form_item_repeater.js +0 -127
  100. data/app/views/binda/field_groups/form_item/_form_item_new_editor.html.erb +0 -8
@@ -2,11 +2,9 @@
2
2
  /// INDEX OF BINDA'S SCRIPTS
3
3
  ///- - - - - - - - - - - - - - - - - - - -
4
4
 
5
- import { _FormItem } from "./components/form_item";
6
- import { _FormItemRepeater } from "./components/form_item_repeater";
5
+ import { _FormItemCollapsable } from "./components/form_item_collapsable";
7
6
  import { _FormItemImage } from "./components/form_item_image";
8
7
  import { _FieldSettingChoices } from "./components/field_setting_choices";
9
- import { _FormItemEditor } from "./components/form_item_editor";
10
8
  import { _FileUpload } from "./components/fileupload";
11
9
  import { _Shader } from "./components/login-shader";
12
10
  import { _LoginForm } from "./components/login_form";
@@ -17,28 +15,22 @@ import setupSelect2 from "./components/select2";
17
15
  import setupRadioToggle from "./components/radio-toggle";
18
16
 
19
17
  $(document).ready(function() {
20
- if (_FormItem.isSet()) {
21
- _FormItem.setEvents();
18
+ if (_FormItemCollapsable.isPresent()) {
19
+ _FormItemCollapsable.setEvents();
22
20
  }
23
- if (_FormItemRepeater.isSet()) {
24
- _FormItemRepeater.setEvents();
25
- }
26
- if (_FormItemImage.isSet()) {
21
+ if (_FormItemImage.isPresent()) {
27
22
  _FormItemImage.setEvents();
28
23
  }
29
- if (_FieldSettingChoices.isSet()) {
24
+ if (_FieldSettingChoices.isPresent()) {
30
25
  _FieldSettingChoices.setEvents();
31
26
  }
32
- if (_FormItemEditor.isSet()) {
33
- _FormItemEditor.setEvents();
34
- }
35
- if (_FileUpload.isSet()) {
27
+ if (_FileUpload.isPresent()) {
36
28
  _FileUpload.setEvents();
37
29
  }
38
- if (_LoginForm.isSet()) {
30
+ if (_LoginForm.isPresent()) {
39
31
  _LoginForm.init();
40
32
  }
41
- if (_Shader.isSet()) {
33
+ if (_Shader.isPresent()) {
42
34
  _Shader.setup();
43
35
  _Shader.start();
44
36
  }
@@ -51,7 +43,7 @@ $(document).ready(function() {
51
43
 
52
44
  // handle event
53
45
  window.addEventListener("optimizedResize", function() {
54
- if (_Shader.isSet()) {
46
+ if (_Shader.isPresent()) {
55
47
  _Shader.resize();
56
48
  }
57
49
  });
@@ -20,7 +20,10 @@
20
20
  }
21
21
 
22
22
  .field-setting-choices--add-choice {
23
- float: right;
23
+ position: absolute;
24
+ top: 0;
25
+ right: 0;
26
+ padding: 20px 12px 8px;
24
27
  }
25
28
 
26
29
  .form-item--editor .field-setting-choices--choice {
@@ -48,8 +51,7 @@
48
51
 
49
52
  .field-setting-choices--choice-label,
50
53
  .field-setting-choices--choice-value {
51
- position: relative;
52
- float: left;
54
+ display: inline-block;
53
55
  width: calc(50% - 16px);
54
56
  }
55
57
 
@@ -1,8 +1,6 @@
1
1
  .form-item {
2
2
  display: block;
3
3
  overflow-y: hidden;
4
- transition: max-height 0.6s ease-out;
5
-
6
4
  @extend .clearfix;
7
5
  }
8
6
 
@@ -11,9 +9,16 @@
11
9
  }
12
10
 
13
11
  .form-item--half-size {
14
- position: relative;
15
- float: left;
16
- width: 50%;
12
+ // OLD SOLUTION (nicer, but can't be used with max-height=0 and overflow-y=hidden)
13
+ // position: relative;
14
+ // float: left;
15
+ // width: 50%;
16
+
17
+ // NEW SOLUTION (not very nice but plays nicely with max-height=0 and overflow-y=hidden)
18
+ display: inline-block;
19
+ width: calc(
20
+ 50% - 3px
21
+ ); // 5px are for the weird issue caused by non-blank space around the inline-block elements
17
22
 
18
23
  .form-group {
19
24
  border-top: none !important;
@@ -33,12 +38,6 @@
33
38
  }
34
39
  }
35
40
 
36
- .form-item--new {
37
- position: absolute;
38
- top: 101%;
39
- max-height: 0;
40
- }
41
-
42
41
  .form-item--select-input {
43
42
  display: inline-block;
44
43
  width: 240px;
@@ -78,14 +77,6 @@
78
77
  transform: rotate(180deg);
79
78
  }
80
79
 
81
- .form-item--editor {
82
- overflow-y: hidden;
83
- // max-height: is set via javascript in app/assets/javscripts/components/form_item_editor.js;
84
- // default is 0
85
- max-height: 0;
86
- transition: max-height 0.6s ease-out;
87
- }
88
-
89
80
  .form-item--repeater {
90
81
  overflow-y: hidden;
91
82
  // inherit transitions from sortable and add max-height
@@ -102,64 +93,26 @@
102
93
  transition: all 0.6s ease;
103
94
  }
104
95
 
105
- .font-item--collapsed .form-item--repeater-fields {
106
- margin-bottom: 8px;
96
+ .form-item--collapsable {
97
+ transition: box-shadow 0.3s ease, transform 0.3s ease,
98
+ max-height 0.6s ease-out !important;
107
99
  }
108
100
 
109
- .form-item--repeater-section {
110
- overflow-x: hidden; // This ensure that the div doesn't become wider when draggin sortable items to the right
111
- margin-bottom: 0;
112
-
113
- ul.sortable > li {
114
- margin-top: -1px; // this solve the issue of a 2px border between 2 repeaters
115
- border: 1px solid $color-gray-lighter;
116
- }
117
-
118
- ul.sortable > li > * {
119
- display: block;
120
- }
121
-
122
- ul.sortable {
123
- margin-bottom: 0;
124
- }
125
-
126
- .ui-sortable-handle:hover {
127
- background-color: transparent;
128
- }
129
-
130
- .sortable--toggle-text:first-child {
131
- display: none;
132
- }
133
-
134
- .sortable--toggle {
135
- float: right;
136
- margin: 0;
137
- }
138
-
139
- .sortable--enabled {
140
- .form-item--repeater-fields {
141
- margin: 0;
142
- padding: 0;
143
- }
144
- }
101
+ .form-item--collapsable-stack {
102
+ transition: box-shadow 0.3s ease, transform 0.3s ease,
103
+ max-height 0.6s ease-out !important;
104
+ }
145
105
 
146
- .form-item--new {
147
- position: absolute;
148
- top: 101%;
149
- max-height: 0;
150
- }
106
+ .font-item--collapsed .form-item--collapsable-stack {
107
+ margin-bottom: 8px;
151
108
  }
152
109
 
153
- .form-item--repeater-section--add-new {
110
+ .form--add-list-item {
154
111
  float: right;
155
112
  margin-right: 8px;
156
113
  }
157
114
 
158
- .form-item--repeater-title {
159
- display: block;
160
- margin-bottom: 20px;
161
- }
162
-
115
+ .standard-form--secondary-header,
163
116
  .form-item--header {
164
117
  padding: 4px;
165
118
  background-color: $color-gray-lightest !important;
@@ -171,14 +124,14 @@
171
124
  }
172
125
 
173
126
  .form-item--collapse-btn,
174
- .form-item--delete-repeater-item,
127
+ .form--delete-list-item,
175
128
  .form-item--edit-item {
176
129
  float: right;
177
130
  margin-top: 8px;
178
131
  margin-right: 8px;
179
132
  }
180
133
 
181
- a.form-item--delete-repeater-item {
134
+ a.form--delete-list-item {
182
135
  cursor: pointer;
183
136
  transition: color 0.2s ease;
184
137
  color: $color-danger;
@@ -178,5 +178,60 @@
178
178
  padding: 12px;
179
179
  }
180
180
 
181
- .standard-form--choices-header {
181
+ .standard-form--repeater,
182
+ .standard-form--field-group,
183
+ .standard-form--structure {
184
+ overflow-x: hidden; // This ensure that the div doesn't become wider when draggin sortable items to the right
185
+ margin-bottom: 0;
186
+
187
+ ul.sortable > li {
188
+ margin-top: -1px; // this solve the issue of a 2px border between 2 repeaters
189
+ border: 1px solid $color-gray-lighter;
190
+ }
191
+
192
+ ul.sortable > li > * {
193
+ display: block;
194
+ }
195
+
196
+ ul.sortable {
197
+ margin-bottom: 0;
198
+ }
199
+
200
+ .ui-sortable-handle:hover {
201
+ background-color: transparent;
202
+ }
203
+
204
+ .sortable--toggle-text:first-child {
205
+ display: none;
206
+ }
207
+
208
+ .sortable--toggle {
209
+ float: right;
210
+ margin: 0;
211
+ }
212
+
213
+ .sortable--enabled {
214
+ .form-item--collapsable-stack {
215
+ margin: 0;
216
+ padding: 0;
217
+ max-height: 0;
218
+ }
219
+ }
182
220
  }
221
+
222
+ .standard-form--fields-list {
223
+
224
+ .form-group {
225
+ padding: 0px 12px 28px ;
226
+ }
227
+
228
+ > .form-group {
229
+ padding-top: 20px;
230
+ padding-bottom: 0;
231
+ }
232
+ }
233
+
234
+ .standard-form--field-group-description {
235
+ color: $color-gray;
236
+ padding: 20px 12px 28px ;
237
+ }
@@ -70,6 +70,14 @@ body#tinymce {
70
70
 
71
71
  .mce-btn {
72
72
  background-color: $color-gray-lightest !important;
73
+
74
+ &.mce-active {
75
+ background-color: $color-gray !important;
76
+ }
77
+ }
78
+
79
+ .mce-primary {
80
+ background-color: $color-gray !important;
73
81
  }
74
82
 
75
83
  .mce-btn:hover,
@@ -4,6 +4,7 @@ module Binda
4
4
  protect_from_forgery with: :exception
5
5
 
6
6
  before_action :authenticate_user!
7
+ before_action :set_locale
7
8
 
8
9
  # _ indicates that we are not using the argument in the method
9
10
  def after_sign_in_path_for(_)
@@ -15,5 +16,10 @@ module Binda
15
16
  root_path
16
17
  end
17
18
 
19
+ # Avoid inheriting locale from application on which Binda is mounted
20
+ def set_locale
21
+ I18n.locale = I18n.default_locale
22
+ end
23
+
18
24
  end
19
25
  end
@@ -16,7 +16,7 @@ module Binda
16
16
 
17
17
  def update
18
18
  if @board.update(board_params)
19
- redirect_to structure_board_path( @structure.slug, @board.slug ), notice: 'Setting was successfully updated.'
19
+ redirect_to structure_board_path(@structure.slug, @board.slug), notice: 'Setting was successfully updated.'
20
20
  else
21
21
  render :edit, flash: { alert: @board.errors }
22
22
  end
@@ -28,17 +28,14 @@ module Binda
28
28
  end
29
29
 
30
30
  def new_repeater
31
- @repeater_setting = FieldSetting.find( params[:repeater_setting_id] )
32
- position = @instance.repeaters.find_all{|r| r.field_setting_id = @repeater_setting.id }.length + 1
33
- @repeater = @instance.repeaters.create( field_setting: @repeater_setting, position: position )
31
+ @repeater_setting = FieldSetting.find(params[:repeater_setting_id])
32
+ @repeater = @instance.repeaters.create(field_setting: @repeater_setting)
34
33
  render 'binda/fieldable/_form_item_new_repeater', layout: false
35
34
  end
36
35
 
37
36
  def sort_repeaters
38
- params[:repeater].each_with_index do |id, i|
39
- Repeater.find( id ).update({ position: i + 1 })
40
- end
41
- head :ok
37
+ sort_repeaters_by(params["form--list-item"])
38
+ render json: { id: "##{params[:id]}" }, status: 200
42
39
  end
43
40
 
44
41
  def dashboard
@@ -49,7 +46,7 @@ module Binda
49
46
  end
50
47
 
51
48
  def upload
52
- if @board.update( upload_params(:board) )
49
+ if @board.update(upload_params(:board))
53
50
  respond_to do |format|
54
51
  format.json { render json: upload_details }
55
52
  end
@@ -75,10 +72,19 @@ module Binda
75
72
 
76
73
  # Only allow a trusted parameter "white list" through.
77
74
  def board_params
78
- params.require(:board).permit(
79
- :name, :slug, :position, :structure_id,
75
+ params.require(:board).permit(
76
+ :id, :name, :slug, :position, :structure_id,
80
77
  { structure_attributes: [ :id ] },
81
- *fieldable_params )
78
+ *fieldable_params)
79
+ end
80
+
81
+ # Sort repeaters following the order with which are listed in the array provided as a argument.
82
+ #
83
+ # @param repeaters [Array] the list of ids of the repeaters
84
+ def sort_repeaters_by(repeaters)
85
+ repeaters.each_with_index do |id, i|
86
+ Repeater.find(id).update_column('position', i+1)
87
+ end
82
88
  end
83
89
  end
84
90
  end
@@ -35,7 +35,7 @@ module Binda
35
35
  @component = @structure.components.build(component_params)
36
36
 
37
37
  if @component.save
38
- redirect_to structure_component_path( @structure.slug, @component.slug ), notice: "#{ @structure.name } was successfully created."
38
+ redirect_to structure_component_path(@structure.slug, @component.slug), notice: "#{ @structure.name } was successfully created."
39
39
  else
40
40
  @instance = @component
41
41
  render :edit, flash: { alert: @component.errors }
@@ -44,7 +44,7 @@ module Binda
44
44
 
45
45
  def update
46
46
  if @component.update(component_params)
47
- redirect_to structure_component_path( @structure.slug, @component.slug ), notice: "#{ @structure.name.capitalize } was successfully updated."
47
+ redirect_to structure_component_path(@structure.slug, @component.slug), notice: "#{ @structure.name.capitalize } was successfully updated."
48
48
  else
49
49
  render :edit, flash: { alert: @component.errors }
50
50
  end
@@ -52,26 +52,23 @@ module Binda
52
52
 
53
53
  def destroy
54
54
  @component.destroy
55
- redirect_to structure_components_url( @structure.slug ), notice: "#{ @structure.name.capitalize } was successfully destroyed."
55
+ redirect_to structure_components_url(@structure.slug), notice: "#{ @structure.name.capitalize } was successfully destroyed."
56
56
  end
57
57
 
58
58
  def new_repeater
59
- @repeater_setting = FieldSetting.find( params[:repeater_setting_id] )
60
- position = @instance.repeaters.find_all{|r| r.field_setting_id=@repeater_setting.id }.length + 1
61
- @repeater = @instance.repeaters.create( field_setting: @repeater_setting, position: position )
59
+ @repeater_setting = FieldSetting.find(params[:repeater_setting_id])
60
+ @repeater = @instance.repeaters.create!(field_setting: @repeater_setting)
62
61
  render 'binda/fieldable/_form_item_new_repeater', layout: false
63
62
  end
64
63
 
65
- def sort_repeaters
66
- params[:repeater].each_with_index do |id, i|
67
- Repeater.find( id ).update({ position: i + 1 })
68
- end
64
+ def sort_repeaters
65
+ sort_repeaters_by(params["form--list-item"])
69
66
  render json: { id: "##{params[:id]}" }, status: 200
70
67
  end
71
68
 
72
69
  def sort
73
70
  params[:component].each_with_index do |id, i|
74
- Component.find( id ).update_column('position', i + 1) # use update_column to skip callbacks (which leads to huge useless memory consumption)
71
+ Component.find(id).update_column('position', i+1) # use update_column to skip callbacks (which leads to huge useless memory consumption)
75
72
  end
76
73
  render json: { id: "##{params[:id]}" }, status: 200
77
74
  end
@@ -82,7 +79,7 @@ module Binda
82
79
  end
83
80
 
84
81
  def upload
85
- if @component.update( upload_params(:component) )
82
+ if @component.update(upload_params(:component))
86
83
  render json: upload_details, status: 200
87
84
  else
88
85
  render json: @component.errors.full_messages, status: 400
@@ -92,7 +89,7 @@ module Binda
92
89
  private
93
90
  # Use callbacks to share common setup or constraints between actions.
94
91
  def set_structure
95
- @structure = Structure.friendly.find( params[:structure_id] )
92
+ @structure = Structure.friendly.find(params[:structure_id])
96
93
  end
97
94
 
98
95
  def set_component
@@ -105,12 +102,20 @@ module Binda
105
102
 
106
103
  # Only allow a trusted parameter "white list" through.
107
104
  def component_params
108
- params.require(:component).permit(
109
- :name, :slug, :position, :publish_state, :structure_id, :category_ids,
105
+ params.require(:component).permit(
106
+ :id, :name, :slug, :position, :publish_state, :structure_id, :category_ids,
110
107
  {structure_attributes: [ :id ]},
111
- {categories_attributes: [ :id, :category_id ]}, *fieldable_params )
108
+ {categories_attributes: [ :id, :category_id ]}, *fieldable_params)
112
109
  end
113
110
 
111
+ # Sort repeaters following the order with which are listed in the array provided as a argument.
112
+ #
113
+ # @param repeaters [Array] the list of ids of the repeaters
114
+ def sort_repeaters_by(repeaters)
115
+ repeaters.each_with_index do |id, i|
116
+ Repeater.find(id).update_column('position', i)
117
+ end
118
+ end
114
119
  end
115
120
  end
116
121