blossom
Version:
Modern, Cross-Platform Application Framework
261 lines (206 loc) • 6.79 kB
JavaScript
// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2010 Evin Grano
// Portions ©2008-2011 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/**
@class
A `ChildArray` is used to map an array of `ChildRecord` objects.
@extends SC.Enumerable
@extends SC.Array
@since SproutCore 1.0
*/
SC.ChildArray = SC.Object.extend(SC.Enumerable, SC.Array,
/** @scope SC.ChildArray.prototype */ {
/**
If set, it is the default record `recordType`
@default null
@type String
*/
defaultRecordType: null,
/**
If set, the parent record will be notified whenever the array changes so that
it can change its own state
@default null
@type {SC.Record}
*/
record: null,
/**
If set will be used by the many array to get an editable version of the
`storeId`s from the owner.
@default null
@type String
*/
propertyName: null,
/**
Actual references to the hashes
@default null
@type {SC.Array}
*/
children: null,
/**
The store that owns this record array. All record arrays must have a
store to function properly.
@type SC.Store
@property
*/
store: function() {
return this.getPath('record.store');
}.property('record').cacheable(),
/**
The storeKey for the parent record of this many array. Editing this
array will place the parent record into a `READY_DIRTY state.
@type Number
@property
*/
storeKey: function() {
return this.getPath('record.storeKey');
}.property('record').cacheable(),
/**
Returns the storeIds in read only mode. Avoids modifying the record
unnecessarily.
@type SC.Array
@property
*/
readOnlyChildren: function() {
return this.get('record').readAttribute(this.get('propertyName'));
}.property(),
/**
Returns an editable array of child hashes. Marks the owner records as
modified.
@type {SC.Array}
@property
*/
editableChildren: function() {
var store = this.get('store'),
storeKey = this.get('storeKey'),
pname = this.get('propertyName'),
ret, hash;
ret = store.readEditableProperty(storeKey, pname);
if (!ret) {
hash = store.readEditableDataHash(storeKey);
ret = hash[pname] = [];
}
return ret ;
}.property(),
// ..........................................................
// ARRAY PRIMITIVES
//
/** @private
Returned length is a pass-through to the storeIds array.
@type Number
@property
*/
length: function() {
var children = this.get('readOnlyChildren');
return children ? children.length : 0;
}.property('readOnlyChildren'),
/**
Looks up the store id in the store ids array and materializes a
records.
@param {Number} idx index of the object to retrieve.
@returns {SC.Record} The record if found or undefined.
*/
objectAt: function(idx) {
var recs = this._records,
children = this.get('readOnlyChildren'),
hash, ret, pname = this.get('propertyName'),
parent = this.get('record');
var len = children ? children.length : 0;
if (!children) return undefined; // nothing to do
if (recs && (ret=recs[idx])) return ret ; // cached
if (!recs) this._records = recs = [] ; // create cache
// If not a good index return undefined
if (idx >= len) return undefined;
hash = children.objectAt(idx);
if (!hash) return undefined;
// not in cache, materialize
recs[idx] = ret = parent.registerNestedRecord(hash, pname, pname+'.'+idx);
return ret;
},
/**
Pass through to the underlying array. The passed in objects must be
records, which can be converted to `storeId`s.
@param {Number} idx index of the object to replace.
@param {Number} amt number of records to replace starting at idx.
@param {Number} recs array with records to replace.
@returns {SC.Record} The record if found or undefined.
*/
replace: function(idx, amt, recs) {
var children = this.get('editableChildren'),
len = recs ? (recs.get ? recs.get('length') : recs.length) : 0,
record = this.get('record'), newRecs,
pname = this.get('propertyName'),
cr, recordType;
newRecs = this._processRecordsToHashes(recs);
children.replace(idx, amt, newRecs);
// notify that the record did change...
if (newRecs !== this._prevChildren) this.recordPropertyDidChange();
record.recordDidChange(pname);
return this;
},
/** @private
Converts a records array into an array of hashes.
@param {SC.Array} recs records to be converted to hashes.
@returns {SC.Array} array of hashes.
*/
_processRecordsToHashes: function(recs){
var store, sk;
recs = recs || [];
recs.forEach( function(me, idx){
if (me.isNestedRecord){
store = me.get('store');
sk = me.storeKey;
recs[idx] = store.readDataHash(sk);
}
});
return recs;
},
/**
Calls normalize on each object in the array
*/
normalize: function(){
this.forEach(function(child,id){
if(child.normalize) child.normalize();
});
},
// ..........................................................
// INTERNAL SUPPORT
//
/**
Invoked whenever the children array changes. Observes changes.
@param {SC.Array} keys optional
@returns {SC.ChildArray} itself.
*/
recordPropertyDidChange: function(keys) {
if (keys && !keys.contains(this.get('propertyName'))) return this;
var children = this.get('readOnlyChildren');
var prev = this._prevChildren, f = this._childrenContentDidChange;
if (children === prev) return this; // nothing to do
if (prev) prev.removeObserver('[]', this, f);
this._prevChildren = children;
if (children) children.addObserver('[]', this, f);
var rev = (children) ? children.propertyRevision : -1 ;
this._childrenContentDidChange(children, '[]', children, rev);
return this;
},
/** @private
Invoked whenever the content of the children array changes. This will
dump any cached record lookup and then notify that the enumerable content
has changed.
@param {Number} target
@param {Number} key
@param {Number} value
@param {Number} rev
*/
_childrenContentDidChange: function(target, key, value, rev) {
this._records = null ; // clear cache
this.enumerableContentDidChange();
},
/** @private */
init: function() {
arguments.callee.base.apply(this, arguments);
this.recordPropertyDidChange();
}
}) ;