Posts Tagged ‘sproutcore’

SC.$ doesn’t support complex selectors

  July 12th, 2010

SC.$ doesn’t support complex selectors for performance reasons. For example, the following queries will return an empty or incorrect result set:

SC.$('.foo .bar:first-child')
SC.$(':empty')
SC.$('foo > bar')

If you don’t need your application to be compatible with IE 7 and Firefox 3.0, you can use the querySelectorAll method to overcome that limitation:

MyApp.$ = function(str) {
  return SC.$(document.querySelectorAll(str));
}

Testing and auto-generated ids in SproutCore

  January 27th, 2010

If you’re trying to automate tests of your SproutCore application with Selenium, for example, you’ll realise that the HTML element ids are automatically generated. They change every now and then and break all your tests.

To get rid of this problem, you can override the layerId method of certain view classes to generate a stable, and human readable, id.

The following code takes the view hierarchy, and generates an id based on the parents’ names. For example, the mainPage.mainView.someOtherView.theTargetView view will be given the mmsome.theTargetView id.

CT._ct_layerId = function() {
  var k1, k2, k3, k4, guid;

  if (!this._ct_layerId_cache) {
    guid = SC.guidFor(this);

    // Compute the new value
    k1 = this.get('keyForParentView');

    // If k1 is null, defer the computation to later...
    if (!k1) return SC.guidFor(this);

    // Finish the computation
    k2 = this.getPath('parentView.keyForParentView') || '';
    k3 = this.getPath('parentView.parentView.keyForParentView') || '';
    k4 = this.getPath('parentView.parentView.parentView.keyForParentView') || '';

    // Cache the result
    this._ct_layerId_cache = k4.substr(0,1) + k3.substr(0,1) + k2.substr(0,4) + '.' + k1;

    // If the id is already taken, fallback to guid
    if (SC.View.views[this._ct_layerId_cache]) {
      this._ct_layerId_cache = guid;

    // else, update the SC.View.views hash with the new value
    } else {
      delete SC.View.views[guid];
      SC.View.views[this._ct_layerId_cache] = this;
    }
  }

  return this._ct_layerId_cache;
}.property();

SC.View.prototype.mixin({

  keyForParentView: function() {
    var parentView = this.get('parentView'),
        key;
    for (key in parentView) {
      if (this === parentView[key]) return key;
    }
    return null;
  }.property()

});

SC.TextFieldView.prototype.mixin({
  layerId: CT._ct_layerId
});

SC.LabelView.prototype.mixin({
  layerId: CT._ct_layerId
});

SC.ImageView.prototype.mixin({
  layerId: CT._ct_layerId
});

Monkey patching SproutCore

  November 16th, 2009

You can add or overwrite a class property using the mixin method on the class prototype. The following example dynamically adds a keyForParentView method on the SC.View class without modifying SproutCore’s source code:

SC.View.prototype.mixin({
  keyForParentView: function() {
    var parentView = this.get('parentView'),
        key;
    for (key in parentView) {
      if (this === parentView[key]) return key;
    }
    return null;
  }
});

SC.Response and JSON

  November 13th, 2009

SproutCore’s SC.Response was recently rewritten and I patched it to avoid an exception to be thrown when a malformed JSON string was parsed. The standard way to write your data source didFetch method is now:

didFetch: function(response, params) {
  var results, query = params.query;
  if (SC.ok(response) && SC.ok(results = response.get('body'))) {
    ...
  } else {
    store.dataSourceDidErrorQuery(query);
  }
}

Alternate row colors in SC.ListViews

  September 1st, 2009

This post will show you how to create a SproutCore ListView with alternate row colors.

Let’s add alternate row colors to the Todos tutorial’s list view. Clone the source code, then switch to the step-5 branch. It should look like this:

Todos, Step 5

The code that’s actually responsible for the ListView is in main_page.js:46:

middleView: SC.ScrollView.design({
  hasHorizontalScroller: NO,
  layout: { top: 36, bottom: 32, left: 0, right: 0 },
  backgroundColor: 'white',
  contentView: SC.ListView.design({
    contentBinding: 'Todos.tasksController.arrangedObjects',
    selectionBinding: 'Todos.tasksController.selection',
    contentValueKey: "description",
    contentCheckboxKey: "isDone",
    canEditContent: YES,
    canReorderContent: YES,
    canDeleteContent: YES,
    destroyOnRemoval: YES,
    rowHeight: 21
  })
}),

This creates a standard list view whose rows are instances of the SC.ListItemView class. We want to customize the appearance of these rows, so we are going to subclass the SC.ListItemView class and override the render method:

Todos.ListItemView = SC.ListItemView.extend({
  render: function(context, firstTime) {
    if (this.get('contentIndex') % 2 === 0) {
      context.addClass('even');
    } else {
      context.addClass('odd');
    }
    return sc_super();
  }
});

Let go through this code snippet step by step:

  • Todos.ListItemView is the subclass’ name
  • SC.ListItemView.extend is the SproutCore syntax for subclassing
  • render: function(context, firstTime) is the method defined in view.js that we need to override to customize the view’s appearance
  • now, we need to determine the index of the current row and add an ‘odd’ or ‘even’ css class name to the underlying HTML element
    • by looking at SproutCore’s source code, you’ll notice that a ListViewItem has a contentIndex property, which is exactly the row’s index. The property is obtained by calling the get method.
    • SproutCore renders things using a context object, instance of SC.RenderContext. It has an addClass which adds a css class name to the current HTML element.
  • finally, we need to call the superclass’ implementation. This is done with sc_super().

Now, we need to tell the contentView that it should use the Todos.ListItemView class for rows. This is done by adding the following line to the contentView’s parameters:

exampleView: Todos.ListItemView,

Finally, we need to add some css code to style the odd and even rows. Create a list_item.css file in your english.lproj folder:

.sc-list-item-view.even {
  background-color: #E4E4E4;
}

Reload your browser, it should look like that:

Todos, with alternate row colors

EDIT: As nexneo points out, there is a simpler way to get the current row index. I’ve replaced this.owner.contentIndexForLayerId(this.layerId) by this.get('contentIndex'). Thanks!