SproutCore

SproutCore Guides

These guides are designed to help you write and perfect your code.

Classes and SC.Object

The guide covers some of the core concepts of object-oriented programming with the SproutCore framework. By referring to this guide, you will be able to:

  • Understand how Classes work, including SC.Object.
  • Write your own subclasses based on SC.Object.
  • Create mixins to share functionality between Classes.
  • Add Class properties and functions

1 - SC.Object

SproutCore uses a traditional class-based object-oriented programming style based on the SC.Object Class. In this style, SC.Object becomes the base for all "Classes" in SproutCore and provides powerful extensions to regular JavaScript objects. These extensions include computed properties, automatic bindings, observers and more, which will be described in detail below.

1.1 - Creating an SC.Object Instance

Creation of an SC.Object instance is straightforward.

var obj = SC.Object.create()

In most cases you will want to create your SC.Object with some pre- set properties. You accomplish this by providing the properties in a hash. The following example creates a new SC.Object to represent a person.

// Create an SC.Object to represent a person. var person = SC.Object.create({ firstName: 'Peter', lastName: 'Wagenet' }); // Access our object's properties person.get('firstName'); // 'Peter' person.get('lastName'); // 'Wagenet'

The get() and set() functions allow for Key-Value observing which is described in more detail below. For now, trust that they are important and you should make a habit of always using them to access properties on SC.Object and its subclasses.

1.2 - Creating an SC.Object Subclass

In many cases you will want to create subclasses of SC.Object rather than direct instances. In the above example, we created a new SC.Object to represent a person, but it often makes more sense to define a unique Person Class, and to create instances of it. Doing so allows us to define default properties on the Class and to include related helper functions. To do this, we simply call extend() on SC.Object as the next example shows.

MyApp.Person = SC.Object.extend({ firstName: null, lastName: null, permissions: 'none', fullName: function() { return [this.get('firstName'), this.get('lastName')].compact().join(' '); } });

After defining the Person Class, we can now create new Person objects and each will contain the properties firstName, lastName and permissions as well as the simple fullName() function. The following example shows this.

var person = MyApp.Person.create({ firstName: 'Peter', lastName: 'Wagenet' }); person.fullName(); // 'Peter Wagenet' person.get('permissions'); // 'none'
1.2.1 - Calling superclass methods with sc_super

In some cases when you subclass a Class you will want to augment a method of the parent Class without completely overriding it. In this case, SproutCore provides the sc_super method which calls the original function. For those familiar with Ruby's super method, sc_super is very similar. Here is an example of a subclass of Person that extends and uses the superclass's fullName() method.

// Define a new subclass of MyApp.Person MyApp.FormalPerson = MyApp.Person.extend({ title: null, fullName: function() { // Joins `title` with the result of `fullName()` on the superclass. return [this.get('title'), sc_super()].compact().join(' '); } }); // Create a MyApp.FormalPerson object var person = MyApp.FormalPerson.create({ title: 'Mr.', firstName: 'Peter', lastName: 'Wagenet' }); person.fullName(); // Mr. Peter Wagenet person.get('permissions'); // 'none'

sc_super is one of the rare exceptions in SproutCore in that it is not actually a JavaScript function. It's just a pre-processor directive that gets replaced with arguments.callee.base.apply(this, arguments) by the Build Tools.

1.2.2 - The init method

Whenever an instance of an SC.Object is created, its init() method is called first. This function can be overridden when you need to perform additional setup each time one of your objects is created. The next example uses init() to ensure that a calculation is performed each time a new MyApp.Calculation object is created.

MyApp.Calculation = SC.Object.extend({ input: null, result: null, _calculate: function() { // Do expensive calculation this.set('result', this.get('input') * 2); }, init: function() { sc_super(); // Warning: Always invoke SC.Object's init() method! this._calculate(); } }); var calc = MyApp.Calculation.create({ input: 5 }); calc.get('result'); // 10

When overriding the init() method it is important that you call sc_super at the start of your custom init() method. This will ensure that all internal object initialization takes place as expected.

1.2.3 - Using Mixins

Mixins are an easy way to extend multiple classes, that don't share an inheritance tree, with similar functionality. Mixins are simply a hash with a series of properties that will be added to the objects you create. The next example defines the MyApp.Introducable mixin, which can then be shared between many different Classes.

MyApp.Introducable = { sayHello: function() { return 'Hello, my name is ' + this.name; } };

To add one or more mixins to a Class, add them as parameters to extend. For example,

MyApp.Person = SC.Object.extend(MyApp.Introducable, { name: ''; }); var person = MyApp.Person.create({ name: 'Bob' }); person.sayHello(); // Hello, my name is Bob

As you may have guessed, SC.Object.extend just takes a series of hashes and mixes them all in to a newly created class.

1.2.4 - Class methods

You can also define Class methods on your custom Classes. If you need to add a single Class method to a Class, you can do it in the traditional JavaScript manner. For example, the following adds a quickCreate() helper function to MyApp.Person.

MyApp.Person.quickCreate = function(firstName, lastName){ return MyApp.Person.create({ firstName: firstName, lastName: lastName }); }; var person = MyApp.Person.quickCreate('Peter', 'Wagenet'); person.fullName(); // Peter Wagenet

However, sometimes you may want to add a series of Class methods and properties. In this case you can use the mixin() method.

MyApp.Person.mixin({ quickCreate: function(firstName, lastName){ return MyApp.Person.create({ firstName: firstName, lastName: lastName }); }), }); person = MyApp.Person.quickCreate('Mary', 'Shelley'); person.fullName(); // Mary Shelley

Note that the mixin() method is different from the Mixins described above. The mixin() method will add the properties and methods within the block as Class properties and methods. Mixins themselves add instance variables and methods.

2 - Changelog

  • January 12, 2011: initial partial version by Peter Wagenet
  • January 19, 2011: further updates by Peter Wagenet
  • January 20, 2011: corrections to "The init Method" and "The Run Loop" by Peter Wagenet
  • July 19, 2013: added "Changelog" by Topher Fangio
  • August 27, 2013: converted to Markdown format for DocPad guides by deeDude