Skip to content

Commit

Permalink
✨ Add support for setting event handlers from templates
Browse files Browse the repository at this point in the history
  • Loading branch information
skerit committed May 6, 2024
1 parent a3d1b33 commit e0fd20c
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 24 deletions.
1 change: 1 addition & 0 deletions lib/core/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
47 changes: 46 additions & 1 deletion lib/core/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -2628,6 +2658,21 @@ Renderer.setMethod(function applyElementOptions(element, options, for_sync_rende
}
});

/**
* Attach event handlers
*
* @author Jelle De Loecker <[email protected]>
* @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
*
Expand Down
98 changes: 75 additions & 23 deletions lib/element/custom_element.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 = {};
}
}

Expand Down Expand Up @@ -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') {
Expand All @@ -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 <[email protected]>
* @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 <[email protected]>
* @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
*
Expand Down Expand Up @@ -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,
Expand All @@ -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
};
}

Expand Down Expand Up @@ -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]();
Expand Down

0 comments on commit e0fd20c

Please sign in to comment.