/** | |
* @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; | |
}); |