Observing properties for instant updates

While writing an application, you occasionally want to do some action "X" whenever some change "Y" occurs. Traditionally, you may have created a helper function that you would call each time you performed a change. For example, this might result in code that looks something like the following:

MyApp.Item = SC.Object.extend({

  quantity: 0,

  updateServer: function () {
    // Sends the new information to the server.
  }

});

// Elsewhere in the code …
var item = MyApp.Item.create();
item.set('quantity', 100);

// Elsewhere in the code at a later date ...
item.updateServer();  // Oops.. missed this.  Don't forget to 
update the server!

// Elsewhere in the code …
item.decrementProperty('quantity'),

// Elsewhere in the code at a later date ...
item.updateServer();   // Darn it!  DON'T forget to update the 
server!! 

As you can tell by the colorful comments, the approach in the previous example has led to problems in the developer's code over time. Each time the quantity changed, he or she had to remember to call the update helper function. But as the application grew or as people churned in and out of the project, "keep it in the developer's mind" did not work well. Instead, we should let the code itself remember to do the right thing when certain properties change using SproutCore's observers.

The simplest observer can be set up when our object is created using the observes() function. Here's an easy improvement on the previous example, that would have saved the developer a few headaches:

MyApp.Item = SC.Object.extend({

  quantity: 0,

  updateServer: function () {
    // Sends the new information to the server.
    console.log("Updated the server.");
  }.observes('quantity')

});

var item = MyApp.Item.create();

// Elsewhere in the code ...
item.set('quantity', 100);
 > "Updated the server."

// Elsewhere in the code ...
item.decrementProperty('quantity'),
 > "Updated the server."

Now each time the quantity changes, the updateServer() function runs without us having to remember to manually trigger it, thus simplifying our code and possibly saving us from serious problems in the future.

The observes function works by adding observers when an object is created, but we can also easily add observers manually after the object already exists using addObserver. To do this, we call addObserver on the object and pass in the name of the property to observe along with the callback function. For example:

var item = SC.Object.create({ quantity: 0 });

// Manually add an observer.
item.addObserver('quantity', function() {
  // Sends the new information to the server.
  console.log("Updated the server.");
});

Obviously, if every instance such as item needed the same observer, it would be easier to make an Item class and use observes like in the first example. However, addObserver works really well when paired with removeObserver. This is a useful pattern when we need to only temporarily observe a property.

To do this, we use alternate arguments for addObserver and pass in a target and method rather than a callback function. Here's an example that begins observation at a certain point and then turns it off again when it is no longer needed:

MyApp.Item = SC.Object.extend({

  quantity: 0,

  quantityDidChange: function () {
    // Sends the new information to the server.
    console.log("Updated the server.");
  }

});

var item = SC.Object.create();

// Start observing quantity now (target: item, method: 
item.quantityDidChange)
item.addObserver('quantity', item, item.quantityDidChange);

item.set('quantity', 100);
 > Updated the server.

// Stop observing quantity now.
item.removeObserver('quantity', item, 'quantityDidChange'),

In this way, we don't need to have observers connected and receiving updates if our application happens to not be in a state that can use the updates.

Tip

Just as computed properties should only be used to return values, observers should only be used to call functions when a property changes. Try to avoid using observers to set flags such as isReady. Instead, your flags should be computed properties that compute themselves based on the state of the application.

Lastly, we can also observe properties across objects using "property paths". Property paths are simply strings containing chains of property names connected with periods that point to a property in another object. These paths may be relative or absolute.

For example, the absolute path to the fullName property on the MyApp.userController.content object would be:

'MyApp.userController.content.fullName'

The relative path within the userController object to the same property would be:

'.content.fullName'

As you can see, absolute paths start with a global object while relative paths begin with a period. Let's look at an example using the property paths discussed:

Observing properties for instant updates

This example is simple enough. When I change the value of fullName, both observer functions are called because the property paths resolve to the same property on the content object.

Did you notice that I used the helpers getPath and setPath, rather than chaining together get and set? If I hadn't used setPath for instance, the line would have looked like:

MyApp.userController.get('content').set('fullName', 'Heather 
Keating'),

There is one more catch though. Are you able to guess it? Pay attention to what happens when I set content in the same example:

Observing properties for instant updates

Uh-oh! I swapped out the content and its fullName property and neither one of our observers fired. The problem here is that only the last property in the path is observed on the second-to-last object (that is fullName is observed on MyApp.userController.content). This means that two fullName observers are attached to the initial content object and when that initial content object is removed or replaced, our observer code goes with it. The new content object does not automatically get the observers transferred over to it.

To address this, observers (and bindings as we'll see later) recognize a special * property path separator. As I mentioned earlier, the last property named is observed on the second-to-last object named, but with the * separator, all objects named after the star are observed for changes. This means that the following two property paths are essentially the same:

'MyApp.userController.content.fullName'
'MyApp.userController.content*fullName'

However, by moving the * separator in the path we can observe fullName and content changes as shown

'MyApp.userController*content.fullName'

With the property path discussed, fullName will be observed on content and additionally content will be observed on MyApp.userController. Let's try it out in an example:

Observing properties for instant updates

Now when I change either fullName of content or content itself, our observers update accordingly.

Tip

Be aware that each additional chained property in the property path requires more processing to manage. So it's a good idea to only observe what absolutely must be observed.

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

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