diff --git a/lib/core/base.js b/lib/core/base.js index dd97611..51febc5 100644 --- a/lib/core/base.js +++ b/lib/core/base.js @@ -469,6 +469,7 @@ Hawkejs.DELAY_SYNC_RENDER = Symbol('delay_sync_render'); Hawkejs.CREATED_MANUALLY = Symbol('created_manually'); Hawkejs.REACTIVE_VALUES = Symbol('reactive_values'); Hawkejs.APPLIED_OPTIONS = Symbol('applied_options'); +Hawkejs.EVENT_HANDLERS = Symbol('event_handlers'); Hawkejs.RENDER_CONTENT = Symbol('render_hawkejs_content'); Hawkejs.SERIALIZE_FORM = Symbol('serialize_form'); Hawkejs.GET_FORM_DATA = Symbol('get_form_data'); diff --git a/lib/core/renderer.js b/lib/core/renderer.js index 317dfc1..aecdcb6 100644 --- a/lib/core/renderer.js +++ b/lib/core/renderer.js @@ -2581,15 +2581,45 @@ Renderer.setMethod(function applyElementOptions(element, options, for_sync_rende } if (options.hooks?.length) { + let event_handlers = []; + for (let hook of options.hooks) { - if (hook.name === 'ref') { + const name = hook.name; + + if (name === 'ref') { let value = this.parseRuntimeExpression(hook.value); if (value) { value.value = element; } + + continue; + } + + switch (name) { + case 'click': + case 'focus': + case 'hover': + case 'blur': + case 'change': + case 'input': + case 'keydown': + case 'keyup': + case 'keypress': + if (hook.value?.$expression) { + event_handlers.push({ + type : name, + expression: hook.value?.$expression, + }); + } + break; } } + + if (event_handlers.length) { + attachEventHandlers(element, event_handlers); + this.registerElementInstance(element); + } } // If the element body is rendered using reference variables, @@ -2628,6 +2658,21 @@ Renderer.setMethod(function applyElementOptions(element, options, for_sync_rende } }); +/** + * Attach event handlers + * + * @author Jelle De Loecker + * @since 2.4.0 + * @version 2.4.0 + * + * @param {HTMLElement} element + * @param {Object[]} handlers + */ +const attachEventHandlers = (element, handlers) => { + element[Hawkejs.EVENT_HANDLERS] = handlers; + Hawkejs.Element.Element.ensureEventHandlers(element, handlers); +}; + /** * Attach reactive references * diff --git a/lib/element/custom_element.js b/lib/element/custom_element.js index 5d9dc70..cdfc0e6 100644 --- a/lib/element/custom_element.js +++ b/lib/element/custom_element.js @@ -11,7 +11,8 @@ let has_premature_undried_elements, const CURRENT_RENDER = Symbol('current_render'), STATE_DEFINITION = Symbol('state_definition'), - STATE_VALUES = Symbol('state_values'); + STATE_VALUES = Symbol('state_values'), + ADDED_EVENT_LISTENERS = Symbol('added_event_listeners'); let custom_stylesheet_handler; @@ -474,16 +475,9 @@ Element.setStatic(function unDry(obj, force) { } } - if (obj.assigned_data) { - element.assigned_data = obj.assigned_data; - } - if (obj.renderer) { element.hawkejs_renderer = obj.renderer; } - - // Make it empty, or else it'll set it again later - obj = {}; } } @@ -546,6 +540,11 @@ Element.setStatic(function unDry(obj, force) { element[STATE_VALUES] = obj.state_values; } + if (obj.event_handlers?.length) { + element[Hawkejs.EVENT_HANDLERS] = obj.event_handlers; + Element.ensureEventHandlers(element, obj.event_handlers); + } + // Delay this so the object is fully undried Element.sceneReady(function() { if (typeof element.undried == 'function') { @@ -556,6 +555,57 @@ Element.setStatic(function unDry(obj, force) { return element; }); +/** + * Make sure the event handlers will be added to the given element + * + * @author Jelle De Loecker + * @since 2.4.0 + * @version 2.4.0 + * + * @param {HTMLElement} element + * @param {Object[]} event_handlers + * @param {boolean} force + */ +Element.setStatic(function ensureEventHandlers(element, event_handlers, force = false) { + + if (!element || !event_handlers?.length || element[ADDED_EVENT_LISTENERS]) { + return; + } + + if (element.is_custom_hawkejs_element && !force && !element.isConnected()) { + return; + } + + element[ADDED_EVENT_LISTENERS] = true; + + for (let handler of event_handlers) { + element.addEventListener(handler.type, e => { + doHawkejsEventHandler(element, handler, e); + }); + } +}); + +/** + * Actually perform the given event handler + * + * @author Jelle De Loecker + * @since 2.4.0 + * @version 2.4.0 + * + * @param {HTMLElement} element + * @param {Object} handler + * @param {Event} event + */ +const doHawkejsEventHandler = (element, handler, event) => { + + let renderer = Element.prototype.ensureHawkejsRenderer.call(element); + let variables = Hawkejs.Variables.cast(element[Hawkejs.VARIABLES], renderer); + + variables.set('$event', event); + + renderer.parseExpression(handler.expression, variables); +}; + /** * Make sure contents are rendered before doing this parent getter/method * @@ -1678,10 +1728,18 @@ Element.setMethod(function toDry() { } } + value = { + hawkejs_id : this.hawkejs_id, + assigned_data : this.assigned_data, + variables : this[Hawkejs.VARIABLES], + reactive : this[Hawkejs.REACTIVE_VALUES], + instructions : this[Hawkejs.RENDER_INSTRUCTION], + state_values : this[STATE_VALUES], + event_handlers : this[Hawkejs.EVENT_HANDLERS], + }; + if (serialize_all) { value = { - hawkejs_id : this.hawkejs_id, - assigned_data : this.assigned_data, tagName : this.tagName, attributes : this.attributes, dataset : this.dataset, @@ -1690,19 +1748,7 @@ Element.setMethod(function toDry() { cssText : this.style.cssText, innerHTML : this.innerHTML, renderer : this.hawkejs_renderer, - variables : this[Hawkejs.VARIABLES], - reactive : this[Hawkejs.REACTIVE_VALUES], - instructions : this[Hawkejs.RENDER_INSTRUCTION], - state_values : this[STATE_VALUES], - }; - } else { - value = { - hawkejs_id : this.hawkejs_id, - assigned_data : this.assigned_data, - variables : this[Hawkejs.VARIABLES], - reactive : this[Hawkejs.REACTIVE_VALUES], - instructions : this[Hawkejs.RENDER_INSTRUCTION], - state_values : this[STATE_VALUES], + ...value }; } @@ -2332,6 +2378,12 @@ Element.setMethod(function connectedCallback() { that.emit('rendered', {bubbles: false}); } + let event_handlers = that[Hawkejs.EVENT_HANDLERS]; + + if (event_handlers) { + Element.ensureEventHandlers(that, event_handlers, true); + } + if (has_template && that[Hawkejs.CREATED_MANUALLY] && that[Hawkejs.RENDER_CONTENT] && !that.has_rendered) { let rendering = that[Hawkejs.RENDER_CONTENT]();