Skip to content

Commit

Permalink
✨ Add way of defining/getting/setting state variables on a custom ele…
Browse files Browse the repository at this point in the history
…ment
  • Loading branch information
skerit committed May 6, 2024
1 parent 68ee9af commit a3d1b33
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* Add initial reactive variable support
* Allow assigning an element to a reference using `:ref` syntax
* Add support for ternary conditionals
* Add `CustomElement.defineStateVariable(name, config)` static method
* Add `CustomElement#setState(name, value)` and `CustomElement#getState(name)` methods

## 2.3.19 (2024-04-13)

Expand Down
192 changes: 191 additions & 1 deletion lib/element/custom_element.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ let has_premature_undried_elements,
has_v1 = typeof customElements == 'object',
render_counter = 0;

const CURRENT_RENDER = Symbol('current_render');
const CURRENT_RENDER = Symbol('current_render'),
STATE_DEFINITION = Symbol('state_definition'),
STATE_VALUES = Symbol('state_values');

let custom_stylesheet_handler;

if (Blast.isBrowser) {
Expand Down Expand Up @@ -539,6 +542,10 @@ Element.setStatic(function unDry(obj, force) {
element[Hawkejs.RENDER_INSTRUCTION] = obj.instructions;
}

if (obj.state_values?.size) {
element[STATE_VALUES] = obj.state_values;
}

// Delay this so the object is fully undried
Element.sceneReady(function() {
if (typeof element.undried == 'function') {
Expand Down Expand Up @@ -817,6 +824,80 @@ Element.setStatic(function monitor(name, callback) {
this._monitors[name].push(callback);
});

/**
* Actually add the efinition to the given element class
*
* @author Jelle De Loecker <[email protected]>
* @since 2.4.0
* @version 2.4.0
*
* @param {Function} element_class
* @param {string} name
* @param {Object} config
*/
const addStateDefinition = (element_class, name, config) => {

let definitions = element_class[STATE_DEFINITION];

if (!definitions) {
element_class[STATE_DEFINITION] = definitions = new Map();
}

definitions.set(name, config);
};

/**
* Get all the state values in a map.
* Expected ones will be created.
*
* @author Jelle De Loecker <[email protected]>
* @since 2.4.0
* @version 2.4.0
*
* @param {Function} element_class
* @param {string} name
* @param {Object} config
*/
const getAllStateValues = (element) => {

let values = element[STATE_VALUES],
definitions = element.constructor[STATE_DEFINITION];

// If there are no existing values,
// and no values have a definition, return nothing
if (!values && !definitions?.size) {
return;
}

if (definitions?.size) {
for (let [name, config] of definitions) {
// Trigger initial state value creation
element.getStateOptional(name);
}
}

return element[STATE_VALUES];
};

/**
* Add a state variable definition
*
* @author Jelle De Loecker <[email protected]>
* @since 2.4.0
* @version 2.4.0
*
* @param {string} name
* @param {Object} config
*/
Element.setStatic(function defineStateVariable(name, config) {

assertPropertyName(name);

this.constitute(function stateDefiner() {
addStateDefinition(this, name, config);
});
});

/**
* Add a property that is stored in assigned_data
*
Expand Down Expand Up @@ -1104,6 +1185,18 @@ function renderContentsWithTemplate(element, template, variables, slot_data, ple
variables = renderer.prepareVariables(variables);
}

// Make sure custom element state values are available as variables
if (element.is_custom_hawkejs_element) {

let state_values = getAllStateValues(element);

// State values always take precedence
// and are injected into the variables
if (state_values?.size) {
variables = variables.overlay(state_values);
}
}

if (slot_data) {
variables.setFromTemplate('self', element);
variables.setFromTemplate('child_nodes', slot_data.child_nodes);
Expand Down Expand Up @@ -1600,6 +1693,7 @@ Element.setMethod(function toDry() {
variables : this[Hawkejs.VARIABLES],
reactive : this[Hawkejs.REACTIVE_VALUES],
instructions : this[Hawkejs.RENDER_INSTRUCTION],
state_values : this[STATE_VALUES],
};
} else {
value = {
Expand All @@ -1608,12 +1702,108 @@ Element.setMethod(function toDry() {
variables : this[Hawkejs.VARIABLES],
reactive : this[Hawkejs.REACTIVE_VALUES],
instructions : this[Hawkejs.RENDER_INSTRUCTION],
state_values : this[STATE_VALUES],
};
}

return {value: value};
});

/**
* Get a state variable.
* Will always return an optional.
*
* @author Jelle De Loecker <[email protected]>
* @since 2.4.0
* @version 2.4.0
*
* @param {string} name
*
* @return {Develry.ObservableOptional}
*/
Element.setMethod(function getStateOptional(name) {

let values = this[STATE_VALUES],
optional;

if (!values) {
this[STATE_VALUES] = values = new Map();
}

optional = values.get(name);

if (!optional) {
optional = new Classes.Develry.ObservableOptional();
values.set(name, optional);

let definition = this.constructor[STATE_DEFINITION]?.get(name);

if (definition) {
if (definition.default != null) {
if (typeof definition.default == 'function') {
optional.value = definition.default();
} else {
optional.value = definition.default;
}
}

if (definition.on_change) {
optional.addListener(definition.on_change.bind(this));
}
}
}

return optional;
});

/**
* Get a state variable's value
*
* @author Jelle De Loecker <[email protected]>
* @since 2.4.0
* @version 2.4.0
*
* @param {string} name
*
* @return {Develry.ObservableOptional}
*/
Element.setMethod(function getState(name) {
return this.getStateOptional(name).value;
});

/**
* Set a state value
*
* @author Jelle De Loecker <[email protected]>
* @since 2.4.0
* @version 2.4.0
*
* @param {string} name
* @param {*} value
*
* @return {Develry.ObservableOptional}
*/
Element.setMethod(function setState(name, value) {

if (!name) {
return;
}

if (typeof name == 'object') {
let key;

for (key in name) {
this.setState(key, name[key]);
}

return;
}

let optional = this.getStateOptional(name);
optional.value = value;
return optional;
});

/**
* Make sure this element has a Renderer attached to it,
* and return it.
Expand Down

0 comments on commit a3d1b33

Please sign in to comment.