Data Binding

TIBET Data Binding

wins

  • Bind authoring happens almost entirely in markup via bind: attributes.
  • Bind data references use industry-standard URI and query path syntax.
  • You can quickly bind to JSON, XML, or JavaScript objects with equal ease.
  • Clear syntax differentiates binds from templating ([[...]] vs {{…}}).
  • Signal-driven operation makes for easy testing and mocking.

concepts

In the context of web applications, data binding is the common term used to describe observation of changes to one or more source objects by one or more observers. As such, data binding is a central feature of modern MV*C architectures and hence most modern web platforms.

In TIBET, data binding is syntactic sugar for setting up observations of Change signals (or one of their subtypes) coming from a source object. These binds can be either one-way or bi-directional depending on your requirements. All binds in TIBET are ultimately implemented via Signals.

TIBET sugars data binding via attributes in the TIBET-specific bind: namespace. You can also create binds using double-brackets [[ ]] within markup, particularly within attribute values.

Dozens of examples of binds can be found in `~lib/test/src/bind`.

The markup below uses TIBET's bind:scope attribute to set a binding context and TIBET's [[ ]] syntax to bind the value attribute's value:

<input type="text" bind:scope="urn:tibet:currentEmployee" value="[[lname]]"/>

Bind scopes can be nested as shown below. In this particular case, the xctrls:list is scoped to ~app_dat/codes and ultimately asks TIBET to invoke the list's set method for the data attribute to whatever data is contained in the file ~app_dat/codes/states.json:

<body bind:scope="~app_dat">
    <div bind:scope="codes">
        <xctrls:list bind:in="{data: states.json}"/>
    </div>
</body>

All bind: sources and sinks are represented by TIBET URIs, a standard way to refer to resources in the web and in TIBET. All specific data references use standards-based TIBET Paths such as JSONPath or XPath depending on the source data format.

Using TIBET's bind:scope, bind:in, bind:out, and bind:io attributes along with more advanced options such as bind:repeat, you can quickly and easily bind data to your UI regardless of whether it is JSON, XML, or a standard JavaScript object.

bind:scope

The bind:scope attribute lets you define a scope on an outer element which will serve as a root for any relative bind paths at or below that scope. This is particular useful on elements like forms or tables where a number of individual properties or rows may benefit from shared scope:

<form bind:scope="urn:tibet:CurrentEmployee">
...
    <input bind:io="firstname"/>
...
</form>

Values in bind:scope attributes found along a UI element's ancestor chain are combined as needed, supporting fully-nested scoping. Any access path below a bind:scope can always be set to an absolute value to directly target a different data scope.

bind:io

Most TIBET binds are bi-directional and use bind:io as their directional attribute to signify the target control both receives input and sends output to the bind:

<input type="text" bind:io="value"/>

bind:in

When dealing with read-only UI controls you can use bind:in to signify that the control receives input from the bind but does not send output to it:

<span bind:in="urn:tibet:hello"/>

bind:out

While not common, it's also possible to bind output separately from input. This can be used to create more interesting data flows via binds:

<unique:widget bind:out="urn:tibet:hello"/>

Bind Values

Simple binds often reference an absolute or relative URI with no specific query. When using the simple form the bind will default to a query of 'value' and invoke get('value') on the resolved URI to acquire the value of the bind.

You can include a query with the URI (or as a standalone bind component) using XPointer syntax (a fragment # specifier followed by a recognized query). Common query syntax options include: xpath1 (XPath 1.0), jpath (JSON Path), and tibet (our own JavaScript path syntax):

<!-- XPath -->
<span bind:scope="urn:tibet:people#xpath1(/people/person[1])">
    First name (bind:io): <textarea bind:io="firstname"></textarea>
</span>


<!-- TIBET path (with slicing) -->
<input type="text" bind:io="{value: urn:tibet:people#tibet(people[0].firstname)}"/>

More complex binds can provide a simplified JavaScript object string to define how the bind should operate:

<input type="text" bind:io="{value: 'The index: [[$INDEX]]'}"/>

As shown in the example above, TIBET binds can include references to a small number of internal variables including the current iteration $INDEX.

Binds also support the TIBET Shell (TSH) formatter syntax via .%. In the example below we're formatting the current item's 'middlename` slot to be upper case:

<input type="text" bind:io="{value: 'The middle name: [[$_.middlename .% upperCase]]'}"/>

To target specific aspects of your source or sink TIBET allows you to use an appropriate query syntax for the data in question such as JSONPath, CSS Queries, XPath, or expanded TIBET path syntax (see TIBET Paths for details).

For attribute values such as class= or to target element content regions you can use a double-bracket syntax [[ ]] to create binds. We use [[ ]] for binds to avoid conflicts with double-curly(brace) {{ }} syntax used by TIBET Templating and other templating systems.

<span bind:scope="urn:tibet:currentEmployee">[[lname]]</span>

The core machinery of data binding, Change signals triggered from set() calls, is universal in TIBET. All TIBET-derived objects automatically implement set() and can trigger Change signals if desired. In TIBET there is no unique Model type or associated inheritance requirement. Any object can serve as a Model, provided you can reference it via TIBET URI.

NOTE: in the RC1 version of TIBET certain source aspects such as `type` and `name` can be problematic. It's best for now if your source JSON uses names that don't mask those. This issue should be resolved in an upcoming release.

As mentioned earlier, all binds in TIBET ultimately refer to a TIBET URI object. TIBET URIs give you an industry-standard syntax to access, cache, and manage application data including being able to snapshot data for undo processing.

A specific URI subtype, TP.core.URN, provides a way to give any data a public name and a URI reference, allowing you to bind to any object.

For example, we could create or retrieve employeeData and assign it to the TIBET urn urn:tibet:CurrentEmployee and then refer to that data from anywhere in TIBET using that URN value as the name:

urn = TP.uc('urn:tibet:CurrentEmployee');
urn.setContent(employeeData);
...

<form bind:scope="urn:tibet:CurrentEmployee">
...
</form>

Data binding in TIBET is fully tooled. When you're using the Sherpa your current bind information is automatically displayed in the Sherpa's HUD display for easy reference and manipulation.

cookbook

Bind Input Elements

Standard HTML <input> elements, or any other elements whose value property should be bound in a bi-directional fashion, can be bound very simply using the bind:io attribute:

// assume this URN and data exists in-page or in memory...
TP.uc('urn:tibet:test_person',
    '{"person": {"lastname": "Smith", "firstname": "Joe"}}'
);

<input type="text" id="firstNameField"
    bind:io="urn:tibet:test_person#jpath($.person.firstname)"/>

As with any bind, the bind:io attribute value contains a URI with optional XPointer defining any query portion. In the sample above we point to the URN urn:tibet:test_person and query it using JSONPath for the person.firstname value.

Bind Element Content

You can bind to the content area of an element using TIBET's [[ ]] syntax.

In this example we use the same data and attribute value as the prior cookbook entry but since a span is normally read-only we can use bind:in to simplify the observations TIBET sets up:

// assume this URN and data exists in-page or in memory...
TP.uc('urn:tibet:test_person',
    '{"person": {"lastname": "Smith", "firstname": "Joe"}}'
);

<span>[[urn:tibet:test_person#jpath($.person.firstname)]]</span>

There's no restriction on what portion of an element's content you bind, just be certain the result creates valid XHTML (so properly escape or use entities as needed).

Bind Attribute Values

To bind into a full or partial attribute value use TIBET's [[ ]] syntax, placing the brackets inside the attribute text in the location you require.

Below we again bind to an <input> element's value, this time via attribute syntax to define a value for the value attribute rather than using a bind: attribute:

// assume this URN and data exists in-page or in memory...
TP.uc('urn:tibet:test_person',
    '{"person": {"lastname": "Smith", "firstname": "Joe"}}'
);

// For clarity we split the URN into a scope and a query below...
<div bind:scope="urn:tibet:test_people">
    <input type="text" value="[[#jpath($.person.firstname)]]"/>
</div>

You can place [[ ]] syntax anywhere in an attribute, using the bind to populate only a portion of the attribute value:

<span id="colorSpan"
    style="background-color: [[urn:tibet:test_people#jpath($.people[0].color)]];
    font-weight: bold">This should be colored based on bound data value.</span>

Yes, we know…don't use inline style… It's a snippet, not a suggestion ;).

Bind To JSON

Binding to JSON strings (as opposed to JavaScript objects) is simple. Point your bind attribute(s) at JSON content and TIBET does the rest.

For example, here's another way to write Hello World! provided your project name is 'hello' ;)

<hello:app id="app">
    <tibet:service href="~/tibet.json" id="urn:tibet:tibet_json"
        on:attach="UIActivate"/>
    <div>[[urn:tibet:tibet_json#jpath($.project.name)]] world!</div>
</hello:app>

In TIBET the easiest way to fetch data from markup is to use the tibet:service tag. This tag lets us leverage TIBET's virtual path syntax to point to a remote resource, provide a URN to name the content, and configure a number of other features such as how we process various HTTP verbs. In this case we added on:attach="UIActivate" to tell the tag we want it to fetch its data essentially 'onload' (for TIBET on:attach) so the data is available immediately.

One of the more interesting/useful features of the tibet:service tag is that you can add a watched="true" attribute to it and any changes to the original source which are passed to the client will trigger change notification.

If we add watched="true" to our example and then make a change to the tibet.json file on the file system we'll see our div update automatically.

Set the watched attribute:

<hello:app id="app">
<tibet:service href="~/tibet.json" id="urn:tibet:tibet_json"
    on:attach="UIActivate" watched="true"/>
<div>[[urn:tibet:tibet_json#jpath($.project.name)]] world!</div>
</hello:app>

And change tibet.json to have a new project name:

    "name": "out of this"

Bind To XML

TIBET lets you bind directly to XML, just point to it with a URI or URN:

<!-- assume this data...  -->
<tibet:data id="urn:tibet:test_person">
    <person xmlns="">
        <lastname>Smith</lastname>
        <firstname>Joe</firstname>
    </person>
</tibet:data>

<input type="text" bind:io="urn:tibet:test_person#xpath1(/person/firstname)"/>

In the markup above we have an inline XML block we name and bind to. Note that in this example our query is an XPath so we can slice into the data to access the specific value(s) we are after.

Bind To JS Object

You can use TIBET's URN syntax to name, and then bind to, any object in your application. For example, TIBET automatically registers the current TP.core.User instance under the URN urn:tibet:user:

<div>[[urn:tibet:user]]</div>

TIBET automatically resolves the object reference from any URN and resolves it based on the aspect the bind references. In the majority of cases that's equivalent to invoking get('value') on the resource being referenced.

Query Into JSON

For JSON data TIBET uses JSONPath to query the data.

In our Bind To JSON example we sliced into our tibet.json file above and extract the project name instead using the XPointer jpath($.project.name). We can do that regardless of the bind syntax we use (bind:scope, bind:io, etc):

<div bind:scope="urn:tibet:tibet_json">
    <input type="text" value="[[#jpath($.project.name)]]"/>
</div>

See https://www.npmjs.com/package/jsonpath-plus for details on JSONPath syntax as currently implemented in TIBET.

Query Into XML

TIBET allows you to query XML using the XPath 1.0 syntax supported by modern browsers. This query capability isn't just limited to binds, you can use it with any XML content.

As with JSON or JS Object bindings you place your query in an industry-standard XPointer. Below we use the XPath /person/firstname which TIBET will auto-collapse into a single value if the target is a single-valued component (like our input field here):

<!-- assume this data...  -->
<tibet:data id="urn:tibet:test_person">
    <person xmlns="">
        <lastname>Smith</lastname>
        <firstname>Joe</firstname>
    </person>
</tibet:data>

<input type="text" bind:io="urn:tibet:test_person#xpath1(/person/firstname)"/>

Query Into JS Object

As mentioned in the Bind To JS Object example, TIBET automatically registers the current TP.core.User instance under the URN urn:tibet:user. We can bind to that object's vcard property using the following syntax:

<div>[[urn:tibet:user#tibet(vcard)]]</div>

TIBET's path syntax is loosely based on the access path syntax used in Python so you can use [index] variations to slice into arrays etc. For more on the query syntax see TIBET Paths.

Scoping A Bind

TIBET's bind:scope attribute lets you define binding scopes, nested scopes within which relative paths are resolved.

For example, the tibet.json file could be scoped in multiple levels as follows:

<tibet:service href="~/tibet.json" id="urn:tibet:tibet_json"
    on:attach="UIActivate"/>
<div bind:scope="urn:tibet:tibet_json">
    <div bind:scope="$.project">
        <span bind:in="name"/>
    </div>
</div>

Note that in the sample above (and in all such cases) the value for bind:scope should fit the proper query language for the target data.

You should use XPath when scoping XML, JSONPath when scoping JSON, and a valid TIBET path segment when scoping a JavaScript object.

Repeating Binds

Let's say we have a remote service that vends us XML, something like the people element below. Maybe it's an ASP.net service. Maybe it's SOAP. Maybe it's EDI data. Regardless, we've got some XML and we want to render as a table.

<tibet:data id="urn:tibet:test_people">
    <people xmlns="">
        <person>
            <lastname>Smith</lastname>
            <firstname>Joe</firstname>
        </person>
        <person>
            <lastname>Jones</lastname>
            <firstname>John</firstname>
        </person>
        <person>
            <lastname>Homemaker</lastname>
            <firstname>Billy</firstname>
        </person>
        <person>
            <lastname>Professional</lastname>
            <firstname>Pamela</firstname>
        </person>
    </people>
</tibet:data>

We could write something that transforms that data into JSON, sends it over the wire, and then templates it back into a table. But TIBET lets us render it as a table with much less effort using a normal table coupled with bind:repeat and a couple of content-level binds:

<table>
    <thead>
        <tr>
            <th>First Name</th><th>Last Name</th>
        </tr>
    </thead>
    <tbody id="people" bind:repeat="urn:tibet:test_people#xpath1(/people/person)">
        <tr>
            <td>[[firstname]]</td><td>[[lastname]]</td>
        </tr>
    </tbody>
</table>

In the markup above we use bind:repeat with the URN and xpath(/people/person) to effectively define a binding scope at the tbody level consisting of each person element in the XPath result set.

With the bind:repeat in place TIBET will then repeatedly use the content of the tbody with each item in the result set. The td elements leverage this 'scope' to bind to firstname and lastname completing our table without having to convert data or write any code.

For examples that show repeats with more complexity, including paging support, see the various files in ~lib/test/src/bind.

Defining In-Page Data

There are several cases where you might choose to "inline" data for a page directly within that page itself. TIBET's data binding tests make extensive use of this approach for example.

To inline data in a page you can use the tibet:data element. The name attribute on that element will be used to automatically take any content in the element and assign it as the content of the referenced URN.

In the sample below we define urn:tibet:test_person and let it hold a small JSON data structure. NOTE we use a CDATA block to ensure any syntax in the content itself is ignored and the block is treated opaquely by the browser:

<tibet:data id="urn:tibet:test_person">
    <![CDATA[
    {"person": {"lastname": "Smith", "firstname": "Joe"}}
    ]]>
</tibet:data>

As mentioned, this is an excellent approach to use for test pages which may not be able to rely upon a server during test execution.

reference

Binding Variables

'$REQUEST', null,
'TP', TP,
'APP', APP,
'$SOURCE', source,
'$TAG', targetTPElem,
'$TARGET', tpDoc,
'$_', wrappedVal,
'$INPUT', repeatSource,
'$INDEX', index,
'$FIRST', index === 0,
'$MIDDLE', index > 0 && index < last,
'$LAST', index !== last,
'$EVEN', index % 2 === 0,
'$ODD', index % 2 !== 0,
'$#', index);

code

Binds make use of a broad array of functionality from across TIBET, from signaling to URIs to set() methods to tag attach/detach processing in the DOM.

Dozens of examples of binds can be found in ~lib/test/src/bind.

Each test will typically ensure a data source, a markup template, and test criteria. The markup template will show you the particular bind syntax being used while the data source will help you analyze how the bind paths are accessing the data.

The "driver" file TP.bind_Tests.js manages loading the individual .xhtml files containing the bind syntax examples you're probably looking for. Once each page loads the driver checks that their values match expectations.

Binding, at least UI binding, in TIBET is managed by code in the bind: namespace.found in ~lib/src/bind and the TIBET kernel file TIBETBinding.js.

Since all binds in TIBET reference URIs of various forms the TIBETURITypes.js file in the TIBET kernel handles most URI-related functionality.

Finally, since all binds are essentially just sugared change notifications the TIBETSignaling.js file and set() methods which trigger change notifications are central to how binding operates.