js/spreadsheet/jsDataFactory.js (260 lines of code) (raw):

/** * @file js data 是一个封装了数据和行为的model,并作为各方的接口。 * @author sushuang(sushuang@baidu.com) */ define(function (require) { var $ = require('jquery'); var dtLib = require('dt/lib'); var helper = require('./helper'); var constant = require('./constant'); var dataTableProcessor = require('./dataTableProcessor'); var codeInputsProcessor = require('./codeInputsProcessor'); /** * [Usage] * * Create: * var jsDataOb = jsDataFactory.create(); * * Get and set data: * var data = jsDataOb(); * jsDataOb(someNewData); * * Get and set dimColStep: * jsDataOb.dimColStep(4); * var dimColStep = jsDataOb.getDimColStep(); * * Subscribe: * jsDataOb.subscribe(function(newValue, jsDataOb) { * // ... * }); */ /** * [Types] * * constant.JSDATA_DIM_ARRAY: * [ * [[12, 32], [54, 64], [1212, 22]], // Each row coresponds to an text input。 * [[434, 'sdfa'], [2, 23]], // two-dimension. Inner item must be Array (must not be null or undefined). * [12, 442, 'asdf', '-'] // one-dimension * ] * * constant.JSDATA_ARRAY_OBJECT: * [ * [ * {name: 'aa', value: 1212}, // Each item must be Object (must not be null or undefined). * {name: 'aa', value: 1212}, * {name: 'aa', value: 1212}, * {name: 'aa', value: 1212} * ] * ] * * constant.JSDATA_GEO: * [ * [['China', 122.23, 100.22], ...], * ] */ /** * [Empty Value Representation] * * Empty value in jsDataOb is specified by jsDataOb.getEmptyValue. * Empty value in dataTable (handsontable) is null. */ var jsDataFactory = {}; jsDataFactory.create = function () { var ob = dtLib.ob([]); dtLib.assign(ob, methods); ob.setType(constant.JSDATA_DIM_ARRAY); // init ob.fillJSDataByDataTable = makeThrottle(dataTableProcessor.fillJSData, ob); ob.fillJSDataByCodeInputs = makeThrottle(codeInputsProcessor.fillJSData, ob); return ob; }; var methods = []; // jshint ignore:line /** * @public * @return {string} type */ methods.getType = function (type) { return this._type; }; /** * @public * @param {string} type 值可为: * constant.JSDATA_DIM_ARRAY, * constant.JSDATA_ARRAY_OBJECT, * constant.JSDATA_GEO * @return {Object} jsDataOb */ methods.setType = function (type) { this._type = type; initDataMeta.call(this); return this; }; /** * @inner */ function initDataMeta() { var dataMeta = this._dataMeta || (this._dataMeta = {}); // Some default settings. if (!dataMeta.propertyMetas) { dataMeta.propertyMetas = []; } if (!dataMeta.itemDataType) { dataMeta.itemDataType = 'auto'; } if (!dataMeta.hasOwnProperty('emptyValue')) { dataMeta.emptyValue = constant.EC_EMPTY_VALUE; } // Default code info. if (!dataMeta.codeInfo) { dataMeta.codeInfo = { outputFormat: 'js', quotationMark: '\'', indentBase: 4, compress: false }; } } /** * Available after bindDataTable called. * * @public */ methods.fillJSDataByDataTable = $.noop; /** * Available after bindCodeInputs called. * * @public */ methods.fillJSDataByCodeInputs = $.noop; /** * @public */ methods.bindDataTable = function (htIns) { this._htIns = htIns; this.subscribe(onJSDataChanged, this); function onJSDataChanged(jsData, jsDataOb) { // 自己更新导致的jsData变化,不刷新自己。 if (!dtLib.checkValueInfoForConfirmed(jsDataOb, constant.UI_TABLE_DATA)) { dataTableProcessor.fillFromJSData(jsDataOb); htIns.render(); } } }; /** * @public */ methods.bindCodeInputs = function (codeInputsListViewModels) { this._codeInputsListViewModels = codeInputsListViewModels; this.subscribe(onJSDataChanged, this); function onJSDataChanged(jsData, jsDataOb) { // 自己引发的改变,不更新自己 if (!dtLib.checkValueInfoForConfirmed(jsDataOb, constant.UI_CODE_INPUTS)) { codeInputsProcessor.fillFromJSData(jsDataOb); } } }; /** * @pubilc */ methods.clear = function () { this([[]]); }; /** * @public */ methods.getHTIns = function () { return this._htIns; }; /** * @public */ methods.getCodeInputsListViewModels = function () { return this._codeInputsListViewModels; }; /** * @public */ methods.getEmptyValue = function (value) { return this._dataMeta.emptyValue; }; /** * @public * @param {number=} [dataMeta.emptyValue] 什么东西表示数据项的“空”。默认是'-',还可以设成null。 */ methods.setEmptyValue = function (value) { this._dataMeta.emptyValue = value; }; /** * @public */ methods.setCodeInfo = function (options) { dtLib.assign( this._dataMeta.codeInfo, options || {}, ['outputFormat', 'quotationMark', 'indentBase', 'compress'] ); }; /** * @public */ methods.getCodeStringifyParam = function (value) { return dtLib.assign( { errorMessage: 'Error: illegal data!', singleLineDepth: this.getSeriesDim() === 2 ? 1 : null }, this._dataMeta.codeInfo ); }; /** * 设置或得到二维数组的内层宽度定义。 * 这种设计是为了方便“多系列”的一起处理。 * (多系列一起贴进excel,直接输出结果) * * dimColStep的意义是,假设excel中有数据: * 6 5 4 3 2 * 16 15 14 13 12 * * 如果dimColStep是3, * 则从excel得到的jsData结构为: * [ * [[6, 5, 4], [16, 15, 14]], * [[3, 2], [13, 12]] * ] * * 如果dimColStep是null或undefined, * 则从excel得到的jsData结构为(降一维): * [ * [6, 16], * [5, 15], * [4, 14], * [3, 13], * [2, 12] * ] * * 如果dimColStep是'max',表示无限大, * 则从excel得到的jsData结构为: * [ * [[6, 5, 4, 3, 2], [16, 15, 14, 13, 12]] * ] * * 以上是type为constant.JSDATA_DIM_ARRAY的例子,type为constant.JSDATA_ARRAY_OBJECT时同理。 * * @public * @return {number} value */ methods.getDimColStep = function () { return this.getType() === constant.JSDATA_GEO ? 'max' : this._dataMeta.dimColStep; }; /** * @public * @param {(number|string)=} value 传值则表示设置。 * @return {Object} jsDataOb */ methods.setDimColStep = function (value) { this._dataMeta.dimColStep = value; return this; }; /** * itemDataType: 'string', 'number', 'auto'(默认。能转成number则为number,否则为string) * * @public */ methods.getItemDataType = function () { return this._dataMeta.itemDataType; }; /** * @public * @param {string=} [dataMeta.itemDataType] 值为:'string', 'number', 'auto' * (默认,即尽量为number,否则为string) */ methods.setItemDataType = function (value) { this._dataMeta.itemDataType = value; }; /** * @public * @param {string} jsDataType * @param {number=} dataTableColCount Auto calculate when ignore. * @return {Object} { * count: ..., >= 0 * colStep: ..., >= 1 * seriesDim: ... the same as getSeriesDim() * } */ methods.getSeriesInfo = function (jsDataType, dataTableColCount) { if (dataTableColCount == null) { dataTableColCount = this.getColCount(); } var dimColStep = this.getDimColStep(); var count; var colStep; if (jsDataType === constant.JSDATA_DIM_ARRAY) { if (dimColStep === 'max') { colStep = dataTableColCount; count = dataTableColCount ? 1 : 0; } else if (dimColStep) { colStep = dimColStep; count = Math.ceil(dataTableColCount / colStep); } else { count = dataTableColCount; colStep = 1; } } else if (jsDataType === constant.JSDATA_ARRAY_OBJECT) { var propertyMetas = this.getPropertyMetas(); colStep = propertyMetas.length; count = Math.ceil(dataTableColCount / colStep); } else if (jsDataType === constant.JSDATA_GEO) { colStep = dataTableColCount; count = dataTableColCount ? 1 : 0; } else { dtLib.assert(false, 'Invalid jsDataType!'); } return { count: count, colStep: colStep, seriesDim: this.getSeriesDim() }; }; /** * @pubilc */ methods.getSeriesDim = function () { return this.getType() === constant.JSDATA_ARRAY_OBJECT ? 2 : (this.getDimColStep() ? 2 : 1); }; /** * @public */ methods.getPropertyMetas = function () { return dtLib.clone(this._dataMeta.propertyMetas); }; /** * @public * @param {Array.<Object>} [dataMeta.propertyMetas] constant.JSDATA_ARRAY_OBJECT 时有效 * Array每项值为 { * itemDataType: 取值值同上, * propertyName: string * } */ methods.setPropertyMetas = function (inputPropertyMetas) { var propertyMetas = this._dataMeta.propertyMetas; var i = 0; for (var len = inputPropertyMetas.length; i < len; i++) { var meta = propertyMetas[i] || ( propertyMetas[i] = {propertyName: '', itemDataType: 'auto'} ); dtLib.assign(meta, inputPropertyMetas[i]); } // Remove remains. propertyMetas.splice(i, propertyMetas.length - i); }; /** * @pubilc */ methods.getRowCount = function (rowBuffer) { var rowMax = 0; var jsData = this(); // Get rowMax for (var k = 0, lenk = jsData.length; k < lenk; k++) { var oneSeriesData = jsData[k] || []; if (rowMax < oneSeriesData.length) { rowMax = oneSeriesData.length; } } if (rowBuffer == null) { rowBuffer = 2; } return rowMax + rowBuffer; }; /** * @pubilc */ methods.getColCount = function (colBuffer) { var colMax = 0; var seriesDim = this.getSeriesDim(); var jsData = this(); // Get colMax if (seriesDim === 1) { colMax = jsData.length; } else { var dimColStep = this.getDimColStep(); // Find max length. colMax = dimColStep === 'max' ? getColMax[this.getType()](jsData) : jsData.length * dimColStep; } if (colBuffer == null) { colBuffer = 2; } return colMax + colBuffer; }; /** * @public */ methods.getColDescBySeries = function (seriesIndex, jsDataType) { var seriesInfo = this.getSeriesInfo(jsDataType); var colStart = seriesIndex * seriesInfo.colStep; var colEnd = colStart + seriesInfo.colStep - 1; return { start: this.getColDesc(colStart), end: this.getColDesc(colEnd), single: colStart === colEnd }; }; /** * @public */ methods.getColDescInSeries = function (seriesIndexList, offset, jsDataType) { var seriesInfo = this.getSeriesInfo(jsDataType); offset = offset % seriesInfo.colStep; var ret = []; for (var i = 0, len = seriesIndexList.length; i < len; i++) { var colStart = seriesIndexList[i] * seriesInfo.colStep; ret.push(this.getColDesc(colStart + offset)); } return ret; }; /** * @public */ methods.getColDesc = function (colIndex) { // colIndex 可以超出边界 return this._htIns.getColHeader(colIndex); }; /** * @inner * @type {Object} Contains functions */ var getColMax = {}; // jshint ignore:line function arrayGetColMax(jsData) { // Find max length. var colMax = 0; var oneSeriesData = jsData[0] || []; for (var j = 0, lenj = oneSeriesData.length; j < lenj; j++) { var line = oneSeriesData[j] || []; if (colMax < line.length) { colMax = line.length; } } return colMax; }; getColMax[constant.JSDATA_DIM_ARRAY] = arrayGetColMax; getColMax[constant.JSDATA_ARRAY_OBJECT] = function (jsData) { // Find max length. var colMax = 0; var arrayObject = jsData[0] || []; for (var j = 0, lenj = arrayObject.length; j < lenj; j++) { var obj = arrayObject[j] || {}; var propertyCount = helper.objectPropertyCount(obj); if (colMax < propertyCount) { colMax = propertyCount; } } return colMax; }; getColMax[constant.JSDATA_GEO] = arrayGetColMax; /** * 统一throttle而非在调用点throttle,是为了让所有此方法的调用有一致的时序。 * * @inner */ function makeThrottle(fillJSData, jsDataOb) { return dtLib.throttle( dtLib.curry(fillJSData, jsDataOb), constant.JSDATA_UPDATE_DELAY, true, true ); } return jsDataFactory; });