Coding with XMLUI

💡

When you create apps, sometimes you need to declare UI logic. The XMLUI components and the framework relieve you of many aspects other web UI frameworks require you to add code to.

However, there are a few occasions where you need to add code. XMLUI strives to reduce coding to using expressions instead of utilizing advanced concepts like function declarations, conditional statements, loops, etc.

This article will teach you the fundamental concepts and constructs of working with XMLUI code.

Fundamental Concepts

There are a few fundamental concepts the understanding of which help you add UI logic to your apps.

💡

XMLUI has a scripting language (XmlUiScript), a subset of JavaScript that follows its syntax and semantics.

Do not feel intimidated if you do not have experience with JavaScript. You rarely have to write complex code with XMLUI. In most cases, your intention can be described with short expressions.

You can learn more details about the subset of JavaScript supported by XMLUI in these articles:

Inline Code Snippets

💡

You can place code snippets inline into component properties, variable values, and event handlers.

This technique works well when the code snippets are short and close to the components using them.

<App var.result="???" >
  <Button
    label="Calculate the sum of 1 to 100"
    onClick="
      let sum = 0;
      for (let i = 1; i <= 100; i++) sum += i;
      result = sum;
    " />
  <Text>Result: {result}</Text>
</App>

This technique works well when the code snippets are short and close to the components using them. In the following markup, the value of the onClick attribute is a code snippet just like the {result} expression within Text. Now, accept them as they are; later, you will learn about their syntax.

Code-Behind Files

💡

You can put code (variables, functions) into code-behind files. Markup files (.xmlui extension) may have a code-behind file using the markup file name but the .xmlui.xs extension.

XMLUI automatically binds component markup with code-behind files. This technique is helpful when you have long code snippets that would otherwise disturb your understanding of the markup.


Main.xmlui (markup)
<Button 
  id="calcButton"
  label="Show delay"
  onClick="showDelay()"/>
Main.xmlui.xs (code-behind)
function showDelay() {
  const delay = 500 * 2;
  toast('Delay calculated: ' + delay);
}
📔

The code you declare in a code-behind file is visible in the entire component but not outside.

Later, you will learn about how you can use scripts in the code-behind files.

Component IDs

💡

With the id attribute of a component tag, you can assign an identifier to a component that you can use to refer to that particular component in code (expressions). Component IDs are visible only in the scope of their markup (and associated code-behind) file.

You can access the exposed methods and properties of the particular component through its component ID. The following section shows a sample.

Expressions

💡

Expressions are the primary tools for binding components to work one with the state of another.

<App>
  <TextBox id="myText" initialValue="Hello!" />
  <Text>You typed: {myText.value}</Text>
</App>

Type some text into the textbox to discover how this small example works.

The {myText.value} part in the Text component is an expression. The wrapping braces sign the engine that the content within will be evaluated as the app runs.

The TextBox component's identifier is myText. You can access the TextBox's exposed values (and methods) through this ID. myText.value represents the current text content within the box. So, this expression binds the TextBox's value to the Text's displayed label.

💡

When the XMLUI engine observes that a dependency of an expression changes, it re-evaluates the expression and re-renders the affected part of the UI (and only that part).

In the small app above, when you type something in the text box, every key you press triggers such a change.

💡

When you set property values, you can mix literal strings with expressions.

<App>
  <Text>I am a string literal.</Text>
  <Text>{"I am" + " an " + "expression."}</Text>
  <Text>I see {2+3} apples and {3*4} oranges.</Text>
</App>

Here, the first text uses a string literal, the second is a sheer expression, and the third combines string literals and expressions.

Variable Declarations

Variables are indispensable to the UI logic as they store some state. You can use expressions to set a variable's initial value:

<App
  var.myValue="{6 * 7}">
  Life, universe, and everything: {myValue}
</App>

Here, var.myValue is a variable declaration with a variable name (myValue) and an initial value ({6 * 7}). Before displaying the text, the engine initializes the myValue variable. Thus, when that is displayed, {myValue} is replaced with its current value, 42.

💡

You can move variable declarations into code-behind files.

The following code has the same behavior as the previous one.


Main.xmlui (markup)
<App>
  Life, universe, and everything: {myValue}
</App>
Main.xmlui.xs (code-behind)
var myValue = 42;
 
 
📔

XMLUI attribute values are strings by default. While components transform string attribute values to the expected types, there is no such an expected type of the variable so that they will be initialized to a string. Use an expression to set the initial variable value to utilize the proper type.

The following sample demonstrates this concept:

<App
  var.stringValue="true"
  var.boolValue="{true}"
  var.num1Value="123"
  var.num2Value="{123}">
  <Text>'stringValue' is a {typeof stringValue}, it's value is {stringValue}</Text>
  <Text>'boolValue' is a {typeof boolValue}, it's value is {boolValue}</Text>
  <Text>'num1Value' is a {typeof num1Value}, it's value is {num1Value}</Text>
  <Text>'num2Value' is a {typeof num2Value}, it's value is {num2Value}</Text>
</App>

Check the variable types in the output:

💡

XMLUI variables are reactive: whenever their dependencies change, their value is reevaluated.

<App var.count="{0}" var.countTimes3="{3 * count}" >
  <Button
    label="Click to increment!"
    onClick="count++" />
  <Text>Click count = {count}</Text>
  <Text>Click count * 3 = {countTimes3}</Text>
</App>

Clicking the button increments only the count variable. Because countTimes3 is a reactive variable that depends on count, incrementing count triggers updating countTimes3 too.

Event Handlers

💡

When a user interaction or system event occurs, the XMLUI engine executes component event handlers. You can override the default event handler with your custom event handler.

In an event handler, the entire attribute value is an XMLUI Script code snippet, so you do not need to wrap the code into curly braces:

<App>
  <Button
    label="Greet me!"
    onClick="toast('Howdy!')" />
</App>

If you click the button, the engine executes the toast('Howdy!') code and displays a toast message.

Event handlers often contain not only expressions but also statements.

💡

You can use local variables within expressions and function declarations. The let keyword allows using a mutable variable; const declares an immutable one (following the JavaScript semantics). In contrast to var, local variables declared with let and const are not reactive.

<App var.result="???" >
  <Button
    label="Calculate the sum of 1 to 100"
    onClick="
      let sum = 0;
      for (let i = 1; i <= 100; i++) sum += i;
      result = sum;
    " />
  <Text>Result: {result}</Text>
</App>
💡

You can move event handler declarations into code-behind files.

The following code has the same behavior as the previous one.


Main.xmlui (markup)
<App>
    <Button
        label="Calculate the sum of 1 to 100"
        onClick="calculate()"/>
    <Text>Result: {result}</Text>
</App>
 
Main.xmlui.xs (code-behind)
var result = "???";
 
function calculate() {
  let sum = 0;
  for (let i = 1; i <= 100; i++) sum += i;
  result = sum;
  }
💡

When your component has an ID, the engine automatically binds component event handlers with functions in the code-behind file.

For example, in the following markup, the button has the id calcButton. The engine automatically binds the button's click event with the calcButton_onClick function in the code-behind file.


Main.xmlui (markup)
<App>
    <Button
        id="calcButton"
        label="Calculate the sum of 1 to 100"/>
    <Text>Result: {result}</Text>
</App>
 
Main.xmlui.xs (code-behind)
var result = "???";
 
function calcButton_onClick() {
  let sum = 0;
  for (let i = 1; i <= 100; i++) sum += i;
  result = sum;
}
💡

Some event handlers may have one or more arguments.

In the following example, the didChange event of TextBox has a single argument: the new value after the change.

<App var.message="(not changed yet)">
  <TextBox
    initialValue="Hello!"
    onDidChange="(newValue) => message = newValue" />
  <Text>The new TexBox value: {message}</Text>
</App>

The (newValue) => message = newValue expression is an arrow function (with the same syntax as JavaScript). In the expression, newValue is the function argument, message = newValue is the function's body.

When you change the text in the TextBox, the message variable is updated with the new value.

📔

XMLUI understands your intention when defining an event handler. In addition to the arrow function syntax, it accepts several other alternatives, as here the Event Handlers section describes.

Exposed Methods

📔

Reusable components can expose methods to other components.

These methods can use XMLUI Script statements like event handler functions. You can learn more about them in the Reusable Components article.

Code-Behind Variables and Functions

💡

Code-behind files may contain only reactive variables (var keyword) and function declarations on their top-level scope. Any other statement or expression declaration is forbidden.

Thus, the following code-behind file is valid:

var count = 0;
 
function increment() {
  count++;
}

However, this declaration is not, as it contains a local variable and an expression statement:

var selectedIndex = -1;
 
const MY_LABEL = "My label";
selectedIndex++;
 
function selectItem(index) {
  selectedIndex = index;
}
💡

Function declarations can be nested in other functions.

In this case, markup files can see only the outermost declarations; inner functions are unavailable.

While the following code-behind file exposes the calculateValue function, it hides square:

function calculateValue(n) {
  function square(x) {
    return x * x;
  }
 
  let sum = 0;
  for (let i = 1; i <= n; i++) sum += square(i);
  return sum;
}
💡

Currently, XMLUI does not implement the concept of imports, so you cannot create code files and import them into code-behind files (for example, to share utility functions). This feature is being added to the framework in the future.

How XMLUI Code Runs

💡

The engine does not create JavaScript code to execute it in the browser; it runs interpreted code created from the compiled source script.

These are the main reasons XMLUI prefers this technique:

  • It is easier to avoid potential security breaches and work with the browser's CSP (Content Security Policy)
  • XMLUI makes intelligent decisions about when to run the code synchronously and when to opt for asynchronous execution.
  • The rendering engine can make the UI responsive without any special care in the code.

No Frozen UI

💡

You do not need to use async..await constructs.

These keywords are not even part of the XMLUI scripting syntax. The framework will ensure that even if you start an infinite loop in the code, the UI remains responsive:

<App var.count="{0}" var.loopRuns="{false}">
  <HStack>
    <Button enabled="{!loopRuns}"
      label="Start infinite loop"
      themeColor="attention"
      onClick="loopRuns = true; while (loopRuns) count++;"
    />
    <Button
      enabled="{loopRuns}"
      label="Exit infinite loop"
      onClick="loopRuns = false;"/>
  </HStack>
  Counter value: {count}
</App>

Though the first button starts an infinite loop (if it were native JavaScript, it would block the UI), you can still click the other button, which allows the code to break out of the loop, by setting the loopRuns variables to false.

This is the event handler with the infinite while loop:

loopRuns = true; while (loopRuns) count++;

Now, try the code and break out of the loop by clicking the second button:

Async and Sync Code Execution

The engine runs event handler code asynchronously. However, synchronous code immediately gets the expression value to render the component's UI when evaluating expressions.

This behavior has a significant consequence: You cannot use property values of variable initializations that run asynchronously.

For example, in the following markup, using the delay() function would raise an error with this message: "Promises (async function calls) are not allowed in binding expressions":

<Text value="My value {delay(100), 123}" />

Identifier Resolution

When you refer to a particular variable, XMLUI starts resolving it from the innermost scope of its usage (exactly like JavaScript). If an identifier cannot be resolved in its local scope (inline expression, inline event handler, or code-behind file), the engine tries to resolve it in other scopes in this order:

  • Context values (such as $props for reusable components, $item for lists, etc.
  • Declarations in the current markup file (for example, the markup file may declare component IDs or variables.
  • XMLUI utility functions. The framework provides several global utility functions.
  • The global JavaScript scope (window).