@twobirds/microcomponents
Version:
Micro Components Organization Class
249 lines • 9.68 kB
JavaScript
;
import { debounce, deepEqual, flattenObject, copyGettersSetters, } from './helpers.js';
import { DC } from './MC.js';
function isObservableObject(value) {
return value != null && typeof value === 'object';
}
function getPlaceholders(target) {
let placeholders = new Set(), regEx = /\{[^\{\}]*\}/g;
target
.getAttributeNames()
.filter((attr) => target.getAttribute(attr)?.match(regEx))
.forEach((attr) => {
let phs = new Set(target.getAttribute(attr)?.match(regEx));
phs.forEach((placeholder) => placeholders.add(placeholder.replace(/[\{\}]/g, '')));
});
[...target.childNodes]
.filter((childNode) => {
return (childNode.nodeType === 3 &&
(childNode.nodeValue || '').match(regEx));
})
.forEach((childNode) => {
(childNode.nodeValue || '')
.match(regEx)
.forEach((placeholder) => {
placeholders.add(placeholder.replace(/[\{\}]/g, ''));
});
});
return [...placeholders].map((placeholder) => placeholder.replace(/[\{\}]/g, ''));
}
const inputs = [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement];
class Placeholders extends DC {
data = {};
oldDisplay = '';
constructor(target) {
super(target);
let that = this;
getPlaceholders(target).forEach((property) => (that.data[property] = ''));
observe(that);
that.#attachCallbacks();
delete that._mc;
}
#attachCallbacks() {
let that = this, target = that.target, hide = false;
target
.getAttributeNames()
.filter((attr) => target.getAttribute(attr)?.match(/\{[^\{\}]*\}/g))
.forEach((attr) => {
let phs = new Set(target.getAttribute(attr)?.match(/\{[^\{\}]*\}/g));
that.data.observe(((template) => (data) => {
let result = template;
phs.forEach((placeholder) => {
result = result.replace(placeholder, data[placeholder.replace(/[\{\}]/g, '')]);
});
target.setAttribute(attr, result);
})(target.getAttribute(attr)));
});
[...target.childNodes]
.filter((childNode) => {
return (childNode.nodeType === 3 &&
(childNode.nodeValue || '').match(/\{[^\{\}]*\}/g));
})
.forEach((childNode) => {
hide = true;
let phs = new Set(childNode.nodeValue.match(/\{[^\{\}]*\}/g));
that.data.observe(((template) => (data) => {
let result = template;
phs.forEach((placeholder) => {
result = result.replace(placeholder, data[placeholder.replace(/[\{\}]/g, '')]);
});
if (Object.keys(data).some((key) => {
return !!data[key];
})) {
if (!!that.oldDisplay) {
target.style = that.oldDisplay;
}
else {
target.removeAttribute('style');
}
delete that.oldDisplay;
}
childNode.nodeValue = result;
})(childNode.nodeValue));
that.oldDisplay = target.getAttribute('style)') || '';
if (hide &&
!inputs.includes(target.constructor) &&
target.tagName !== 'FIELDSET') {
target.style.display = 'none';
}
});
}
onData(ev) {
let that = this, data = {};
Object.keys(ev.data).forEach((key) => {
if (that.data.hasOwnProperty(key))
data[key] = ev.data[key];
});
if (JSON.stringify(data) !== JSON.stringify(that.data))
Object.assign(that.data, data);
}
}
function getPlaceholderElements(target) {
let elements = (target instanceof HTMLElement ? [target] : [])
.concat([...target.querySelectorAll('*')])
.filter((e) => !!getPlaceholders(e).length)
.filter((e) => e.tagName !== 'STYLE');
return elements;
}
function makePlaceholders(element) {
getPlaceholderElements(element).forEach((e) => {
if (e?._mc?._placeholders)
return;
DC.add(e, '_placeholders', new Placeholders(e));
});
}
const classRepo = {};
function makeObservable(val) {
const Constructor = val.constructor, ConstructorName = Constructor.name;
let ObservableClass = class extends Constructor {
#notify = true;
#callbacks = [];
constructor(val) {
super(val);
this.constructor.callbacks = [];
if (isObservableObject(val)) {
Object.getOwnPropertyNames(val).forEach((key) => {
this[key] = val[key];
});
}
}
get callbacks() {
return this.#callbacks;
}
set callbacks(callbacks) {
this.#callbacks = callbacks;
}
observe(f) {
this.#callbacks.push(f);
return this;
}
bind(target) {
const that = this, iso = isObservableObject(that.valueOf());
if (iso) {
makePlaceholders(target);
const placeholders = [
...target.querySelectorAll('[_mc]'),
].filter((e) => !!e?._mc?._placeholders);
const func = function callback(data) {
let d = flattenObject(data);
placeholders.forEach((e) => {
if (!e)
return;
e._mc.trigger('data', d);
});
};
that.#callbacks.push(func);
that.notify();
}
else {
console.warn('IGNORED - cannot bind a non-Object to the dom, IS:', typeof that.valueOf(), that);
}
return this;
}
notify(notify) {
let that = this, data = isObservableObject(that.valueOf()) &&
that.valueOf() instanceof Array === false
? structuredClone(Object.assign({}, that))
: that.valueOf();
if (notify !== undefined) {
that.#notify = notify;
}
that.#callbacks.forEach((f) => {
f(data);
});
return that;
}
};
classRepo[ConstructorName] = ObservableClass;
return ObservableClass;
}
function getConstructor(val) {
let key = val.constructor.name;
return classRepo[key] || makeObservable(val);
}
export const observe = (target, propertyName) => {
let keys = !propertyName
? [...Object.getOwnPropertyNames(target)]
: [propertyName];
keys.forEach((key) => {
if (target.hasOwnProperty(key)
&& typeof target[key] !== 'function'
&& !target?.[key]?.constructor?.prototype?.observe) {
if (key[0] === '_' || target?.[key] === undefined)
return target;
let val = target[key];
let o = new (getConstructor(val))(val);
Object.defineProperty(target, key, {
enumerable: true,
get() {
if (isObservableObject(o.valueOf())) {
let check = structuredClone(Object.assign({}, o));
setTimeout(debounce(function checkChanges() {
let state = structuredClone(Object.assign({}, o));
if (!deepEqual(state, check)) {
o.notify();
}
}, 0), 0);
}
return o;
},
set(val) {
if (typeof val === typeof o.valueOf()) {
let iso = isObservableObject(o.valueOf()) &&
o.valueOf() instanceof Array === false;
if (iso) {
let old = structuredClone(Object.assign({}, o));
if (o?.constructor?.prototype?.observe === undefined) {
o = new (getConstructor(val))(structuredClone(Object.assign({}, val)));
copyGettersSetters(val, o);
o.seal();
}
Object.assign(o, val);
setTimeout(debounce(function checkChanges() {
let state = structuredClone(Object.assign({}, o));
if (!deepEqual(state, old)) {
o.notify();
}
}, 0), 0);
}
else {
let old = o.valueOf(), callbacks = o.callbacks;
if (val !== old) {
o = new (getConstructor(val))(val);
o.callbacks = callbacks;
o.notify();
}
}
}
else {
console.warn('IGNORED - cannot change observable types, IS:', typeof o.valueOf(), o, '- CANNOT BECOME:', typeof val);
}
},
});
target[key] = val;
}
;
});
return target;
};
//# sourceMappingURL=observables.js.map