import * as library from "../core/library.js";
import { isa_fn, xt, xta, λnull, Ωempty } from "../helper/utilities.js";
import { addListener, addNativeListener, removeListener, removeNativeListener } from "../core/events.js";Okay, so I got very bored of writing boilerplate to react to various form elements user interactions across the demos. So I wrote some functions to setup (and take down) batches of DOM event listeners to make my life easier. These are:
Then there was the use case for reacting to various mouse (and touch) events, so I bundled all those up into a set of complementary functions:
Even so, there was still a lot of boilerplate code to write, in particular to listening for user interaction with form elements (which can be anywhere on the web page). So I further factorised that code into an observeAndUpdate function which uses the listener functions internally.
I have no idea how useful the observeAndUpdate function will be to anyone else. All I know is that it works for me and my demos, and that makes me happy.
Note: observeAndUpdate() returns a function that will (in theory) remove all the bundled event listeners from their DOM elements when it is invoked. Not yet tested.
Exported function (to modules and the scrawl object). Capture changes in a form and apply them to a target Scrawl-canvas artefact, asset, style, group, etc object
observeAndUpdate - example
scrawl.observeAndUpdate({
// The input event which will trigger the O&U
event: ['input', 'change'],
// CSS selector used to gather the DOM elements this O&U will be attached to
origin: '.controlItem',
// The SC artefact, or Group object, to be updated by user interaction changes
target: piccy,
// Performs an `addNativeListener` when true; `addListener` when false
useNativeListener: true,
// Invokes the event listener's `preventDefault` function
preventDefault: true,
// The SC artefact attributes to be updated by various DOM inputs. See below
updates: {
copy_start_xPercent: ['copyStartX', '%'],
copy_start_xAbsolute: ['copyStartX', 'round'],
copy_start_yPercent: ['copyStartY', '%'],
copy_start_yAbsolute: ['copyStartY', 'round'],
copy_dims_widthPercent: ['copyWidth', '%'],
copy_dims_widthAbsolute: ['copyWidth', 'round'],
copy_dims_heightPercent: ['copyHeight', '%'],
copy_dims_heightAbsolute: ['copyHeight', 'round'],
},
// A callback function to be performed after any attributes updates
callback: () => myLoom.update(),
// Similarly, we can invoke a function to run before performing the updates
setup: () => myLoom.update(),
});
import * as library from "../core/library.js";
import { isa_fn, xt, xta, λnull, Ωempty } from "../helper/utilities.js";
import { addListener, addNativeListener, removeListener, removeNativeListener } from "../core/events.js";Shared constants
import { _ceil, _floor, _round, T_GROUP, TRUE } from '../helper/shared-vars.js';Local constants
const FALSE = 'false';observeAndUpdate - exported function
export const observeAndUpdate = function (items = Ωempty) {
if (!xta(items.event, items.origin, items.updates)) return false;
const target = (items.target.substring && items.targetLibrarySection) ?
library[items.targetLibrarySection][items.target] :
items.target;
if (!target) return false;
const event = items.event,
origin = items.origin;
const listener = (items.useNativeListener) ? addNativeListener : addListener,
killListener = (items.useNativeListener) ? removeNativeListener : removeListener;
let stop = λnull;
if (items.preventDefault) {
stop = (e) => {
e.preventDefault();
e.returnValue = false;
};
}
const setup = (isa_fn(items.setup)) ? items.setup : λnull;
const callback = (isa_fn(items.callback)) ? items.callback : λnull;
const func = function (e) {
stop(e);
const id = (e && e.target) ? e.target.id : false;
if (id) {
const updates = items.updates,
actionArray = updates[id];
if (actionArray) {
setup();
const actionAttribute = actionArray[0],
action = actionArray[1],
targetVal = e.target.value;
let actionFlag = true,
val;
switch (action) {Supplied value converted to float Number
case 'float' :
val = parseFloat(targetVal);
break;Supplied value converted to integer Number
case 'int' :
val = parseInt(targetVal, 10);
break;Supplied value rounded up or down to nearest integer Number
case 'round' :
val = _round(targetVal);
break;Supplied value rounded down to nearest integer Number
case 'roundDown' :
val = _floor(targetVal);
break;Supplied value rounded up to nearest integer Number
case 'roundUp' :
val = _ceil(targetVal);
break;Supplied value not modified in any way
case 'raw' :
val = targetVal;
break;Supplied value not modified in any way
case 'parse' :
val = (targetVal.substring) ? JSON.parse(targetVal) : targetVal;
break;Supplied value converted to String
case 'string' :
val = `${targetVal}`;
break;Supplied value converted to Boolean
case 'boolean' :
if (xt(targetVal)) {
if (targetVal.substring) {
if (TRUE === targetVal.toLowerCase()) val = true;
else if (FALSE === targetVal.toLowerCase()) val = false;
else val = (parseFloat(targetVal)) ? true : false;
}
else val = (targetVal) ? true : false;
}
else val = false;
break;Supplied value converted to float number, then action value (eg: ‘%’) added to result to output a String
default :
if (action.substring) val = `${parseFloat(targetVal)}${action}`;
else actionFlag = false;
}Update - we can apply updates to a Group of artefacts, or to a single artefact
if (actionFlag) {
if (target.type === T_GROUP) {
target.setArtefacts({
[actionAttribute]: val
});
}
else {
target.set({
[actionAttribute]: val
});
}Invoke any supplied callback function
callback(e);
}
}
}
};
const kill = function () {
killListener(event, func, origin);
};
listener(event, func, origin);
return kill;
};
export const makeUpdater = (items = Ωempty) => observeAndUpdate(items);initializeDomInputs - exported helper function
[kind: String, selector: String, value: String | number], wherekind defines the kind of element to be processed - input, select, button, elementselector - for input, select and button elements, that element’s unique id value; for anything else, a valid CSS query selector stringvalue - the initial value to set the input to - for select this will be a numberexport const initializeDomInputs = (items) => {
const results = {};
items.forEach(item => {
const [kind, selector, value] = item;
switch (kind) {
case 'input' : {
if (value.substring) {
/** @type {HTMLInputElement} */
const S = document.querySelector(`#${selector}`);
if (S) {
S.value = value;
results[selector] = S;
}
else results[selector] = {};
}
else results[selector] = {};
break;
}
case 'select' : {
if (value.toFixed) {
/** @type {HTMLSelectElement} */
const S = document.querySelector(`#${selector}`);
if (S) {
S.options.selectedIndex = value;
results[selector] = S;
}
else results[selector] = {};
}
else results[selector] = {};
break;
}
case 'button' : {
if (value.substring) {
/** @type {HTMLButtonElement} */
const S = document.querySelector(`#${selector}`);
if (S) {
S.textContent = value;
results[selector] = S;
}
else results[selector] = {};
}
else results[selector] = {};
break;
}
case 'element' : {
/** @type {HTMLElement} */
const S = document.querySelector(`${selector}`);
if (S) results[selector] = S;
else results[selector] = {};
break;
}
default : {
/** @type {HTMLElement} */
const S = document.querySelector(`#${selector}`);
if (S) results[selector] = S;
else results[selector] = {};
}
}
});
return results;
};