blob: 25f2fb72e0babbd253cc4c618bb31302f7bbbab2 [file] [log] [blame]
/**
* @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;
});