Binding properties for error-free consistency

At this point, we've already got a great toolkit for working with the properties of our SC.Object subclasses, but we don't yet have the proper means to connect separate object properties together. This is where we will turn to SproutCore's bindings. Bindings are another one of those framework features that native software developers have enjoyed for some time, but have not been available in a web framework. I say enjoy for good reason, bindings makes up for clean and magical code. As it turns out, SproutCore's bindings were actually modeled on and so I've heard, improved upon the binding system in Cocoa.

Let's look at why bindings are so important. Here's a screenshot of a SproutCore application of mine called Hubbub.

Binding properties for error-free consistency

What we're looking at is an item that has been lent out and I've highlighted five areas in red on the screen. These five areas share something in common; they all depend on the isBorrowed property of the selected item. Now if I were to toggle the isBorrowed property from true to false, then the lent count (A) would have to decrement, the button title (B) would have to change, the row style (C) would have to update, the borrower details (D) would have to disappear and the title (E) would need to be modified. Without the means to bind all of these changes to my property I would be left searching out all the relevant parts of the UI and manually updating them. This is why so many web apps have UI consistency problems. As the elements move around, the code to search out and update every property gets more and more complex until it's a nightmare to attempt even the slightest change.

The proper approach, as we've already seen briefly in Chapter 1, Introducing SproutCore, is to bind our properties together. Binding from one property to another will observe each property for changes and efficiently synchronize the changes between the two.

Note

What makes bindings so efficient is that they will only synchronize the value once per run loop. If the isBorrowed property from above were toggled 100 times in immediate succession, only the final value would propagate through the app.

Similar to observers, bindings can be created when an object is initialized using a special property naming convention, which is the name of the property followed by the word "Binding". For example, in order to bind property x of the foo object to property y of bar object, we would first create one of the objects as shown:

MyApp.bar = SC.Object.create({
  y: 'baz'
});

Next we would create the binding on our other object as shown:

MyApp.foo = SC.Object.create({
  x: 'baz',
  xBinding: SC.Binding.from('MyApp.bar.y')
});

In this example, xBinding creates a binding between the x and y properties of the two objects.

Tip

Note that we also pre-defined the x property in MyApp.foo, while not strictly necessary here, it's a good habit to follow. Otherwise the initial value of x would be undefined, and the binding would have to synchronize once initially to baz from undefined.

There is a short form for writing bindings using only the path string for the value (for example, "MyApp.bar.y"), but the reason it is good to use the long form version is that the short form only translates to SC.Binding.from, which relays changes in both directions.

However, more often we have one root object that owns the property and all other bound objects just want to get but not set the property. If you think back to the Hubbub example described earlier, none of those five highlighted areas needed to set the isBorrowed property, they just needed to be updated when the object changes.

Therefore, in this common situation it is a good idea to use the slightly more efficient unidirectional version of SC.Binding, which has no short form as shown:

xBinding: SC.Binding.oneWay('MyApp.bar.y')

Now whenever y changes, x will be updated to match, but changing x directly would not update y.

So what do we do if we want x to be dependent on y, but not exactly the same value as y? One option is that we could make a computed property. For example:

MyApp.bar = SC.Object.create({
  y: 'baz'
});

MyApp.foo = SC.Object.create({
  
  x:'baz',
  xBinding: 'MyApp.bar.y',
  
  displayX: function () {
    var x = this.get('x'),

    return x.toUpperCase();
  }.property('x').cacheable()

});

This works, but it's possible to do it with less code using a "binding transform". To create a transform, we chain the transform function to the binding. For example:

MyApp.foo = SC.Object.create({

  displayX: 'Baz',
  displayXBinding: SC.Binding.oneWay('MyApp.bar.y').transform(
    function (value) {
      return value.toUpperCase();
    }
  )
});

There are several pre-built binding transforms that you can use within SproutCore:

  • bool(): This forces the value to be boolean true or false (null, undefined, and 0 are all false).
  • single(): This forces the value to be a single value. Arrays with a single item will return that item, arrays with multiple items will return SC.MULTIPLE_PLACEHOLDER.
  • multiple(): This forces the value to be an array-like object. Single items will be wrapped in an array.
  • notEmpty(): This forces the value to not be empty. Values of null, undefined, '', and [] will return SC.EMPTY_PLACEHOLDER.
  • notNull(): This forces the value to not be null. Values of null and undefined will return SC.EMPTY_PLACEHOLDER. Values of '' and [] are untouched.
  • not(): This forces the value to be the inverse boolean. This is the same as bool(), but inverted.
  • isNull(): This forces the value to be true if the original value is null or undefined, otherwise the value will be false.

Just as we did with the custom transform above, we can chain transforms together to finely tune the resulting value. For example:

// Takes a selection array and returns true if there is only one 
item and it is truthy
isFirstItemValidBinding: SC.Binding.oneWay('MyApp.listController.selection').single().bool()

Finally, just like with observers, you can manually connect and disconnect bindings on the fly, but this is a rarely used pattern and beyond the scope of this book. For now, everything we've learned so far is all we need to build production worthy applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.126.56