Static UI int he context of this document we will refer to as inflation.
Let me explain what this means and how it works.
Not all UI requires data updates that require binding. Some examples of this are
- static lists
- select
Let say I have a list of data. For each item in that list I want to generate repetative ui. Typically you need to create a template element for the UI to repeat and loop through each item in your data and create a new template instance. The problem comes in on populating your instance where the data parts need to go. That process we call inflation, taking a data item and copying the values to where they need to go in your element created from a template. In other words, inflate your element with data.
This is also a very nice way to create custom components such as data grids, lists, trees...
Garbage collection is a expensive operation and though creating new objects is cheap enough, garbage collecting them is not. When working on virtualized systems it may be in your best interest to maintain a element store so that you can reuse elements that are removed due to them scrolling out of view.
In these cases you don't want to retain the information in the element from the last data item that inflated it. Due to this the inflation manager also provides a deflate feature. Deflating a element removes the set data from the required places but only removes information at the loctions origionally inflated.
This means you can re-inflate a element with a new data model when it comes into service again.
- Create the template that defines the UI structure.
- Register the template with the inflation manager.
- Get instances for the UI from the inflation manager.
- Unregister the template once done.
Lets start off with defining a simple HTMLTemplateElement.
<template id="person-template">
<div >${firstName}</div>
<div>${lastName}</div>
<div classlist.if="age < 18 'red' : 'black'">${age}</div>
</template>
For most of the time, all the expressions are attribute or innerText expressions using the string template "${}" syntax. The exceptions to this is style binding where the syntax is exactly the same as it is in normal binding. The big difference being that the evaluation takes place during the inflation process and not again.
Now that we have the template let's register it with the inflation manager and give it an id to identify it by.
const template = this._element.querySelector("#person-template");
crsbinding.inflationManager.register("list-item", template);
Now that you have it you can request UI from the inflation manager using the id that you defined in the first parameter.
const data = getData();
const fragment = crsbinding.inflationManager.get("list-item", data);
this.list.appendChild(fragment);
In the above code we get an array of data items from our source.
Then we ask the inflation manager to give us back a list of elements inflated with the data items.
The result of this is a document fragment.
We then add it the elements to the relevant DOM element for display.
If you are done with an element, before you send it to your element store, you can deflate it this way.
crsbinding.inflationManager.deflate("list-item", element);
The second parameter can either be a individual element or an array of elements.
Lets say that we want to populate a combobox with a list of items that get returned from the server. You can follow the procedure above but it would be more convenient if you could just use a binding expression in the HTML.
<select value.bind="selectedId">
<template for.once="item of items">
<option value="${item.id}">${item.caption}</option>
</template>
</select>
Note the .once
synatx postfixing the for attribute.
This will use the inflation manager under the hood to statically generate the options you need for the select.
The result of this operation looking like this in the inspector.
<select>
<option value="1">Item 1</option>
<option value="2">Item 2</option>
<option value="3">Item 3</option>
</select>
You may want to remove elements if it fails a binding condition.
This allows you to use one template but cater for more than one scenario.
To remove a element you can use the remove.if
attribute.
<div remove.if="title == null ? true">${title}</div>
This will cost you a little more processing when building the UI but keep your dom structure clean of redundent elements.
You can also use this optional elements when using template's for syntax but please take note of the following.
<div class="container">
<template for.once="item of data">
<div data-person="${item.person}" data-trade="${item.trade}" type="${item.type}" value="${item.value}">
<div remove.if="item.title == null ? true">${item.title}</div>
<svg class="icon" remove.if="item.value == null ? true">
<use xlink:href="${item.value == true ? '#checked' : '#unchecked'}" />
</svg>
</div>
</template>
</div>
- Note that you must use
for.once
as this uses the inflation manager under the hood where the normal for uses live binding. You can't use remove.if on live binding. - The template must have a single child that contains other elements.
Render collection is a shorthand for using the inflation manager. The scenario is as following:
- I have a template I want to repeat over but I don't want the overhead of a dynamic collection binding. Just take this array of data and render it out statically using a given template.
- Preload in this case will not work as this data is dependent on other criteria and may introduced at any time.
crsbinding.utils.renderCollection
helps you deal with this scenario easily.
Consider the following HTML.
<ul>
<template id="myTemplate">
<li>${title}</li>
</template>
</ul>
To render this out when we get our data you need to do the following in javascript.
const template = this._element.querySelector("#myTemplate");
crsbinding.utils.renderCollection(template, data);
In this scenario it will render the collection at the same location as the template element.
In other words the UL
element.
This is the parameters for the renderCollection
function and how to use them.
- template - that template element to use for rendering the collection
- collection - the collection of data (array) to use in the rendering process
- oldElements = null - what elements do you want to reuse in the next inflation. These should be elements you invated in a previous
renderCollection
. - parentElement = null - that is the parent element you want the collection to be added too. If null it will use the template's parent.
Since the removeInflated will remove all inflated elements you should make sure your structure is of such a nature that you don't remove un-intended elements.
In the above example the UL is the parent container, and it's safe to call this over and over rendering collection changes with no fear.
If you want to find all the elements that was used before so that you can pass it on as oldElements
look at the following code.
updateUI(data) {
const template = this._element.querySelector("template");
let elements = Array.from(template.parentElement.children).filter(item => item.__inflated == true);
if (elements.length == 0) elements = null;
crsbinding.utils.renderCollection(template, data, elements);
}
If the oldElements the old elements will be removed and and the new elements added.
Elements you pass on as old elements just get re-inflated with new values.
They don't get removed from the dom, making things a bit faster.