SC.RunLoop is the primary mechanism within SproutCore that will ensure all bindings propagate data changes. The reason for the run loop is due to how properties can be chained together through bindings.

Let’s say we have the following SC objects:

objA = SC.Object.create({ value: 0 });
objB = SC.Object.create({ value: 0, valueBinding: 'objA.value' });
objC = SC.Object.create({ value: 0, valueBinding: 'objB.value' });

Above we have three objects where objB‘s value property is bound to objA‘s value property, and objC‘s value property is bound to objB‘s value property. Given this binding configuration, what then happens when we do the following:

SC.RunLoop.begin();
objA.set('value', 1);
SC.RunLoop.end();

The run loop will perform two loops. When objA‘s value is set to 1, the binding connecting objA.value to objB.value will be queued up to propagate the changes. The run loop will then take the current pending change and inform the binding to actually propagate the changes through the SC.Binding.flushPendingChanges() command. This then means that objB‘s value become 1. Of course when that happens the binding connecting objB.value to objC.value will then be queued up to propagate its changes, which means a second loop is performed. Therefore the run loop is batching binding value changes and coordinating the order in which data is propagated throughout your application. Note that the run loop will only finish executing under certain circumstances, one of which is when there are no more queued up bindings to flush in SC.Binding

Now as for when a run loop is invoked, there are three common ways that this occurs within SproutCore. The first is when the browser raises a user event. The second is through timers. The third is when you make requests to a server.

User Events

When a browser raises a user event such as a mouse click, pressing a key on the keyboard, or performing some touch event, the first to respond to all these events is the SC.RootResponder. Every SproutCore application instance has one root responder, which you can find in the foundation sub-framework. When the root responder is notified of a raised event it will be responsible for beginning and ending the run loop when passing the event through a root responder chain. As an example, you can witness this by looking at the root responder’s mousedown function on line 1643 for SC 1.4 (a bunch of code has been taken out for clarity):

mousedown: function(evt) {

  // ...

  var fr, view = this.targetViewForEvent(evt) ;
 
  // ...
 
  view = this._mouseDownView = this.sendEvent('mouseDown', evt, view) ;

  // ...
 
}

Above, the root responder gets the view that was the target of the event and then proceeds to send the event to a pane via the sendEvent method. Within sendEvent you have the following:

sendEvent: function(action, evt, target) {
  var pane, ret ;

  SC.run(function() {
    // get the target pane
    if (target) pane = target.get('pane') ;
    else pane = this.get('menuPane') || this.get('keyPane') || this.get('mainPane') ;

    // if we found a valid pane, send the event to it
    ret = (pane) ? pane.sendEvent(action, evt, target) : null ;
  }, this);

  return ret ;
}

The run loop is triggered when SC.run() is called. SC.run() is just a convenience method that will execute a given function between SC.RunLoop.begin() and SC.RunLoop.end(). This concept basically applies to all the user events within the root responder in one form or another. Note that you are free to call the root responder’s sendEvent method yourself if you’d like to manually send a user event or even your own custom event. Alright, now on to timers.

Timers

In JavaScript you can call setInterval and setTimeout to trigger code to execute at a specific duration of time either once or continuously. In SproutCore you are free to call these method if you so choose; however, SproutCore also abstracts these low-level mechanisms via the SC.Timer located in the foundation sub-framework. Why SC.Timer? Because it was designed to be more efficient then directly calling setInterval and setTimeout yourself whenever a run loop is already in progress or you manually begin and end the run loop yourself. As a basic example of using SC.Timer, we have the following:

var timer = SC.Timer.schedule({ 
  target: myObject, action: 'timerFired', interval: 100 
});

So after 100 milliseconds the myObject object will have it’s timerFired method invoked but this is coordinated through the run loop which maintains a special timer queue. After a timeout has fired it causes the run loop to start again.

The most common case of when the SC.Timer is used is any time you happen to call a SC object’s invokeLater method. As an example:

// Invoke method foo on obj after 1000 milliseconds 
// (1 second) has passed from current time
obj.invokeLater('foo', 1000);

Server Requests

For most applications your build with SproutCore you’re probably going to make calls to a server in order to fetch data that users can view and interact with. In SproutCore, the data you fetch from the server is usually represented as record objects that are stored within a data store (SC.Store). The data store is an abstract repository that decouples your application from an underlying data source (SC.DataSource). The data source is what you implement to actually make calls to a server. Since that data store is oblivious to how a data source actually fetches data, the store does not execute the run loop itself; that is left to the data source to deal with.

Because it is the data source that is ultimately responsible for making calls to a server, you’re probably thinking AJAX and deciding to use the XmlHttpRequest (XHR) object to make requests and that will fire callbacks upon a response. So if that’s the case, those callbacks are when you’d ideally make calls to the run loop and notify the store when the data source is complete. Sure, you could do that, but then you’re doing a lot of the low-level mechanics yourself. Rather, let SproutCore help you by using SC.Request that you can find in the foundation sub-framework.

SC.Request abstracts away the underlying mechanics of using XHR. Instead, all you do is set what is to be notified upon a response from the server (or a timeout) and then make a call to send a message to the server. As a basic example:

var req = SC.Request.create({ type: 'GET', isJSON: YES });
// ... set various headers if required
req.set('address', '/path/to/resource/123');
req.notify(myObj, 'handleResponse');
req.send();

As you can guess with the code above, a GET request is made to /path/to/resource/123 that will handle the request and response as JSON data. Upon a response, myObj‘s handleReponse will be invoked that will be given the response data, if any. Seems pretty straightforward, but what does this have to do with the run loop? Ah, there is the mystery.

What you’re not seeing is that when the send method is called it is actually creating an instance of a SC.Response object that does all the real meat and potatoes of calling the server via an XHR object. Although there is a lot of code going on in SC.Reponse, the main thing to know is that when the server does respond, the response object’s receive method will eventually get invoked. It is within the receive method where the run loop is executed and the target and action originally provided to the request object gets called. This can be witnessed in response.js on line 240 for SC 1.4 (a bunch of code removed for clarity):

receive: function(callback, context) {

  if (!this.get('timedOut')) {

    // ...
    
    SC.run(function() {
      // ...

      if (!this.get('isCancelled')) this.notify();
    }, this);
  }

  // ...    

  return this;
}

The call to the response object’s notify method is what invokes a target’s action that was provided by the request object.

So to connect this back to the data store, when you have your data source, say, retrieve data from the server via a GET request using the SC.Request object, you might do the following:

MyApp.MyDataSource = SC.DataSource.extend({

  retrieveRecord: function(store, storeKey, id) {
    var req = SC.Request.create({ type: 'GET', isJSON: YES });
    // ... set various headers if required
    req.set('address', '/path/to/resource/123');
    req.notify(this, 'handleRetrieveResponse', { 
      store: store, 
      storeKey: storeKey,
      id: id 
    }).send();

    return YES;
  },

  handleRetrieveResponse: function(response, params) {
    // For this example we will assume that we did receive a 
    // response from the server with valid data and it contains a 
    // valid unique identifier

    var store = params.store, key = params.storeKey,
         result = response.get('body');
    store.dataSourceDidComplete(key, result, result.id);
  }

});

No fussing with the run loop above. Once the server provides a response, the data source’s handleRetrieveResponse is invoked that is then responsible for notifying the data store that the data source has completed its data retrieval. With the data store, it will go ahead and make updates to internal data hashes and then notify registered observers that a record’s properties have change. And all this is done within the run loop that the response object kicked off.

In Conclusion

So there ya have it. Three ways in SproutCore in which the run loop gets executed. Mind you, you can always invoke the run loop manually if you prefer since there’s nothing from stopping you to do so. As an example, you might feel the need to call setInterval or setTimeout outside of an already executing run loop. When the timer fires a callback, you then go ahead and execute the run loop to propagate data through bindings. Or, as another example, you might be doing some interesting JSONP outside of the data source. When the JS is fetched and executed, a callback is invoked that could modify data and requires the run loop to get involved to propagate data throughout your application. Finally, you may need to handle a special user event outside of the root responder, which you can do. Therefore, when the event is raised, the method that acts as the handler for the event will then have to execute the run loop itself.

You might have noticed a running theme going on here? Every time the run loop is executed it has come down to some kind of asynchronous event. A user action. A timer firing. The server responding. Therefore the run loop is also used to drive data propagation via binding whenever an asynchronous event is fired in order to drive the application. You can think of the run loop as being the underlying engine of your SproutCore application and the various events as fuel to keep the engine running.

-FC

Advertisements