var assign = require('object-assign');
/**
* DataStore
* http://gitcafe.com/towry
*
* Provide an index persistent data store.
* The data is a list, that will be added with new items,
* or delete item from it, but the original index used by
* the react component must be persistence, so that we can
* get the data from data store with the index after the list
* changed.
*
* DO NOT USE THIS DIRECTLY.
*/
function DataStore () {
this._data = [];
this._removedIndex = [];
this._subscribers = [];
this._sequence = [];
this._rents = [];
this._deleted = [];
this._cache = [];
}
module.exports = DataStore;
/**
* @api public
*/
DataStore.prototype.setData = function (data) {
if (this._data.length) {
return;
}
if (Object.prototype.toString.call(data) !== '[object Array]') {
typeof console !== 'undefined' && console.warn && console.warn("argument is not an array");
return;
}
// make a copy of it
this._data = data.slice();
for (var i = 0; i < data.length; i++) {
this._sequence.push(i);
}
}
/**
* @api public
*/
DataStore.prototype.replaceData = function (data) {
this._data = data;
this._sequence = [];
for (var i = 0; i < data.length; i++) {
this._sequence.push(i);
}
}
/**
* @api public
*/
DataStore.prototype.all = function () {
if (this._cache.length) {
return this._cache;
}
var all = [];
for (var i = 0; i < this._sequence.length; i++) {
all.push(this._data[this._sequence[i]]);
}
this._cache = all;
return all;
}
/**
* The `cb` is a function that takes three arguments
* [item, index, persistent index]
* So, do not use the index as the component's key,
* because the data may be changed in the futrue and the
* index will be taken by another component.
* Use the persistent index.
* @api public
*/
DataStore.prototype.forEach = function (cb, context) {
var item;
var indic = 0;
for (var i = 0; i < this._sequence.length; i++) {
item = this._data[this._sequence[i]];
if (item === null || item === undefined) {
continue;
}
cb.call(context, this._data[this._sequence[i]], indic, this._sequence[i]);
indic++;
}
}
/**
* @api public
*/
DataStore.prototype.isEmpty = function () {
return this._sequence.length === 0;
}
/**
* Return new index
* When you want to get an index for later use
* @api public
*/
DataStore.prototype.rent = function () {
var id;
if (this._removedIndex.length) {
id = this._removedIndex.shift();
} else {
id = this._data.length;
this._data.push(null);
}
this._rents.push(id);
return id;
}
/**
* When datastore change, inform the subscribers
* @api public
*/
DataStore.prototype.subscribe = function (cb) {
if (this._subscribers.indexOf(cb) !== -1) {
return;
}
return this._subscribers.push(cb);
}
/**
* @api private
*/
DataStore.prototype.update = function (a) {
this._cache = [];
var cbs = this._subscribers, cb;
for (var i = 0, l = cbs.length; i < l; i++) {
cb = cbs[i];
cb(a);
}
}
/**
* Add an item to datastore
* @api public
*/
DataStore.prototype.add = function (item, index) {
var indexType;
if ((indexType = Object.prototype.toString.call(index)) === '[object Undefined]') {
throw new Error("When adding an item to store, please provide an index or null value");
}
index = indexType == '[object Number]' ? index : this.getAvailableIndex();
this._data[index] = item;
var idx = this._rents.indexOf(index);
if (idx >= 0) {
this._rents.splice(idx, 1);
}
this._sequence.push(index);
this.update({
index: index,
type: 'add',
});
}
DataStore.prototype.clear = function () {
var ret = this._deleted;
this._deleted = [];
return ret;
}
DataStore.prototype.hasDestroy = function () {
return this._deleted.length !== 0;
}
/**
* Remove an item from datastore
* @api public
*/
DataStore.prototype.remove = function (index, cb) {
if (index >= this._data.length) {
return;
}
/* TODO
*/
this._deleted.push(this._data[index]);
this._deleted[this._deleted.length - 1]._destroy = 1;
/** */
this._data[index] = null;
var idx = this._rents.indexOf(index);
if (idx >= 0) {
this._rents.splice(idx, 1);
}
idx = this._sequence.indexOf(index);
if (idx >= 0) {
this._sequence.splice(idx, 1);
}
this.addRemoveIndex(index);
this.update({
index: index,
type: 'remove',
});
cb();
}
/**
* @api public
*/
DataStore.prototype.put = function (index, value) {
if (index >= this._data.length) {
return;
}
this.removeIndexFromRemove(index);
var old = this._data[index];
if (Object.prototype.toString.call(value) === '[object Object]') {
this._data[index] = assign({}, old, value);
} else {
this._data[index] = value;
}
var idx = this._sequence.indexOf(index);
if (idx < 0) {
this._sequence.push(index);
}
idx = this._rents.indexOf(index);
if (idx >= 0) {
this._rents.splice(idx, 1);
}
this.update({
index: index,
type: 'put',
})
}
/**
* @api private
*/
DataStore.prototype.removeIndexFromRemove = function (index) {
var idx = this._removedIndex.indexOf(index);
if (idx < 0) return;
this._removedIndex.splice(idx, 1);
}
/**
* @api private
*/
DataStore.prototype.addRemoveIndex = function (index) {
var idx = this._removedIndex.indexOf(index);
if (idx >= 0) {
return;
} else {
this._removedIndex.push(index);
}
}
/**
* @api private
*/
DataStore.prototype.getAvailableIndex = function () {
if (this._removedIndex.length) {
return this._removedIndex.pop();
} else {
return this._data.length;
}
}
/**
* @api public
*/
DataStore.prototype.destroy = function () {
this._subscribers = [];
this._data = [];
this._removedIndex = [];
}
/**
* @api public
*/
DataStore.createStore = function (child, a) {
if (typeof child !== 'function') {
throw new TypeError("argument is not a function");
}
return new child(a);
}