var util = require('./util'); /** * DataSet * * Usage: * var dataSet = new DataSet({ * fieldId: '_id', * fieldTypes: { * // ... * } * }); * * dataSet.add(item); * dataSet.add(data); * dataSet.update(item); * dataSet.update(data); * dataSet.remove(id); * dataSet.remove(ids); * var data = dataSet.get(); * var data = dataSet.get(id); * var data = dataSet.get(ids); * var data = dataSet.get(ids, options, data); * dataSet.clear(); * * A data set can: * - add/remove/update data * - gives triggers upon changes in the data * - can import/export data in various data formats * @param {Object} [options] Available options: * {String} fieldId Field name of the id in the * items, 'id' by default. * {Object.} [fieldTypes] * {String[]} [fields] filter fields * @param {Array | DataTable} [data] If provided, items will be appended * to this array or table. Required * in case of Google DataTable * @return {Array | Object | DataTable | null} data * @throws Error */ DataSet.prototype.get = function (ids, options, data) { var me = this; // shift arguments when first argument contains the options if (util.getType(ids) == 'Object') { data = options; options = ids; ids = undefined; } // merge field types var fieldTypes = {}; if (this.options && this.options.fieldTypes) { util.forEach(this.options.fieldTypes, function (value, field) { fieldTypes[field] = value; }); } if (options && options.fieldTypes) { util.forEach(options.fieldTypes, function (value, field) { fieldTypes[field] = value; }); } var fields = options ? options.fields : undefined; // determine the return type var type; if (options && options.type) { type = (options.type == 'DataTable') ? 'DataTable' : 'Array'; if (data && (type != util.getType(data))) { throw new Error('Type of parameter "data" (' + util.getType(data) + ') ' + 'does not correspond with specified options.type (' + options.type + ')'); } if (type == 'DataTable' && !util.isDataTable(data)) { throw new Error('Parameter "data" must be a DataTable ' + 'when options.type is "DataTable"'); } } else if (data) { type = (util.getType(data) == 'DataTable') ? 'DataTable' : 'Array'; } else { type = 'Array'; } if (type == 'DataTable') { // return a Google DataTable var columns = this._getColumnNames(data); if (ids == undefined) { // return all data util.forEach(this.data, function (item) { me._appendRow(data, columns, me._castItem(item)); }); } else if (util.isNumber(ids) || util.isString(ids)) { var item = me._castItem(me.data[ids], fieldTypes, fields); this._appendRow(data, columns, item); } else if (ids instanceof Array) { ids.forEach(function (id) { var item = me._castItem(me.data[id], fieldTypes, fields); me._appendRow(data, columns, item); }); } else { throw new TypeError('Parameter "ids" must be ' + 'undefined, a String, Number, or Array'); } } else { // return an array data = data || []; if (ids == undefined) { // return all data util.forEach(this.data, function (item) { data.push(me._castItem(item, fieldTypes, fields)); }); } else if (util.isNumber(ids) || util.isString(ids)) { // return a single item return this._castItem(me.data[ids], fieldTypes, fields); } else if (ids instanceof Array) { ids.forEach(function (id) { data.push(me._castItem(me.data[id], fieldTypes, fields)); }); } else { throw new TypeError('Parameter "ids" must be ' + 'undefined, a String, Number, or Array'); } } return data; }; /** * Remove an object by pointer or by id * @param {String | Number | Object | Array} id Object or id, or an array with * objects or ids to be removed * @param {String} [senderId] Optional sender id, used to trigger events for * all but this sender's event subscribers. */ DataSet.prototype.remove = function (id, senderId) { var items = [], me = this; if (util.isNumber(id) || util.isString(id)) { delete this.data[id]; delete this.internalIds[id]; items.push(id); } else if (id instanceof Array) { id.forEach(function (id) { me.remove(id); }); items = items.concat(id); } else if (id instanceof Object) { // search for the object for (var i in this.data) { if (this.data.hasOwnProperty(i)) { if (this.data[i] == id) { delete this.data[i]; delete this.internalIds[i]; items.push(i); } } } } this._trigger('remove', {items: items}, senderId); }; /** * Clear the data * @param {String} [senderId] Optional sender id, used to trigger events for * all but this sender's event subscribers. */ DataSet.prototype.clear = function (senderId) { var ids = Object.keys(this.data); this.data = {}; this.internalIds = {}; this._trigger('remove', {items: ids}, senderId); }; /** * Find the item with maximum value of a specified field * @param {String} field * @return {Object} item Item containing max value, or null if no items */ DataSet.prototype.max = function (field) { var data = this.data, ids = Object.keys(data); var max = null; var maxField = null; ids.forEach(function (id) { var item = data[id]; var itemField = item[field]; if (itemField != null && (!max || itemField > maxField)) { max = item; maxField = itemField; } }); return max; }; /** * Find the item with minimum value of a specified field * @param {String} field */ DataSet.prototype.min = function (field) { var data = this.data, ids = Object.keys(data); var min = null; var minField = null; ids.forEach(function (id) { var item = data[id]; var itemField = item[field]; if (itemField != null && (!min || itemField < minField)) { min = item; minField = itemField; } }); return min; }; /** * Add a single item * @param {Object} item * @return {String} id * @private */ DataSet.prototype._addItem = function (item) { var id = item[this.fieldId]; if (id == undefined) { // generate an id id = util.randomUUID(); item[this.fieldId] = id; this.internalIds[id] = item; } var d = {}; for (var field in item) { if (item.hasOwnProperty(field)) { var type = this.fieldTypes[field]; // type may be undefined d[field] = util.cast(item[field], type); } } this.data[id] = d; //TODO: fail when an item with this id already exists? return id; }; /** * Cast and filter the fields of an item * @param {Object | undefined} item * @param {Object.} [fieldTypes] * @param {String[]} [fields] * @return {Object | null} castedItem * @private */ DataSet.prototype._castItem = function (item, fieldTypes, fields) { var clone, fieldId = this.fieldId, internalIds = this.internalIds; if (item) { clone = {}; fieldTypes = fieldTypes || {}; if (fields) { // output filtered fields util.forEach(item, function (value, field) { if (fields.indexOf(field) != -1) { clone[field] = util.cast(value, fieldTypes[field]); } }); } else { // output all fields, except internal ids util.forEach(item, function (value, field) { if (field != fieldId || !(value in internalIds)) { clone[field] = util.cast(value, fieldTypes[field]); } }); } } else { clone = null; } return clone; }; /** * Update a single item: merge with existing item * @param {Object} item * @return {String} id * @private */ DataSet.prototype._updateItem = function (item) { var id = item[this.fieldId]; if (id == undefined) { throw new Error('Item has no id (item: ' + JSON.stringify(item) + ')'); } var d = this.data[id]; if (d) { // merge with current item for (var field in item) { if (item.hasOwnProperty(field)) { var type = this.fieldTypes[field]; // type may be undefined d[field] = util.cast(item[field], type); } } } else { // create new item this._addItem(item); } return id; }; /** * Get an array with the column names of a Google DataTable * @param {DataTable} dataTable * @return {Array} columnNames * @private */ DataSet.prototype._getColumnNames = function (dataTable) { var columns = []; for (var col = 0, cols = dataTable.getNumberOfColumns(); col < cols; col++) { columns[col] = dataTable.getColumnId(col) || dataTable.getColumnLabel(col); } return columns; }; /** * Append an item as a row to the dataTable * @param dataTable * @param columns * @param item * @private */ DataSet.prototype._appendRow = function (dataTable, columns, item) { var row = dataTable.addRow(); columns.forEach(function (field, col) { dataTable.setValue(row, col, item[field]); }); }; // exports module.exports = exports = DataSet;