Skip to main content

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)

tip

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:

NameTypeExpression
appIdstring${context.appId}
pluginIdstring${context.pluginId}
appUserobject${context.appUser}
appThemeobject${context.appTheme}
tip

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)}.

notes

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);
})
tip

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.

tip

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

NameTypeRequiredDescriptionDefault
idstringnoA unique id that identify the evaluation requestAuto generated id
expressionstringnoThe content that will be evaluated
extendedContextobjectnoAdditional context to be used for evaluating expressions
options.extendedContext
NameTypeRequiredDescriptionDefault
extendedContextobjectnoAdditional context to be used for evaluating expressions

callback(err, res)

NameTypeDescription
errstringError string, null when the operation is successful
resobjectContains the evaluated expression and the evaluation request
res
NameTypeDescription
evaluatedExpressionstringThe evaluated expressions
evaluationRequestobjectThe evaluation request data, which could be used if needed, in addition to the destroy functionality
res.evaluationRequest
NameTypeDescription
idstringThe given id in the evaluate method options
optionsstringThe evaluate method options
callbackfunctionThe callback of evaluate method
destroyfunctionProvide 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
});
caution

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.

tip

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

tip

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)
}
caution

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)

NameTypeDescription
errstringError string, null when the operation is successful
contextstringThe 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.

tip

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

Expression builder custom expressions

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)

NameTypeDescription
errstringError string, null when the operation is successful
datastringThe 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})

tip

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.

note

This function is used by the WYSIWYG, where you can open the Expression Builder by clicking on (Insert expression) menu item in the WYSIWYG (Insert) menu

Expression Builder
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

NameTypeDescription
errstringError string
expressionstringThe 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 enabled

note

Expressions evaluation will be turned on automatically when the WYSIWYG content contains an expression (${}) and will be turned off if there are no expressions

note

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.

note

Expressions will be added at the last position of the cursor before opening the Expression Builder

Insert expression

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.

Expression Builder

Inserted expression

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 ToolTemplate
WYSIWYG Code ToolWYSIWYG Code Tool

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>