buildfire.dynamic.expressions
This feature provides the capability to dynamically evaluate expressions. For example, the expression ${context.appId}
will be evaluated to the app id of the app if it was passed to this feature. Any javascript code could be used as an expression (arithmetic operations, new Date(), Math...) in addition to some predefined context properties like (appId
, pluginId
, appTheme
, appUser
)
An expression is any content inside the template literals ${}
The context
keyword should always be used when trying to evaluate predefined context properties (appId
, pluginId
, appTheme
, appUser
)
The WYSIWYG component is fully integrated with this feature; whenever an expression is written in the WYSIWYG, it will be automatically evaluated
Predefined Context
There are predefined context properties that are available for any plugin to evaluate against:
Name | Type | Expression |
---|---|---|
appId | string | ${context.appId} |
pluginId | string | ${context.pluginId} |
appUser | object | ${context.appUser} |
appTheme | object | ${context.appTheme} |
If you are not sure about the properties that an object - such as appUser
or appTheme
- consists of, you can use iterate through the properties. For example, ${Object.keys(context.appTheme)}
.
For objects like appUser
or any object that won't always be available, if the user is not logged in for example, it is better to check for nullability. The evaluation won't break and will continue to evaluate other expressions. So rather than evaluating an appUser
property like ${context.appUser.lastName}
, it is better to check nullability. For example, ${context.appUser ? context.appUser.lastName : ''}
. This ensures it won't break, and it would render an empty string rather than undefined
if the lastName
property doesn't exist.
Methods
evaluate()
buildfire.dynamic.expressions.evaluate(options, callback)
This function takes the expression as a string and returns the evaluated expression in the callback
buildfire.dynamic.expressions.evaluate({expression: '${context.pluginId}'}, (err, result) => {
if (err) return console.error(err);
console.log('The Evaluated Expression: ', result.evaluatedExpression);
})
The callback of the evaluate
method would be called multiple times if the triggerContextChange
method was called. For example, if ${context.appUser}
was evaluated and the user status changed (the user was logged in then logged out or vice versa), the triggerContextChange
method will be triggered under the hood and all the registered callbacks will be called.
To stop listening to the evaluate
method's callback, the registered callback could be destroyed by the destroy
method, which is returned in the callback with the evaluation result
options
Name | Type | Required | Description | Default |
---|---|---|---|---|
id | string | no | A unique id that identify the evaluation request | Auto generated id |
expression | string | no | The content that will be evaluated | |
extendedContext | object | no | Additional context to be used for evaluating expressions |
options.extendedContext
Name | Type | Required | Description | Default |
---|---|---|---|---|
extendedContext | object | no | Additional context to be used for evaluating expressions |
callback(err, res)
Name | Type | Description |
---|---|---|
err | string | Error string, null when the operation is successful |
res | object | Contains the evaluated expression and the evaluation request |
res
Name | Type | Description |
---|---|---|
evaluatedExpression | string | The evaluated expressions |
evaluationRequest | object | The evaluation request data, which could be used if needed, in addition to the destroy functionality |
res.evaluationRequest
Name | Type | Description |
---|---|---|
id | string | The given id in the evaluate method options |
options | string | The evaluate method options |
callback | function | The callback of evaluate method |
destroy | function | Provide the capability to destroy the registered callback of the evaluate method (stop listening for callbacks) |
More Examples
// How to extend the default context
const extendedContext = {
plugin: {
currentWeight: 85,
goalWeight: 65
}
}
buildfire.dynamic.expressions.evaluate({
expression:'${context.plugin.currentWeight}',
extendedContext
}, (err, result) => {
console.log('The result is:', result.evaluatedExpression); // => The result is: 85
});
The keyword data
is a reserved word; so it can't be used as an id for the extended context
const extendedContext = {
data: { // data is a reserved word (id)
...
}
}
// How to stop listening to the evaluation callback
buildfire.dynamic.expressions.evaluate({ expression: '${context.appId}' }, (err, result) => {
if (err) return console.error(err);
result.evaluationRequest.destroy();
console.log('The result is:', result.evaluatedExpression);
});
getContext()
buildfire.dynamic.expressions.getContext(options, callback)
This function provides the capability to extend the context that is used when evaluating expressions.
Custom plugin expressions that are being added using buildfire.dynamic.expressions.getContext
will be accessible in the root of the context. For example, the extended context properties added in the example below could be accessed as follows ${context.plugin.currentWeight}
and ${context.plugin.goalWeight}
respectively
It is always better to send the custom context with a unique id (plugin
as in the below example); so it won't interfere with other context properties.
buildfire.dynamic.expressions.getContext = (options, callback) => {
const context = {
plugin: {
currentWeight: 85,
goalWeight: 65
}
}
callback(null, context)
}
The keyword data
is a reserved word; so it can't be used as an id for the custom context
buildfire.dynamic.expressions.getContext = (options, callback) => {
const context = {
data: { // data is a reserved word (id)
...
}
}
callback(null, context)
}
options
For Future use
callback(err, context)
Name | Type | Description |
---|---|---|
err | string | Error string, null when the operation is successful |
context | string | The plugin custom context |
getCustomExpressions()
buildfire.dynamic.expressions.getCustomExpressions(options, callback)
This function provides the capability to provide friendly names and the corresponding expressions to be shown in the Expression Builder. In addition to the default expressions (predefined context), this function would help in showing other expressions that the plugin supports (the plugin has extended the context). For example, Current Weight
and Goal Weight
are in the image below.
This function is just for showing the supported expressions by the plugin. For these custom expressions to work, the plugin should extend the default context using (buildfire.dynamic.expressions.getContext
) or via the extendedContext
buildfire.dynamic.expressions.getCustomExpressions = (options, callback) => {
const customExpressions = [
{
name: "Current Weight",
expression: "${context.plugin.currentWeight}"
},
{
name: "Goal Weight",
expression: "${context.plugin.goalWeight}"
}
]
callback(null, {expressions: customExpressions})
}
options
For Future use
callback(err, data)
Name | Type | Description |
---|---|---|
err | string | Error string, null when the operation is successful |
data | string | The plugin custom expressions |
triggerContextChange()
buildfire.dynamic.triggerContextChange(options)
This function provides the capability to reevaluate expressions if the context of any evaluated expression has changed. For example, the expression (${context.appUser}
), will be reevaluated when the user status change (the user was logged in then logged out or vice versa), where the triggerContextChange
is triggered under the hood and this also applies on (${context.appTheme}
)
If a custom plugin context was added by the plugin via (buildfire.dynamic.expressions.getContext) or via the extendedContext option (in the evaluate
method), then it is the plugin responsibility to trigger the reevaluation by calling triggerContextChange
when the provided custom context was updated. For the previous example in getContext section, a reevaluation could be triggered by calling buildfire.dynamic.triggerContextChange({contextProperty: 'plugin'})
showDialog()
buildfire.dynamic.expressions.showDialog(options, callback)
This function provides the capability to open the Expression Builder
. In the expression builder, expressions could be written manually or can be added by clicking on one of the variables in the menu on the left, where an expression will be added to the Expression
textarea. After adding the expression, the user could check the evaluation of the expressions in the Results
textarea by clicking on the Evaluate
button.
After finishing adding and evaluating expressions, expressions would be returned in the callback by clicking on the Use Expression
button.
buildfire.dynamic.expressions.showDialog(null, (err, expression) => {
if (err) return console.error(err);
console.log("Expression: ", expression);
});
options
For Future use
callback(err, expression)
Callback function after clicking on the Use Expression
button in the Expression Builder
or when the dialog is closed
Name | Type | Description |
---|---|---|
err | string | Error string |
expression | string | The expression that is added in the Expression textarea. Will be null if the dialog was closed |
WYSIWYG Expressions
WYSIWYG is fully integrated with the expressions feature; so whenever an expression is written in the WYSIWYG, it will be automatically evaluated, where you can see if the evaluation is enabled or disabled by looking at the Expressions
menu item in the Tools
menu.
Expressions evaluation will be turned on automatically when the WYSIWYG content contains an expression (${}
) and will be turned off if there are no expressions
Passing bf_dynamic_expressions
as false
with the WYSIWYG properties on initialization will stop expressions evaluation in the WYSIWYG. For example:
tinymce.init({
selector: "#template",
bf_dynamic_expressions: false
});
Insert Expressions
Rather than writing expressions manually, expressions could be added easily by the Expression Builder
, which could be opened by clicking the Insert expression
menu item in the Insert
menu in the WYSIWYG.
Expressions will be added at the last position of the cursor before opening the Expression Builder
Expression Builder
In the expression builder, expressions could be written manually or can be added by clicking on one of the variables in the menu on the left, where an expression will be added to the Expression
textarea. After adding the expression, the user could check the evaluation of the expressions in the Results
textarea by clicking on the Evaluate
button.
After finishing adding and evaluating expressions, expressions could be added to the WYSIWYG by clicking the Use Expression
button.
WYSIWYG Special Cases
In some scenarios, it would be needed to add additional attributes to the elements in the WYSIWYG, and here are some of them:
Image Source:
Images with the src as an expression will be broken inside the WYSIWYG because expressions are not evaluated inside the WYSIWYG. Images could have a hardcoded src (normal url); so it would work as a placeholder in the WYSIWYG, and it would have (expr-src
or data-expr-src
) attributes, which contain the expressions. These attributes will be dynamically evaluated and will replace the placeholder src (the hardcoded src). For example:
// In WYSIWYG
<img src='https://via.placeholder.com/600/92c952' expr-src='${context.appUser?.imageUrl}'>
// In the widget After an evaluation (expr-src content will be evaluated and will replace the original src)
// the image of the logged-in user
<img src='https://s3.amazonaws.com/Kaleo.DevBucket/VxM8sqV5esqi_zOW-guS-50k.jpeg'>
Element Style/Classes:
In cases where styles or classes attributes are expressions and contain capital letters, other attributes (expr-style
or data-expr-style
) or (expr-class
or data-expr-class
) should be used, where capital letters will be converted to lower cases by the WYSIWYG, which will break the evaluation.
For example:
// The wrong way
<div style="color: ${context.appTheme?.colors?.dangerTheme};">Danger Theme Color</div>
// Will be changed in the WYSIWYG to:
<div style="color: ${context.apptheme?.colors?.dangertheme};">Danger Theme Color</div>
// Will be evaluated in widget to
<div style="color: undefined;">Danger Theme Color</div> // Wrong result
// The right way
<div expr-style="color: ${context.appTheme?.colors?.dangerTheme};">Danger Theme Color</div>
// Will be evaluated to
<div style="color: #f24965;">Danger Theme Color</div> // The intended result
Expressions Repeater
Rather than handling an array's values individually in expressions
, arrays can be evaluated by using the buildfire-repeat
HTML attribute. This will iterate through the array provided in the expressions
context. For example: if the context was extended, and we have the array context.restaurant.orders
, we can iterate over its values as shown below.
First, we set up the template and the container to render the evaluated expression.
<!-- Repeater template -->
<script id="templateSource" type="text/template">
<div buildfire-repeat="order in context.restaurant.orders">
<span>Order ID: ${order.id}</span>
<span>Order Date: ${new Date(order.date).toLocaleTimeString()}</span>
<!-- nested repeater -->
<div buildfire-repeat="item in order.items">
<span>Item: ${item}</span>
</div>
</div>
</script>
<!-- An element in the widget to render the result in -->
<div id="repeaterContainer"></div>
Next, the context
is extended by using the getContext
function as shown below, and the repeater is evaluated.
// Extending the default context using
// buildfire.dynamic.expressions.getContext in the plugin's widget
buildfire.dynamic.expressions.getContext = (options, callback) => {
let context = {
restaurant: {
orders: [
{ id: 1, date: 1686520711235, items: ['Pizza', 'Cola'] },
{ id: 2, date: 1686520721425, items: ['Burger', 'Fries'] },
{ id: 3, date: 1686520830611, items: ['Hamburger'] }
]
},
};
callback(null, context);
};
// Get the repeater template
let expression = document.getElementById('templateSource').innerHTML;
// Ealuate the expression and render it
buildfire.dynamic.expressions.evaluate({ expression }, (error, result) => {
if (error) return console.error(error);
console.log('Repeater Result:', result.evaluatedExpression);
document.getElementById('repeaterContainer').innerHTML = result.evaluatedExpression;
});
The outputted HTML will be:
<!-- Repeater Result -->
<div>
<span>Order ID: 1</span>
<span>Order Date: 2:58:31 PM</span>
<div>
<span>Item: Pizza</span>
</div>
<div>
<span>Item: Cola</span>
</div>
</div>
<div>
<span>Order ID: 2</span>
<span>Order Date: 2:58:41 PM</span>
<div>
<span>Item: Burger</span>
</div>
<div>
<span>Item: Fries</span>
</div>
</div>
<div>
<span>Order ID: 3</span>
<span>Order Date: 3:00:30 PM</span>
<div>
<span>Item: Hamburger</span>
</div>
</div>
WYSIWYG Repeaters
Expressions are supported in the WYSIWYG. If the context was extended as shown above, you can just add the repeater in the WYSIWYG and it will be evaluated automatically.
First, extend the context in the widget just like before.
// Extending the default context using
// buildfire.dynamic.expressions.getContext in the plugin's widget
buildfire.dynamic.expressions.getContext = (options, callback) => {
let context = {
restaurant: {
orders: [
{ id: 1, date: 1686520711235, items: ['Pizza', 'Cola'] },
{ id: 2, date: 1686520721425, items: ['Burger', 'Fries'] },
{ id: 3, date: 1686520830611, items: ['Hamburger'] }
]
},
};
callback(null, context);
};
Next, update the WYSIWYG source code with the HTML template. Open the toolbar and select the code tool. When the code tool opens, insert the repeater template and save.
Code Tool | Template |
---|---|
This uses the same template from above.
<div buildfire-repeat="order in context.restaurant.orders">
<span>Order ID: ${order.id}</span>
<span>Order Date: ${new Date(order.date).toLocaleTimeString()}</span>
<!-- nested repeater -->
<div buildfire-repeat="item in order.items">
<span>Item: ${item}</span>
</div>
</div>