toloframework
Version:
Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.
283 lines (221 loc) • 6.76 kB
JavaScript
"use strict";
/** @module tfw.binding.list */
require('tfw.binding.list', function (require, module, exports) {
var _ = function () {
var D = {
"en": {},
"fr": {}
},
X = require("$").intl;
function _() {
return X(D, arguments);
}
_.all = D;
return _;
}();
"use strict";
/*
fire( functionName, arguments, wave )
addListener( listener )
removeListener( listener )
link( list )
unlink( list )
get( index )
put( index, elem )
remove( elem )
removeAt( index )
mapInPlace( function(elem, index) )
*/
var PM = require("tfw.binding.property-manager"),
Listeners = require("tfw.listeners");
var ID = "__tfw.binding.list__";
var COUNT = 1;
/**
* A list is an observable array.
*
* Several lists can share the same internal array. In that case, when
* one list modifies the array, all lists will emit the same event as
* it. Such lists are linked.
*/
var List = function List(arg) {
readOnly(this, ID, COUNT++);
readOnly(this, "_listeners", new Listeners());
readOnly(this, "_links", []); // An object with the attribute `isContentChangeAware === true`
// can fire a property value changed when its contant has change.
readOnly(this, "isContentChangeAware", true);
if (Array.isArray(arg)) {
readOnly(this, "_array", arg);
} else if (isList(arg)) {
readOnly(this, "_array", arg._array);
this.link(arg);
} else {
readOnly(this, "_array", [arg]);
}
};
module.exports = List;
List.isList = isList;
List.prototype.fire = function (functionName, args, wave) {
var id = this[ID];
if (!Array.isArray(wave)) wave = [id];else if (wave.indexOf(id) === -1) wave.push(id); // Listeners for content change.
this._listeners.fire(functionName, args, wave); // Two Lists are linked if and only if they share the same array.
this._links.forEach(function (list) {
if (wave.indexOf(list[ID]) > -1) return;
list.fire(functionName, args, wave);
}); // The content of the List has changed. We must fire a 'changed'
// event on the manager if this list is linkable.
var properties = PM.getProperties(this);
if (Array.isArray(properties)) {
properties.forEach(function (prop) {
prop.manager.fire(prop.name);
});
}
};
List.prototype.addListener = function (listener) {
this._listeners.add(listener);
};
List.prototype.removeListener = function (listener) {
this._listeners.remove(listener);
};
List.prototype.link = function (list) {
if (!isList(list)) {
console.error("Argument: ", list);
throw Error("[tfw.binding.list.link] Argument must be a tfw.binding.list object!");
}
if (this._links.indexOf(list) === -1) {
this._links.push(list);
list.link(this);
}
};
List.prototype.unlink = function (list) {
var index = this._links.indexOf(list);
if (index === -1) return;
this._links.splice(index, 1);
list.unlink(this);
};
/**
* The braket notation doesn't work with list.
* This function
*/
List.prototype.get = function (index) {
return this._array[index];
};
List.prototype.put = function (index, newValue) {
var oldValue = this._array[index];
if (oldValue === newValue) return false;
this._array[index] = newValue;
this.fire("put", {
index: index,
oldValue: oldValue,
newValue: newValue
});
return true;
};
List.prototype.remove = function (elem) {
var index = this._array.indexOf(elem);
if (index === -1) return false;
this._array.splice(index, 1);
this.fire("remove", {
elem: elem,
index: index
});
return true;
};
List.prototype.removeAt = function (index) {
var elem = this._array[index];
this._array.splice(index, 1);
this.fire("remove", {
elem: elem,
index: index
});
return true;
};
List.prototype.mapInPlace = function (func) {
var that = this;
this._Array.forEach(function (itm, idx) {
var newValue = func(itm);
that.put(idx, newValue);
});
return this;
};
var DEFAULT_COMPARATOR = function DEFAULT_COMPARATOR(a, b) {
if (a == b) return 0;
return a < b ? -1 : 1;
};
/**
* Sort and fire only if the array is not already in order.
*/
List.prototype.sort = function (comparator) {
if (typeof comparator !== 'function') comparator = DEFAULT_COMPARATOR;
if (isAlreadySorted(this._array, comparator)) return this;
this._array.sort(comparator);
this.fire("sort", comparator);
return this;
};
function isAlreadySorted(array, comparator) {
if (array.length < 2) return true;
var previous = array[0];
var current;
for (var k = 1; k < array.length; k++) {
current = array[k];
if (comparator(previous, current) > 0) return false;
previous = current;
}
return true;
}
["push", "pop", "shift", "unshift", "splice", "reverse"].forEach(function (funcName) {
List.prototype[funcName] = function () {
var args = Array.prototype.slice.call(arguments);
var result = Array.prototype[funcName].apply(this._array, args);
this.fire(funcName, args);
return result;
};
});
/**
* Functions that don't change the array.
*/
["slice", "forEach", "filter", "map", "reduce", "indexOf", "lastIndexOf"].forEach(function (funcName) {
List.prototype[funcName] = function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
// const args = Array.prototype.slice.call(arguments);
return Array.prototype[funcName].apply(this._array, args);
};
});
if (!Array.prototype.includes) {
// Polyfill for IE11.
Array.prototype.includes = function (item) {
return this.indexOf(item) !== -1;
};
}
List.prototype.includes = function (item) {
return Array.prototype.includes.call(this._array, item);
};
/**
* Define the `length` property.
*/
Object.defineProperty(List.prototype, "length", {
get: function get() {
return this._array.length;
},
set: function set(v) {
this._array.length = v;
this.fire("length", v);
},
configurable: false,
enumerable: true
});
function isList(candidate) {
if (!candidate) return false;
return typeof candidate[ID] === 'number';
}
function readOnly(object, attrib, value) {
Object.defineProperty(object, attrib, {
value: value,
writable: false,
configurable: false,
enumerable: true
});
}
module.exports._ = _;
});