fae-rails 2.1.0 → 3.0.0
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.
- checksums.yaml +4 -4
- data/README.md +5 -2
- data/app/assets/config/fae/manifest.js +2 -0
- data/app/assets/javascripts/fae/_contrast.js +50 -0
- data/app/assets/javascripts/fae/_deploy.js +198 -0
- data/app/assets/javascripts/fae/_modals.js +94 -0
- data/app/assets/javascripts/fae/application.js +7 -1
- data/app/assets/javascripts/fae/form/_ajax.js +17 -5
- data/app/assets/javascripts/fae/form/_filtering.js +34 -0
- data/app/assets/javascripts/fae/form/_form.js +5 -2
- data/app/assets/javascripts/fae/form/_form_manager.js +295 -0
- data/app/assets/javascripts/fae/form/_slugger.js.erb +2 -2
- data/app/assets/javascripts/fae/form/_validator.js +224 -55
- data/app/assets/javascripts/fae/form/drag_drop.js +109 -0
- data/app/assets/javascripts/fae/form/inputs/_select.js +10 -4
- data/app/assets/javascripts/fae/form/inputs/_text.js +23 -9
- data/app/assets/javascripts/fae/vendor/simplemde/codemirror-4.inline-attachment.js +95 -0
- data/app/assets/javascripts/fae/vendor/simplemde/inline-attachment.js +405 -0
- data/app/assets/stylesheets/fae/application.css +1 -0
- data/app/assets/stylesheets/fae/base.scss +7 -3
- data/app/assets/stylesheets/fae/globals/_tags.scss +1 -1
- data/app/assets/stylesheets/fae/globals/imports/_variables.scss +1 -0
- data/app/assets/stylesheets/fae/globals/layout/_base.scss +9 -4
- data/app/assets/stylesheets/fae/globals/layout/_content-header.scss +14 -4
- data/app/assets/stylesheets/fae/globals/legacy/_pre-1.3.scss +1 -1
- data/app/assets/stylesheets/fae/globals/navigation/_footer.scss +1 -1
- data/app/assets/stylesheets/fae/globals/navigation/_header.scss +3 -3
- data/app/assets/stylesheets/fae/globals/navigation/_multi-col-subnav.scss +50 -0
- data/app/assets/stylesheets/fae/globals/navigation/_sidenav.scss +2 -2
- data/app/assets/stylesheets/fae/globals/navigation/_utility.scss +1 -1
- data/app/assets/stylesheets/fae/modules/_buttons.scss +11 -0
- data/app/assets/stylesheets/fae/modules/_deploy.scss +25 -0
- data/app/assets/stylesheets/fae/modules/_errors-bar.scss +19 -0
- data/app/assets/stylesheets/fae/modules/_modal.scss +25 -0
- data/app/assets/stylesheets/fae/modules/_toggles.scss +39 -23
- data/app/assets/stylesheets/fae/modules/forms/_asset-actions.scss +1 -1
- data/app/assets/stylesheets/fae/modules/forms/_base.scss +14 -1
- data/app/assets/stylesheets/fae/modules/forms/_checkbox.scss +1 -1
- data/app/assets/stylesheets/fae/modules/forms/_date.scss +20 -4
- data/app/assets/stylesheets/fae/modules/forms/_form-manager.scss +82 -0
- data/app/assets/stylesheets/fae/modules/forms/_hints.scss +1 -1
- data/app/assets/stylesheets/fae/modules/forms/_label.scss +1 -1
- data/app/assets/stylesheets/fae/modules/forms/_radio.scss +1 -1
- data/app/assets/stylesheets/fae/modules/forms/_select.scss +9 -8
- data/app/assets/stylesheets/fae/modules/forms/_simple-mde.scss +72 -1
- data/app/assets/stylesheets/fae/modules/forms/_textarea.scss +1 -1
- data/app/assets/stylesheets/fae/modules/tables/_base.scss +1 -1
- data/app/assets/stylesheets/fae/modules/tables/_filters.scss +4 -0
- data/app/assets/stylesheets/fae/modules/tables/_pagination.scss +2 -2
- data/app/assets/stylesheets/fae/pages/_error.scss +1 -1
- data/app/assets/stylesheets/fae/pages/_login.scss +5 -1
- data/app/assets/stylesheets/fae/simplemde_override.scss +32 -0
- data/app/controllers/fae/application_controller.rb +9 -2
- data/app/controllers/fae/base_controller.rb +6 -2
- data/app/controllers/fae/deploy_controller.rb +24 -0
- data/app/controllers/fae/deploy_hooks_controller.rb +71 -0
- data/app/controllers/fae/form_managers_controller.rb +19 -0
- data/app/controllers/fae/options_controller.rb +1 -0
- data/app/controllers/fae/setup_controller.rb +2 -2
- data/app/controllers/fae/static_pages_controller.rb +6 -1
- data/app/controllers/fae/users_controller.rb +11 -1
- data/app/controllers/fae/utilities_controller.rb +18 -6
- data/app/helpers/fae/application_helper.rb +36 -2
- data/app/helpers/fae/form_helper.rb +26 -2
- data/app/helpers/fae/view_helper.rb +26 -9
- data/app/models/concerns/fae/base_model_concern.rb +17 -0
- data/app/models/concerns/fae/seo_set_concern.rb +1 -0
- data/app/models/fae/change.rb +20 -6
- data/app/models/fae/deploy_hook.rb +12 -0
- data/app/models/fae/form_manager.rb +24 -0
- data/app/models/fae/option.rb +1 -0
- data/app/models/fae/seo_set.rb +14 -0
- data/app/models/fae/user.rb +2 -2
- data/app/services/fae/netlify_api.rb +213 -0
- data/app/uploaders/fae/file_uploader.rb +1 -1
- data/app/uploaders/fae/image_uploader.rb +2 -2
- data/app/views/devise/unlocks/new.html.slim +5 -9
- data/app/views/fae/application/_content_form.html.slim +16 -11
- data/app/views/fae/application/_file_uploader.html.slim +7 -2
- data/app/views/fae/application/_global_search_results.html.slim +1 -1
- data/app/views/fae/application/_header.slim +4 -1
- data/app/views/fae/application/_mobilenav.slim +3 -0
- data/app/views/fae/application/_seo_set_form.html.slim +12 -0
- data/app/views/fae/deploy/index.html.slim +40 -0
- data/app/views/fae/deploy_hooks/_form.html.slim +18 -0
- data/app/views/fae/deploy_hooks/_table.html.slim +28 -0
- data/app/views/fae/deploy_hooks/edit.html.slim +3 -0
- data/app/views/fae/deploy_hooks/new.html.slim +3 -0
- data/app/views/fae/images/_image_uploader.html.slim +12 -3
- data/app/views/fae/options/_form.html.slim +6 -2
- data/app/views/fae/pages/activity_log.html.slim +7 -3
- data/app/views/fae/pages/home.html.slim +3 -4
- data/app/views/fae/shared/_errors.slim +0 -3
- data/app/views/fae/shared/_form_header.html.slim +20 -12
- data/app/views/fae/shared/_nested_table.html.slim +5 -2
- data/app/views/fae/shared/_recent_changes.html.slim +1 -1
- data/app/views/fae/shared/_shared_nested_table.html.slim +9 -3
- data/app/views/layouts/fae/application.html.slim +2 -1
- data/config/deploy.rb +3 -1
- data/config/initializers/carrierwave.rb +41 -2
- data/config/initializers/devise.rb +6 -6
- data/config/initializers/fae_judge.rb +4 -2
- data/config/locales/fae.en.yml +49 -4
- data/config/locales/fae.zh-CN.yml +2 -2
- data/config/puma.rb +82 -0
- data/config/routes.rb +9 -1
- data/db/migrate/20140809222030_add_user_table.rb +1 -1
- data/db/migrate/20190925153222_create_fae_form_managers.rb +11 -0
- data/db/migrate/20220118192729_create_fae_publish_hooks.rb +10 -0
- data/db/migrate/20220128133730_rename_publish_hooks.rb +5 -0
- data/db/migrate/20220202153607_add_position_to_deploy_hooks.rb +6 -0
- data/db/migrate/20221118161833_create_fae_seo_sets.rb +13 -0
- data/lib/fae/concerns/models/base.rb +2 -0
- data/lib/fae/engine.rb +3 -3
- data/lib/fae/options.rb +18 -18
- data/lib/fae/version.rb +1 -1
- data/lib/generators/fae/base_generator.rb +28 -5
- data/lib/generators/fae/controller_generator.rb +0 -1
- data/lib/generators/fae/install_generator.rb +1 -1
- data/lib/generators/fae/model_generator.rb +1 -2
- data/lib/generators/fae/nested_index_scaffold_generator.rb +1 -2
- data/lib/generators/fae/nested_scaffold_generator.rb +23 -2
- data/lib/generators/fae/page_generator.rb +1 -2
- data/lib/generators/fae/scaffold_generator.rb +1 -1
- data/lib/generators/fae/templates/assets/fae.js +1 -1
- data/lib/generators/fae/templates/controllers/nested_scaffold_controller.rb +13 -1
- data/lib/generators/fae/templates/controllers/scaffold_controller.rb +7 -0
- data/lib/generators/fae/templates/initializers/fae.rb +16 -1
- data/lib/generators/fae/templates/views/_form.html.slim +12 -1
- data/lib/generators/fae/templates/views/_form_index_nested.html.slim +15 -1
- data/lib/generators/fae/templates/views/_form_nested.html.slim +22 -2
- data/lib/generators/fae/templates/views/static_page_form.html.slim +13 -1
- metadata +53 -24
- data/config/deploy/dev.rb +0 -19
- data/config/deploy/prod.rb +0 -19
- data/config/deploy/stage.rb +0 -19
- /data/app/assets/javascripts/fae/vendor/{simplemde.min.js → simplemde/simplemde.min.js} +0 -0
@@ -6,11 +6,11 @@
|
|
6
6
|
* @memberof form
|
7
7
|
*/
|
8
8
|
Fae.form.validator = {
|
9
|
-
|
10
9
|
is_valid: '',
|
11
10
|
validations_called: 0,
|
12
11
|
validations_returned: 0,
|
13
12
|
validation_test_count: 0,
|
13
|
+
invalidFields: [],
|
14
14
|
|
15
15
|
init: function () {
|
16
16
|
// validate all forms except the login form
|
@@ -20,6 +20,7 @@ Fae.form.validator = {
|
|
20
20
|
this.bindValidationEvents();
|
21
21
|
this.formValidate();
|
22
22
|
this.length_counter.init();
|
23
|
+
this.checkForSsrImageAndFileErrors();
|
23
24
|
}
|
24
25
|
},
|
25
26
|
|
@@ -29,14 +30,13 @@ Fae.form.validator = {
|
|
29
30
|
formValidate: function ($scope) {
|
30
31
|
var _this = this;
|
31
32
|
|
32
|
-
if (typeof
|
33
|
+
if (typeof $scope === 'undefined') {
|
33
34
|
$scope = FCH.$document;
|
34
35
|
}
|
35
36
|
$scope.on('submit', 'form:not([data-remote=true])', function (e) {
|
36
37
|
var $this = $(this);
|
37
38
|
|
38
39
|
if ($this.data('passed_validation') !== 'true') {
|
39
|
-
|
40
40
|
// pause form submission
|
41
41
|
e.preventDefault();
|
42
42
|
|
@@ -63,8 +63,14 @@ Fae.form.validator = {
|
|
63
63
|
|
64
64
|
_this.testValidation($this, $scope);
|
65
65
|
|
66
|
+
} else if (_this._preventFormSaveDueToNestedForm()) {
|
67
|
+
// nested form has unsaved changes and user cancelled form submission
|
68
|
+
e.preventDefault();
|
69
|
+
|
70
|
+
} else {
|
71
|
+
// form is valid and can submit so set saving indication
|
72
|
+
_this._setSavingIndicator();
|
66
73
|
}
|
67
|
-
|
68
74
|
});
|
69
75
|
},
|
70
76
|
|
@@ -72,15 +78,13 @@ Fae.form.validator = {
|
|
72
78
|
* Tests a forms validation after all validation checks have responded
|
73
79
|
* Polls validations responses every 50ms to allow uniqueness AJAX calls to complete
|
74
80
|
*/
|
75
|
-
testValidation: function($this, $scope) {
|
81
|
+
testValidation: function ($this, $scope) {
|
76
82
|
var _this = this;
|
77
83
|
_this.validation_test_count++;
|
78
84
|
|
79
|
-
setTimeout(function(){
|
80
|
-
|
85
|
+
setTimeout(function () {
|
81
86
|
// if all the validation checks have returned a response
|
82
87
|
if (_this.validations_called === _this.validations_returned) {
|
83
|
-
|
84
88
|
if (_this.is_valid) {
|
85
89
|
// if form is valid, submit it
|
86
90
|
$this.data('passed_validation', 'true');
|
@@ -89,15 +93,17 @@ Fae.form.validator = {
|
|
89
93
|
} else {
|
90
94
|
// otherwise scroll to the top to display alerts (unless in a nested form scope)
|
91
95
|
Fae.navigation.language.checkForHiddenErrors();
|
92
|
-
if (typeof
|
96
|
+
if (typeof $scope === 'undefined') {
|
93
97
|
FCH.smoothScroll($('#js-main-header'), 500, 100, 0);
|
94
98
|
}
|
95
99
|
|
96
|
-
|
97
|
-
|
100
|
+
// display error messages grouped at top of form with jump links to invalid fields
|
101
|
+
_this._buildErrorLinks($scope);
|
102
|
+
|
103
|
+
if ($('.field_with_errors').length) {
|
104
|
+
$('.errors-bar-wrapper').slideDown('fast');
|
98
105
|
}
|
99
106
|
}
|
100
|
-
|
101
107
|
} else {
|
102
108
|
// check again if it hasn't run more than 50 times
|
103
109
|
// (to prevent against infinite loop)
|
@@ -105,9 +111,7 @@ Fae.form.validator = {
|
|
105
111
|
_this.testValidation($this);
|
106
112
|
}
|
107
113
|
}
|
108
|
-
|
109
114
|
}, 50);
|
110
|
-
|
111
115
|
},
|
112
116
|
|
113
117
|
/**
|
@@ -116,7 +120,7 @@ Fae.form.validator = {
|
|
116
120
|
bindValidationEvents: function ($scope) {
|
117
121
|
var _this = this;
|
118
122
|
|
119
|
-
if (typeof
|
123
|
+
if (typeof $scope === 'undefined') {
|
120
124
|
$scope = $('body');
|
121
125
|
}
|
122
126
|
|
@@ -129,13 +133,13 @@ Fae.form.validator = {
|
|
129
133
|
$this.blur(function () {
|
130
134
|
_this._judgeIt($this);
|
131
135
|
});
|
132
|
-
|
133
136
|
} else if ($this.hasClass('hasDatepicker')) {
|
134
137
|
// date pickers need a little delay
|
135
138
|
$this.blur(function () {
|
136
|
-
setTimeout(function
|
139
|
+
setTimeout(function () {
|
140
|
+
_this._judgeIt($this);
|
141
|
+
}, 500);
|
137
142
|
});
|
138
|
-
|
139
143
|
} else if ($this.is('select')) {
|
140
144
|
// selects validate on change
|
141
145
|
$this.change(function () {
|
@@ -146,6 +150,67 @@ Fae.form.validator = {
|
|
146
150
|
});
|
147
151
|
},
|
148
152
|
|
153
|
+
/**
|
154
|
+
* Sent indicator to let the user know the form is saving
|
155
|
+
* @protected
|
156
|
+
*/
|
157
|
+
_setSavingIndicator: function () {
|
158
|
+
var $saveButton = $('.js-content-header').find('input[type="submit"]');
|
159
|
+
$saveButton.addClass('saving').val('Saving...');
|
160
|
+
},
|
161
|
+
|
162
|
+
/**
|
163
|
+
* Detect if a nested form has content and alert user
|
164
|
+
* @protected
|
165
|
+
*/
|
166
|
+
_preventFormSaveDueToNestedForm: function () {
|
167
|
+
let preventSave = false;
|
168
|
+
const $nestedFormWrapper = $('.js-addedit-form-wrapper:visible');
|
169
|
+
// exit if no nested objects
|
170
|
+
if ($nestedFormWrapper.length === 0) return false;
|
171
|
+
|
172
|
+
const $form = $nestedFormWrapper.find('form');
|
173
|
+
|
174
|
+
// get all form values without hidden fields. this omits utf encoding, csrf token, and parent item id fields
|
175
|
+
const formValues = $form.find(':input:not(:hidden)').serializeArray();
|
176
|
+
|
177
|
+
// detect if any fields have content
|
178
|
+
const formHasContent = formValues.some((item) => item.value !== '');
|
179
|
+
|
180
|
+
if (formHasContent) {
|
181
|
+
const formLabel = $nestedFormWrapper.siblings('h2').text();
|
182
|
+
// set to true if user decides not to continue
|
183
|
+
preventSave = !window.confirm(
|
184
|
+
`${formLabel} has unsaved changes! To return to your draft, click “Cancel.” To proceed without saving, click “OK.”`
|
185
|
+
);
|
186
|
+
|
187
|
+
if (preventSave) {
|
188
|
+
FCH.smoothScroll($nestedFormWrapper, 500, 100, -100);
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
192
|
+
return preventSave;
|
193
|
+
},
|
194
|
+
|
195
|
+
// check for server side errors for images/files and display error bar with jump links on form re-render
|
196
|
+
checkForSsrImageAndFileErrors: function () {
|
197
|
+
const _this = this;
|
198
|
+
let imageErrorFound = false;
|
199
|
+
|
200
|
+
$('.input.file').each(function () {
|
201
|
+
$assetInput = $(this);
|
202
|
+
if ($assetInput.hasClass('field_with_errors')) {
|
203
|
+
const errorMessage = $assetInput.find('.error').text();
|
204
|
+
_this._addInvalidField($assetInput, [errorMessage]);
|
205
|
+
imageErrorFound = true;
|
206
|
+
}
|
207
|
+
});
|
208
|
+
|
209
|
+
if (imageErrorFound) {
|
210
|
+
this._buildErrorLinks();
|
211
|
+
$('.errors-bar-wrapper').slideDown('fast');
|
212
|
+
}
|
213
|
+
},
|
149
214
|
|
150
215
|
/**
|
151
216
|
* Initialize Judge on a field
|
@@ -159,6 +224,7 @@ Fae.form.validator = {
|
|
159
224
|
valid: function () {
|
160
225
|
_this.validations_returned++;
|
161
226
|
_this._createSuccessClass($input);
|
227
|
+
_this._clearInvalidField($input);
|
162
228
|
},
|
163
229
|
invalid: function (input, messages) {
|
164
230
|
_this.validations_returned++;
|
@@ -166,9 +232,95 @@ Fae.form.validator = {
|
|
166
232
|
if (messages.length) {
|
167
233
|
_this.is_valid = false;
|
168
234
|
_this._createOrReplaceError($input, messages);
|
235
|
+
_this._addInvalidField($input, messages);
|
169
236
|
}
|
170
|
-
}
|
237
|
+
},
|
238
|
+
});
|
239
|
+
},
|
240
|
+
|
241
|
+
/**
|
242
|
+
* Add error to invalid fields array if it doesn't already exist
|
243
|
+
* @protected
|
244
|
+
* @param {jQuery} $input - field to check if exists
|
245
|
+
*/
|
246
|
+
_addInvalidField: function ($input, messages) {
|
247
|
+
const foundIndex = this.invalidFields.findIndex((item) => {
|
248
|
+
return item.$input[0] === $input[0];
|
171
249
|
});
|
250
|
+
if (foundIndex === -1) {
|
251
|
+
this.invalidFields.push({
|
252
|
+
$input: $input,
|
253
|
+
messages: messages,
|
254
|
+
});
|
255
|
+
}
|
256
|
+
},
|
257
|
+
|
258
|
+
/**
|
259
|
+
* Remove error from invalid fields array if exists
|
260
|
+
* @protected
|
261
|
+
* @param {jQuery} $input - field to check if exists
|
262
|
+
*/
|
263
|
+
_clearInvalidField: function ($input) {
|
264
|
+
const foundIndex = this.invalidFields.findIndex((item) => {
|
265
|
+
return item.$input[0] === $input[0];
|
266
|
+
});
|
267
|
+
if (foundIndex !== -1) {
|
268
|
+
this.invalidFields.splice(foundIndex, 1);
|
269
|
+
}
|
270
|
+
},
|
271
|
+
|
272
|
+
/**
|
273
|
+
* Builds jump links for all invalid fields to display at top of form
|
274
|
+
* @protected
|
275
|
+
* @param {jQuery} $scope - form scope
|
276
|
+
*/
|
277
|
+
|
278
|
+
_buildErrorLinks: function ($scope) {
|
279
|
+
const $header = $('.js-content-header');
|
280
|
+
const headerHeight = $header[0].getBoundingClientRect().height;
|
281
|
+
|
282
|
+
if (typeof $scope === 'undefined') {
|
283
|
+
$scope = $('body');
|
284
|
+
}
|
285
|
+
|
286
|
+
// get all fields with non-empty validations
|
287
|
+
const fieldsWithValidation = Array.from(
|
288
|
+
$scope.find('[data-validate*="{"]')
|
289
|
+
);
|
290
|
+
|
291
|
+
// sort invalid fields to match ordering of fields within form
|
292
|
+
this.invalidFields.sort((a, b) => {
|
293
|
+
return (
|
294
|
+
fieldsWithValidation.indexOf(a.$input[0]) -
|
295
|
+
fieldsWithValidation.indexOf(b.$input[0])
|
296
|
+
);
|
297
|
+
});
|
298
|
+
|
299
|
+
// generate error links
|
300
|
+
const $errorLinks = this.invalidFields.map((field) => {
|
301
|
+
const $wrapper = field.$input.parents('div.input');
|
302
|
+
let label = $wrapper.find('.label_inner').text();
|
303
|
+
|
304
|
+
// build clean label with name of field and error message
|
305
|
+
label = `${label.replace('*', '')} - ${field.messages.join(', ')}`;
|
306
|
+
|
307
|
+
// build jump link
|
308
|
+
let $errorLink = $('<a/>', {
|
309
|
+
class: 'error-jump-link',
|
310
|
+
href: `#`,
|
311
|
+
html: label,
|
312
|
+
});
|
313
|
+
|
314
|
+
// smooth scroll invalid field right below header
|
315
|
+
$errorLink.click((e) => {
|
316
|
+
e.preventDefault;
|
317
|
+
FCH.smoothScroll($wrapper, 500, 100, headerHeight * -1);
|
318
|
+
});
|
319
|
+
return $errorLink;
|
320
|
+
});
|
321
|
+
|
322
|
+
// append all links to error bar in form_header partial
|
323
|
+
$('.errors-bar').empty().append($errorLinks);
|
172
324
|
},
|
173
325
|
|
174
326
|
/**
|
@@ -208,7 +360,11 @@ Fae.form.validator = {
|
|
208
360
|
var $styled_input = this._setTargetInput($input);
|
209
361
|
$styled_input.addClass('valid').removeClass('invalid');
|
210
362
|
|
211
|
-
$input
|
363
|
+
$input
|
364
|
+
.parent()
|
365
|
+
.removeClass('field_with_errors')
|
366
|
+
.children('.error')
|
367
|
+
.remove();
|
212
368
|
},
|
213
369
|
|
214
370
|
/**
|
@@ -225,7 +381,9 @@ Fae.form.validator = {
|
|
225
381
|
if ($wrapper.children('.error').length) {
|
226
382
|
$wrapper.children('.error').text(messages.join(', '));
|
227
383
|
} else {
|
228
|
-
$wrapper
|
384
|
+
$wrapper
|
385
|
+
.addClass('field_with_errors')
|
386
|
+
.append("<span class='error'>" + messages.join(', ') + '</span>');
|
229
387
|
}
|
230
388
|
},
|
231
389
|
|
@@ -239,17 +397,18 @@ Fae.form.validator = {
|
|
239
397
|
var $styled_input = $input;
|
240
398
|
|
241
399
|
// If field is a chosen input
|
242
|
-
if (
|
400
|
+
if ($input.next('.chosen-container').length) {
|
243
401
|
if ($input.next('.chosen-container').find('.chosen-single').length) {
|
244
402
|
$styled_input = $input.next('.chosen-container').find('.chosen-single');
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
403
|
+
} else if (
|
404
|
+
$input.next('.chosen-container').find('.chosen-choices').length
|
405
|
+
) {
|
406
|
+
$styled_input = $input
|
407
|
+
.next('.chosen-container')
|
408
|
+
.find('.chosen-choices');
|
249
409
|
}
|
250
410
|
} else if ($input.hasClass('mde-enabled')) {
|
251
411
|
$styled_input = $input.siblings('.editor-toolbar, .CodeMirror-wrap');
|
252
|
-
|
253
412
|
}
|
254
413
|
|
255
414
|
return $styled_input;
|
@@ -260,13 +419,16 @@ Fae.form.validator = {
|
|
260
419
|
* @param {jQuery} $field - Input fields
|
261
420
|
* @param {String} kind - Type of validation (e.g. 'presence' or 'confirmation')
|
262
421
|
*/
|
263
|
-
stripValidation: function($field, kind) {
|
422
|
+
stripValidation: function ($field, kind) {
|
264
423
|
var validations = $field.data('validate');
|
265
424
|
|
266
425
|
for (var i = 0; i < validations.length; i++) {
|
267
426
|
// validation items can be strings or JSON objects
|
268
427
|
// let's convert the strings to JSON so we're dealing with consistent types
|
269
|
-
if (
|
428
|
+
if (
|
429
|
+
typeof validations[i] == 'string' ||
|
430
|
+
validations[i] instanceof String
|
431
|
+
) {
|
270
432
|
validations[i] = JSON.parse(validations[i]);
|
271
433
|
}
|
272
434
|
|
@@ -289,14 +451,17 @@ Fae.form.validator = {
|
|
289
451
|
* @memberof! validator
|
290
452
|
*/
|
291
453
|
password_confirmation_validation: {
|
292
|
-
init: function() {
|
454
|
+
init: function () {
|
293
455
|
var _this = this;
|
294
456
|
|
295
457
|
_this.$password_field = $('#user_password');
|
296
458
|
_this.$password_confirmation_field = $('#user_password_confirmation');
|
297
459
|
|
298
460
|
if (_this.$password_confirmation_field.length) {
|
299
|
-
Fae.form.validator.stripValidation(
|
461
|
+
Fae.form.validator.stripValidation(
|
462
|
+
_this.$password_field,
|
463
|
+
'confirmation'
|
464
|
+
);
|
300
465
|
_this.addCustomValidation();
|
301
466
|
}
|
302
467
|
},
|
@@ -304,7 +469,7 @@ Fae.form.validator = {
|
|
304
469
|
/**
|
305
470
|
* Validate password on blur and form submit; halt form execution if invalid
|
306
471
|
*/
|
307
|
-
addCustomValidation: function() {
|
472
|
+
addCustomValidation: function () {
|
308
473
|
var _this = this;
|
309
474
|
|
310
475
|
/**
|
@@ -316,18 +481,24 @@ Fae.form.validator = {
|
|
316
481
|
function validateConfirmation() {
|
317
482
|
var validator = Fae.form.validator;
|
318
483
|
|
319
|
-
if (
|
484
|
+
if (
|
485
|
+
_this.$password_field.val() ===
|
486
|
+
_this.$password_confirmation_field.val()
|
487
|
+
) {
|
320
488
|
validator._createSuccessClass(_this.$password_confirmation_field);
|
321
489
|
} else {
|
322
490
|
var message = ['must match Password'];
|
323
491
|
validator.is_valid = false;
|
324
|
-
validator._createOrReplaceError(
|
492
|
+
validator._createOrReplaceError(
|
493
|
+
_this.$password_confirmation_field,
|
494
|
+
message
|
495
|
+
);
|
325
496
|
}
|
326
497
|
}
|
327
498
|
|
328
499
|
this.$password_confirmation_field.on('blur', validateConfirmation);
|
329
500
|
|
330
|
-
$('form').on('submit', function(ev) {
|
501
|
+
$('form').on('submit', function (ev) {
|
331
502
|
_this.is_valid = true;
|
332
503
|
validateConfirmation();
|
333
504
|
|
@@ -335,13 +506,13 @@ Fae.form.validator = {
|
|
335
506
|
ev.preventDefault();
|
336
507
|
}
|
337
508
|
});
|
338
|
-
}
|
509
|
+
},
|
339
510
|
},
|
340
511
|
|
341
512
|
/**
|
342
513
|
* Judge always read the `on: :create` validations, so we need to strip the password presence validation on the user edit form
|
343
514
|
*/
|
344
|
-
passwordPresenceConditional: function() {
|
515
|
+
passwordPresenceConditional: function () {
|
345
516
|
var $edit_user_password = $('.edit_user #user_password');
|
346
517
|
if ($edit_user_password.length) {
|
347
518
|
this.stripValidation($edit_user_password, 'presence');
|
@@ -354,25 +525,24 @@ Fae.form.validator = {
|
|
354
525
|
* @memberof! validator
|
355
526
|
*/
|
356
527
|
length_counter: {
|
357
|
-
|
358
|
-
init: function(){
|
528
|
+
init: function () {
|
359
529
|
this.findLengthValidations();
|
360
530
|
},
|
361
531
|
|
362
532
|
/**
|
363
533
|
* Add counter text to fields that validate based on character counts
|
364
534
|
*/
|
365
|
-
findLengthValidations: function() {
|
535
|
+
findLengthValidations: function () {
|
366
536
|
var _this = this;
|
367
537
|
|
368
538
|
$('[data-validate]').each(function () {
|
369
539
|
var $this = $(this);
|
370
540
|
|
371
|
-
if ($this.data('validate').length
|
541
|
+
if ($this.data('validate').length) {
|
372
542
|
var validations = $this.data('validate');
|
373
543
|
|
374
|
-
$.grep(validations, function(item){
|
375
|
-
if (item.kind === 'length'){
|
544
|
+
$.grep(validations, function (item) {
|
545
|
+
if (item.kind === 'length') {
|
376
546
|
$this.data('length-max', item.options.maximum);
|
377
547
|
_this._setupCounter($this);
|
378
548
|
}
|
@@ -386,17 +556,17 @@ Fae.form.validator = {
|
|
386
556
|
* @access protected
|
387
557
|
* @param {jQuery} $elem - Input field being counted
|
388
558
|
*/
|
389
|
-
_setupCounter: function($elem) {
|
559
|
+
_setupCounter: function ($elem) {
|
390
560
|
var _this = this;
|
391
561
|
|
392
562
|
_this._createCounterDiv($elem);
|
393
563
|
_this.updateCounter($elem);
|
394
564
|
|
395
565
|
$elem
|
396
|
-
.keyup(function() {
|
566
|
+
.keyup(function () {
|
397
567
|
_this.updateCounter($elem);
|
398
568
|
})
|
399
|
-
.keypress(function(e) {
|
569
|
+
.keypress(function (e) {
|
400
570
|
if (_this._charactersLeft($elem) <= 0) {
|
401
571
|
if (e.keyCode !== 8 && e.keyCode !== 46) {
|
402
572
|
e.preventDefault();
|
@@ -410,17 +580,17 @@ Fae.form.validator = {
|
|
410
580
|
* @protected
|
411
581
|
* @param {jQuery} $elem - Input field to evaluate
|
412
582
|
*/
|
413
|
-
_createCounterDiv: function($elem) {
|
583
|
+
_createCounterDiv: function ($elem) {
|
414
584
|
if ($elem.siblings('.counter').length === 0) {
|
415
|
-
var text =
|
585
|
+
var text = 'Maximum Characters: ' + $elem.data('length-max');
|
416
586
|
text += " / <span class='characters-left'></span>";
|
417
587
|
|
418
588
|
var $counter_div = $('<div />', {
|
419
589
|
class: 'counter',
|
420
|
-
html: '<p>' + text + '</p>'
|
590
|
+
html: '<p>' + text + '</p>',
|
421
591
|
});
|
422
592
|
|
423
|
-
$elem.parent().append(
|
593
|
+
$elem.parent().append($counter_div);
|
424
594
|
}
|
425
595
|
},
|
426
596
|
|
@@ -428,7 +598,7 @@ Fae.form.validator = {
|
|
428
598
|
* Updates the counter count and class
|
429
599
|
* @param {jQuery} $elem - Input field to evaluate
|
430
600
|
*/
|
431
|
-
updateCounter: function($elem) {
|
601
|
+
updateCounter: function ($elem) {
|
432
602
|
var $count_span = $elem.siblings('.counter').find('.characters-left');
|
433
603
|
if ($count_span.length) {
|
434
604
|
var current = this._charactersLeft($elem);
|
@@ -452,14 +622,13 @@ Fae.form.validator = {
|
|
452
622
|
* @param {jQuery} $elem - Input field being counted
|
453
623
|
* @return {integer} The number of characters left
|
454
624
|
*/
|
455
|
-
_charactersLeft: function($elem) {
|
625
|
+
_charactersLeft: function ($elem) {
|
456
626
|
var input_value = $elem.val();
|
457
627
|
var current = $elem.data('length-max') - input_value.length;
|
458
628
|
// Rails counts a newline as two characters, so let's make up for it here
|
459
629
|
current -= (input_value.match(/\n/g) || []).length;
|
460
630
|
|
461
631
|
return current;
|
462
|
-
}
|
463
|
-
}
|
464
|
-
|
632
|
+
},
|
633
|
+
},
|
465
634
|
};
|
@@ -0,0 +1,109 @@
|
|
1
|
+
/* global Fae */
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Fae form drag n drop uploads
|
5
|
+
* @namespace form.dragDrop
|
6
|
+
* @memberof form
|
7
|
+
*/
|
8
|
+
Fae.form.dragDrop = {
|
9
|
+
|
10
|
+
init: function () {
|
11
|
+
this.fileInputs = document.querySelectorAll('input[type="file"]');
|
12
|
+
if (this.fileInputs.length === 0) return;
|
13
|
+
|
14
|
+
this.bindListeners();
|
15
|
+
},
|
16
|
+
|
17
|
+
bindListeners() {
|
18
|
+
|
19
|
+
Array.from(this.fileInputs).forEach(input => {
|
20
|
+
const container = input.closest('.input.field');
|
21
|
+
|
22
|
+
['dragenter', 'dragover'].forEach((eventName) => {
|
23
|
+
container.addEventListener(eventName, this.highlight.bind(container));
|
24
|
+
});
|
25
|
+
|
26
|
+
['dragleave', 'drop'].forEach((eventName) => {
|
27
|
+
container.addEventListener(eventName, this.unhighlight.bind(container));
|
28
|
+
});
|
29
|
+
|
30
|
+
// This needed to be bound via jquery since tests have to use a simulated jquery event and native event listeners do not pickup on jquery events
|
31
|
+
$(container).on('drop', this.handleDrop.bind(this, container));
|
32
|
+
})
|
33
|
+
},
|
34
|
+
|
35
|
+
highlight(e) {
|
36
|
+
e.stopPropagation();
|
37
|
+
e.preventDefault();
|
38
|
+
this.classList.add('highlight');
|
39
|
+
},
|
40
|
+
|
41
|
+
unhighlight(e) {
|
42
|
+
e.stopPropagation();
|
43
|
+
e.preventDefault();
|
44
|
+
this.classList.remove('highlight');
|
45
|
+
},
|
46
|
+
|
47
|
+
handleDrop(inputContainer, e) {
|
48
|
+
// return the original event from the jquery event
|
49
|
+
if (e.originalEvent) {
|
50
|
+
e = e.originalEvent;
|
51
|
+
}
|
52
|
+
const input = inputContainer.querySelector('input[type="file"]');
|
53
|
+
const fileList = e.dataTransfer.files;
|
54
|
+
const file = fileList[0];
|
55
|
+
const isValidFile = this.validatesFileSize(input, file);
|
56
|
+
|
57
|
+
if (isValidFile) {
|
58
|
+
this.attachFile(input, fileList);
|
59
|
+
this.addFileInfo(inputContainer, file);
|
60
|
+
}
|
61
|
+
},
|
62
|
+
|
63
|
+
attachFile(input, files) {
|
64
|
+
input.files = files;
|
65
|
+
},
|
66
|
+
|
67
|
+
addFileInfo(inputContainer, file) {
|
68
|
+
const deleteButton = inputContainer.querySelector('.asset-delete');
|
69
|
+
|
70
|
+
// only exists if image is already loaded into field
|
71
|
+
let label = inputContainer.querySelector('.asset-title');
|
72
|
+
if (!label) {
|
73
|
+
// else is a new image
|
74
|
+
label = inputContainer.querySelector('.asset-actions span');
|
75
|
+
}
|
76
|
+
label.innerText = file.name;
|
77
|
+
deleteButton.style.display = 'block';
|
78
|
+
},
|
79
|
+
|
80
|
+
validatesFileSize(input, file) {
|
81
|
+
const limit = parseInt(input.dataset.limit);
|
82
|
+
const fileSize = file.size / 1024 / 1024;
|
83
|
+
|
84
|
+
if (fileSize < limit) {
|
85
|
+
this.removeFileSizeError(input);
|
86
|
+
return true;
|
87
|
+
}
|
88
|
+
|
89
|
+
this.addFileSizeError(input, limit);
|
90
|
+
return false;
|
91
|
+
},
|
92
|
+
|
93
|
+
addFileSizeError(input, limit) {
|
94
|
+
const errorElem = document.createElement('span');
|
95
|
+
errorElem.innerText = input.dataset.exceeded.replace('###', limit);
|
96
|
+
errorElem.classList.add('error');
|
97
|
+
input.after(errorElem);
|
98
|
+
input.parentElement.classList.add('field_with_errors');
|
99
|
+
},
|
100
|
+
|
101
|
+
removeFileSizeError(input) {
|
102
|
+
const nextSibling = input.nextSibling;
|
103
|
+
if (nextSibling.classList.contains('error')) {
|
104
|
+
nextSibling.remove();
|
105
|
+
}
|
106
|
+
input.parentElement.classList.remove('field_with_errors');
|
107
|
+
}
|
108
|
+
|
109
|
+
};
|
@@ -92,11 +92,17 @@ Fae.form.select = {
|
|
92
92
|
})
|
93
93
|
});
|
94
94
|
|
95
|
-
//
|
96
|
-
$
|
95
|
+
// prevent multiple deselect all actions from being added when nested forms are generated
|
96
|
+
if ($('.multiselect-action_wrap').length === 0) {
|
97
|
+
// Add actions to wraper
|
98
|
+
$deselect_all_action.insertAfter($chosen);
|
99
|
+
}
|
97
100
|
|
98
|
-
//
|
99
|
-
|
101
|
+
// prevent multiple 'SELECT ALL' options from being added when nested forms are generated
|
102
|
+
if ($element[0].options[0].value != select_all_value) {
|
103
|
+
// Add special "Select All" option and notify Chosen of new option
|
104
|
+
addSelectAllOption($element);
|
105
|
+
}
|
100
106
|
|
101
107
|
// Mark label wrapper as having multiselect actions for styling
|
102
108
|
$label.addClass('has-multiselect-actions');
|