From def9a0c837576838f259a5fcbec281832cf7d6ca Mon Sep 17 00:00:00 2001 From: MattMo Date: Fri, 3 Nov 2023 20:43:45 -0700 Subject: [PATCH] Added new type ahead feature, documented it and tested it. --- ignite-template.js | 83 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/ignite-template.js b/ignite-template.js index 10e4545..37af457 100644 --- a/ignite-template.js +++ b/ignite-template.js @@ -805,6 +805,89 @@ class IgniteTemplate { return this; } + /** + * Adds a special event handler to this element that invokes with the content of an input after a certain amount of time has passed after typing. + * This function supports inputs, text area's and content editable elements. + * @param {Function|IgniteProperty} eventCallback The callback function to be invoked when a typeAhead event occurs, it will be passed the typed value. + * @param {Number} minCharacters Min number of characters excluding whitespace that have to be typed before this event is invoked. Default is 2. + * @param {Number} callbackDelay The delay in miliseconds between each event callback. Default is 350. + * @param {Boolean} callbackReset Whether or not to reset the callback delay on each keypress. Default is true. If true, type ahead only occurrs when typing has stopped. + * @param {Boolean} enterCallback Whether or not the enter key should be considered for a type ahead. Default is true. + * @returns {IgniteTemplate} This ignite template so function calls can be chained. + */ + onTypeAhead(eventCallback, minCharacters = 2, callbackDelay = 350, callbackReset = true, enterCallback = true) { + var typeAheadTimeout = null; + + var typeAheadRunning = false; + + var lastValue = null; + + var typeAhead = async (target, force) => { + var value = null; + + typeAheadRunning = true; + + //Get the value from the target if we can. + if (target instanceof HTMLElement) { + if (target.tagName == "INPUT" || target.tagName == "TEXTAREA") { + value = target.value; + } else if (target.contentEditable == "true") { + value = target.innerText; + } + } + + //Trim the value if we can + value = value?.trim(); + + //If the value has at least the min number of characters after being trimmed invoke the callback. + if (value != null && value.length >= minCharacters && (lastValue != value || force)) { + try { + if (eventCallback instanceof IgniteProperty) { + await eventCallback.value(value); + } else if (eventCallback instanceof Function) { + await eventCallback(value); + } + } catch (error) { + console.error("Error occurred during type ahead callback", error); + } + + //Request another type ahead incase the value changed while we were waiting for the event callback to finish. + typeAheadTimeout = setTimeout(() => typeAhead(target, false), callbackDelay); + } else { + typeAheadTimeout = null; + } + + lastValue = value; + + typeAheadRunning = false; + }; + + //Attach a keydown event and handle the type ahead. + this.on("keydown", (e) => { + //If the key was an enter key, dont allow if it's not allowed. + if (e.key == "Enter" && !enterCallback) { + return; + } + + //If callback reset is true and there's a pending type ahead, clear it. + if (callbackReset && typeAheadTimeout && !typeAheadRunning) { + clearTimeout(typeAheadTimeout); + typeAheadTimeout = null; + } + + //Schedule a new type ahead timeout if one isn't already running. + if (!typeAheadTimeout) { + if (e.key == "Enter") { + typeAheadTimeout = setTimeout(() => typeAhead(e.target, true), 0); + } else { + typeAheadTimeout = setTimeout(() => typeAhead(e.target, false), callbackDelay); + } + } + }); + + return this; + } + /** * Adds a CSS property to this template with a value and priority. * @param {String} name The name of the CSS property to set.