XML Markup
When you write XML markup to create an XMLUI app, you use XML tags to name components. And you use XML attributes to set properties that govern their behavior.
App file roots
In Main.xmlui, the top-level markup can include reusable component declarations as well as the app markup. Top-level <Component> elements define user-defined components. The single top-level element that is not <Component> is the app to render.
<App>
<StatusPill value="Ready" />
</App>
<Component name="StatusPill">
<Badge value="{ $props.value }" variant="pill" />
</Component>An entry file can contain zero, one, or many top-level <Component> declarations, but it can contain only one top-level app root. Their order does not matter: put the app first when you want readers to understand the page flow, or put component definitions first when the reusable building blocks are the focus. If the file contains only <Component> declarations, XMLUI renders an empty app as if the file contained <Fragment />, and logs a browser warning.
This mixed top-level form is entry-file only. A component file in the components folder still contains a single <Component> definition. See User-defined components for the component rules and Keep a small app in one file for a practical example.
Properties
An attribute may be a literal string that sets the value of a property.
<Text value="This is rendered as text." /><Text value="This is rendered as text." />Expressions
An attribute may also be a JavaScript expression — enclosed in curly braces { } — that dynamically sets the value of a property.
<Text value="Life, the universe, and everything: { 6 * 7 }" /><Text value="Life, the universe, and everything: { 6 * 7 }" />XML comments are ignored before expression parsing. Curly braces inside comments are treated as comment text, so expressions such as {count} in <!-- ... --> are not evaluated and do not affect the markup that follows.
<!-- This comment mentions {count}, but XMLUI does not evaluate it. -->
<Text value="Count: {count}" />Quotes inside attributes
XMLUI markup follows XML quoting rules before it parses JavaScript expressions or event handlers. If an attribute is wrapped in double quotes, an unescaped double quote inside the value closes the attribute, even when that quote appears in JavaScript code or a JavaScript comment.
This is invalid markup:
<Button onClick="() => {
// "quoted text" closes the onClick attribute early
count++;
}" />
Prefer single quotes inside a double-quoted attribute:
<Button onClick="() => {
// 'quoted text' is safe here
count++;
}" />Or wrap the attribute in single quotes when the handler needs double-quoted strings:
<Button onClick='() => {
const label = "Save";
count++;
}' />You can also escape literal double quotes as ", but the two patterns above are usually easier to read.
An expression can hold a JSON list.
<Items data="{ ['Bakerloo', 'Central', 'Circle'] }" >
<Text>{ $item }</Text>
</Items><Items data="{ ['Bakerloo', 'Central', 'Circle'] }" >
<Text>{ $item }</Text>
</Items>Or a complex JSON object, in which case you'll write an outer set of curly braces to introduce the expression and an inner set to define an object like this form's data property.
<App>
<Form id="searchForm" padding="0.5rem"
data='{ { station: "Brixton", wifi: true, toilets: false } }'
onSubmit="() => { preview.setValue(JSON.stringify($data)) }"
>
<Text>Search for station amenities</Text>
<HStack verticalAlignment="center" >
<FormItem bindTo="station" width="*" />
<FormItem
type="checkbox"
label="wifi"
bindTo="wifi"
labelPosition="start"
width="fit-content"
/>
<FormItem
type="checkbox"
label="toilets"
bindTo="toilets"
labelPosition="start"
width="fit-content"
/>
</HStack>
<property name="buttonRowTemplate">
<Button type="submit" icon="search" label="Search"/>
</property>
</Form>
<TextArea id="preview" />
</App><App>
<Form id="searchForm" padding="0.5rem"
data='{ { station: "Brixton", wifi: true, toilets: false } }'
onSubmit="() => { preview.setValue(JSON.stringify($data)) }"
>
<Text>Search for station amenities</Text>
<HStack verticalAlignment="center" >
<FormItem bindTo="station" width="*" />
<FormItem
type="checkbox"
label="wifi"
bindTo="wifi"
labelPosition="start"
width="fit-content"
/>
<FormItem
type="checkbox"
label="toilets"
bindTo="toilets"
labelPosition="start"
width="fit-content"
/>
</HStack>
<property name="buttonRowTemplate">
<Button type="submit" icon="search" label="Search"/>
</property>
</Form>
<TextArea id="preview" />
</App>In addition to a literal string or an expression, you will sometimes use the special tag
<property>to set a value using markup, like theButtonRowTemplatethat defines this form's button.
Variables
A component may declare a variable that's visible to itself and its children. Variable names start with a letter or an underscore (_) and continue with these characters or digits.
You can declare a variable using the var prefix.
<App var.stations="{ [ 'Bakerloo', 'Central', 'Circle'] }">
<Items data="{stations}">
<Text> {$item} </Text>
</Items>
</App><App var.stations="{ [ 'Bakerloo', 'Central', 'Circle'] }">
<Items data="{stations}">
<Text> {$item} </Text>
</Items>
</App>Or using the <variable> helper tag.
<App>
<variable
name="stations"
value="{ [ 'Bakerloo', 'Central', 'Circle'] }" />
<Items data="{stations}">
<Text>{$item}</Text>
</Items>
</App><App>
<variable
name="stations"
value="{ [ 'Bakerloo', 'Central', 'Circle'] }" />
<Items data="{stations}">
<Text>{$item}</Text>
</Items>
</App>When does a variable stop following its initial value?
A variable declared with var.name="{expr}" starts as a reactive binding. XMLUI re-evaluates expr when its dependencies change.
As soon as you assign to that same variable in an event handler, the variable switches to the assigned runtime value. In practice: it no longer follows the original expression.
Click Increment source first: mirror follows source.
Then click Override mirror: from that point, mirror no longer tracks source.
This is intentional: explicit assignment means "from now on, use this runtime value for this variable."
<App var.source="{0}" var.mirror="{source}">
<Text>source = {source}</Text>
<Text>mirror = {mirror}</Text>
<Button label="Increment source" onClick="source++" />
<Button label="Override mirror" onClick="mirror = 999" />
</App><App var.source="{0}" var.mirror="{source}">
<Text>source = {source}</Text>
<Text>mirror = {mirror}</Text>
<Button label="Increment source" onClick="source++" />
<Button label="Override mirror" onClick="mirror = 999" />
</App>A common place this surprises developers is when a variable mirrors a DataSource result.
In this example, items starts reactive (apiResult.value ?? []) and then gets reassigned to a filtered snapshot. After that reassignment, items does not follow refetches:
<App>
<DataSource id="apiResult" url="/api/names-with-activity-decoupled" />
<APICall
id="addItem"
method="post"
url="/api/names-with-activity-decoupled"
invalidates="/api/names-with-activity-decoupled"
/>
<variable name="items" value="{apiResult.value ?? []}" />
<Text>DataSource count: {(apiResult.value ?? []).length}</Text>
<Text>items count: {items.length}</Text>
<Button
label="Take snapshot of active items"
onClick="items = (apiResult.value ?? []).filter(i => i.active)"
/>
<Button
label="Add active item"
onClick="addItem.execute()"
/>
<Items data="{items}">
<Text>{$item.name}</Text>
</Items>
</App><App>
<DataSource id="apiResult" url="/api/names-with-activity-decoupled" />
<APICall
id="addItem"
method="post"
url="/api/names-with-activity-decoupled"
invalidates="/api/names-with-activity-decoupled"
/>
<variable name="items" value="{apiResult.value ?? []}" />
<Text>DataSource count: {(apiResult.value ?? []).length}</Text>
<Text>items count: {items.length}</Text>
<Button
label="Take snapshot of active items"
onClick="items = (apiResult.value ?? []).filter(i => i.active)"
/>
<Button
label="Add active item"
onClick="addItem.execute()"
/>
<Items data="{items}">
<Text>{$item.name}</Text>
</Items>
</App>If you need the variable to stay reactive while also supporting local overrides, keep the override in a separate variable and combine them in the binding expression:
<App>
<DataSource id="apiResult" url="/api/names-with-activity-live" />
<APICall
id="addItem"
method="post"
url="/api/names-with-activity-live"
invalidates="/api/names-with-activity-live"
/>
<variable name="activeOnly" value="{false}" />
<Text>
Visible count: {
(apiResult.value ?? []).filter(i => !activeOnly || i.active).length
}
</Text>
<Button
label="Toggle active filter"
onClick="activeOnly = !activeOnly"
/>
<Button
label="Add active item"
onClick="addItem.execute()"
/>
<Items
data="{(apiResult.value ?? []).filter(i => !activeOnly || i.active)}"
>
<Text>{$item.name}</Text>
</Items>
</App><App>
<DataSource id="apiResult" url="/api/names-with-activity-live" />
<APICall
id="addItem"
method="post"
url="/api/names-with-activity-live"
invalidates="/api/names-with-activity-live"
/>
<variable name="activeOnly" value="{false}" />
<Text>
Visible count: {
(apiResult.value ?? []).filter(i => !activeOnly || i.active).length
}
</Text>
<Button
label="Toggle active filter"
onClick="activeOnly = !activeOnly"
/>
<Button
label="Add active item"
onClick="addItem.execute()"
/>
<Items
data="{(apiResult.value ?? []).filter(i => !activeOnly || i.active)}"
>
<Text>{$item.name}</Text>
</Items>
</App>Special case: assigning a variable directly to a component API object (for example
myData = ds) is tracked as a live API reference so it can stay reactive to API updates.
Nested variables
The same variable name can be declared in nested scopes. The engine resolves the name to the variable in the closest (innermost) scope.
<App var.title="var.title is 'Hello, from App!'">
<H1>{title}</H1>
<VStack var.title="var.title is 'Hello, from VStack'!">
<Text>{title}</Text>
</VStack>
</App><App var.title="var.title is 'Hello, from App!'">
<H1>{title}</H1>
<VStack var.title="var.title is 'Hello, from VStack'!">
<Text>{title}</Text>
</VStack>
</App>Multiple instances
Each counter is a separate instance of CounterTest with its own local component variables.
<App>
<HStack horizontalAlignment="center">
<VStack>
<Text variant="caption">Counter Instance 1</Text>
<CounterTest instance="1" />
</VStack>
<Stack width="10rem"/>
<VStack>
<Text variant="caption">Counter Instance 2</Text>
<CounterTest instance="2" />
</VStack>
</HStack>
</App>
<Component name="CounterTest" var.count="{0}">
<Text>Counter ID: {$props.instance}</Text>
<Text>Count: {count}</Text>
<Button onClick="count = count + 1">
Increment {$props.id}
</Button>
</Component><App>
<HStack horizontalAlignment="center">
<VStack>
<Text variant="caption">Counter Instance 1</Text>
<CounterTest instance="1" />
</VStack>
<Stack width="10rem"/>
<VStack>
<Text variant="caption">Counter Instance 2</Text>
<CounterTest instance="2" />
</VStack>
</HStack>
</App>
<Component name="CounterTest" var.count="{0}">
<Text>Counter ID: {$props.instance}</Text>
<Text>Count: {count}</Text>
<Button onClick="count = count + 1">
Increment {$props.id}
</Button>
</Component>Reactive variables
In Reactive data binding we saw how a Select can cause a Table to refresh by setting the url property of the DataSource on which the Table depends. Here's a more basic example of how components interact with reactive variables.
<App var.count="{0}" var.countTimes3="{3 * count}" >
<Button
label="Click to increment the count"
onClick="count++" />
<Text>Click count = {count} (changes directly)</Text>
<Text>Click count * 3 = {countTimes3} (changes indirectly)</Text>
</App><App var.count="{0}" var.countTimes3="{3 * count}" >
<Button
label="Click to increment the count"
onClick="count++" />
<Text>Click count = {count} (changes directly)</Text>
<Text>Click count * 3 = {countTimes3} (changes indirectly)</Text>
</App>The Button's click handler increments count directly. But because countTimes3 is define using an expression that refers to count, the engine reevalautes that expression each time count changes and updates countTimes3 indirectly.
We've seen two ways to declare variables: a var declaration in an XML attribute, or the variable tag. A similar pattern applies to event handlers. The Button to increment the count declares its handler in an attribute whose name combines the on prefix with the name click. You may also use the <event> helper tag with no prefix for the event name.
<App var.count="{0}" >
<Button label="Click me! Click count = {count}">
<event name="click">
{ count++ }
</event>
</Button>
</App><App var.count="{0}" >
<Button label="Click me! Click count = {count}">
<event name="click">
{ count++ }
</event>
</Button>
</App>Why use the
<event>tag? In this example there's no reason to prefer it. But as we saw above, you declare aForm'sbuttonRowTemplateproperty in XMLUI markup using the<property>helper tag. The same pattern applies when a button's handler is an XMLUI component like<APICall>.<Button label="Click to increment the count on the server"> <event name="click"> <APICall url="/count/increment" /> </event> <Text>Click count = {count}</Text> </Button>
Global Variables
A component may declare a global variable that's visible everywhere in the application. Global variable names follow the variable naming shown earlier.
Globals must be declared in one of two places: the root element of
Main.xmlui(usingglobal.or<global>) or as top-level declarations in aGlobals.xscode-behind file. They cannot be declared in user-defined component files. See Scoping › Declaring globals in Globals.xs for the code-behind form. You can declare a variable using theglobalprefix.
<App global.stations="{ [ 'Bakerloo', 'Central', 'Circle'] }">
<H2>Station List</H2>
<Stations />
</App>
<Component name="Stations">
<Items data="{stations}">
<Text> {$item} </Text>
</Items>
</Component><App global.stations="{ [ 'Bakerloo', 'Central', 'Circle'] }">
<H2>Station List</H2>
<Stations />
</App>
<Component name="Stations">
<Items data="{stations}">
<Text> {$item} </Text>
</Items>
</Component>Alternatively, you can use the <global> helper tag.
<App>
<global name="stations" value="{ [ 'Bakerloo', 'Central', 'Circle'] }" />
<H2>Station List</H2>
<Stations />
</App>
<Component name="Stations">
<Items data="{stations}">
<Text> {$item} </Text>
</Items>
</Component><App>
<global name="stations" value="{ [ 'Bakerloo', 'Central', 'Circle'] }" />
<H2>Station List</H2>
<Stations />
</App>
<Component name="Stations">
<Items data="{stations}">
<Text> {$item} </Text>
</Items>
</Component>In markup, you can declare globals only in the root element of
Main.xmlui— declaring them on a nested element or inside a user-defined component file produces an error. The other supported location is theGlobals.xscode-behind file.