/* <copyright>
This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
(c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved.
</copyright> */
/**
@module "montage/ui/repetition.reel"
@requires montage/core/core
@requires montage/ui/component
@requires montage/ui/template
@requires montage/core/logger
@requires montage/core/gate
*/
var Montage = require("montage").Montage,
Component = require("ui/component").Component,
Template = require("ui/template").Template,
logger = require("core/logger").logger("repetition"),
Gate = require("core/gate").Gate,
ChangeNotification = require("core/change-notification").ChangeNotification,
PropertyChangeNotification = require("core/change-notification").PropertyChangeNotification;
var FakeObjects = Montage.create(Object.prototype, {
_repetition: {value: null},
_fakeIndex: {value: null},
_unusedIndexes: {value: null},
initWithRepetition: {
value: function(repetition) {
this._repetition = repetition;
this._fakeIndex = [];
this._unusedIndexes = [];
return this;
}
},
automaticallyDispatchPropertyChangeListener: {
value: function() {
return false;
}
},
undefinedGet: {
value: function(propertyName) {
if (this._repetition.objects) {
return this._repetition.objects[this._fakeIndex.indexOf(propertyName)];
}
}
},
// This is to catch a two-way binding on
"0": {
// This is to catch two way bindings
set: function() {
throw("You cannot use a two-way binding on the \"objectAtCurrentIteration\" or \"current\" property.");
},
get: function() {
if (this._repetition.objects) {
return this._repetition.objects[this._fakeIndex.indexOf("0")];
}
}
},
addFakeObjectAtPosition: {
value: function(position) {
var index;
if (this._unusedIndexes.length > 0) {
index = this._unusedIndexes.pop();
} else {
index = String(this._fakeIndex.length);
}
this._fakeIndex.splice(position, 0, index);
return index;
}
},
resetFakeObjects: {
value: function() {
var objects = this._repetition.objects;
this._fakeIndex.length = 0;
if (objects) {
for (var i = 0, l = objects.length; i < l; i++) {
this._fakeIndex[i] = String(i);
}
}
}
},
removeFakeObjectAtPosition: {
value: function(position) {
var index;
this._unusedIndexes.unshift(this._fakeIndex.splice(position, 1)[0]);
return this._unusedIndexes[0];
}
},
_dispatchFakePropertyChange: {
value: function(propertyName, minus) {
var descriptor,
notification;
descriptor = ChangeNotification.getPropertyChangeDescriptor(this, propertyName);
if (descriptor) {
notification = Object.create(PropertyChangeNotification);
notification.target = this;
notification.propertyPath = propertyName;
notification.minus = minus;
notification.plus = this.undefinedGet(propertyName);
if (minus !== notification.plus) {
descriptor.handleChange(notification);
}
}
}
}
});
/**
@class module:"montage/ui/repetition.reel".Repetition
@extends module:montage/ui/component.Component
*/
var Repetition = exports.Repetition = Montage.create(Component, /** @lends module:"montage/ui/repetition.reel".Repetition# */{
/**
Description TODO
*/
hasTemplate: {value: false},
didCreate: {
value: function() {
this.addPropertyChangeListener("objects", this);
this._fakeObjects = Object.create(FakeObjects).initWithRepetition(this);
}
},
_emptyFunction: {value: function(){}},
_updateItems: {
value: function(minus, plus, index) {
var fakeObjects = this._fakeObjects,
fakeIndex,
minusCount = minus ? minus.length : 0,
plusCount = plus ? plus.length : 0,
max, min, delta;
max = Math.max(minusCount, plusCount);
min = Math.min(minusCount, plusCount);
delta = plusCount - minusCount;
//console.log("Going to change " + min + " iterations", fakeObjects._fakeIndex);
// send updates for the elements that were just replaced by new ones
for (var i = 0; i < min; i++) {
//console.log("Going to change " + (index+i), minus[index+i]);
fakeObjects._dispatchFakePropertyChange(fakeObjects._fakeIndex[index+i], minus[index+i]);
}
// add new objects, no need to send updates on this one, they're new!
if (delta > 0) {
//console.log("Going to add " + (max-i) + " iterations");
this._expectedChildComponentsCount += (this._iterationChildComponentsCount||1) * delta;
this.canDrawGate.setField("iterationLoaded", false);
for (; i < max; i++) {
//console.log("New item " + (index+i) + " " + plus[index+i].uuid);
fakeObjects.addFakeObjectAtPosition(index + i);
this._addItem({index: index + i, insertionIndex: index + i});
}
} else if (delta < 0) { // remove elements and send updates
//console.log("Going to remove " + (max-i) + " iterations");
// this index is fixed because we're changing the array at each iteration of the for loop
removeIndex = index + min;
for (; i < max; i++) {
//console.log("Going to remove " + (index+i), minus[i], min);
fakeIndex = fakeObjects.removeFakeObjectAtPosition(removeIndex);
fakeObjects._dispatchFakePropertyChange(fakeIndex, minus[i]);
this._deleteItem(removeIndex);
}
}
}
},
handleChange: {
enumerable: false,
value: function(notification) {
if ("objects" === notification.currentPropertyPath && this._isComponentExpanded) {
this._updateItems(notification.minus, notification.plus, notification.index || 0);
}
}
},
_fakeObjects: {
value: null
},
/**
@private
*/
_hasBeenDeserialized: {
value: false,
enumerable: false
},
/**
Description TODO
@private
*/
_nextDeserializedItemIx: {
enumerable: false,
value: 0,
distinct: true
},
/**
Description TODO
@function
@returns itself
*/
init: {
enumerable: false,
value: function() {
this._items = [];
this._itemsToAppend = [];
this._nextDeserializedItemIx = 0;
this._itemsToRemove = [];
this._deletedItems = [];
return this;
}
},
/**
Description TODO
@private
*/
_contentController: {
value: null
},
/**
The collection of items managed the Repetition.
@type {Function}
@default null
*/
contentController: {
get: function() {
return this._contentController;
},
set: function(value) {
if (this._contentController === value) {
return;
}
if (this._contentController) {
Object.deleteBinding(this, "o
|