Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
248
gaia/apps/settings/js/modules/mvvm/list_view.js
Executable file
248
gaia/apps/settings/js/modules/mvvm/list_view.js
Executable file
|
|
@ -0,0 +1,248 @@
|
|||
'use strict';
|
||||
|
||||
define(function(require) {
|
||||
var elements = new WeakMap();
|
||||
var ObservableArray = require('modules/mvvm/observable_array');
|
||||
|
||||
/*
|
||||
* A ListView takes an ObservableArray or an ordinary array, and generate/
|
||||
* manipulate the corresponding DOM elements of the content in the array using
|
||||
* the given template function. If the array is an ObservableArray, ListView
|
||||
* updates the DOM elements accordingly when the array is manipulated.
|
||||
*/
|
||||
var ListView = function(root, observableArray, templateFunc) {
|
||||
var _observableArray = null;
|
||||
var _root = root;
|
||||
var _templateFunc = templateFunc;
|
||||
var _enabled = true;
|
||||
|
||||
// keep track of observeables so we can unobserve
|
||||
var observerMap = new WeakMap();
|
||||
|
||||
function releaseObservers(element) {
|
||||
var observable = observerMap.get(element);
|
||||
if (!observable || typeof observable !== 'object') {
|
||||
return;
|
||||
}
|
||||
var observers = observerMap.get(observable);
|
||||
if (observers) {
|
||||
observers.forEach(function eachObserver(observer) {
|
||||
observable.unobserve(observer);
|
||||
});
|
||||
observerMap.delete(observable);
|
||||
observerMap.delete(element);
|
||||
}
|
||||
}
|
||||
|
||||
// helper to observe something and automatically remove old observers
|
||||
function observeAndCall(observable, map) {
|
||||
var observers = observerMap.get(observable) || [];
|
||||
if (observerMap.has(observable)) {
|
||||
observers = observerMap.get(observable);
|
||||
}
|
||||
observers = Object.keys(map).map(function eachProperty(property) {
|
||||
observable.observe(property, map[property]);
|
||||
map[property]();
|
||||
return map[property];
|
||||
});
|
||||
observerMap.set(observable, observers);
|
||||
}
|
||||
|
||||
var _helper = {
|
||||
observeAndCall: observeAndCall
|
||||
};
|
||||
|
||||
var _handleEvent = function(event) {
|
||||
// Ignore the change event when the list view is not enabled.
|
||||
if (!_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var data = event.detail;
|
||||
switch (event.type) {
|
||||
case 'insert':
|
||||
_insert(data.index, data.items);
|
||||
break;
|
||||
case 'remove':
|
||||
_remove(data.index, data.count);
|
||||
break;
|
||||
case 'replace':
|
||||
_replace(data.index, data.newValue);
|
||||
break;
|
||||
case 'reset':
|
||||
_reset(data.items || []);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var _insert = function(index, items) {
|
||||
if (items.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add DOM elements
|
||||
var referenceElement = _root.children[index];
|
||||
for (var i = items.length - 1; i >= 0; i--) {
|
||||
var curElement = _templateFunc(items[i], undefined, _helper);
|
||||
observerMap.set(curElement, items[i]);
|
||||
_root.insertBefore(curElement, referenceElement);
|
||||
referenceElement = curElement;
|
||||
}
|
||||
};
|
||||
|
||||
var _remove = function(index, count) {
|
||||
if (count === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove DOM elements
|
||||
if (count >= _root.childElementCount) {
|
||||
// clear all
|
||||
while (_root.firstElementChild) {
|
||||
releaseObservers(_root.firstElementChild);
|
||||
_root.removeChild(_root.firstElementChild);
|
||||
}
|
||||
} else {
|
||||
var nextElement = _root.children[index];
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (nextElement) {
|
||||
var temp = nextElement.nextElementSibling;
|
||||
releaseObservers(nextElement);
|
||||
_root.removeChild(nextElement);
|
||||
nextElement = temp;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var _replace = function(index, value) {
|
||||
var element = _root.children[index];
|
||||
if (element) {
|
||||
releaseObservers(element);
|
||||
var newElement = _templateFunc(value, element, _helper);
|
||||
observerMap.set(element, value);
|
||||
if (newElement !== element) {
|
||||
_root.insertBefore(newElement, element);
|
||||
_root.removeChild(element);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var _reset = function(items) {
|
||||
var itemCount = items.length;
|
||||
var elementCount = _root.childElementCount;
|
||||
|
||||
if (itemCount === 0) {
|
||||
_remove(0, elementCount);
|
||||
} else if (itemCount <= elementCount) {
|
||||
items.forEach(function(item, index) {
|
||||
_replace(index, item);
|
||||
});
|
||||
// remove extra elements
|
||||
_remove(itemCount, elementCount - itemCount);
|
||||
} else {
|
||||
var slicedItems = items.slice(0, elementCount);
|
||||
var remainingItems = items.slice(elementCount);
|
||||
|
||||
slicedItems.forEach(function(item, index) {
|
||||
_replace(index, item);
|
||||
});
|
||||
// add extra elements
|
||||
_insert(elementCount, remainingItems);
|
||||
}
|
||||
};
|
||||
|
||||
var _enabledChanged = function() {
|
||||
if (_enabled) {
|
||||
_reset(_observableArray.array);
|
||||
}
|
||||
};
|
||||
|
||||
var view = {
|
||||
set: function lv_set(newArray) {
|
||||
if (_observableArray) {
|
||||
_observableArray.removeEventListener('insert', _handleEvent);
|
||||
_observableArray.removeEventListener('remove', _handleEvent);
|
||||
_observableArray.removeEventListener('replace', _handleEvent);
|
||||
_observableArray.removeEventListener('reset', _handleEvent);
|
||||
}
|
||||
|
||||
if (!newArray) {
|
||||
// clear all existing items
|
||||
if (_observableArray) {
|
||||
_remove(0, _observableArray.length);
|
||||
_observableArray = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If new array is an ordinary array, convert it to an ObservableArray.
|
||||
if (Array.isArray(newArray)) {
|
||||
newArray = ObservableArray(newArray);
|
||||
}
|
||||
_observableArray = newArray;
|
||||
|
||||
// An ObservableArray fires the four events for notifying its changes.
|
||||
// Register to these events for updating UI.
|
||||
_observableArray.addEventListener('insert', _handleEvent);
|
||||
_observableArray.addEventListener('remove', _handleEvent);
|
||||
_observableArray.addEventListener('replace', _handleEvent);
|
||||
_observableArray.addEventListener('reset', _handleEvent);
|
||||
|
||||
if (this.enabled) {
|
||||
_reset(_observableArray.array);
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
// unobserve from array and null everything out
|
||||
if (_observableArray) {
|
||||
_observableArray.removeEventListener('insert', _handleEvent);
|
||||
_observableArray.removeEventListener('remove', _handleEvent);
|
||||
_observableArray.removeEventListener('replace', _handleEvent);
|
||||
_observableArray.removeEventListener('reset', _handleEvent);
|
||||
}
|
||||
_remove(0, _observableArray.length);
|
||||
elements.delete(_root);
|
||||
_root = null;
|
||||
_observableArray = null;
|
||||
_templateFunc = null;
|
||||
_enabled = false;
|
||||
},
|
||||
|
||||
get element() {
|
||||
return _root;
|
||||
},
|
||||
|
||||
set enabled(value) {
|
||||
if (_enabled !== value) {
|
||||
_enabled = value;
|
||||
_enabledChanged();
|
||||
}
|
||||
},
|
||||
|
||||
get enabled() {
|
||||
return _enabled;
|
||||
}
|
||||
};
|
||||
|
||||
if (elements.has(_root)) {
|
||||
// destroy old ListView if we setup a second one
|
||||
elements.get(_root).destroy();
|
||||
}
|
||||
|
||||
elements.set(_root, view);
|
||||
|
||||
// emtpy element at creation time
|
||||
_root.innerHTML = '';
|
||||
|
||||
view.set(observableArray);
|
||||
return view;
|
||||
};
|
||||
|
||||
return ListView;
|
||||
});
|
||||
360
gaia/apps/settings/js/modules/mvvm/observable.js
Executable file
360
gaia/apps/settings/js/modules/mvvm/observable.js
Executable file
|
|
@ -0,0 +1,360 @@
|
|||
/**
|
||||
* Observable provides ways of defining properties that the value changes of
|
||||
* them can be observed. In addition to normal properties, it allows to
|
||||
* create read-only properties and dependency properties.
|
||||
*
|
||||
* Observable creation:
|
||||
* There are two ways of defining an Observable: object literal or extending
|
||||
* from Observable. Object literal is useful when defining a singleton or you
|
||||
* want to create an observable easily.
|
||||
*
|
||||
* @example
|
||||
* var observable = Observable({
|
||||
* prop: 10,
|
||||
* func: function() {}
|
||||
* });
|
||||
*
|
||||
* Extending from Observable allow you to define a class of Observable. The
|
||||
* advantage of this compared to object literal is that the accessers are
|
||||
* shared across all instances of the class. The syntax compatible with the
|
||||
* javascript prototype definition.
|
||||
*
|
||||
* @example
|
||||
* var ExtendedObservable = Module.create(function ExtendedObservable() {
|
||||
* this.super(Observable).call(this);
|
||||
* }).extend(Observable);
|
||||
* Observable.defineObservableProperty(ExtendedObservable.prototype, 'prop',
|
||||
* {
|
||||
* value: 10
|
||||
* });
|
||||
* ExtendedObservable.prototype.func = function() {};
|
||||
*
|
||||
* // Extend from NewObservable
|
||||
* var ExtendedObservable2 = Module.create(function() {
|
||||
* // constructor
|
||||
* }).extend(ExtendedObservable);
|
||||
* Observable.defineObservableProperty(
|
||||
* ExtendedObservable2.prototype, 'prop2',
|
||||
* {
|
||||
* value: 20
|
||||
* });
|
||||
*
|
||||
* var observable = ExtendedObservable2();
|
||||
* console.log(observable.prop); // 10
|
||||
* console.log(observable.prop2); // 20
|
||||
*
|
||||
* Defining a read-only property:
|
||||
* This is only supported when extending from Observable. When you define a
|
||||
* read-only property, an internal property with a '_' prefix is defined at
|
||||
* the same time so you can still change the value inside the observable.
|
||||
*
|
||||
* @example
|
||||
* Observable.defineObservableProperty(ExtendedObservable.prototype, 'prop',
|
||||
* {
|
||||
* readonly: true,
|
||||
* value: 10
|
||||
* });
|
||||
* ExtendedObservable.prototype.inc = function() {
|
||||
* this._prop = 100;
|
||||
* };
|
||||
* var observable = new ExtendedObservable();
|
||||
* observable.prop = 100; // throws an exception
|
||||
* observable.inc(); // observers on "prop" are called
|
||||
*
|
||||
* Defining a dependency property:
|
||||
* This is only supported when extending from Observable. You provide a list
|
||||
* of the dependent properties and when each of them changes, the observsers
|
||||
* on the defined property are called. Dependency properties are read-only and
|
||||
* their values are determined by the specified getter.
|
||||
*
|
||||
* @example
|
||||
* Observable.defineObservableProperty(ExtendedObservable.prototype, 'prop',
|
||||
* {
|
||||
* value: 10
|
||||
* });
|
||||
* Observable.defineObservableProperty(
|
||||
* ExtendedObservable.prototype, 'prop2',
|
||||
* {
|
||||
* value: 20
|
||||
* });
|
||||
* Observable.defineObservableProperty(
|
||||
* ExtendedObservable.prototype, 'dependencyProp',
|
||||
* {
|
||||
* dependency: ['prop', 'prop2'],
|
||||
* get: function() {
|
||||
* return this.prop + this.prop2;
|
||||
* }
|
||||
* });
|
||||
* var observable = new ExtendedObservable();
|
||||
* observable.prop = 100; // observers on "dependencyProp" are called
|
||||
* observable.prop2 = 200; // observers on "dependencyProp" are called
|
||||
* // observable.dependencyProp is 300.
|
||||
*
|
||||
* @module modules/mvvm/observable
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Module = require('modules/base/module');
|
||||
var DependencyGraph = require('modules/base/dependency_graph');
|
||||
|
||||
var OP_PREFIX = (name) => { return '$OP_' + name; };
|
||||
|
||||
/**
|
||||
* @class Observable
|
||||
* @requires module:modules/base/module
|
||||
* @requires module:modules/base/dependency_graph
|
||||
* @returns {Observable}
|
||||
*/
|
||||
var Observable = Module.create(function Observable(object) {
|
||||
this._observers = {};
|
||||
if (object) {
|
||||
this._initWithObject(object);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialize the observable with a prototype object. This is not required
|
||||
* for objects that are defined using prototype as everything should be
|
||||
* defined via Observable.defineObservableProperty explicitly.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf Observable.prototype
|
||||
* @param {Object} object
|
||||
*/
|
||||
Observable.prototype._initWithObject = function o_init(object) {
|
||||
for (var name in object) {
|
||||
// If name is a function, simply add it to the observable.
|
||||
if (typeof object[name] === 'function') {
|
||||
this[name] = object[name];
|
||||
} else {
|
||||
_defineObservableProperty(this, name, {
|
||||
value: object[name]
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify the value change of a property.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf Observable.prototype
|
||||
* @param {String} name
|
||||
* @param {Object} newValue
|
||||
* @param {Object} oldValue
|
||||
*/
|
||||
Observable.prototype._notify = function o__notify(name, newValue, oldValue) {
|
||||
var observers = this._observers[name];
|
||||
if (observers) {
|
||||
observers.forEach(function(observer) {
|
||||
observer(newValue, oldValue);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove an observer from a property.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf Observable.prototype
|
||||
* @param {Function} observer
|
||||
* @param {String} name
|
||||
*/
|
||||
Observable.prototype._removeObserver =
|
||||
function o__removeObserver(observer, name) {
|
||||
// arguments in reverse order to support .bind(observer) for the
|
||||
// unbind from all case
|
||||
var observers = this._observers[name];
|
||||
if (observers) {
|
||||
var index = observers.indexOf(observer);
|
||||
if (index >= 0) {
|
||||
observers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Observe a property with an observer. The observer is called when the
|
||||
* property changes.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf Observable.prototype
|
||||
* @param {String} name
|
||||
* @param {Function} observer
|
||||
*/
|
||||
Observable.prototype.observe = function o_observe(name, observer) {
|
||||
if (typeof observer !== 'function') {
|
||||
return;
|
||||
}
|
||||
(this._observers[name] = this._observers[name] || []).push(observer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unobserve a property
|
||||
*
|
||||
* @access public
|
||||
* @memberOf Observable.prototype
|
||||
* @param {String} name
|
||||
* @param {Function} observer
|
||||
*/
|
||||
Observable.prototype.unobserve = function o_unobserve(name, observer) {
|
||||
if (typeof name === 'function') {
|
||||
// (observer) -- remove from every key in _observers
|
||||
Object.keys(this._observers).forEach(
|
||||
this._removeObserver.bind(this, name));
|
||||
} else {
|
||||
if (observer) {
|
||||
// (prop, observer) -- remove observer from the specific prop
|
||||
this._removeObserver(observer, name);
|
||||
} else if (name in this._observers) {
|
||||
// (prop) -- otherwise remove all observers for property
|
||||
this._observers[name] = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Static functions
|
||||
var _dependencyGraphs = new Map();
|
||||
/**
|
||||
* Each module should have its own dependency graph that decides what
|
||||
* observers to called when a property changes. The function returns the
|
||||
* dependency graph of a module. It creates one if the map does not exist.
|
||||
*
|
||||
* @param {Object} modulePrototype
|
||||
*/
|
||||
function _getDependencyGraph(modulePrototype) {
|
||||
var dependencyGraph = _dependencyGraphs.get(modulePrototype);
|
||||
if (!dependencyGraph) {
|
||||
// register a new dependency graph of the module based on the existing
|
||||
// dependency graph on the module.
|
||||
dependencyGraph = DependencyGraph(modulePrototype._dependencyGraph);
|
||||
modulePrototype._dependencyGraph = dependencyGraph;
|
||||
_dependencyGraphs.set(modulePrototype, dependencyGraph);
|
||||
}
|
||||
return dependencyGraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* The function helps query the values of all dependent properties of a
|
||||
* specified property.
|
||||
*
|
||||
* @param {Observable} observable
|
||||
* @param {String} sourceProperty
|
||||
* The source property name.
|
||||
*/
|
||||
function _getAllDependentValues(observable, sourceProperty) {
|
||||
var dependentList = observable._dependencyGraph &&
|
||||
observable._dependencyGraph.getAllDependent(sourceProperty);
|
||||
if (dependentList && dependentList.length) {
|
||||
return dependentList.map((name) => {
|
||||
return {
|
||||
name: name,
|
||||
value: observable[name]
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function _getterTemplate(name, defaultValue) {
|
||||
return function() {
|
||||
var value = this[OP_PREFIX(name)];
|
||||
if (typeof value === 'undefined') {
|
||||
value = this[OP_PREFIX(name)] = defaultValue;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
function _setterTemplate(name) {
|
||||
return function(value) {
|
||||
var oldValue = this[name];
|
||||
if (oldValue !== value) {
|
||||
// cache the old values of all dependent
|
||||
var dependentValues = _getAllDependentValues(this, name);
|
||||
// change the value
|
||||
this[OP_PREFIX(name)] = value;
|
||||
// notify the changes
|
||||
this._notify(name, value, oldValue);
|
||||
if (dependentValues) {
|
||||
dependentValues.forEach((obj) => {
|
||||
this._notify(obj.name, this[obj.name], obj.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function _defineObservablePropertyCore(object, name, options) {
|
||||
var dependency = options && options.dependency;
|
||||
|
||||
// Update dependency information
|
||||
if (dependency) {
|
||||
var dependencyGraph = _getDependencyGraph(object);
|
||||
dependency.forEach((dependentName) => {
|
||||
// name depends on dependentName
|
||||
dependencyGraph.addDependency(name, dependentName);
|
||||
});
|
||||
}
|
||||
|
||||
Object.defineProperty(object, name, {
|
||||
enumerable: true,
|
||||
get: options.get,
|
||||
set: options.set
|
||||
});
|
||||
}
|
||||
|
||||
function _defineObservableProperty(object, name, options) {
|
||||
if (options && options.readonly) {
|
||||
var internalName = '_' + name;
|
||||
_defineObservablePropertyCore(object, name, {
|
||||
dependency: [internalName],
|
||||
get: function() {
|
||||
return this[internalName];
|
||||
}
|
||||
});
|
||||
_defineObservablePropertyCore(object, internalName, {
|
||||
get: _getterTemplate(internalName, options && options.value),
|
||||
set: _setterTemplate(internalName)
|
||||
});
|
||||
} else if (options && options.dependency && options.dependency.length) {
|
||||
if (typeof options.get !== 'function') {
|
||||
throw new Error('Observable: getter of ' + name + ' is invalid');
|
||||
}
|
||||
_defineObservablePropertyCore(object, name, {
|
||||
dependency: options.dependency,
|
||||
get: options.get
|
||||
});
|
||||
} else {
|
||||
_defineObservablePropertyCore(object, name, {
|
||||
get: _getterTemplate(name, options && options.value),
|
||||
set: _setterTemplate(name)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Observe a property with an observer. The observer is called when the
|
||||
* property changes.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf Observable
|
||||
* @param {Object} object
|
||||
* @param {String} name
|
||||
* @param {Object} options
|
||||
* @param {Boolean} options.readonly
|
||||
* Indicating if the property is read-only.
|
||||
* @param {Array.<String>} options.dependency
|
||||
* List of the dependent properties.
|
||||
* @param {Function} options.get
|
||||
* Getter of the property.
|
||||
*/
|
||||
Object.defineProperty(Observable, 'defineObservableProperty', {
|
||||
get: function() {
|
||||
return _defineObservableProperty;
|
||||
}
|
||||
});
|
||||
return Observable;
|
||||
});
|
||||
228
gaia/apps/settings/js/modules/mvvm/observable_array.js
Executable file
228
gaia/apps/settings/js/modules/mvvm/observable_array.js
Executable file
|
|
@ -0,0 +1,228 @@
|
|||
/**
|
||||
* ObservableArray is able to notify its change through events of four basic
|
||||
* operations including 'insert', 'remove', 'replace', 'reset'.
|
||||
* ObservableArray implements most used methods including 'push', 'pop', and
|
||||
* 'splice'. Due to the syntax limitation it provides 'get' and 'set' for
|
||||
* manuplication values stored in the array. 'reset' is also provided for
|
||||
* replacing te entire array with another one. In addition to these methods,
|
||||
* methods including 'forEach', 'map', 'every', 'some', 'indexOf',
|
||||
* 'lastIndexOf', 'reduce', and 'reduceRight'.
|
||||
*
|
||||
* ObservableArray creation:
|
||||
* It can be initialized by an ordinary array. If the array is not given, an
|
||||
* empty is used by default.
|
||||
*
|
||||
* @example
|
||||
* var observableArray = ObservableArray([1, 2, 3]);
|
||||
*
|
||||
* Events:
|
||||
* Information regarding the operation is provided in the event detail. Please
|
||||
* check the following example:
|
||||
*
|
||||
* @example
|
||||
* var observableArray = ObservableArray([1, 2, 3]);
|
||||
* var handler = {
|
||||
* handleEvent: function(event) { console.log(JSON.stringify(event)); }
|
||||
* }
|
||||
* observableArray.addEventListener('insert', handler);
|
||||
* observableArray.addEventListener('remove', handler);
|
||||
* observableArray.addEventListener('replace', handler);
|
||||
* observableArray.addEventListener('reset', handler);
|
||||
*
|
||||
* observableArray.push(4);
|
||||
* // observableArray is [1, 2, 3, 4]
|
||||
* // event.type is 'insert'
|
||||
* // event.detail is {index: 3, count: 1, items: [4]}
|
||||
*
|
||||
* observableArray.pop();
|
||||
* // observableArray is [1, 2, 3]
|
||||
* // event.type is 'remove'
|
||||
* // event.detail is {index: 3, count: 1, items: [4]}
|
||||
*
|
||||
* observableArray.set(2, 4);
|
||||
* // observableArray is [1, 2, 4]
|
||||
* // event.type is 'replace'
|
||||
* // event.detail is {index: 2, oldValue: 3, newValue: 4}
|
||||
*
|
||||
* observableArray.reset([1, 2, 3]);
|
||||
* // observableArray is [1, 2, 3]
|
||||
* // event.type is 'reset'
|
||||
* // event.detail is {items: [1, 2, 3]}
|
||||
*
|
||||
* observableArray.splice(2, 1, [4, 5, 6]);
|
||||
* // observableArray is [1, 2, 4, 5, 6]
|
||||
* // First event:
|
||||
* // event.type is 'remove'
|
||||
* // event.detail is {index: 2, count: 1, items: [3]}
|
||||
* // Second event:
|
||||
* // event.type is 'insert'
|
||||
* // event.detail is {index: 2, count: 3, items: [4, 5, 6]}
|
||||
*
|
||||
* @module modules/mvvm/observable_array
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var Module = require('modules/base/module');
|
||||
var EventEmitter = require('modules/base/event_emitter');
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
|
||||
var Events = ['insert', 'remove', 'replace', 'reset'];
|
||||
var ReadOnlyMethods = ['forEach', 'map', 'every', 'some', 'indexOf',
|
||||
'lastIndexOf', 'reduce', 'reduceRight'];
|
||||
|
||||
/**
|
||||
* @class ObservableArray
|
||||
* @requires module:modules/base/module
|
||||
* @requires module:modules/base/event_emitter
|
||||
* @requires module:modules/mvvm/observable
|
||||
* @returns {ObservableArray}
|
||||
*/
|
||||
var ObservableArray = Module.create(function ObservableArray(array) {
|
||||
this.super(Observable).call(this);
|
||||
this.super(EventEmitter).call(this, Events);
|
||||
|
||||
this._array = array || [];
|
||||
this._length = this._array.length;
|
||||
}).extend(Observable, EventEmitter);
|
||||
|
||||
/**
|
||||
* An observable property indicating the length of the array.
|
||||
*
|
||||
* @access public
|
||||
* @type {Number}
|
||||
*/
|
||||
Observable.defineObservableProperty(ObservableArray.prototype, 'length', {
|
||||
readonly: true
|
||||
});
|
||||
|
||||
/**
|
||||
* An observable property representing the inner array.
|
||||
*
|
||||
* @access public
|
||||
* @type {Array}
|
||||
*/
|
||||
Observable.defineObservableProperty(ObservableArray.prototype, 'array', {
|
||||
readonly: true
|
||||
});
|
||||
|
||||
ReadOnlyMethods.forEach(function(op) {
|
||||
ObservableArray.prototype[op] = function() {
|
||||
return this._array[op].apply(this._array, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
ObservableArray.prototype.push = function(item) {
|
||||
this._array.push(item);
|
||||
this._length = this._array.length;
|
||||
|
||||
this._emitEvent('insert', {
|
||||
index: this._array.length - 1,
|
||||
count: 1,
|
||||
items: [item]
|
||||
});
|
||||
};
|
||||
|
||||
ObservableArray.prototype.pop = function() {
|
||||
if (!this._array.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var item = this._array.pop();
|
||||
this._length = this._array.length;
|
||||
|
||||
this._emitEvent('remove', {
|
||||
index: this._array.length,
|
||||
count: 1,
|
||||
items: [item]
|
||||
});
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
ObservableArray.prototype.splice = function(index, count) {
|
||||
if (arguments.length < 2) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Fix the arguments based on the behavior of the real spice function.
|
||||
if (index >= this._length) {
|
||||
index = this._length - 1;
|
||||
} else if (index < 0) {
|
||||
index = this._length + index;
|
||||
}
|
||||
if (count < 0) {
|
||||
count = 0;
|
||||
}
|
||||
|
||||
var addedItems = Array.prototype.slice.call(arguments, 2);
|
||||
var removedItems = this._array.splice.apply(this._array, arguments);
|
||||
this._length = this._array.length;
|
||||
|
||||
if (removedItems.length) {
|
||||
this._emitEvent('remove', {
|
||||
index: index,
|
||||
count: count,
|
||||
items: removedItems
|
||||
});
|
||||
}
|
||||
|
||||
if (addedItems.length) {
|
||||
this._emitEvent('insert', {
|
||||
index: index,
|
||||
count: addedItems.length,
|
||||
items: addedItems
|
||||
});
|
||||
}
|
||||
|
||||
return removedItems;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a value to the field of a specfied index.
|
||||
*
|
||||
* @access public
|
||||
* @param {Number} index
|
||||
* @param {Object} value
|
||||
*/
|
||||
ObservableArray.prototype.set = function(index, value) {
|
||||
if (index < 0 || index >= this._array.length) {
|
||||
this.throw('set: out of range');
|
||||
}
|
||||
|
||||
var oldValue = this._array[index];
|
||||
this._array[index] = value;
|
||||
this._emitEvent('replace', {
|
||||
index: index,
|
||||
oldValue: oldValue,
|
||||
newValue: value
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the value from the field of a specfied index.
|
||||
*
|
||||
* @access public
|
||||
* @param {Number} index
|
||||
* @returns {Object}
|
||||
*/
|
||||
ObservableArray.prototype.get = function(index) {
|
||||
return this._array[index];
|
||||
};
|
||||
|
||||
/**
|
||||
* Replace the entire array with another one.
|
||||
*
|
||||
* @access public
|
||||
* @param {Array} array
|
||||
*/
|
||||
ObservableArray.prototype.reset = function(array) {
|
||||
this._array = array || [];
|
||||
this._length = this._array.length;
|
||||
this._emitEvent('reset', {
|
||||
items: this._array
|
||||
});
|
||||
};
|
||||
|
||||
return ObservableArray;
|
||||
});
|
||||
Loading…
Reference in a new issue