diff options
Diffstat (limited to 'asset/js/notjQuery.js')
-rw-r--r-- | asset/js/notjQuery.js | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/asset/js/notjQuery.js b/asset/js/notjQuery.js new file mode 100644 index 0000000..d24cd90 --- /dev/null +++ b/asset/js/notjQuery.js @@ -0,0 +1,161 @@ +define(function () { + + "use strict"; + + class notjQuery { + /** + * Create a new notjQuery object + * + * @param {Element} element + */ + constructor(element) { + if (! element) { + throw new Error("Can't create a notjQuery object for `" + element + "`"); + } + + this.element = element; + } + + /** + * Add an event listener to the element + * + * @param {string} type + * @param {string} selector + * @param {function} handler + * @param {object} context + */ + on(type, selector, handler, context = null) { + if (typeof selector === 'function') { + context = handler; + handler = selector; + selector = null; + } + + if (selector === null) { + this.element.addEventListener(type, e => { + if (type === 'focusin' && e.target.receivesCustomFocus) { + // Ignore native focus event if a custom one follows + if (e instanceof FocusEvent) { + delete e.target.receivesCustomFocus; + e.stopImmediatePropagation(); + return; + } + } + + if (context === null) { + handler.apply(e.currentTarget, [e]); + } else { + handler.apply(context, [e]); + } + }); + } else { + this.element.addEventListener(type, e => { + if (type === 'focusin' && e.target.receivesCustomFocus) { + // Ignore native focus event if a custom one follows + if (e instanceof FocusEvent) { + delete e.target.receivesCustomFocus; + e.stopImmediatePropagation(); + return; + } + } + + Object.defineProperty(e, 'currentTarget', { value: e.currentTarget, writable: true }); + + let currentParent = e.currentTarget.parentNode; + for (let target = e.target; target && target !== currentParent; target = target.parentNode) { + if (target.matches(selector)) { + e.currentTarget = target; + if (context === null) { + handler.apply(target, [e]); + } else { + handler.apply(context, [e]); + } + + break; + } + } + }, false); + } + } + + /** + * Trigger a custom event on the element, asynchronously + * + * The event will bubble and is not cancelable. + * + * @param {string} type + * @param {{}} detail + */ + trigger(type, detail = null) { + setTimeout(() => { + this.element.dispatchEvent(new CustomEvent(type, { + cancelable: true, // TODO: this should depend on whether it's a native or custom event + bubbles: true, + detail: detail + })); + }, 0); + } + + /** + * Focus the element + * + * Any other option than `preventScroll` is used as `event.detail`. + * + * @param {{}} options + */ + focus(options = {}) { + let { preventScroll = false, ...data } = options; + + const hasData = Object.keys(data).length > 0; + if (hasData) { + this.element.receivesCustomFocus = true; + } + + // Put separately on the event loop because focus() forces layout. + setTimeout(() => this.element.focus({ preventScroll: preventScroll }), 0); + + if (hasData) { + this.trigger('focusin', data); + } + } + + /** + * Render the element string as DOM Element + * + * @param {string} html + * @return {Element} + */ + static render(html) { + if (typeof html !== 'string') { + throw new Error("Can\'t render `" + html + "`"); + } + + let template = document.createElement('template'); + template.innerHTML = html; + return template.content.firstChild; + } + } + + /** + * Return a notjQuery object for the given element + * + * @param {Element} element + * @return {notjQuery} + */ + let factory = function (element) { + return new notjQuery(element); + } + + // Define the static methods on the factory + for (let name of Object.getOwnPropertyNames(notjQuery)) { + if (['length', 'prototype', 'name'].includes(name)) { + continue; + } + + Object.defineProperty(factory, name, { + value: notjQuery[name] + }); + } + + return factory; +}); |