Expressions

Expressions play a pivotal role in binding engines. They serve as the means by which we articulate our intentions within the markup. The binding engine then evaluates these expressions, discerning our intent and preparing it for execution based on specific events.

When establishing a binding engine, the foremost task is to characterize these expressions and their meanings.

Given that the UI markup shapes the user interface, these expressions are inherently defined within the markup. Thus, an initial step involves drafting a conceptual example of what to support and how to delineate it.

<button click.call="perform">Click Me</button>
<button click.call="perform($event)>Click Me</button>
<button click.call="perform('hello world', $event)">Click Me</button>

In the given example, we encounter a series of expressions. An integral aspect of crafting these expressions is pondering their usability. Translated into plain language, the aforementioned expression states:

“When this button is clicked, execute this particular method and provide these specific attributes.”

We’ve adopted the attribute syntax directly from a programming language, in this instance, JavaScript. Within the example, there’s a noticeable keyword “$event.” In JavaScript, events typically have an event parameter, which is why we’ve taken the term “event” for our use. The “$” symbol is a convention employed to signify that it’s a keyword. This principle will be consistently applied across our expressions.

JavaScript features a call function, allowing you to invoke a function and explicitly assign the value of “this” for that function’s context. In simpler terms, when “this” is referenced within that function, it will point to whatever was designated when using the call function. The expression previously discussed takes inspiration from this JavaScript capability. This design choice ensures familiarity for developers, as it mirrors the same action—executing the specified function.

Data binding expressions

Consider the sentence: “John Doe is 20 years old” from the given example.

This statement is crafted using the expression:
“${person.firstName} ${person.lastName} is ${person.age} old”.

Upon evaluating this expression, it’s evident that three properties can influence its outcome. If any of these three properties change, the text content must be refreshed. In addition, if the person object changes, the text must also refresh. This is true for each item on a property path. This characteristic is typical of one-way bindings, where data flows solely from the binding context to the user interface.

<div>${person.firstName} ${person.lastName} is ${person.age} old</div>

The given expression takes inspiration from the JavaScript string template expression. When processed, the sanitizer will prepend the properties with “context.”. Subsequently, the compiler will formulate a function tasked with producing the resultant string, which will then be updated in the designated element.

const fn = (context) => {
  return `
  ${context.person.firstName} 
  ${context.person.lastName} is 
  ${context.personage} old`;
}

Note: The above code is generated as a single line.

By leveraging concepts from JavaScript, we can simplify the underlying processes. This efficiency not only lightens the load on the compiler but also bolsters the system’s overall speed and performance.

Conditional expressions

Conditional expressions are commonly represented using “if” or “case” statements. With the “if” statement, we take cues from the JavaScript ternary operator. We’ll delve deeper into specific examples when discussing “if” and “case” expressions. For the moment, it’s vital to recognize that conditional expressions are fundamental in binding evaluations, serving as their essential components.

For such scenarios, the sanitizer will prepend the property paths with “context.”. Subsequently, the compiler will produce a full-fledged “if” statement.

Consider a expression such as:

<div data-status.if="person.firstName == 'John' ? 'pass' : 'fail'"></div>

This will result in:

const fn = (context) => {
  if (context.person.firstName == 'John') {
    return 'pass';
  }
  else {
    return 'fail';
  }
}

Every time the person or firstName properties change, the attribute will be updated based on the conditional expression and the value of the property being evaluated.

Complex expressions are also supported so that you can add “and” and “or” operators to the expressions. In this case we borrow once again from the JavaScript expressions as it reduces parsing cost.

<div 
  data-status.if="name == 'John' || name == 'Jane' ? 'pass' : 'fail'">
</div>

Supported expressions