UNPKG

15.5 kBJavaScriptView Raw
1import 'babel-polyfill';
2
3var classCallCheck = function (instance, Constructor) {
4 if (!(instance instanceof Constructor)) {
5 throw new TypeError("Cannot call a class as a function");
6 }
7};
8
9var createClass = function () {
10 function defineProperties(target, props) {
11 for (var i = 0; i < props.length; i++) {
12 var descriptor = props[i];
13 descriptor.enumerable = descriptor.enumerable || false;
14 descriptor.configurable = true;
15 if ("value" in descriptor) descriptor.writable = true;
16 Object.defineProperty(target, descriptor.key, descriptor);
17 }
18 }
19
20 return function (Constructor, protoProps, staticProps) {
21 if (protoProps) defineProperties(Constructor.prototype, protoProps);
22 if (staticProps) defineProperties(Constructor, staticProps);
23 return Constructor;
24 };
25}();
26
27
28
29
30
31
32
33var get = function get(object, property, receiver) {
34 if (object === null) object = Function.prototype;
35 var desc = Object.getOwnPropertyDescriptor(object, property);
36
37 if (desc === undefined) {
38 var parent = Object.getPrototypeOf(object);
39
40 if (parent === null) {
41 return undefined;
42 } else {
43 return get(parent, property, receiver);
44 }
45 } else if ("value" in desc) {
46 return desc.value;
47 } else {
48 var getter = desc.get;
49
50 if (getter === undefined) {
51 return undefined;
52 }
53
54 return getter.call(receiver);
55 }
56};
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74var set = function set(object, property, value, receiver) {
75 var desc = Object.getOwnPropertyDescriptor(object, property);
76
77 if (desc === undefined) {
78 var parent = Object.getPrototypeOf(object);
79
80 if (parent !== null) {
81 set(parent, property, value, receiver);
82 }
83 } else if ("value" in desc && desc.writable) {
84 desc.value = value;
85 } else {
86 var setter = desc.set;
87
88 if (setter !== undefined) {
89 setter.call(receiver, value);
90 }
91 }
92
93 return value;
94};
95
96/**
97 * extractValidity - Extracts the ValidityState information from a given
98 * object into an object suitable for manipulation.
99 *
100 * @param {HTMLElement} el A DOM element containing a ValidityState object.
101 * @return {object} A non-read-only object mimicing the ValidityState
102 * object for the given element.
103 */
104function extractValidity(el) {
105 var validity = el.validity;
106
107 // VaidityState.tooShort/minlength polyfill for older browsers.
108 var tooShort = validity.tooShort;
109 var valid = validity.valid;
110 var minlength = el.getAttribute('minlength');
111 if (minlength && typeof tooShort === 'undefined') {
112 tooShort = el.value.length < minlength;
113 if (tooShort) {
114 valid = false;
115 el.setCustomValidity(('\n Please lengthen this text to ' + minlength + ' characters or more (you are\n currently using ' + el.value.length + ' characters).\n ').trim());
116 } else {
117 el.setCustomValidity('');
118 }
119 }
120
121 return {
122 badInput: validity.badInput,
123 customError: validity.customError,
124 patternMismatch: validity.patternMismatch,
125 rangeOverflow: validity.rangeOverflow,
126 rangeUnderflow: validity.rangeUnderflow,
127 stepMismatch: validity.stepMismatch,
128 tooLong: validity.tooLong,
129 tooShort: tooShort,
130 typeMismatch: validity.typeMismatch,
131 valid: valid,
132 valueMissing: validity.valueMissing
133 };
134}
135
136var VueForm = function () {
137 function VueForm(options) {
138 classCallCheck(this, VueForm);
139
140 var defaults$$1 = {
141 wasFocusedClass: 'wasFocused',
142 wasSubmittedClass: 'wasSubmitted',
143 noValidate: true,
144 required: []
145 };
146 Object.assign(defaults$$1, options);
147 this.$noValidate = defaults$$1.noValidate;
148 this.$wasFocusedClass = defaults$$1.wasFocusedClass;
149 this.$wasSubmittedClass = defaults$$1.wasSubmittedClass;
150 this.$requiredFields = defaults$$1.required;
151 this.$wasSubmitted = false;
152 this.$isInvalid = false;
153 this.$isValid = true;
154 this.$invalidFields = [];
155 }
156
157 createClass(VueForm, [{
158 key: '$setCustomValidity',
159
160
161 /**
162 * setCustomValidity - A wrapper for HTML5s setCustomValidity function so that
163 * the end user can trigger a custom error without an error message, the
164 * custom error message is accessible through the form object, and the overall
165 * form validity is updated.
166 *
167 * @param {string} field The identifier for the field you wish to
168 * set the validity for.
169 * @param {boolean|string} invalid Whether the field is invalid (true), or
170 * not (false), or the custom error message
171 * for an invalid field.
172 */
173 value: function $setCustomValidity(field, invalid) {
174 if (this[field]) {
175 var isBoolean = typeof invalid === 'boolean';
176 var isNonEmptyString = typeof invalid === 'string' && invalid.length > 0;
177 if (invalid && (isBoolean || isNonEmptyString)) {
178 if (isNonEmptyString) {
179 this[field].customMessage = invalid;
180 this[field].$el.setCustomValidity(invalid);
181 } else {
182 this[field].$el.setCustomValidity('Error');
183 }
184 } else {
185 delete this[field].customMessage;
186 this[field].$el.setCustomValidity('');
187 }
188 Object.assign(this[field], extractValidity(this[field].$el));
189 this.$updateFormValidity(field);
190 }
191 }
192
193 /**
194 * updateFormValidity - Updates the overall validity of the form based on the
195 * existing validity state of its fields and the updated validity state of
196 * the given field.
197 *
198 * @param {string} field The identifier for the field whose validity state
199 * has updated and has consequently triggered the update
200 * of the overall forms validity.
201 */
202
203 }, {
204 key: '$updateFormValidity',
205 value: function $updateFormValidity(field) {
206 var index = this.$invalidFields.indexOf(field);
207 if (this[field].valid && index !== -1) {
208 this.$invalidFields.splice(index, 1);
209 if (this.$invalidFields.length === 0) {
210 this.$isValid = true;
211 this.$isInvalid = false;
212 }
213 } else if (!this[field].valid && index === -1) {
214 this.$isValid = false;
215 this.$isInvalid = true;
216 this.$invalidFields.push(field);
217 }
218 }
219
220 /**
221 * $isFieldRequired - Checks if a given named group has been manually
222 * designated as required through the VueForm constructor options.
223 *
224 * @param {string} name The name of the field to be checked.
225 *
226 * @returns {boolean} True if the field is required, false otherwise.
227 */
228
229 }, {
230 key: '$isFieldRequired',
231 value: function $isFieldRequired(name) {
232 return this.$requiredFields.filter(function (field) {
233 var isDynamic = field.name && field.name === name && field.required();
234 if (field === name || isDynamic) {
235 return field;
236 }
237 }).length > 0;
238 }
239
240 /**
241 * $updateNamedValidity - For the use case of requiring a value for a set of
242 * checkboxes or radio buttons with the same name, VueForm provides the
243 * validity state of the overall group using the name as the identifier. This
244 * function updates this validity state.
245 *
246 * @param {HTMLElement} el The DOM element that may trigger an update to the
247 * validity of the named group.
248 * @param {Vue} Vue The Vue.js instance given when this plugin is
249 * installed.
250 */
251
252 }, {
253 key: '$updateNamedValidity',
254 value: function $updateNamedValidity(el, Vue) {
255
256 // Check if the element has a name
257 if (el.hasAttribute('name')) {
258 var name = el.getAttribute('name');
259
260 // Check if the named group was marked as required.
261 if (this.$isFieldRequired(name)) {
262
263 // Set the validity state of the named group.
264 var valid = this.$getNamedValue(name);
265 var validity = { valid: valid, valueMissing: !valid };
266 if (this[name]) {
267 Object.assign(this[name], validity);
268 } else {
269 Vue.set(this, name, validity);
270 }
271
272 // Update the forms overall validity.
273 this.$updateFormValidity(name);
274 }
275 }
276 }
277 }, {
278 key: '$getNamedValue',
279 value: function $getNamedValue(name) {
280 var elements = this.$el.querySelectorAll('[name=' + name + ']');
281 var value = void 0;
282 var _iteratorNormalCompletion = true;
283 var _didIteratorError = false;
284 var _iteratorError = undefined;
285
286 try {
287 for (var _iterator = elements[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
288 var el = _step.value;
289
290 if (['radio', 'checkbox'].indexOf(el.type) !== -1) {
291 if (el.checked) {
292 if (el.type === 'radio') {
293 value = el.value;
294 break;
295 } else if (el.type === 'checkbox') {
296 if (value) {
297 value.push(el.value);
298 } else {
299 value = [el.value];
300 }
301 }
302 }
303 } else if (elements.length === 1) {
304 value = el.value;
305 } else if (value) {
306 value.push(el.value);
307 } else {
308 value = [el.value];
309 }
310 }
311 } catch (err) {
312 _didIteratorError = true;
313 _iteratorError = err;
314 } finally {
315 try {
316 if (!_iteratorNormalCompletion && _iterator.return) {
317 _iterator.return();
318 }
319 } finally {
320 if (_didIteratorError) {
321 throw _iteratorError;
322 }
323 }
324 }
325
326 return value;
327 }
328 }], [{
329 key: 'install',
330 value: function install(Vue) {
331
332 // v-form directive.
333 Vue.directive('form', function (el, _ref) {
334 var value = _ref.value;
335
336
337 if (value instanceof VueForm) {
338
339 // Setup the form object when the directive is first bound to the
340 // form element.
341 if (!value.$el) {
342 value.$el = el;
343 value.$el.noValidate = value.$noValidate;
344
345 // Pre-populate required fields with an empty object in case they are
346 // dynamically inserted.
347 value.$requiredFields.forEach(function (field) {
348 return value[field.name || field] = {};
349 });
350
351 // Update the forms $wasSubmitted state and apply the appropriate CSS
352 // class when the forms submit event is triggered.
353 value.$el.addEventListener('submit', function () {
354 value.$wasSubmitted = true;
355 value.$el.classList.add(value.$wasSubmittedClass);
356 });
357
358 // Update the form and child field state and remove any corresponding
359 // CSS classes when the forms reset event is triggered.
360 value.$el.addEventListener('reset', function () {
361 value.$wasSubmitted = false;
362 value.$el.classList.remove(value.$wasSubmittedClass);
363
364 // Reset $wasFocused property and remove the corresponding class
365 // from each child node.
366 var _iteratorNormalCompletion2 = true;
367 var _didIteratorError2 = false;
368 var _iteratorError2 = undefined;
369
370 try {
371 for (var _iterator2 = Object.keys(value)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
372 var id = _step2.value;
373
374 if (id.indexOf('$') === -1 && value[id].$el) {
375 value[id].$wasFocused = false;
376 value[id].$el.classList.remove(value.$wasFocusedClass);
377 Object.assign(value[id], extractValidity(value[id].$el));
378 value.$updateFormValidity(id);
379 }
380 }
381 } catch (err) {
382 _didIteratorError2 = true;
383 _iteratorError2 = err;
384 } finally {
385 try {
386 if (!_iteratorNormalCompletion2 && _iterator2.return) {
387 _iterator2.return();
388 }
389 } finally {
390 if (_didIteratorError2) {
391 throw _iteratorError2;
392 }
393 }
394 }
395 });
396 }
397
398 // Go through each field within the form, set up its state within
399 // the form object, and listen to input or change events to keep its
400 // state in sync.
401 var _iteratorNormalCompletion3 = true;
402 var _didIteratorError3 = false;
403 var _iteratorError3 = undefined;
404
405 try {
406 for (var _iterator3 = el.querySelectorAll('input, textarea, select')[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
407 var $el = _step3.value;
408
409
410 // Only work with elements that belong to the form, have the ability
411 // to be validated, and have and id or name property.
412 if ($el.form === el && $el.willValidate) {
413 (function () {
414 var id = $el.getAttribute('id');
415 var isUnregistered = id && !value[id];
416
417 //
418 if (isUnregistered) {
419
420 // Create the field object and extract its validity state.
421 var field = Object.assign({ $el: $el }, extractValidity($el));
422 Vue.set(value, id, field);
423 value.$updateFormValidity(id);
424
425 // Add wasFocused class to element when focus event is triggered.
426 $el.addEventListener('focus', function (_ref2) {
427 var target = _ref2.target;
428
429 value[id].$wasFocused = true;
430 target.classList.add(value.$wasFocusedClass);
431 });
432 }
433
434 //
435 value.$updateNamedValidity($el, Vue);
436
437 // On change or input events, update the field and form validity
438 // state.
439 var type = $el.getAttribute('type');
440 var isCheckable = ['radio', 'checkbox'].indexOf(type) !== -1;
441 var eventType = isCheckable ? 'change' : 'input';
442 $el.addEventListener(eventType, function (_ref3) {
443 var target = _ref3.target;
444
445 if (id) {
446 Object.assign(value[id], extractValidity(target));
447 value.$updateFormValidity(id);
448 }
449 value.$updateNamedValidity(target, Vue);
450 });
451 })();
452 }
453 }
454 } catch (err) {
455 _didIteratorError3 = true;
456 _iteratorError3 = err;
457 } finally {
458 try {
459 if (!_iteratorNormalCompletion3 && _iterator3.return) {
460 _iterator3.return();
461 }
462 } finally {
463 if (_didIteratorError3) {
464 throw _iteratorError3;
465 }
466 }
467 }
468 }
469 });
470 }
471 }]);
472 return VueForm;
473}();
474
475export default VueForm;