2018-05-02 18:04:48 +02:00
|
|
|
HTMLElement.prototype.isChildOf = function(parent) {
|
2017-08-29 12:58:02 +02:00
|
|
|
var node = this.parentNode;
|
|
|
|
while (node != null) {
|
|
|
|
if (node == parent) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
node = node.parentNode;
|
|
|
|
}
|
|
|
|
return false;
|
2018-05-02 18:04:48 +02:00
|
|
|
};
|
2017-08-29 12:58:02 +02:00
|
|
|
|
2017-08-26 23:44:47 +02:00
|
|
|
class SerializerField {
|
|
|
|
/**
|
|
|
|
* constructor
|
|
|
|
*
|
|
|
|
* @param {HTMLElement} f Input field (input|select|textarea)
|
|
|
|
* @returns {SerializerField}
|
|
|
|
*/
|
2018-05-02 18:04:48 +02:00
|
|
|
constructor(f) {
|
2017-08-31 12:21:39 +02:00
|
|
|
this.name;
|
|
|
|
this.parent;
|
2017-08-26 23:44:47 +02:00
|
|
|
this.field = f;
|
2018-05-02 18:04:48 +02:00
|
|
|
this.required = f.required;
|
2017-08-31 12:21:39 +02:00
|
|
|
this.type = this.field.getAttribute("type");
|
2018-05-03 12:33:36 +02:00
|
|
|
this.flatten = this.field.hasAttribute("flatten");
|
2017-08-26 23:44:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* serialize
|
|
|
|
*
|
2017-08-31 12:21:39 +02:00
|
|
|
* @returns {(String|Number|Boolean|Date)}
|
2017-08-26 23:44:47 +02:00
|
|
|
*/
|
2018-05-02 18:04:48 +02:00
|
|
|
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") {
|
2017-08-26 23:44:47 +02:00
|
|
|
return Number(this.field.value);
|
2018-05-02 18:04:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
this.type == "date" ||
|
|
|
|
this.type == "datetime-local" ||
|
|
|
|
this.type == "month" ||
|
|
|
|
this.type == "week"
|
|
|
|
) {
|
2017-08-26 23:44:47 +02:00
|
|
|
return new Date(this.field.value);
|
2018-05-02 18:04:48 +02:00
|
|
|
}
|
|
|
|
|
2018-05-03 16:03:34 +02:00
|
|
|
if (this.type == "radio") {
|
|
|
|
if (!this.field.checked) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-02 18:04:48 +02:00
|
|
|
if (this.type == "checkbox") {
|
2017-08-31 12:21:39 +02:00
|
|
|
let checked = this.field.checked;
|
2018-05-02 18:04:48 +02:00
|
|
|
if (this.field.hasAttribute("string")) {
|
|
|
|
if (!checked) {
|
2017-08-31 12:21:39 +02:00
|
|
|
return null;
|
|
|
|
}
|
2018-05-02 18:04:48 +02:00
|
|
|
return this.field.value;
|
2017-08-31 12:21:39 +02:00
|
|
|
}
|
|
|
|
return checked;
|
2018-05-02 18:04:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return this.field.value;
|
2017-08-26 23:44:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Serializer {
|
|
|
|
/**
|
|
|
|
* constructor
|
|
|
|
*
|
|
|
|
* @param {HTMLElement} element
|
|
|
|
* @returns {Serializer}
|
|
|
|
*/
|
2018-05-02 18:04:48 +02:00
|
|
|
constructor(element) {
|
|
|
|
let fields = element.querySelectorAll(
|
|
|
|
`:scope input:not([type="submit"]), :scope select, :scope textarea, :scope group`
|
|
|
|
);
|
2017-08-29 12:58:02 +02:00
|
|
|
let groups = [].slice.call(element.querySelectorAll(`:scope group`));
|
2017-08-26 23:44:47 +02:00
|
|
|
|
2017-08-31 12:21:39 +02:00
|
|
|
this.name = ":root";
|
|
|
|
this.parent;
|
2017-08-26 23:44:47 +02:00
|
|
|
this._fields = new Map();
|
2017-08-31 12:21:39 +02:00
|
|
|
this.min = Number(element.getAttribute("min") || -1);
|
2018-05-03 15:07:54 +02:00
|
|
|
this.flatten = element.hasAttribute("flatten")
|
2017-08-31 12:21:39 +02:00
|
|
|
|
|
|
|
this.required = element.getAttribute("required") !== null || this.min != -1;
|
2017-08-26 23:44:47 +02:00
|
|
|
|
2018-05-02 18:04:48 +02:00
|
|
|
for (let f of fields) {
|
|
|
|
if (f.isChildOf(element) && groups.every(g => !f.isChildOf(g))) {
|
2017-08-29 12:58:02 +02:00
|
|
|
this.addField(f);
|
|
|
|
}
|
2017-08-26 23:44:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* addField
|
|
|
|
*
|
|
|
|
* @param {HTMLElement} f
|
|
|
|
*/
|
2018-05-02 18:04:48 +02:00
|
|
|
addField(f) {
|
2017-08-26 23:44:47 +02:00
|
|
|
let fieldName = f.getAttribute("name");
|
2018-05-02 18:22:25 +02:00
|
|
|
if(!fieldName) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-05-02 18:04:48 +02:00
|
|
|
let isArray = fieldName.match(/^\[(\d*)\]/);
|
2018-05-03 16:03:34 +02:00
|
|
|
let isRadio = f.type == "radio";
|
2017-08-26 23:44:47 +02:00
|
|
|
|
2018-05-02 18:04:48 +02:00
|
|
|
fieldName = fieldName.replace(/^\[\d*\]/, "");
|
|
|
|
|
2017-08-26 23:44:47 +02:00
|
|
|
let field;
|
2018-05-02 18:04:48 +02:00
|
|
|
if (f.tagName == "GROUP") {
|
|
|
|
field = new Serializer(f);
|
2017-08-26 23:44:47 +02:00
|
|
|
} else {
|
2017-08-31 12:21:39 +02:00
|
|
|
field = new SerializerField(f);
|
2017-08-26 23:44:47 +02:00
|
|
|
}
|
2017-09-16 21:40:32 +02:00
|
|
|
|
2018-05-02 18:04:48 +02:00
|
|
|
if (field.type == "file") {
|
2017-09-16 21:40:32 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-08-31 12:21:39 +02:00
|
|
|
field.name = fieldName;
|
|
|
|
field.parent = this;
|
|
|
|
|
2018-05-02 18:04:48 +02:00
|
|
|
f.serializer = field;
|
2017-08-26 23:44:47 +02:00
|
|
|
|
2018-05-02 18:04:48 +02:00
|
|
|
if (isArray) {
|
|
|
|
if (!this._fields.has(fieldName)) {
|
|
|
|
this._fields.set(fieldName, []);
|
2017-08-26 23:44:47 +02:00
|
|
|
}
|
2018-05-02 18:04:48 +02:00
|
|
|
if (isArray[1] != "") {
|
|
|
|
this._fields.get(fieldName)[isArray[1]] = field;
|
2017-08-26 23:44:47 +02:00
|
|
|
} else {
|
2018-05-02 18:04:48 +02:00
|
|
|
this._fields.get(fieldName).push(field);
|
2017-08-26 23:44:47 +02:00
|
|
|
}
|
2018-05-03 16:03:34 +02:00
|
|
|
} else if (isRadio) {
|
|
|
|
if (!this._fields.has(fieldName)) {
|
|
|
|
this._fields.set(fieldName, []);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._fields.get(fieldName).push(field);
|
2017-08-26 23:44:47 +02:00
|
|
|
} else {
|
2018-05-02 18:04:48 +02:00
|
|
|
this._fields.set(fieldName, field);
|
2017-08-26 23:44:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-31 12:22:52 +02:00
|
|
|
/**
|
|
|
|
* removeField
|
|
|
|
*
|
|
|
|
* @param {(HTMLElement|Serializer|SerializerField)} f Serializer child
|
|
|
|
*/
|
2018-05-02 18:04:48 +02:00
|
|
|
removeField(f) {
|
|
|
|
if (this._fields.has(f.name)) {
|
|
|
|
let fields = this._fields.get(f.name);
|
|
|
|
if (Array.isArray(fields)) {
|
2017-08-31 12:22:52 +02:00
|
|
|
let index = fields.indexOf(f);
|
2018-05-02 18:04:48 +02:00
|
|
|
this._fields.set(f.name, fields.splice(index, 1));
|
2017-08-31 12:22:52 +02:00
|
|
|
} else {
|
|
|
|
this._fields.delete();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-26 23:44:47 +02:00
|
|
|
/**
|
|
|
|
* serialize
|
|
|
|
*
|
|
|
|
* @returns {Object}
|
|
|
|
*/
|
2018-05-02 18:04:48 +02:00
|
|
|
serialize() {
|
2017-08-26 23:44:47 +02:00
|
|
|
let json = {};
|
2018-05-02 18:04:48 +02:00
|
|
|
for (let [k, f] of this._fields) {
|
|
|
|
if (Array.isArray(f)) {
|
|
|
|
if (k != "") {
|
2017-08-26 23:44:47 +02:00
|
|
|
json[k] = [];
|
|
|
|
} else {
|
|
|
|
json = [];
|
|
|
|
}
|
2018-05-02 18:04:48 +02:00
|
|
|
for (let key in f) {
|
2018-05-03 16:03:34 +02:00
|
|
|
if (f[key].type == "radio") {
|
|
|
|
json = {}
|
|
|
|
|
|
|
|
let d = f[key].serialize();
|
|
|
|
if (d !== null) {
|
|
|
|
if (k == "") {
|
|
|
|
json = d;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
json[k] = d;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
2018-05-02 18:04:48 +02:00
|
|
|
if (f[key]) {
|
2017-08-31 12:21:39 +02:00
|
|
|
let d = f[key].serialize();
|
2018-05-03 12:33:36 +02:00
|
|
|
if (d == null) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if(k == "") {
|
2018-05-03 15:07:54 +02:00
|
|
|
if(f[key].flatten || this.flatten){
|
2018-05-03 12:33:36 +02:00
|
|
|
json[key].push(d);
|
|
|
|
} else {
|
2017-08-31 12:21:39 +02:00
|
|
|
json[key] = d;
|
|
|
|
}
|
2018-05-03 12:33:36 +02:00
|
|
|
|
|
|
|
continue
|
2017-08-26 23:44:47 +02:00
|
|
|
}
|
2018-05-03 12:33:36 +02:00
|
|
|
|
2018-05-03 15:07:54 +02:00
|
|
|
if(f[key].flatten || this.flatten){
|
2018-05-03 12:33:36 +02:00
|
|
|
json[k].push(d);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
json[k][key] = d;
|
2017-08-26 23:44:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2017-08-31 12:21:39 +02:00
|
|
|
let d = f.serialize();
|
2018-05-03 14:27:55 +02:00
|
|
|
if (d !== null) {
|
2018-05-02 18:04:48 +02:00
|
|
|
if (k == "") {
|
|
|
|
json = d;
|
2017-08-31 12:21:39 +02:00
|
|
|
continue;
|
|
|
|
}
|
2018-05-02 18:04:48 +02:00
|
|
|
json[k] = d;
|
2017-08-26 23:44:47 +02:00
|
|
|
}
|
2018-05-02 18:04:48 +02:00
|
|
|
}
|
2017-08-26 23:44:47 +02:00
|
|
|
}
|
2018-05-03 14:27:55 +02:00
|
|
|
return Object.keys(json).length > 0 ? json : null;}
|
2017-08-26 23:44:47 +02:00
|
|
|
}
|
2017-08-31 12:21:39 +02:00
|
|
|
|
|
|
|
class Validator {
|
|
|
|
/**
|
|
|
|
* validateRequired
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @param {(erializer|SerializerField)} field
|
|
|
|
* @returns {Object}
|
|
|
|
*/
|
2018-05-02 18:04:48 +02:00
|
|
|
static validateRequired(field) {
|
2017-08-31 12:21:39 +02:00
|
|
|
let e = {
|
|
|
|
valid: true,
|
2018-05-02 18:04:48 +02:00
|
|
|
errors: {}
|
|
|
|
};
|
2017-08-31 12:21:39 +02:00
|
|
|
let v;
|
2018-05-02 18:04:48 +02:00
|
|
|
if (field instanceof Serializer) {
|
2017-08-31 12:21:39 +02:00
|
|
|
v = Validator.validateRequiredSerializer(field);
|
|
|
|
} else {
|
2018-05-02 18:04:48 +02:00
|
|
|
v = Validator.validateRequiredSerialzerField(field);
|
2017-08-31 12:21:39 +02:00
|
|
|
}
|
2018-05-02 18:04:48 +02:00
|
|
|
if (!v.valid) {
|
2017-08-31 12:21:39 +02:00
|
|
|
e.valid = false;
|
|
|
|
e.errors = v.errors;
|
|
|
|
}
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* validateRequiredSerializer
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @param {Serializer} field
|
|
|
|
* @returns {Object}
|
|
|
|
*/
|
2018-05-02 18:04:48 +02:00
|
|
|
static validateRequiredSerializer(field) {
|
2017-08-31 12:21:39 +02:00
|
|
|
let e = {
|
|
|
|
valid: true,
|
2018-05-02 18:04:48 +02:00
|
|
|
errors: {}
|
|
|
|
};
|
|
|
|
|
2017-08-31 12:21:39 +02:00
|
|
|
let min = field.min;
|
|
|
|
let validFieldsCount = 0;
|
2018-05-02 18:04:48 +02:00
|
|
|
for (let [k, f] of field._fields) {
|
|
|
|
if (Array.isArray(f)) {
|
2017-08-31 12:21:39 +02:00
|
|
|
e.errors = {
|
2018-05-02 18:04:48 +02:00
|
|
|
message: null
|
2017-08-31 12:21:39 +02:00
|
|
|
};
|
|
|
|
let validCount = 0;
|
2018-05-02 18:04:48 +02:00
|
|
|
for (let sf of f) {
|
|
|
|
if (!(sf.name in e.errors)) {
|
2017-08-31 12:21:39 +02:00
|
|
|
e.errors[sf.name] = [];
|
|
|
|
}
|
|
|
|
|
2018-05-02 18:04:48 +02:00
|
|
|
let v = Validator.validateRequired(sf);
|
|
|
|
if (!v.valid) {
|
2018-05-03 12:33:36 +02:00
|
|
|
console.error(e.errors);
|
2018-05-02 18:04:48 +02:00
|
|
|
e.errors[sf.name].push(v.errors);
|
2017-08-31 12:21:39 +02:00
|
|
|
continue;
|
|
|
|
}
|
2018-05-02 18:04:48 +02:00
|
|
|
validCount++;
|
2017-08-31 12:21:39 +02:00
|
|
|
}
|
2018-05-02 18:04:48 +02:00
|
|
|
|
|
|
|
e.valid = min <= validCount;
|
|
|
|
if (e.valid) {
|
|
|
|
e.errors = {};
|
2017-08-31 12:21:39 +02:00
|
|
|
} else {
|
2018-05-02 18:04:48 +02:00
|
|
|
e.errors.message = `Should contain atleast ${min} value(s)`;
|
2017-08-31 12:21:39 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let v = Validator.validateRequired(f);
|
2018-05-02 18:04:48 +02:00
|
|
|
if (!v.valid) {
|
2017-08-31 12:21:39 +02:00
|
|
|
e.valid = false;
|
|
|
|
e.errors[f.name] = v.errors;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* validateRequiredSerialzerField
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @param {SerializerField} field
|
|
|
|
* @returns {Object}
|
|
|
|
*/
|
2018-05-02 18:04:48 +02:00
|
|
|
static validateRequiredSerialzerField(field) {
|
2017-08-31 12:21:39 +02:00
|
|
|
let e = {
|
|
|
|
valid: true,
|
2018-05-02 18:04:48 +02:00
|
|
|
errors: {}
|
|
|
|
};
|
2017-08-31 12:21:39 +02:00
|
|
|
|
2018-05-02 18:04:48 +02:00
|
|
|
if (field.required || (field.parent && field.parent.required)) {
|
|
|
|
if (field.type != "checkbox") {
|
|
|
|
e.valid = field.field.validity.valueMissing !== true;
|
|
|
|
e.errors = "Is required";
|
2017-08-31 12:21:39 +02:00
|
|
|
} else {
|
2018-05-02 18:04:48 +02:00
|
|
|
e.valid = field.field.checked;
|
|
|
|
e.errors = "Not checked";
|
2017-08-31 12:21:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
}
|