Chapter 2. The Runtime Environment

Much of SproutCore is built on top of a few core technologies that comprise the "runtime environment". You can think of the runtime environment as the foundation that you will build your application on top of. The success story for SproutCore is that its foundation is so well designed, which makes it possible to build extremely large and complex applications while still achieving exceptional performance. Therefore, being able to understand the fundamentals of the runtime environment and how to use it properly is the key to any well-written SproutCore application and in this chapter we will attempt to do just that.

In this chapter we will cover the following:

  • Building on SproutCore's object model
  • Using mixins to share functionality
  • Working with properties and computed properties
  • Observing properties for instant updates
  • Binding properties for error-free consistency
  • Working with enumerables:
    • SC.Array
    • SC.Set
    • Observing enumerables
    • Observing properties on enumerable items
  • Understanding the run loop

Building on SproutCore's object model

As you are likely aware, JavaScript uses prototypes and there are no classes in the "class-based object-oriented programming" sense within JavaScript. But in SproutCore, we often refer to classes and objects (that is instances of a class) and the reason for this jargon is in part due to the familiarity of those terms to most developers and in part due to the manner in which we use SproutCore's root class, SC.Object, to organize and enhance our code. Please don't get hung up on the semantics of a prototype versus a class, it really doesn't matter for our purposes.

What you should pay attention to though, is that SC.Object is the base of every class within SproutCore and provides the class-like features, such as inheritance, instantiation, mixin support, key-value coding, binding and observing, computed properties, and other conveniences that we will use.

It's probably easier to explain these features through example, so let's first create a subclass of SC.Object. For instance if we had an app, which we'll simply call MyApp, that contained different types of vehicles, we may wish to define a reusable class containing properties shared by all of the vehicles. To do so we simply subclass SC.Object using the extend method. For example:

MyApp.Vehicle = SC.Object.extend();

To define common properties for the MyApp.Vehicle class, we pass a JavaScript object to the extend method. Here's a nice professional example defining the MyApp.Vehicle class along with its default properties:

/** @class
  A vehicle.
*/
MyApp.Vehicle = SC.Object.extend({

  /** 
    The make of the vehicle.

    @type String
    @default 'unknown'
  */
  make: 'unknown',

  /** 
    The model of the vehicle.
    
    @type String
    @default 'unknown'
  */ 
  model: 'unknown',

  /** 
    The type of the vehicle.
    
    @type String
    @default null
  */
  type: null

});

Note

The property types of the object passed to extend are not restricted to the JavaScript primitives; you can also set functions in order to define instance methods for the new class.

Once the MyApp.Vehicle class is defined, we can further subclass it as we like. For example:

MyApp.Car = MyApp.Vehicle.extend({

  /**
    The top speed of the car in miles per hour.

    @type Number
    @default 0
  */
  topSpeed: 0,

  /** MyApp.Vehicle override */
  type: 'car',

  /**
    Helper method to convert the given speed in miles per hour to 
kilometers per hour.

    @param {Number} speedMph The speed in mph.
    @returns {Number} The speed in kph.
  */
  calculateSpeedKph: function (speedMph) {
    return speedMph * 1.609344;
  } 

});

Once we've defined the classes we need for our application, we are able to create the instances of these classes for use. To create a new instance of SC.Object or any of its subclasses, we use the create method. For example, we could create an SC.Object instance as shown:

var myObject = SC.Object.create();

We would create instances of SC.Object subclasses in the same way as shown:

var aVehicle = MyApp.Vehicle.create();
var aCar = MyApp.Car.create();
var aView = SC.View.create();

As you would expect, instances inherit the properties of their parent classes and are unique from one another. For example:

aVehicle.get('type'),  // returns null
aCar.get('type'),       // returns 'car'
aVehicle.calculateSpeedKph;   // returns undefined
aCar.speedKph(60);  // returns 96.56064

And just like with extend, we can initialize and further configure our object on creation by passing in one or more JavaScript objects. For example:

var myObject = SC.Object.create({ 
  a: 'foo',
  b: 'bar'
});
myObject.get('a'), // returns 'foo'

var fastCar = MyApp.Car.create({ topSpeed: 195 }); 
fastCar.get('topSpeed'),  // returns 195

Did you notice in the previous example that we created an instance, myObject, based directly off of SC.Object? While we could have made a subclass and used it to create an instance, it was simpler just to pass the custom properties and methods to the create method. Doing this is a useful way to create singleton objects that can exist for the lifetime of the running application, which is more efficient then defining a subclass and only ever instantiating one object from it.

Before we look at more features of SC.Object, there are two important methods defined within SC.Object that are used when dealing with the object's lifecycle. These two methods are init and destroy . As you might expect, the purpose of the methods is to set up and teardown an object. Therefore if you would like to perform extra initialization on your object as it is created, you would override the init method and likewise, to perform additional cleanup on destruction, you would override the destroy method.

For example:

GolfApp.Game = SC.Object.create({

  /** Array of scores for each hole in order. */
  scores: null,

  /** @private */
  init: function () {
    // Always call the superclass method!
    sc_super();

    // Any extra initialization can go here.  For example,
    // Initialize the scores.
    this.scores = [];
  },

  /** @private */
  destroy: function () {
    // Always call the superclass method!
    sc_super();

    // Any extra cleanup can go here.
  }

});

There are two important things to recognize about the previous example. The first is that you need to be careful when defining non-primitive values for properties (that is Arrays and JavaScript objects). Notice that the value of the scores property is defined initially as null and later set to the array object in init? If we had rather defined the property as an array, like scores: [], … , every instance of GolfApp.Game would share the same array object in memory. While the bad players might enjoy sharing their scores with the good players, it's not what we want to happen. Instead, when each instance is created we use init to give that instance a unique scores array.

The second important thing to recognize is that we are able to override the SC.Object class's original init and destroy methods and still call them using sc_super. This is a special class-like behavior added by SproutCore that is not normally easy to accomplish in JavaScript, but with SC.Object it's trivial.

Note

The sc_super() function is not actually a JavaScript function, but a preprocessor command for the build tools. This is one of the few places where the build tools will modify your code and replace sc_super() with arguments.callee.base.apply(this, arguments), which looks up the current function on the super class and calls it using the arguments given.

Now that we've learned how to build a simple class hierarchy using SC.Object, let's look at further approaches to defining properties that makes it easier to prevent code duplication and keep our hierarchy lightweight.

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

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