/******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ "./src/field-error.riot": /*!******************************!*\ !*** ./src/field-error.riot ***! \******************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) /* harmony export */ }); /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({ css: null, exports: { state: { errors: [ ], // css class for closest: '.field-group', }, /** * * * @param {Object} props * @param {Object} state * */ onBeforeMounted(props, state) { if (props.closest) { state.closest = props.closest } }, /** * * * @param {Object} props * @param {Object} state * */ onMounted(props, state) { // getting parent element for entire field const parent = this.root.closest(state.closest) // getting current element by name const element = parent.querySelector('[name="' + props.name + '"]') // getting form const form = element.closest('form') // element, form are exists and nofieldupdate is not set // each change of the element dispatch a event to form validation if (element && form && !props.nofieldupdate) { element.addEventListener('input', (event) => { this.dispatchCustomEvent(event, form, props.name) }) } // add custom event to listen to form-validation this.root.addEventListener('form-validation', (event) => { this.onFormValidation(event, parent) }) }, /** * process form validation triggered by form * * @param {Event} event * @param {Element} parent * */ onFormValidation(event, parent) { // if detail is a value, set to errors if (event.detail) { this.state.errors = event.detail parent.classList.add('field--error') parent.classList.remove('field--valid') } else { this.state.errors = [] parent.classList.remove('field--error') parent.classList.add('field--valid') } this.update() }, /** * create event to send to form validation * * @param {Event} event * @param {Element} form * @param {string} name * */ dispatchCustomEvent(event, form, name) { const fieldUpdateEvent = new CustomEvent('field-update', { 'detail': { 'name': name, 'value': event.target.value } }) form.dispatchEvent(fieldUpdateEvent) } }, template: ( template, expressionTypes, bindingTypes, getComponent ) => template( '
', [ { type: bindingTypes.IF, evaluate: _scope => _scope.state.errors.length > 0, redundantAttribute: 'expr0', selector: '[expr0]', template: template( '', [ { type: bindingTypes.EACH, getKey: null, condition: null, template: template( ' ', [ { expressions: [ { type: expressionTypes.TEXT, childNodeIndex: 0, evaluate: _scope => [ _scope.error ].join( '' ) } ] } ] ), redundantAttribute: 'expr1', selector: '[expr1]', itemName: 'error', indexName: null, evaluate: _scope => _scope.state.errors } ] ) } ] ), name: 'field-error' }); /***/ }), /***/ "./src/FormValidator.js": /*!******************************!*\ !*** ./src/FormValidator.js ***! \******************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) /* harmony export */ }); /* harmony import */ var validate_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! validate.js */ "./node_modules/validate.js/validate.js"); /* harmony import */ var validate_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(validate_js__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var form_serialize__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! form-serialize */ "./node_modules/form-serialize/index.js"); /* harmony import */ var form_serialize__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(form_serialize__WEBPACK_IMPORTED_MODULE_1__); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } /** * Form Validator with RiotJS Components * * * @author HerrHase * @ * */ var FormValidator = /*#__PURE__*/function () { /** * * @param {[type]} formSelector [description] * @param {[type]} constraits [description] */ function FormValidator(formSelector, constraits, onSuccess) { var _this = this; _classCallCheck(this, FormValidator); // getting selector to find form-element this.formSelector = formSelector; // constraits for validate.js this.constraits = constraits; // adding onSuccess this._onSuccess = onSuccess; // if form not found if (!this._onSuccess) { console.error('FormValidator: onSuccess not found!'); } // get form and elements this.form = document.querySelector(this.formSelector); // if form not found if (!this.form) { console.error('FormValidator: form not found!'); } this.elements = this.form.querySelectorAll('field-error'); // adding submit event this.form.addEventListener('submit', function (event) { _this._onSubmit(event); }); // adding event if a element is updated this.form.addEventListener('field-update', function (event) { _this._onFieldUpdate(event); }); } /** * handle submit * * * @param {Event} event * */ _createClass(FormValidator, [{ key: "_onSubmit", value: function _onSubmit(event) { var _this2 = this; // getting data from target of submit event var data = form_serialize__WEBPACK_IMPORTED_MODULE_1___default()(event.target, { hash: true }); // options for validate.js var options = { fullMessages: false }; validate_js__WEBPACK_IMPORTED_MODULE_0___default().async(data, this.constraits, options).then( // handling success function () { _this2._onSuccess(event, data); }, // handling error function (errors) { event.preventDefault(); // send each element a event _this2.elements.forEach(function (element) { var elementErrors = false; // check for errors by name if (errors[element.attributes.name.nodeValue]) { elementErrors = errors[element.attributes.name.nodeValue]; } _this2._dispatchCustomEvent(elementErrors, element); }); }); } /** * send update to fields * * * @param {Event} event * */ }, { key: "_onFieldUpdate", value: function _onFieldUpdate(event) { var _this3 = this; // workaround, make sure that value for single is undefined if it is empty if (event.detail.value == '') { event.detail.value = undefined; } var errors = validate_js__WEBPACK_IMPORTED_MODULE_0___default().single(event.detail.value, this.constraits[event.detail.name]); // search for element by name and dispatch event this.elements.forEach(function (element) { if (element.attributes.name.nodeValue == event.detail.name) { _this3._dispatchCustomEvent(errors, element); } }); } /** * dispatch event to single element * * @param {Array} errors * @param {Element} element * */ }, { key: "_dispatchCustomEvent", value: function _dispatchCustomEvent(errors, element) { var detail = false; if (errors) { detail = errors; } var formValidationEvent = new CustomEvent('form-validation', { 'detail': detail }); element.dispatchEvent(formValidationEvent); } }]); return FormValidator; }(); /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (FormValidator); /***/ }), /***/ "./src/demo.js": /*!*********************!*\ !*** ./src/demo.js ***! \*********************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var riot__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! riot */ "./node_modules/riot/riot.esm.js"); /* harmony import */ var _FormValidator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./FormValidator */ "./src/FormValidator.js"); /* harmony import */ var _field_error_riot__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./field-error.riot */ "./src/field-error.riot"); riot__WEBPACK_IMPORTED_MODULE_2__.register('field-error', _field_error_riot__WEBPACK_IMPORTED_MODULE_1__["default"]); riot__WEBPACK_IMPORTED_MODULE_2__.mount('field-error'); var formValidation = new _FormValidator__WEBPACK_IMPORTED_MODULE_0__["default"]('form', { 'email': { 'presence': true, 'email': true }, 'password': { 'presence': true } }, function (event, data) { event.preventDefault(); document.querySelector('#result .content').innerHTML = '

' + JSON.stringify(data) + '

'; document.querySelector('#result').classList.remove('hidden'); }); /***/ }), /***/ "./node_modules/form-serialize/index.js": /*!**********************************************!*\ !*** ./node_modules/form-serialize/index.js ***! \**********************************************/ /***/ ((module) => { // get successful control from form and assemble into object // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2 // types which indicate a submit action and are not successful controls // these will be ignored var k_r_submitter = /^(?:submit|button|image|reset|file)$/i; // node names which could be successful controls var k_r_success_contrls = /^(?:input|select|textarea|keygen)/i; // Matches bracket notation. var brackets = /(\[[^\[\]]*\])/g; // serializes form fields // @param form MUST be an HTMLForm element // @param options is an optional argument to configure the serialization. Default output // with no options specified is a url encoded string // - hash: [true | false] Configure the output type. If true, the output will // be a js object. // - serializer: [function] Optional serializer function to override the default one. // The function takes 3 arguments (result, key, value) and should return new result // hash and url encoded str serializers are provided with this module // - disabled: [true | false]. If true serialize disabled fields. // - empty: [true | false]. If true serialize empty fields function serialize(form, options) { if (typeof options != 'object') { options = { hash: !!options }; } else if (options.hash === undefined) { options.hash = true; } var result = (options.hash) ? {} : ''; var serializer = options.serializer || ((options.hash) ? hash_serializer : str_serialize); var elements = form && form.elements ? form.elements : []; //Object store each radio and set if it's empty or not var radio_store = Object.create(null); for (var i=0 ; i myString */ function dashToCamelCase(string) { return string.replace(/-(\w)/g, (_, c) => c.toUpperCase()); } /** * Get all the element attributes as object * @param {HTMLElement} element - DOM node we want to parse * @returns {Object} all the attributes found as a key value pairs */ function DOMattributesToObject(element) { return Array.from(element.attributes).reduce((acc, attribute) => { acc[dashToCamelCase(attribute.name)] = attribute.value; return acc; }, {}); } /** * Move all the child nodes from a source tag to another * @param {HTMLElement} source - source node * @param {HTMLElement} target - target node * @returns {undefined} it's a void method ¯\_(ツ)_/¯ */ // Ignore this helper because it's needed only for svg tags function moveChildren(source, target) { if (source.firstChild) { target.appendChild(source.firstChild); moveChildren(source, target); } } /** * Remove the child nodes from any DOM node * @param {HTMLElement} node - target node * @returns {undefined} */ function cleanNode(node) { clearChildren(node.childNodes); } /** * Clear multiple children in a node * @param {HTMLElement[]} children - direct children nodes * @returns {undefined} */ function clearChildren(children) { Array.from(children).forEach(removeChild); } /** * Remove a node * @param {HTMLElement}node - node to remove * @returns {undefined} */ const removeChild = node => node && node.parentNode && node.parentNode.removeChild(node); /** * Insert before a node * @param {HTMLElement} newNode - node to insert * @param {HTMLElement} refNode - ref child * @returns {undefined} */ const insertBefore = (newNode, refNode) => refNode && refNode.parentNode && refNode.parentNode.insertBefore(newNode, refNode); /** * Replace a node * @param {HTMLElement} newNode - new node to add to the DOM * @param {HTMLElement} replaced - node to replace * @returns {undefined} */ const replaceChild = (newNode, replaced) => replaced && replaced.parentNode && replaced.parentNode.replaceChild(newNode, replaced); // Riot.js constants that can be used accross more modules const COMPONENTS_IMPLEMENTATION_MAP$1 = new Map(), DOM_COMPONENT_INSTANCE_PROPERTY$1 = Symbol('riot-component'), PLUGINS_SET$1 = new Set(), IS_DIRECTIVE = 'is', VALUE_ATTRIBUTE = 'value', MOUNT_METHOD_KEY = 'mount', UPDATE_METHOD_KEY = 'update', UNMOUNT_METHOD_KEY = 'unmount', SHOULD_UPDATE_KEY = 'shouldUpdate', ON_BEFORE_MOUNT_KEY = 'onBeforeMount', ON_MOUNTED_KEY = 'onMounted', ON_BEFORE_UPDATE_KEY = 'onBeforeUpdate', ON_UPDATED_KEY = 'onUpdated', ON_BEFORE_UNMOUNT_KEY = 'onBeforeUnmount', ON_UNMOUNTED_KEY = 'onUnmounted', PROPS_KEY = 'props', STATE_KEY = 'state', SLOTS_KEY = 'slots', ROOT_KEY = 'root', IS_PURE_SYMBOL = Symbol('pure'), IS_COMPONENT_UPDATING = Symbol('is_updating'), PARENT_KEY_SYMBOL = Symbol('parent'), ATTRIBUTES_KEY_SYMBOL = Symbol('attributes'), TEMPLATE_KEY_SYMBOL = Symbol('template'); var globals = /*#__PURE__*/Object.freeze({ __proto__: null, COMPONENTS_IMPLEMENTATION_MAP: COMPONENTS_IMPLEMENTATION_MAP$1, DOM_COMPONENT_INSTANCE_PROPERTY: DOM_COMPONENT_INSTANCE_PROPERTY$1, PLUGINS_SET: PLUGINS_SET$1, IS_DIRECTIVE: IS_DIRECTIVE, VALUE_ATTRIBUTE: VALUE_ATTRIBUTE, MOUNT_METHOD_KEY: MOUNT_METHOD_KEY, UPDATE_METHOD_KEY: UPDATE_METHOD_KEY, UNMOUNT_METHOD_KEY: UNMOUNT_METHOD_KEY, SHOULD_UPDATE_KEY: SHOULD_UPDATE_KEY, ON_BEFORE_MOUNT_KEY: ON_BEFORE_MOUNT_KEY, ON_MOUNTED_KEY: ON_MOUNTED_KEY, ON_BEFORE_UPDATE_KEY: ON_BEFORE_UPDATE_KEY, ON_UPDATED_KEY: ON_UPDATED_KEY, ON_BEFORE_UNMOUNT_KEY: ON_BEFORE_UNMOUNT_KEY, ON_UNMOUNTED_KEY: ON_UNMOUNTED_KEY, PROPS_KEY: PROPS_KEY, STATE_KEY: STATE_KEY, SLOTS_KEY: SLOTS_KEY, ROOT_KEY: ROOT_KEY, IS_PURE_SYMBOL: IS_PURE_SYMBOL, IS_COMPONENT_UPDATING: IS_COMPONENT_UPDATING, PARENT_KEY_SYMBOL: PARENT_KEY_SYMBOL, ATTRIBUTES_KEY_SYMBOL: ATTRIBUTES_KEY_SYMBOL, TEMPLATE_KEY_SYMBOL: TEMPLATE_KEY_SYMBOL }); const EACH = 0; const IF = 1; const SIMPLE = 2; const TAG = 3; const SLOT = 4; var bindingTypes = { EACH, IF, SIMPLE, TAG, SLOT }; const ATTRIBUTE = 0; const EVENT = 1; const TEXT = 2; const VALUE = 3; var expressionTypes = { ATTRIBUTE, EVENT, TEXT, VALUE }; const HEAD_SYMBOL = Symbol('head'); const TAIL_SYMBOL = Symbol('tail'); /** * Create the