413 lines
7.7 KiB
JavaScript
413 lines
7.7 KiB
JavaScript
|
|
var VnObject = require ('./object');
|
|
|
|
// TODO: Remove this dependency
|
|
var Type = require ('db/connection').Type;
|
|
|
|
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)
|
|
{
|
|
this._setStatus (Status.LOADING);
|
|
this._realSort (columnName, way);
|
|
this._setStatus (Status.READY);
|
|
}
|
|
|
|
,_realSort: function (columnName, way)
|
|
{
|
|
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);
|
|
}
|
|
|
|
,getHashFunc: function (type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case Type.TIMESTAMP:
|
|
case Type.DATE_TIME:
|
|
case Type.DATE:
|
|
return function (value) { return value.toString (); };
|
|
default:
|
|
return function (value) { return value; };
|
|
}
|
|
}
|
|
|
|
,_buildIndex: function (columnName)
|
|
{
|
|
if (this.columnMap[columnName] === undefined)
|
|
return;
|
|
|
|
var index = {};
|
|
var data = this._data;
|
|
var hashFunc = getHashFunc (this.columns[columnName].type);
|
|
|
|
for (var i = 0; i < data.length; i++)
|
|
index[hashFunc (data[i][columnName])] = i;
|
|
|
|
this._indexes[columnName] = index;
|
|
}
|
|
|
|
/**
|
|
* Searchs a value on the model and returns the row index of the first
|
|
* ocurrence.
|
|
* If an index have been built on that column, it will be used, for more
|
|
* information see the indexColumn() method.
|
|
*
|
|
* @param {String} columnIndex The column index
|
|
* @param {Object} value The value to search
|
|
* @return {Number} The column index
|
|
*/
|
|
,searchByIndex: function (columnIndex, value)
|
|
{
|
|
var columnName = this.columns[columnIndex].name;
|
|
return this.search (columnName, value);
|
|
}
|
|
|
|
/**
|
|
* 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 columnIndex = this.columnMap[columnName];
|
|
|
|
if (columnIndex === undefined)
|
|
return -1;
|
|
|
|
if (value)
|
|
switch (this.columns[columnIndex].type)
|
|
{
|
|
case Type.BOOLEAN:
|
|
value = !!value;
|
|
break;
|
|
case Type.INTEGER:
|
|
value = parseInt (value);
|
|
break;
|
|
case Type.DOUBLE:
|
|
value = parseFloat (value);
|
|
break;
|
|
default:
|
|
value = value.toString ();
|
|
}
|
|
|
|
// Searchs the value using an internal index.
|
|
|
|
var index = this._indexes[columnName];
|
|
|
|
if (index)
|
|
{
|
|
if (index[value] !== undefined)
|
|
return index[value];
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Searchs the value using a loop.
|
|
|
|
var data = this._data;
|
|
|
|
switch (this.columns[columnIndex].type)
|
|
{
|
|
case Type.TIMESTAMP:
|
|
case Type.DATE_TIME:
|
|
case Type.DATE:
|
|
{
|
|
for (var i = 0; i < data.length; i++)
|
|
if (value === data[i][columnName].toString ())
|
|
return i;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
for (var i = 0; i < data.length; i++)
|
|
if (value === data[i][columnName])
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
});
|