OpoTests/Public/ecma/Application/Components.ecma.js

409 lines
15 KiB
JavaScript

"use strict";
import {Check} from "../Utils/Check.ecma.js";
/**
* @typedef {import("../Application/OpoTests.ecma.js").OpoTests} OpoTests
*/
/**
* @class Components
* @constructor
* @param {!OpoTests} ot
* @returns {void}
* @access public
* @static
*/
export const Components = (function(){
/**
* @callback components_event_callback
* @param {!HTMLElement} item
* @param {!Event} event
* @returns {boolean|void}
*/
/**
* @constructs Components
* @param {!OpoTests} ot
* @returns {void}
* @access private
* @static
*/
const Components = function(ot){
/** @type {Components} */
const self = this;
/**
* @returns {void}
* @access private
*/
const constructor = () => {};
/**
* @param {!(string|Array.<string>)} i18n
* @param {!string} [tag = "span"]
* @param {?string} [_default = null]
* @returns {[string, Object.<string, string>, string]}
* @access public
*/
this.i18n = (i18n, tag = "span", _default = null) => [tag, {data_i18n : i18n}, ot.i18n.get(i18n, null, _default)];
/**
* @param {!string} name
* @param {!string} [tag = "span"]
* @returns {[string, Object.<string, string>]}
* @access public
*/
this.icon = (name, tag = "span") => [tag, {data_icon : name}];
/**
* @param {!string} name
* @param {?components_event_callback} [on_click = null]
* @param {!string} [type = "button"]
* @param {?string} [default_text = null]
* @returns {[string, Object.<string, (string|function)>, Array.<(string|[string, Object.<string, string>, string])>]}
* @access public
*/
this.button = (name, on_click = null, type = "button", default_text) => ["button", {
type : type,
data_i18n : name,
data_i18n_without : true,
title : ot.i18n.get(name, null, default_text),
...(on_click ? {on_click : on_click} : {})
}, [
self.icon(name),
self.i18n(name, "span", default_text)
]];
/**
* @param {!string} type
* @param {!string} name
* @param {!boolean} checked
* @param {?components_event_callback} on_change
* @param {?string} default_text
* @returns {[string, Object.<string, (string|function)>, Array.<(string|[string, Object.<string, string>, string])>]}
* @access private
*/
const select_item = (type, name, checked, on_change, default_text) => {
/** @type {string} */
const id = name.slice(-2) == "[]" ? ot.identifiers.get() : name;
return ["label", {
for : id,
class : type + "-input",
data_i18n : name,
data_i18n_without : true,
title : ot.i18n.get(name, null, default_text)
}, [
["input", {
type : type,
id : id,
name : name,
...(on_change ? {on_change : on_change} : {}),
...(checked ? {checked : "checked"} : {})
}],
self.icon(type, "span"),
self.i18n(name, "span", default_text)
]];
};
/**
* @param {!string} name
* @param {!boolean} [checked = false]
* @param {?components_event_callback} [on_change = null]
* @param {?string} [default_text = null]
* @returns {[string, Object.<string, (string|function)>, Array.<(string|[string, Object.<string, string>, string])>]}
*/
this.checkbox = (name, checked = false, on_change = null, default_text) => (
select_item("checkbox", name, checked, on_change, default_text)
);
/**
* @param {!string} name
* @param {!boolean} [checked = false]
* @param {?components_event_callback} [on_change = null]
* @param {?string} [default_text = null]
* @returns {[string, Object.<string, (string|function)>, Array.<(string|[string, Object.<string, string>, string])>]}
*/
this.radio = (name, checked = false, on_change = null, default_text) => (
select_item("radio", name, checked, on_change, default_text)
);
/**
* @param {!string} name
* @param {?number} [value = null]
* @param {?number} [minimum = null]
* @param {?number} [maximum = null]
* @param {?number} [step = null]
* @param {?components_event_callback} [on_change = null]
* @param {?string} [default_text = null]
* @returns {[string, Object.<string, (string|number|function)>, Array.<(string|[string, Object.<string, string>, string])>]}
* @access public
*/
this.number = (name, value = null, minimum = null, maximum = null, step = null, on_change = null, default_text) => {
/** @type {string} */
const text = ot.i18n.get(name, null, default_text);
return ["label", {
for : name,
class : "number-input",
data_i18n : name,
data_i18n_without : true,
title : text,
}, [
["input", {
type : "number",
id : name,
name : name,
...(value !== null ? {value : value} : {}),
...(minimum !== null ? {min : minimum} : {}),
...(maximum !== null ? {max : maximum} : {}),
...(step !== null ? {step : step} : {}),
...(on_change ? {on_change : on_change} : {}),
data_i18n : name,
data_i18n_without : true,
placeholder : text + "..."
}],
["span", {class : "minimum"}, minimum || "-∞"],
["span", {class : "maximum"}, maximum || "∞"]
]];
};
/**
* @param {!string} type
* @param {!string} name
* @param {?string} [value = null]
* @param {?number} [maximum_length = null]
* @param {?string} [pattern = null]
* @param {?components_event_callback} [on_change = null]
* @param {?string} [default_text = null]
* @returns {[string, Object.<string, (string|function)>, Array.<(string|[string, Object.<string, string>, string])>]}
* @access private
*/
const text = (type, name, value = null, maximum_length = null, pattern = null, on_change = null, default_text = null) => {
/** @type {string} */
const text = ot.i18n.get(name, null, default_text);
return ["label", {
for : name,
class : "input-" + type
}, [
["input", {
type : type,
id : name,
name : name,
...(value !== null ? {value : value} : {}),
...(pattern !== null ? {pattern : pattern} : {}),
...(on_change ? {on_input : on_change} : {}),
data_i18n : name,
data_i18n_without : true,
placeholder : text + "..."
}],
["span", {class : "length"}, 0],
...(maximum_length ? [["span", {class : "maximum-length"}, maximum_length || "∞"]] : [])
]];
};
/**
* @param {!string} name
* @param {?string} [value = null]
* @param {?number} [maximum_length = null]
* @param {?string} [pattern = null]
* @param {?components_event_callback} [on_change = null]
* @param {?string} [default_text = null]
* @returns {[string, Object.<string, (string|function)>, Array.<(string|[string, Object.<string, string>, string])>]}
* @access public
*/
this.text = (name, value = null, maximum_length = null, pattern = null, on_change = null, default_text = null) => (
text("text", name, value, maximum_length, pattern, on_change, default_text)
);
/**
* @param {!string} name
* @param {?string} [value = null]
* @param {?number} [maximum_length = null]
* @param {?string} [pattern = null]
* @param {?components_event_callback} [on_change = null]
* @param {?string} [default_text = null]
* @returns {[string, Object.<string, (string|function)>, Array.<(string|[string, Object.<string, string>, string])>]}
* @access public
*/
this.password = (name, value = null, maximum_length = null, pattern = null, on_change = null, default_text = null) => (
text("password", name, value, maximum_length, pattern, on_change, default_text)
);
/**
* @param {...[string, components_event_callback|null, string, string|null]} buttons
* @returns {[string, Object.<string, string>, Array.<(string|[string, Object.<string, string>, string])>]}
* @access public
*/
this.buttons = (...buttons) => ["div", {class : "buttons"}, buttons.map(button => self.button(...button))];
/**
* @param {!string} name
* @param {...any} items
* @returns {[string, Object.<string, string>, Array.<(string|[string, Object.<string, string>, string])>]}
*/
this.group = (name, ...items) => ["div", {
class : "group",
data_i18n : name,
data_i18n_without : true,
title : ot.i18n.get(name)
}, items];
/**
* @param {!string} name
* @param {!Array.<any|null>} structure
* @param {?components_event_callback} on_submit
* @param {...(components_event_callback|null)} [extra_actions]
* @returns {[string, Object.<string, (string|function)>, Array.<(string|[string, Object.<string, string>, string])>]}
* @access public
*/
this.form = (name, structure, on_submit, ...extra_actions) => ["form", {
class : "form",
data_name : name,
method : "get",
action : "#",
...(on_submit ? {on_submit : on_submit} : {})
}, [
["fieldset", {}, [
self.i18n(name, "legend"),
self.i18n(name + "_text", "p"),
["div", {class : "structure"}, structure.map(([type, name, ...item], i) => {
return ["div", {
data_i : i,
data_type : type,
data_i18n : name,
data_i18n_without : true,
title : ot.i18n.get(name, null, item[1] || name)
}, [
["label", {for : name}, [
self.i18n(name),
self.i18n(name + "_description")
]],
["span", {class : "input"}, self[type] ? [self[type](name, ...item)] : item],
["ul", {class : "errors"}]
]];
})],
["ul", {class : "form-errors"}],
self.buttons(
["clean", null, "clean"],
...extra_actions,
on_submit ? ["submit", null, "submit"] : null
)
]]
]];
/**
* @param {!HTMLElement} form
* @returns {HTMLFormElement|null}
* @access public
*/
this.get_form = form => {
if(form)
while(form.tagName.toLowerCase() != "form" && (form = form.parentNode));
return form;
};
/**
* @param {!HTMLElement} form
* @returns {Object.<string, (string|number|boolean|Array.<(string|number|boolean)>)>}
* @access public
*/
this.get_form_data = form => (
[...self.get_form(form).querySelectorAll("[name]")].reduce((data, item) => {
/** @type {string} */
let name_value = item.getAttribute("name");
/** @type {string} */
const name = name_value.replace(/\[\]$/i, ""),
/** @type {boolean} */
is_array = name_value != name;
is_array && !(name in data) && (data[name] = []);
switch(item.getAttribute("type")){
case "radio":
data[name] == [] && (data[name] = false);
item.checked && (data[name] = Number(item.value));
break;
case "checkbox":
if(is_array)
data[name].push(item.checked);
else
data[name] = item.checked;
break;
case "number":
if(is_array)
data[name].push(Number(item.value));
else
data[name] = Number(item.value);
break;
default:
if(is_array)
data[name].push(item.value);
else
data[name] = item.value;
break;
};
return data;
}, {})
);
/**
* @param {!(string|Array.<string>)} sources
* @param {?(string|Array.<string>)} [i18n = null]
* @param {?string} [default_text = null]
* @returns {[string, Object.<string, (string|number|function)>, (string|[string, Object.<string, string>, string])>]}
* @access public
*/
this.image = (sources, i18n = null, default_text = null) => {
/** @type {number} */
let i = 0;
Check.is_array(sources) || (sources = [sources]);
return ["span", {
class : "image",
data_status : "loading",
...(
i18n ? {data_i18n : i18n, title : ot.i18n.get(i18n, null, default_text)} :
default_text ? {title : default_text} :
{}),
}, [
["img", {
src : sources[i],
...(
i18n ? {data_i18n : i18n, alt : ot.i18n.get(i18n, null, default_text)} :
default_text ? {alt : default_text} :
{}),
on_error : (image, event) => {
if(i >= sources.length)
image.parentNode.setAttribute("data-status", "error");
else
image.parentNode.setAttribute("src", sources[++ i]);
},
on_load : (image, event) => {
image.parentNode.setAttribute("data-status", "loaded");
image.parentNode.querySelector("span").style.backgroundImage = "url('" + sources[i] + "')";
}
}],
["span"]
]];
};
constructor();
};
return Components;
})();