public
Description: JavaScript Application Framework - JS library only
Home | Edit | New

Computed Property Code Dissection

A while back, Evin Grano (etgryphon) posted a new article on SproutCore’s wiki on “Many-to-Many Relationship: Relationship Object Method”. You can find the article here .

In the original article, Evin included some “computed getters”, which illustrated a number of interesting SproutCore API issues. With Evin’s permission, I’m going to discuss his code here for the benefit of all.

First, the code:

    myApp.CategoryProduct= SC.Record.extend({

      // TODO: Add your own code here.
      properties: ['guid', 'product', 'category'],
      productType: 'myApp.Product',
      categoryType: 'myApp.Category'

      productName: function() {
        var myProdName = this.get('product').get('name');

        return myProdName ? myProdName : 'Unassigned';
      }.property('product')

    }) ;

NOTE: I have removed an identical computed property called categoryName. All comments below apply to that as well.

So, the intent of the productName accessor is to provide easy access to a myApp.CategoryProduct object’s related product’s name, with a default if that can’t be found for some reason. There are at least four issues with this code.

1. The first line could fail with a runtime exception.

If this.get('product') returns null or undefined, the next .get('name') will fail. Instead, you should write:

    // safely handles property traversal
    var myProdNam = this.getPath('product.name');

2. The observer specification is incorrect.

Updating a product’s name will not trigger a change to the productName property of myApp.CategoryProduct, because the product relationship itself, and not the actual name property of the product, is being observed. Instead, you should write:

    productName: function() {
      var myProdNam = this.getPath('product.name');

      return myProdName ? myProdName : 'Unassigned';
    }.property('product.name')

3. SproutCore already supports property paths via getPath().

The whole idea behind productName is unnecessary in SproutCore. Instead of:

    // don't create/use collapsed getters for property path traversal
    var catProd = myApp.CategoryProduct.create();
    console.log( catProd.get('productName') );
Use this:

    // do use getPath()
    var catProd = myApp.CategoryProduct.create();
    console.log( catProd.getPath('product.name') );

To get the 'Unassigned' default returned, define a computed getter on myApp.Product that returns 'Unassigned' when the product’s name property is null or undefined. Unfortunately, even that’s not a good idea, because it violates MVC.

4. Don’t put default or placeholder values for the view in the model.

'Unassigned' is meant to be used by the view. Thus, it should be set in the view, not in the model. SproutCore bindings can easily substitute any user-defined value when a null property is returned for a binding. Thus, neither the productName computed getter on myApp.CategoryProduct, nor the default-returning computed getter for name we could define on myApp.Product should be written. The code is 100% unnecessary and inappropriate, as far as proper MVC architecture is concerned.

Instead, suppose a I had a myApp.CategoryProduct object set as the content object for some object controller, and I had a view bound to that object controller. Here’s what the correct binding would look like:


  // using the formatter property of a label view
  myView: SC.LabelView.create({
    valueBinding: 'myCategoreProjectObjectController.product.name',

    formatter: function( value, view ) {
      return ( !value ) ? 'Unassigned' : value ; 
    }

  })

  // OR using the binding property alone (with a custom view, for kicks)
  myView: myApp.MyCustomView.create({

    // assume myApp.MyCustomView access it's "content" property for rendering
    contentBinding: SC.Binding.extend({
      from: 'myCategoreProjectObjectController.product.name',

      // set this to some value if you want a placeholder value when content is null.
      nullPlaceholder: 'Unassigned'
    })

  })

Conclusion

A big thanks to Evin for letting me dissect his code. What Evin wrote is perfectly reasonable in a lot of environments. SproutCore’s API is much more powerful than the simple demo apps and tutorials would indicate, and using it correctly results in less code, with more power.

Last edited by etgryphon, Tue Sep 16 14:56:29 -0700 2008
Home | Edit | New
Versions: