Data Binding

The Geocortex HTML5 Framework introduces a powerful data-binding engine, allowing users to focus on writing purpose-specific business code while avoiding tedious, hard-to-maintain DOM code.

Data binding typically occurs when a view is added to a region, and a view model is associated with that view. Binding is performed by the attach method of the View base class. Binding expressions are used in data-binding HTML attributes in view markup. Binding expressions are resolved during the attach phase, and a tree of binding expressions is created and stored in memory.

Binding expressions take this form:

{DIRECTIVE: SOURCE}

Multiple binding expressions can be present in a single data-binding attribute, and white space between binding expressions is ignored.

The directive portion of a binding expression dictates the type of binding to create:

The source of a binding represents the piece of information (or abstraction) that is reflected by the user interface element. The binding source is typically a view model field, or the name of an event handler in the view code, depending on the type of binding.

The Observable Type

The Geocortex HTML5 Framework uses the concept of observable properties to facilitate data binding and to provide clean declarative relationships between view markup, view logic, and view models. Regular JavaScript variables, while powerful given the language’s functional and object-oriented abilities, provide no universal mechanism to detect changes to variables.

In a data bound system, changes to view model state must automatically update the user interface. However, without a proper mechanism to notify consumers of changes, this becomes tricky. To provide this behavior, two special types of framework object are used: Observable and ObservableCollection. All view model fields intended to be public should be instances of Observable or ObservableCollection.

Observable is a wrapper around instances of Object. It represents a single field value. When the value of this field is to be updated, a set method is called on the observable object. Likewise, when the value of this field is to be fetched, a get method is called.

Observable objects possess an internal event object and event handlers can be bound to the observable object to notify interested parties of changes using a bind method. The bind method subscribes an event handler and executes it in a given scope when the set method is called.

When an observable object is modified using set(), any subscribers are automatically notified of the new value. This concept lies at the heart of data binding and enables creation of sophisticated user interfaces without the need to write DOM manipulation code.

The pattern for creating observable objects is as follows:

dojo.declare("myNamespace.myViewModel", null, {
    title: null,
    constructor: function () {
        this.title = new Observable();
    },
    initialize: function (config) {
        this.title.set(config.title);
    } 
});

Observable objects should always be initially set to null, and should always be initialized in the constructor of an object.

Developers should take care to always use set() on observable objects, as regular assignment do not work and break bindings. While binding to regular, non-observable JavaScript variables will work in some cases, the binding will be a one-time binding and updates to the source variables will not be reflected in the UI.

See the SDK reference for more information about observable objects.

Attribute Binding

Attribute bindings are simple, powerful bindings. In an attribute binding, the directive represents a DOM attribute, while the source references an observable view model property. When the observable view property updates, the view is automatically updated with the value of the property. For example:

<div data-binding="{className: activeClass}"></div>

In this example, the DOM attribute className is bound to a property called activeClass in the view model attached to the view.

Another example:

<img data-binding="{src: imageUrl}"></div>

If the source of an attribute binding is an observable object, any changes to the observable will update the bound attribute.

Text Binding

Binding textual content to user interface elements is a very common scenario. To do so, the @text binding directive is provided. The @text directive binds the inner textual content of an element to a view model property. For example:

<span data-binding="{@text: username}"></span>

Whenever the observable username property is set, the span element is updated to reflect the content assigned to username.

The @text directive escapes HTML, including HTML entity codes. This helps prevent a class of attacks called Cross-Site Scripting (XSS for short) where a malicious user attempts to inject JavaScript into data fields that are then displayed verbatim (thus running code) on someone else’s machine.

Visibility Bindings

Visibility bindings are useful for controlling element visibilities. For example, extending the previous example:

<div data-binding="{@visible: showImage}">
    <img data-binding="{src: imageUrl}"></div>
</div>

Visibility bindings typically bind to Boolean values, but can also be bound to strings and collections. If the source of a visibility binding is a string, it resolves to the visible state if the string has more than 0 characters. If the source of a visibility binding is a collection, it resolves to the visible state if the collection has more than 0 items.

The visibility binding type has an inverse, called @hidden. The @hidden binding uses the exact some logic as @visible, but inverted. For example, using an observable collection called items:

<div data-binding="{@hidden: items}">Sorry, no items exist.</div>
<div data-binding="{@visible: items}">
     <ul data-binding="{@source: items}">
            <!-- (items) -->
     </ul>
</div>

Events

Event bindings provide a way to declaratively wire up events. Event bindings take this form:

{@event-EVENT_NAME: HANDLER_NAME}

where EVENT_NAME is the name of a DOM event and HANDLER_NAME is a method in the View class. For example:

<a href="javascript:void(0)" data-binding="{@event-onclick: handleClickLink}"></a>

In this example, onclick follows the @event- portion of the directive. When the onclick event of the anchor element is raised, the method handleClickLink in the View class is executed.

View event handlers take this form:

handleClickLink: function (event, element, context) {
     alert("The link was clicked.");
     return false;
}

The parameters are as follows:

You may wonder why the context view model of a data-bound event handler is passed into the method, when it is already available in the view through this.ViewModel. The reason for this will become apparent when dealing with collection bindings.

ObservableCollection

ObservableCollection extends the observable concept to JavaScript array collections. An observable collection wraps an internal array value and publishes events that notify consumers of changes to the collection.

When an observable collection is updated, it raises its binding event and any handlers attached are executed and passed an instance of a CollectionChangedArgs object. This object represents a change that is about to be made to the actual collection. This object carries an operation type, such as append, remove, or clear, as well as a range representing which items in the array are to be modified.

Observable collections contain a number of methods for interacting with the underlying array, all of which publish the appropriate CollectionChangedArgs items.

The use of observable collections facilitates one of the most powerful types of data binding: collection binding.

Collections

Collection binding is a powerful and versatile way to represent collections of items in a user interface. Collection binding, also called source binding, allows a view to bind to an observable collection belonging to a view model. Source-bound elements automatically update when the bound collection is modified.

To create a collection binding, the @source directive is used, with the source pointing to an observable collection in the view model.

Collection binding uses a template-based approach to generate the structure of the view. The source-bound element should contain a single child element that defines the template for each item in the collection. The child element does so by using the @template-for directive, with a source pointing to the same view model member as the parent source-bound element. For example:

<ul data-binding="{@source: customers}">
    <!-- This is the template item. -->
    <li data-binding="{@template-for: customers}">
        <div class="customer-listing">
            <a href="javascript:void(0)" data-binding="{innerHTML: displayName}"></a>
        </div>
    </li>
</ul>

When this view is attached to its view model and added to the user interface, it binds to the customers collection and populate the ul element with elements from the li template.

When a source-bound element is populated based on a collection, each item of that collection serves as the view model for a view whose markup is that of the template.

Event handlers specified in a template binding will still point to the parent view, but when the handler is executed, the context parameter will represent the view model for that collection item.

Consider the following:

<div data-binding="{@source: items}">
    <div data-binding="{@template-for: items}">
        <a data-binding="{@event-onclick: handleClickItem}{innerHTML: description}"></a>
    </div>
</div>

This piece of view markup is bound to a list of items in the view model, and each item is displayed as an anchor element inside a div. Each item is associated with an onclick event handler called handleClickItem.

While this item will be bound to its own view model (a member of items), its associated event handler, handleClickItem, lives in the original view, its parent view, and look like this:

handleClickItem: function (event, element, context) {
    // ... "context" will be the View Model whose bound View was clicked.
}

Template items in a source-bound element are created as views themselves; therefore any valid binding expression can be used within them. Multiple levels of collection binding are possible. This is particularly useful when displaying hierarchical data.

If you want to use source binding with TABLE elements to display rows or columns based on a collection, the binding expression must be attached to the tbody element, not the table element:

<table>
   <tbody data-binding="{@source: features}">
       <tr data-binding="{@source: attributes}{@template-for: features}">
       <!-- etc. -->
       </tr>
   </tbody>
</table>

Source binding works on single observable objects as well as observable collection objects. Binding expressions in the template of an observable source binding are resolved against the fields of the bound observable.

© 2019 Latitude Geographics Group Ltd. All Rights Reserved.

Documentation Version 4.11