UNPKG

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