Microsoft Press Programming Windows Store Apps with HTML, CSS and JavaScript (2014)
Chapter 6 Data Binding, Templates, and Collections
Having just now in Chapter 5, “Controls and Control Styling,” thoroughly teased you with plenty of UI elements to consider, I’m going to step away from UI and controls for a short time to talk about glue. You see, I have a young son and glue is a big part of his life! OK, I’m joking about the real glue, but it’s an appropriate term when we talk about data binding because data binding is, in many ways, a kind of glue that keeps the UI we build with various controls appropriately attached to the data that the UI represents.
A while back I saw a question on one of the MSDN forums that asked, simply, “When do I use data binding at all?” It’s a good question, because writers of both books and documentation often assume that their readers know the why’s and when’s already, so they just launch into discussions about models, views, and controllers, tossing out acronyms like MVC, MVVM, MVMMVMV, MMVMMVMVMVMVMCVVM, and so on until you think they’re revving up an engine for some purpose or another or perhaps getting extra practice at writing confusing Roman numerals! Indeed, the whole subject can often be shrouded in some kind of impenetrable mystique. As I don’t at all count myself among the initiates into such mysteries, I’ll try to express the concepts in prosaic terms. I’ll covers the basics of what data binding is and why you care about it, and then I’ll demonstrate how data binding is expressed through WinJS. (Though you can implement it however you like, WinJS serves as a helpful utility here, as is true for most of the library. Other developers employ angular.js for this purpose, which is certainly another option but one that I don’t address in this book.)
At first we’ll be looking at data binding with simple data sources, such as single objects with interesting properties. Where data binding really starts to prove its worth, however, is with collections of such objects, which is exactly why we’re talking about it here before going on to Chapter 7, “Collection Controls.” To that end, we’ll explore the different kinds of collections that you might encounter when writing apps, including those from WinRT, such as the vector, and the WinJS.Binding.List
that is an essential building block for collection controls.
Speaking of building blocks, this chapter also includes a subject that flows naturally from the others: templates. A template is a way to define how to render some data structure such that you can give it any instance of that structure and have it produce UI. Again, this is clearly another building block for collection controls, but it’s something you can use separately from them.
So let’s play with the glue and get our hands sticky!
Data Binding
Put simply, data binding means to create relationships between properties of data objects and properties of UI elements (including styles). This way those UI elements automatically reflect what’s happening in the data to which they are “bound,” which is often exactly what you want to accomplish in your user experience. Data binding can also work in the other direction: data objects can also be bound to UI elements such that changes a user makes in the UI are reflected back to the data objects.
When small bits of UI and data are involved, you can easily set up all these relationships however you want, by directly initializing UI element values from their associated data object and updating those values when the data is modified. You’ve probably already written plenty of code like this. And to go the other direction, you can watch for change events on those UI elements and update the data objects in response. You’ve probably written that kind of code as well!
As the UI gets more involved, however, such as when you're using collections or you're setting up relationships to multiple properties of multiple elements, having more formalized structures to handle the data-to-UI wiring starts to make a lot of sense, especially when you start dealing with collections. It’s especially helpful to have a declarative means to set up those relationships, rather than having to do it all through procedural code. In this section, then, we’ll start off with the basics of how data binding works, and then we’ll look at the features of the WinJS.Binding
namespace and what it has to offer (including declarative structures). This gives us the basis for working with collections, which leads us naturally (in the next chapter) to how we bind collections to collection controls.
Let me note that we won’t be talking about other formalized patterns and coding conventions for data binding, such as “model-view-controller” (MVC), “model-view-viewmodel” (MVVM), and others, primarily because this book’s focus is on the features of the Windows platform more than higher-level software engineering practices. (Indeed, I’ve often found discussions of data binding to start right in on patterns and frameworks, leaving the basics of data binding in the dust!) If you like working with these patterns, you can of course design your app’s architecture accordingly around the mechanisms that WinJS or other frameworks provide.
Data Binding Basics
The general idea of data binding is again to connect or “bind” properties of two different objects together, typically properties of a data object—or context—and properties of a UI object. We can generically refer these as source and target. A key here is that data binding generally happens between properties of the source and target objects, not the objects as a whole.
The binding can also involve converting values from one type into another, such as converting a set of separate source properties into a single string as suitable for the target. It’s also possible to have multiple targets bound to the same source or one target bound to multiple sources. This flexibility is exactly why the subject of data binding can become somewhat nebulous, and why numerous conventions have evolved around it! Still, for most scenarios, we can keep the story simple.
A common data-binding scenario is shown in Figure 6-1, where we have specific properties of two UI elements, a span
and an img
, bound to properties of a data object. There are three bindings here: (1) the span.innerText
property is bound to the source.name
property; (2) the img.src
property is bound to the source.photoURL
property; and (3) the span.style.color
property is bound to the output of a converter function that changes the source.userType
property into a color.
FIGURE 6-1 A common data-binding scenario between a source data object and two target UI elements, involving two direct bindings and one binding with a conversion function.
How these bindings actually behave at run time then depends on the particular direction of each binding, which can be one of the following (omitting any converters that might be involved):
One-time: the value of the source property is copied to the target property at some point, after which there is no further relationship. This is what you automatically do when passing variables to control constructors, for instance, or simply initializing target property values with source properties. What’s useful here is to have a declarative means to make such assignments directly in element attributes, as we’ll see.
One-way: the target object listens for change events on bound source properties so that it can update itself with new values. This is typically used to update a UI element in response to underlying changes in the data. Changes within the target element (like a UI control), however, are not reflected back to the data itself (but can be sent elsewhere as with form submission, which could in turn update the data through another channel).
Two-way: essentially one-way binding in both directions, as the source object also listens to change events for target object properties. Changes made within a UI element like a text box are thus saved back in the bound source property, just as changes to the source property update the UI element. Obviously, there must be some means to not get stuck in an infinite loop; typically, both objects avoid firing another change event if the new value is the same as the existing one.
Data Binding in WinJS
Now that we’ve seen what data binding is all about, we can see how it can be implemented within a Windows Store app. Again, you can create whatever scheme you want for data binding or use a third-party JavaScript library for the job: it’s just about connecting properties of source objects with properties of target objects.
If you’re anything like a number of my paternal ancestors, who seemed to wholly despise relying on anyone to do anything they could do themselves (like drilling wells, mining coal, and manufacturing engine parts), you may very well be content with engineering your own data-binding solution. But if you have a more tempered nature like I do (thanks to my mother’s side), I’m delighted when someone is thoughtful enough to create a solution for me. Thus my gratitude goes out to the WinJS team who, knowing of the common need for data binding, created theWinJS.Binding
API. This supports one-time and one-way binding, both declaratively and procedurally, along with converter functions. At present, WinJS does not provide for two-way binding, but such structures aren’t difficult to set up.
Within the WinJS structures, properties of multiple target elements can be bound to a single data source property. WinJS.Binding
, in fact, provides for what are called templates, basically collections of target elements whose properties are all bound to the same data source, as we’ll see later in this chapter. Though we don’t recommend it, it’s possible to bind a single target element to multiple sources, but this gets tricky to manage properly. A better approach in such cases is to wrap those separate sources into a single object and bind to its properties instead. This way the wrapper object can manage the process of combining multiple source properties into a single one to use in the binding relationship.
To understand core data binding with WinJS, let’s look at how we’d write our own binding code and then see the solution that WinJS offers. We’ll use the scenario shown in Figure 6-1, where we have a source object bound to two separate UI elements, with one converter that changes a source property into a color.
One-Time Binding
One-time binding, as mentioned before, is essentially what you do whenever you just assign values to properties of an element. Consider the following HTML, which is found in Test 1 of the BindingTests example in this chapter’s companion content:
<!-- Markup: the UI elements we'll bind to a data object -->
<section id="loginDisplay1">
<p>You are logged in as <span id="loginName1"></span></p>
<img id="photo1"></img>
</section>
Given the following data source object:
var login1 = { name: "liam", id: "12345678",
photoURL: "http://www.kraigbrockschmidt.com/images/Liam07.png", userType: "kid"};
we can bind as follows, where we include a converter function (userTypeToColor
) in the process:
//"Binding" is done one property at a time, with converter functions just called directly
var name = document.getElementById("loginName1");
name.innerText = login1.name;
name.style.color = userTypeToColor(login1.userType);
document.getElementById("photo1").src = login1.photoURL;
function userTypeToColor(type) {
return type == "kid" ? "Orange" : "Black";
}
This gives the following result, in which I shamelessly publish a picture of my kid as a baby:
With WinJS we can accomplish the same thing by using a declarative syntax and a processing function. In markup, we use the attribute data-win-bind
to map target properties of the containing element to properties of the source object. The processing function,WinJS.Binding.processAll
, then creates the necessary code to implement those binding relationships. This follows the same idea as using the declarative data-win-control
and data-win-options
attributes instruct WinJS.UI.process[All]
how to instantiate controls, as we saw in Chapter 5.
The value of data-win-bind
is a string of property pairs. Each pair’s syntax is <target property> : <source property>[<initializer>]
where the <initializer>
is an optional function that determines how the binding relationship is set up.
Each property identifier can use dot notation as needed (see the sidebar coming up for additional syntax), and uses JavaScript property names. Property pairs are separated by a semicolon, as shown in the HTML for Test 2 in the example:
<section id="loginDisplay2">
<p>You are logged in as
<span id="loginName2"
data-win-bind="innerText: name; style.color: userType Tests.typeColorInitializer">
</span>
</p>
<img id="photo2" data-win-bind="src: photoURL"/>
</section>
Tip The syntax of data-win-bind
is different than data-win-options
! Whereas the options string is an object within braces { }
with options separated by a comma, a binding string has no braces and items are separated by semicolons. If you find exceptions being thrown fromBinding.processAll
, check that you have the right syntax.
Here we’re saying that the innerText
property of the loginName2 target is bound to the source’s name
property and that the target’s style.color
property is bound to the source’s userType
property according to whatever relationship the Tests.typeColorInitializer
establishes. As we’ll see in a moment, that initializer is built directly around the userTypeToColor
converter.
As you can see, the data-win-bind
notation above does not specify the source object. That’s the purpose of Binding.processAll
. So, assuming we have a data source as before:
var login2 = { name: "liamb", id: "12345678",
photoURL: "http://www.kraigbrockschmidt.com/images/Liam07.png", userType: "kid"};
we call processAll
with the target element and the source object as follows:
//processAll scans the element'stree for data-win-bind, using given object as data context
WinJS.Binding.processAll(document.getElementById("loginDisplay2"), login2);
The data context The second argument to Binding.processAll
is the data context (or source) with which to perform the binding. If you omit this, it defaults to the global JavaScript context. If, for example, you have a namespace called Data
that contains a property bindSource
, you can specify Data.bindSource
in data-win-bind
and omit a data context in processAll
, or you can specify just bindSource
in data-win-bind
and pass Data
as the second argument to processAll
.
Other arguments Binding.processAll
performs a deep traversal of all elements contained within the given root, so you need only call it once on that root for all data binding in that part of the DOM. If you want to process only that element’s children and not the root element itself, passtrue
as the third parameter (called skipRoot
). A fourth parameter called bindingCache
also exists as an optimization for holding the results of parsing data-win-bind expressions. Both skipRoot
and bindingCache
are useful when working with binding templates that we’ll talk about toward the end of this chapter.
The result of all this, in Test 2, is identical to what we did manually in Test 1. In fact, processAll
basically takes the declarative data-win-bind
syntax along with the source and target and dynamically executes the same code that we wrote out explicitly in Test 1. The one added bit is that the initializer function must be globally accessible and marked for processing, which is done as follows:
//Use a namespace to export function from the current module so WinJS.Binding can find it
WinJS.Namespace.define("Tests", {
typeColorInitializer: WinJS.Binding.converter(userTypeToColor)
});
As with control constructors defined with WinJS.Class.define
, WinJS.Binding.converter
automatically marks the functions it returns as safe for processing. It does a few more things as well, but we’ll return to that subject in a bit.
We could also put the data source object and applicable initializers within the same namespace.51 For example, in Test 3 we place our login
data object and the typeColorInitializer
function in a LoginData
namespace, and the markup and code now look like this:
<section id="loginDisplay3">
<p>You are logged in as
<span id="loginName3"
data-win-bind="innerText: name; style.color: userType LoginData.typeColorInitializer">
</span>
</p>
<img id="photo3" data-win-bind="src: photoURL"/>
</section>
WinJS.Binding.processAll(document.getElementById("loginDisplay3"), LoginData.login);
WinJS.Namespace.define("LoginData", {
login : {
name: "liamb", id: "12345678",
photoURL: "http://www.kraigbrockschmidt.com/images/Liam07.png",
userType: "kid"
},
typeColorInitializer: WinJS.Binding.converter(userTypeToColor)
});
In summary, for one-time binding WinJS.Binding
simply gives you a declarative syntax to do exactly what you’d do in code, with a lot less code. Because it’s all just some custom markup and a processing function, there’s no magic here, though such useful utilities are magical in their own way! In fact, the code here is really just one-way binding without having the source fire any change events. We’ll see how to do that with WinJS.Binding.as
in a moment after a couple more notes.
First, Binding.processAll
is actually an async function that returns a promise. Any completed handler given to its then
/done
method will be called when the processing is finished, if you have additional code that’s depending on that state.
Second, you can call Binding.processAll
more than once on the same target element, specifying a different source object (data context) each time. This won’t replace any existing bindings, mind you—it just adds new ones, meaning that you could end up binding the same target property to more than one source, which could become a big mess. So again, a better approach is to combine those sources into a single object and bind to that, using dot notation to identify nested properties.52
Third, the binding that we’ve created with WinJS here is actually one-way binding by default, not one-time, because one-way binding is the most common scenario. To get true one-time binding, use the built-in initializer function, oneTime
, in your data-win-bind
string. More on this under “Binding Initializers” later on.
Sidebar: Additional Property Syntax and Binding to WinJS Controls
Property identifiers in data-win-bind
can be single identifiers like name
or compound identifiers such as style.color
. This applies to both source and target properties. This is important when binding to properties of a WinJS control, rather than its root element, as those properties must be referenced through winControl
. For example, if we had a source object called myData
and wanted to bind some of its properties to those of a WinJS.UI.Rating
control, we’d use the following syntax:
<div id="rating1" data-win-control="WinJS.UI.Rating"
data-win-options="{onchange: changeRating}"
data-win-bind="{winControl.averageRating: myData.average,
winControl.userRating: myData.rating}">
</div>
Notice that data-win-control
, data-win-options
, and data-win-bind
can be used together and UI.process[All]
and Binding.processAll
will pick up the appropriate attributes. It’s important, however, thatUI.process[All]
has completed its work before calling Binding.processAll
as the latter depends on the control being instantiated. Typically, then, you’ll call Binding.processAll
within the completed handler for UI.process[All]
:
args.setPromise(WinJS.UI.processAll().then(function () {
WinJS.Binding.processAll(document.getElementById("boundElement"));
}));
Within data-win-bind
, array lookup for source properties using [ ]
is not supported, though you can do that through an initializer. Array lookup is supported on the target side, as is dot notation. Also, if the target object has a property that you want to refer to using a hyphenated identifier (where dot notation won’t work), you can use the following syntax:
<span data-win-bind="this[hyphenated-property']: source"></span>
That is, the target property string in data-win-bind
basically carries through into the binding code that Binding.processAll
generates; just as you can use this['hyphenated-property']
in code (this
being the data context), you can use it in data-win-bind
.
A similar syntax is necessary for binding attributes of the target element, such as the aria-*
attributes for accessibility. As you probably know, attributes aren’t directly accessible through JavaScript properties on an element: they’re set through the element.setAttribute
method instead. So you’d need to insert a converter into the data binding process to perform that particular step. Fortunately, WinJS.Binding
includes initializers that do exactly this, initializer, setAttribute
(for one-way binding) and setAttributeOneTime
(for one-time binding). These are used as follows:
<label data-win-bind="this['aria-label']: title WinJS.Binding.setAttribute"></label>
<label data-win-bind="this['aria-label']: title
WinJS.Binding.setAttributeOneTime"></label>
One-Way Binding
The goal for one-way binding is, again, to update a target property, typically in a UI control, when the bound source property changes. One-way binding means to effectively repeat the one-time binding process whenever the source property changes.
In the previous section’s code, if we changed login.name
after calling Binding.processAll
, nothing will change in the output UI. So how can we automatically update the output?
Generally speaking, this requires that the data source maintains a list of bindings, where each binding describes a source property, a target property, and any associated converter function. The data source also needs to provide methods to manage that list, like addBinding, removeBinding, and so forth. Thirdly, whenever one of its bindable properties changes it goes through its list of bindings and updates any affected target property accordingly. All together, this makes what we call an observable source.
These requirements are quite generic; you can imagine that their implementation would pretty much join the ranks of classic boilerplate code. So, of course, WinJS.Binding
provides just such an implementation with two functions at its core!
• Binding.as
wraps any arbitrary object with an observable structure. (It’s a no-op for nonobjects, and Binding.unwrap
removes that structure if there’s a need.)
• Binding.define
creates a constructor for observable objects around a set of properties (described by a kind of empty object that just has property names). Such a constructor allows you to instantiate source objects dynamically, as when processing data retrieved from an online service.
Let’s see some code. Going back to the last BindingTests example above (Test 3), any time before or after Binding.processAll
we can take the LoginData.login
object and make it observable with just one line of code:
var loginObservable = WinJS.Binding.as(LoginData.login)
With everything else the same as before, we can now change a bound property within the loginObservable
object:
loginObservable.name = "liambro";
This will update the target property (the mechanics of which we’ll talk about under “Under the Covers: Binding mixins” a little later):
Here’s how we’d then create and use a reusable class for an observable object (Test 4 in the BindingTests example). Notice the object we pass to Binding.define
contains property names, but no values (they’ll be ignored in any case):
WinJS.Namespace.define("LoginData", {
//...
//LoginClass is a constructor for observable objects with the specified properties
LoginClass: WinJS.Binding.define({name: "", id: "", photoURL: "", userType: "" }),
});
We can now create instances of LoginClass
, initializing desired properties with values. In this example, we’re using a different picture and leaving userType
uninitialized:
var login4 = new LoginData.LoginClass({ name: "liamb",
photoURL: "http://www.kraigbrockschmidt.com/images/Liam08.png" });
Binding to this login
object, the username initially comes out black.
//Do the binding (initial color of name would be black)
WinJS.Binding.processAll(document.getElementById("loginDisplay"), login4);
Updating the userType
property will then cause an update the color of the target property, which happens through the converter automatically. In the example, this line of code is executed after a two-second delay so that you can see it change when you run the app:
login4.userType = "kid";
Sidebar: Binding to WinRT Objects
Although it is possible to declaratively bind directly to WinRT source objects, those objects support only one-time binding. The underlying reason for this is that each WinRT object is represented in JavaScript by a thin proxy that’s linked to that object instance, but callingWinJS.Binding.as
on this proxy does not make the WinRT observable within the rest of the WinJS binding mechanisms. For this reason, it’s necessary to enforce one-time binding when using such sources directly. This can be done by specifying the WinJS oneTime
initializer within eachdata-win-bind
relationship, or specifying oneTime
as the default initializer for processAll
(the fifth argument). For example:
WinJS.Binding.processAll(element, winRTSourceObject, false, null, WinJS.Binding.oneTime);
As in the previous sidebar, if you’re binding to element attributes, you’ll need to use the setAttributeOneTime
initializer instead.
To work around this limitation, you can create a custom proxy in JavaScript that watches changes in the WinRT source and copies those values to properties in the proxy. Because this proxy has its own set of properties, you can then make it observable with WinJS.Binding.as
.
Implementing Two-Way Binding
As mentioned earlier, WinJS doesn’t presently include any facilities for two-way binding, but it’s straightforward to implement on your own:
1. Add listeners to the appropriate UI element events that relate to bound data source properties.
2. Within those handlers, update the data source properties.
The data source should be smart enough to know when the new value of the property is already the same as the target property, in which case it shouldn’t try to update the target lest you get caught in a loop. The observable object code that WinJS provides does this type of check for you.
To see an example of this, refer scenario 1 of the Declarative binding sample in the SDK, which listens for the change
event on text boxes and updates values in its source accordingly. The input controls are declared as follows (html/1_BasicBinding.html, omitting much of the surrounding HTML structure):
<input type="text" id="basicBindingInputText"/>
<input type="text" id="basicBindingInputRed"/>
<input type="text" id="basicBindingInputGreen"/>
<input type="text" id="basicBindingInputBlue"/>
and the output elements like so:
<p>The text you entered was<span data-win-bind="innerHTML: text"></span>.
The value for red is<span data-win-bind="innerHTML: color.red"></span>,
the value for green is <span data-win-bind="innerHTML: color['green']"></span>,
and blueis <span data-win-bind="innerHTML: color.blue"></span>.
</p>
<p data-win-bind="style.background: color BasicBinding.toCssColor">
And here's your color as a background.</p>
The source object is defined in js/1_BasicBinding.js as a member of the surrounding page control:
this.bindingSource = {
text: "Initial text",
color: {
red: 128,
green: 128,
blue: 128
}
};
and is made observable through this line of code:
var b = WinJS.Binding.as(this.bindingSource);
You can see how its members are referenced in the data-win-bind
attributes above, so that when we call processAll
(on the div
that contains all the output element):
WinJS.Binding.processAll(element.querySelector("#basicBindingOutput"), this.bindingSource);
we’ve set up one-way binding between the data source and the output. To complete two-way binding, we set up event handlers for the change
event of each input
field that take the value from the control and copy it back to the observable source:
this.bindTextBox("#basicBindingInputText", b.text,
function (value) { b.text = toStaticHTML(value); });
this.bindTextBox("#basicBindingInputRed", b.color.red,
function (value) { b.color.red = toStaticHTML(value); });
this.bindTextBox("#basicBindingInputGreen", b.color.green,
function (value) { b.color.green = toStaticHTML(value); });
this.bindTextBox("#basicBindingInputBlue", b.color.blue,
function (value) { b.color.blue = toStaticHTML(value); });
bindTextBox: function (selector, initialValue, setterCallback) {
var textBox = this.element.querySelector(selector);
textBox.addEventListener("change", function (evt) {
setterCallback(evt.target.value);
}, false);
textBox.value = initialValue;
}
In the HTML, you might have noticed an initializer called BasicBinding.toCssColor
. This is defined in js/1_BasicBinding.js as follows:
var toCssColor = WinJS.Binding.initializer(
function toCssColor(source, sourceProperty, dest, destProperty) {
function setBackColor() {
dest.style.backgroundColor =
rgb(source.color.red, source.color.green,source.color.blue);
}
return WinJS.Binding.bind(source, {
color: {red: setBackColor,green: setBackColor,blue: setBackColor,}
});
}
);
// A little helper function to convert from separate rgb values to a css color
function rgb(r, g, b) { return"rgb(" + [r, g, b].join(",") + ")"; }
WinJS.Namespace.define("BasicBinding", {
toCssColor: toCssColor
});
Clearly, there’s more going on here than just creating a simple converter as we did before, including use of the methods WinJS.Binding.initializer
and WinJS.Binding.bind
. Now is a good time, then, to peek under the covers to see how initializers work and what functions like bind
are doing.
Under the Covers: Binding mixins
Earlier in the “One-Way Binding” section I spelled out three requirements for an observable object: it maintains a list of bindings, provides methods to manage that list, and iterates through that list to update any target properties when source properties change. And we saw that Binding.as
and Binding.define
let you easily add such structures to a source object or class definition (you can find the source for both in the WinJS base.js file).
Both as
and define
create an observable object from an existing one by adding the necessary methods that the rest of WinJS.Binding
requires. (This is done through mixins; to review details on WinJS.Class.mix
and mixins, refer to Appendix B, “WinJS Extras.”)
The as
method, for its part, assumes that the data source is just a plain object (and not a Date
, Array
, or nonobject) and creates a proxy object around it using an internal WinJS class called ObservableProxy. You wouldn’t instantiate this class directly, of course, but it’s instructive to look at its definition, where data
is the object you give to as
:
var ObservableProxy = WinJS.Class.mix(function (data) {
this._initObservable(data);
Object.defineProperties(this, expandProperties(data));
}, dynamicObservableMixin);
The call to _initObservable
on the data source turns out to be very important. All it does is ensure that the class has a property called _backingData
that’s set to the original source. Without this you’d see a bunch of exceptions at run time.
Binding.define
does pretty much the same thing except that its purpose is to create a constructor for an observable object class, rather than just wrapping an existing instance. It’s how you define your own variant of the internal ObservableProxy class we see above.
You can also create an observable source manually, as shown in Scenario 3 of the Programmatic binding sample. Here it defines a RectangleSprite
class with Class.define
and then makes it observable (and adds a few more observable properties) as follows (js/3_CreatingBindableTypes.js):
WinJS.Class.mix(RectangleSprite,
WinJS.Binding.mixin,
WinJS.Binding.expandProperties({ position: 0, r: 0, g: 0, b: 0 })
);
Note that the RectangleSprite
constructor in this case must call this._initObservable()
or else none of the binding will work (that is, the app will crash and burn). This is the only case I know of where you need to explicitly call an underscore-named method in WinJS!
However you do it, though, the bottom line is that we’re creating a new class that combines the members defined by each argument to mix
. In the cases above we have three such arguments: the original class, WinJS.Binding.mixin
, and whatever object is produced byWinJS.Binding.expand-Properties
.
expandProperties
creates an object whose properties match those in the given object (the same names, but not the values), where each new property is wrapped in the proper structure for binding.53 Clearly, this type of operation is useful only when doing a mix, and it’s exactly whyBinding.define
can digest an oddball, no-values object like the one we gave to it in Test 4 of the BindingTest example.
By “proper structure for binding,” I mean that each property has a get
and set
method (along with two properties names enumerable
and configurable
, both set to true
). These methods rely on the class also having methods called getProperty
and setProperty
:
get: function () { returnthis.getProperty(propertyName); },
set: function (value) { this.setProperty(propertyName, value); },
It’s unlikely that any arbitrary class will contain such methods already, which is where the Binding.mixin
object comes in. It contains a standard implementation of the binding functions, like [get | set]Property
, that the rest of WinJS.Binding
expects.
The mixin
object itself an extension of the more basic Binding.observableMixin
, which just contains three methods to manage a list of bindings:
• bind
Saves a binding (property name and a listener function to invoke on change) into an internal list (an array). This is the binding equivalent of addEventListener
.
• unbind
Removes a binding created by bind
(removing it from the list), or removes all bindings if a specific one isn’t given. This is the binding equivalent of removeEventListener
.
• notify
Goes through the bindings list for a property and asynchronously invokes its listeners with the new property value (at “normal” priority in the scheduler). notify
will cancel any update that’s already in progress for the same property. This is the binding equivalent ofdispatchEvent
.
The mixin
object then adds five methods to manage properties through bind
, unbind
, and notify
:
• setProperty
Updates a property value and notifies listeners if the value changed.
• updateProperty
Like setProperty
, but returns a promise that completes when all listeners have been notified (the result in the promise is the new property value).
• getProperty
Retrieves a property value as an observable object itself, which makes it possible to bind within nested object structures (obj1.obj2.prop3
, etc.).
• addProperty
Adds a new property to the object that is automatically enabled for binding (it adds the same properties and methods that that expandProperties
does).
• removeProperty
Removes a property altogether from the object.
The Binding.dynamicObservableMixin
object, I should note, is exactly the same as mixin
except for one small difference. If you take an observable class built with mixin
and then derive a new class from it (see WinJS.Class.derive
), the new class will not automatically be observable. If you build the base class with dynamicObservableMixin
, on the other hand, its observability will apply to derived classes.
Programmatic Binding and WinJS.Binding.bind
Taking a step back from the details now, we can see that the eight mixin
methods, along with the get
and set
implementations from expandProperties
, fulfill the requirements of an observable data source. With such a source in hand you can do some interesting things programmatically. First, you can call the object’s bind
method directly to hook up any number of additional actions manually. A couple of scenarios come to mind where this would apply:
• You set up two-way binding between a local data source and the app’s UI, and want to also sync changes to the source with a back-end service. A second handler attached through bind
would be called whenever the source is modified through any other means.
• You need to create an intermediate source object that combines and consolidates property changes from other sources, simplifying binding to a final target UI element. Calling bind
directly in such cases is necessary because Binding.processAll
specifically works with UI elements declared in HTML rather than arbitrary JavaScript objects.
The notify
method, for its part, is something you can call directly to trigger notifications. This is useful with additional bindings that don’t necessarily depend on the values themselves, just the fact that they changed. The major use case here is to implement computed properties—ones that change in response to another property value changing.
The WinJS binding engine also has some intelligent handling of multiple changes to the same source property. After the initial binding, further change notifications are asynchronous and multiple pending changes to the same property are coalesced. So, if in our example we made several changes to the name property in quick succession:
login.name = "Kenichiro";
login.name = "Josh";
login.name = "Chris";
only one notification for the last value would be sent and that would be the value that shows up in bound targets.
A couple of additional demonstrations of calling these binding methods directly can also be found in scenarios 1 and 2 of the Programmatic binding sample, which doesn’t use any declarative syntax at all. Scenario 1, for example, creates an observable source with Binding.as
and wires it up with bind
method to a couple of change handlers for two-way binding. (It employs the WinJS.Utilities.query
and WinJS.Utilities.QueryCollection.listen
methods, which are described in Appendix B.) Be mindful when looking at lines like this (js/1_BasicBinding.js):
this.bindSource.bind("x", this.onXChanged.bind(this));
that the first bind
method on the source object comes from WinJS.Binding.mixin
, whereas the bind
method on the onXChanged
function is the standard JavaScript method to manage the this
variable!
Scenario 2 demonstrates a coding construct called a binding descriptor that works in conjunction with the static helper method WinJS.Binding.bind
. Yes, be aware! WinJS.Binding.bind
is yet another bind
method that’s separate from a source object’s bind
that’s defined in the mixins and separate from a function’s bind
method. Fortunately, you must always call WinJS.Binding.bind
with its full name, and I’ll refer to it this way to avoid confusion.
I call WinJS.Binding.bind
a helper function, because it’s basically a way to make a bunch of bind
calls for properties of a complex object. Consider the source object that’s created in scenario 2 of the Programmatic binding sample (js/2_BindingDescriptors.js):
this.objectPosition = WinJS.Binding.as({
position: { x: 10, y: 10},
color: { r: 128, g: 128, b: 128 }
}
If we wanted to set up binding relationships to each of these properties, we’d have to make a bunch of calls to this.objectPosition.bind
like this:
this.objectPosition.bind(this.objectPosition.position.x, <action>)
Such code gets ugly to write in a hurry. A binding descriptor, then, succinctly expresses the property-action mappings in a structure that matches the source object. The generic syntax is: { property: { sub-property: function(value) { ... } } }
. Here’s a concrete example from scenario 2 (js/2_BindingDescriptors.js):
{
position: {
x: onPositionChange,
y: onPositionChange
},
color: {
r: onColorChange,
g: onColorChange,
b: onColorChange
}
}
where onPositionChange
and onColorChange
both refresh the output in response to data changes.54 Scenario 1 of the Declarative binding sample has another example (js/1_BasicBinding.js):
{
color: {
red: setBackColor,
green: setBackColor,
blue: setBackColor,
}
}
When calling WinJS.Binding.bind
, then, just pass the source object as the first argument and the binding descriptor as the second. As shown in the Declarative binding sample:
return WinJS.Binding.bind(source, {
color: {
red: setBackColor,
green: setBackColor,
blue: setBackColor,
}
});
The return value of WinJS.Binding.bind
is an object with a cancel method that will clear out all these binding relationships (basically iterating through the structure calling unbind
). In the Declarative binding sample, it returns this object from the binding initializer where it appears, which is actually very important. So let’s now turn to initializers.
Binding Initializers
When Binding.processAll
encounters a data-win-bind
attribute on an element, its main job is to take each <target property> : <source property>[<initializer>]
string and turn it into a real binding relationship. Assuming that the data source is observable, this basically means calling the source’s bind
method with the source property name and some handler that will update the target property accordingly.
The purpose of the initializer function in this process is to define exactly what happens in that handler—that is, what happens to the target property in response to a source property update. In essence, an initializer provides the body of the handler given to the source’s bind
. In a simple binding relationship, that code might simply copy the source value to the target or it might involve a converter function. It could also consolidate multiple source properties—you can really do anything you want here. The key thing to remember is that the initializer itself will be called once and only once for each binding relationship, but the code it provides, such as a converter function, will be called every time the source property changes.
Now if you don’t specify a custom initializer within data-win-bind
, WinJS will always use a default, namely WinJS.Binding.defaultBind
.55 It simply sets up a binding relationship that copies the source property’s value straight over to the target property, as you’d expect. In short, the following two binding declarations have identical 'margin-top:12.0pt;margin-right:0cm;margin-bottom:12.0pt;margin-left: 0cm'>data-win-bind="innerText: name"
data-win-bind="innerText: name defaultBind"
WinJS.Binding
provides a number of other built-in initializers that can come in handy, most of which we’ve already encountered:
• oneTime
Performs a one-time copy of the source property to the target property without setting up any other binding relationship. This is necessary when the source is a WinRT object, as noted earlier in “Sidebar: Binding to WinRT Objects.”
• setAttribute
and setAttributeOneTime
Similar to defaultBind
and oneTime
but injects a call to the target element’s setAttribute
method instead of just copying the source value to a target property. See “Sidebar: Additional Property Syntax and Binding to WinJS Controls” earlier for more details.
• addClassOneTime
Like setAttributeOneTime
except that it interprets the source property as a class name and thus calls the target element’s classList.add
method to apply that class. This is useful when working with templates, which are described later in this chapter, because you can assign static classes to elements with the class
attribute and then apply additional data-bound classes with this initializer.
Beyond these, we enter into the realm of custom initializers. The most common and simplest case is when you need to inject a converter into the binding relationship. All this takes on your part is to pass your conversion function to WinJS.Binding.converter
, which returns the appropriate initializer (that’s also marked for declarative processing). We did this in Tests 2, 3 and 4 of the BindingTests example. For Tests 3 and 4, for example, the initializer LoginData.typeColorInitializer
is created as follows:
WinJS.Namespace.define("LoginData", {
//...
typeColorInitializer: WinJS.Binding.converter(userTypeToColor)
});
which we use in the HTML like so:
<span id="loginName3"
data-win-bind="innerText: name; style.color: userType LoginData.typeColorInitializer">
</span>
Doing anything more requires that you implement the initializer function directly. This is necessary, for instance, if you need to apply a converter to a WinRT data source, where normally you’re required to use the oneTime
initializer already. A custom initializer can then do both steps at once.
Scenario 1 of the Declarative binding sample gives us an example of an initializer function (js/1_BasicBinding.js):
var toCssColor = WinJS.Binding.initializer(
function toCssColor(source, sourceProperty, dest, destProperty) {
function setBackColor() {
dest.style.backgroundColor =
rgb(source.color.red, source.color.green,source.color.blue);
}
return WinJS.Binding.bind(source, {
color: {red: setBackColor,green: setBackColor,blue: setBackColor,}
});
}
);
// A little helper function to convert from separate rgb values to a css color
function rgb(r, g, b) { return"rgb(" + [r, g, b].join(",") + ")"; }
WinJS.Namespace.define("BasicBinding", {
toCssColor: toCssColor
});
WinJS.Binding.initializer
is just an alias for WinJS.Utilities.markSupportedFor-Processing
, as discussed in Chapter 4, “Web Content and Services.” It makes sure you can reference this initializer from processAll
and doesn’t add anything else where binding is concerned.
The arguments passed to your initializer clearly tell you which properties of source
and target (dest
) are involved in this particular relationship. The source
and dest
arguments are the same as the dataContext
and rootElement
arguments given to processAll
, respectively. ThesourceProperty
and destProperty
arguments are both arrays that contain the “paths” to their respective properties, where each part of the identifier is an element in the array. That is, if you have an identifier like style.color
in data-win-bind, the path array will be [style, color]
.
Tip If you find it tricky to set a breakpoint inside an initializer, just insert the debugger
statement at the top and you’ll always stop at that point.
With all this information in hand, you can then set up whatever binding relationships you want by calling the source’s bind
method with whatever properties and handlers you require. To duplicate the behavior of oneTime
, as with WinRT sources, you wouldn’t call bind
but instead just assign the value of the source property to the destination property.
Although sourceProperty
is what’s present in the data-win-bind
attribute, you’re free to wire up to any other property you want to include in the relationship. The code above, for example, will be called with sourceProperty
equal to [color]
, but it doesn’t actually bind to that directly. It instead uses a binding descriptor with WinJS.Binding.bind
to hook up the setBackColor
handler to three separate color subproperties. Although setBackColor
is used for all three, you could just as easily have separate handlers for each one if, for example, each required a unique conversion.
Ultimately, what’s important is that the handler given to the source.bind
method performs an appropriate update on the target object. In the code above, you can see that setBackColor
sets dest.style.backgroundColor
.
Hmmm. Do you see a problem there? Try changing the last data-win-bind
attribute in html/1_BasicBinding.html to set the text color instead:
data-win-bind="style.color : color BasicBinding.toCssColor"
Oops! It still changes the background color! This is because the initializer isn’t honoring destProperty
, and that would be a difficult bug to track down when it didn’t work as expected. Indeed, because the initializer pays no attention to destProperty
you can use a nonexistent identifier and it will still change the background color:
data-win-bind="some.ridiculous.identifier : color BasicBinding.toCssColor"
Technically speaking, then, we could rewrite the code as follows:
dest.[destProperty[0]].[destProperty[1]] =
rgb(source.color.red, source.color.green, source.color.blue);
Even this code makes the assumption that the target path has only two components—to be really proper about it, you need to iterate through the array and traverse each step of the path. Fortunately, WinJS.Utilities.getMember
is a helper for just that purpose, though to do an assignment you need to pop the last property from the array so that you can use [ ]
to reference it:
var lastProp = destProperty.pop();
var target = destProperty.length ?
WinJS.Utilities.getMember(destProperty.join("."), dest) : dest;
destProperty.push(lastProp);
function setBackColor() {
target[lastProp] = rgb(source.color.red, source.color.green, source.color.blue);
}
This code can be found in the modified Declarative binding sample in this chapter’s companion content. Note that I’m building that reference outside of the setBackColor
handler because there’s no need to rebuild it every time a source property changes. Also, calling push
to restoredestProperty
is important in scenario 3 when this initializer is used with a template that’s rendered multiple times on the same page. In that case the same destProperty
array is used with each call to the initializer, meaning that we don’t want to make any permanent changes.
Remember also that sourceProperty
can also contain multiple parts, so you may need to evaluate that path as well. The sample gets away with ignoring sourceProperty
because it knows it’s only using the toCssColor
initializer with source.color
to begin with. Still, if you’re going to write an initializer, best to make it as robust as you can! Again, you can use getMember
to assemble the reference you need, and because you’re just retrieving the value, you can do it in one line:
var value = WinJS.Utilities.getMember(sourceProperty.join("."), source);
Binding Templates
Now that we understand how controls work from Chapter 5 and how data binding works to populate those controls from a data source, the next question to ask is how we might define reusable pieces of HTML that also integrate declarative data binding.
There are two ways to do this. First, you can certainly use data-win-bind
syntax within an HtmlControl
or a custom control (remembering to bind control properties through winControl
rather than the root element). Once the control is instantiated with WinJS.UI.process[All]
, you can then call WinJS.Binding.processAll
to bind each element to an appropriate source.
The second way is through the WinJS.Binding.Template
control. It provides a way to define an HTML snippet, either inline or in a separate HTML file, and then render that snippet, however many times you want, with whatever data source you need for each rendering. The template makes the call to Binding.processAll
automatically and provides some other options where binding is concerned.
To define a template control, first create a div
with data-win-control= "WinJS.Binding.Template"
and an optional data-win-options
string. The contents of the template—which are copied into the DOM when you render the template—can then be defined either inline or in a separate file. In that markup you’re free to use whatever controls you want along with data-win-bind
attributes (as well as data-win-res
attributes for localization).
Tip Blend for Visual Studio 2013 has some helpful features to easily create templates and data bindings from a data source for WinJS collection controls. We’ll see this in Chapter 7.
Template designs For an extensive collection of premade templates (designed primarily for the ListView control, but a great reference nonetheless), see Item templates for grid layouts and Item templates for list layouts.
Here’s an example from scenario 2 of the modified Declarative binding sample in this chapter’s companion content (html/2_TemplateControl.html, where the toCssColor
initializer is corrected as we did earlier for scenario 1):
<div id="templateControlTemplate" data-win-control="WinJS.Binding.Template">
<!-- Bind both the background and the aria-label HTML attribute for the div -->
<div class="templateControlRenderedItem"
data-win-bind="style.background: color TemplateControl.toCssColor;
this['aria-label']: text WinJS.Binding.setAttribute" role="article">
<ol>
<li data-win-bind="textContent: text"></li>
<li><span class="templateControlTemplateLabel">r:
</span><span data-win-bind="textContent: color.r"></span></li>
<li><span class="templateControlTemplateLabel">g:
</span><span data-win-bind="textContent: color.g"></span></li>
<li><span class="templateControlTemplateLabel">b:
</span><span data-win-bind="textContent: color.b"></span></li>
</ol>
</div>
</div>
Scenario 3 of the example, which is an addition I’ve made to the original sample, has the same template contents stored in html/3_TemplateContents.html (<!DOCTYPE html>
, <html>
, and <body>
tags are optional and have no effect). We then refer to those contents with the control’s href
option in data-win-options
(html/3_TemplateControlHref.html):
<div id="templateControlTemplate" data-win-control="WinJS.Binding.Template"
data-win-options="{ href : '/html/3_TemplateContents.html' }">
</div>
Note Using the href
option disables certain optimizations with templates that dramatically improve performance. You should only use href
, then, when absolutely necessary.
What’s unique about this control is that it automatically hides itself (setting style.display="none"
) inside its constructor so that its contents never appear in your layout. You can confirm this when you run scenarios 2 or 3 of the example—if you expand everything in Visual Studio’s DOM Explorer, you won’t be able to find the template control anywhere. The control instance, however, is still in memory: a quick getElementById
or querySelector
will get you to the hidden root element, and that element’s winControl
property is where you’ll find the WinJS template object.
That object has a number of methods and properties that we’ll return to in a bit. The most important of these is the asynchronous render
method. Given whatever data source you want to bind to, render
returns a promise that’s fulfilled with a copy of the template’s contents in a new element, where Binding.processAll
has already been called with the data source. You can then attach that element to the DOM wherever you’d like.
Scenario 2 of the example, for instance, renders the template three times, binding each to a different source object that contains text and a color. First, in html/2_TemplateControl.html, the target div for the rendered templates is initially empty:
<div id="templateControlRenderTarget"></div>
In js/2_TemplateControl.js, we define the observable data sources as follows (showing WinJS.Binding.define
):
var DataSource = WinJS.Binding.define({
text: "",color: { r: 0, g: 0, b: 0 }
});
// In the page control's init method
this.sourceObjects = [
new DataSource({ text: "First object", color: { r: 192, g: 64, b: 64 } }),
new DataSource({ text: "Second object", color: { r: 64, g: 192, b: 64 } }),
new DataSource({ text: "Third object", color: { r: 51, g: 153, b: 255 } })
];
In the page’s ready
method, which is called after the page is rendered (meaning UI.processAll
has instantiated the template), we do a bunch of work to wire up two-way binding and then render the template for each data source:
var templateControl = element.querySelector("#templateControlTemplate").winControl;
var renderHere = element.querySelector("#templateControlRenderTarget");
this.sourceObjects.forEach(function (o) {
templateControl.render(o).then(function (e) {
renderHere.appendChild(e);
});
});
Again, the template control is hidden but still present in the DOM, so we can get to it with the querySelector
call. We then get the target div
and iterate through the data sources, rendering the template with each source in turn, and attaching the resulting element tree (in e
) to the target. The result of all this is shown below:
Note that render
will, by default, create an extra container div
for the template contents. That is, the output above will have this structure:
<div id="templateControlRenderTarget">
<div class="win-template win-disposable">
<!-- Template contents, starting with <div class="templateControlRenderedItem"> -->
</div>
<div class="win-template win-disposable">
<!-- ... -->
</div>
<div class="win-template win-disposable">
<!-- ... -->
</div>
</div>
You can eliminate that extra div
by setting the template’s extractChild
option to true
:
data-win-options="{ extractChild : true }"
Also keep in mind that render
calls Binding.processAll
as part of its process, using, of course, the source you gave to render
. Because there’s no point to process the template’s root element—the one with the data-win-control= "WinJS.Binding.Template"
—the call to processAll
has theskipRoot
argument set to true
. Internally WinJS also uses the bindingCache
argument to optimize repeated renderings. That’s really what those arguments are there for.
As you can see, binding templates are quite straightforward to use. They will come in very handy when we talk about collection controls in the next chapter, because you can just point those controls to a template to use with each item in the collection. And with the WinJS.UI.Repeater
control, we’ll even see how you can use nested templates.
Sidebar: The Static WinJS.Binding.Template.render method
Within the WinJS.Binding.Template
API is an odd duck of a method that’s documented as render.value
. This is actually just a static method whose actual name in code is WinJS.Binding.Template.render
and whose implementation is simply the following:
value: function (href, dataContext, container) {
returnnew WinJS.Binding.Template(null, { href: href }).render(dataContext, container);
}
This provides a programmatic shortcut for declaring a template with an href
option, calling WinJS.UI.processAll
to instantiate it, and then obtaining the control object and calling its render
method. In short, WinJS.Binding.Template.render
, which you’ll always call with the full identifier, is a one-line wonder for quickly rendering a file-based template with some data source into a container element. Scenario 4 of the modified Declarative Binding sample in this chapter’s companion content has a quick demonstration. The caveat is that the template will be loaded from disk each time you make this call, so it’s really best for one-shot renderings. If you’ll be using a template in multiple places, use an inline declaration so that the template object is present in the DOM for the lifetime of the page.
Template Options, Properties, and Compilation
Now that we’ve seen the basics of the Template
control, let’s see what other features it supports. First are the options for the constructor that you can include in data-win-options
:56
• href
Specifies the path of an in-package HTML file that contains the template definition, as we’ve seen. Using this implicitly sets disableOptimizedProcessing
to true
.
• bindingInitializer
Specifies a binding initializer to apply as the default to all data-win-bind
relationships. This does not override any explicit initializer given in data-win-bind
.
• debugBreakOnRender
Executes a debugger
statement whenever the template is first rendered and especially gives you a hook into a compiled template (see below). This is essential when a template’s render
call is being made implicitly from within another control like the ListView
.
• disableOptimizedProcessing
Turns off template compiling; see below.
• extractChild
If set to true
, suppresses creation of a containing div
for the template contents, as discussed in the previous section. The default is false
.
All of these except for href
can be accessed at run time through properties with the same names on the control: bindingInitializer
, debugBreakOnRender
, disableOptimizedProcessing
, and extractChild
.57 Note that changing any of these properties at run time will reset the template and cause it to be recompiled the next time it’s used.
Related to the behavior of the extractChild
option is a second argument to the render
method. If you already have a container into which you want the template contents rendered, you can provide it through this argument. For example, with extractChild
set to true
, the following code in scenario 3 of the modified Declarative binding sample (in the companion content) produces the same result as the default behavior (js/3_TemplateControlHRef.js):
this.sourceObjects.forEach(function (o) {
var container = document.createElement("div");
WinJS.Utilities.addClass(container, "win-template win-disposable");
renderHere.appendChild(container);
templateControl.render(o, container);
});
Such code gives you complete control over the kind of container element that’s created for the template, if you have need of it.
As for disableOptimizedProcessing
and debugBreakOnRender
, these relate to a significant change for WinJS 2.0 (in Windows 8.1): template compilation. In WinJS 1.0, template rendering was always done in an interpreted manner, meaning that each time you rendered a template, WinJS would parse your template’s markup and create each element in turn. You could improve this process by creating a rendering function directly, but you then lost the advantages of declarative markup.
To improve performance of declarative templates, especially with the ListView control, WinJS 2.0 compiles each template upon first rendering. This step dynamically creates a rendering function that makes subsequent use of the template much faster. In fact, if you have a Windows 8 (WinJS 1.0) app that uses the ListView
control and you simply migrate the project to Windows 8.1 (WinJS 2.0), you’ll probably find it performing much better than before with no other changes.
Of course, with such extensive restructuring of the template engine there’s always the possibility that it breaks some existing code somewhere—perhaps yours! If you find that’s the case, or if for some reason want your app to just run slower, set disableOptimizedProcessing
to true
to bypass compilation and use the WinJS 1.0 rendering behavior. And again, note that using href
to refer to template content will implicitly disable compilation.
With compiled templates, the WinJS engineers found it very helpful to have a hook into the dynamically-generated code, so they included the debugBreakOnRender
option. If set to true
(try it in scenario 2) and you run an app in the debugger, you’ll hit a debugger
statement in code like this:
// generated template rendering function
returnfunction render(data, container) {
if (++sv23_debugCounter === 1) { debugger; }
if (typeof data === "object"&&typeof data.then === "function") {
// async data + a container falls back to interpreted path
If you step through this code you can see the nature of the compiled template. The HTML contents are added through a single insertAdjacentHtml
with a string, for instance, and the steps that would normally happen within the async Binding.processAll
, including calls to initializers, are done inline, so there’s no extra parsing of data-win-bind
attributes. The same is true for WinJS controls in the template that would normally go through the async UI.processAll
method: they are instead instantiated directly with new
and a pre-parsed options object. Avoiding async calls and eliminating extra parsing clearly make compiled templates run much faster.
On the other hand, if you’re using href
or have disableOptimizedProcessing
turned on (as in scenario 3 of the example), you’ll instead hit a debugger
statement inside this internal WinJS function:
function interpretedRender(template, dataContext, container) {
if (++template._counter === 1
&& (template.debugBreakOnRender || WinJS.Binding.Template._debugBreakOnRender)) {
debugger;
}
Stepping through this code you’ll see just how much more work is going on, such as a call to WinJS.UI.Fragments.renderCopy
to asynchronously load up the template contents. Inside the completed handler for this (a variable called renderComplete
), you’ll also see calls to the asyncUI.processAll
and Binding.processAll
methods.
If you continue looking through both renderers (compiled or interpreted), you’ll see they return an object with two properties called element
and renderComplete
. element
is a promise that’s fulfilled when the element tree has been built up in memory, and renderComplete
is a function that’s called once the promise is fulfilled (where data binding can then happen). This isn’t at all important for using the Template
object but becomes important with collection controls like the ListView when you want to do performance optimization through your own rendering functions. We’ll see all that in Chapter 7.
Collection Data Types
No sooner had I sat down to start this section than my wife called me about the shopping list she’d left lying on our kitchen counter. It was a timely reminder that much of our reality where data is concerned is oriented not around single items or object instances, as we’ve dealt with thus far in this chapter, but around collections of such things. And as I said in the introduction to this chapter, dealing with collections and presenting them in an app’s UI is where data binding becomes exceptional useful. Otherwise you’d become very proficient (though I imagine you are already!) at doing copy/paste with lots of redundant binding code.
I assume that you’re already well familiar with the core collection type in JavaScript—the Array
—whose various capabilities are all supported in Windows Store apps, as are the typed JavaScript collections like Uint16Array
.
By itself, Array
and typed arrays are not observable for data-binding purposes. Fortunately, WinJS provides the WinJS.Binding.List
collection type that is easily built on an array. The List
also supports creating grouped, sorted, and filtered projections of itself.
What also enters into the picture are specific language-neutral collection types that are used with WinRT APIs. In Chapter 4, for example, we encountered vectors in a number of places, such as network information, the credential locker, the content precacher, and background transfers. The other types are iterators, key-value pairs, maps, and property sets. As we’re talking about collections in this section, we’ll take the opportunity to familiarize ourselves with each of these constructs.
What’s most important to understand about the WinRT collections—especially where data binding is concerned—is how they project into the JavaScript environment, because that determines whether you can easily bind to them. Earlier we learned that it’s not possible to do one-way or two-way binding with WinRT objects, and these collections count as such. Fortunately, the JavaScript projection layer conveniently turns some of them into arrays, meaning that we can create a List
and go from there.
Windows.Foundation.Collection Types
All of the WinRT collection types are found in the Windows.Foundation.Collections
namespace. What you’ll notice immediately upon perusing the namespace is that it actually contains only one concrete class, PropertySet
, and otherwise it is chock full of “interfaces”with curious names like IIterable<T>
, IMapView<K, V>
, and IVectorView<T>
.
If you’ve at least fiddled with .NET languages like C# in your development career, you probably already know what the I
and <T>
business is all about because you get to type them out all the time. For the rest of you, it probably makes you appreciate the simplicity of JavaScript! In any case, let me explain a little more.
An interface, first of all, is an abstract definition of a group of related methods and properties. Interfaces in and of themselves have no implementation, so they’re sometimes referred to as abstract or virtualtypes. They simply describe a way to interact with some object that “implements” the interface, regardless of what else the object might do. Many objects, in fact, implement multiple interfaces. So anytime you see an I
in front of some identifier, it’s just a shorthand for a group of members with some well-defined behavior.58
With collections in particular, it’s convenient to talk about the collection’s behavior independently from the particular object type that the collection contains. That is, whether you have an array of strings, integers, floats, or other complex objects, you still use the same methods to manipulate the array and the same properties like length
are always there. The <T>
shorthand is thus a way of saying “of type T” or “of whatever type the collection contains.” It certainly saves the documentation writers from having to copy and paste the same text into dozens of separate pages and do a search-and-replace on the data type! Of course, you’ll never encounter an abstract collection interface directly—in every instance you’ll see a concrete type in place of <T>
. For instance, IVector<String>
denotes a vector of strings: you navigate the collection through the methods of IVector
, and the items in the collection are of type String
.
In a few cases you’ll see <K>
and <K, V>
where K
means key and V
means value, both of which can also be of some arbitrary type. Again, it’s just a shorthand that enables us to talk about the behavior of the collection without getting caught up in the details of whatever object type it contains.
So that’s the syntactical sugar—let’s now look at the different WinRT collections.
Iterators
An iterator is the most basic form of a collection and one that maps directly to a simple array in JavaScript. The two iterator interfaces in WinRT are IIterable<T>
and IIterator<T>
. You’ll never work with these directly in JavaScript, however. For one thing, a JavaScript array (containing objects of any type) can be used with any WinRT API that wants an IIterable
. Second, when a WinRT API produces an iterator as a result, you treat it as an array. (There are, in fact, no WinRT APIs available from JavaScript that directly produce a plain iterator; they produce vectors and maps that derive from IIterable
and thus have those methods.)
In short, iterators are transparent from the JavaScript point of view, so when you see IIterable
in the documentation for an API, just think “array.” For example, the first argument to Background-Downloader.requestUnconstrainedDownloadsAsync
that we saw in Chapter 4 is documented as an IIterable<DownloadOperation>
. In JavaScript this just means an array of DownloadOperation
objects; the JavaScript projection layer will make sure that the array is translated into an IIterable
suitable for WinRT.
Similarly, a number of APIs in the Windows.Storage.FileIO
and PathIO
classes, such as appendLinesAsync
and writeLinesAsync
all take arguments of type IIterable<String>
, which to us just means an array of strings.
Occasionally you’ll run into an API like ImageProperties.savePropertiesAsync
(in Windows.-Storage.FileProperties
) that takes an argument of type IIterable<IKeyValuePair>
, which forces us to pause and ask just what we’re supposed to do with a collection interface of another interface! (IKeyValuePair<K, V>
is also in Windows.Foundation.Collections
.) Fortunately, the JavaScript projection layer translates IIterable<IKeyValuePair>
into the concrete Windows.Foundation.-Collections.PropertySet
class, which can be easily addressed as an array with named members, as we’ll see shortly.
Vectors and Vector Views
By itself, an iterator has methods to go through the collection in only one direction (IIterable.first
returns an IIterator
whose only methods are getMany
and moveNext
). A vector is a more capable variant (IVector
derives from IIterable
) that adds random-access methods (getAt
andindexOf
) and methods to modify the collection (append
, clear
, insertAt
, removeAt
, removeAtEnd
, replaceAll
, and setAt
). A vector can also report its size
.59
Because a vector is a type of iterator, you can also just treat it as a JavaScript array—a vector that you obtain from some WinRT API, that is, will have methods like forEach
and splice
as you’d expect. The one caveat here is that if you inspect a vector in Visual Studio’s debugger (as when you hover over a variable), you’ll see only its IVector
members. But trust me, the others are there!
Vectors basically exist to help marshal changes to the collection between the JavaScript environment and WinRT. This is why IIterable
is used as arguments to WinRT APIs (the input array is effectively read-only), whereas IVector
is used as an output type, either for the return value of a synchronous API or for the results of an asynchronous API. For example, the readLinesAsync
methods of the FileIO
and PathIO
methods in Windows.Storage
provide results as a vector.
Within WinRT you’ll most often encounter vectors with a read-write collection property on some object. For example, the Windows.UI.Popups.MessageDialog
object, which we’ll meet in Chapter 9, “Commanding UI,” has a commands
property of type IVector<IUICommand>
, which translates into JavaScript simply as an array of UICommand
objects. Because the collection can be modified by either the app or the WinRT API, a vector is used to marshal those changes across the boundary.
In quite a number of cases—properties and methods alike—the collection involved is read-only but still needs to support random-access characteristics. For this we have the vector view (IVectorView
also derives from IIterable
), which doesn’t have the vector’s modification methods. To give a few examples, a vector view of ConnectionProfile
objects comes back from NetworkInformation.-getConnectionProfiles
(which we saw in Chapter 4). Similarly, StorageFolder.getFilesAsync
provides a vector view of StorageFile
objects. The user’s preferred languages in theWindows.-System.UserProfile.GlobalizationPreferences
object is a vector view of strings. And you can always get a read-only view for any given read-write vector through the latter’s getView
method.
Now because a vector is just a more capable iterator, guess what? You can just treat a vector like an array. For example, the Folder enumeration sample uses StorageFolder.getFilesAsync
and getItemsAsync
to list the contents of in your various media libraries, using forEach
to iterate the array that those APIs produce. Using that example, here’s what I meant earlier when I mentioned that Visual Studio shows only the IVector
members—the items from getItemsAsync
doesn’t show array methods, but we can clearly call forEach
(circled):
What all this means for data binding, to return to the context of this chapter, is that it’s no problem to create a WinJS.Binding.List
from the WinRT methods and properties that produce vectors and vector views, because those collections are projected as arrays.
Tip If you’re enumerating folder contents to create a gallery experience—that is, displaying thumbnails of files in a control like the WinJS.UI.ListView
—always use Windows.Storage.Storage-File.getThumbnailAsync
or StorageFile.getScaledImageAsThumbnailAsync
to retrieve a small image for your data source, which can be passed to URL.createObjectURL
and the result assigned to an img.src
attribute. This performs far better in both speed and memory efficiency, as the API draws from the thumbnails cache instead of loading the entire file contents (as happens if you call URL.createObjectURL
on the full StorageFile
). See the File and folder thumbnail sample for demonstrations.
Maps and Property Sets
A map (IMap<K, V>
) and its read-only map view companion (IMapView<K, V>
)are additional derivations of the basic iterator. A map is composed of key-value pairs, where the keys and values can both be arbitrary types.60 A closely related construct is the property set (IPropertySet
), which relates to the map. The difference is that maps and map views are used (like vectors) to return collections from WinRT APIs and to handle certain properties of WinRT objects. Property sets, for their part, as used (like basic iterators) as input arguments to WinRT APIs.61
It’s important to note right up front that maps and property sets are not projected as arrays. They do support value lookup through the [ ]
operator, but otherwise do not have array methods. A map, instead, has methods (from IMap
) called lookup
(same as [ ]
), insert
, remove
, clear
, andhasKey
, along with a size
property and a getView
method like the vector. A map view shares hasKey
, lookup
, and size
, and adds a split
method. You can also pass a map or map view to Object.keys
to retrieve an array of keys.
A property set has a more extensive interface, but let me come back to that in a bit.
The generic term for maps and property sets alike is a dictionary. A dictionary does just what the word means in colloquial language: it lets you look up an item (similar to a definition) in a collection (the dictionary) by a key (such as a word). This is very useful when the collection you’re working with doesn’t store items in a linear or indexed array, or doesn’t have a need to do so. For example, when working with the Windows.Storage.Pickers.FileSavePicker
class, you give the user a choice of file types through its fileTypeChoices
property. This property is interesting because its type is IMap<String, IVector>
, meaning that it’s a map between a string key and a value that is itself an array. This makes sense, because you want to use something like “JPEG” for a key while yet mapping that single key to a group of values like “.jpg” and “.jpeg”.
Maps are also used extensively when working with file properties, where you have access to a deeper hierarchy of metadata that’s composed of key-value pairs. This is the same metadata that you can explore if you right-click a file in Windows Explorer and click the details tab, as shown here for a picture I took on a trip to Egypt where the camera recorded exposure details and so forth:
As we’ll see in Chapter 11, “The Story of State, Part 2,” the Windows.Storage.FileProperties
API is how you get to all this metadata. Images provide an ImageProperties
object, whose retrievePropertiesAsync
produces a map containing the metadata. You can play with this in theSimple Imaging sample if you’d like, where once you’ve obtained a map you can access individual properties by name (retrievedProps
is the map):
retrievedProps["System.Photo.ExposureTime"]
On the flip side of this metadata scene is where we encounter property sets. Again, these are a form of dictionary like maps but one that an app needs to create for input to methods like ImageProperties.savePropertiesAsync
. This is why Windows.Foundation.Collections
has a concretePropertySet
class and not just an interface, because then we can new
up an instance like so:
var properties = new Windows.Foundation.Collections.PropertySet();
and create key-value pairs with the [ ]
operator:
properties["System.GPS.LatitudeNumerator"] = latNum;
or with the insert
method:
properties.insert("System.GPS.LatitudeNumerator", latNum);
Be mindful that while the documentation for PropertySet
lists quite a few methods and properties, only some of them are projected into JavaScript:
• Properties: size
• Lookup methods: [ ]
, lookup
, hasKey
, first
(returns an iterator for the key-value pairs)
• Manipulation methods: clear
, insert
, remove
• Other: getView
(retrieves a map view) and the mapChanged
event
In summary, maps and property sets are distinct collection constructs that must be worked with through their own methods and properties. Vectors, on the other hand, are projected into JavaScript as an array and are thus suitable for data binding. If you want to bind to a map or property set, on the other hand, you’ll need to maintain an array copy.
WinJS Binding Lists
The WinJS.Binding.List
object is the core of collection-based data binding in WinJS and is what you use with both templates and collection controls, as discussed in this chapter and the next. That is, the name of the ListView
control is quite apt: it’s a view of a list but not the list itself. That list is a WinJS.Binding.List
, which I’ll just refer to as List
or “list” for convenience.
A List
takes a JavaScript array and makes it observable, because such a process means more than WinJS.Binding.as
does with a single object. The array in question can contain any kind of objects, from simple values to complex objects and other arrays. All it takes is a new
:
list = new WinJS.Binding.List(dataArray);
Note that you can build a List
with a sparse array—that is, an array with noncontiguous indices (see Using Arrays).
Once created, the List
provides a number of array-like methods and properties, namely length
, concat
, every
, filter
, forEach
, indexOf
, join
(using a comma), lastIndexOf
, map
, push
, pop
, reduce
, reduceRight
, reverse
, shift
, slice
, sort
, and unshift
. These operate exactly like they do with a typical array except that functions like concat
produce another List
rather than an array. The List
also provides additional lookup and management methods: getAt
, getItem
, getItemFromKey
,indexOfKey
, move
, setAt
, and some
. I’ll leave you to check out the details of all these as needed, as they are all very simple and straightforward.
Hint You can create an empty List
without an array, using new WinJS.Binding.List()
. You then use methods like push
to populate the list. Any data-binding relationships you set up with the empty list will remain in effect as you add items or remove and change existing items.
What’s much more interesting are those List
features that apply more to data binding in particular, which come under four groups:
• Constructor options The binding
and proxy
options affect whether items in the list are individually observable (binding
) and whether the list uses the array directly for data storage (proxy
).
• Projections The createFiltered
, createGrouped
, and createSorted
methods generate new List
objects whose contents are controlled by filtering, grouping, and sorting functions, respectively.
• Events As an observable object, the list can notify listeners when changes happen within its content. This is clearly important for any controls that are built on the List
, such as the ListView
. The supported events are itemchanged
, iteminserted
, itemmoved
, itemmutated
, itemremoved
, andreload
. (The List
has the standard addEventListener
, removeEvent-Listener
, and dispatchEvent
methods, and provides on*
properties for the events.) Three additional methods—notify
, notifyMutated
, and notifyReload
—will trigger appropriate events; notifyReload
is generally called within operations like reverse
and sort
.
• Binding The bind
and unbind
methods support binding to the list’s own properties (rather than those of its contents). The dataSource
property supplies an “adapter” that’s specifically used by WinJS ListView
and FlipView
controls (see Chapter 7) or custom controls that want to use the same data model.
You’ll find that many Windows SDK samples use a List
, especially those that deal with collection controls; there’s no shortage of examples. However, those samples are typically intertwined with the details of the control, so I’ve included the more fundamental BindingLists example in this chapter’s companion content to demonstrate two aspects of the List
class: how the list relates to its underlying array (if one exists), and the various projections. These are the subjects of the next two sections.
Sidebar: Async Data Sources and Populating from a Feed
The implementation of List
is wholly synchronous, meaning that significant operations on large data sets can block the UI thread. To avoid this, consider executing list-modifying code at a lower than normal priority using the WinJS.Utilities.Scheduler
discussed in Chapter 3, “App Anatomy and Performance Fundamentals.” If you’re creating a custom control around a potentially large collection, consider using data sources that implement the IListDataSource
interface as used by WinJS controls like the ListView. This interface accommodates asynchronous behavior as well as virtualization. See “Collection Control Data Sources” in Chapter 7.
If your collection isn’t so large that the synchronous nature of List
will be a problem, you can still populate that list asynchronously such that items will automatically appear within data-bound controls. An excellent example of this (both virtualized and nonvirtualized cases) can be found in scenario 6 of the HTML FlipView control sample
that we’ll meet in Chapter 7 and discuss in that chapter’s “Custom Data Sources and WinJS.UI.VirtualizedDataSource” section.
List-Array Relationships
By default, a List
built on an array of simple values does not have any inherent relationship to the array. Scenario 1 of the BindingLists example in the companion content, for instance, creates a simple array of random numbers (this._array
) and builds a list on it (this._list
; js/scenario1.js):
this._list = new WinJS.Binding.List(this._array, this._options);
where this._options
is optional and initially empty—more on this in a bit. Two buttons in this scenario then randomize the contents of the array and List
separately. In both cases we iterate through the collection, using array[i]
or List.setAt(i)
to set the new numbers:
function randomizeArray(arr, num, lower, upper) {
for (var i = 0; i < num; i++) {
arr[i] = randInt(lower, upper);
}
}
function randomizeList(list, num, lower, upper) {
for (var i = 0; i < list.length; i++) {
list.setAt(i, randInt(lower, upper));
}
}
If you tap these buttons in scenario 1 (leaving the Proxy option unchecked for now), you’ll see that modifying the array does not update the List
, nor does modifying the List
update the array. Now let’s play with the checkboxes that recreate the list with its different constructor options:
In scenario 1 you’ll see that the Binding checkbox doesn’t do anything, which is expected. Now check the Proxy option and you’ll see that changes to the list now show up in the array because the list in this case is just a thin veneer over the array. Bear in mind, though, that if you then change the array, the list doesn’t get updated. This means it won’t fire any events and any other control that is built on the list will get badly out of sync with the real data. For this reason, it’s best to avoid changing the array directly when using the proxy
option.
It’s very helpful to peek under the covers again and understand exactly what theList
object is doing with the array, why the proxy works like it does, and what specific behaviors arise when object arrays are involved.
By default, with proxy
set to false
, the List
creates an internal map of the array’s contents (no such map exists if the List
is created without an array). Each entry in the map holds a key for an array item (typically its positional index as a string) and the item’s value, as shown in Figure 6-2.
FIGURE 6-2 The default relationship between a WinJS.Binding.List
and its underlying array.
If the binding
option is set to true
, then the item in the map is the result of WinJS.Binding.as(<array_item>)
instead, as shown in Figure 6-3.
FIGURE 6-3 The relationship between a WinJS.Binding.List
and its underlying array with the binding
option set to true
. If the array is sparse, the keys will not be contiguous, of course.
In both of these cases, all of the list manipulation methods like concat
, splice
, reverse
, sort
, and so forth affect only the map—the array itself is left untouched.
That changes when the proxy
option is set to true
, because here the List
no longer maintains a separate map but just holds a direct reference to the array, as shown in Figure 6-4. In this case, the binding
option is ignored and the manipulation methods act directly on the array—they simply call the array’s methods of the same name. In this relationship it is obvious why changes to the List
are reflected in the array, as scenario 1 of the example demonstrates.
FIGURE 6-4 The relationship between a WinJS.Binding.List
and its underlying array with the proxy
option set to true
. The binding
option is ignored in this case.
With proxy: true
, this relationship holds regardless of whether the array contains simple values or complex objects. Note also in this case that when you change the array, methods like List.getAt
will clearly return the updated value.
In contrast, when the List
is using a map (proxy: false
), something a little more interesting happens with an array of objects by virtue of this little bit of (condensed) code in the list’s constructor:
item = options.binding ? WinJS.Binding.as(inputArray[i]) : inputArray[i];
_keyMap[key] = { key: key, data: item }
If the item at inputArray[i]
is a simple value, then the map will contain a copy of that value. However, if the item is an object, then the map will contain the item object itself (or an observable variant), rather than a copy. “So what?” you might ask. On the surface this doesn’t seem to mean much at all, but in fact it has two important implications (still assuming proxy: false
):
• If you replace a whole item object in the array, the corresponding List
entry will be unchanged because the original item object is still intact.
• If you change properties on an item object in the array, the properties of the corresponding List entry will also change.
This effect is demonstrated in scenario 2 of the BindingLists example. This scenario repeats the UI as scenario 1 but uses an array of objects rather than simple values. I have added an additional output element that’s data-bound to the first item in the list to show the effect of the binding
option. The effect of the modification buttons and the Binding and Proxy checkboxes is the same as before. For example, with Proxy unchecked, the Modify Array Objects button replaces the array’s content with new objects like so (js/scenario2.js):
arr[i] = { number: randInt(lower, upper), color: randColor() };
but because the list’s map maintains references to the original items in the array, the List
doesn’t see any changes (see the left side of Figure 6-5). Similarly, changing the list’s item through setAt
:
list.setAt(i, { number: randInt(lower, upper), color: randColor() } );
replaces the whole object in the map with the new one, leaving the array unaffected (see the right side of Figure 6-5).
FIGURE 6-5 Replacing a whole object in either the array or the list will disconnect the related item in the other.
But now, with Proxy still unchecked, try the new button labeled Modify Array Object Properties, which instead modifies each array item this way:
arr[i].number = randInt(lower, upper);
arr[i].color = randColor();
Because we’re now modifying the existing objects in the array instead of replacing them, and because the list’s map contains references to those same objects, you’ll see that those changes are reflected automatically in the List
(as well as the one element bound to a list item). This is illustrated on the left side of Figure 6-6. On the flip side, the Modify List Object Properties button calls List.getAt
to obtain the item in the map and then changes its properties:
var item = list.getAt(i);
item.number = randInt(lower, upper);
item.color = randColor();
Because that’s still the same item as the one in the array, the array also sees those changes, as shown on the right side of Figure 6-6.
Note Tapping the Modify Array Objects or Modify List Objects buttons will replace all objects in the corresponding collection, disconnecting it from the other. The result is that modifying objects via properties will have no effect on the other collection. To restore the connection, re-create the List by changing one of the checkboxes.
FIGURE 6-6 Changing properties of objects in either the array or the list will change the properties in the other, because both are still referencing the same object.
All of this is important to keep in mind as you’re building more complex UI around a data source of some kind, whether using controls like the ListView
or a custom control built on the List
. That is, if you have code that modifies the array—or UI capabilities in the control to modify theList
—you’ll know how the list and the array will be correspondingly affected. Otherwise you might be left scratching your head wondering just why certain changes to the list or array (item replacement) don’t affect the other whereas other changes (via properties) do!
Indeed, in scenario 2 you might notice that the span
element that’s data-bound to the item from List.getAt(0)
automatically updates when changes come through the List
but not when they come through the array. This is because the binding mechanics are watching the list’s item, not the array item sitting beneath it. In such cases, be sure to make modifications through only the List
.
It’s also good to know how the array and List
relate when you start using projections of the List
, because changes to the projections will propagate to the original List
as well, as we’ll see next.
List Projections
The idea of a projection comes from the world of databases and data sources to mean a particular view of a data source that is still completely connected to that source. Projections are typically used to massage a raw data source into something more suitable for data binding to your UI, but without altering the original source.
For example, a data source for your personal contacts might have those contacts listed in any order and would of course contain the entire collection of those contacts. In an app’s UI, however, you probably want to sort and re-sort those contacts by one or more fields in each record. Each time you change the sort order in the UI, you’d create another sorted projection and bind the UI to it. Again, creating a projection doesn’t alter the data source. At the same time, changing properties of an item in the projection, inserting or removing items, or otherwise modifying the projection does propagate those changes back to the source. Similarly, if you add, remove, or change items in the underlying source—even through a projection—those changes propagate to all projections. In short, there is only ever one data source no matter how many projections are involved.
You might also want to present only a subset of your contacts in the UI based on certain filtering criteria. That’s called a filtered projection. You might also want to group those contacts by the first letter of the last name or by location. Whatever the case, projections make all this easier by applying such operations at the level of the data source so that you can just use the same UI and the same data binding code across the board.
Sorting, filtering, and grouping are the most common operations applied to data projections, and thus WinJS.Binding.List
supports these through its createSorted
, createFiltered
, and createGrouped
methods:
Performance tip If deriving the group key from an item at run time requires an involved process, you’ll improve overall performance by storing a prederived key in the item instead and just returning that from the group key function.
Globalization tip When sorting or otherwise comparing items, be sure to use globalization APIs like localeCompare
to determine sort orders, or else you’ll really confuse your customers in different world markets! For grouping of textual items, also use theWindows.Globalization.Collation.-CharacterGroupings
API. We’ll see an example in Chapter 7 in “Quickstart #4: The ListView Grouping Sample.”
Each projection generated by these methods is itself a special variant of the List
class, meaning that each projection is individually bindable. You can also compose projections together. Calling List.createFiltered().createSorted()
, for example, will first filter the original List
and then apply sorting. List.createFiltered().createGrouped()
will filter the List
and then apply grouping (and sorting). It’s quite common, in fact, to filter a list first and then apply sorting and/or grouping because filtering is typically a less expensive operation than sorting or grouping.
When you create a grouped projection, it won’t necessarily call your grouping functions for every item right away. Instead, the projection will call those functions only when necessary, specifically when someone is asking the List
or a projection for one or more items. This makes it possible to use projections with virtualized data sources where all the data isn’t necessarily in memory but loaded only when a control like the ListView
is preparing a page of items.
Debugging tip You can of course set breakpoints within sorter, predicate, and grouping functions and step through the code a few times. With large and even modest collections, however, breakpoints quickly become tedious as these functions will be called many, many times! Instead, try using console.log
or WinJS.log
to emit the parameters received by these functions as well as their return values, allowing you to review the overall results much more quickly. In fact, it’s a great place to use WinJS.log
with a tag specific to projections so that you can turn this specific logging output on and off independently.
Let’s see some examples now, drawing from scenario 3 of the BindingLists example in the companion content for the fundamentals. Trust me, we’ll see plenty more of projections in the next chapter when we work with collection controls!
Scenario 3 (js/scenario3.js) of the example creates an empty List
without an underlying array and uses push
to populate it with objects containing a random number and color (like scenario 2):
this._list = new WinJS.Binding.List();
for (var i = 0; i < num; i++) {
this._list.push({ number: randInt(this._rangeLower, this._rangeUpper), color: randColor() });
}
The output of this list is as follows:
It then creates two filtered projections, one that contains only those items whose key is greater than 50 and another that contains only those items where the blue of the color is greater than 192. The first filtering is done with an inline predicate, the second with a separate function. Note that in this and the following code I’ve omitted calls to WinJS.log
and intermediate variables used for logging, and I’ll just show the output afterwards without additional comment:
this._filteredByNumber = this._list.createFiltered(function (j) {return j.number > 50;});
this._filteredByBlueness = this._list.createFiltered(filterBluenessOver192);
function filterBluenessOver192(j) {
return j.color.b > 192;
}
Then we have three sorted projections, one of the original list sorted by number (then blueness in case of repeats), one sorted just by blueness, and one sorted from the list filtered by blueness:
this._sorted = this._list.createSorted(sortByNumberThenBlueness);
this._sortedByBlueness = this._list.createSorted(sortByBlueness);
this._sortedByFilteredBlueness = this._filteredByBlueness.createSorted(sortByBlueness);
function sortByNumberThenBlueness (j, k) {
var result = j.number - k.number;
//If the items' numbers are the same, sort by blueness as the second tier
if (result == 0) {
result = sortByBlueness(j, k)
}
return result;
}
function sortByBlueness(j, k) {
return j.color.b - k.color.b;
}
Then we can add a projection that’s grouped by decades:
this._groupedByDecades = this._list.createGrouped(decadeKey, decadeGroupData.bind(this._list));
function decade(n) {return Math.floor(Math.floor(n / 10) * 10);}
function decadeKey(j) {
return decade(j.number);
}
//"this" will be bound to the list that's being grouped
function decadeGroupData(j) {
var dec = decade(j.number);
//Do a quick in-place filtering of the list so we can get the populationof the decade.
var decArray = this.filter(function (v) {return (decade(v.number) == dec);})
return {
decade: dec,
name: dec.toString(),
count: decArray.length
}
}
The last one is grouped by decades with the groups sorted in descending order, using the same key and group data functions as above:
this._groupedByDecadesSortedByCount =
this._list.createGrouped(decadeKey, decadeGroupData.bind(this._list),
//j and k are keys as returned from decadeKey; k-j does reverse sort
function (j, k) {return k - j;});
The Modify List button that’s included with this scenario demonstrates how the projections are always tied to the underlying List
: when the list is repopulated with new values, you’ll see that all the projections get those new values as well. And if you look at the console output, you’ll see the log entries from the different sorting, filtering, and grouping functions. (You can turn logging off in js/default.js by removing the WinJS.Utilities.startLog
calls.)
Tip When making many modifications to the root List, the group key, sorting, and filtering methods of the projections will be called repeatedly, but the groupData function won’t get called unless the groupKey function returns a key that doesn’t already exist. This means that the group data can get out of sync with the real list. For this reason I call the List.notifyReload
method after repopulating the list to make sure that the projections are reset.
The Modify Projection button demonstrates that a change to a projection ripples through the other projections and the original list. In this case I just change the first item of the groupedByDecades-SortedByCount
projection, which just goes to show that it doesn’t matter how many projection layers you have—you’re always talking to the same data source ultimately.
The code that generates the group data output answers a question you might have had earlier: what happens to the objects returned from the createGrouped
method’s groupData function? Did all that just vanish into the netherworld? Not at all! Those objects simply ended up in the groups
property of the GroupedSortedListProjection
object. This property is just another List
(technically a GroupedListProjection
object) that contains that group data. As a List
you can bind UI to it, filter it, sort it, and so on. In the example, I just output its raw data along with its projection.
There’s one last detail to point out with the groupData function and the contents of the groups
list: because you can return whatever objects you want from groupData, and because that function is called only once for each group, you can calculate other information like sums, counts—and really anything else!—and include that in the returned object. The List
and its projections won’t care one way or the other. So if you want to communicate such information to the consumers of the grouped projection, the groupData function is the place to do it. This becomes very useful with collection controls like the ListView
and especially the Semantic Zoom control that lets you switch between two lists with different levels of data. With semantic zoom, the zoomed-out view of a list typically shows group information and allows you to quickly navigate between groups. The objects you return from groupData are where you store any data you want to use in that view.
What We’ve Just Learned
• WinJS provides declarative data-binding capabilities for one-time and one-way binding, employing data-win-bind
attributes and the WinJS.Binding.processAll
method.
• WinJS.Binding
mixins along with WinJS.Binding.as
and WinJS.Binding.define
simplify making arbitrary JavaScript objects observable and able to participate in data binding.
• With a little extra code to watch for changes in UI elements, an app can implement two-way binding.
• By default, data binding performs a simple copy between source and target properties. Through binding initializers, apps can customize the binding behavior, such as adding a conversion function or defining complex binding relationships between multiple properties.
• WinJS.Binding.Template
controls provide a declarative means to easily define bits of HTML that can be repeatedly rendered with different source objects. Templates are heavily used in collection controls.
• In addition to the Array
type of JavaScript, WinRT supports a number of other collection types such as the iterator, vector, map, and property set. Iterators and vectors project into JavaScript as an array and can be used with data binding through WinJS.Binding.List
.
• WinJS.Binding.List
provides an observable collection type that is a building block for collection controls. A List can be built from scratch or from an existing JavaScript array, as well as WinRT types that are projected as arrays.
• List
projections provide filtering, sorting, and grouping capabilities on top of the original List
without altering the list. As projections share the same data source as the original list, changes to items in the list or a projection will propagate to the list and all other projections.
51 More commonly, initializers and converters would be part of a namespace in which applicable UI elements are defined, because they’re more specific to the UI than to a data source.
52 A final comment that only warrants a footnote is that in Windows 8, apps typically added a line of code WinJS.Binding.optimizeBindingReferences = true
to avoid some memory leaks. This is done automatically in WinJS 2.0 (for Windows 8.1), so that line is no longer necessary.
53expandProperties
also includes properties of the object’s prototype.
54 The actual code uses this.onPositionChange.bind(this)
which I’ve simplified to avoid confusion between all the binds
!
55 As shown earlier in “Sidebar: Binding to WinRT Objects,” you can override the default initializer by providing your own as the fifth argument to processAll
(after skipRoot
and bindingCache
). Though this argument doesn’t appear in the MSDN documentation, it is described within the WinJS source file and is safe to use.
56 Two other options, enableRecycling
and processTimeout
, are deprecated and for internal use, respectively.
57 One other property called isDeclarativeControlContainer
is always set to true
, and as with all other WinJS controls, the template has an element
property that contains its root element in the DOM.
58 There are actually all kinds of interfaces floating around the WinRT API. When working in JavaScript, however, you rarely bump into them because the language doesn’t have such a formal construct. For example, a page control technically implements the WinJS.UI.Pages.IPageControlMembers
interface, but you never see a direct reference to that name. Instead, you simply include its methods among the instance members of your page class. In contrast, when creating classes in other languages like C#, you explicitly list an interface as an abstract base class to inherit its methods.
59 The IObservableVector<T>
interface in Windows.Foundations.Collections
exists for other languages and is not ever seen in JavaScript.
60 Each pair is described by the IKeyValuePair<K, V>
interface, but you don’t encounter that construct explicitly in JavaScript. Also, the IObservableMap<K, V>
interface is also meant for other languages and not used in JavaScript.
61 In JavaScript, a property set is the only type of WinRT collection that you can create directly with the new
operator. However, a few APIs that require a Map
or IIterable
argument have managed to slip through the API review process, thereby posing a problem for JavaScript developers. In these few cases, you have to find another method that returns the right type and then munge it to fit your needed input argument. See this forum post for an example of this workaround.