• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

Runtime-Using SCArray

Page history last edited by Maurits Lamers 13 years, 8 months ago

The SC.Enumerable mixin provides a generic API for working with any kind of collection, including both ordered and unordered collections.  SC.Array builds on SC.Enumerable to add support for ordered collections of objects such as arrays.

 

Using the SC.Array API

 

All native Array objects automatically support the SC.Array mixin.  SproutCore also defines a number of other "array-like" objects that support SC.Array including SC.SparseArray, SC.ArrayController, SC.RecordArray and more.  In general you can treat any of these objects as arrays as long as you use on the the SC.Array method.

 

All objects that use the SC.Array mixin must also include the SC.Enumerable mixin.  This means any of the methods described in SC.Enumerable will work for SC.Array objects as well.  In addition to these methods, you get the following with SC.Array:

 

Getting Items: objectAt()

 

To get an object at a specific index in an array use the objectAt() method.  This is the SC.Array equivalent of using array[0] with a regular array:

 

var foo = myArray.objectAt(10);  // is the equivalent to...

var foo = myArray[10]

 

Like a regular array, objectAt() will return a value for any index less than the object's length property.  Any value greater will return undefined.

 

Setting Items: replace()

 

SC.Array defines a number of methods to modify an array.  All of them boil down to one primitive method called replace().  To use this method, you should pass the starting index you want to replace, the number of items you want to replace, and an array of items you want to have inserted at the location (or null if you just want to delete some items):

 

// delete indexes 2,3

[0,1,2,3,4].replace(2,2,null) 

=> [0,1,4]

 

// insert at index 2

[0,1,2,3,4].replace(2,0,[6,7])

=> [0,1,6,7,2,3,4]

 

// replace indexes 2,3 with new items

[0,1,2,3,4].replace(2,2,[6,7])

=> [0,1,6,7,4]

 

Replace is generally equivalent to using the array[0]=foo syntax for primitive arrays when called with a single index:

 

myArray.replace(2,1,['foo']); // is equivalent to...

myArray[2] = 'foo';

 

Used effectively, replace() can be a very efficient way to update an SC.Array object.  Usually, however, you will not call replace() directly.  Instead you will use one of the many mutation helpers defined by SC.Array 

 

Mutation Helpers

 

SC.Array defines many mutation helpers that implement common patterns you might use for replace().  For most arrays, these will boil down to calls to replace.  It is useful to know these helpers so you can use them in your own code:

 

  • insertAt(idx, item) - inserts item at the named index.  same as myArray.replace(idx, 0, [item])
  • removeAt(idx) - removes item at the named index.  same as myArray.replace(idx, 0, null);
  • removeObject(obj)/removeObjects([objects...]) - finds the first index of the named object or objects and removes it from the array.   same as myArray.replace(myArray.indexOf(obj), 0, null);  
  • pushObject(obj)/pushObjects([obj...]) - adds the named objects onto the end of the array.  same as myArray.replace(myArray.get('length'), 0, [obj]);
  • unshiftObject(obj)/unshiftObjects([objects...]) - adds the named objects onto the beginning of the array.  same as myArray.replace(0,0,[obj])
  • popObject() - removes and returns the last object in the array or undefined if array is empty.  
  • shiftObject() - removes and returns the first object in the array or undefined if array is empty.

 

Detecting Membership

 

In addition to modifying and retrieving items, you can also search an SC.Array to determine if a given object belongs to the array (and if so where).  To detect membership in an array, use the indexOf() and lastIndexOf() methods.  These are the same methods implemented on primitive Arrays; they will also be available on any other object implementing SC.Array. 

 

See the MDC descriptions of indexOf() and lastIndexOf() for more information.

 

Transforms

 

Finally, SC.Array defines a few useful transform methods that can make it easier to work with.  These methods are implemented and optimized even for primitive arrays, so you can use them everywhere:

 

  • without(a,b,c) - returns a new Array with the same contents excluding any items you pass into the method.
  • compact() - returns a new Array with any null items removed
  • uniq() - returns a new Array with any duplicate objects removed

 

Basic SC.Array Observing 

 

SC.Array based operations are observable, just like other SC.Observable objects.  It's important to understand, however, that array and enumerables only keep track of membership  not the state of objects that belong to the set.  For example:

 

var array = [];

var objectA = SC.Object.create({ name: "a" });

 

array.addObserver(0, function() { ... });

array.insertAt(0, objectA);  // notifies observer

objectA.set('name', 'foo'); // does NOT notify observer

 

Understanding these limitations, you can observe a number of different properties to receive updates:

 

Observing Individual Indexes

 

As shown in the example code above, array indexes are usually treated as regular properties by an array as well.  This means you can observe individual indexes if you ever need to just like any other property.

 

Observing Enumerable Changes

 

If you just want to know when the membership of an array has changed without worrying about the specific changes, you can also observe the enumerable property: "[]"

 

array.addObserver('[]', function() { ... });

array.insertAt(0, objectA); // notifies observer

array.insertAt(1, objectA); // notifies observer

 

Range Observers

 

Many times, you may simply want to observe a specific range of indices in the array.  For this purpose you can setup a range observer.   To add a range observer, simply use the addRangeObserver() method.  This method works just like addObserver() except the first param must be an IndexSet that describes the indexes you want to observe:

 

var ro = array.addRangeObserver(SC.IndexSet.create(0,3), someObject, someObject.rangeDidChange);

 

This will invoke the someObject.rangeDidChange() method anytime any item for indexes 0,1,2 are changed.

 

Since RangeObservers are based on an IndexSet, it is also possible to create a single range observer for multiple, discontiguous ranges.  For example:

 

// call someObject.rangeDidChange if index 1, 3, or 5 changes

var set = SC.IndexSet.create().add(0,1).add(3,1).add(5,1);

var ro = array.addRangeObserver(set, someObject, someObject.rangeDidChange);

 

Note that the unlike addObserver(), addRangeObserver() returns a RangeObserver object.  You should keep a reference to this object to update or remove the range observer at a later time.

 

Updating Range Observers

 

Note that range observers track locations within an array; not objects.  If you insert or remove objects before a range observer, the observer will still observe the same indicies.  If you want to change the indexes a range observer notifies, just call the updateRangeObserver method.  Pass in the original RangeObserver object returned from addRangeObserver().

 

// setup a range observer - observes indexes 0-2

var ro = array.addRangeObserver(SC.IndexSet.create(0,3), someObject, someObject.rangeDidChange);

 

// change to observe indexes 5-10

array.updateRangeObserver(ro, SC.IndexSet.create(5,5));

 

Removing a Range Observer

 

If you no longer want a range observer to fire, just call removeRangeObserver().  Pass in the original RangeObserver object returned from addRangeObserver():

 

// setup a range observer - observes indexes 0-2

var ro = array.addRangeObserver(SC.IndexSet.create(0,3), someObject, someObject.rangeDidChange);

 

// remove the observer

array.removeRangeObserver(ro);

 
Putting It All Together
 
TODO: Example using all three methods...

 

Array vs SC.Array

 

JavaScript comes with a built-in native Array class that provides basic support for ordered objects.  Arrays are relatively fast in JavaScript but they are also limited.  Because you use the built-in bracket notation (i.e. array[0]), native arrays can't be modified to notify observers or perform "lazy" resolution such as generating array contents on the fly.

 

This is the reason for the SC.Array mixin.  It implements an alternative API for accessing array contents that can be modified to support observers and other optimizations.  Instead of using array[0] you use array.objectAt(0).  Instead of modifying an array with array[0] = 'foo' you use array.replace(0,0,[foo]) or related methods.

 

The SC.Array mixin is applied automatically to the built-in Array class when you load SproutCore.  This means you can actually work with native Arrays using their built-in bracket notation or using the new SC.Array API.  In general, you should always interact with a particular array using one API or the other.  

 

We recommend that you use the SC.Array API whenever you work with arrays that may be passed into other code or used as a property on an observable object.  Use the native Array API when working with private variables where property observing will never come into play and performance is paramount.

 

Creating Your Own Array

 

Thanks to SC.Array's flexible API, it is actually very easy to create your own Array-like objects.  You can use these objects any place you might use an array as long as the code always calls the SC.Array methods instead of native Array method.s

 

To implement SC.Array in your own object, you only need to implement two methods and one property:  objectAt(), replace(), and length.  If you want your object to be read-only, you don't even need to implement replace().  

 

The example below implements SC.Array using an internal array as storage as an example:

 

var MyArray = SC.Object.extend(SC.Enumerable, SC.Array, {

 

  // length property should always reflect the internal array length.

  // it can be cached since replace() will call enumerableDidChange

  // to recache it

  length: function() {

    return this._array ? this._array.length : 0;

  }.property().cacheable(),

 

  // return an object at the named index

  objectAt: function(idx) {

    return this._array ? this._array[idx] : undefined;

  },

 

  // modify the array.  note that we defer creating internal array

  // until this is called to keep memory usage down.  Also, be

  // sure to call enumerableContentDidChange() to notify.

  replace: function(start, amt, items) {

    var array = this._array;

    if (!array) array = this._array = [];

    array.replace(start, amt, items);  // pass through

 

    // compute the delta change...remember items may be null

    var len = items ? items.length : 0 ;

    var delta = len - amt;

    

    // notify observers..

    this.enumerableContentDidChange(start, amt, delta);

    return this;

  }

});

 

Note that while these two methods are the minimum required to implement SC.Array, you can also override the other methods provided by SC.Array to make them more efficient if needed.  In particular, you may often want to implement indexOf() and lastIndexOf() to more efficiently determine the index of a given item.  The default implementation iterates through the array until it finds a result.

 

Moving On

 

Learn about the built-in unordered enumerable SC.Set and relatives »

or Back to Runtime Programming Guide Home »

 

Comments (0)

You don't have permission to comment on this page.