2022-05-30 01:30:33 +00:00
|
|
|
|
|
|
|
var VnObject = require('./object');
|
|
|
|
var simpleEquals = require('./value').simpleEquals;
|
|
|
|
|
|
|
|
var Klass = new Class();
|
|
|
|
module.exports = Klass;
|
|
|
|
|
|
|
|
var Status = {
|
|
|
|
CLEAN : 1
|
|
|
|
,LOADING : 2
|
|
|
|
,READY : 3
|
|
|
|
,ERROR : 4
|
|
|
|
};
|
|
|
|
|
|
|
|
var SortWay = {
|
|
|
|
ASC : 1
|
|
|
|
,DESC : 2
|
|
|
|
};
|
|
|
|
|
|
|
|
Klass.extend({
|
|
|
|
Status
|
|
|
|
,SortWay
|
|
|
|
});
|
|
|
|
|
|
|
|
Klass.implement({
|
|
|
|
Extends: VnObject
|
|
|
|
,Tag: 'vn-model'
|
|
|
|
,Properties: {
|
|
|
|
/**
|
|
|
|
* The model internal data.
|
|
|
|
*/
|
|
|
|
data: {
|
|
|
|
type: Array
|
2022-11-16 01:46:44 +00:00
|
|
|
,get() {
|
2022-05-30 01:30:33 +00:00
|
|
|
return this._data;
|
|
|
|
}
|
2022-11-16 01:46:44 +00:00
|
|
|
,set(x) {
|
2022-05-30 01:30:33 +00:00
|
|
|
this._setData(x);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* The number of rows in the model.
|
|
|
|
*/
|
|
|
|
numRows: {
|
|
|
|
type: Number
|
2022-11-16 01:46:44 +00:00
|
|
|
,get() {
|
2022-05-30 01:30:33 +00:00
|
|
|
if (this._data)
|
|
|
|
return this._data.length;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* The current status of the model.
|
|
|
|
*/
|
|
|
|
status: {
|
|
|
|
type: Number
|
2022-11-16 01:46:44 +00:00
|
|
|
,get() {
|
2022-05-30 01:30:33 +00:00
|
|
|
return this._status;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Checks if the model data is ready.
|
|
|
|
*/
|
|
|
|
ready: {
|
|
|
|
type: Boolean
|
2022-11-16 01:46:44 +00:00
|
|
|
,get() {
|
2022-05-30 01:30:33 +00:00
|
|
|
return this._status === Status.READY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
,_data: null
|
|
|
|
,_status: Status.CLEAN
|
|
|
|
|
|
|
|
,_requestedSortName: null
|
|
|
|
,_sortColumn: null
|
|
|
|
,_sortWay: null
|
|
|
|
|
|
|
|
,_requestedIndexes: {}
|
|
|
|
,_indexes: []
|
|
|
|
|
|
|
|
//+++++++++++++++++++++++++++++ Data hadling
|
|
|
|
|
2022-11-16 01:46:44 +00:00
|
|
|
,_setData(data) {
|
2022-05-30 01:30:33 +00:00
|
|
|
this._data = data;
|
|
|
|
this._refreshRowIndexes(0);
|
|
|
|
|
|
|
|
if (this._requestedSortName != null)
|
|
|
|
this._realSort(this._requestedSortName, this._sortWay);
|
|
|
|
for (column in this._requestedIndexes)
|
|
|
|
this._buildIndex(column);
|
|
|
|
|
|
|
|
this._setStatus(Status.READY);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if the row exists.
|
|
|
|
*
|
|
|
|
* @param {Integer} rowIndex The row index
|
|
|
|
* @return {Boolean} %true if row exists, %false otherwise
|
|
|
|
*/
|
2022-11-16 01:46:44 +00:00
|
|
|
,checkRowExists(rowIndex) {
|
2022-05-30 01:30:33 +00:00
|
|
|
return this._data != null
|
|
|
|
&& rowIndex >= 0
|
|
|
|
&& rowIndex < this._data.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a value from the model.
|
|
|
|
*
|
|
|
|
* @param {Number} rowIndex The row index
|
|
|
|
* @param {Number} columnName The column name
|
|
|
|
* @return {*} The value
|
|
|
|
*/
|
2022-11-16 01:46:44 +00:00
|
|
|
,get(rowIndex, columnName) {
|
2022-05-30 01:30:33 +00:00
|
|
|
if (!this.checkRowExists(rowIndex))
|
|
|
|
return undefined;
|
|
|
|
|
|
|
|
return this._data[rowIndex][columnName];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates a value on the model.
|
|
|
|
*
|
|
|
|
* @param {Number} rowIndex The row index
|
|
|
|
* @param {Number} columnName The column name
|
|
|
|
* @param {*} value The new value
|
|
|
|
*/
|
2022-11-16 01:46:44 +00:00
|
|
|
,set(rowIndex, columnName, value) {
|
2022-05-30 01:30:33 +00:00
|
|
|
if (!this.checkRowExists(rowIndex))
|
|
|
|
return;
|
|
|
|
|
|
|
|
this.emit('row-updated-before', rowIndex);
|
|
|
|
this._data[rowIndex][columnName] = value;
|
|
|
|
this.emit('row-updated', rowIndex, [columnName]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a row as an object using the column index.
|
|
|
|
*
|
|
|
|
* @param {Number} rowIndex The row index
|
|
|
|
* @return {Object} The row as an object
|
|
|
|
*/
|
2022-11-16 01:46:44 +00:00
|
|
|
,getObject(rowIndex) {
|
2022-05-30 01:30:33 +00:00
|
|
|
return this.checkRowExists(rowIndex) ?
|
|
|
|
this._data[rowIndex] : null;
|
|
|
|
}
|
|
|
|
|
2022-11-16 01:46:44 +00:00
|
|
|
,_setStatus(status) {
|
2022-05-30 01:30:33 +00:00
|
|
|
this._status = status;
|
|
|
|
this.emit('status-changed', status);
|
|
|
|
this.emit('status-changed-after', status);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Inserts a new row on the model.
|
|
|
|
*
|
|
|
|
* @return The index of the inserted row
|
|
|
|
*/
|
2022-11-16 01:46:44 +00:00
|
|
|
,insertRow(newRow) {
|
2022-05-30 01:30:33 +00:00
|
|
|
var rowIndex = this._data.push(newRow) - 1;
|
|
|
|
newRow.index = rowIndex;
|
|
|
|
this.emit('row-inserted', rowIndex);
|
|
|
|
return rowIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deletes a row from the model.
|
|
|
|
*
|
|
|
|
* @param {Number} rowIndex The row index
|
|
|
|
*/
|
2022-11-16 01:46:44 +00:00
|
|
|
,deleteRow(rowIndex) {
|
2022-05-30 01:30:33 +00:00
|
|
|
if (!this.checkRowExists(rowIndex))
|
|
|
|
return;
|
|
|
|
|
|
|
|
this.emit('row-deleted-before', rowIndex);
|
|
|
|
this._data.splice(rowIndex, 1);
|
|
|
|
this.emit('row-deleted', rowIndex);
|
|
|
|
this._refreshRowIndexes(rowIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
//+++++++++++++++++++++++++++++ Sorting
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Orders the model by the specified column name.
|
|
|
|
*
|
|
|
|
* @param {String} columnName The column name
|
|
|
|
* @param {SortWay} way The sort way
|
|
|
|
*/
|
2022-11-16 01:46:44 +00:00
|
|
|
,sort(columnName, way) {
|
2022-05-30 01:30:33 +00:00
|
|
|
this._requestedSortName = columnName;
|
|
|
|
this._sort(columnName, way);
|
|
|
|
}
|
|
|
|
|
2022-11-16 01:46:44 +00:00
|
|
|
,_sort(columnName, way) {
|
2022-05-30 01:30:33 +00:00
|
|
|
var status = this._status;
|
|
|
|
this._setStatus(Status.LOADING);
|
|
|
|
this._realSort(columnName, way);
|
|
|
|
this._setStatus(status);
|
|
|
|
}
|
|
|
|
|
2022-11-16 01:46:44 +00:00
|
|
|
,_realSort(columnName, way) {
|
2022-05-30 01:30:33 +00:00
|
|
|
if (!this._data)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (columnName !== this._sortColumn) {
|
|
|
|
if (way === SortWay.DESC)
|
|
|
|
var sortFunction = this.sortFunctionDesc;
|
|
|
|
else
|
|
|
|
var sortFunction = this.sortFunctionAsc;
|
|
|
|
|
|
|
|
this._data.sort(sortFunction.bind(this, columnName));
|
|
|
|
} else if (way !== this._sortWay)
|
|
|
|
this._data.reverse();
|
|
|
|
|
|
|
|
this._sortColumn = columnName;
|
|
|
|
this._sortWay = way;
|
|
|
|
|
|
|
|
this._refreshRowIndexes(0);
|
|
|
|
}
|
|
|
|
|
2022-11-16 01:46:44 +00:00
|
|
|
,_refreshRowIndexes(start) {
|
2022-05-30 01:30:33 +00:00
|
|
|
var data = this._data;
|
|
|
|
|
|
|
|
for (var i = start; i < data.length; i++)
|
|
|
|
data[i].index = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Function used to sort the model ascending.
|
|
|
|
*/
|
2022-11-16 01:46:44 +00:00
|
|
|
,sortFunctionAsc(column, a, b) {
|
2022-05-30 01:30:33 +00:00
|
|
|
if (a[column] < b[column])
|
|
|
|
return -1;
|
|
|
|
else if (a[column] > b[column])
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Function used to sort the model descending.
|
|
|
|
*/
|
2022-11-16 01:46:44 +00:00
|
|
|
,sortFunctionDesc(column, a, b) {
|
2022-05-30 01:30:33 +00:00
|
|
|
if (a[column] > b[column])
|
|
|
|
return -1;
|
|
|
|
else if (a[column] < b[column])
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+++++++++++++++++++++++++++++ Searching
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Builds an internal hash index for the specified column, this speeds
|
|
|
|
* significantly searches on that column, specially when model has a lot of
|
|
|
|
* rows.
|
|
|
|
*
|
|
|
|
* FIXME: Not fully implemented.
|
|
|
|
*
|
|
|
|
* @param {String} column The column name
|
|
|
|
*/
|
2022-11-16 01:46:44 +00:00
|
|
|
,indexColumn(column) {
|
2022-05-30 01:30:33 +00:00
|
|
|
this._requestedIndexes[column] = true;
|
|
|
|
|
|
|
|
if (this._status === Status.READY)
|
|
|
|
this._buildIndex(column);
|
|
|
|
}
|
|
|
|
|
2022-11-16 01:46:44 +00:00
|
|
|
,getHashValue(value) {
|
2022-05-30 01:30:33 +00:00
|
|
|
if (value instanceof Date)
|
|
|
|
return value.getTime();
|
|
|
|
else
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2022-11-16 01:46:44 +00:00
|
|
|
,_buildIndex(columnName) {
|
2022-05-30 01:30:33 +00:00
|
|
|
if (this.columnMap[columnName] === undefined)
|
|
|
|
return;
|
|
|
|
|
|
|
|
var data = this._data;
|
|
|
|
var values = {};
|
|
|
|
var nulls = [];
|
|
|
|
|
|
|
|
for (var i = 0; i < data.length; i++) {
|
|
|
|
var value = data[i][columnName];
|
|
|
|
|
|
|
|
if (value == null) {
|
|
|
|
nulls.push(data[i]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
index[this.getHashValue(value)] = data[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
this._indexes[columnName] = {
|
|
|
|
values: values,
|
|
|
|
index: index
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Searchs a value on the model and returns the row index of the first
|
|
|
|
* ocurrence.
|
|
|
|
*
|
|
|
|
* @param {Number} columnName The column name
|
|
|
|
* @param {Object} value The value to search
|
|
|
|
* @return {Number} The column index
|
|
|
|
*/
|
2022-11-16 01:46:44 +00:00
|
|
|
,search(columnName, value) {
|
2022-05-30 01:30:33 +00:00
|
|
|
var data = this._data;
|
|
|
|
|
|
|
|
if (data == null)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
// Searchs the value using an internal index.
|
|
|
|
|
|
|
|
var index = this._indexes[columnName];
|
|
|
|
|
|
|
|
if (index) {
|
|
|
|
if (value == null) {
|
|
|
|
if (index.nulls[0] !== undefined)
|
|
|
|
return index.nulls[0].index;
|
|
|
|
} else {
|
|
|
|
var row = index.values[this.getHashValue(value)];
|
|
|
|
|
|
|
|
if (rowIndex !== undefined)
|
|
|
|
return row.index;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Searchs the value using a loop.
|
|
|
|
|
|
|
|
for (var i = 0; i < data.length; i++)
|
|
|
|
if (simpleEquals(data[i][columnName], value))
|
|
|
|
return i;
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//+++++++++++++++++++++++++++++ Virtual
|
|
|
|
|
2022-11-16 01:46:44 +00:00
|
|
|
,refresh() {
|
2022-05-30 01:30:33 +00:00
|
|
|
this._setStatus(Status.READY);
|
|
|
|
}
|
|
|
|
});
|