Working with properties and computed properties

While the class-like features of SC.Object are wonderful for creating and managing complex software hierarchies, they are not likely the primary reason you would use SC.Object over a regular JavaScript object. Instead, I think you'll find that the most powerful component of SC.Object is the key-value coding and the observing features it provides. Or if you don't find that to be the case just yet, you will soon see it in action. In fact, if you followed through the tutorial in Chapter 1, Introducing SproutCore, you've already seen how bindings, a technology backed by key-value coding and observing, can magically update your application.

While we will get to bindings soon, we first need to backtrack a bit. Before we bind and observe properties, we need to understand how to always use them properly. For instance, to ensure that changes to our properties will update the observers of those properties, we follow the concept of key-value coding or KVC. If you're familiar with frameworks like Cocoa, you'll already understand this concept, but even if you haven't encountered it yet, it's extremely easy to implement by following one simple rule: always use get() to retrieve a property's value and always use set() to set it.

For example, if our class has a public property, lastName, rather than setting its value directly as shown:,

personObj.lastName = 'Keating'; 

We always use the set method, as shown:

personObj.set('lastName', 'Keating'),

Doing it the latter way ensures that any observer code for the lastName property of personObj will execute when the value has changed. If we had instead set it using the dot notation approach, we would have to do all the work manually propagating that change across our app by ourselves, which is not a desirable task.

So while this simple example explains an important reason to always use set, it doesn't explain why we need to always use get to access the same property. The reason we do use get is this, our properties are not always regular properties, they may also be computed properties.

Computed properties are really just special functions that return a value based on some other property or properties. They allow you to expose a single public property in your object's API without requiring the user to understand the internals of how it is calculated; a concept called encapsulation.

We create computed properties by defining a function that we call property(). You've already seen a computed property in Chapter 1, Introducing SproutCore, the fullName property of ContactsApp.Person. Let's look at it again:

ContactsApp.Person = SC.Object.extend({

  firstName: '',

  lastName: '',

  /** This property is computed from firstName and lastName. */
  fullName: function () {
    var firstName = this.get('firstName'),
    lastName = this.get('lastName'),

    return [firstName, lastName].compact().join(' '),
  }.property()

});

// Some example code to use the above.
var me = ContactsApp.Person.create({ firstName: 'Tyler', lastName: 'Keating' });
me.get('fullName'),        // Returns "Tyler Keating" 

// Change a dependent property of fullName using KVC
me.set('firstName', 'Trevor'),
me.get('fullName'),        // Returns "Trevor Keating"

// Don't use '.' notation!
me.fullName;                 // Returns "function () { var 
firstName …

As you can see, we can access the fullName property at any time and it will always reflects the state of the firstName and lastName properties, as long as we use get and set.

Because get and set are used so frequently within SproutCore they are as efficient as possible, but there is a way that we can make our computed properties even faster. Look at the following example from my browser console, which is based on the code discussed:

Working with properties and computed properties

Notice that each time we get fullName the console message is repeated. However, the value of our computed property hasn't changed and so there is really no need to re-compute it each time. To prevent fullName from being re-calculated, we should use the cacheable() function.

Let's append the cacheable() function to our computed property and observe the output again as shown in the following screenshot:

Working with properties and computed properties

As you can see from the preceding screenshot, the first time that we get fullName, it is calculated, but all successive get instances return the cached value. While this is now much more efficient, in particular if our computed property involved complex computations, it does pose a problem. What happens when firstName or lastName change? As you may have guessed, the value of fullName will not yet update accordingly. This is because we have not indicated which properties that fullName is dependent on, which we will remedy right now. Since fullName is dependent on firstName and lastName, we simply pass the names of those properties to the property() function. This is illustrated in the following screenshot:

Working with properties and computed properties

Tip

Remember, only getting the computed property runs the property code. Therefore, do not use computed properties to call other functions, this is the role of observers, which we will look at soon. Computed properties should only be used to compute a value.

Can you see how the property doesn't re-calculate until we modify a dependent property? Excellent! we now have an efficient computed property that we can use to simplify our code.

However, so far the computed property we've been using is read-only. This is typically all we need, but it is possible to make computed properties writeable. The way it works is that each time a computed property is accessed via get or set, the function is passed two arguments: key and value. In the case of get, only the key argument will be defined and value will be undefined. Knowing this allows us to determine whether the property is being retrieved or set.

Here's an example of our same computed property, now with write support:

fullName: function (key, value) {
  if (value !== undefined) { // use !== instead of != to avoid 
type coercion
    // Setting fullName.
    var names = value.split(' '),
    this.set('firstName', names[0]);
    this.set('lastName', names[1]);
  }

  var firstName = this.get('firstName'),
  lastName = this.get('lastName'),

  return [firstName, lastName].compact().join(' '),
}.property('firstName', 'lastName').cacheable()

That concludes our look at accessing properties according to the key-value coding principle. There are a few other convenience methods in SC.Object that you will encounter for use with the properties, notably toggleProperty, incrementProperty, and decrementProperty. We won't look at these right now, but the names should explain well enough what they do and all you need to know is that internally they are simply conveniences on top of get and set.

Let's continue on and observe our properties for changes.

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

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