fae-rails 2.2.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -1
- 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 +1 -1
- data/app/assets/javascripts/fae/application.js +5 -1
- data/app/assets/javascripts/fae/form/_ajax.js +14 -4
- data/app/assets/javascripts/fae/form/_form.js +4 -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 +7 -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 +2 -1
- data/app/assets/stylesheets/fae/globals/_tags.scss +1 -1
- data/app/assets/stylesheets/fae/globals/layout/_base.scss +2 -2
- data/app/assets/stylesheets/fae/globals/layout/_content-header.scss +6 -2
- 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 +2 -2
- 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/_errors-bar.scss +19 -0
- data/app/assets/stylesheets/fae/modules/_toggles.scss +1 -1
- data/app/assets/stylesheets/fae/modules/forms/_base.scss +13 -0
- data/app/assets/stylesheets/fae/modules/forms/_date.scss +4 -4
- 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/_select.scss +2 -2
- data/app/assets/stylesheets/fae/modules/forms/_simple-mde.scss +71 -0
- data/app/assets/stylesheets/fae/modules/tables/_base.scss +1 -1
- 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 +1 -1
- data/app/assets/stylesheets/fae/simplemde_override.scss +32 -0
- data/app/controllers/fae/application_controller.rb +2 -2
- data/app/controllers/fae/base_controller.rb +1 -1
- data/app/controllers/fae/deploy_hooks_controller.rb +4 -4
- data/app/controllers/fae/setup_controller.rb +2 -2
- data/app/helpers/fae/application_helper.rb +10 -1
- data/app/helpers/fae/view_helper.rb +4 -0
- data/app/models/concerns/fae/base_model_concern.rb +8 -0
- data/app/models/concerns/fae/seo_set_concern.rb +1 -0
- data/app/models/fae/change.rb +1 -0
- data/app/models/fae/option.rb +1 -0
- data/app/models/fae/seo_set.rb +14 -0
- data/app/services/fae/netlify_api.rb +22 -6
- data/app/uploaders/fae/file_uploader.rb +1 -1
- data/app/uploaders/fae/image_uploader.rb +2 -2
- data/app/views/fae/application/_header.slim +2 -2
- data/app/views/fae/application/_seo_set_form.html.slim +12 -0
- data/app/views/fae/options/_form.html.slim +2 -1
- data/app/views/fae/pages/home.html.slim +1 -1
- data/app/views/fae/shared/_errors.slim +0 -3
- data/app/views/fae/shared/_form_header.html.slim +4 -0
- data/app/views/fae/shared/_nested_table.html.slim +2 -1
- data/app/views/fae/shared/_shared_nested_table.html.slim +5 -2
- data/app/views/layouts/fae/application.html.slim +1 -1
- data/config/initializers/carrierwave.rb +41 -2
- data/config/initializers/fae_judge.rb +4 -2
- data/config/locales/fae.en.yml +10 -1
- data/config/puma.rb +82 -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 +17 -19
- data/lib/fae/version.rb +1 -1
- data/lib/generators/fae/base_generator.rb +17 -4
- 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 +13 -5
- data/lib/generators/fae/page_generator.rb +1 -2
- data/lib/generators/fae/scaffold_generator.rb +1 -1
- data/lib/generators/fae/templates/controllers/nested_scaffold_controller.rb +1 -3
- data/lib/generators/fae/templates/controllers/scaffold_controller.rb +7 -0
- data/lib/generators/fae/templates/views/_form.html.slim +2 -0
- data/lib/generators/fae/templates/views/_form_nested.html.slim +3 -0
- metadata +29 -20
- 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,14 +92,17 @@ Fae.form.select = {
|
|
92
92
|
})
|
93
93
|
});
|
94
94
|
|
95
|
-
// prevent
|
96
|
-
if (
|
95
|
+
// prevent multiple deselect all actions from being added when nested forms are generated
|
96
|
+
if ($('.multiselect-action_wrap').length === 0) {
|
97
97
|
// Add actions to wraper
|
98
98
|
$deselect_all_action.insertAfter($chosen);
|
99
99
|
}
|
100
100
|
|
101
|
-
//
|
102
|
-
|
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
|
+
}
|
103
106
|
|
104
107
|
// Mark label wrapper as having multiselect actions for styling
|
105
108
|
$label.addClass('has-multiselect-actions');
|