first commit
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
//import { Ut } from ".";
|
||||
|
||||
export class AppendOptions {
|
||||
|
||||
constructor(target, data, opts) {
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
if (opts.clear) {
|
||||
this.dxHtml('');
|
||||
}
|
||||
|
||||
if (typeof data === 'string') {
|
||||
|
||||
if (data == '{}') {
|
||||
return this;
|
||||
}
|
||||
|
||||
data.slice(1).slice(0, -1).split(',').forEach(function(token) {
|
||||
|
||||
let option = token.split(':').map(function(a) {
|
||||
return a.slice(1).slice(0, -1);
|
||||
});
|
||||
|
||||
target.append(new Option(option[1], option[0]));
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
||||
for (let id in data) {
|
||||
target.append(new Option(data[id], id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ut.extendNodeUx(AppendOptions);
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Ut } from "./Ut";
|
||||
import { FormValidator } from './form-validator/FormValidator';
|
||||
|
||||
////////////////////
|
||||
// Class Form
|
||||
////////////////////
|
||||
|
||||
export class Form extends FormValidator {
|
||||
|
||||
pendingRequest = false;
|
||||
|
||||
constructor(target, caller, opts) {
|
||||
|
||||
super(target, opts);
|
||||
|
||||
const that = this;
|
||||
opts = opts || {};
|
||||
this.reqOpts = {};
|
||||
|
||||
this.opts.onSubmit = () => {
|
||||
|
||||
if (Ut.isFn(opts.onSubmit) && !opts.onSubmit(this.entry, this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let btnIcon = null;
|
||||
let btnIconCls = null;
|
||||
|
||||
if (opts.btnLoader) {
|
||||
|
||||
btnIcon = target.dxFind('button[type="submit"] i');
|
||||
|
||||
if (btnIcon) {
|
||||
btnIconCls = btnIcon.className;
|
||||
btnIcon.className = 'icon-left fas fa-rotate-right fa-spin fz14';
|
||||
}
|
||||
}
|
||||
|
||||
that.pendingRequest = true;
|
||||
this.reqOpts.vars = this.entry;
|
||||
|
||||
caller(this.reqOpts).then(jr => {
|
||||
|
||||
that.pendingRequest = false;
|
||||
|
||||
if (btnIcon) {
|
||||
btnIcon.className = btnIconCls;
|
||||
}
|
||||
|
||||
Ut.trigger(opts.done, jr.code, jr.data, that.target.elements);
|
||||
|
||||
}).catch(error => {
|
||||
that.pendingRequest = false;
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//Ut.extendNodeUx(Form);
|
||||
@@ -0,0 +1,127 @@
|
||||
import { Ut } from './Ut';
|
||||
|
||||
export class Options {
|
||||
|
||||
/**
|
||||
* Options, Contructor
|
||||
*/
|
||||
constructor(target, caller, events, opts) {
|
||||
|
||||
this.target = target;
|
||||
|
||||
//if (this.target.dxData('options')) {
|
||||
// return;
|
||||
//}
|
||||
|
||||
//this.target.dxData('options', this);
|
||||
|
||||
this.caller = caller;
|
||||
this.events = events || {};
|
||||
this.opts = { ... { clear: false, cache: false }, ... opts };
|
||||
this.size = 0;
|
||||
|
||||
this.targetName = this.target.name;
|
||||
|
||||
Resort.data.options = Resort.data.options || {};
|
||||
|
||||
this.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Options, Create
|
||||
*/
|
||||
create() {
|
||||
|
||||
let that = this;
|
||||
|
||||
if (this.opts.clear) {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
if (this.opts.cache && Resort.data.options[ this.targetName ]) {
|
||||
this.appendOptions(Resort.data.options[ this.targetName ]);
|
||||
}
|
||||
else {
|
||||
|
||||
this.request(data => {
|
||||
|
||||
that.appendOptions(data);
|
||||
|
||||
if (that.opts.cache) {
|
||||
Resort.data.options[ that.targetName ] = data;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options, Append Options
|
||||
*/
|
||||
appendOptions(data) {
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
let that = this;
|
||||
|
||||
if (typeof data === 'object') {
|
||||
|
||||
each(data, (val, id) => {
|
||||
that.size++;
|
||||
that.target.append(new Option(val, id));
|
||||
});
|
||||
}
|
||||
else if (typeof data === 'string' && data != '{}') {
|
||||
|
||||
data.slice(1).slice(0, -1).split(',').forEach(function(token) {
|
||||
|
||||
let opt = token.split(':').map(function(a) {
|
||||
that.size++;
|
||||
return a.slice(1).slice(0, -1);
|
||||
});
|
||||
|
||||
that.target.append(new Option(opt[1], opt[0]));
|
||||
});
|
||||
}
|
||||
|
||||
Ut.trigger(this.events.update, this);
|
||||
|
||||
let val = this.target.dataset.val;
|
||||
|
||||
if (val) {
|
||||
this.target.dxVal(val).dxTrigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options, Request
|
||||
*/
|
||||
request(callback) {
|
||||
|
||||
this.caller().then(jr => {
|
||||
|
||||
if (jr.code == Rc.DONE) {
|
||||
callback(data);
|
||||
}
|
||||
|
||||
}).catch(error => console.error(error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Options, Clear
|
||||
*/
|
||||
clear() {
|
||||
|
||||
let options = this.target.options;
|
||||
|
||||
if (options.length && (options[0].value == '' || options[0].value == 0)) {
|
||||
this.target.dxFind('option', (option, i) => i && option.remove());
|
||||
}
|
||||
else {
|
||||
this.target.dxHtml('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Ut.extendNodeUx(Options);
|
||||
@@ -0,0 +1,71 @@
|
||||
export class Password {
|
||||
|
||||
// Random Pwd Utils
|
||||
|
||||
static ALPHA_LOWER_CHARS = "abcdefghijklmnopqrstuvwxyz";
|
||||
static ALPHA_UPPER_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
static NUMERIC_CHARS = "0123456789"
|
||||
static SPECIAL_CHARS = "~!@#$%^&*()_-=+{}[];<,.>?"
|
||||
static HASH_CHARS = "abcdef0123456789"
|
||||
|
||||
static ALPHA_LOWER = 0x01;
|
||||
static ALPHA_UPPER = 0x02;
|
||||
static NUMERIC = 0x08;
|
||||
static SPECIAL = 0x10;
|
||||
static HASH = 0x20;
|
||||
|
||||
/**
|
||||
* Randowm Password Generator
|
||||
*/
|
||||
generate(flags, length) {
|
||||
|
||||
let chList = [];
|
||||
let n = 0;
|
||||
|
||||
if (flags & Password.ALPHA_LOWER) {
|
||||
chList.push(Password.ALPHA_LOWER_CHARS);
|
||||
n++;
|
||||
}
|
||||
|
||||
if (flags & Password.ALPHA_UPPER) {
|
||||
chList.push(Password.ALPHA_UPPER_CHARS);
|
||||
n++;
|
||||
}
|
||||
|
||||
if (flags & Password.NUMERIC) {
|
||||
chList.push(Password.NUMERIC_CHARS);
|
||||
n++;
|
||||
}
|
||||
|
||||
if (flags & Password.SPECIAL) {
|
||||
chList.push(Password.SPECIAL_CHARS);
|
||||
n++;
|
||||
}
|
||||
|
||||
if (flags & Password.HASH) {
|
||||
chList.push(Password.HASH_CHARS);
|
||||
n++;
|
||||
}
|
||||
|
||||
let div = Math.floor(length / n);
|
||||
let rem = length % n;
|
||||
|
||||
let chars = [];
|
||||
|
||||
for (let i = 0; i < chList.length; i++) {
|
||||
|
||||
let chSet = chList[i];
|
||||
|
||||
if (i + 1 == chList.length) {
|
||||
div += rem;
|
||||
}
|
||||
|
||||
for (let j = 0; j < div; j++) {
|
||||
chars.push(chSet.charAt( Math.floor(Math.random() * chSet.length) ));
|
||||
}
|
||||
}
|
||||
|
||||
return chars.sort(() => Math.random()-.5).join('');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Ut } from "./Ut";
|
||||
|
||||
export class Print{
|
||||
|
||||
constructor(opts) {
|
||||
|
||||
opts = { ... { width: 1200, height: 800, lang: 'en' }, ... opts };
|
||||
|
||||
let win = window.open('', 'print', 'width=' + opts.width + ',height=' + opts.height);
|
||||
|
||||
let doc = win.document;
|
||||
|
||||
doc.write('<html lang="'+ opts.lang +'"><head><title>Print</title>');
|
||||
|
||||
if (opts.style) {
|
||||
doc.write('<link rel="stylesheet" href="'+ opts.style +'" type="text/css" />');
|
||||
}
|
||||
|
||||
doc.write('</head><body>');
|
||||
doc.write(this.cloneNode(true).innerHTML);
|
||||
doc.write('</body></html>');
|
||||
|
||||
doc.close();
|
||||
|
||||
setTimeout(() => win.print(), 500);
|
||||
|
||||
win.onafterprint = function() {
|
||||
win.close();
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
Ut.extendNodeUx(Print);
|
||||
@@ -0,0 +1,199 @@
|
||||
export class SwipeEvents {
|
||||
|
||||
constructor(currentTarget, target, opts) {
|
||||
|
||||
this.currentTarget = currentTarget;
|
||||
target = target || null;
|
||||
this.opts = { desktopSwipe: false, ... opts };
|
||||
|
||||
this.uiDev = {
|
||||
touchCapable : ('ontouchstart' in window), // Is Mobile Device Flag
|
||||
isMobile : /Mobi/.test(navigator.userAgent),
|
||||
swipe_h_threshold : 50,
|
||||
swipe_v_threshold : 50,
|
||||
startevent : ('ontouchstart' in window) ? 'touchstart' : 'mousedown',
|
||||
endevent : ('ontouchstart' in window) ? 'touchend' : 'mouseup',
|
||||
moveevent : ('ontouchstart' in window) ? 'touchmove' : 'mousemove'
|
||||
};
|
||||
|
||||
if (!opts.desktopSwipe && !uiDev.isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
let duplicate = this.dxData('swipe') || 0;
|
||||
|
||||
this.dxData('swipe', ++duplicate);
|
||||
|
||||
if (duplicate > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.started = false,
|
||||
this.hasSwiped = false,
|
||||
this.originalCoord = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
|
||||
this.finalCoord = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
|
||||
this.startEvnt = {};
|
||||
|
||||
this.dxOn(this.uiDev.startevent + '.' + 'swipe', e => this.touchStart(e), target, { passive: true } );
|
||||
this.dxOn(this.uiDev.moveevent + '.' + 'swipe', e => this.touchMove(e), target, { passive: true });
|
||||
this.dxOn(this.uiDev.endevent + '.' + 'swipe', e => this.touchEnd(e), target, { passive: true });
|
||||
}
|
||||
|
||||
// Screen touched, store the original coordinate
|
||||
touchStart(e) {
|
||||
|
||||
this.originalCoord.x = (e.targetTouches) ? e.targetTouches[0].pageX : e.pageX;
|
||||
this.originalCoord.y = (e.targetTouches) ? e.targetTouches[0].pageY : e.pageY;
|
||||
this.finalCoord.x = originalCoord.x;
|
||||
this.finalCoord.y = originalCoord.y;
|
||||
this.started = true;
|
||||
|
||||
let offset = this.currentTarget.dxOffset();
|
||||
|
||||
// Read event data into our startEvt:
|
||||
this.startEvnt = {
|
||||
'position': {
|
||||
'x': (this.uiDev.touchCapable) ? e.touches[0].pageX : e.pageX,
|
||||
'y': (this.uiDev.touchCapable) ? e.touches[0].pageY : e.pageY
|
||||
},
|
||||
'offset': {
|
||||
'x': (this.uiDev.touchCapable) ? Math.round(e.changedTouches[0].pageX - offset.left) : Math.round(e.pageX - offset.left),
|
||||
'y': (this.uiDev.touchCapable) ? Math.round(e.changedTouches[0].pageY - offset.top) : Math.round(e.pageY - offset.top)
|
||||
},
|
||||
'time': Date.now(),
|
||||
'target': e.target
|
||||
};
|
||||
}
|
||||
|
||||
// Store coordinates as finger is swiping
|
||||
touchMove(e) {
|
||||
|
||||
this.finalCoord.x = (e.targetTouches) ? e.targetTouches[0].pageX : e.pageX;
|
||||
this.finalCoord.y = (e.targetTouches) ? e.targetTouches[0].pageY : e.pageY;
|
||||
|
||||
let swipedir;
|
||||
|
||||
// We need to check if the element to which the event was bound contains a data-xthreshold | data-vthreshold:
|
||||
let ele_x_threshold = (this.currentTarget.parentNode.dxData('xthreshold')) ? this.currentTarget.parentNode.dxData('xthreshold') : this.currentTarget.dxData('xthreshold'),
|
||||
ele_y_threshold = (this.currentTarget.parentNode.dxData('ythreshold')) ? this.currentTarget.parentNode.dxData('ythreshold') : this.currentTarget.dxData('ythreshold'),
|
||||
h_threshold = (typeof ele_x_threshold !== 'undefined' && ele_x_threshold !== false && parseInt(ele_x_threshold)) ? parseInt(ele_x_threshold) : this.uiDev.swipe_h_threshold,
|
||||
v_threshold = (typeof ele_y_threshold !== 'undefined' && ele_y_threshold !== false && parseInt(ele_y_threshold)) ? parseInt(ele_y_threshold) : this.uiDev.swipe_v_threshold;
|
||||
|
||||
if (this.originalCoord.y > this.finalCoord.y && (this.originalCoord.y - this.finalCoord.y > v_threshold)) {
|
||||
swipedir = 'swipeup';
|
||||
}
|
||||
|
||||
if (originalCoord.x < finalCoord.x && (finalCoord.x - originalCoord.x > h_threshold)) {
|
||||
swipedir = 'swiperight';
|
||||
}
|
||||
|
||||
if (originalCoord.y < finalCoord.y && (finalCoord.y - originalCoord.y > v_threshold)) {
|
||||
swipedir = 'swipedown';
|
||||
}
|
||||
|
||||
if (this.originalCoord.x > this.finalCoord.x && (this.originalCoord.x - this.finalCoord.x > h_threshold)) {
|
||||
swipedir = 'swipeleft';
|
||||
}
|
||||
|
||||
if (swipedir != undefined && this.started) {
|
||||
|
||||
originalCoord.x = 0;
|
||||
originalCoord.y = 0;
|
||||
finalCoord.x = 0;
|
||||
finalCoord.y = 0;
|
||||
this.started = false;
|
||||
|
||||
let offset = this.currentTarget.dxOffset();
|
||||
|
||||
let endEvnt = {
|
||||
'position': {
|
||||
'x': (this.uiDev.touchCapable) ? e.touches[0].pageX : e.pageX,
|
||||
'y': (this.uiDev.touchCapable) ? e.touches[0].pageY : e.pageY
|
||||
},
|
||||
'offset': {
|
||||
'x': (this.uiDev.touchCapable) ? Math.round(e.changedTouches[0].pageX - offset.left) : Math.round(e.pageX - offset.left),
|
||||
'y': (this.uiDev.touchCapable) ? Math.round(e.changedTouches[0].pageY - offset.top) : Math.round(e.pageY - offset.top)
|
||||
},
|
||||
'time': Date.now(),
|
||||
'target': e.target
|
||||
};
|
||||
|
||||
// Calculate the swipe amount (normalized):
|
||||
let xAmount = Math.abs(this.startEvnt.position.x - endEvnt.position.x),
|
||||
yAmount = Math.abs(this.startEvnt.position.y - endEvnt.position.y);
|
||||
|
||||
const touchData = {
|
||||
'startEvnt' : this.startEvnt,
|
||||
'endEvnt' : endEvnt,
|
||||
'direction' : swipedir.replace('swipe', ''),
|
||||
'xAmount' : xAmount,
|
||||
'yAmount' : yAmount,
|
||||
'duration' : endEvnt.time - this.startEvnt.time
|
||||
};
|
||||
|
||||
this.hasSwiped = true;
|
||||
|
||||
this.currentTarget.dxTrigger('swipe', touchData).dxTrigger(swipedir, null, touchData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch End
|
||||
*/
|
||||
touchEnd(e) {
|
||||
this.started = false;
|
||||
this.hasSwiped = false;
|
||||
}
|
||||
|
||||
destroy(target) {
|
||||
|
||||
if (!this.opts.desktopSwipe && !this.uiDev.isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
let duplicate = this.dxData('swipe') || 0;
|
||||
|
||||
this.dxData('swipe', --duplicate);
|
||||
|
||||
if (!duplicate) {
|
||||
|
||||
if (target) {
|
||||
this.dxOff(this.uiDev.startevent + '.' + 'swipe', target);
|
||||
this.dxOff(this.uiDev.moveevent + '.' + 'swipe', target);
|
||||
this.dxOff(this.uiDev.endevent + '.' + 'swipe', target);
|
||||
}
|
||||
else {
|
||||
this.dxOff(this.uiDev.startevent + '.' + 'swipe');
|
||||
this.dxOff(this.uiDev.moveevent + '.' + 'swipe');
|
||||
this.dxOff(this.uiDev.endevent + '.' + 'swipe');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Custom Swipe Events
|
||||
*/
|
||||
export const customEvents = {
|
||||
|
||||
swipe: {
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
customEvents.swipeup = customEvents.swipe;
|
||||
customEvents.swiperight = customEvents.swipe;
|
||||
customEvents.swipedown = customEvents.swipe;
|
||||
customEvents.swipeleft = customEvents.swipe;
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
const stamp = Date.now();
|
||||
|
||||
export class Ut {
|
||||
|
||||
static EventsUID = 'Events' + stamp;
|
||||
static DataUID = 'Data' + stamp;
|
||||
|
||||
static isSet = (val) => typeof val !== 'undefined';
|
||||
static isFn = (val) => typeof val === 'function';
|
||||
static isStr = (val) => typeof val === 'string';
|
||||
static ucFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
||||
static lcFirst = (str) => str.charAt(0).toLowerCase() + str.slice(1);
|
||||
static isEmptyObj = obj => !Object.keys(obj).length;
|
||||
static objVal = obj => Object.values(obj)[0];
|
||||
|
||||
static trigger = (handler, ...args) => typeof handler === 'function' ? handler.call(this, ...args) : null;
|
||||
|
||||
static each(obj, callback, context) {
|
||||
for (let key in obj) {
|
||||
callback.call(context || this, obj[key], key);
|
||||
}
|
||||
}
|
||||
|
||||
static tplString = (str, context) => new Function('return `' + str + '`;').call(context || {});
|
||||
|
||||
static extendNode(name, value) {
|
||||
|
||||
if (!Object.getOwnPropertyDescriptor(Node.prototype, name)) {
|
||||
Object.defineProperty(Node.prototype, name, {
|
||||
value: value,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static extendNodeUx(...classRefs) {
|
||||
|
||||
classRefs.forEach(classRef => {
|
||||
|
||||
const clsName = `ux${classRef.name}`;
|
||||
|
||||
if (!Object.getOwnPropertyDescriptor(Node.prototype, clsName)) {
|
||||
Object.defineProperty(Node.prototype, clsName, {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: function (...args) {
|
||||
return new classRef(this, ...args);
|
||||
}}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static device = {
|
||||
touchCapable : ('ontouchstart' in window), // Is Mobile Device Flag
|
||||
isMobile : /Mobi/.test(navigator.userAgent),
|
||||
swipe_h_threshold : 50,
|
||||
swipe_v_threshold : 50,
|
||||
startevent : ('ontouchstart' in window) ? 'touchstart' : 'mousedown',
|
||||
endevent : ('ontouchstart' in window) ? 'touchend' : 'mouseup',
|
||||
moveevent : ('ontouchstart' in window) ? 'touchmove' : 'mousemove'
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Redirect
|
||||
*/
|
||||
static url = (location) => window.location.href = location;
|
||||
|
||||
/**
|
||||
* Decode HTML entity
|
||||
*/
|
||||
static unescape = str => ((typeof str === 'string') && (new RegExp(/&|<|>|"|'/).test(str)))
|
||||
? str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/'/g, "'")
|
||||
: str;
|
||||
|
||||
/**
|
||||
* Encode HTML Entities
|
||||
*/
|
||||
htmlEntities = str => String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
|
||||
/**
|
||||
* Convert RGB to Hex
|
||||
*/
|
||||
rgb2hex = rgb => '#' + rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/).slice(1).map(n => parseInt(n, 10).toString(16).padStart(2, '0')).join('');
|
||||
|
||||
/**
|
||||
* Copy To Clipboard
|
||||
*/
|
||||
static copyToClipboard(target) {
|
||||
|
||||
if (navigator.clipboard) {
|
||||
|
||||
if (target instanceof Node) {
|
||||
|
||||
target.select();
|
||||
target.setSelectionRange(0, 99999);
|
||||
|
||||
target = target.value;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(target);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let tval = null;
|
||||
|
||||
if (target instanceof Node) {
|
||||
|
||||
if (target.dataset.notrusted) {
|
||||
delete target.dataset.notrusted;
|
||||
return;
|
||||
}
|
||||
|
||||
tval = target.value;
|
||||
}
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.className = 'hidden';
|
||||
textarea.value = tval || target;
|
||||
|
||||
document.body.prepend(textarea);
|
||||
textarea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
finally {
|
||||
|
||||
textarea.remove();
|
||||
|
||||
if (target instanceof Node) {
|
||||
|
||||
target.dataset.notrusted = 1;
|
||||
|
||||
target.select();
|
||||
target.setSelectionRange(0, 99999);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,765 @@
|
||||
import { Ut } from './Ut';
|
||||
|
||||
/**
|
||||
* Smart Query Selector
|
||||
*/
|
||||
export function $(selector, list) {
|
||||
|
||||
// DOM parser
|
||||
if (selector.startsWith('<')) {
|
||||
|
||||
// Case 1 -> return Node
|
||||
let template = document.createElement('template');
|
||||
template.innerHTML = selector;
|
||||
return template.content.firstChild;
|
||||
}
|
||||
else if (list === true) {
|
||||
// Case 2 -> return Nodelist
|
||||
return document.querySelectorAll(selector);
|
||||
}
|
||||
else if (typeof list === 'function') {
|
||||
// Case 3 -> return Array considering the callback filter
|
||||
return [... document.querySelectorAll(selector)].filter(list);
|
||||
}
|
||||
|
||||
// Most Cases -> return Node
|
||||
return document.querySelector(selector);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a vdom element
|
||||
*/
|
||||
export function El(tag, attr, ...children) {
|
||||
|
||||
attr = attr || {};
|
||||
|
||||
let elem = document.createElement(tag);
|
||||
|
||||
for (let name in attr) {
|
||||
|
||||
if (attr[name] === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
elem.setAttribute(name, attr[name]);
|
||||
}
|
||||
|
||||
children.forEach(child => child && elem.append(child));
|
||||
|
||||
return elem;
|
||||
};
|
||||
|
||||
/**
|
||||
* Static Event Options
|
||||
*/
|
||||
export class EvtOptions {
|
||||
static nsEvent = false;
|
||||
static section = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Dom Uti;s
|
||||
*/
|
||||
export const domUtils = {
|
||||
|
||||
/**
|
||||
* Attach an event handler function for one or more elements.
|
||||
*/
|
||||
on(events, selector, handler, opts) {
|
||||
|
||||
let nsPref = (EvtOptions.nsEvent && EvtOptions.section) ? (EvtOptions.section + '.') : '';
|
||||
|
||||
opts = { ... opts };
|
||||
|
||||
if (!this[ Ut.EventsUID ]) {
|
||||
this[ Ut.EventsUID ] = {};
|
||||
}
|
||||
|
||||
if (!Ut.isSet(handler) || handler === null) {
|
||||
|
||||
events.split(' ').forEach(function(evt) {
|
||||
|
||||
evt = evt.split('.');
|
||||
|
||||
let eName = evt[0];
|
||||
let namespace = nsPref + (evt[1] || '*');
|
||||
|
||||
if (!this[ Ut.EventsUID ][ eName ]) {
|
||||
this[ Ut.EventsUID ][ eName ] = [];
|
||||
}
|
||||
|
||||
let handler = function(event) {
|
||||
|
||||
if (!event.detail || !event.detail.namespace || event.detail.namespace == nsPref + '*' || event.detail.namespace == namespace) {
|
||||
selector.call(this, event, this, event.detail && event.detail.data && event.detail.data || null );
|
||||
}
|
||||
};
|
||||
|
||||
this[ Ut.EventsUID ][ eName ].push([ namespace, null, handler ]);
|
||||
|
||||
if (eName.startsWith('swipe')) {
|
||||
// new SwipeEvents( this, eName, null, handler, opts );
|
||||
}
|
||||
|
||||
this.addEventListener(eName, handler, opts);
|
||||
|
||||
}, this);
|
||||
}
|
||||
else {
|
||||
|
||||
events.split(' ').forEach(function(evt) {
|
||||
|
||||
evt = evt.split('.');
|
||||
|
||||
let eName = evt[0];
|
||||
let namespace = nsPref + (evt[1] || '*');
|
||||
|
||||
if (!this[ Ut.EventsUID ][ eName ]) {
|
||||
this[ Ut.EventsUID ][ eName ] = [];
|
||||
}
|
||||
|
||||
let _handler = function(event) {
|
||||
|
||||
let target = event.target;
|
||||
|
||||
while (target && target !== this) {
|
||||
|
||||
if (target.matches(selector)) {
|
||||
|
||||
if (!event.detail || !event.detail.namespace || event.detail.namespace == nsPref + '*' || event.detail.namespace == namespace) {
|
||||
handler.call(target, event, target, event.detail && event.detail.data && event.detail.data || null);
|
||||
}
|
||||
|
||||
if (!opts.allChildren) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
target = target.parentNode;
|
||||
}
|
||||
};
|
||||
|
||||
this[ Ut.EventsUID ][ eName ].push([ namespace, selector, _handler ]);
|
||||
|
||||
if (eName.startsWith('swipe')) {
|
||||
// new SwipeEvents(this, eName, target, _handler, opts);
|
||||
}
|
||||
|
||||
this.addEventListener(eName, _handler, opts);
|
||||
|
||||
}, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an event handler.
|
||||
*/
|
||||
off(events, selector) {
|
||||
|
||||
let nsPref = (EvtOptions.nsEvent && EvtOptions.section) ? (EvtOptions.section + '.') : '';
|
||||
|
||||
if (!this[Ut.EventsUID]) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!events) {
|
||||
this.replaceWith(this.cloneNode(true));
|
||||
return this;
|
||||
}
|
||||
|
||||
events.split(' ').forEach(function(evt) {
|
||||
|
||||
evt = evt.split('.');
|
||||
|
||||
let eName = evt[0];
|
||||
let namespace = nsPref + (evt[1] || '*');
|
||||
|
||||
let eList = this[Ut.EventsUID][eName];
|
||||
|
||||
if (eList) {
|
||||
|
||||
let i = eList.length;
|
||||
|
||||
while(i--) {
|
||||
|
||||
let et = eList[i];
|
||||
|
||||
if ((et[0] == namespace || namespace == nsPref + '*') && (!selector || (et[1] == selector)) ) {
|
||||
|
||||
//if (customEvents[eName]) {
|
||||
// customEvents[eName].destroy.call(this, eName, et[1]);
|
||||
//}
|
||||
|
||||
if (eName.startsWith('swipe')) {
|
||||
|
||||
}
|
||||
|
||||
this.removeEventListener(eName, et[2]);
|
||||
|
||||
eList.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Find descendents
|
||||
*/
|
||||
find(selector, list) {
|
||||
|
||||
if (list === true) {
|
||||
// Case 1 -> Return NodeList
|
||||
return this.querySelectorAll(selector);
|
||||
}
|
||||
else if (typeof list === 'function') {
|
||||
// Case 2 -> return Array considering the callback filter
|
||||
return [... this.querySelectorAll(selector)].filter(list);
|
||||
}
|
||||
|
||||
// Most cases -> Return First Node
|
||||
return this.querySelector(selector);
|
||||
},
|
||||
|
||||
children(selector, list) {
|
||||
|
||||
if (list === true) {
|
||||
|
||||
// Case 1 -> Array
|
||||
|
||||
if (selector) {
|
||||
|
||||
let children = [];
|
||||
|
||||
[...this.children].forEach(child => {
|
||||
if (child.matches(selector)) {
|
||||
children.push(child);
|
||||
}
|
||||
});
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
return [...this.children];
|
||||
}
|
||||
else if (typeof list === 'function') {
|
||||
// Case 2 Callback filter > Return filtered array
|
||||
return [...this.children].filter((child, i) => {
|
||||
if (!selector || child.matches(selector)) {
|
||||
return list(child, i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (selector) {
|
||||
return [...this.children].find(node => node.matches(selector)) || null;
|
||||
}
|
||||
|
||||
return this.children[0] || null;
|
||||
},
|
||||
|
||||
/*
|
||||
* Get Node Index Position
|
||||
*/
|
||||
index(nodes) {
|
||||
return [...nodes].indexOf(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get Nodes Siblings
|
||||
*/
|
||||
siblings(selector, list) {
|
||||
|
||||
let sibs = [];
|
||||
let elem = this.parentNode.firstChild;
|
||||
|
||||
if (typeof list === 'function') {
|
||||
|
||||
while (elem) {
|
||||
|
||||
if (elem != this && elem.nodeType == 1 && (!selector || elem.matches(selector)) ) {
|
||||
|
||||
if (list(elem)) {
|
||||
sibs.push(elem);
|
||||
}
|
||||
}
|
||||
|
||||
elem = elem.nextElementSibling;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
while (elem) {
|
||||
|
||||
if (elem != this && elem.nodeType == 1 && (!selector || elem.matches(selector)) ) {
|
||||
sibs.push(elem);
|
||||
}
|
||||
|
||||
elem = elem.nextElementSibling;
|
||||
}
|
||||
}
|
||||
|
||||
return sibs;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the next node sibling according to the selector
|
||||
*/
|
||||
next(selector) {
|
||||
|
||||
let node = this.nextElementSibling;
|
||||
|
||||
if (node && selector && !node.matches(selector)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the previous node sibling according to the selector
|
||||
*/
|
||||
prev(selector) {
|
||||
|
||||
let node = this.previousElementSibling;
|
||||
|
||||
if (node && selector && !node.matches(selector)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get node parents according to the selector
|
||||
*/
|
||||
parents(selector, list) {
|
||||
|
||||
let parents = [];
|
||||
let parent = this.parentElement;
|
||||
|
||||
while (parent) {
|
||||
|
||||
if (!selector || parent.matches(selector)) {
|
||||
|
||||
if (!Ut.isSet(list)) {
|
||||
// Case 1 -> Return the closest parent Node
|
||||
return parent;
|
||||
}
|
||||
else if (list === true) {
|
||||
// Case 2 -> It will return an Array
|
||||
parents.push(parent);
|
||||
}
|
||||
}
|
||||
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
|
||||
return list ? parents : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Node Filter
|
||||
*/
|
||||
filter(selector, callback) {
|
||||
|
||||
if (this instanceof Node) {
|
||||
|
||||
if (this.matches(selector)) {
|
||||
return Ut.isFn(callback) ? callback(this) : this;
|
||||
}
|
||||
}
|
||||
else if (this instanceof NodeList) {
|
||||
|
||||
if (callback === true) {
|
||||
return ([...this].filter((item) => item.matches(selector)) );
|
||||
}
|
||||
else if (typeof callback === 'function') {
|
||||
|
||||
return [...this].filter((item) => {
|
||||
if (item.matches(selector)) {
|
||||
return callback(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// return [ ... this ].dxFind(node => node.matches(selector)) || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Dispath An Event
|
||||
*/
|
||||
trigger(evt) {
|
||||
|
||||
evt = evt.split('.');
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent(
|
||||
evt[0], {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
namespace: ((EvtOptions.nsEvent && EvtOptions.section) ? (EvtOptions.section + '.') : '') + (evt[1] || '*'),
|
||||
data: [...arguments].splice(1)
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return this;
|
||||
},
|
||||
css(prop, val) {
|
||||
|
||||
if (Ut.isSet(val)) {
|
||||
|
||||
if (!val) {
|
||||
this.style.removeProperty(prop);
|
||||
}
|
||||
else {
|
||||
this.style[ prop ] = val;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Object.assign(this.style, prop);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
offset() {
|
||||
|
||||
if (!this.getClientRects().length) {
|
||||
return { top: 0, left: 0 };
|
||||
}
|
||||
|
||||
const rect = this.getBoundingClientRect();
|
||||
const win = this.ownerDocument.defaultView;
|
||||
|
||||
return {
|
||||
top: rect.top + win.scrollY,
|
||||
left: rect.left + win.scrollX
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Store / Retreive Arbitrary Data
|
||||
*/
|
||||
data(key, val) {
|
||||
|
||||
this[ Ut.DataUID ] = this[ Ut.DataUID ] || {};
|
||||
|
||||
if (!Ut.isSet(val)) {
|
||||
return this[ Ut.DataUID ][ key ] || null;
|
||||
}
|
||||
|
||||
this[ Ut.DataUID ][ key ] = val;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove Arbitrary Data
|
||||
*/
|
||||
removeData(key) {
|
||||
|
||||
this[ Ut.DataUID ] = this[ Ut.DataUID ] || {};
|
||||
delete this[ Ut.DataUID ][ key ];
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
removeEvents() {
|
||||
this.replaceWith(this.cloneNode(true));
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Inject HTML & Return Node
|
||||
* Or return raw content
|
||||
*/
|
||||
html(content) {
|
||||
|
||||
if (Ut.isSet(content)) {
|
||||
this.innerHTML = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
return this.innerHTML;
|
||||
},
|
||||
|
||||
/** Wrap element
|
||||
*
|
||||
* @return wrappewr
|
||||
*/
|
||||
wrap(wrapper) {
|
||||
this.parentNode.insertBefore(wrapper, this);
|
||||
wrapper.appendChild(this);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set text & Return Node
|
||||
* Or return text content
|
||||
*/
|
||||
text(content) {
|
||||
|
||||
if (Ut.isSet(content)) {
|
||||
this.textContent = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
return this.textContent;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set value & Return Node
|
||||
* Or return trimmed value
|
||||
*/
|
||||
val(content) {
|
||||
|
||||
if (Ut.isSet(content)) {
|
||||
this.value = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
return this.value.trim();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the specified class(es) to the selected element.
|
||||
* @param String classNames One or more space-separated classes to be added
|
||||
* @return Element Selected object
|
||||
*/
|
||||
addClass(classNames) {
|
||||
this.classList.add(...classNames.split(' '));
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Remove the specified class(es) to the selected element.
|
||||
* @param String classNames One or more space-separated classes to be removed
|
||||
* @return Element Selected object
|
||||
*/
|
||||
removeClass(classNames) {
|
||||
this.classList.remove(...classNames.split(' '));
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add or remove a class to the selected Element
|
||||
* @param String classNames
|
||||
* @param bool force If included, turns the toggle into a one way-only operation
|
||||
*/
|
||||
toggleClass(className, force) {
|
||||
this.classList.toggle(className, force);
|
||||
return this;
|
||||
},
|
||||
|
||||
/*
|
||||
* Get Computed Style Property
|
||||
*/
|
||||
getProp(name) {
|
||||
return window.getComputedStyle(this).getPropertyValue(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset Form
|
||||
*/
|
||||
resetForm() {
|
||||
|
||||
this.reset();
|
||||
|
||||
let i = 0;
|
||||
|
||||
for(; i < this.elements.length; i++) {
|
||||
|
||||
let el = this.elements[i];
|
||||
|
||||
if (el.type == 'radio' || el.type == 'checkbox') {
|
||||
el.checked = el.value;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Dom Content Loaded Handler
|
||||
*/
|
||||
ready(callback, context, ...args) {
|
||||
|
||||
context = context || this;
|
||||
|
||||
if (document.readyState != 'loading') {
|
||||
callback.call(context, ...args);
|
||||
}
|
||||
else {
|
||||
document.addEventListener('DOMContentLoaded', callback.bind(context, ...args) );
|
||||
}
|
||||
},
|
||||
|
||||
///////////////////////
|
||||
// Call a widget fn
|
||||
///////////////////////
|
||||
|
||||
handler(widgetName, method, ... args) {
|
||||
|
||||
const context = domUtils.data.call(this, widgetName);
|
||||
|
||||
if (!context) {
|
||||
return this;
|
||||
}
|
||||
|
||||
let handler = context[ method ];
|
||||
|
||||
if (handler && typeof handler == 'function') {
|
||||
handler.call(context, ... args);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill El Form Data
|
||||
*/
|
||||
export function fillData(el, data) {
|
||||
|
||||
for (let key in data) {
|
||||
|
||||
let val = data[key];
|
||||
let target = el[key];
|
||||
|
||||
if (!target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (target instanceof RadioNodeList) { // RadioNodeList
|
||||
|
||||
// Input Radio
|
||||
|
||||
if (target[0].type == 'radio') {
|
||||
|
||||
target.forEach(item => {
|
||||
|
||||
if (item.matches('[value="'+ val +'"]')) {
|
||||
|
||||
item.checked = true;
|
||||
item.parentNode.classList.add('checked');
|
||||
|
||||
domUtils.trigger.call(item, 'change');
|
||||
}
|
||||
else {
|
||||
item.checked = false;
|
||||
item.parentNode.classList.remove('checked');
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
||||
// Input Checkbox
|
||||
|
||||
let i = 0;
|
||||
let choices = [];
|
||||
|
||||
target.forEach((item) => {
|
||||
|
||||
if (item.matches('[value='+ val +']')) {
|
||||
item.checked = true;
|
||||
item.parentNode.classList.add('checked');
|
||||
choices.push(selected.parentNode.textContent);
|
||||
}
|
||||
else {
|
||||
item.checked = false;
|
||||
item.parentNode.classList.remove('checked');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let tagName = target.tagName.toLowerCase();
|
||||
|
||||
switch (tagName) {
|
||||
|
||||
case 'input':
|
||||
|
||||
if (target.type == 'file' && val) {
|
||||
|
||||
let uploader = domUtils.data.call(target, 'uploader');
|
||||
|
||||
if (uploader) {
|
||||
uploader.updateFiles(Array.isArray(val) ? val : [ val ]);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('value' in target.dataset) {
|
||||
|
||||
target.dataset.value = Ut.unescape(val);
|
||||
target.value = Ut.unescape(data[key + '_value'] ?? val);
|
||||
|
||||
if (domUtils.data.call(target, 'datepicker')) {
|
||||
domUtils.trigger.call(target, 'restore')
|
||||
}
|
||||
|
||||
if (domUtils.data.call(target, 'counter')) {
|
||||
domUtils.trigger.call(target, 'input')
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
target.value = Ut.unescape(val);
|
||||
|
||||
break;
|
||||
case 'select':
|
||||
target.value = val;
|
||||
target.dataset.val = val;
|
||||
break;
|
||||
|
||||
case 'textarea':
|
||||
|
||||
target.value = Ut.decode(val);
|
||||
|
||||
const autoexpand = domUtils.data.call(target, 'autoexpand');
|
||||
|
||||
if (autoexpand) { // Set Autoexpand
|
||||
autoexpand.update();
|
||||
}
|
||||
|
||||
if (domUtils.data.call(target, 'counter')) {
|
||||
domUtils.trigger.call(target, 'input')
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DX Assign
|
||||
*/
|
||||
export function domify(prefix = 'dx') {
|
||||
|
||||
Ut.each(domUtils, (fn, k) => Ut.extendNode(`${prefix}${Ut.ucFirst(k)}`, fn) );
|
||||
|
||||
window[ `${prefix}On` ] = domUtils.on;
|
||||
window[ `${prefix}Off` ] = domUtils.off;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dx Ready
|
||||
*/
|
||||
export function dxReady(handler, prefix) {
|
||||
domify(prefix || 'dx');
|
||||
domUtils.ready(handler);
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
import './validator.css';
|
||||
|
||||
import { Ut } from '../Ut';
|
||||
import { El } from '../dom';
|
||||
import { Boot } from '../../core/Boot';
|
||||
import { Validator } from './Validator';
|
||||
|
||||
////////////////////////////
|
||||
// Class Form Validator
|
||||
////////////////////////////
|
||||
|
||||
export class FormValidator extends Validator {
|
||||
|
||||
constructor(target, opts) {
|
||||
|
||||
super(target);
|
||||
|
||||
this.target = target;
|
||||
|
||||
if (this.target.dxData('form')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.target.dxData('form', this);
|
||||
|
||||
this.opts = { ... {
|
||||
submit : false,
|
||||
display_err: true,
|
||||
reset_form : false
|
||||
}, ... opts };
|
||||
|
||||
Ut.trigger(this.opts.onInit, this.target.elements);
|
||||
|
||||
this.setEvents();
|
||||
}
|
||||
|
||||
setEvents() {
|
||||
|
||||
// Click on error UI
|
||||
|
||||
this.target.dxOn('click.ui', '.sp-has-error', (_, t) => this.target.dxFind('input[name="' + t.dxFind('input').name + '"', item => item.parentNode?.classList.remove('sp-has-error')) );
|
||||
|
||||
// Input Focus - Handler
|
||||
|
||||
this.target.dxOn('click.form', 'input, select, textarea', (_, t) => {
|
||||
|
||||
if (this.opts.display_err) {
|
||||
|
||||
let fieldWrapper = t.dxParents('.form-field');
|
||||
|
||||
fieldWrapper?.classList.remove('form-field-error');
|
||||
|
||||
fieldWrapper?.dxFind('.sp-label-error')?.remove();
|
||||
}
|
||||
|
||||
Ut.trigger(this.opts.onClick);
|
||||
});
|
||||
|
||||
// Submit - Handler
|
||||
this.target.dxOn('submit.form', e => {
|
||||
|
||||
if (!this.opts.submit) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
this.entry = this.getData();
|
||||
|
||||
if (Ut.isFn(this.opts.valid) && !this.valid()) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Ut.isFn(this.opts.onSubmit) && !this.opts.onSubmit(this.entry, this)) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.opts.submit) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Ut.isFn(this.opts.onSubmit)) {
|
||||
this.opts.onSubmit.call(this);
|
||||
}
|
||||
|
||||
if (this.opts.reset_form) {
|
||||
this.target.dxResetForm();
|
||||
}
|
||||
});
|
||||
|
||||
// Fix keydown enter bug
|
||||
|
||||
let delayed = false;
|
||||
|
||||
this.target.dxOn('keydown.form', e => {
|
||||
|
||||
if (e.key == 'Enter' && e.target.tagName.toLowerCase() != 'textarea') {
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
if (!e.target.readOnly && !this.pendingRequest && !delayed) {
|
||||
|
||||
delayed = true;
|
||||
|
||||
setTimeout(() => delayed = false, 200);
|
||||
this.target.dxTrigger('submit');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
valid () {
|
||||
|
||||
let pass = true;
|
||||
|
||||
this.target.dxFind('.form-field-error', item => item.classList.remove('form-field-error', 'sp-has-error'));
|
||||
this.target.dxFind('.sp-label-error', item => item.remove());
|
||||
|
||||
let rules = this.opts.valid(this.entry);
|
||||
|
||||
for (let name in rules) {
|
||||
|
||||
if (!(name in this.target.elements)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let el = this.target.elements[ name ];
|
||||
|
||||
if (!('length' in el ) || el.nodeName.toLowerCase() == 'select') {
|
||||
el = [ el ];
|
||||
}
|
||||
|
||||
for (let input of el) {
|
||||
|
||||
let val = (input.type && input.type == 'checkbox')
|
||||
? (input.checked ? input.value : '')
|
||||
: El('p', {}, input.value).textContent.trim();
|
||||
|
||||
let ruleList = rules[ name ].split('|');
|
||||
|
||||
for (let tokenRule of ruleList) {
|
||||
|
||||
let chunks = tokenRule.split(':');
|
||||
|
||||
let args = chunks[1] ?? null;
|
||||
|
||||
let rule = chunks[0];
|
||||
|
||||
if (!this.handlers[rule]) {
|
||||
throw new Error(`The rule "{${rule}}" does not exist`);
|
||||
}
|
||||
|
||||
let format = { value: Validator.INPUT_STR };
|
||||
|
||||
let result = this.handlers[ rule ] (val ?? null, !!input, args, format);
|
||||
|
||||
if (result === false) {
|
||||
|
||||
pass = false;
|
||||
|
||||
let emsg = this.renderRuleMsg(format, rule, args);
|
||||
|
||||
if (this.opts.display_err) {
|
||||
this.inputError(input, emsg);
|
||||
}
|
||||
|
||||
Ut.trigger(this.opts.onError, emsg);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (result === null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pass;
|
||||
}
|
||||
|
||||
renderRuleMsg(format, rule, args) {
|
||||
|
||||
args = args ? args.split(',') : null;
|
||||
|
||||
return Boot.intData.error[ this.formatName(format.value) + '_' + rule ].replace(/\$(\d+)/g, (_, i) => args[i - 1] ?? '')
|
||||
}
|
||||
|
||||
formatName(val) {
|
||||
|
||||
switch (val) {
|
||||
case Validator.INPUT_STR: return 'str';
|
||||
case Validator.INPUT_NUM: return 'num';
|
||||
case Validator.INPUT_FILE: return 'file';
|
||||
default: return 'str';
|
||||
}
|
||||
}
|
||||
|
||||
inputError(input, emsg) {
|
||||
|
||||
if (input.type == 'checkbox' || input.type == 'radio') {
|
||||
input.parentNode.classList.add('sp-has-error');
|
||||
}
|
||||
else {
|
||||
|
||||
let fieldWrapper = input.dxParents('.form-field');
|
||||
|
||||
if (!fieldWrapper) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fieldWrapper.classList.add('form-field-error');
|
||||
|
||||
fieldWrapper.append( El('div', { class: 'sp-label-error' }, emsg ) );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.target.dxOff('submit.form click.form keydown.form click.ui');
|
||||
this.target.dxRemoveData('form');
|
||||
}
|
||||
}
|
||||
|
||||
// Ut.extendNodeUx(FormValidator);
|
||||
@@ -0,0 +1,45 @@
|
||||
import './validator.css';
|
||||
|
||||
//import { Ut } from ".";
|
||||
import { El } from '../dom';
|
||||
|
||||
export class InputError {
|
||||
|
||||
/**
|
||||
* InputError, Constructor
|
||||
*/
|
||||
constructor(target, emsg) {
|
||||
|
||||
this.target = target;
|
||||
this.emsg = emsg;
|
||||
|
||||
this.fieldWrapper = target.dxParents('.form-field');
|
||||
|
||||
if (!this.fieldWrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.fieldWrapper.classList.contains('form-field-error')) {
|
||||
this.create();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* InputError, Create
|
||||
*/
|
||||
create() {
|
||||
this.fieldWrapper.classList.add('form-field-error');
|
||||
this.fieldWrapper.append( El('div', { class: 'sp-label-error' }, this.emsg) );
|
||||
}
|
||||
|
||||
/**
|
||||
* InputError, Destroy
|
||||
*/
|
||||
destroy() {
|
||||
|
||||
this.fieldWrapper.classList.remove('form-field-error');
|
||||
this.fieldWrapper.dxFind('.sp-label-error')?.remove();
|
||||
}
|
||||
}
|
||||
|
||||
//Ut.extendNodeUx(InputError);
|
||||
@@ -0,0 +1,382 @@
|
||||
////////////////////
|
||||
// Class Validator
|
||||
////////////////////
|
||||
|
||||
export class Validator {
|
||||
|
||||
constructor(target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
static INPUT_STR = 1;
|
||||
static INPUT_NUM = 2;
|
||||
static INPUT_FILE = 3;
|
||||
|
||||
handlers = {
|
||||
|
||||
required: (val, present) => present && val !== '' && val !== undefined && val !== null,
|
||||
|
||||
not_empty: (val, present) => present && val !== '' && val !== undefined && val !== null && val !== 0 && val !== '0',
|
||||
|
||||
sometimes: (_, present) => !present ? null : true,
|
||||
|
||||
filled: (val, present) => !present ? null : val !== '' && val !== undefined && val !== null,
|
||||
|
||||
present: (_, present) => present,
|
||||
|
||||
missing: (_, present) => !present,
|
||||
|
||||
nullable: (val, present) => (!present || val === '' || val === undefined || val === null) ? null: true,
|
||||
|
||||
numeric: (val, present, arg, format) => {
|
||||
format.value = Validator.INPUT_NUM;
|
||||
return !present ? null : !isNaN(val);
|
||||
},
|
||||
|
||||
integer: (val, present, arg, format) => {
|
||||
format.value = Validator.INPUT_NUM;
|
||||
return !present ? null : Number.isInteger(Number(val));
|
||||
},
|
||||
|
||||
boolean: (val, present, arg, format) => {
|
||||
format.value = Validator.INPUT_NUM;
|
||||
return !present ? null : [true, false, 0, 1, '0', '1'].includes(val);
|
||||
},
|
||||
|
||||
string: (val, present, arg, format) => {
|
||||
format.value = Validator.INPUT_STR;
|
||||
return !present ? null : typeof val === 'string';
|
||||
},
|
||||
file: (val, present, arg, format) => {
|
||||
|
||||
format.value = Validator.INPUT_FILE;
|
||||
|
||||
return !present ? null : (
|
||||
typeof val === 'object' &&
|
||||
'error' in val &&
|
||||
'tmp_name' in val &&
|
||||
val.error === 0
|
||||
);
|
||||
},
|
||||
|
||||
image: (val, present, arg, format) => {
|
||||
|
||||
format.value = Validator.INPUT_FILE;
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
if (
|
||||
typeof val !== 'object' ||
|
||||
!('error' in val) ||
|
||||
!('tmp_name' in val) ||
|
||||
val.error !== 0 ||
|
||||
typeof val.name !== 'string'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ext = val.name.split('.').pop().toLowerCase();
|
||||
|
||||
return ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'svg', 'webp'].includes(ext);
|
||||
},
|
||||
|
||||
mimes: (val, present, arg, format) => {
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
if (
|
||||
typeof val !== 'object' ||
|
||||
!('error' in val) ||
|
||||
!('tmp_name' in val) ||
|
||||
val.error !== 0 ||
|
||||
typeof val.name !== 'string'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const allowed = arg.split(',').map(ext => ext.trim().toLowerCase());
|
||||
const ext = val.name.split('.').pop().toLowerCase();
|
||||
|
||||
return allowed.includes(ext);
|
||||
},
|
||||
|
||||
min: (val, present, arg, format) => {
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
return (
|
||||
(format.value === Validator.INPUT_STR && val.length >= arg) ||
|
||||
(format.value === Validator.INPUT_NUM && Number(val) >= arg)
|
||||
);
|
||||
},
|
||||
|
||||
max: (val, present, arg, format) => {
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
return (
|
||||
(format.value === Validator.INPUT_STR && val.length <= arg) ||
|
||||
(format.value === Validator.INPUT_NUM && Number(val) <= arg)
|
||||
);
|
||||
},
|
||||
|
||||
size: (val, present, arg, format) => {
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
return (
|
||||
(format.value === Validator.INPUT_STR && val.length == arg) ||
|
||||
(format.value === Validator.INPUT_NUM && Number(val) == arg)
|
||||
);
|
||||
},
|
||||
|
||||
gt: (val, present, arg, format) => {
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
return (
|
||||
(format.value === Validator.INPUT_STR && val.length > arg) ||
|
||||
(format.value === Validator.INPUT_NUM && Number(val) > arg)
|
||||
);
|
||||
},
|
||||
|
||||
gte: (val, present, arg, format) => {
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
return (
|
||||
(format.value === Validator.INPUT_STR && val.length >= arg) ||
|
||||
(format.value === Validator.INPUT_NUM && Number(val) >= arg)
|
||||
);
|
||||
},
|
||||
|
||||
lt: (val, present, arg, format) => {
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
return (
|
||||
(format.value === Validator.INPUT_STR && val.length < arg) ||
|
||||
(format.value === Validator.INPUT_NUM && Number(val) < arg)
|
||||
);
|
||||
},
|
||||
|
||||
lte: (val, present, arg, format) => {
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
return (
|
||||
(format.value === Validator.INPUT_STR && val.length <= arg) ||
|
||||
(format.value === Validator.INPUT_NUM && Number(val) <= arg)
|
||||
);
|
||||
},
|
||||
|
||||
between: (val, present, arg, format) => {
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
const [min, max] = arg.split(',').map(Number);
|
||||
|
||||
if (isNaN(min) || isNaN(max)) return false;
|
||||
|
||||
if (format.value === Validator.INPUT_STR) {
|
||||
const len = val.length;
|
||||
return len >= min && len <= max;
|
||||
} else if (format.value === Validator.INPUT_NUM) {
|
||||
return Number(val) >= min && Number(val) <= max;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
accepted: (val, present) => !present ? null : ['yes', 'on', 1, '1', true, 'true'].includes(val),
|
||||
|
||||
declined: (val, present) => !present ? null : ['no', 'off', 0, '0', false, 'false'].includes(val),
|
||||
|
||||
starts_with: (val, present, arg) => {
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
return arg.split(',').some(prefix => val.startsWith(prefix));
|
||||
},
|
||||
|
||||
ends_with: (val, present, arg) => {
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
return arg.split(',').some(suffix => val.endsWith(suffix));
|
||||
},
|
||||
|
||||
alpha: (val, present) => !present ? null : /^[a-zA-Z]+$/.test(val),
|
||||
|
||||
alpha_num: (val, present) => !present ? null : /^[a-zA-Z0-9]+$/.test(val),
|
||||
|
||||
uppercase: (val, present) => !present ? null : val === val.toUpperCase(),
|
||||
|
||||
lowercase: (val, present) => !present ? null : val === val.toLowerCase(),
|
||||
|
||||
domain: (val, present) => !present ? null : /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/.test(val),
|
||||
|
||||
email: (val, present) => !present ? null : /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val),
|
||||
|
||||
url: (val, present) => {
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
try {
|
||||
new URL(val);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
ip: (val, present) => !present ? null : (this.handlers.ipv4(val, present) || this.handlers.ipv6(val, present) ),
|
||||
|
||||
ipv4: (val, present) => !present ? null : /^(25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?)$/.test(val),
|
||||
|
||||
ipv6: (val, present) => !present ? null : /^([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4})$/.test(val),
|
||||
|
||||
hash: (val, present) => !present ? null : /^[a-f0-9]+$/i.test(val),
|
||||
|
||||
in: (val, present, arg) => !present ? null : arg.split(',').includes(val),
|
||||
|
||||
same: (val, present, arg) => !present ? null : val === this.target.elements[arg].value,
|
||||
|
||||
json: (val, present) => {
|
||||
|
||||
if (!present) return null;
|
||||
|
||||
try {
|
||||
JSON.parse(val);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
date: (val, present, arg, format) => !present ? null : format.value === Validator.INPUT_STR && !isNaN(Date.parse(val)),
|
||||
|
||||
cnp: (val, present, arg, format) => !present ? null : this.validCNP(val)
|
||||
};
|
||||
|
||||
getData() {
|
||||
|
||||
let fdata = new FormData();
|
||||
|
||||
[...this.target.elements].forEach(el => {
|
||||
|
||||
if (!el.name || el.type == 'button' || el.type == 'submit') {
|
||||
return true;
|
||||
}
|
||||
|
||||
let value;
|
||||
let displayedVal = null;
|
||||
|
||||
if ('value' in el.dataset) {
|
||||
value = el.dataset.value;
|
||||
displayedVal = el.value.trim();
|
||||
}
|
||||
else {
|
||||
value = el.value.trim();
|
||||
}
|
||||
|
||||
switch (el.type) {
|
||||
|
||||
case 'file':
|
||||
|
||||
let uploader = el.dxData('uploader');
|
||||
|
||||
if (uploader) {
|
||||
uploader.getFiles().forEach(filename => fdata.append('files_' + el.name + '[]', filename));
|
||||
}
|
||||
|
||||
break;
|
||||
case 'radio':
|
||||
|
||||
if (!fdata.has(el.name)) {
|
||||
fdata.set(el.name, '');
|
||||
}
|
||||
|
||||
if (el.checked) {
|
||||
fdata.set(el.name, value);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'checkbox':
|
||||
|
||||
if (el.checked) {
|
||||
fdata.append(el.name + '[]', value );
|
||||
}
|
||||
|
||||
break;
|
||||
case 'select-multiple':
|
||||
|
||||
for (let j = 0; j < el.options.length; j++) {
|
||||
if (el.options[j].selected && el.options[j].value != 0) {
|
||||
fdata.append(el.name + '[]', /^\d+$/.test(el.options[j].value)
|
||||
? parseInt(el.options[j].value)
|
||||
: el.options[j].value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
|
||||
if (fdata.has(el.name)) {
|
||||
|
||||
fdata.append(el.name + '[]', fdata.get(el.name) );
|
||||
fdata.delete(el.name);
|
||||
fdata.append(el.name + '[]', value);
|
||||
}
|
||||
else if (fdata.has(el.name + '[]')) {
|
||||
fdata.append(el.name + '[]', value);
|
||||
}
|
||||
else {
|
||||
|
||||
fdata.set(el.name, value);
|
||||
|
||||
if (displayedVal) {
|
||||
fdata.set(el.name + '_value', displayedVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return fdata;
|
||||
}
|
||||
|
||||
validCNP(value) {
|
||||
|
||||
let i = 0 , year = 0 , hashResult = 0 , cnp = [] , hashTable = [ 2, 7, 9, 1, 4, 6, 3, 5, 8, 2, 7, 9 ];
|
||||
|
||||
if ( value.length !== 13 ) { return false; }
|
||||
|
||||
for ( i = 0 ; i < 13 ; i++ ) {
|
||||
|
||||
cnp[i] = parseInt( value.charAt(i) , 10 );
|
||||
|
||||
if( isNaN( cnp[i] ) ) { return false; }
|
||||
|
||||
if( i < 12 ) { hashResult = hashResult + ( cnp[i] * hashTable[i] ); }
|
||||
}
|
||||
|
||||
hashResult = hashResult % 11;
|
||||
|
||||
if( hashResult === 10 ) { hashResult = 1; }
|
||||
|
||||
year = (cnp[1]*10)+cnp[2];
|
||||
|
||||
switch( cnp[0] ) {
|
||||
case 1 : case 2 : { year += 1900; } break;
|
||||
case 3 : case 4 : { year += 1800; } break;
|
||||
case 5 : case 6 : { year += 2000; } break;
|
||||
case 7 : case 8 : case 9 : { year += 2000; if( year > ( parseInt( new Date().getFullYear() , 10 ) - 14 ) ) { year -= 100; } } break;
|
||||
default : { return false; }
|
||||
}
|
||||
|
||||
if( year < 1800 || year > 2099 ) { return false; }
|
||||
|
||||
return ( cnp[12] === hashResult );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/* Validator */
|
||||
|
||||
.sp-has-error{
|
||||
color: red;
|
||||
}
|
||||
|
||||
.sp-label-error {
|
||||
position: absolute;
|
||||
background: #fb434a;
|
||||
font-size: 12px;
|
||||
padding: 5px 8px;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
z-index: 1;
|
||||
white-space: nowrap;
|
||||
bottom: -30px;
|
||||
}
|
||||
|
||||
.sp-label-error:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-bottom: 5px solid #fb434a;
|
||||
top: -5px;
|
||||
left: 10px;
|
||||
margin: auto;
|
||||
}
|
||||
Reference in New Issue
Block a user