View templates were introduced in version 4.90.0 of the JourneyApps Runtime.
In TypeScript apps, the minimum runtime-build version required is 2.4.7. See the docs here on how to configure this.
Overview
View templates allow developers to define view XML as a standalone template, and reference it across views. What this means for developers:
Less code duplication across views.
Easier to maintain complex views, by breaking up the view XML into smaller snippets in templates.
Can introduce a better separation of concerns - a set of view components can be grouped into distinct templates.
View templates are supported in both JavaScript and TypeScript apps.
Limitations
Limited validation and auto-complete in OXIDE.
Usage & Syntax
Using view templates involves two things:
Defining the template -> This is done using template definitions (template.xml files) for your app.
Referencing it in a view -> This is done using the template UI component on a view.
Let's dive into these:
1) Define a view template
When working with view templates we recommend opening (and docking) the View Templates panel in OXIDE:
Here you can create new templates, view and select existing templates.
You can create multiple template files (e.g. demo.template.xml), and each can contain multiple template definitions (<template-def />). A template file is simply a way to group similar template definitions.
Inside template-def, you can define UI components in the similar to how you’d define them in views. You can pass parameters from your views into the template definition, including objects and functions.
In your view XML, reference the view template using the template UI component and pass the parameters you defined:
<!-- main.view.xml --><?xml version="1.0" encoding="UTF-8"?><viewtitle="buttons"> <varname="user"type="user" /><!-- Provide the name of the template definition in the name="" attribute. --><!-- Pass parameters from your view to the template using the parameters you defined in the template definition. --> <templatename="save-back-buttons"user="user"onBack="$:goBack()"onSave="$:save()" /></view>
// main.tsasyncfunctioninit() {view.user =awaitDB.user.first();}functiongoBack() {notification.success("Back was pressed");}functionsave() {notification.success("Save was pressed");}
Use Cases
Functions
The below example illustrates how a function can be called in a view template and pass parameters to the view.
Template definition:
<!-- dialog.template.xml --><!-- Dialog to select an item from an object table --><template-defname="select-item-template"> <paramname="items"type="array:line_item" /> <paramname="selectItem"type="function"><!-- Define function arguments and type --> <argname="selectedItem"type="line_item"/> </param> <dialogid="select-item-dialog"title="Select an item"auto-hide="true"> <body> <object-tablequery="items"label="Items"empty-message="Your items will appear here"> <columnheading="Name">{name}</column> <columnheading="Code">{product_code}</column> <actionon-press="$:selectItem($object)" /> </object-table> </body> </dialog> </template-def>
View XML:
<!-- main.view.xml --><!-- Reference the template --><templatename="select-item-template"items="items"selectItem="$:selectItem(selectedItem)" />
Your app will fail to deploy with a duplicate identifier error if you use the same identifier for an App Module in a view and in a template that is referenced in the view. Ensure that you do not import Modules that are already imported in the underlying view.
Nested templates
You can nest templates by referencing one in a template definition as follows:
The below shows a composable dialog as a view template and demonstrates:
Passing objects or functions as parameters to the template.
Using a function expression (inline function)
Using an app module in a view template.
<!-- dialog.template.xml --><?xml version="1.0" encoding="UTF-8"?><templates> <template-defname="create-edit-item-template"> <importmodule="*"as="shared"from="~/lib/index"/> <paramname="lineItem"type="line_item" /> <paramname="onCancel"type="function" /> <paramname="onSave"type="function" /><!-- Dialog that will be used in multiple views --> <dialogid="create-edit-item"title="{$:lineItem ? 'Edit' : 'Create'} Item"auto-hide="false"> <body> <text-inputlabel="Name"bind="lineItem.name"required="true" /> <text-inputlabel="Product Code"bind="lineItem.product_code"required="true" /> <heading /> <buttonshow-if="lineItem"label="Delete"icon="{$:shared.ICONS.delete}"on-press="$:shared.dialogHelper.delete('add-edit-item')"validate="false"color="negative"style="outline" /> </body> <button-group> <buttonlabel="Cancel"icon="{$:shared.ICONS.close}"on-press="$:onCancel"validate="false"style="outline" /> <buttonlabel="Save"icon="{$:shared.ICONS.done}"on-press="$:onSave"validate="true" /> </button-group> </dialog> </template-def></templates>
The corresponding view XML and TS:
<!-- main.view.xml --><?xml version="1.0" encoding="UTF-8"?><viewtitle="Dialog Example"> <varname="line_item"type="line_item" /> <buttonlabel="Show dialog"on-press="$:shared.dialogHelper.open('create-edit-item')"validate="false" /> <templatename="create-edit-item-template"lineItem="line_item"onCancel="$:cancel('create-edit-item')"onSave="$:save('create-edit-item')" /> <!-- For reference, here's how the dialog was defined prior to the template --> <dialogid="reference"title="{$:view.line_item ? 'Edit' : 'Add'} Item"auto-hide="false"> <body> <text-inputlabel="Name"bind="line_item.name"required="true" /> <text-inputlabel="Product Code"bind="line_item.product_code"required="true" /> <heading /> <buttonshow-if="line_item"label="Delete"icon="{$:shared.ICONS.delete}"on-press="$:shared.dialogHelper.delete()"validate="false"color="negative"style="outline" /> </body> <button-group> <buttonlabel="Cancel"icon="{$:shared.ICONS.close}"on-press="$:cancel()"validate="false"style="outline" /> <buttonlabel="Save"icon="{$:shared.ICONS.done}"on-press="$:save()"validate="true" /> </button-group> </dialog></view>
// main.tsasyncfunctioninit() { view.line_item =awaitDB.line_item.first();}functioncancel(dialogId:string) {notification.success("Cancel was pressed");shared.dialogHelper.close(dialogId);}asyncfunctionsave(dialogId:string) {awaitview.line_item.save();notification.success("Save was pressed");shared.dialogHelper.close(dialogId);}
Architecture
It is important to note that a view template, unlike other UI components, does not evaluate expressions, but instead replaces templated components and attributes with the provided values.
Let us use the following example of a template with functionparam:
The template-def declares a function parameter with two arguments, msg and theUser.
When this template-def is compiled for the particular view (this happens during a deploy, not at runtime), the expression is broken up into parts and the view's template component is replaced with the components inside the template-def. Function definitions are replaced as follows:
This results in the button’s attribute on-press="$:alert('Hello', user)"
Also note that one could still pass view specific arguments, for example <template name="test" sayHello="$:alert(msg, theUser, 'main')"/> and only the two matching arg values will be replaced, resulting in on-press="$:alert('Hello', user, 'main')"