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 ,get() { return this._data; } ,set(x) { this._setData(x); } }, /** * The number of rows in the model. */ numRows: { type: Number ,get() { if (this._data) return this._data.length; return 0; } }, /** * The current status of the model. */ status: { type: Number ,get() { return this._status; } }, /** * Checks if the model data is ready. */ ready: { type: Boolean ,get() { return this._status === Status.READY; } } } ,_data: null ,_status: Status.CLEAN ,_requestedSortName: null ,_sortColumn: null ,_sortWay: null ,_requestedIndexes: {} ,_indexes: [] //+++++++++++++++++++++++++++++ Data hadling ,_setData(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(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(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(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(rowIndex) { return this.checkRowExists(rowIndex) ? this._data[rowIndex] : null; } ,_setStatus(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(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(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(columnName, way) { this._requestedSortName = columnName; this._sort(columnName, way); } ,_sort(columnName, way) { var status = this._status; this._setStatus(Status.LOADING); this._realSort(columnName, way); this._setStatus(status); } ,_realSort(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(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(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(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(column) { this._requestedIndexes[column] = true; if (this._status === Status.READY) this._buildIndex(column); } ,getHashValue(value) { if (value instanceof Date) return value.getTime(); else return value; } ,_buildIndex(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(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() { this._setStatus(Status.READY); } });