SproutCore 1.0: New Bindings and Observers

November 2nd, 2008

Property observers and bindings form the very core of SproutCore’s API.  In fact, SproutCore is one of the few frameworks to have implemented bindings and observers from the very beginning instead of bolting it on at a later date.  This contributes heavily to SproutCore’s compact design.

The centrality of bindings and observers also makes it one of our most important so that is why it was one of the first things we revisited for SproutCore 1.0.  Since this code as so old, it was quite easy to rewrite it using some more efficient techniques, giving us a 2x performance boost when creating new bindings and setting up new observers.

In addition to a raw perf boost, we also changed some key behaviors related to timing that could change how your application behaves.  These changes should speed up your app even further by reducing the number of bindings that fire in your application.  Depending on how you’ve written your code, however, it could change how your code behaves.  Here is what we did:

Change 1: setIfChanged Becomes Default

Today, every time you call set() on an object, SproutCore will invoke any observers you have as well, even if the value you are setting has not changed.  For example, if you write some code like this:

obj = SC.Object.create({
  firstName: null,

  firstNameObserver: function() {
    console.log("observer fired");
  }.observes('firstName')
});

obj.set('firstName', 'John');
> observer fired
obj.set('firstName', 'John');
> observer fired
obj.set('firstName', 'John');
> observer fired

Now, the default behavior has changed.  With the new code, observers will only fire if the value you set actually changes.  For example, the output of the above code with SproutCore 1.0 is:

obj.set('firstName', 'John');
> observer fired
obj.set('firstName', 'John');
obj.set('firstName', 'John');

This means you can write less code to make sure your observer is not fired more often than necessary.  Big win.

The only problem with this change is that this means computed properties may sometimes not be set more than once if you call set() with the same value.  Usually this will not matter, but if your computed property needs to be called everytime you try to set the value, you can change this behavior by adding the indempotent() helper to your property:

fullName: function(key, value) {
  // do something interesting here
}.property().indempotent(NO)

Adding this qualifier to your property will cause it to be called every time there is a set() instead of just when the value changes.

Change 2: Binding and Observer Timings

Currently, whenever a property changes on your application, observers are notified immediately unless you are already inside another observer handler.  Then they are queued and notified when the root observer exits.  Since bindings are implemented using observers, bindings will trigger when your other observers finish but before the set() returns, again unless you are more than one level deep in observers.

If this sounds confusing to you, that’s because it is confusing.  It also makes it hard to predict exactly how change notifications will propagate through your application.

With SproutCore 1.0, this timing behavior is significantly cleaned up.  The rules are simple:

  • Observers (i.e. methods you setup with observes() or addObserver()/removeObsevers()) will fire immediately when you call set(), unless wrapped inside of a beginPropertyChanges()/endPropertyChanges() pair.  In that case, they will fire immediately when you call endPropertyChanges().
  • Bindings on the other hand queue up.  They fire only once at the end of the run loop, when all of your normal event handling or timer code finishes executing.  Bindings are also coalesced.  If a binding changes more than once during a single run loop, it will still only fire once at the end.

The net effect of this timing change will be that when a property changes on an object, the observers you have written for that object will execute immediately so you can update other state within your object.  But, the bindings that tie together the objects in your app will fire all at once.  This will cause changes to relay through your app rather like a wave, starting at the model or view layer and propagating to the other side in layers.

This more controlled rate of change will generally not require you to change any code, but it does mean that code you put in to gate changes and control duplicate messages will be less necessary.  It will also generally speed up your application since binding change notifications occur less frequently.

Change 3: Cache Computed Properties

I mentioned this the other day so I won’t spend a lot of time on it now, but the basic idea here is that often the return value of a computed property only changes when one of its dependent keys or some other state changes.  In the mean time, multiple calls to get that property could return a cached value.  Caching used to require many lines of code, but now it can be done with just one.  Here is how you write a cached property:

contact = SC.Object.create({
  firstName: 'John',
  lastName: 'Doe',

  // computed property, cached until first or last name changes
  fullName: function() {
    console.log('computing fullName') ;
    return this.getEach('firstName','lastName').compact().join(' ');
  }.property('firstName', 'lastName').cacheable()
});

contact.get('fullName')
> computing fullName
> "John Doe"

contact.get('fullName')
> "John Doe"

contact.set('firstName', 'Jane');
contact.get('fullName');
> computing fullName
> "Jane Doe"

contact.get('fullName');
> "Jane Doe"

Now the fullName computed property will be called only once and cached.  You can get() as often as you like without paying the cost to recompute the value.  If you ever need to recache a computed property without changing one of its dependent keys, you can do so by calling properyDidChange().

Change 4: Use a target and method with addObserver() and removeObserver()

The low-level addObserver() and removeObserver() methods are used to setup and remove observers on an object.  Their API currently requires you to pass the property you want to observe along with a function to execute.  To execute the function in the context of a particular object, you had to wrap you function using Function.bind(), which adds a stack frame, another object and closure and generally slows everything down.

Now addObserver() and removeObserver() will be able to accept a target and method, which will be tracked separately for you.  This allows you (and SproutCore) to avoid having to create these extra closures, which increases performance and helps on limited JavaScript engines like IE7’s JScript.

Change 5: Disable Automatic Observer Notification

Normally, when you call set() with a new value on a property, SproutCore will automatically notify observers when the property has changed.  Sometimes, however, you may have a property with a more complex behavior and you don’t want the automatic notification process to take place.  Now you can override this behavior by implementing a new method on your class called automaticallyNotifiesObserversFor(key).  Returning NO for this method will disable automatic notification for the passed key.

This technique is most useful when used with computed properties.  For example, let’s say you decide to implement a computed property that only allows values greater than 10 to be set on it.  Here is how you might implement this:

obj = SC.Object.create({
  // computed property can be set to any number
  // as long as it is greater than 10
  value: function(key, value) {
    if ((value !== undefined) && (value > 10) && (value !== this._value)) {
      this.propertyWillChange('value'); // implement notification manually
      this._value = value ;
      this.propertyDidChange('value', value) ;
    }
    return this._value ;
  },

  // disable automatic notification for this property
  automaticallyNotifiesObserversForKey: function(key) {
    return (key === "value") ? NO : YES ;
  },

  // add observer just to show  what happens
  valueDidChange: function() {
    console.log("value did change to: %@".fmt(this.get('value')) ;
  }.observes('value')
});

obj.set('value', 20) ;
> value did change to 20

obj.set('value', 5);
[no output]

obj.set('value', 21)
> value did change to 21

obj.set('value', 21)
[no output]

This is definitely an advanced topic, but if you need to control precisely how and when property change notifications happen, it can be a god-send.

In general, you will be able to use the new property observers and bindings in SproutCore without having to worry about the changes here.  You should just notice your app running a little faster.  Understanding how some of these work though can dramatically reduce the amount of code you have to write in certain advanced situations.  If you have questions about this once its available, be sure to ask on IRC or the mailing list!

On the future of SproutCore

October 31st, 2008

It’s been nearly four months since SproutCore launched to the public at WWDC and we couldn’t be happier with the results.  18,000 developers have installed SproutCore (sudo gem install sproutcore ftw), nearly 1,000 developers have joined the mailing list, and dozens of projects are underway at companies around the world.  One additional one has already gone public (OtherInbox).

During this time the developers working on SproutCore haven’t stood still either.  150 tickets closed, some major new features, and enhancements for Windows, IE7, Chrome, and others.  Many of the changes we’ve applied have come from you, the community.  In fact, over 20 people have contributed code to SproutCore now, which is outstanding for such a young project.

Now that I’m back from my trip, though, I thought we should spend a little time talking about where we are headed next.

Put simply, our next major milestone is SproutCore 1.0.  When I started planning SproutCore 1.0, here were the criteria I laid out for it:

  • Make the common easy and the uncommon possible. Typical behavior for an application should be nearly automatic without limiting a developer’s ability to hack something cool.
  • Support the whole application. SproutCore must support the whole application development process, including the model, view, and controller layers as well as design, testing, documentation, and deployment concerns.
  • A small consistent API. Favor configuration over class-bloat.  Use consistent “guessable” design patterns.  The API should be vetted well enough that it will not need to change dramatically once released.
  • Offer broad platform support. Perform well on all modern browsers.  Perform adequately on IE7 and earlier.

So far, we’ve come pretty close to achieving most of these goals.  In particular, SproutCore is the only open source web application framework that can help you build your model layer and your view layer, talk to your web server, and then tie it all together with bindings.  It is also one of the few that include documentation and unit testing right in the build tools.  The API is also pretty good when you consider how much functionality you can deliver with a few lines of code, thanks to bindings.

After watching nearly a dozen apps developed with SproutCore, though, we’ve definitely found some places we could improve both the API and the implementation.  To that end, we’ve undertaken a major review of the entire SproutCore framework over the last few weeks.  Based on that review, we are rewriting some of the oldest parts of our code to make them faster and more memory efficient and revisiting parts of our API to make them more consistent.

A lot of this work is taking place in different repositories, some of them private, so the code will not drop into the public repository until we have everything integrated.  In the mean time, I can tell you about some of the changes we have made.  Over the next few weeks, I will delve deeper into some of the specifics of our APIs, but here are some of the things we are doing at a high level:

Faster Observers and Bindings

Property observing and bindings underpin almost everything you do in the SproutCore framework.  Because of that it is really important to make this feature small and fast.  We have currently rewritten this code to make it almost 2x faster on its own, and to use significantly less memory.  More on this in the coming days.

DOM Library Independence

Currently, SproutCore depends on Prototype for a few cross-platform functions.  This really doesn’t make much sense.  In particular we think of Prototype, jQuery, and others as “DOM manipulation libraries”; somewhat like low-level drawing APIs.  SproutCore should live above this layer, allowing you to choose whichever drawing library you like to create custom views.  Additionally, removing this dependence will allow those who do not want to use Prototype to eliminate that page weight from their apps.

New Model Layer

The current implementation for SC.Store, SC.Collection, SC.Record and the servers have not been revisited since they were written almost two years ago. When these were first deployed, they worked fairly well for the small apps that used them.  Since then we’ve seen applications loading 40,000+ records into memory in a regular basis and a move towards investigating use of the coming local storage facilities on modern browsers.  This code is going to see a wholesale rewrite as we update the API to accommodate this new, larger scale world.

Better Documentation

During our revisiting of the API, we are also adding to and improving the inline documentation for all classes.  Combined with some new tutorials, the final SproutCore should be much easier to learn and to use.

And a whole lot more…

We have some other big changes in the works as well that we can’t talk about yet, but there is one more thing:

Compatibility

Even though SproutCore’s API will change, dramatically in some places, we know there is a lot of code already running on the current SproutCore API.  To support these older applications we are developing in tandem with the changes a “sproutcore-deprecated” framework.  This framework can be installed in your apps to support all of the existing API until you have a chance to update your code at your own pace.

We believe that the web is about to undergo a major revolution in terms of how we develop software.  As more applications move to the cloud, users will want to take the rich, desktop experience they are accustomed to with them.  We think that SproutCore can be one of the best options to deliver this kind of experience with our open source heritage, simple API, and focus on the whole application.  Our hope is that the new SproutCore 1.0 will solidify this case.

SproutCore Meetup at Google

October 27th, 2008

The Silicon Valley SproutCore meetup group will be meeting at Google on November 12.  We are co-hosting this meeting with our friends at the Google Technology User’s Group.  Thanks especially to Van Riper who helped set this up.

The topic, of course, will be using SproutCore with Google AppEngine and Chrome.  If you are in the area, RSVP and come on by!

Meetup Page

Caching Computed Properties

October 26th, 2008

David brings up another good point in a comment on my recent post about computed properties.  If you have a property that is computed but only changes occasionally, you can use a hybrid approach with both observers and computed properties to cache this value efficiently.  Here is how the fullName example we used yesterday would be implemented:

Addressbook.Contact = SC.Record.extend({

  // firstName, lastName provided automatically by server
  // computed property returns fullName.  Use cached value if
  // provided or compute.  Save computed value in cache.
  fullName: function() {
    if (!this._fullName) {
      this._fullName = this.getEach('firstName','lastName').compact().join(' ');
   }
   return this._fullName;
  }.property(),

  // clear fullName cache whenever firstName or lastName changes.
  fullNameShouldChange: function() {
    this.propertyWillChange('fullName') ;
    this._fullName = null; // clear
    this.propertyDidChange('fullName');
  }.observes('firstName','lastName')
});

Note how the computed property here is not listed as having any dependent keys.  Instead, we write both an observer and a computed property.  The observer will notify that the fullName property has changed, but avoids the cost of calculating the new fullName property itself.  This still only happens when another object tries to get() the value of the fullName property.

For an example this simple, the added overhead of managing this caching probably does not yield enough benefits, but if computing the fullName property is significantly more expensive, its easy to see how this might be useful.  Let’s see, for example, how you might use this to automatically lazily load some data.  The example below assumes you are writing a twitter client and lazily loads the twitter feed for the current user.  Whenever the user changes, it clears the feed cache so that the next time you ask for the feed it can load:

Twitter.feedController = SC.Object.create({
  // this is the name of the user you want to load
  user: 'okito',

  // computed property lazily loads the twitter feed for the
  // current user.
  feedContent: function() {
    if (!this._feedContent) this.loadFeedContentFor(this.get('user'));
    return this._feedContent;
  }.property(),

  // uses Ajax to load the new feed data, then calls
  // feedContentDidLoad() with the new feed content.
  loadFeedContentFor: function(user) {
    // loading code goes here...
  },

  // observe the user and reset the feed when needed.
  userDidChange: function() {
    this.propertyWillChange('feedContent');
    this._feedContent = null;
    this.propertyDidChange('feedContent');
  }.observes('user'),

  // called when loadFeedContentFor() gets the data.
  feedContentDidLoad: function(feedContent) {
    this.propertyWillChange('feedContent');
    this._feedContent = feedContent ;
    this.propertyDidChange('feedContent') ;
  }
});

Now an indecisive user can change their mind as often as they want about whose feed they want to view and your client won’t actually try to load the feed until you need to actually display the data.  Best of all, the rest of your application never has to understand any of this logic.  It just binds to the user and feedContent properties and trust the controller to manage it accordingly.

One More Thing… (Sorry, couldn’t resist)

You can use the technique described above to cache computed properties in your applications today.  Over the last year, though, this has become such a common design pattern, that we’ve decided to support it natively in SproutCore.  Starting with the RC1 release of SproutCore 1.0, you will be able to add cacheable() to your computed property and SproutCore will cache it for you.  Here is how you would cache the fullName example above:

fullName: function() {
  return this.getEach('firstName','lastName').compact().join(' ');
}.property('firstName', 'lastName').cacheable()

That’s it.  No observers or private variables are required.  SproutCore will automatically cache the return value of this property the first time you get() it.  It will then use the cached property until one of your dependent keys (’firstName’ or ‘lastName’ in this case) changes or until you call propertyDidChange('fullName') on the record.

This new feature will not only help you eliminate glue code but also can make your application quite a bit faster.  Don’t let that stop you from using cached computed properties today with the techniques describe above though.  It can’t really help you clean up your code.  Thanks for the tip, Dave.

Using Computed Properties To Lazily Load Data

October 24th, 2008

Following up on yesterday’s computed properties post, here is a useful technique for lazily loading data using computed properties.  Let’s say your contacts application will show you a list of all the email you have exchanged with a particular contact.  This is a lot of detail that you generally will not want to load from the server but you need to have it available anyway.

How would we construct such a thing?

Well, a simple way to start would be to create a collection view that uses a property (called “messages”) on your Contact as its content:

<%= list_view :bind => { :content => "Addressbook.currentContact.messages" }, :content_value_key => :subject %>

This view helper would show a list view with the messages array.  But, this means that your Contact record has to include an array of all the messages.  We just said we didn’t want to load those messages immediately, but only when the user views a contact’s details.  How do we do this without having to write a lot of glue code?

Turns out computed properties come to the rescue.  Here is some sample code we could write:

Addressbook.Contact = SC.Record.extend({
  // ... other useful code
  messages: function() {
    if (this._messages) return this._messages; // use cache if present

    // otherwise, load from server
    Addressbook.server.loadMessagesForContact(this);
    return [];
  }.property(),

  // this method is called by Addressbook.server.loadMessagesForContact() when
  // the messages have loaded.  The passed value is an array of
  // Addressbook.Message records.
  messagesDidLoad: function(messages) {
    this.propertyWillChange('messages') ;
    this._messages = messages;  // save in cache
    this.propertyDidChange('messages') ;
  }
});

This code assumes that you also have an object called Addressbook.server with a method loadMessagesForContact() which will, as the name says, load the messages for the named contact and then calls the messagesDidLoad() method on the contact with the array of messages.

There are two really important things to notice about this code:

First, the computed property simply returns a cached value or, if it does not have the cached value, requests the value from the server.  When you first select a contact, your list view will initially display an empty list while it kicks off the request for more data in the back end.

Second, the messagesDidLoad() method, which is called when the values have loaded from the server, simply saves these in the cache and then tells SproutCore that the value of the messages property has changed.  If you are still viewing this contact’s details when this happens, this change notification will trigger the binding to your ListView and cause it to request the value of messages again, now with the loaded data.

Later, if you view other contacts and come back to this one, the messages will still be cached and will display immediately without refreshing the server.  Simple, lazily loaded data and, best of all, no glue code.

What’s This propertyWillChange/properyDidChange Thing?

The two methods called in messagesDidLoad() here might be a new trick for you.  What do these methods do?  In fact, the code you see in messagesDidLoad() is very similar to what happens whenever you call set().  They simply tell SproutCore that the value for the named property has likely changed.  This will in turn cause SproutCore to notify any observers and trigger bindings.

Usually you do not need to call these two methods yourself, but, as you can see here, they have their place.  You can use this technique anytime you have a “virtual” property like this one.   Note that the messages property above does not explicitly list any dependent property keys.  This is because the property depends on the calls to propertyWillChange()/propertyDidChange() in messagesDidLoad() to tell SproutCore when the property has changed instead.

And What If My Data Changes?

Finally, let me show you a common variant on this technique that can make it even more useful.  The above technique is useful if the data you are loading lazily will never change once you have loaded it once.   But what if this is not the case?  Maybe a new message has been received since the last time you viewed a contact.  How can you make sure your data always stays up to date?

It turns out this is quite easy.  Just rewrite your messages computed property like so:

messages: function() {
  // always refresh
  Addressbook.server.loadMessagesForContact(this);

  // then return the current value, if there is one
  return this._messages || [] ;
}.property()

Now every time some other object asks for the value of messages, it will check the server for any changes.  Of course you could rate limit this if you want to implement any other policy to control how often you check for new messages, but the principle is the same.

Computed properties are a powerful way to lazily compute - and load - data in your application.  When used with bindings, they make it easy for you to keep the data you load at one time to a minimum.  Hopefully this little demonstration will makes it a little easier to do that.

About Computed Properties

October 23rd, 2008

A lot of people who are new to SproutCore wonder about computed properties at some point.  What is the difference between a computed property and an observer?  This article aims to answer that question for you.  Let’s look at a common example.

Let’s say you are building a contact application, not unlike the sample you can get off of our github site.  Your Contact record would probably have the following properties.

  • firstName - the first name, comes from the server
  • lastName - the last name, comes from the server
  • fullName - the first and last name combined.  Used for display in the contact list, etc.

Obviously, you plan to get the firstName and lastName from your server, but where will the fullName come from?  After all, that is the property you want to display in your contact list.

The Observer Way

It would be best to automatically generate the fullName from your firstName and lastName.  Whenever either one of those properties changes, the fullName property should change also.  Sounds like a job for an observer function.  Your first take on this might look something like:

Addressbook.Contact = SC.Record.extend({
  // firstName, lastName both added automatically from server or fixture data
  fullName: '', // default value

  rebuildFullName: function() {
    this.setIfChanged('fullName',
      this.getEach('firstName', 'lastName').compact().join(' '));
  }.observes('firstName', 'lastName')
});

Not bad.  Nice and compact.  Now anytime firstName or lastName changes, fullName will be regenerated.  We even use the nice compact() and join() methods so that if either property is null, the name will still look nice.

But, there is a problem here.  Every time you edit firstName or lastName, you now bear the added expense of recalculating fullName.  Not such a big deal with one contact, but if you load thousands of them or even if you just want to run on Internet Explorer, this added cost really adds up.

Wouldn’t it be better if we could somehow calculate the fullName property on demand instead?  This way you could change firstName and lastName anytime you want.  You only bear the cost of recalculating the fullName property when some part of your application asks for it.  This is where computed properties come in.

The Computed Property Way

Let’s rewrite our class with a computed property now:

Addressbook.Contact = SC.Record.extend({
  // firstName, lastName both added automatically from server or fixture data

  fullName: function() {
    return this.getEach('firstName','lastName').compact().join(' ');
  }.property('firstName','lastName')
});

This code looks really similar to the example we had above, but uses some new notation you may not have seen before.  fullName now points to a function, but that function has a special “property” declaration at the end.  This is how you tell SproutCore that this method is a computed property.  Now, whenever you do something like:

contact.get('fullName');

Instead of simply returning the value of the fullName property, SproutCore will execute the function and return its result.  Hence, a computed property.

The two property names passed to the property() function tell SproutCore that the fullName computed property “depends” on both the firstName and lastName properties.  Whenever either of these properties are changed, SproutCore will also notify observers of the fullName property that it has changed also, just like what happens when you call this.set(’fullName’) in the observer example above.

This computed property approach implements basically the same behavior as we get with the observer method above, but now you only bear the cost of computing fullName when some other part of your app actually needs to use it.

Let’s say you load your contact application with 100 contacts.  Although the firstName and lastName properties will be set on all of the contacts, the fullName property will be calculated only for the 10-15 contacts or so that are visible on the screen.  Instant performance gain.

When to use Computed Properties

Computed properties are a nice way to deal with generating complex values at a lower cost, but when should you use them?

The general guideline is that you should use a computed property when you have one property that is somewhat expensive to generate and that will only be needed some of the time.  Usually you will use computed properties on model objects to compute complex properties, like above, and to lazily load data.

Once you get to the controller or view layer, computed properties have less value because a property you generate here is likely to be used all the time anyway.  Nevertheless, computed properties are another useful tool to add to your arsenal that can give your app a major speed boost, brought to you by SproutCore.

Mike Subelsky on SproutCore

October 16th, 2008

Mike Subelsky, one of the people behind SproutCore-based OtherInbox, gave a great talk at B’more on Rails last week and some folks were kind enough to film it for us. It’s a big video file but a good in-depth look at how Mike is using SproutCore. Great work Mike!

Screenshot

View the movie

Thanks to Smarticus for the link!

SproutCore relaxes with CouchDB

October 1st, 2008

Thanks to the hard work of Geoffrey Donaldson, SproutCore has its first major 3rd-party framework: an SC.Server subclass to interact with a CouchDB backend!

There’s a page on the SproutCore wiki with usage information and you can install the framework by doing this (note: this command should be run from the root of your sproutcore directory, the one with sc-config in it):

sc-install geoffreyd-couchdb

Then edit sc-config to load the framework into your clients:

c[:required] = [:sproutcore, :'geoffreyd-couchdb']

And restart sc-server. Head on over to the wiki to find out how to use it, and be sure to give Geoffrey feedback on the mailing list.

If you have patches, please submit them through SproutCore’s ticket system.

SproutCore 0.9.19 — hot fix release

September 30th, 2008

A change in the new project template resulted in new SC clients not loading. This releases fixes that bug, and also includes a fix for SC.Server from Evin Grano.

To get the new release, just do:

sudo gem install sproutcore

SproutCore 0.9.18 - “Black (Wonderful Life)” release

September 30th, 2008

Black (It’s A Wonderful Life), film by Gerard de Thame

Get the Flash Player to see this player.

This release focuses on user contributions, and there were lots. 21 people contributed to this release, making this our biggest community effort yet. Nearly 50 tickets were retired, all of the IE7 tests now run (with one minor exception), and 100% of our tests run on Google Chrome.

Major highlights of this release include:

  • rewritten SC.SplitView Huge thanks to Lawrence Pit for doing this, including support for both directions! Plus: a new sample client, split_view, demonstrating usage.
  • improved HAML and now SASS support Also from Lawrence Pit
  • Merb-compatibility We now work with merb-core 0.9.1 and later.

Build Tools

  • Ticket [52] sc-gen should give warning on incorrect usage (Alexei Svitkine)
  • Ticket [119] Regression: body.html.erb no longer works (Michael Allman)
  • Ticket [125] c[:javascript_libs] included too often (Lawrence Pit)
  • Ticket [43] sc-server has to be restarted after error in code is executed (macinjosh)
  • Ticket [70] id attribute generated twice for views (Alexei Svitkine)
  • Ticket [24] text_area_view missing ’show-hint’ class (mrc)
  • Ticket [117] Sass support (Lawrence Pit)
  • Ticket [132] html tag is missing required attribute xmlns (Alexei Svitkine)
  • Ticket [173] c[:resources_relative] = true should be default in sc-config (Davide Benini)
  • Ticket [131] Proxy doesn’t forward GET params with merb 0.9.4 (Alexei Svitkine)
  • Ticket [179] text_area_view’s generated textarea tag does not validate (Sindre Aarsaethe)
  • Ticket [130] sc-gen language USAGE file wrong and doesn’t not generate files correctly (Trek)
  • Ticket [147] Allow view_helpers (and templates) to call dom_id to generate a guaranteed unique DOM id (Lawrence Pit)
  • Ticket [146] Fix memory leak in build tool (Lawrence Pit)
  • Ticket [144] require(’…’) doesn’t accept .js extension on end of nam (Lawrence Pit)
  • Ticket [143, 145] -s and –source not working on sc-instal (Trek)
  • Ticket [126] Add –haml and –sass options to sc-init and sc-client (Lawrence Pit)
  • Ticket [116] Refactored rendering…. allow yield :foobar (Lawrence Pit)
  • Ticket [148, 151] Compress CSS when build for production (Lawrence Pit)

Framework

  • Ticket [82] picker.js bug (Alexei Svitkine)
  • Ticket [103, 104] selection_support.js problem with SelectFieldView (Bill Klaila)
  • Ticket [127] Fixed radio enabled display state (Gert Hulstein)
  • Ticket [12] CollectionView doesn’t resize groups after a frame change (Christopher Swasey)
  • Ticket [101] SC.Record.Date should handle GMT formatted strings (Joshua Dickens, Erich Ocean)
  • Ticket [102] Missing SC.Collection.removeRecords method (mm)
  • Ticket [99] SC.Record.create() inconsistency (Michael Allman)
  • Ticket [166] toMany relationship is null (Reto Wolf)
  • Ticket [166] SC.CollectionView itemViewForEvent somtimes throws exception on IE8 (Darryl Fuller)
  • Ticket [152] SC.Collection doesn’t update itself properly (Christopher Swasey)
  • Ticket [155] SC.Record notifies SC.Store improperly of changes (Christopher Swasey)
  • Ticket [156] Basic tests for collections (initial) (Christopher Swasey)
  • Ticket [154] problem with record.js; parent-child relationship; and tomcat (Bill Klaila)
  • Ticket [178] SC.CollectionController.hasSelection() does not return anything when there is no content (Maurits Lamers, Erich Ocean)
  • Ticket [123] CollectionView doesn’t properly redraw contents if it isn’t visible at the time of a content change (Christopher Swasey)
  • Ticket [137] Split view can collapse when thickness becomes less than a given value (Lawrence Pit)
  • Ticket [162] IE6 Fix (sujoykroy)
  • Ticket [165] String.fmt ordered arguments are broken (Boris Smus, Erich Ocean)
  • Ticket [163] Reimplementation of Array.invoke breaks Scriptaculous (Boris Smus)
  • Ticket [139] record.js/readAttributes() Bug (Evin Grano)
  • Ticket [161] Records with multiple toMany relationships mixup data (Reto Wolf, Erich Ocean)
  • Ticket [138] Segmented buttons are 1 pixel too tall towards the bottom (Lawrence Pit)
  • Ticket [128] Split view doesn’t honor canCollapse property (Lawrence Pit)
  • Ticket [129] Split view doesn’t honor min_thickness property (Lawrence Pit)