HTMLElement.prototype.isChildOf = function(parent) { var node = this.parentNode; while (node != null) { if (node == parent) { return true; } node = node.parentNode; } return false; }; class SerializerField { /** * constructor * * @param {HTMLElement} f Input field (input|select|textarea) * @returns {SerializerField} */ constructor(f) { this.name; this.parent; this.field = f; this.required = f.required; this.type = this.field.getAttribute("type"); this.flatten = this.field.hasAttribute("flatten"); } /** * serialize * * @returns {(String|Number|Boolean|Date)} */ serialize() { if (this.field.tagName == "SELECT" && this.field.multiple) { return Array.apply(null, this.field.options).filter(o => o.selected).map(o => o.value) } if (this.type == "number" || this.type == "range") { return Number(this.field.value); } if ( this.type == "date" || this.type == "datetime-local" || this.type == "month" || this.type == "week" ) { return new Date(this.field.value); } if (this.type == "radio") { if (!this.field.checked) { return null } } if (this.type == "checkbox") { let checked = this.field.checked; if (this.field.hasAttribute("string")) { if (!checked) { return null; } return this.field.value; } return checked; } return this.field.value; } } class Serializer { /** * constructor * * @param {HTMLElement} element * @returns {Serializer} */ constructor(element) { let fields = element.querySelectorAll( `:scope input:not([type="submit"]), :scope select, :scope textarea, :scope group` ); let groups = [].slice.call(element.querySelectorAll(`:scope group`)); this.name = ":root"; this.parent; this._fields = new Map(); this.min = Number(element.getAttribute("min") || -1); this.flatten = element.hasAttribute("flatten") this.required = element.getAttribute("required") !== null || this.min != -1; for (let f of fields) { if (f.isChildOf(element) && groups.every(g => !f.isChildOf(g))) { this.addField(f); } } } /** * addField * * @param {HTMLElement} f */ addField(f) { let fieldName = f.getAttribute("name"); if(!fieldName) { return } let isArray = fieldName.match(/^\[(\d*)\]/); let isRadio = f.type == "radio"; fieldName = fieldName.replace(/^\[\d*\]/, ""); let field; if (f.tagName == "GROUP") { field = new Serializer(f); } else { field = new SerializerField(f); } if (field.type == "file") { return; } field.name = fieldName; field.parent = this; f.serializer = field; if (isArray) { if (!this._fields.has(fieldName)) { this._fields.set(fieldName, []); } if (isArray[1] != "") { this._fields.get(fieldName)[isArray[1]] = field; } else { this._fields.get(fieldName).push(field); } } else if (isRadio) { if (!this._fields.has(fieldName)) { this._fields.set(fieldName, []); } this._fields.get(fieldName).push(field); } else { this._fields.set(fieldName, field); } } /** * removeField * * @param {(HTMLElement|Serializer|SerializerField)} f Serializer child */ removeField(f) { if (this._fields.has(f.name)) { let fields = this._fields.get(f.name); if (Array.isArray(fields)) { let index = fields.indexOf(f); this._fields.set(f.name, fields.splice(index, 1)); } else { this._fields.delete(); } } } /** * serialize * * @returns {Object} */ serialize() { let json = {}; for (let [k, f] of this._fields) { if (Array.isArray(f)) { if (k != "") { json[k] = []; } else { json = []; } for (let key in f) { if (f[key].type == "radio") { json = {} let d = f[key].serialize(); if (d !== null) { if (k == "") { json = d; break; } json[k] = d; break; } continue } if (f[key]) { let d = f[key].serialize(); if (d == null) { continue } if(k == "") { if(f[key].flatten || this.flatten){ json[key].push(d); } else { json[key] = d; } continue } if(f[key].flatten || this.flatten){ json[k].push(d); continue; } json[k][key] = d; } } } else { let d = f.serialize(); if (d !== null) { if (k == "") { json = d; continue; } json[k] = d; } } } return Object.keys(json).length > 0 ? json : null;} } class Validator { /** * validateRequired * * @static * @param {(erializer|SerializerField)} field * @returns {Object} */ static validateRequired(field) { let e = { valid: true, errors: {} }; let v; if (field instanceof Serializer) { v = Validator.validateRequiredSerializer(field); } else { v = Validator.validateRequiredSerialzerField(field); } if (!v.valid) { e.valid = false; e.errors = v.errors; } return e; } /** * validateRequiredSerializer * * @static * @param {Serializer} field * @returns {Object} */ static validateRequiredSerializer(field) { let e = { valid: true, errors: {} }; let min = field.min; let validFieldsCount = 0; for (let [k, f] of field._fields) { if (Array.isArray(f)) { e.errors = { message: null }; let validCount = 0; for (let sf of f) { if (!(sf.name in e.errors)) { e.errors[sf.name] = []; } let v = Validator.validateRequired(sf); if (!v.valid) { console.error(e.errors); e.errors[sf.name].push(v.errors); continue; } validCount++; } e.valid = min <= validCount; if (e.valid) { e.errors = {}; } else { e.errors.message = `Should contain atleast ${min} value(s)`; } } else { let v = Validator.validateRequired(f); if (!v.valid) { e.valid = false; e.errors[f.name] = v.errors; } } } return e; } /** * validateRequiredSerialzerField * * @static * @param {SerializerField} field * @returns {Object} */ static validateRequiredSerialzerField(field) { let e = { valid: true, errors: {} }; if (field.required || (field.parent && field.parent.required)) { if (field.type != "checkbox") { e.valid = field.field.validity.valueMissing !== true; e.errors = "Is required"; } else { e.valid = field.field.checked; e.errors = "Not checked"; } } return e; } }