392 lines
6.9 KiB
JavaScript
392 lines
6.9 KiB
JavaScript
|
|
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: Status
|
|
,SortWay: SortWay
|
|
});
|
|
|
|
Klass.implement
|
|
({
|
|
Extends: VnObject
|
|
,Tag: 'vn-model'
|
|
,Properties:
|
|
{
|
|
/**
|
|
* The model internal data.
|
|
*/
|
|
data:
|
|
{
|
|
type: Array
|
|
,get: function ()
|
|
{
|
|
return this._data;
|
|
}
|
|
,set: function (x)
|
|
{
|
|
this._setData (x);
|
|
}
|
|
},
|
|
/**
|
|
* The number of rows in the model.
|
|
*/
|
|
numRows:
|
|
{
|
|
type: Number
|
|
,get: function ()
|
|
{
|
|
if (this._data)
|
|
return this._data.length;
|
|
|
|
return 0;
|
|
}
|
|
},
|
|
/**
|
|
* The current status of the model.
|
|
*/
|
|
status:
|
|
{
|
|
type: Number
|
|
,get: function ()
|
|
{
|
|
return this._status;
|
|
}
|
|
},
|
|
/**
|
|
* Checks if the model data is ready.
|
|
*/
|
|
ready:
|
|
{
|
|
type: Boolean
|
|
,get: function ()
|
|
{
|
|
return this._status === Status.READY;
|
|
}
|
|
}
|
|
}
|
|
|
|
,_data: null
|
|
,_status: Status.CLEAN
|
|
|
|
,_requestedSortName: null
|
|
,_sortColumn: null
|
|
,_sortWay: null
|
|
|
|
,_requestedIndexes: {}
|
|
,_indexes: []
|
|
|
|
//+++++++++++++++++++++++++++++ Data hadling
|
|
|
|
,_setData: function (data)
|
|
{
|
|
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
|
|
*/
|
|
,checkRowExists: function (rowIndex)
|
|
{
|
|
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
|
|
*/
|
|
,get: function (rowIndex, columnName)
|
|
{
|
|
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
|
|
*/
|
|
,set: function (rowIndex, columnName, value)
|
|
{
|
|
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
|
|
*/
|
|
,getObject: function (rowIndex)
|
|
{
|
|
return this.checkRowExists (rowIndex) ?
|
|
this._data[rowIndex] : null;
|
|
}
|
|
|
|
,_setStatus: function (status)
|
|
{
|
|
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
|
|
*/
|
|
,insertRow: function (newRow)
|
|
{
|
|
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
|
|
*/
|
|
,deleteRow: function (rowIndex)
|
|
{
|
|
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
|
|
*/
|
|
,sort: function (columnName, way)
|
|
{
|
|
this._requestedSortName = columnName;
|
|
this._sort (columnName, way);
|
|
}
|
|
|
|
,_sort: function (columnName, way)
|
|
{
|
|
var status = this._status;
|
|
this._setStatus (Status.LOADING);
|
|
this._realSort (columnName, way);
|
|
this._setStatus (status);
|
|
}
|
|
|
|
,_realSort: function (columnName, way)
|
|
{
|
|
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);
|
|
}
|
|
|
|
,_refreshRowIndexes: function (start)
|
|
{
|
|
var data = this._data;
|
|
|
|
for (var i = start; i < data.length; i++)
|
|
data[i].index = i;
|
|
}
|
|
|
|
/*
|
|
* Function used to sort the model ascending.
|
|
*/
|
|
,sortFunctionAsc: function (column, a, b)
|
|
{
|
|
if (a[column] < b[column])
|
|
return -1;
|
|
else if (a[column] > b[column])
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Function used to sort the model descending.
|
|
*/
|
|
,sortFunctionDesc: function (column, a, b)
|
|
{
|
|
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
|
|
*/
|
|
,indexColumn: function (column)
|
|
{
|
|
this._requestedIndexes[column] = true;
|
|
|
|
if (this._status === Status.READY)
|
|
this._buildIndex (column);
|
|
}
|
|
|
|
,getHashValue: function (value)
|
|
{
|
|
if (value instanceof Date)
|
|
return value.getTime();
|
|
else
|
|
return value;
|
|
}
|
|
|
|
,_buildIndex: function (columnName)
|
|
{
|
|
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
|
|
*/
|
|
,search: function (columnName, value)
|
|
{
|
|
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
|
|
|
|
,refresh: function ()
|
|
{
|
|
this._setStatus (Status.READY);
|
|
}
|
|
});
|