From c4be541632580f2a7eb7008e3f616d22f64ebdfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B4=A2=E4=B8=98?= Date: Sun, 6 Jul 2014 11:46:35 +0800 Subject: [PATCH] init data project --- .gitignore | 23 + .spmignore | 8 + HISTORY.md | 7 + README.md | 27 + dist/bui-data/1.1.0/index-debug.js | 2620 ++++++++++++++++++++++++++++ dist/bui-data/1.1.0/index.js | 1 + examples/index.md | 11 + index.js | 18 + package.json | 29 + src/abstractstore.js | 504 ++++++ src/node.js | 91 + src/proxy.js | 446 +++++ src/sortable.js | 163 ++ src/store.js | 922 ++++++++++ src/treestore.js | 606 +++++++ tests/data-spec.js | 10 + 16 files changed, 5486 insertions(+) create mode 100644 .gitignore create mode 100644 .spmignore create mode 100644 HISTORY.md create mode 100644 README.md create mode 100644 dist/bui-data/1.1.0/index-debug.js create mode 100644 dist/bui-data/1.1.0/index.js create mode 100644 examples/index.md create mode 100644 index.js create mode 100644 package.json create mode 100644 src/abstractstore.js create mode 100644 src/node.js create mode 100644 src/proxy.js create mode 100644 src/sortable.js create mode 100644 src/store.js create mode 100644 src/treestore.js create mode 100644 tests/data-spec.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3aca77d --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +*.iml +.idea/ +.ipr +.iws +*~ +~* +*.diff +*.patch +*.bak +.DS_Store +Thumbs.db +.project +.*proj +.svn/ +*.swp +*.swo +*.pyc +*.pyo +.build +node_modules +_site +sea-modules +.cache diff --git a/.spmignore b/.spmignore new file mode 100644 index 0000000..b9a3a51 --- /dev/null +++ b/.spmignore @@ -0,0 +1,8 @@ +dist +_site +sea-modules +node_modules +.git +tests +examples +test diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..dfd543f --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,7 @@ +# History + +--- + +## 1.1.0 + +`new` It is the first version of data. diff --git a/README.md b/README.md new file mode 100644 index 0000000..38242c1 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# data + +--- + +[![spm version](http://spmjs.io/badge/data)](http://spmjs.io/package/data) + +An awesome spm package! + +--- + +## Install + +``` +$ spm install data --save +``` + +## Usage + +```js +var data = require('data'); +// use data +``` + +## Api + +Here is more details. + diff --git a/dist/bui-data/1.1.0/index-debug.js b/dist/bui-data/1.1.0/index-debug.js new file mode 100644 index 0000000..1ba0085 --- /dev/null +++ b/dist/bui-data/1.1.0/index-debug.js @@ -0,0 +1,2620 @@ +define("bui-data/1.1.0/index-debug", ["bui-common/1.1.0/index-debug", "bui-data/1.1.0/src/sortable-debug", "bui-data/1.1.0/src/proxy-debug", "bui-data/1.1.0/src/abstractstore-debug", "bui-data/1.1.0/src/store-debug", "bui-data/1.1.0/src/node-debug", "bui-data/1.1.0/src/treestore-debug"], function(require, exports, module) { + /** + * @fileOverview Data 命名空间的入口文件 + * @ignore + */ + var BUI = require("bui-common/1.1.0/index-debug"), + Data = BUI.namespace('Data'); + BUI.mix(Data, { + Sortable: require("bui-data/1.1.0/src/sortable-debug"), + Proxy: require("bui-data/1.1.0/src/proxy-debug"), + AbstractStore: require("bui-data/1.1.0/src/abstractstore-debug"), + Store: require("bui-data/1.1.0/src/store-debug"), + Node: require("bui-data/1.1.0/src/node-debug"), + TreeStore: require("bui-data/1.1.0/src/treestore-debug") + }); + module.exports = Data; +}); +define("bui-data/1.1.0/src/sortable-debug", [], function(require, exports, module) { + /** + * @fileOverview 可排序扩展类 + * @ignore + */ + var ASC = 'ASC', + DESC = 'DESC'; + /** + * 排序扩展方法,无法直接使用 + * 请在继承了 {@link BUI.Base}的类上使用 + * @class BUI.Data.Sortable + * @extends BUI.Base + */ + var sortable = function() {}; + sortable.ATTRS = { + /** + * 比较函数 + * @cfg {Function} compareFunction + * 函数原型 function(v1,v2),比较2个字段是否相等 + * 如果是字符串则按照本地比较算法,否则使用 > ,== 验证 + */ + compareFunction: { + value: function(v1, v2) { + if (v1 === undefined) { + v1 = ''; + } + if (v2 === undefined) { + v2 = ''; + } + if (BUI.isString(v1)) { + return v1.localeCompare(v2); + } + if (v1 > v2) { + return 1; + } else if (v1 === v2) { + return 0; + } else { + return -1; + } + } + }, + /** + * 排序字段 + * @cfg {String} sortField + */ + /** + * 排序字段 + * @type {String} + */ + sortField: {}, + /** + * 排序方向,'ASC'、'DESC' + * @cfg {String} [sortDirection = 'ASC'] + */ + /** + * 排序方向,'ASC'、'DESC' + * @type {String} + */ + sortDirection: { + value: 'ASC' + }, + /** + * 排序信息 + *
    + *
  1. field: 排序字段
  2. + *
  3. direction: 排序方向,ASC(默认),DESC
  4. + *
+ * @cfg {Object} sortInfo + */ + /** + * 排序信息 + *
    + *
  1. field: 排序字段
  2. + *
  3. direction: 排序方向,ASC(默认),DESC
  4. + *
+ * @type {Object} + */ + sortInfo: { + getter: function() { + var _self = this, + field = _self.get('sortField'); + return { + field: field, + direction: _self.get('sortDirection') + }; + }, + setter: function(v) { + var _self = this; + _self.set('sortField', v.field); + _self.set('sortDirection', v.direction); + } + } + }; + BUI.augment(sortable, { + compare: function(obj1, obj2, field, direction) { + var _self = this, + dir; + field = field || _self.get('sortField'); + direction = direction || _self.get('sortDirection'); + //如果未指定排序字段,或方向,则按照默认顺序 + if (!field || !direction) { + return 1; + } + dir = direction === ASC ? 1 : -1; + return _self.get('compareFunction')(obj1[field], obj2[field]) * dir; + }, + /** + * 获取排序的集合 + * @protected + * @return {Array} 排序集合 + */ + getSortData: function() {}, + /** + * 排序数据 + * @param {String|Array} field 排序字段或者数组 + * @param {String} direction 排序方向 + * @param {Array} records 排序 + * @return {Array} + */ + sortData: function(field, direction, records) { + var _self = this, + records = records || _self.getSortData(); + if (BUI.isArray(field)) { + records = field; + field = null; + } + field = field || _self.get('sortField'); + direction = direction || _self.get('sortDirection'); + _self.set('sortField', field); + _self.set('sortDirection', direction); + if (!field || !direction) { + return records; + } + records.sort(function(obj1, obj2) { + return _self.compare(obj1, obj2, field, direction); + }); + return records; + } + }); + module.export = sortable; +}); +define("bui-data/1.1.0/src/proxy-debug", ["bui-data/1.1.0/src/sortable-debug"], function(require, exports, module) { + var Sortable = require("bui-data/1.1.0/src/sortable-debug"); + /** + * 数据代理对象,加载数据, + * 一般不直接使用,在store里面决定使用什么类型的数据代理对象 + * @class BUI.Data.Proxy + * @extends BUI.Base + * @abstract + */ + var proxy = function(config) { + proxy.superclass.constructor.call(this, config); + }; + proxy.ATTRS = {}; + BUI.extend(proxy, BUI.Base); + BUI.augment(proxy, { + /** + * @protected + * 读取数据的方法,在子类中覆盖 + */ + _read: function(params, callback) {}, + /** + * 读数据 + * @param {Object} params 键值对形式的参数 + * @param {Function} callback 回调函数,函数原型 function(data){} + * @param {Object} scope 回调函数的上下文 + */ + read: function(params, callback, scope) { + var _self = this; + scope = scope || _self; + _self._read(params, function(data) { + callback.call(scope, data); + }); + }, + /** + * @protected + * 保存数据的方法,在子类中覆盖 + */ + _save: function(ype, data, callback) {}, + /** + * 保存数据 + * @param {String} type 类型,包括,add,update,remove,all几种类型 + * @param {Object} saveData 键值对形式的参数 + * @param {Function} callback 回调函数,函数原型 function(data){} + * @param {Object} scope 回调函数的上下文 + */ + save: function(type, saveData, callback, scope) { + var _self = this; + scope = scope || _self; + _self._save(type, saveData, function(data) { + callback.call(scope, data); + }); + } + }); + var TYPE_AJAX = { + READ: 'read', + ADD: 'add', + UPDATE: 'update', + REMOVE: 'remove', + SAVE_ALL: 'all' + }; + /** + * 异步加载数据的代理 + * @class BUI.Data.Proxy.Ajax + * @extends BUI.Data.Proxy + */ + var ajaxProxy = function(config) { + ajaxProxy.superclass.constructor.call(this, config); + }; + ajaxProxy.ATTRS = BUI.mix(true, proxy.ATTRS, { + /** + * 限制条数 + * @cfg {String} [limitParam='limit'] + */ + /** + * 限制条数 + * @type {String} + * @default 'limit' + */ + limitParam: { + value: 'limit' + }, + /** + * 起始纪录代表的字段 + * @cfg {String} [startParam='start'] + */ + /** + * 起始纪录代表的字段 + * @type {String} + */ + startParam: { + value: 'start' + }, + /** + * 页码的字段名 + * @cfg {String} [pageIndexParam='pageIndex'] + */ + /** + * 页码的字段名 + * @type {String} + * @default 'pageIndex' + */ + pageIndexParam: { + value: 'pageIndex' + }, + /** + * 保存类型的字段名,如果每种保存类型未设置对应的Url,则附加参数 + * @type {Object} + */ + saveTypeParam: { + value: 'saveType' + }, + /** + * 保存数据放到的字段名称 + * @type {String} + */ + saveDataParam: {}, + /** + * 传递到后台,分页开始的页码,默认从0开始 + * @type {Number} + */ + pageStart: { + value: 0 + }, + /** + * 加载数据时,返回的格式,目前只支持"json、jsonp"格式
+ * @cfg {String} [dataType='json'] + */ + /** + * 加载数据时,返回的格式,目前只支持"json、jsonp"格式
+ * @type {String} + * @default "json" + */ + dataType: { + value: 'json' + }, + /** + * 获取数据的方式,'GET'或者'POST',默认为'GET' + * @cfg {String} [method='GET'] + */ + /** + * 获取数据的方式,'GET'或者'POST',默认为'GET' + * @type {String} + * @default 'GET' + */ + method: { + value: 'GET' + }, + /** + * 异步请求的所有自定义参数,开放的其他属性用于快捷使用,如果有特殊参数配置,可以使用这个属性,
+ * 不要使用success和error的回调函数,会覆盖默认的处理数据的函数 + * @cfg {Object} ajaxOptions + */ + /** + * 异步请求的所有自定义参数 + * @type {Object} + */ + ajaxOptions: { + value: {} + }, + /** + * 是否使用Cache + * @type {Boolean} + */ + cache: { + value: false + }, + /** + * 保存数据的配置信息 + * @type {Object} + */ + save: {}, + /** + * 加载数据的链接 + * @cfg {String} url + * @required + */ + /** + * 加载数据的链接 + * @type {String} + * @required + */ + url: {} + }); + BUI.extend(ajaxProxy, proxy); + BUI.augment(ajaxProxy, { + _processParams: function(params) { + var _self = this, + pageStart = _self.get('pageStart'), + arr = ['start', 'limit', 'pageIndex']; + if (params.pageIndex != null) { + params.pageIndex = params.pageIndex + pageStart; + } + BUI.each(arr, function(field) { + var fieldParam = _self.get(field + 'Param'); + if (fieldParam !== field) { + params[fieldParam] = params[field]; + delete params[field]; + } + }); + }, + //获取异步请求的url + _getUrl: function(type) { + var _self = this, + save = _self.get('save'), + url; + if (type === TYPE_AJAX.READ) { //获取数据,直接返回 url + return _self.get('url'); + } + //如果不存在保存参数,则返回 url + if (!save) { + return _self.get('url') + } + if (BUI.isString(save)) { + return save; + } + url = save[type + 'Url']; + if (!url) { + url = _self.get('url'); + } + return url; + }, + //根据类型附加额外的参数 + _getAppendParams: function(type) { + var _self = this, + save, + saveTypeParam, + rst = null; + if (type == TYPE_AJAX.READ) { + return rst; + } + save = _self.get('save'); + saveTypeParam = _self.get('saveTypeParam'); + if (save && !save[type + 'Url']) { + rst = {}; + rst[saveTypeParam] = type; + } + return rst; + }, + /** + * @protected + * @private + */ + _read: function(params, callback) { + var _self = this, + cfg; + params = BUI.cloneObject(params); + _self._processParams(params); + cfg = _self._getAjaxOptions(TYPE_AJAX.READ, params); + _self._ajax(cfg, callback); + }, + //获取异步请求的选项 + _getAjaxOptions: function(type, params) { + var _self = this, + ajaxOptions = _self.get('ajaxOptions'), + url = _self._getUrl(type), + cfg; + BUI.mix(params, _self._getAppendParams(type)); + cfg = BUI.merge({ + url: url, + type: _self.get('method'), + dataType: _self.get('dataType'), + data: params, + cache: _self.get('cache') + }, ajaxOptions); + return cfg; + }, + //异步请求 + _ajax: function(cfg, callback) { + var _self = this, + success = cfg.success, + error = cfg.error; + //复写success + cfg.success = function(data) { + success && success(data); + callback(data); + }; + //复写错误 + cfg.error = function(jqXHR, textStatus, errorThrown) { + error && error(jqXHR, textStatus, errorThrown); + var result = { + exception: { + status: textStatus, + errorThrown: errorThrown, + jqXHR: jqXHR + } + }; + callback(result); + } + $.ajax(cfg); + }, + _save: function(type, data, callback) { + var _self = this, + cfg; + cfg = _self._getAjaxOptions(type, data); + _self._ajax(cfg, callback); + } + }); + /** + * 读取缓存的代理 + * @class BUI.Data.Proxy.Memery + * @extends BUI.Data.Proxy + * @mixins BUI.Data.Sortable + */ + var memeryProxy = function(config) { + memeryProxy.superclass.constructor.call(this, config); + }; + memeryProxy.ATTRS = { + /** + * 匹配的字段名 + * @type {Array} + */ + matchFields: { + value: [] + } + }; + BUI.extend(memeryProxy, proxy); + BUI.mixin(memeryProxy, [Sortable]); + BUI.augment(memeryProxy, { + /** + * @protected + * @ignore + */ + _read: function(params, callback) { + var _self = this, + pageable = params.pageable, + start = params.start, + sortField = params.sortField, + sortDirection = params.sortDirection, + limit = params.limit, + data = _self.get('data'), + rows = []; + data = _self._getMatches(params); + _self.sortData(sortField, sortDirection); + if (limit) { //分页时 + rows = data.slice(start, start + limit); + callback({ + rows: rows, + results: data.length + }); + } else { //不分页时 + rows = data.slice(start); + callback(rows); + } + }, + //获取匹配函数 + _getMatchFn: function(params, matchFields) { + var _self = this; + return function(obj) { + var result = true; + BUI.each(matchFields, function(field) { + if (params[field] != null && !(params[field] === obj[field])) { + result = false; + return false; + } + }); + return result; + } + }, + //获取匹配的值 + _getMatches: function(params) { + var _self = this, + matchFields = _self.get('matchFields'), + matchFn, + data = _self.get('data') || []; + if (params && matchFields.length) { + matchFn = _self._getMatchFn(params, matchFields); + data = BUI.Array.filter(data, matchFn); + } + return data; + }, + /** + * @protected + * 保存修改的数据 + */ + _save: function(type, saveData, callback) { + var _self = this, + data = _self.get('data'); + if (type == TYPE_AJAX.ADD) { + data.push(saveData); + } else if (type == TYPE_AJAX.REMOVE) { + BUI.Array.remove(data, saveData); + } else if (type == TYPE_AJAX.SAVE_ALL) { + BUI.each(saveData.add, function(item) { + data.push(item); + }); + BUI.each(saveData.remove, function(item) { + BUI.Array.remove(data, item); + }); + } + } + }); + proxy.Ajax = ajaxProxy; + proxy.Memery = memeryProxy; + module.exports = proxy; +}); +define("bui-data/1.1.0/src/abstractstore-debug", ["bui-common/1.1.0/index-debug", "bui-data/1.1.0/src/proxy-debug", "bui-data/1.1.0/src/sortable-debug"], function(require, exports, module) { + /** + * @fileOverview 抽象数据缓冲类 + * @ignore + */ + var BUI = require("bui-common/1.1.0/index-debug"), + Proxy = require("bui-data/1.1.0/src/proxy-debug"); + /** + * @class BUI.Data.AbstractStore + * 数据缓冲抽象类,此类不进行实例化 + * @extends BUI.Base + */ + function AbstractStore(config) { + AbstractStore.superclass.constructor.call(this, config); + this._init(); + } + AbstractStore.ATTRS = { + /** + * 创建对象时是否自动加载 + *

+     *   var store = new Data.Store({
+     *     url : 'data.php',  //设置加载数据的URL
+     *     autoLoad : true    //创建Store时自动加载数据
+     *   });
+     * 
+ * @cfg {Boolean} [autoLoad=false] + */ + autoLoad: { + value: false + }, + /** + * 是否服务器端过滤数据,如果设置此属性,当调用filter()函数时发送请求 + * @type {Object} + */ + remoteFilter: { + value: false + }, + /** + * 上次查询的参数 + * @type {Object} + * @readOnly + */ + lastParams: { + shared: false, + value: {} + }, + /** + * 初始化时查询的参数,在初始化时有效 + *

+     * var store = new Data.Store({
+     *     url : 'data.php',  //设置加载数据的URL
+     *     autoLoad : true,    //创建Store时自动加载数据
+     *     params : {         //设置请求时的参数
+     *       id : '1',
+     *       type : '1'
+     *     }
+     *   });
+     * 
+ * @cfg {Object} params + */ + params: {}, + /** + * 数据代理对象,用于加载数据的ajax配置,{@link BUI.Data.Proxy} + *

+     *   var store = new Data.Store({
+     *     url : 'data.php',  //设置加载数据的URL
+     *     autoLoad : true,    //创建Store时自动加载数据
+     *     proxy : {
+     *       method : 'post',
+     *       dataType : 'jsonp'
+     *     }
+     *   });
+     * 
+ * @cfg {Object|BUI.Data.Proxy} proxy + */ + proxy: { + shared: false, + value: {} + }, + /** + * 请求数据的地址,通过ajax加载数据, + * 此参数设置则加载远程数据 + * ** 你可以设置在proxy外部 ** + *

+     *   var store = new Data.Store({
+     *     url : 'data.php',  //设置加载数据的URL
+     *     autoLoad : true,    //创建Store时自动加载数据
+     *     proxy : {
+     *       method : 'post',
+     *       dataType : 'jsonp'
+     *     }
+     *   });
+     * 
+ * ** 你也可以设置在proxy上 ** + *

+     *   var store = new Data.Store({
+     *     autoLoad : true,    //创建Store时自动加载数据
+     *     proxy : {
+     *       url : 'data.php',  //设置加载数据的URL
+     *       method : 'post',
+     *       dataType : 'jsonp'
+     *     }
+     *   });
+     * 
+ * 否则把 {BUI.Data.Store#cfg-data}作为本地缓存数据加载 + * @cfg {String} url + */ + /** + * 请求数据的url + *

+     *   //更改url
+     *   store.get('proxy').set('url',url);
+     * 
+ * @type {String} + */ + url: {}, + events: { + value: [ + /** + * 数据接受改变,所有增加、删除、修改的数据记录清空 + * @name BUI.Data.Store#acceptchanges + * @event + */ + 'acceptchanges', + /** + * 当数据加载完成后 + * @name BUI.Data.Store#load + * @event + * @param {jQuery.Event} e 事件对象,包含加载数据时的参数 + */ + 'load', + /** + * 当数据加载前 + * @name BUI.Data.Store#beforeload + * @event + */ + 'beforeload', + /** + * 发生在,beforeload和load中间,数据已经获取完成,但是还未触发load事件,用于获取返回的原始数据 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.data 从服务器端返回的数据 + */ + 'beforeprocessload', + /** + * 当添加数据时触发该事件 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.record 添加的数据 + */ + 'add', + /** + * 加载数据发生异常时触发 + * @event + * @name BUI.Data.Store#exception + * @param {jQuery.Event} e 事件对象 + * @param {String|Object} e.error 加载数据时返回的错误信息或者加载数据失败,浏览器返回的信息(httpResponse 对象 的textStatus) + * @param {String} e.responseText 网络或者浏览器加载数据发生错误是返回的httpResponse 对象的responseText + */ + 'exception', + /** + * 当删除数据是触发该事件 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.data 删除的数据 + */ + 'remove', + /** + * 当更新数据指定字段时触发该事件 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.record 更新的数据 + * @param {Object} e.field 更新的字段 + * @param {Object} e.value 更新的值 + */ + 'update', + /** + * 前端发生排序时触发 + * @name BUI.Data.Store#localsort + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.field 排序的字段 + * @param {Object} e.direction 排序的方向 'ASC','DESC' + */ + 'localsort', + /** + * 前端发生过滤时触发 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Array} e.data 过滤完成的数据 + * @param {Function} e.filter 过滤器 + */ + 'filtered' + ] + }, + /** + * 本地数据源,使用本地数据源时会使用{@link BUI.Data.Proxy.Memery} + * @cfg {Array} data + */ + /** + * 本地数据源 + * @type {Array} + */ + data: { + setter: function(data) { + var _self = this, + proxy = _self.get('proxy'); + if (proxy.set) { + proxy.set('data', data); + } else { + proxy.data = data; + } + //设置本地数据时,把autoLoad置为true + _self.set('autoLoad', true); + } + } + }; + BUI.extend(AbstractStore, BUI.Base); + BUI.augment(AbstractStore, { + /** + * 是否是数据缓冲对象,用于判断对象 + * @type {Boolean} + */ + isStore: true, + /** + * @private + * 初始化 + */ + _init: function() { + var _self = this; + _self.beforeInit(); + //初始化结果集 + _self._initParams(); + _self._initProxy(); + _self._initData(); + }, + /** + * @protected + * 初始化之前 + */ + beforeInit: function() {}, + //初始化数据,如果默认加载数据,则加载数据 + _initData: function() { + var _self = this, + autoLoad = _self.get('autoLoad'); + if (autoLoad) { + _self.load(); + } + }, + //初始化查询参数 + _initParams: function() { + var _self = this, + lastParams = _self.get('lastParams'), + params = _self.get('params'); + //初始化 参数 + BUI.mix(lastParams, params); + }, + /** + * @protected + * 初始化数据代理类 + */ + _initProxy: function() { + var _self = this, + url = _self.get('url'), + proxy = _self.get('proxy'); + if (!(proxy instanceof Proxy)) { + if (url) { + proxy.url = url; + } + //异步请求的代理类 + if (proxy.type === 'ajax' || proxy.url) { + proxy = new Proxy.Ajax(proxy); + } else { + proxy = new Proxy.Memery(proxy); + } + _self.set('proxy', proxy); + } + }, + /** + * 加载数据 + *

+     *  //一般调用
+     *  store.load(params);
+     *
+     *  //使用回调函数
+     *  store.load(params,function(data){
+     *
+     *  });
+     *
+     *  //load有记忆参数的功能
+     *  store.load({id : '123',type="1"});
+     *  //下一次调用
+     *  store.load();默认使用上次的参数,可以对对应的参数进行覆盖
+     * 
+ * @param {Object} params 参数键值对 + * @param {Function} fn 回调函数,默认为空 + */ + load: function(params, callback) { + var _self = this, + proxy = _self.get('proxy'), + lastParams = _self.get('lastParams'); + BUI.mix(lastParams, _self.getAppendParams(), params); + _self.fire('beforeload', { + params: lastParams + }); + //防止异步请求未结束,又发送新请求回调参数错误 + params = BUI.cloneObject(lastParams); + proxy.read(lastParams, function(data) { + _self.onLoad(data, params); + if (callback) { + callback(data, params); + } + }, _self); + }, + /** + * 触发过滤 + * @protected + */ + onFiltered: function(data, filter) { + var _self = this; + _self.fire('filtered', { + data: data, + filter: filter + }); + }, + /** + * 加载完数据 + * @protected + * @template + */ + onLoad: function(data, params) { + var _self = this; + var processResult = _self.processLoad(data, params); + //如果处理成功,返回错误时,不进行后面的处理 + if (processResult) { + _self.afterProcessLoad(data, params); + } + }, + /** + * 获取当前缓存的纪录 + */ + getResult: function() {}, + /** + * 过滤数据,此函数的执行同属性 remoteFilter关联密切 + * + * - remoteFilter == true时:此函数只接受字符串类型的过滤参数,将{filter : filterStr}参数传输到服务器端 + * - remoteFilter == false时:此函数接受比对函数,只有当函数返回true时生效 + * + * @param {Function|String} fn 过滤函数 + * @return {Array} 过滤结果 + */ + filter: function(filter) { + var _self = this, + remoteFilter = _self.get('remoteFilter'), + result; + filter = filter || _self.get('filter'); + if (remoteFilter) { + _self.load({ + filter: filter + }); + } else if (filter) { + _self.set('filter', filter); + //如果result有值时才会进行filter + if (_self.getResult().length > 0) { + result = _self._filterLocal(filter); + _self.onFiltered(result, filter); + } + } + }, + /** + * @protected + * 过滤缓存的数据 + * @param {Function} fn 过滤函数 + * @return {Array} 过滤结果 + */ + _filterLocal: function(fn) {}, + /** + * 获取过滤后的数据,仅当本地过滤(remoteFilter = false)时有效 + * @return {Array} 过滤过的数据 + */ + getFilterResult: function() { + var filter = this.get('filter'); + if (filter) { + return this._filterLocal(filter); + } else { + return this.getResult(); + } + }, + _clearLocalFilter: function() { + this.set('filter', null); + }, + /** + * 清理过滤 + */ + clearFilter: function() { + var _self = this, + remoteFilter = _self.get('remoteFilter'), + result; + if (remoteFilter) { + _self.load({ + filter: '' + }); + } else { + _self._clearLocalFilter(); + result = _self.getFilterResult(); + _self.onFiltered(result, null); + } + }, + /** + * @private + * 加载完数据处理数据 + */ + processLoad: function(data, params) { + var _self = this, + hasErrorField = _self.get('hasErrorProperty'); + _self.fire('beforeprocessload', { + data: data + }); + //获取的原始数据 + _self.fire('beforeProcessLoad', data); + if (data[hasErrorField] || data.exception) { + _self.onException(data); + return false; + } + return true; + }, + /** + * @protected + * @template + * 处理数据后 + */ + afterProcessLoad: function(data, params) {}, + /** + * @protected + * 处理错误函数 + * @param {*} data 出错对象 + */ + onException: function(data) { + var _self = this, + errorProperty = _self.get('errorProperty'), + obj = {}; + //网络异常、转码错误之类,发生在json获取或转变时 + if (data.exception) { + obj.type = 'exception'; + obj[errorProperty] = data.exception; + } else { //用户定义的错误 + obj.type = 'error'; + obj[errorProperty] = data[errorProperty]; + } + _self.fire('exception', obj); + }, + /** + * 是否包含数据 + * @return {Boolean} + */ + hasData: function() {}, + /** + * 获取附加的参数 + * @template + * @protected + * @return {Object} 附加的参数 + */ + getAppendParams: function() { + return {}; + } + }); + module.exports = AbstractStore; +}); +define("bui-data/1.1.0/src/store-debug", ["bui-data/1.1.0/src/proxy-debug", "bui-data/1.1.0/src/abstractstore-debug", "bui-data/1.1.0/src/sortable-debug", "bui-common/1.1.0/index-debug"], function(require, exports, module) { + /** + * @fileOverview 数据缓冲对象 + * @author dxq613@gmail.com + * @ignore + */ + var Proxy = require("bui-data/1.1.0/src/proxy-debug"), + AbstractStore = require("bui-data/1.1.0/src/abstractstore-debug"), + Sortable = require("bui-data/1.1.0/src/sortable-debug"); + //移除数据 + function removeAt(index, array) { + if (index < 0) { + return; + } + var records = array, + record = records[index]; + records.splice(index, 1); + return record; + } + + function removeFrom(record, array) { + var index = BUI.Array.indexOf(record, array); + if (index >= 0) { + removeAt(index, array); + } + } + + function contains(record, array) { + return BUI.Array.indexOf(record, array) !== -1; + } + /** + * 用于加载数据,缓冲数据的类 + *

+ * + *

+ * ** 缓存静态数据 ** + *

+   *  var store = new Store({
+   *    data : [{},{}]
+   *  });
+   * 
+ * ** 异步加载数据 ** + *

+   *  var store = new Store({
+   *    url : 'data.json',
+   *    autoLoad : true,
+   *    params : {id : '123'},
+   *    sortInfo : {
+   *      field : 'id',
+   *      direction : 'ASC' //ASC,DESC
+   *    }
+   *  });
+   * 
+ * + * @class BUI.Data.Store + * @extends BUI.Data.AbstractStore + * @mixins BUI.Data.Sortable + */ + var store = function(config) { + store.superclass.constructor.call(this, config); + //this._init(); + }; + store.ATTRS = { + /** + * 保存数据时,是否自动更新数据源的数据,常用于添加、删除、更改数据后重新加载数据。 + * @cfg {Boolean} autoSync + */ + autoSync: { + value: false + }, + /** + * 当前页码 + * @cfg {Number} [currentPage=0] + * @ignore + */ + /** + * 当前页码 + * @type {Number} + * @ignore + * @readOnly + */ + currentPage: { + value: 0 + }, + /** + * 删除掉的纪录 + * @readOnly + * @private + * @type {Array} + */ + deletedRecords: { + shared: false, + value: [] + }, + /** + * 错误字段,包含在返回信息中表示错误信息的字段 + *

+     *   //可以修改接收的后台参数的含义
+     *   var store = new Store({
+     *     url : 'data.json',
+     *     errorProperty : 'errorMsg', //存放错误信息的字段(error)
+     *     hasErrorProperty : 'isError', //是否错误的字段(hasError)
+     *     root : 'data',               //存放数据的字段名(rows)
+     *     totalProperty : 'total'     //存放记录总数的字段名(results)
+     *   });
+     * 
+ * @cfg {String} [errorProperty='error'] + */ + /** + * 错误字段 + * @type {String} + * @ignore + */ + errorProperty: { + value: 'error' + }, + /** + * 是否存在错误,加载数据时如果返回错误,此字段表示有错误发生 + *

+     *   //可以修改接收的后台参数的含义
+     *   var store = new Store({
+     *     url : 'data.json',
+     *     errorProperty : 'errorMsg', //存放错误信息的字段(error)
+     *     hasErrorProperty : 'isError', //是否错误的字段(hasError)
+     *     root : 'data',               //存放数据的字段名(rows)
+     *     totalProperty : 'total'     //存放记录总数的字段名(results)
+     *   });
+     * 
+ * @cfg {String} [hasErrorProperty='hasError'] + */ + /** + * 是否存在错误 + * @type {String} + * @default 'hasError' + * @ignore + */ + hasErrorProperty: { + value: 'hasError' + }, + /** + * 对比2个对象是否相当,在去重、更新、删除,查找数据时使用此函数 + * @default + * function(obj1,obj2){ + * return obj1 == obj2; + * } + * @type {Object} + * @example + * function(obj1 ,obj2){ + * //如果id相等,就认为2个数据相等,可以在添加对象时去重 + * //更新对象时,仅提供改变的字段 + * return obj1.id == obj2.id; + * } + * + */ + matchFunction: { + value: function(obj1, obj2) { + return obj1 == obj2; + } + }, + /** + * 更改的纪录集合 + * @type {Array} + * @private + * @readOnly + */ + modifiedRecords: { + shared: false, + value: [] + }, + /** + * 新添加的纪录集合,只读 + * @type {Array} + * @private + * @readOnly + */ + newRecords: { + shared: false, + value: [] + }, + /** + * 是否远程排序,默认状态下内存排序 + * - 由于当前Store存储的不一定是数据源的全集,所以此配置项需要重新读取数据 + * - 在分页状态下,进行远程排序,会进行全集数据的排序,并返回首页的数据 + * - remoteSort为 false的情况下,仅对当前页的数据进行排序 + * @cfg {Boolean} [remoteSort=false] + */ + remoteSort: { + value: false + }, + /** + * 缓存的数据,包含以下几个字段 + *
    + *
  1. rows: 数据集合
  2. + *
  3. results: 总的数据条数
  4. + *
+ * @type {Object} + * @private + * @readOnly + */ + resultMap: { + shared: false, + value: {} + }, + /** + * 加载数据时,返回数据的根目录 + * @cfg {String} [root='rows'] + *

+     *    //默认返回数据类型:
+     *    '{"rows":[{"name":"abc"},{"name":"bcd"}],"results":100}'
+     *   //可以修改接收的后台参数的含义
+     *   var store = new Store({
+     *     url : 'data.json',
+     *     errorProperty : 'errorMsg', //存放错误信息的字段(error)
+     *     hasErrorProperty : 'isError', //是否错误的字段(hasError)
+     *     root : 'data',               //存放数据的字段名(rows)
+     *     totalProperty : 'total'     //存放记录总数的字段名(results)
+     *   });
+     * 
+ * + */ + root: { + value: 'rows' + }, + /** + * 当前Store缓存的数据条数 + * @type {Number} + * @private + * @readOnly + */ + rowCount: { + value: 0 + }, + /** + * 加载数据时,返回记录的总数的字段,用于分页 + * @cfg {String} [totalProperty='results'] + *

+     *    //默认返回数据类型:
+     *    '{"rows":[{"name":"abc"},{"name":"bcd"}],"results":100}'
+     *   //可以修改接收的后台参数的含义
+     *   var store = new Store({
+     *     url : 'data.json',
+     *     errorProperty : 'errorMsg', //存放错误信息的字段(error)
+     *     hasErrorProperty : 'isError', //是否错误的字段(hasError)
+     *     root : 'data',               //存放数据的字段名(rows)
+     *     totalProperty : 'total'     //存放记录总数的字段名(results)
+     *   });
+     * 
+ */ + totalProperty: { + value: 'results' + }, + /** + * 加载数据的起始位置 + *

+     *  //初始化时,可以在params中配置
+     *  var store = new Store({
+     *    url : 'data.json',
+     *    params : {
+     *      start : 100
+     *    }
+     *  });
+     * 
+ * @type {Object} + */ + start: { + value: 0 + }, + /** + * 每页多少条记录,默认为null,此时不分页,当指定了此值时分页 + *

+     *  //当请求的数据分页时
+     *  var store = new Store({
+     *    url : 'data.json',
+     *    pageSize : 30
+     *  });
+     * 
+ * @cfg {Number} pageSize + */ + pageSize: {} + }; + BUI.extend(store, AbstractStore); + BUI.mixin(store, [Sortable]); + BUI.augment(store, { + /** + * 添加记录,默认添加在后面 + *

+     *  //添加记录
+     *  store.add({id : '2',text: 'new data'});
+     *  //是否去重,重复数据不能添加
+     *  store.add(obj,true); //不能添加重复数据,此时用obj1 === obj2判断
+     *  //使用匹配函去重
+     *  store.add(obj,true,function(obj1,obj2){
+     *    return obj1.id == obj2.id;
+     *  });
+     *
+     * 
+ * @param {Array|Object} data 添加的数据,可以是数组,可以是单条记录 + * @param {Boolean} [noRepeat = false] 是否去重,可以为空,默认: false + * @param {Function} [match] 匹配函数,可以为空, + * @default 配置项中 matchFunction 属性传入的函数,默认是:
+ * function(obj1,obj2){ + * return obj1 == obj2; + * } + * + */ + add: function(data, noRepeat, match) { + var _self = this, + count = _self.getCount(); + _self.addAt(data, count, noRepeat, match) + }, + /** + * 添加记录,指定索引值 + *

+     *  //使用方式跟类似于add,增加了index参数
+     *  store.add(obj,0);//添加在最前面
+     * 
+ * @param {Array|Object} data 添加的数据,可以是数组,可以是单条记录 + * @param {Number} index 开始添加数据的位置 + * @param {Boolean} [noRepeat = false] 是否去重,可以为空,默认: false + * @param {Function} [match] 匹配函数,可以为空, + */ + addAt: function(data, index, noRepeat, match) { + var _self = this; + match = match || _self._getDefaultMatch(); + if (!BUI.isArray(data)) { + data = [data]; + } + $.each(data, function(pos, element) { + if (!noRepeat || !_self.contains(element, match)) { + _self._addRecord(element, pos + index); + _self.get('newRecords').push(element); + removeFrom(element, _self.get('deletedRecords')); + removeFrom(element, _self.get('modifiedRecords')); + } + }); + }, + /** + * 验证是否存在指定记录 + *

+     *  store.contains(obj); //是否包含指定的记录
+     *
+     *  store.contains(obj,function(obj1,obj2){ //使用匹配函数
+     *    return obj1.id == obj2.id;
+     *  });
+     * 
+ * @param {Object} record 指定的记录 + * @param {Function} [match = function(obj1,obj2){return obj1 == obj2}] 默认为比较2个对象是否相同 + * @return {Boolean} + */ + contains: function(record, match) { + return this.findIndexBy(record, match) !== -1; + }, + /** + * 查找记录,仅返回第一条 + *

+     *  var record = store.find('id','123');
+     * 
+ * @param {String} field 字段名 + * @param {String} value 字段值 + * @return {Object|null} + */ + find: function(field, value) { + var _self = this, + result = null, + records = _self.getResult(); + $.each(records, function(index, record) { + if (record[field] === value) { + result = record; + return false; + } + }); + return result; + }, + /** + * 查找记录,返回所有符合查询条件的记录 + *

+     *   var records = store.findAll('type','0');
+     * 
+ * @param {String} field 字段名 + * @param {String} value 字段值 + * @return {Array} + */ + findAll: function(field, value) { + var _self = this, + result = [], + records = _self.getResult(); + $.each(records, function(index, record) { + if (record[field] === value) { + result.push(record); + } + }); + return result; + }, + /** + * 根据索引查找记录 + *

+     *  var record = store.findByIndex(1);
+     * 
+ * @param {Number} index 索引 + * @return {Object} 查找的记录 + */ + findByIndex: function(index) { + return this.getResult()[index]; + }, + /** + * 查找数据所在的索引位置,若不存在返回-1 + *

+     *  var index = store.findIndexBy(obj);
+     *
+     *  var index = store.findIndexBy(obj,function(obj1,obj2){
+     *    return obj1.id == obj2.id;
+     *  });
+     * 
+ * @param {Object} target 指定的记录 + * @param {Function} [match = matchFunction] @see {BUI.Data.Store#matchFunction}默认为比较2个对象是否相同 + * @return {Number} + */ + findIndexBy: function(target, match) { + var _self = this, + position = -1, + records = _self.getResult(); + match = match || _self._getDefaultMatch(); + if (target === null || target === undefined) { + return -1; + } + $.each(records, function(index, record) { + if (match(target, record)) { + position = index; + return false; + } + }); + return position; + }, + /** + * 获取下一条记录 + *

+     *  var record = store.findNextRecord(obj);
+     * 
+ * @param {Object} record 当前记录 + * @return {Object} 下一条记录 + */ + findNextRecord: function(record) { + var _self = this, + index = _self.findIndexBy(record); + if (index >= 0) { + return _self.findByIndex(index + 1); + } + return; + }, + /** + * 获取缓存的记录数 + *

+     *  var count = store.getCount(); //缓存的数据数量
+     *
+     *  var totalCount = store.getTotalCount(); //数据的总数,如果有分页时,totalCount != count
+     * 
+ * @return {Number} 记录数 + */ + getCount: function() { + return this.getResult().length; + }, + /** + * 获取数据源的数据总数,分页时,当前仅缓存当前页数据 + *

+     *  var count = store.getCount(); //缓存的数据数量
+     *
+     *  var totalCount = store.getTotalCount(); //数据的总数,如果有分页时,totalCount != count
+     * 
+ * @return {Number} 记录的总数 + */ + getTotalCount: function() { + var _self = this, + resultMap = _self.get('resultMap'), + total = _self.get('totalProperty'); + return parseInt(resultMap[total], 10) || 0; + }, + /** + * 获取当前缓存的纪录 + *

+     *   var records = store.getResult();
+     * 
+ * @return {Array} 纪录集合 + */ + getResult: function() { + var _self = this, + resultMap = _self.get('resultMap'), + root = _self.get('root'); + return resultMap[root]; + }, + /** + * 是否包含数据 + * @return {Boolean} + */ + hasData: function() { + return this.getCount() !== 0; + }, + /** + * 设置数据源,非异步加载时,设置缓存的数据 + *

+     *   store.setResult([]); //清空数据
+     *
+     *   var data = [{},{}];
+     *   store.setResult(data); //重设数据
+     * 
+ */ + setResult: function(data) { + var _self = this, + proxy = _self.get('proxy'); + if (proxy instanceof Proxy.Memery) { + _self.set('data', data); + _self.load({ + start: 0 + }); + } else { + _self._setResult(data); + //如果有filter则进行过滤 + if (_self.get('filter')) { + _self.filter(); + } + } + }, + /** + * 删除一条或多条记录触发 remove 事件. + *

+     *  store.remove(obj);  //删除一条记录
+     *
+     *  store.remove([obj1,obj2...]); //删除多个条记录
+     *
+     *  store.remvoe(obj,funciton(obj1,obj2){ //使用匹配函数
+     *    return obj1.id == obj2.id;
+     *  });
+     * 
+ * @param {Array|Object} data 添加的数据,可以是数组,可以是单条记录 + * @param {Function} [match = function(obj1,obj2){return obj1 == obj2}] 匹配函数,可以为空 + */ + remove: function(data, match) { + var _self = this, + delData = []; + match = match || _self._getDefaultMatch(); + if (!BUI.isArray(data)) { + data = [data]; + } + $.each(data, function(index, element) { + var index = _self.findIndexBy(element, match), + record = removeAt(index, _self.getResult()); + //添加到已删除队列中,如果是新添加的数据,不计入删除的数据集合中 + if (!contains(record, _self.get('newRecords')) && !contains(record, _self.get('deletedRecords'))) { + _self.get('deletedRecords').push(record); + } + removeFrom(record, _self.get('newRecords')); + removeFrom(record, _self.get('modifiedRecords')); + _self.fire('remove', { + record: record + }); + }); + }, + /** + * 保存数据,有几种类型: + * + * - add 保存添加的记录, + * - remove 保存删除, + * - update 保存更新, + * - all 保存store从上次加载到目前更改的记录 + * + * + * @param {String} type 保存的类型 + * @param {Object} saveData 数据 + * @param {Function} callback + */ + save: function(type, saveData, callback) { + var _self = this, + proxy = _self.get('proxy'); + if (BUI.isFunction(type)) { //只有回调函数 + callback = type; + type = undefined; + } + if (BUI.isObject(type)) { //未指定类型 + callback = saveData; + saveData = type; + type = undefined; + } + if (!type) { + type = _self._getSaveType(saveData); + } + if (type == 'all' && !saveData) { //如果保存全部,同时未提供保存的数据,自动获取 + saveData = _self._getDirtyData(); + } + _self.fire('beforesave', { + type: type, + saveData: saveData + }); + proxy.save(type, saveData, function(data) { + _self.onSave(type, saveData, data); + if (callback) { + callback(data, saveData); + } + }, _self); + }, + //根据保存的数据获取保存的类型 + _getSaveType: function(saveData) { + var _self = this; + if (!saveData) { + return 'all'; + } + if (BUI.Array.contains(saveData, _self.get('newRecords'))) { + return 'add'; + } + if (BUI.Array.contains(saveData, _self.get('modifiedRecords'))) { + return 'update'; + } + if (BUI.Array.contains(saveData, _self.get('deletedRecords'))) { + return 'remove'; + } + return 'custom'; + }, + //获取未保存的数据 + _getDirtyData: function() { + var _self = this, + proxy = _self.get('proxy'); + if (proxy.get('url')) { + return { + add: BUI.JSON.stringify(_self.get('newRecords')), + update: BUI.JSON.stringify(_self.get('modifiedRecords')), + remove: BUI.JSON.stringify(_self.get('deletedRecords')) + }; + } else { + return { + add: _self.get('newRecords'), + update: _self.get('modifiedRecords'), + remove: _self.get('deletedRecords') + }; + } + }, + /** + * 保存完成后 + * @private + */ + onSave: function(type, saveData, data) { + var _self = this, + hasErrorField = _self.get('hasErrorProperty'); + if (data[hasErrorField] || data.exception) { //如果失败 + _self.onException(data); + return; + } + _self._clearDirty(type, saveData); + _self.fire('saved', { + type: type, + saveData: saveData, + data: data + }); + if (_self.get('autoSync')) { + _self.load(); + } + }, + //清除脏数据 + _clearDirty: function(type, saveData) { + var _self = this; + switch (type) { + case 'all': + _self._clearChanges(); + break; + case 'add': + removeFrom(saveData, 'newRecords'); + break; + case 'update': + removeFrom(saveData, 'modifiedRecords'); + break; + case 'remove': + removeFrom(saveData, 'deletedRecords'); + break; + default: + break; + } + + function removeFrom(obj, name) { + BUI.Array.remove(_self.get(name), obj); + } + }, + /** + * 排序,如果remoteSort = true,发送请求,后端排序 + *

+     *   store.sort('id','DESC'); //以id为排序字段,倒序排序
+     * 
+ * @param {String} field 排序字段 + * @param {String} direction 排序方向 + */ + sort: function(field, direction) { + var _self = this, + remoteSort = _self.get('remoteSort'); + if (!remoteSort) { + _self._localSort(field, direction); + } else { + _self.set('sortField', field); + _self.set('sortDirection', direction); + _self.load(_self.get('sortInfo')); + } + }, + /** + * 计算指定字段的和 + *

+     *   var sum = store.sum('number');
+     * 
+ * @param {String} field 字段名 + * @param {Array} [data] 计算的集合,默认为Store中的数据集合 + * @return {Number} 汇总和 + */ + sum: function(field, data) { + var _self = this, + records = data || _self.getResult(), + sum = 0; + BUI.each(records, function(record) { + var val = record[field]; + if (!isNaN(val)) { + sum += parseFloat(val); + } + }); + return sum; + }, + /** + * 设置记录的值 ,触发 update 事件 + *

+     *  store.setValue(obj,'value','new value');
+     * 
+ * @param {Object} obj 修改的记录 + * @param {String} field 修改的字段名 + * @param {Object} value 修改的值 + */ + setValue: function(obj, field, value) { + var record = obj, + _self = this; + record[field] = value; + if (!contains(record, _self.get('newRecords')) && !contains(record, _self.get('modifiedRecords'))) { + _self.get('modifiedRecords').push(record); + } + _self.fire('update', { + record: record, + field: field, + value: value + }); + }, + /** + * 更新记录 ,触发 update事件 + *

+     *   var record = store.find('id','12');
+     *   record.value = 'new value';
+     *   record.text = 'new text';
+     *   store.update(record); //触发update事件,引起绑定了store的控件更新
+     * 
+ * @param {Object} obj 修改的记录 + * @param {Boolean} [isMatch = false] 是否需要进行匹配,检测指定的记录是否在集合中 + * @param {Function} [match = matchFunction] 匹配函数 + */ + update: function(obj, isMatch, match) { + var record = obj, + _self = this, + match = null, + index = null; + if (isMatch) { + match = match || _self._getDefaultMatch(); + index = _self.findIndexBy(obj, match); + if (index >= 0) { + record = _self.getResult()[index]; + } + } + record = BUI.mix(record, obj); + if (!contains(record, _self.get('newRecords')) && !contains(record, _self.get('modifiedRecords'))) { + _self.get('modifiedRecords').push(record); + } + _self.fire('update', { + record: record + }); + }, + //添加纪录 + _addRecord: function(record, index) { + var records = this.getResult(); + if (index == undefined) { + index = records.length; + } + records.splice(index, 0, record); + this.fire('add', { + record: record, + index: index + }); + }, + //清除改变的数据记录 + _clearChanges: function() { + var _self = this; + BUI.Array.empty(_self.get('newRecords')); + BUI.Array.empty(_self.get('modifiedRecords')); + BUI.Array.empty(_self.get('deletedRecords')); + }, + /** + * @protected + * 过滤缓存的数据 + * @param {Function} fn 过滤函数 + * @return {Array} 过滤结果 + */ + _filterLocal: function(fn, data) { + var _self = this, + rst = []; + data = data || _self.getResult(); + if (!fn) { //没有过滤器时直接返回 + return data; + } + BUI.each(data, function(record) { + if (fn(record)) { + rst.push(record); + } + }); + return rst; + }, + //获取默认的匹配函数 + _getDefaultMatch: function() { + return this.get('matchFunction'); + }, + //获取分页相关的信息 + _getPageParams: function() { + var _self = this, + sortInfo = _self.get('sortInfo'), + start = _self.get('start'), + limit = _self.get('pageSize'), + pageIndex = _self.get('pageIndex') || (limit ? start / limit : 0); + params = { + start: start, + limit: limit, + pageIndex: pageIndex //一般而言,pageIndex = start/limit + }; + if (_self.get('remoteSort')) { + BUI.mix(params, sortInfo); + } + return params; + }, + /** + * 获取附加的参数,分页信息,排序信息 + * @override + * @protected + * @return {Object} 附加的参数 + */ + getAppendParams: function() { + return this._getPageParams(); + }, + /** + * @protected + * 初始化之前 + */ + beforeInit: function() { + //初始化结果集 + this._setResult([]); + }, + //本地排序 + _localSort: function(field, direction) { + var _self = this; + _self._sortData(field, direction); + _self.fire('localsort', { + field: field, + direction: direction + }); + }, + _sortData: function(field, direction, data) { + var _self = this; + data = data || _self.getResult(); + _self.sortData(field, direction, data); + }, + //处理数据 + afterProcessLoad: function(data, params) { + var _self = this, + root = _self.get('root'), + start = params.start, + limit = params.limit, + totalProperty = _self.get('totalProperty'); + if (BUI.isArray(data)) { + _self._setResult(data); + } else { + _self._setResult(data[root], data[totalProperty]); + } + _self.set('start', start); + if (limit) { + _self.set('pageIndex', start / limit); + } + //如果本地排序,则排序 + if (!_self.get('remoteSort')) { + _self._sortData(); + } + _self.fire('load', { + params: params + }); + //如果有本地过滤,则本地过滤 + if (!_self.get('remoteFilter') && _self.get('filter')) { + _self.filter(_self.get('filter')); + } + }, + //设置结果集 + _setResult: function(rows, totalCount) { + var _self = this, + resultMap = _self.get('resultMap'); + totalCount = totalCount || rows.length; + resultMap[_self.get('root')] = rows; + resultMap[_self.get('totalProperty')] = totalCount; + //清理之前发生的改变 + _self._clearChanges(); + } + }); + module.exports = store; +}); +define("bui-data/1.1.0/src/node-debug", ["bui-common/1.1.0/index-debug"], function(require, exports, module) { + /** + * @fileOverview 树形数据结构的节点类,无法直接使用数据作为节点,所以进行一层封装 + * 可以直接作为TreeNode控件的配置项 + * @ignore + */ + var BUI = require("bui-common/1.1.0/index-debug"); + + function mapNode(cfg, map) { + var rst = {}; + if (map) { + BUI.each(cfg, function(v, k) { + var name = map[k] || k; + rst[name] = v; + }); + rst.record = cfg; + } else { + rst = cfg; + } + return rst; + } + /** + * @class BUI.Data.Node + * 树形数据结构的节点类 + */ + function Node(cfg, map) { + var _self = this; + cfg = mapNode(cfg, map); + BUI.mix(this, cfg); + } + BUI.augment(Node, { + /** + * 是否根节点 + * @type {Boolean} + */ + root: false, + /** + * 是否叶子节点 + * @type {Boolean} + */ + leaf: null, + /** + * 显示节点时显示的文本 + * @type {Object} + */ + text: '', + /** + * 代表节点的编号 + * @type {String} + */ + id: null, + /** + * 子节点是否已经加载过 + * @type {Boolean} + */ + loaded: false, + /** + * 从根节点到此节点的路径,id的集合如: ['0','1','12'], + * 便于快速定位节点 + * @type {Array} + */ + path: null, + /** + * 父节点 + * @type {BUI.Data.Node} + */ + parent: null, + /** + * 树节点的等级 + * @type {Number} + */ + level: 0, + /** + * 节点是否由一条记录封装而成 + * @type {Object} + */ + record: null, + /** + * 子节点集合 + * @type {BUI.Data.Node[]} + */ + children: null, + /** + * 是否是Node对象 + * @type {Object} + */ + isNode: true + }); + module.exports = Node; +}); +define("bui-data/1.1.0/src/treestore-debug", ["bui-common/1.1.0/index-debug", "bui-data/1.1.0/src/node-debug", "bui-data/1.1.0/src/proxy-debug", "bui-data/1.1.0/src/abstractstore-debug", "bui-data/1.1.0/src/sortable-debug"], function(require, exports, module) { + /** + * @fileOverview 树形对象缓冲类 + * @ignore + */ + var BUI = require("bui-common/1.1.0/index-debug"), + Node = require("bui-data/1.1.0/src/node-debug"), + Proxy = require("bui-data/1.1.0/src/proxy-debug"), + AbstractStore = require("bui-data/1.1.0/src/abstractstore-debug"); + /** + * @class BUI.Data.TreeStore + * 树形数据缓冲类 + *

+ * + *

+ *

+   *   //加载静态数据
+   *   var store = new TreeStore({
+   *     root : {
+   *       text : '根节点',
+   *       id : 'root'
+   *     },
+   *     data : [{id : '1',text : 1},{id : '2',text : 2}] //会加载成root的children
+   *   });
+   *   //异步加载数据,自动加载数据时,会调用store.load({id : 'root'}); //root为根节点的id
+   *   var store = new TreeStore({
+   *     root : {
+   *       text : '根节点',
+   *       id : 'root'
+   *     },
+   *     url : 'data/nodes.php',
+   *     autoLoad : true  //设置自动加载,初始化后自动加载数据
+   *   });
+   *
+   *   //加载指定节点
+   *   var node = store.findNode('1');
+   *   store.loadNode(node);
+   *   //或者
+   *   store.load({id : '1'});//可以配置自定义参数,返回值附加到指定id的节点上
+   * 
+ * @extends BUI.Data.AbstractStore + */ + function TreeStore(config) { + TreeStore.superclass.constructor.call(this, config); + } + TreeStore.ATTRS = { + /** + * 根节点 + *

+     *  var store = new TreeStore({
+     *    root : {text : '根节点',id : 'rootId',children : [{id : '1',text : '1'}]}
+     *  });
+     * 
+ * @cfg {Object} root + */ + /** + * 根节点,初始化后不要更改对象,可以更改属性值 + *

+     *  var root = store.get('root');
+     *  root.text = '修改的文本';
+     *  store.update(root);
+     * 
+ * @type {Object} + * @readOnly + */ + root: {}, + /** + * 数据映射,用于设置的数据跟@see {BUI.Data.Node} 不一致时,进行匹配。 + * 如果此属性为null,那么假设设置的对象是Node对象 + *

+     *   //例如原始数据为 {name : '123',value : '文本123',isLeaf: false,nodes : []}
+     *   var store = new TreeStore({
+     *     map : {
+     *       'name' : 'id',
+     *       'value' : 'text',
+     *       'isLeaf' : 'leaf' ,
+     *       'nodes' : 'children'
+     *     }
+     *   });
+     *   //映射后,记录会变成  {id : '123',text : '文本123',leaf: false,children : []};
+     *   //此时原始记录会作为对象的 record属性
+     *   var node = store.findNode('123'),
+     *     record = node.record;
+     * 
+ * **Notes:** + * 使用数据映射的记录仅做于展示数据,不作为可更改的数据,add,update不会更改数据的原始数据 + * @cfg {Object} map + */ + map: {}, + /** + * 标示父元素id的字段名称 + * @type {String} + */ + pidField: {}, + /** + * 返回数据标示数据的字段
+ * 异步加载数据时,返回数据可以使数组或者对象 + * - 如果返回的是对象,可以附加其他信息,那么取对象对应的字段 {nodes : [],hasError:false} + * - 如何获取附加信息参看 @see {BUI.Data.AbstractStore-event-beforeprocessload} + *

+     *  //返回数据为数组 [{},{}],会直接附加到加载的节点后面
+     *
+     *  var node = store.loadNode('123');
+     *  store.loadNode(node);
+     *
+     * 
+ * @cfg {Object} [dataProperty = 'nodes'] + */ + dataProperty: { + value: 'nodes' + }, + events: { + value: [ + /** + * 当添加数据时触发该事件 + * @event + *

+         *  store.on('add',function(ev){
+         *    list.addItem(e.node,index);
+         *  });
+         * 
+ * @param {jQuery.Event} e 事件对象 + * @param {Object} e.node 添加的节点 + * @param {Number} index 添加的位置 + */ + 'add', + /** + * 当更新数据指定字段时触发该事件 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.node 更新的节点 + */ + 'update', + /** + * 当删除数据时触发该事件 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.node 删除的节点 + * @param {Number} index 删除节点的索引 + */ + 'remove', + /** + * 节点加载完毕触发该事件 + *

+         *   //异步加载节点,此时节点已经附加到加载节点的后面
+         *   store.on('load',function(ev){
+         *     var params = ev.params,
+         *       id = params.id,
+         *       node = store.findNode(id),
+         *       children = node.children;  //节点的id
+         *     //TO DO
+         *   });
+         * 
+ * + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.node 加载的节点 + * @param {Object} e.params 加载节点时的参数 + */ + 'load' + ] + } + } + BUI.extend(TreeStore, AbstractStore); + BUI.augment(TreeStore, { + /** + * @protected + * @override + * 初始化前 + */ + beforeInit: function() { + this.initRoot(); + }, + //初始化数据,如果默认加载数据,则加载数据 + _initData: function() { + var _self = this, + autoLoad = _self.get('autoLoad'), + pidField = _self.get('pidField'), + proxy = _self.get('proxy'), + root = _self.get('root'); + //添加默认的匹配父元素的字段 + if (!proxy.get('url') && pidField) { + proxy.get('matchFields').push(pidField); + } + if (autoLoad && !root.children) { + //params = root.id ? {id : root.id}: {}; + _self.loadNode(root); + } + }, + /** + * @protected + * 初始化根节点 + */ + initRoot: function() { + var _self = this, + map = _self.get('map'), + root = _self.get('root'); + if (!root) { + root = {}; + } + if (!root.isNode) { + root = new Node(root, map); + //root.children= []; + } + root.path = [root.id]; + root.level = 0; + if (root.children) { + _self.setChildren(root, root.children); + } + _self.set('root', root); + }, + /** + * 添加节点,触发{@link BUI.Data.TreeStore#event-add} 事件 + *

+     *  //添加到根节点下
+     *  store.add({id : '1',text : '1'});
+     *  //添加到指定节点
+     *  var node = store.findNode('1'),
+     *    subNode = store.add({id : '11',text : '11'},node);
+     *  //插入到节点的指定位置
+     *  var node = store.findNode('1'),
+     *    subNode = store.add({id : '12',text : '12'},node,0);
+     * 
+ * @param {BUI.Data.Node|Object} node 节点或者数据对象 + * @param {BUI.Data.Node} [parent] 父节点,如果未指定则为根节点 + * @param {Number} [index] 添加节点的位置 + * @return {BUI.Data.Node} 添加完成的节点 + */ + add: function(node, parent, index) { + var _self = this; + node = _self._add(node, parent, index); + _self.fire('add', { + node: node, + record: node, + index: index + }); + return node; + }, + // + _add: function(node, parent, index) { + parent = parent || this.get('root'); //如果未指定父元素,添加到跟节点 + var _self = this, + map = _self.get('map'), + nodes = parent.children, + nodeChildren; + if (!node.isNode) { + node = new Node(node, map); + } + nodeChildren = node.children || [] + if (nodeChildren.length == 0 && node.leaf == null) { + node.leaf = true; + } + if (parent) { + parent.leaf = false; + } + node.parent = parent; + node.level = parent.level + 1; + node.path = parent.path.concat(node.id); + index = index == null ? parent.children.length : index; + BUI.Array.addAt(nodes, node, index); + _self.setChildren(node, nodeChildren); + return node; + }, + /** + * 移除节点,触发{@link BUI.Data.TreeStore#event-remove} 事件 + * + *

+     *  var node = store.findNode('1'); //根据节点id 获取节点
+     *  store.remove(node);
+     * 
+ * + * @param {BUI.Data.Node} node 节点或者数据对象 + * @return {BUI.Data.Node} 删除的节点 + */ + remove: function(node) { + var parent = node.parent || _self.get('root'), + index = BUI.Array.indexOf(node, parent.children); + BUI.Array.remove(parent.children, node); + if (parent.children.length === 0) { + parent.leaf = true; + } + this.fire('remove', { + node: node, + record: node, + index: index + }); + node.parent = null; + return node; + }, + /** + * 设置记录的值 ,触发 update 事件 + *

+     *  store.setValue(obj,'value','new value');
+     * 
+ * @param {Object} obj 修改的记录 + * @param {String} field 修改的字段名 + * @param {Object} value 修改的值 + */ + setValue: function(node, field, value) { + var _self = this; + node[field] = value; + _self.fire('update', { + node: node, + record: node, + field: field, + value: value + }); + }, + /** + * 更新节点 + *

+     *  var node = store.findNode('1'); //根据节点id 获取节点
+     *  node.text = 'modify text'; //修改文本
+     *  store.update(node);        //此时会触发update事件,绑定了store的控件会更新对应的DOM
+     * 
+ * @return {BUI.Data.Node} 更新节点 + */ + update: function(node) { + this.fire('update', { + node: node, + record: node + }); + }, + /** + * 返回缓存的数据,根节点的直接子节点集合 + *

+     *   //获取根节点的所有子节点
+     *   var data = store.getResult();
+     *   //获取根节点
+     *   var root = store.get('root');
+     * 
+ * @return {Array} 根节点下面的数据 + */ + getResult: function() { + return this.get('root').children; + }, + /** + * 设置缓存的数据,设置为根节点的数据 + *

+     *     var data = [
+     *       {id : '1',text : '文本1'},
+     *       {id : '2',text : '文本2',children:[
+     *         {id : '21',text : '文本21'}
+     *       ]},
+     *       {id : '3',text : '文本3'}
+     *     ];
+     *     store.setResult(data); //会对数据进行格式化,添加leaf等字段:
+     *                            //[{id : '1',text : '文本1',leaf : true},{id : '2',text : '文本2',leaf : false,children:[...]}....]
+     *   
+ * @param {Array} data 缓存的数据 + */ + setResult: function(data) { + var _self = this, + proxy = _self.get('proxy'), + root = _self.get('root'); + if (proxy instanceof Proxy.Memery) { + _self.set('data', data); + _self.load({ + id: root.id + }); + } else { + _self.setChildren(root, data); + } + }, + /** + * 设置子节点 + * @protected + * @param {BUI.Data.Node} node 节点 + * @param {Array} children 子节点 + */ + setChildren: function(node, children) { + var _self = this; + node.children = []; + if (!children.length) { + return; + } + BUI.each(children, function(item) { + _self._add(item, node); + }); + }, + /** + * 查找节点 + *

+     *  var node = store.findNode('1');//从根节点开始查找节点
+     *
+     *  var subNode = store.findNode('123',node); //从指定节点开始查找
+     * 
+ * @param {String} id 节点Id + * @param {BUI.Data.Node} [parent] 父节点 + * @param {Boolean} [deep = true] 是否递归查找 + * @return {BUI.Data.Node} 节点 + */ + findNode: function(id, parent, deep) { + return this.findNodeBy(function(node) { + return node.id === id; + }, parent, deep); + }, + /** + * 根据匹配函数查找节点 + * @param {Function} fn 匹配函数 + * @param {BUI.Data.Node} [parent] 父节点 + * @param {Boolean} [deep = true] 是否递归查找 + * @return {BUI.Data.Node} 节点 + */ + findNodeBy: function(fn, parent, deep) { + var _self = this; + deep = deep == null ? true : deep; + if (!parent) { + var root = _self.get('root'); + if (fn(root)) { + return root; + } + return _self.findNodeBy(fn, root); + } + var children = parent.children, + rst = null; + BUI.each(children, function(item) { + if (fn(item)) { + rst = item; + } else if (deep) { + rst = _self.findNodeBy(fn, item); + } + if (rst) { + return false; + } + }); + return rst; + }, + /** + * 查找节点,根据匹配函数查找 + *

+     *  var nodes = store.findNodesBy(function(node){
+     *   if(node.status == '0'){
+     *     return true;
+     *   }
+     *   return false;
+     *  });
+     * 
+ * @param {Function} func 匹配函数 + * @param {BUI.Data.Node} [parent] 父元素,如果不存在,则从根节点查找 + * @return {Array} 节点数组 + */ + findNodesBy: function(func, parent) { + var _self = this, + root, + rst = []; + if (!parent) { + parent = _self.get('root'); + } + BUI.each(parent.children, function(item) { + if (func(item)) { + rst.push(item); + } + rst = rst.concat(_self.findNodesBy(func, item)); + }); + return rst; + }, + /** + * 根据path查找节点 + * @return {BUI.Data.Node} 节点 + * @ignore + */ + findNodeByPath: function(path) { + if (!path) { + return null; + } + var _self = this, + root = _self.get('root'), + pathArr = path.split(','), + node, + i, + tempId = pathArr[0]; + if (!tempId) { + return null; + } + if (root.id == tempId) { + node = root; + } else { + node = _self.findNode(tempId, root, false); + } + if (!node) { + return; + } + for (i = 1; i < pathArr.length; i = i + 1) { + var tempId = pathArr[i]; + node = _self.findNode(tempId, node, false); + if (!node) { + break; + } + } + return node; + }, + /** + * 是否包含指定节点,如果未指定父节点,从根节点开始搜索 + *

+     *  store.contains(node); //是否存在节点
+     *
+     *  store.contains(subNode,node); //节点是否存在指定子节点
+     * 
+ * @param {BUI.Data.Node} node 节点 + * @param {BUI.Data.Node} parent 父节点 + * @return {Boolean} 是否包含指定节点 + */ + contains: function(node, parent) { + var _self = this, + findNode = _self.findNode(node.id, parent); + return !!findNode; + }, + /** + * 加载完数据 + * @protected + * @override + */ + afterProcessLoad: function(data, params) { + var _self = this, + pidField = _self.get('pidField'), + id = params.id || params[pidField], + dataProperty = _self.get('dataProperty'), + node = _self.findNode(id) || _self.get('root'); //如果找不到父元素,则放置在跟节点 + if (BUI.isArray(data)) { + _self.setChildren(node, data); + } else { + _self.setChildren(node, data[dataProperty]); + } + node.loaded = true; //标识已经加载过 + _self.fire('load', { + node: node, + params: params + }); + }, + /** + * 是否包含数据 + * @return {Boolean} + */ + hasData: function() { + //return true; + return this.get('root').children && this.get('root').children.length !== 0; + }, + /** + * 是否已经加载过,叶子节点或者存在字节点的节点 + * @param {BUI.Data.Node} node 节点 + * @return {Boolean} 是否加载过 + */ + isLoaded: function(node) { + var root = this.get('root'); + if (node == root && !root.children) { + return false; + } + if (!this.get('url') && !this.get('pidField')) { //如果不从远程加载数据,默认已经加载 + return true; + } + return node.loaded || node.leaf || !!(node.children && node.children.length); + }, + /** + * 加载节点的子节点 + * @param {BUI.Data.Node} node 节点 + * @param {Boolean} forceLoad 是否强迫重新加载节点,如果设置成true,不判断是否加载过 + */ + loadNode: function(node, forceLoad) { + var _self = this, + pidField = _self.get('pidField'), + params; + //如果已经加载过,或者节点是叶子节点 + if (!forceLoad && _self.isLoaded(node)) { + return; + } + params = { + id: node.id + }; + if (pidField) { + params[pidField] = node.id; + } + _self.load(params); + }, + /** + * 重新加载节点 + * @param {BUI.Data.Node} node node节点 + */ + reloadNode: function(node) { + var _self = this; + node = node || _self.get('root'); + node.loaded = false; + //node.children = []; + _self.loadNode(node, true); + }, + /** + * 加载节点,根据path + * @param {String} path 加载路径 + * @ignore + */ + loadPath: function(path) { + var _self = this, + arr = path.split(','), + id = arr[0]; + if (_self.findNodeByPath(path)) { //加载过 + return; + } + _self.load({ + id: id, + path: path + }); + } + }); + module.exports = TreeStore; +}); \ No newline at end of file diff --git a/dist/bui-data/1.1.0/index.js b/dist/bui-data/1.1.0/index.js new file mode 100644 index 0000000..59dac39 --- /dev/null +++ b/dist/bui-data/1.1.0/index.js @@ -0,0 +1 @@ +define("bui-data/1.1.0/index",["bui-common/1.1.0/index","bui-data/1.1.0/src/sortable","bui-data/1.1.0/src/proxy","bui-data/1.1.0/src/abstractstore","bui-data/1.1.0/src/store","bui-data/1.1.0/src/node","bui-data/1.1.0/src/treestore"],function(e,t,r){var a=e("bui-common/1.1.0/index"),n=a.namespace("Data");a.mix(n,{Sortable:e("bui-data/1.1.0/src/sortable"),Proxy:e("bui-data/1.1.0/src/proxy"),AbstractStore:e("bui-data/1.1.0/src/abstractstore"),Store:e("bui-data/1.1.0/src/store"),Node:e("bui-data/1.1.0/src/node"),TreeStore:e("bui-data/1.1.0/src/treestore")}),r.exports=n}),define("bui-data/1.1.0/src/sortable",[],function(e,t,r){var a="ASC",n=function(){};n.ATTRS={compareFunction:{value:function(e,t){return void 0===e&&(e=""),void 0===t&&(t=""),BUI.isString(e)?e.localeCompare(t):e>t?1:e===t?0:-1}},sortField:{},sortDirection:{value:"ASC"},sortInfo:{getter:function(){var e=this,t=e.get("sortField");return{field:t,direction:e.get("sortDirection")}},setter:function(e){var t=this;t.set("sortField",e.field),t.set("sortDirection",e.direction)}}},BUI.augment(n,{compare:function(e,t,r,n){var i,o=this;return r=r||o.get("sortField"),n=n||o.get("sortDirection"),r&&n?(i=n===a?1:-1,o.get("compareFunction")(e[r],t[r])*i):1},getSortData:function(){},sortData:function(e,t,r){var a=this,r=r||a.getSortData();return BUI.isArray(e)&&(r=e,e=null),e=e||a.get("sortField"),t=t||a.get("sortDirection"),a.set("sortField",e),a.set("sortDirection",t),e&&t?(r.sort(function(r,n){return a.compare(r,n,e,t)}),r):r}}),r.export=n}),define("bui-data/1.1.0/src/proxy",["bui-data/1.1.0/src/sortable"],function(e,t,r){var a=e("bui-data/1.1.0/src/sortable"),n=function(e){n.superclass.constructor.call(this,e)};n.ATTRS={},BUI.extend(n,BUI.Base),BUI.augment(n,{_read:function(){},read:function(e,t,r){var a=this;r=r||a,a._read(e,function(e){t.call(r,e)})},_save:function(){},save:function(e,t,r,a){var n=this;a=a||n,n._save(e,t,function(e){r.call(a,e)})}});var i={READ:"read",ADD:"add",UPDATE:"update",REMOVE:"remove",SAVE_ALL:"all"},o=function(e){o.superclass.constructor.call(this,e)};o.ATTRS=BUI.mix(!0,n.ATTRS,{limitParam:{value:"limit"},startParam:{value:"start"},pageIndexParam:{value:"pageIndex"},saveTypeParam:{value:"saveType"},saveDataParam:{},pageStart:{value:0},dataType:{value:"json"},method:{value:"GET"},ajaxOptions:{value:{}},cache:{value:!1},save:{},url:{}}),BUI.extend(o,n),BUI.augment(o,{_processParams:function(e){var t=this,r=t.get("pageStart"),a=["start","limit","pageIndex"];null!=e.pageIndex&&(e.pageIndex=e.pageIndex+r),BUI.each(a,function(r){var a=t.get(r+"Param");a!==r&&(e[a]=e[r],delete e[r])})},_getUrl:function(e){var t,r=this,a=r.get("save");return e===i.READ?r.get("url"):a?BUI.isString(a)?a:(t=a[e+"Url"],t||(t=r.get("url")),t):r.get("url")},_getAppendParams:function(e){var t,r,a=this,n=null;return e==i.READ?n:(t=a.get("save"),r=a.get("saveTypeParam"),t&&!t[e+"Url"]&&(n={},n[r]=e),n)},_read:function(e,t){var r,a=this;e=BUI.cloneObject(e),a._processParams(e),r=a._getAjaxOptions(i.READ,e),a._ajax(r,t)},_getAjaxOptions:function(e,t){var r,a=this,n=a.get("ajaxOptions"),i=a._getUrl(e);return BUI.mix(t,a._getAppendParams(e)),r=BUI.merge({url:i,type:a.get("method"),dataType:a.get("dataType"),data:t,cache:a.get("cache")},n)},_ajax:function(e,t){var r=e.success,a=e.error;e.success=function(e){r&&r(e),t(e)},e.error=function(e,r,n){a&&a(e,r,n);var i={exception:{status:r,errorThrown:n,jqXHR:e}};t(i)},$.ajax(e)},_save:function(e,t,r){var a,n=this;a=n._getAjaxOptions(e,t),n._ajax(a,r)}});var s=function(e){s.superclass.constructor.call(this,e)};s.ATTRS={matchFields:{value:[]}},BUI.extend(s,n),BUI.mixin(s,[a]),BUI.augment(s,{_read:function(e,t){var r=this,a=(e.pageable,e.start),n=e.sortField,i=e.sortDirection,o=e.limit,s=r.get("data"),d=[];s=r._getMatches(e),r.sortData(n,i),o?(d=s.slice(a,a+o),t({rows:d,results:s.length})):(d=s.slice(a),t(d))},_getMatchFn:function(e,t){return function(r){var a=!0;return BUI.each(t,function(t){return null!=e[t]&&e[t]!==r[t]?(a=!1,!1):void 0}),a}},_getMatches:function(e){var t,r=this,a=r.get("matchFields"),n=r.get("data")||[];return e&&a.length&&(t=r._getMatchFn(e,a),n=BUI.Array.filter(n,t)),n},_save:function(e,t){var r=this,a=r.get("data");e==i.ADD?a.push(t):e==i.REMOVE?BUI.Array.remove(a,t):e==i.SAVE_ALL&&(BUI.each(t.add,function(e){a.push(e)}),BUI.each(t.remove,function(e){BUI.Array.remove(a,e)}))}}),n.Ajax=o,n.Memery=s,r.exports=n}),define("bui-data/1.1.0/src/abstractstore",["bui-common/1.1.0/index","bui-data/1.1.0/src/proxy","bui-data/1.1.0/src/sortable"],function(e,t,r){function a(e){a.superclass.constructor.call(this,e),this._init()}var n=e("bui-common/1.1.0/index"),i=e("bui-data/1.1.0/src/proxy");a.ATTRS={autoLoad:{value:!1},remoteFilter:{value:!1},lastParams:{shared:!1,value:{}},params:{},proxy:{shared:!1,value:{}},url:{},events:{value:["acceptchanges","load","beforeload","beforeprocessload","add","exception","remove","update","localsort","filtered"]},data:{setter:function(e){var t=this,r=t.get("proxy");r.set?r.set("data",e):r.data=e,t.set("autoLoad",!0)}}},n.extend(a,n.Base),n.augment(a,{isStore:!0,_init:function(){var e=this;e.beforeInit(),e._initParams(),e._initProxy(),e._initData()},beforeInit:function(){},_initData:function(){var e=this,t=e.get("autoLoad");t&&e.load()},_initParams:function(){var e=this,t=e.get("lastParams"),r=e.get("params");n.mix(t,r)},_initProxy:function(){var e=this,t=e.get("url"),r=e.get("proxy");r instanceof i||(t&&(r.url=t),r="ajax"===r.type||r.url?new i.Ajax(r):new i.Memery(r),e.set("proxy",r))},load:function(e,t){var r=this,a=r.get("proxy"),i=r.get("lastParams");n.mix(i,r.getAppendParams(),e),r.fire("beforeload",{params:i}),e=n.cloneObject(i),a.read(i,function(a){r.onLoad(a,e),t&&t(a,e)},r)},onFiltered:function(e,t){var r=this;r.fire("filtered",{data:e,filter:t})},onLoad:function(e,t){var r=this,a=r.processLoad(e,t);a&&r.afterProcessLoad(e,t)},getResult:function(){},filter:function(e){var t,r=this,a=r.get("remoteFilter");e=e||r.get("filter"),a?r.load({filter:e}):e&&(r.set("filter",e),r.getResult().length>0&&(t=r._filterLocal(e),r.onFiltered(t,e)))},_filterLocal:function(){},getFilterResult:function(){var e=this.get("filter");return e?this._filterLocal(e):this.getResult()},_clearLocalFilter:function(){this.set("filter",null)},clearFilter:function(){var e,t=this,r=t.get("remoteFilter");r?t.load({filter:""}):(t._clearLocalFilter(),e=t.getFilterResult(),t.onFiltered(e,null))},processLoad:function(e){var t=this,r=t.get("hasErrorProperty");return t.fire("beforeprocessload",{data:e}),t.fire("beforeProcessLoad",e),e[r]||e.exception?(t.onException(e),!1):!0},afterProcessLoad:function(){},onException:function(e){var t=this,r=t.get("errorProperty"),a={};e.exception?(a.type="exception",a[r]=e.exception):(a.type="error",a[r]=e[r]),t.fire("exception",a)},hasData:function(){},getAppendParams:function(){return{}}}),r.exports=a}),define("bui-data/1.1.0/src/store",["bui-data/1.1.0/src/proxy","bui-data/1.1.0/src/abstractstore","bui-data/1.1.0/src/sortable","bui-common/1.1.0/index"],function(e,t,r){function a(e,t){if(!(0>e)){var r=t,a=r[e];return r.splice(e,1),a}}function n(e,t){var r=BUI.Array.indexOf(e,t);r>=0&&a(r,t)}function i(e,t){return-1!==BUI.Array.indexOf(e,t)}var o=e("bui-data/1.1.0/src/proxy"),s=e("bui-data/1.1.0/src/abstractstore"),d=e("bui-data/1.1.0/src/sortable"),c=function(e){c.superclass.constructor.call(this,e)};c.ATTRS={autoSync:{value:!1},currentPage:{value:0},deletedRecords:{shared:!1,value:[]},errorProperty:{value:"error"},hasErrorProperty:{value:"hasError"},matchFunction:{value:function(e,t){return e==t}},modifiedRecords:{shared:!1,value:[]},newRecords:{shared:!1,value:[]},remoteSort:{value:!1},resultMap:{shared:!1,value:{}},root:{value:"rows"},rowCount:{value:0},totalProperty:{value:"results"},start:{value:0},pageSize:{}},BUI.extend(c,s),BUI.mixin(c,[d]),BUI.augment(c,{add:function(e,t,r){var a=this,n=a.getCount();a.addAt(e,n,t,r)},addAt:function(e,t,r,a){var i=this;a=a||i._getDefaultMatch(),BUI.isArray(e)||(e=[e]),$.each(e,function(e,o){r&&i.contains(o,a)||(i._addRecord(o,e+t),i.get("newRecords").push(o),n(o,i.get("deletedRecords")),n(o,i.get("modifiedRecords")))})},contains:function(e,t){return-1!==this.findIndexBy(e,t)},find:function(e,t){var r=this,a=null,n=r.getResult();return $.each(n,function(r,n){return n[e]===t?(a=n,!1):void 0}),a},findAll:function(e,t){var r=this,a=[],n=r.getResult();return $.each(n,function(r,n){n[e]===t&&a.push(n)}),a},findByIndex:function(e){return this.getResult()[e]},findIndexBy:function(e,t){var r=this,a=-1,n=r.getResult();return t=t||r._getDefaultMatch(),null===e||void 0===e?-1:($.each(n,function(r,n){return t(e,n)?(a=r,!1):void 0}),a)},findNextRecord:function(e){var t=this,r=t.findIndexBy(e);return r>=0?t.findByIndex(r+1):void 0},getCount:function(){return this.getResult().length},getTotalCount:function(){var e=this,t=e.get("resultMap"),r=e.get("totalProperty");return parseInt(t[r],10)||0},getResult:function(){var e=this,t=e.get("resultMap"),r=e.get("root");return t[r]},hasData:function(){return 0!==this.getCount()},setResult:function(e){var t=this,r=t.get("proxy");r instanceof o.Memery?(t.set("data",e),t.load({start:0})):(t._setResult(e),t.get("filter")&&t.filter())},remove:function(e,t){var r=this;t=t||r._getDefaultMatch(),BUI.isArray(e)||(e=[e]),$.each(e,function(e,o){var e=r.findIndexBy(o,t),s=a(e,r.getResult());i(s,r.get("newRecords"))||i(s,r.get("deletedRecords"))||r.get("deletedRecords").push(s),n(s,r.get("newRecords")),n(s,r.get("modifiedRecords")),r.fire("remove",{record:s})})},save:function(e,t,r){var a=this,n=a.get("proxy");BUI.isFunction(e)&&(r=e,e=void 0),BUI.isObject(e)&&(r=t,t=e,e=void 0),e||(e=a._getSaveType(t)),"all"!=e||t||(t=a._getDirtyData()),a.fire("beforesave",{type:e,saveData:t}),n.save(e,t,function(n){a.onSave(e,t,n),r&&r(n,t)},a)},_getSaveType:function(e){var t=this;return e?BUI.Array.contains(e,t.get("newRecords"))?"add":BUI.Array.contains(e,t.get("modifiedRecords"))?"update":BUI.Array.contains(e,t.get("deletedRecords"))?"remove":"custom":"all"},_getDirtyData:function(){var e=this,t=e.get("proxy");return t.get("url")?{add:BUI.JSON.stringify(e.get("newRecords")),update:BUI.JSON.stringify(e.get("modifiedRecords")),remove:BUI.JSON.stringify(e.get("deletedRecords"))}:{add:e.get("newRecords"),update:e.get("modifiedRecords"),remove:e.get("deletedRecords")}},onSave:function(e,t,r){var a=this,n=a.get("hasErrorProperty");return r[n]||r.exception?void a.onException(r):(a._clearDirty(e,t),a.fire("saved",{type:e,saveData:t,data:r}),void(a.get("autoSync")&&a.load()))},_clearDirty:function(e,t){function r(e,t){BUI.Array.remove(a.get(t),e)}var a=this;switch(e){case"all":a._clearChanges();break;case"add":r(t,"newRecords");break;case"update":r(t,"modifiedRecords");break;case"remove":r(t,"deletedRecords")}},sort:function(e,t){var r=this,a=r.get("remoteSort");a?(r.set("sortField",e),r.set("sortDirection",t),r.load(r.get("sortInfo"))):r._localSort(e,t)},sum:function(e,t){var r=this,a=t||r.getResult(),n=0;return BUI.each(a,function(t){var r=t[e];isNaN(r)||(n+=parseFloat(r))}),n},setValue:function(e,t,r){var a=e,n=this;a[t]=r,i(a,n.get("newRecords"))||i(a,n.get("modifiedRecords"))||n.get("modifiedRecords").push(a),n.fire("update",{record:a,field:t,value:r})},update:function(e,t,r){var a=e,n=this,r=null,o=null;t&&(r=r||n._getDefaultMatch(),o=n.findIndexBy(e,r),o>=0&&(a=n.getResult()[o])),a=BUI.mix(a,e),i(a,n.get("newRecords"))||i(a,n.get("modifiedRecords"))||n.get("modifiedRecords").push(a),n.fire("update",{record:a})},_addRecord:function(e,t){var r=this.getResult();void 0==t&&(t=r.length),r.splice(t,0,e),this.fire("add",{record:e,index:t})},_clearChanges:function(){var e=this;BUI.Array.empty(e.get("newRecords")),BUI.Array.empty(e.get("modifiedRecords")),BUI.Array.empty(e.get("deletedRecords"))},_filterLocal:function(e,t){var r=this,a=[];return t=t||r.getResult(),e?(BUI.each(t,function(t){e(t)&&a.push(t)}),a):t},_getDefaultMatch:function(){return this.get("matchFunction")},_getPageParams:function(){var e=this,t=e.get("sortInfo"),r=e.get("start"),a=e.get("pageSize"),n=e.get("pageIndex")||(a?r/a:0);return params={start:r,limit:a,pageIndex:n},e.get("remoteSort")&&BUI.mix(params,t),params},getAppendParams:function(){return this._getPageParams()},beforeInit:function(){this._setResult([])},_localSort:function(e,t){var r=this;r._sortData(e,t),r.fire("localsort",{field:e,direction:t})},_sortData:function(e,t,r){var a=this;r=r||a.getResult(),a.sortData(e,t,r)},afterProcessLoad:function(e,t){var r=this,a=r.get("root"),n=t.start,i=t.limit,o=r.get("totalProperty");BUI.isArray(e)?r._setResult(e):r._setResult(e[a],e[o]),r.set("start",n),i&&r.set("pageIndex",n/i),r.get("remoteSort")||r._sortData(),r.fire("load",{params:t}),!r.get("remoteFilter")&&r.get("filter")&&r.filter(r.get("filter"))},_setResult:function(e,t){var r=this,a=r.get("resultMap");t=t||e.length,a[r.get("root")]=e,a[r.get("totalProperty")]=t,r._clearChanges()}}),r.exports=c}),define("bui-data/1.1.0/src/node",["bui-common/1.1.0/index"],function(e,t,r){function a(e,t){var r={};return t?(i.each(e,function(e,a){var n=t[a]||a;r[n]=e}),r.record=e):r=e,r}function n(e,t){e=a(e,t),i.mix(this,e)}var i=e("bui-common/1.1.0/index");i.augment(n,{root:!1,leaf:null,text:"",id:null,loaded:!1,path:null,parent:null,level:0,record:null,children:null,isNode:!0}),r.exports=n}),define("bui-data/1.1.0/src/treestore",["bui-common/1.1.0/index","bui-data/1.1.0/src/node","bui-data/1.1.0/src/proxy","bui-data/1.1.0/src/abstractstore","bui-data/1.1.0/src/sortable"],function(e,t,r){function a(e){a.superclass.constructor.call(this,e)}var n=e("bui-common/1.1.0/index"),i=e("bui-data/1.1.0/src/node"),o=e("bui-data/1.1.0/src/proxy"),s=e("bui-data/1.1.0/src/abstractstore");a.ATTRS={root:{},map:{},pidField:{},dataProperty:{value:"nodes"},events:{value:["add","update","remove","load"]}},n.extend(a,s),n.augment(a,{beforeInit:function(){this.initRoot()},_initData:function(){var e=this,t=e.get("autoLoad"),r=e.get("pidField"),a=e.get("proxy"),n=e.get("root");!a.get("url")&&r&&a.get("matchFields").push(r),t&&!n.children&&e.loadNode(n)},initRoot:function(){var e=this,t=e.get("map"),r=e.get("root");r||(r={}),r.isNode||(r=new i(r,t)),r.path=[r.id],r.level=0,r.children&&e.setChildren(r,r.children),e.set("root",r)},add:function(e,t,r){var a=this;return e=a._add(e,t,r),a.fire("add",{node:e,record:e,index:r}),e},_add:function(e,t,r){t=t||this.get("root");var a,o=this,s=o.get("map"),d=t.children;return e.isNode||(e=new i(e,s)),a=e.children||[],0==a.length&&null==e.leaf&&(e.leaf=!0),t&&(t.leaf=!1),e.parent=t,e.level=t.level+1,e.path=t.path.concat(e.id),r=null==r?t.children.length:r,n.Array.addAt(d,e,r),o.setChildren(e,a),e},remove:function(e){var t=e.parent||_self.get("root"),r=n.Array.indexOf(e,t.children);return n.Array.remove(t.children,e),0===t.children.length&&(t.leaf=!0),this.fire("remove",{node:e,record:e,index:r}),e.parent=null,e},setValue:function(e,t,r){var a=this;e[t]=r,a.fire("update",{node:e,record:e,field:t,value:r})},update:function(e){this.fire("update",{node:e,record:e})},getResult:function(){return this.get("root").children},setResult:function(e){var t=this,r=t.get("proxy"),a=t.get("root");r instanceof o.Memery?(t.set("data",e),t.load({id:a.id})):t.setChildren(a,e)},setChildren:function(e,t){var r=this;e.children=[],t.length&&n.each(t,function(t){r._add(t,e)})},findNode:function(e,t,r){return this.findNodeBy(function(t){return t.id===e},t,r)},findNodeBy:function(e,t,r){var a=this;if(r=null==r?!0:r,!t){var i=a.get("root");return e(i)?i:a.findNodeBy(e,i)}var o=t.children,s=null;return n.each(o,function(t){return e(t)?s=t:r&&(s=a.findNodeBy(e,t)),s?!1:void 0}),s},findNodesBy:function(e,t){var r=this,a=[];return t||(t=r.get("root")),n.each(t.children,function(t){e(t)&&a.push(t),a=a.concat(r.findNodesBy(e,t))}),a},findNodeByPath:function(e){if(!e)return null;var t,r,a=this,n=a.get("root"),i=e.split(","),o=i[0];if(!o)return null;if(t=n.id==o?n:a.findNode(o,n,!1)){for(r=1;r + * var store = new Data.Store({ + * url : 'data.php', //设置加载数据的URL + * autoLoad : true //创建Store时自动加载数据 + * }); + * + * @cfg {Boolean} [autoLoad=false] + */ + autoLoad: { + value :false + }, + /** + * 是否服务器端过滤数据,如果设置此属性,当调用filter()函数时发送请求 + * @type {Object} + */ + remoteFilter: { + value : false + }, + /** + * 上次查询的参数 + * @type {Object} + * @readOnly + */ + lastParams : { + shared : false, + value : {} + }, + /** + * 初始化时查询的参数,在初始化时有效 + *

+     * var store = new Data.Store({
+    *     url : 'data.php',  //设置加载数据的URL
+    *     autoLoad : true,    //创建Store时自动加载数据
+    *     params : {         //设置请求时的参数
+    *       id : '1',
+    *       type : '1'
+    *     }
+    *   });
+     * 
+ * @cfg {Object} params + */ + params : { + + }, + /** + * 数据代理对象,用于加载数据的ajax配置,{@link BUI.Data.Proxy} + *

+     *   var store = new Data.Store({
+    *     url : 'data.php',  //设置加载数据的URL
+    *     autoLoad : true,    //创建Store时自动加载数据
+    *     proxy : {
+    *       method : 'post',
+    *       dataType : 'jsonp'
+    *     }
+    *   });
+     * 
+ * @cfg {Object|BUI.Data.Proxy} proxy + */ + proxy : { + shared : false, + value : { + + } + }, + /** + * 请求数据的地址,通过ajax加载数据, + * 此参数设置则加载远程数据 + * ** 你可以设置在proxy外部 ** + *

+     *   var store = new Data.Store({
+    *     url : 'data.php',  //设置加载数据的URL
+    *     autoLoad : true,    //创建Store时自动加载数据
+    *     proxy : {
+    *       method : 'post',
+    *       dataType : 'jsonp'
+    *     }
+    *   });
+     * 
+ * ** 你也可以设置在proxy上 ** + *

+     *   var store = new Data.Store({
+    *     autoLoad : true,    //创建Store时自动加载数据
+    *     proxy : {
+    *       url : 'data.php',  //设置加载数据的URL
+    *       method : 'post',
+    *       dataType : 'jsonp'
+    *     }
+    *   });
+     * 
+ * 否则把 {BUI.Data.Store#cfg-data}作为本地缓存数据加载 + * @cfg {String} url + */ + /** + * 请求数据的url + *

+     *   //更改url
+     *   store.get('proxy').set('url',url);
+     * 
+ * @type {String} + */ + url : { + + }, + events : { + value : [ + /** + * 数据接受改变,所有增加、删除、修改的数据记录清空 + * @name BUI.Data.Store#acceptchanges + * @event + */ + 'acceptchanges', + /** + * 当数据加载完成后 + * @name BUI.Data.Store#load + * @event + * @param {jQuery.Event} e 事件对象,包含加载数据时的参数 + */ + 'load', + + /** + * 当数据加载前 + * @name BUI.Data.Store#beforeload + * @event + */ + 'beforeload', + + /** + * 发生在,beforeload和load中间,数据已经获取完成,但是还未触发load事件,用于获取返回的原始数据 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.data 从服务器端返回的数据 + */ + 'beforeprocessload', + + /** + * 当添加数据时触发该事件 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.record 添加的数据 + */ + 'add', + + /** + * 加载数据发生异常时触发 + * @event + * @name BUI.Data.Store#exception + * @param {jQuery.Event} e 事件对象 + * @param {String|Object} e.error 加载数据时返回的错误信息或者加载数据失败,浏览器返回的信息(httpResponse 对象 的textStatus) + * @param {String} e.responseText 网络或者浏览器加载数据发生错误是返回的httpResponse 对象的responseText + */ + 'exception', + + /** + * 当删除数据是触发该事件 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.data 删除的数据 + */ + 'remove', + + /** + * 当更新数据指定字段时触发该事件 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.record 更新的数据 + * @param {Object} e.field 更新的字段 + * @param {Object} e.value 更新的值 + */ + 'update', + + /** + * 前端发生排序时触发 + * @name BUI.Data.Store#localsort + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.field 排序的字段 + * @param {Object} e.direction 排序的方向 'ASC','DESC' + */ + 'localsort', + + /** + * 前端发生过滤时触发 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Array} e.data 过滤完成的数据 + * @param {Function} e.filter 过滤器 + */ + 'filtered' + ] + }, + /** + * 本地数据源,使用本地数据源时会使用{@link BUI.Data.Proxy.Memery} + * @cfg {Array} data + */ + /** + * 本地数据源 + * @type {Array} + */ + data : { + setter : function(data){ + var _self = this, + proxy = _self.get('proxy'); + if(proxy.set){ + proxy.set('data',data); + }else{ + proxy.data = data; + } + //设置本地数据时,把autoLoad置为true + _self.set('autoLoad',true); + } + } + }; + + BUI.extend(AbstractStore,BUI.Base); + + BUI.augment(AbstractStore,{ + /** + * 是否是数据缓冲对象,用于判断对象 + * @type {Boolean} + */ + isStore : true, + /** + * @private + * 初始化 + */ + _init : function(){ + var _self = this; + + _self.beforeInit(); + //初始化结果集 + _self._initParams(); + _self._initProxy(); + _self._initData(); + }, + /** + * @protected + * 初始化之前 + */ + beforeInit : function(){ + + }, + //初始化数据,如果默认加载数据,则加载数据 + _initData : function(){ + var _self = this, + autoLoad = _self.get('autoLoad'); + + if(autoLoad){ + _self.load(); + } + }, + //初始化查询参数 + _initParams : function(){ + var _self = this, + lastParams = _self.get('lastParams'), + params = _self.get('params'); + + //初始化 参数 + BUI.mix(lastParams,params); + }, + /** + * @protected + * 初始化数据代理类 + */ + _initProxy : function(){ + var _self = this, + url = _self.get('url'), + proxy = _self.get('proxy'); + + if(!(proxy instanceof Proxy)){ + + if(url){ + proxy.url = url; + } + + //异步请求的代理类 + if(proxy.type === 'ajax' || proxy.url){ + proxy = new Proxy.Ajax(proxy); + }else{ + proxy = new Proxy.Memery(proxy); + } + + _self.set('proxy',proxy); + } + }, + /** + * 加载数据 + *

+     *  //一般调用
+     *  store.load(params);
+     *  
+     *  //使用回调函数
+     *  store.load(params,function(data){
+     *  
+     *  });
+     *
+     *  //load有记忆参数的功能
+     *  store.load({id : '123',type="1"});
+     *  //下一次调用
+     *  store.load();默认使用上次的参数,可以对对应的参数进行覆盖
+     * 
+ * @param {Object} params 参数键值对 + * @param {Function} fn 回调函数,默认为空 + */ + load : function(params,callback){ + var _self = this, + proxy = _self.get('proxy'), + lastParams = _self.get('lastParams'); + + BUI.mix(lastParams,_self.getAppendParams(),params); + + _self.fire('beforeload',{params:lastParams}); + + //防止异步请求未结束,又发送新请求回调参数错误 + params = BUI.cloneObject(lastParams); + proxy.read(lastParams,function(data){ + _self.onLoad(data,params); + if(callback){ + callback(data,params); + } + },_self); + }, + /** + * 触发过滤 + * @protected + */ + onFiltered : function(data,filter){ + var _self = this; + _self.fire('filtered',{data : data,filter : filter}); + }, + /** + * 加载完数据 + * @protected + * @template + */ + onLoad : function(data,params){ + var _self = this; + + var processResult = _self.processLoad(data,params); + //如果处理成功,返回错误时,不进行后面的处理 + if(processResult){ + _self.afterProcessLoad(data,params); + } + }, + /** + * 获取当前缓存的纪录 + */ + getResult : function(){ + }, + /** + * 过滤数据,此函数的执行同属性 remoteFilter关联密切 + * + * - remoteFilter == true时:此函数只接受字符串类型的过滤参数,将{filter : filterStr}参数传输到服务器端 + * - remoteFilter == false时:此函数接受比对函数,只有当函数返回true时生效 + * + * @param {Function|String} fn 过滤函数 + * @return {Array} 过滤结果 + */ + filter : function(filter){ + var _self = this, + remoteFilter = _self.get('remoteFilter'), + result; + + filter = filter || _self.get('filter'); + + if(remoteFilter){ + _self.load({filter : filter}); + }else if(filter){ + _self.set('filter',filter); + //如果result有值时才会进行filter + if(_self.getResult().length > 0){ + result = _self._filterLocal(filter); + _self.onFiltered(result,filter); + } + } + }, + /** + * @protected + * 过滤缓存的数据 + * @param {Function} fn 过滤函数 + * @return {Array} 过滤结果 + */ + _filterLocal : function(fn){ + + }, + /** + * 获取过滤后的数据,仅当本地过滤(remoteFilter = false)时有效 + * @return {Array} 过滤过的数据 + */ + getFilterResult: function(){ + var filter = this.get('filter'); + if(filter) { + return this._filterLocal(filter); + } + else { + return this.getResult(); + } + }, + _clearLocalFilter : function(){ + this.set('filter', null); + }, + /** + * 清理过滤 + */ + clearFilter : function(){ + var _self = this, + remoteFilter = _self.get('remoteFilter'), + result; + + if(remoteFilter){ + _self.load({filter : ''}); + }else{ + _self._clearLocalFilter(); + result = _self.getFilterResult(); + _self.onFiltered(result, null); + } + }, + /** + * @private + * 加载完数据处理数据 + */ + processLoad : function(data,params){ + var _self = this, + hasErrorField = _self.get('hasErrorProperty'); + + _self.fire('beforeprocessload',{data : data}); + + //获取的原始数据 + _self.fire('beforeProcessLoad',data); + + if(data[hasErrorField] || data.exception){ + _self.onException(data); + return false; + } + return true; + }, + /** + * @protected + * @template + * 处理数据后 + */ + afterProcessLoad : function(data,params){ + + }, + /** + * @protected + * 处理错误函数 + * @param {*} data 出错对象 + */ + onException : function(data){ + var _self = this, + errorProperty = _self.get('errorProperty'), + obj = {}; + //网络异常、转码错误之类,发生在json获取或转变时 + if(data.exception){ + obj.type = 'exception'; + obj[errorProperty] = data.exception; + }else{//用户定义的错误 + obj.type = 'error'; + obj[errorProperty] = data[errorProperty]; + } + _self.fire('exception',obj); + + }, + /** + * 是否包含数据 + * @return {Boolean} + */ + hasData : function(){ + + }, + /** + * 获取附加的参数 + * @template + * @protected + * @return {Object} 附加的参数 + */ + getAppendParams : function(){ + return {}; + } + }); + +module.exports = AbstractStore; diff --git a/src/node.js b/src/node.js new file mode 100644 index 0000000..c11c33c --- /dev/null +++ b/src/node.js @@ -0,0 +1,91 @@ +/** + * @fileOverview 树形数据结构的节点类,无法直接使用数据作为节点,所以进行一层封装 + * 可以直接作为TreeNode控件的配置项 + * @ignore + */ + + var BUI = require('bui-common'); + + function mapNode(cfg,map){ + var rst = {}; + if(map){ + BUI.each(cfg,function(v,k){ + var name = map[k] || k; + rst[name] = v; + }); + rst.record = cfg; + }else{ + rst = cfg; + } + return rst; + } + /** + * @class BUI.Data.Node + * 树形数据结构的节点类 + */ + function Node (cfg,map) { + var _self = this; + cfg = mapNode(cfg,map); + BUI.mix(this,cfg); + } + + BUI.augment(Node,{ + /** + * 是否根节点 + * @type {Boolean} + */ + root : false, + /** + * 是否叶子节点 + * @type {Boolean} + */ + leaf : null, + /** + * 显示节点时显示的文本 + * @type {Object} + */ + text : '', + /** + * 代表节点的编号 + * @type {String} + */ + id : null, + /** + * 子节点是否已经加载过 + * @type {Boolean} + */ + loaded : false, + /** + * 从根节点到此节点的路径,id的集合如: ['0','1','12'], + * 便于快速定位节点 + * @type {Array} + */ + path : null, + /** + * 父节点 + * @type {BUI.Data.Node} + */ + parent : null, + /** + * 树节点的等级 + * @type {Number} + */ + level : 0, + /** + * 节点是否由一条记录封装而成 + * @type {Object} + */ + record : null, + /** + * 子节点集合 + * @type {BUI.Data.Node[]} + */ + children : null, + /** + * 是否是Node对象 + * @type {Object} + */ + isNode : true + }); + + module.exports = Node; diff --git a/src/proxy.js b/src/proxy.js new file mode 100644 index 0000000..473ab77 --- /dev/null +++ b/src/proxy.js @@ -0,0 +1,446 @@ + + + var Sortable = require('./sortable'); + + /** + * 数据代理对象,加载数据, + * 一般不直接使用,在store里面决定使用什么类型的数据代理对象 + * @class BUI.Data.Proxy + * @extends BUI.Base + * @abstract + */ + var proxy = function(config){ + proxy.superclass.constructor.call(this,config); + }; + + proxy.ATTRS = { + + }; + + BUI.extend(proxy,BUI.Base); + + BUI.augment(proxy, + + { + /** + * @protected + * 读取数据的方法,在子类中覆盖 + */ + _read : function(params,callback){ + + }, + /** + * 读数据 + * @param {Object} params 键值对形式的参数 + * @param {Function} callback 回调函数,函数原型 function(data){} + * @param {Object} scope 回调函数的上下文 + */ + read : function(params,callback,scope){ + var _self = this; + scope = scope || _self; + + _self._read(params,function(data){ + callback.call(scope,data); + }); + }, + /** + * @protected + * 保存数据的方法,在子类中覆盖 + */ + _save : function(ype,data,callback){ + + }, + /** + * 保存数据 + * @param {String} type 类型,包括,add,update,remove,all几种类型 + * @param {Object} saveData 键值对形式的参数 + * @param {Function} callback 回调函数,函数原型 function(data){} + * @param {Object} scope 回调函数的上下文 + */ + save : function(type,saveData,callback,scope){ + var _self = this; + scope = scope || _self; + _self._save(type,saveData,function(data){ + callback.call(scope,data); + }); + } + }); + + + var TYPE_AJAX = { + READ : 'read', + ADD : 'add', + UPDATE : 'update', + REMOVE : 'remove', + SAVE_ALL : 'all' + }; + /** + * 异步加载数据的代理 + * @class BUI.Data.Proxy.Ajax + * @extends BUI.Data.Proxy + */ + var ajaxProxy = function(config){ + ajaxProxy.superclass.constructor.call(this,config); + }; + + ajaxProxy.ATTRS = BUI.mix(true,proxy.ATTRS, + { + /** + * 限制条数 + * @cfg {String} [limitParam='limit'] + */ + /** + * 限制条数 + * @type {String} + * @default 'limit' + */ + limitParam : { + value : 'limit' + }, + /** + * 起始纪录代表的字段 + * @cfg {String} [startParam='start'] + */ + /** + * 起始纪录代表的字段 + * @type {String} + */ + startParam : { + value : 'start' + }, + /** + * 页码的字段名 + * @cfg {String} [pageIndexParam='pageIndex'] + */ + /** + * 页码的字段名 + * @type {String} + * @default 'pageIndex' + */ + pageIndexParam : { + value : 'pageIndex' + }, + /** + * 保存类型的字段名,如果每种保存类型未设置对应的Url,则附加参数 + * @type {Object} + */ + saveTypeParam : { + value : 'saveType' + }, + /** + * 保存数据放到的字段名称 + * @type {String} + */ + saveDataParam : { + + }, + /** + * 传递到后台,分页开始的页码,默认从0开始 + * @type {Number} + */ + pageStart : { + value : 0 + }, + /** + * 加载数据时,返回的格式,目前只支持"json、jsonp"格式
+ * @cfg {String} [dataType='json'] + */ + /** + * 加载数据时,返回的格式,目前只支持"json、jsonp"格式
+ * @type {String} + * @default "json" + */ + dataType: { + value : 'json' + }, + /** + * 获取数据的方式,'GET'或者'POST',默认为'GET' + * @cfg {String} [method='GET'] + */ + /** + * 获取数据的方式,'GET'或者'POST',默认为'GET' + * @type {String} + * @default 'GET' + */ + method : { + value : 'GET' + }, + /** + * 异步请求的所有自定义参数,开放的其他属性用于快捷使用,如果有特殊参数配置,可以使用这个属性,
+ * 不要使用success和error的回调函数,会覆盖默认的处理数据的函数 + * @cfg {Object} ajaxOptions + */ + /** + * 异步请求的所有自定义参数 + * @type {Object} + */ + ajaxOptions : { + value : { + + } + }, + /** + * 是否使用Cache + * @type {Boolean} + */ + cache : { + value : false + }, + /** + * 保存数据的配置信息 + * @type {Object} + */ + save : { + + }, + /** + * 加载数据的链接 + * @cfg {String} url + * @required + */ + /** + * 加载数据的链接 + * @type {String} + * @required + */ + url :{ + + } + + }); + BUI.extend(ajaxProxy,proxy); + + BUI.augment(ajaxProxy,{ + + _processParams : function(params){ + var _self = this, + pageStart = _self.get('pageStart'), + arr = ['start','limit','pageIndex']; + if(params.pageIndex != null){ + params.pageIndex = params.pageIndex + pageStart; + } + BUI.each(arr,function(field){ + var fieldParam = _self.get(field+'Param'); + if(fieldParam !== field){ + params[fieldParam] = params[field]; + delete params[field]; + } + }); + }, + //获取异步请求的url + _getUrl : function(type){ + var _self = this, + save = _self.get('save'), + url; + if(type === TYPE_AJAX.READ){ //获取数据,直接返回 url + return _self.get('url'); + } + + //如果不存在保存参数,则返回 url + if(!save){ + return _self.get('url') + } + + if(BUI.isString(save)){ + return save; + } + + url = save[type + 'Url']; + if(!url){ + url = _self.get('url'); + } + + return url; + + }, + //根据类型附加额外的参数 + _getAppendParams : function(type){ + var _self = this, + save, + saveTypeParam, + rst = null; + if(type == TYPE_AJAX.READ){ + return rst; + } + save = _self.get('save'); + saveTypeParam = _self.get('saveTypeParam'); + if(save && !save[type + 'Url']){ + rst = {}; + rst[saveTypeParam] = type; + } + return rst; + }, + /** + * @protected + * @private + */ + _read : function(params,callback){ + var _self = this, + cfg; + + params = BUI.cloneObject(params); + _self._processParams(params); + cfg = _self._getAjaxOptions(TYPE_AJAX.READ,params); + + _self._ajax(cfg,callback); + }, + //获取异步请求的选项 + _getAjaxOptions : function(type,params){ + var _self = this, + ajaxOptions = _self.get('ajaxOptions'), + url = _self._getUrl(type), + cfg; + BUI.mix(params,_self._getAppendParams(type)); + cfg = BUI.merge({ + url: url, + type : _self.get('method'), + dataType: _self.get('dataType'), + data : params, + cache : _self.get('cache') + },ajaxOptions); + + return cfg; + }, + //异步请求 + _ajax : function(cfg,callback){ + var _self = this, + success = cfg.success, + error = cfg.error; + //复写success + cfg.success = function(data){ + success && success(data); + callback(data); + }; + //复写错误 + cfg.error = function(jqXHR, textStatus, errorThrown){ + error && error(jqXHR, textStatus, errorThrown); + var result = { + exception : { + status : textStatus, + errorThrown: errorThrown, + jqXHR : jqXHR + } + }; + callback(result); + } + + $.ajax(cfg); + + }, + _save : function(type,data,callback){ + var _self = this, + cfg; + + cfg = _self._getAjaxOptions(type,data); + + _self._ajax(cfg,callback); + } + + }); + + /** + * 读取缓存的代理 + * @class BUI.Data.Proxy.Memery + * @extends BUI.Data.Proxy + * @mixins BUI.Data.Sortable + */ + var memeryProxy = function(config){ + memeryProxy.superclass.constructor.call(this,config); + }; + memeryProxy.ATTRS = { + /** + * 匹配的字段名 + * @type {Array} + */ + matchFields : { + value : [] + } + }; + + BUI.extend(memeryProxy,proxy); + + BUI.mixin(memeryProxy,[Sortable]); + + BUI.augment(memeryProxy,{ + + /** + * @protected + * @ignore + */ + _read : function(params,callback){ + var _self = this, + pageable = params.pageable, + start = params.start, + sortField = params.sortField, + sortDirection = params.sortDirection, + limit = params.limit, + data = _self.get('data'), + rows = []; + + data = _self._getMatches(params); + _self.sortData(sortField,sortDirection); + + if(limit){//分页时 + rows = data.slice(start,start + limit); + callback({rows:rows,results:data.length}); + }else{//不分页时 + rows = data.slice(start); + callback(rows); + } + + }, + //获取匹配函数 + _getMatchFn : function(params, matchFields){ + var _self = this; + return function(obj){ + var result = true; + BUI.each(matchFields,function(field){ + if(params[field] != null && !(params[field] === obj[field])){ + result = false; + return false; + } + }); + return result; + } + }, + //获取匹配的值 + _getMatches : function(params){ + var _self = this, + matchFields = _self.get('matchFields'), + matchFn, + data = _self.get('data') || []; + if(params && matchFields.length){ + matchFn = _self._getMatchFn(params,matchFields); + data = BUI.Array.filter(data,matchFn); + } + return data; + }, + /** + * @protected + * 保存修改的数据 + */ + _save : function(type,saveData,callback){ + var _self = this, + data = _self.get('data'); + + if(type == TYPE_AJAX.ADD){ + data.push(saveData); + }else if(type == TYPE_AJAX.REMOVE){ + BUI.Array.remove(data,saveData); + }else if(type == TYPE_AJAX.SAVE_ALL){ + BUI.each(saveData.add,function(item){ + data.push(item); + }); + + BUI.each(saveData.remove,function(item){ + BUI.Array.remove(data,item); + }); + } + } + + }); + + proxy.Ajax = ajaxProxy; + proxy.Memery = memeryProxy; + + module.exports = proxy; diff --git a/src/sortable.js b/src/sortable.js new file mode 100644 index 0000000..2dac8e3 --- /dev/null +++ b/src/sortable.js @@ -0,0 +1,163 @@ +/** + * @fileOverview 可排序扩展类 + * @ignore + */ + + + var ASC = 'ASC', + DESC = 'DESC'; + /** + * 排序扩展方法,无法直接使用 + * 请在继承了 {@link BUI.Base}的类上使用 + * @class BUI.Data.Sortable + * @extends BUI.Base + */ + var sortable = function(){ + + }; + + sortable.ATTRS = + + { + /** + * 比较函数 + * @cfg {Function} compareFunction + * 函数原型 function(v1,v2),比较2个字段是否相等 + * 如果是字符串则按照本地比较算法,否则使用 > ,== 验证 + */ + compareFunction:{ + value : function(v1,v2){ + if(v1 === undefined){ + v1 = ''; + } + if(v2 === undefined){ + v2 = ''; + } + if(BUI.isString(v1)){ + return v1.localeCompare(v2); + } + + if(v1 > v2){ + return 1; + }else if(v1 === v2){ + return 0; + }else{ + return -1; + } + } + }, + /** + * 排序字段 + * @cfg {String} sortField + */ + /** + * 排序字段 + * @type {String} + */ + sortField : { + + }, + /** + * 排序方向,'ASC'、'DESC' + * @cfg {String} [sortDirection = 'ASC'] + */ + /** + * 排序方向,'ASC'、'DESC' + * @type {String} + */ + sortDirection : { + value : 'ASC' + }, + /** + * 排序信息 + *
    + *
  1. field: 排序字段
  2. + *
  3. direction: 排序方向,ASC(默认),DESC
  4. + *
+ * @cfg {Object} sortInfo + */ + /** + * 排序信息 + *
    + *
  1. field: 排序字段
  2. + *
  3. direction: 排序方向,ASC(默认),DESC
  4. + *
+ * @type {Object} + */ + sortInfo: { + getter : function(){ + var _self = this, + field = _self.get('sortField'); + + return { + field : field, + direction : _self.get('sortDirection') + }; + }, + setter: function(v){ + var _self = this; + + _self.set('sortField',v.field); + _self.set('sortDirection',v.direction); + } + } + }; + + BUI.augment(sortable, + { + compare : function(obj1,obj2,field,direction){ + + var _self = this, + dir; + field = field || _self.get('sortField'); + direction = direction || _self.get('sortDirection'); + //如果未指定排序字段,或方向,则按照默认顺序 + if(!field || !direction){ + return 1; + } + dir = direction === ASC ? 1 : -1; + + return _self.get('compareFunction')(obj1[field],obj2[field]) * dir; + }, + /** + * 获取排序的集合 + * @protected + * @return {Array} 排序集合 + */ + getSortData : function(){ + + }, + /** + * 排序数据 + * @param {String|Array} field 排序字段或者数组 + * @param {String} direction 排序方向 + * @param {Array} records 排序 + * @return {Array} + */ + sortData : function(field,direction,records){ + var _self = this, + records = records || _self.getSortData(); + + if(BUI.isArray(field)){ + records = field; + field = null; + } + + field = field || _self.get('sortField'); + direction = direction || _self.get('sortDirection'); + + _self.set('sortField',field); + _self.set('sortDirection',direction); + + if(!field || !direction){ + return records; + } + + records.sort(function(obj1,obj2){ + return _self.compare(obj1,obj2,field,direction); + }); + return records; + } + }); + +module.export = sortable; diff --git a/src/store.js b/src/store.js new file mode 100644 index 0000000..b1e1688 --- /dev/null +++ b/src/store.js @@ -0,0 +1,922 @@ +/** + * @fileOverview 数据缓冲对象 + * @author dxq613@gmail.com + * @ignore + */ + + + var Proxy = require('./proxy'), + AbstractStore = require('./abstractstore'), + Sortable = require('./sortable'); + + //移除数据 + function removeAt(index,array){ + if(index < 0){ + return; + } + var records = array, + record = records[index]; + records.splice(index,1); + return record; + } + + function removeFrom(record,array){ + var index = BUI.Array.indexOf(record,array); + if(index >= 0){ + removeAt(index,array); + } + } + + function contains(record,array){ + return BUI.Array.indexOf(record,array) !== -1; + } + /** + * 用于加载数据,缓冲数据的类 + *

+ * + *

+ * ** 缓存静态数据 ** + *

+   *  var store = new Store({
+   *    data : [{},{}]
+   *  });
+   * 
+ * ** 异步加载数据 ** + *

+   *  var store = new Store({
+   *    url : 'data.json',
+   *    autoLoad : true,
+   *    params : {id : '123'},
+   *    sortInfo : {
+   *      field : 'id',
+   *      direction : 'ASC' //ASC,DESC
+   *    }
+   *  });
+   * 
+ * + * @class BUI.Data.Store + * @extends BUI.Data.AbstractStore + * @mixins BUI.Data.Sortable + */ + var store = function(config){ + store.superclass.constructor.call(this,config); + //this._init(); + }; + + store.ATTRS = + { + /** + * 保存数据时,是否自动更新数据源的数据,常用于添加、删除、更改数据后重新加载数据。 + * @cfg {Boolean} autoSync + */ + autoSync : { + value : false + }, + /** + * 当前页码 + * @cfg {Number} [currentPage=0] + * @ignore + */ + /** + * 当前页码 + * @type {Number} + * @ignore + * @readOnly + */ + currentPage:{ + value : 0 + }, + + /** + * 删除掉的纪录 + * @readOnly + * @private + * @type {Array} + */ + deletedRecords : { + shared : false, + value:[] + }, + /** + * 错误字段,包含在返回信息中表示错误信息的字段 + *

+     *   //可以修改接收的后台参数的含义
+     *   var store = new Store({
+     *     url : 'data.json',
+     *     errorProperty : 'errorMsg', //存放错误信息的字段(error)
+     *     hasErrorProperty : 'isError', //是否错误的字段(hasError)
+     *     root : 'data',               //存放数据的字段名(rows)
+     *     totalProperty : 'total'     //存放记录总数的字段名(results)
+     *   });
+     * 
+ * @cfg {String} [errorProperty='error'] + */ + /** + * 错误字段 + * @type {String} + * @ignore + */ + errorProperty : { + value : 'error' + }, + /** + * 是否存在错误,加载数据时如果返回错误,此字段表示有错误发生 + *

+     *   //可以修改接收的后台参数的含义
+     *   var store = new Store({
+     *     url : 'data.json',
+     *     errorProperty : 'errorMsg', //存放错误信息的字段(error)
+     *     hasErrorProperty : 'isError', //是否错误的字段(hasError)
+     *     root : 'data',               //存放数据的字段名(rows)
+     *     totalProperty : 'total'     //存放记录总数的字段名(results)
+     *   });
+     * 
+ * @cfg {String} [hasErrorProperty='hasError'] + */ + /** + * 是否存在错误 + * @type {String} + * @default 'hasError' + * @ignore + */ + hasErrorProperty : { + value : 'hasError' + }, + + /** + * 对比2个对象是否相当,在去重、更新、删除,查找数据时使用此函数 + * @default + * function(obj1,obj2){ + * return obj1 == obj2; + * } + * @type {Object} + * @example + * function(obj1 ,obj2){ + * //如果id相等,就认为2个数据相等,可以在添加对象时去重 + * //更新对象时,仅提供改变的字段 + * return obj1.id == obj2.id; + * } + * + */ + matchFunction : { + value : function(obj1,obj2){ + return obj1 == obj2; + } + }, + /** + * 更改的纪录集合 + * @type {Array} + * @private + * @readOnly + */ + modifiedRecords : { + shared : false, + value:[] + }, + /** + * 新添加的纪录集合,只读 + * @type {Array} + * @private + * @readOnly + */ + newRecords : { + shared : false, + value : [] + }, + /** + * 是否远程排序,默认状态下内存排序 + * - 由于当前Store存储的不一定是数据源的全集,所以此配置项需要重新读取数据 + * - 在分页状态下,进行远程排序,会进行全集数据的排序,并返回首页的数据 + * - remoteSort为 false的情况下,仅对当前页的数据进行排序 + * @cfg {Boolean} [remoteSort=false] + */ + remoteSort : { + value : false + }, + /** + * 缓存的数据,包含以下几个字段 + *
    + *
  1. rows: 数据集合
  2. + *
  3. results: 总的数据条数
  4. + *
+ * @type {Object} + * @private + * @readOnly + */ + resultMap : { + shared : false, + value : {} + }, + /** + * 加载数据时,返回数据的根目录 + * @cfg {String} [root='rows'] + *

+     *    //默认返回数据类型:
+     *    '{"rows":[{"name":"abc"},{"name":"bcd"}],"results":100}'
+     *   //可以修改接收的后台参数的含义
+     *   var store = new Store({
+     *     url : 'data.json',
+     *     errorProperty : 'errorMsg', //存放错误信息的字段(error)
+     *     hasErrorProperty : 'isError', //是否错误的字段(hasError)
+     *     root : 'data',               //存放数据的字段名(rows)
+     *     totalProperty : 'total'     //存放记录总数的字段名(results)
+     *   });
+     * 
+ * + */ + root: { value : 'rows'}, + + /** + * 当前Store缓存的数据条数 + * @type {Number} + * @private + * @readOnly + */ + rowCount :{ + value : 0 + }, + /** + * 加载数据时,返回记录的总数的字段,用于分页 + * @cfg {String} [totalProperty='results'] + *

+     *    //默认返回数据类型:
+     *    '{"rows":[{"name":"abc"},{"name":"bcd"}],"results":100}'
+     *   //可以修改接收的后台参数的含义
+     *   var store = new Store({
+     *     url : 'data.json',
+     *     errorProperty : 'errorMsg', //存放错误信息的字段(error)
+     *     hasErrorProperty : 'isError', //是否错误的字段(hasError)
+     *     root : 'data',               //存放数据的字段名(rows)
+     *     totalProperty : 'total'     //存放记录总数的字段名(results)
+     *   });
+     * 
+ */ + totalProperty: {value :'results'}, + + /** + * 加载数据的起始位置 + *

+     *  //初始化时,可以在params中配置
+     *  var store = new Store({
+     *    url : 'data.json',
+     *    params : {
+     *      start : 100
+     *    }
+     *  });
+     * 
+ * @type {Object} + */ + start:{ + value : 0 + }, + /** + * 每页多少条记录,默认为null,此时不分页,当指定了此值时分页 + *

+     *  //当请求的数据分页时
+     *  var store = new Store({
+     *    url : 'data.json',
+     *    pageSize : 30
+     *  });
+     * 
+ * @cfg {Number} pageSize + */ + pageSize : { + + } + }; + BUI.extend(store,AbstractStore); + + BUI.mixin(store,[Sortable]); + + BUI.augment(store, + { + /** + * 添加记录,默认添加在后面 + *

+    *  //添加记录
+    *  store.add({id : '2',text: 'new data'});
+    *  //是否去重,重复数据不能添加
+    *  store.add(obj,true); //不能添加重复数据,此时用obj1 === obj2判断
+    *  //使用匹配函去重
+    *  store.add(obj,true,function(obj1,obj2){
+    *    return obj1.id == obj2.id;
+    *  });
+    *  
+    * 
+ * @param {Array|Object} data 添加的数据,可以是数组,可以是单条记录 + * @param {Boolean} [noRepeat = false] 是否去重,可以为空,默认: false + * @param {Function} [match] 匹配函数,可以为空, + * @default 配置项中 matchFunction 属性传入的函数,默认是:
+ * function(obj1,obj2){ + * return obj1 == obj2; + * } + * + */ + add :function(data,noRepeat,match){ + var _self = this, + count = _self.getCount(); + _self.addAt(data,count,noRepeat,match) + }, + /** + * 添加记录,指定索引值 + *

+    *  //使用方式跟类似于add,增加了index参数
+    *  store.add(obj,0);//添加在最前面
+    * 
+ * @param {Array|Object} data 添加的数据,可以是数组,可以是单条记录 + * @param {Number} index 开始添加数据的位置 + * @param {Boolean} [noRepeat = false] 是否去重,可以为空,默认: false + * @param {Function} [match] 匹配函数,可以为空, + */ + addAt : function(data,index,noRepeat,match){ + var _self = this; + + match = match || _self._getDefaultMatch(); + if(!BUI.isArray(data)){ + data = [data]; + } + + $.each(data,function(pos,element){ + if(!noRepeat || !_self.contains(element,match)){ + _self._addRecord(element,pos + index); + + _self.get('newRecords').push(element); + + removeFrom(element,_self.get('deletedRecords')); + removeFrom(element,_self.get('modifiedRecords')); + } + }); + }, + /** + * 验证是否存在指定记录 + *

+    *  store.contains(obj); //是否包含指定的记录
+    *
+    *  store.contains(obj,function(obj1,obj2){ //使用匹配函数
+    *    return obj1.id == obj2.id;
+    *  });
+    * 
+ * @param {Object} record 指定的记录 + * @param {Function} [match = function(obj1,obj2){return obj1 == obj2}] 默认为比较2个对象是否相同 + * @return {Boolean} + */ + contains :function(record,match){ + return this.findIndexBy(record,match)!==-1; + }, + /** + * 查找记录,仅返回第一条 + *

+    *  var record = store.find('id','123');
+    * 
+ * @param {String} field 字段名 + * @param {String} value 字段值 + * @return {Object|null} + */ + find : function(field,value){ + var _self = this, + result = null, + records = _self.getResult(); + $.each(records,function(index,record){ + if(record[field] === value){ + result = record; + return false; + } + }); + return result; + }, + /** + * 查找记录,返回所有符合查询条件的记录 + *

+    *   var records = store.findAll('type','0');
+    * 
+ * @param {String} field 字段名 + * @param {String} value 字段值 + * @return {Array} + */ + findAll : function(field,value){ + var _self = this, + result = [], + records = _self.getResult(); + $.each(records,function(index,record){ + if(record[field] === value){ + result.push(record); + } + }); + return result; + }, + /** + * 根据索引查找记录 + *

+    *  var record = store.findByIndex(1);
+    * 
+ * @param {Number} index 索引 + * @return {Object} 查找的记录 + */ + findByIndex : function(index){ + return this.getResult()[index]; + }, + /** + * 查找数据所在的索引位置,若不存在返回-1 + *

+    *  var index = store.findIndexBy(obj);
+    *
+    *  var index = store.findIndexBy(obj,function(obj1,obj2){
+    *    return obj1.id == obj2.id;
+    *  });
+    * 
+ * @param {Object} target 指定的记录 + * @param {Function} [match = matchFunction] @see {BUI.Data.Store#matchFunction}默认为比较2个对象是否相同 + * @return {Number} + */ + findIndexBy :function(target,match){ + var _self = this, + position = -1, + records = _self.getResult(); + match = match || _self._getDefaultMatch(); + if(target === null || target === undefined){ + return -1; + } + $.each(records,function(index,record){ + if(match(target,record)){ + position = index; + return false; + } + }); + return position; + }, + /** + * 获取下一条记录 + *

+    *  var record = store.findNextRecord(obj);
+    * 
+ * @param {Object} record 当前记录 + * @return {Object} 下一条记录 + */ + findNextRecord : function(record){ + var _self = this, + index = _self.findIndexBy(record); + if(index >= 0){ + return _self.findByIndex(index + 1); + } + return; + }, + + /** + * 获取缓存的记录数 + *

+     *  var count = store.getCount(); //缓存的数据数量
+     *
+     *  var totalCount = store.getTotalCount(); //数据的总数,如果有分页时,totalCount != count
+     * 
+ * @return {Number} 记录数 + */ + getCount : function(){ + return this.getResult().length; + }, + /** + * 获取数据源的数据总数,分页时,当前仅缓存当前页数据 + *

+     *  var count = store.getCount(); //缓存的数据数量
+     *
+     *  var totalCount = store.getTotalCount(); //数据的总数,如果有分页时,totalCount != count
+     * 
+ * @return {Number} 记录的总数 + */ + getTotalCount : function(){ + var _self = this, + resultMap = _self.get('resultMap'), + total = _self.get('totalProperty'); + return parseInt(resultMap[total],10) || 0; + }, + /** + * 获取当前缓存的纪录 + *

+     *   var records = store.getResult();
+     * 
+ * @return {Array} 纪录集合 + */ + getResult : function(){ + var _self = this, + resultMap = _self.get('resultMap'), + root = _self.get('root'); + return resultMap[root]; + }, + /** + * 是否包含数据 + * @return {Boolean} + */ + hasData : function(){ + return this.getCount() !== 0; + }, + /** + * 设置数据源,非异步加载时,设置缓存的数据 + *

+     *   store.setResult([]); //清空数据
+     *
+     *   var data = [{},{}];
+     *   store.setResult(data); //重设数据
+     * 
+ */ + setResult : function(data){ + var _self = this, + proxy = _self.get('proxy'); + if(proxy instanceof Proxy.Memery){ + _self.set('data',data); + _self.load({start:0}); + }else{ + _self._setResult(data); + //如果有filter则进行过滤 + if(_self.get('filter')){ + _self.filter(); + } + } + }, + + /** + * 删除一条或多条记录触发 remove 事件. + *

+    *  store.remove(obj);  //删除一条记录
+    *
+    *  store.remove([obj1,obj2...]); //删除多个条记录
+    *
+    *  store.remvoe(obj,funciton(obj1,obj2){ //使用匹配函数
+    *    return obj1.id == obj2.id;
+    *  });
+    * 
+ * @param {Array|Object} data 添加的数据,可以是数组,可以是单条记录 + * @param {Function} [match = function(obj1,obj2){return obj1 == obj2}] 匹配函数,可以为空 + */ + remove :function(data,match){ + var _self =this, + delData=[]; + match = match || _self._getDefaultMatch(); + if(!BUI.isArray(data)){ + data = [data]; + } + $.each(data,function(index,element){ + var index = _self.findIndexBy(element,match), + record = removeAt(index,_self.getResult()); + //添加到已删除队列中,如果是新添加的数据,不计入删除的数据集合中 + if(!contains(record,_self.get('newRecords')) && !contains(record,_self.get('deletedRecords'))){ + _self.get('deletedRecords').push(record); + } + removeFrom(record,_self.get('newRecords')); + removeFrom(record,_self.get('modifiedRecords')); + _self.fire('remove',{record:record}); + }); + }, + /** + * 保存数据,有几种类型: + * + * - add 保存添加的记录, + * - remove 保存删除, + * - update 保存更新, + * - all 保存store从上次加载到目前更改的记录 + * + * + * @param {String} type 保存的类型 + * @param {Object} saveData 数据 + * @param {Function} callback + */ + save : function(type,saveData,callback){ + var _self = this, + proxy = _self.get('proxy'); + + if(BUI.isFunction(type)){ //只有回调函数 + callback = type; + type = undefined; + } + if(BUI.isObject(type)){ //未指定类型 + callback = saveData; + saveData = type; + type = undefined; + } + if(!type){ + type = _self._getSaveType(saveData); + } + if(type == 'all' && !saveData){//如果保存全部,同时未提供保存的数据,自动获取 + saveData = _self._getDirtyData(); + } + + _self.fire('beforesave',{type : type,saveData : saveData}); + + proxy.save(type,saveData,function(data){ + _self.onSave(type,saveData,data); + if(callback){ + callback(data,saveData); + } + },_self); + + }, + //根据保存的数据获取保存的类型 + _getSaveType :function(saveData){ + var _self = this; + if(!saveData){ + return 'all'; + } + + if(BUI.Array.contains(saveData,_self.get('newRecords'))){ + return 'add'; + } + + if(BUI.Array.contains(saveData,_self.get('modifiedRecords'))){ + return 'update'; + } + + if(BUI.Array.contains(saveData,_self.get('deletedRecords'))){ + return 'remove'; + } + return 'custom'; + }, + //获取未保存的数据 + _getDirtyData : function(){ + var _self = this, + proxy = _self.get('proxy'); + if(proxy.get('url')){ + return { + add : BUI.JSON.stringify(_self.get('newRecords')), + update : BUI.JSON.stringify(_self.get('modifiedRecords')), + remove : BUI.JSON.stringify(_self.get('deletedRecords')) + }; + }else{ + return { + add : _self.get('newRecords'), + update : _self.get('modifiedRecords'), + remove : _self.get('deletedRecords') + }; + } + + }, + /** + * 保存完成后 + * @private + */ + onSave : function(type,saveData,data){ + var _self = this, + hasErrorField = _self.get('hasErrorProperty'); + + if(data[hasErrorField] || data.exception){ //如果失败 + _self.onException(data); + return; + } + _self._clearDirty(type,saveData); + + _self.fire('saved',{type : type,saveData : saveData,data : data}); + if(_self.get('autoSync')){ + _self.load(); + } + }, + //清除脏数据 + _clearDirty : function(type,saveData){ + var _self = this; + switch(type){ + case 'all' : + _self._clearChanges(); + break; + case 'add' : + removeFrom(saveData,'newRecords'); + break; + case 'update' : + removeFrom(saveData,'modifiedRecords'); + break; + case 'remove' : + removeFrom(saveData,'deletedRecords'); + break; + default : + break; + } + function removeFrom(obj,name){ + BUI.Array.remove(_self.get(name),obj); + } + }, + /** + * 排序,如果remoteSort = true,发送请求,后端排序 + *

+     *   store.sort('id','DESC'); //以id为排序字段,倒序排序
+     * 
+ * @param {String} field 排序字段 + * @param {String} direction 排序方向 + */ + sort : function(field,direction){ + var _self = this, + remoteSort = _self.get('remoteSort'); + + if(!remoteSort){ + _self._localSort(field,direction); + }else{ + _self.set('sortField',field); + _self.set('sortDirection',direction); + _self.load(_self.get('sortInfo')); + } + }, + /** + * 计算指定字段的和 + *

+     *   var sum = store.sum('number');
+     * 
+ * @param {String} field 字段名 + * @param {Array} [data] 计算的集合,默认为Store中的数据集合 + * @return {Number} 汇总和 + */ + sum : function(field,data){ + var _self = this, + records = data || _self.getResult(), + sum = 0; + BUI.each(records,function(record){ + var val = record[field]; + if(!isNaN(val)){ + sum += parseFloat(val); + } + }); + return sum; + }, + /** + * 设置记录的值 ,触发 update 事件 + *

+    *  store.setValue(obj,'value','new value');
+    * 
+ * @param {Object} obj 修改的记录 + * @param {String} field 修改的字段名 + * @param {Object} value 修改的值 + */ + setValue : function(obj,field,value){ + var record = obj, + _self = this; + + record[field]=value; + if(!contains(record,_self.get('newRecords')) && !contains(record,_self.get('modifiedRecords'))){ + _self.get('modifiedRecords').push(record); + } + _self.fire('update',{record:record,field:field,value:value}); + }, + /** + * 更新记录 ,触发 update事件 + *

+    *   var record = store.find('id','12');
+    *   record.value = 'new value';
+    *   record.text = 'new text';
+    *   store.update(record); //触发update事件,引起绑定了store的控件更新
+    * 
+ * @param {Object} obj 修改的记录 + * @param {Boolean} [isMatch = false] 是否需要进行匹配,检测指定的记录是否在集合中 + * @param {Function} [match = matchFunction] 匹配函数 + */ + update : function(obj,isMatch,match){ + var record = obj, + _self = this, + match = null, + index = null; + if(isMatch){ + match = match || _self._getDefaultMatch(); + index = _self.findIndexBy(obj,match); + if(index >=0){ + record = _self.getResult()[index]; + } + } + record = BUI.mix(record,obj); + if(!contains(record,_self.get('newRecords')) && !contains(record,_self.get('modifiedRecords'))){ + _self.get('modifiedRecords').push(record); + } + _self.fire('update',{record:record}); + }, + //添加纪录 + _addRecord :function(record,index){ + var records = this.getResult(); + if(index == undefined){ + index = records.length; + } + records.splice(index,0,record); + this.fire('add',{record:record,index:index}); + }, + //清除改变的数据记录 + _clearChanges : function(){ + var _self = this; + BUI.Array.empty(_self.get('newRecords')); + BUI.Array.empty(_self.get('modifiedRecords')); + BUI.Array.empty(_self.get('deletedRecords')); + }, + /** + * @protected + * 过滤缓存的数据 + * @param {Function} fn 过滤函数 + * @return {Array} 过滤结果 + */ + _filterLocal : function(fn,data){ + + var _self = this, + rst = []; + data = data || _self.getResult(); + if(!fn){ //没有过滤器时直接返回 + return data; + } + BUI.each(data,function(record){ + if(fn(record)){ + rst.push(record); + } + }); + return rst; + }, + //获取默认的匹配函数 + _getDefaultMatch :function(){ + + return this.get('matchFunction'); + }, + + //获取分页相关的信息 + _getPageParams : function(){ + var _self = this, + sortInfo = _self.get('sortInfo'), + start = _self.get('start'), + limit = _self.get('pageSize'), + pageIndex = _self.get('pageIndex') || (limit ? start/limit : 0); + + params = { + start : start, + limit : limit, + pageIndex : pageIndex //一般而言,pageIndex = start/limit + }; + + if(_self.get('remoteSort')){ + BUI.mix(params,sortInfo); + } + + return params; + }, + /** + * 获取附加的参数,分页信息,排序信息 + * @override + * @protected + * @return {Object} 附加的参数 + */ + getAppendParams : function(){ + return this._getPageParams(); + }, + /** + * @protected + * 初始化之前 + */ + beforeInit : function(){ + //初始化结果集 + this._setResult([]); + }, + //本地排序 + _localSort : function(field,direction){ + var _self = this; + + _self._sortData(field,direction); + + _self.fire('localsort',{field:field,direction:direction}); + }, + _sortData : function(field,direction,data){ + var _self = this; + data = data || _self.getResult(); + + _self.sortData(field,direction,data); + }, + //处理数据 + afterProcessLoad : function(data,params){ + var _self = this, + root = _self.get('root'), + start = params.start, + limit = params.limit, + totalProperty = _self.get('totalProperty'); + + if(BUI.isArray(data)){ + _self._setResult(data); + }else{ + _self._setResult(data[root],data[totalProperty]); + } + + _self.set('start',start); + + if(limit){ + _self.set('pageIndex',start/limit); + } + + //如果本地排序,则排序 + if(!_self.get('remoteSort')){ + _self._sortData(); + } + + _self.fire('load',{ params : params }); + + //如果有本地过滤,则本地过滤 + if(!_self.get('remoteFilter') && _self.get('filter')){ + _self.filter(_self.get('filter')); + } + }, + //设置结果集 + _setResult : function(rows,totalCount){ + var _self = this, + resultMap = _self.get('resultMap'); + + totalCount = totalCount || rows.length; + resultMap[_self.get('root')] = rows; + resultMap[_self.get('totalProperty')] = totalCount; + + //清理之前发生的改变 + _self._clearChanges(); + } + }); + +module.exports = store; diff --git a/src/treestore.js b/src/treestore.js new file mode 100644 index 0000000..4ed70fb --- /dev/null +++ b/src/treestore.js @@ -0,0 +1,606 @@ +/** + * @fileOverview 树形对象缓冲类 + * @ignore + */ + + + var BUI = require('bui-common'), + Node = require('./node'), + Proxy = require('./proxy'), + AbstractStore = require('./abstractstore'); + + /** + * @class BUI.Data.TreeStore + * 树形数据缓冲类 + *

+ * + *

+ *

+   *   //加载静态数据
+   *   var store = new TreeStore({
+   *     root : {
+   *       text : '根节点',
+   *       id : 'root'
+   *     },
+   *     data : [{id : '1',text : 1},{id : '2',text : 2}] //会加载成root的children
+   *   });
+   *   //异步加载数据,自动加载数据时,会调用store.load({id : 'root'}); //root为根节点的id
+   *   var store = new TreeStore({
+   *     root : {
+   *       text : '根节点',
+   *       id : 'root'
+   *     },
+   *     url : 'data/nodes.php',
+   *     autoLoad : true  //设置自动加载,初始化后自动加载数据
+   *   });
+   *
+   *   //加载指定节点
+   *   var node = store.findNode('1');
+   *   store.loadNode(node);
+   *   //或者
+   *   store.load({id : '1'});//可以配置自定义参数,返回值附加到指定id的节点上
+   * 
+ * @extends BUI.Data.AbstractStore + */ + function TreeStore(config){ + TreeStore.superclass.constructor.call(this,config); + } + + TreeStore.ATTRS = { + /** + * 根节点 + *

+     *  var store = new TreeStore({
+     *    root : {text : '根节点',id : 'rootId',children : [{id : '1',text : '1'}]}
+     *  });
+     * 
+ * @cfg {Object} root + */ + /** + * 根节点,初始化后不要更改对象,可以更改属性值 + *

+     *  var root = store.get('root');
+     *  root.text = '修改的文本';
+     *  store.update(root);
+     * 
+ * @type {Object} + * @readOnly + */ + root : { + + }, + /** + * 数据映射,用于设置的数据跟@see {BUI.Data.Node} 不一致时,进行匹配。 + * 如果此属性为null,那么假设设置的对象是Node对象 + *

+     *   //例如原始数据为 {name : '123',value : '文本123',isLeaf: false,nodes : []}
+     *   var store = new TreeStore({
+     *     map : {
+     *       'name' : 'id',
+     *       'value' : 'text',
+     *       'isLeaf' : 'leaf' ,
+     *       'nodes' : 'children'
+     *     }
+     *   });
+     *   //映射后,记录会变成  {id : '123',text : '文本123',leaf: false,children : []};
+     *   //此时原始记录会作为对象的 record属性
+     *   var node = store.findNode('123'),
+     *     record = node.record;
+     * 
+ * **Notes:** + * 使用数据映射的记录仅做于展示数据,不作为可更改的数据,add,update不会更改数据的原始数据 + * @cfg {Object} map + */ + map : { + + }, + /** + * 标示父元素id的字段名称 + * @type {String} + */ + pidField : { + + }, + /** + * 返回数据标示数据的字段
+ * 异步加载数据时,返回数据可以使数组或者对象 + * - 如果返回的是对象,可以附加其他信息,那么取对象对应的字段 {nodes : [],hasError:false} + * - 如何获取附加信息参看 @see {BUI.Data.AbstractStore-event-beforeprocessload} + *

+     *  //返回数据为数组 [{},{}],会直接附加到加载的节点后面
+     *  
+     *  var node = store.loadNode('123');
+     *  store.loadNode(node);
+     *  
+     * 
+ * @cfg {Object} [dataProperty = 'nodes'] + */ + dataProperty : { + value : 'nodes' + }, + events : { + value : [ + /** + * 当添加数据时触发该事件 + * @event + *

+        *  store.on('add',function(ev){
+        *    list.addItem(e.node,index);
+        *  });
+        * 
+ * @param {jQuery.Event} e 事件对象 + * @param {Object} e.node 添加的节点 + * @param {Number} index 添加的位置 + */ + 'add', + /** + * 当更新数据指定字段时触发该事件 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.node 更新的节点 + */ + 'update', + /** + * 当删除数据时触发该事件 + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.node 删除的节点 + * @param {Number} index 删除节点的索引 + */ + 'remove', + /** + * 节点加载完毕触发该事件 + *

+        *   //异步加载节点,此时节点已经附加到加载节点的后面
+        *   store.on('load',function(ev){
+        *     var params = ev.params,
+        *       id = params.id,
+        *       node = store.findNode(id),
+        *       children = node.children;  //节点的id
+        *     //TO DO
+        *   });
+        * 
+ * + * @event + * @param {jQuery.Event} e 事件对象 + * @param {Object} e.node 加载的节点 + * @param {Object} e.params 加载节点时的参数 + */ + 'load' + ] + } + } + + BUI.extend(TreeStore,AbstractStore); + + BUI.augment(TreeStore,{ + /** + * @protected + * @override + * 初始化前 + */ + beforeInit:function(){ + this.initRoot(); + }, + //初始化数据,如果默认加载数据,则加载数据 + _initData : function(){ + var _self = this, + autoLoad = _self.get('autoLoad'), + pidField = _self.get('pidField'), + proxy = _self.get('proxy'), + root = _self.get('root'); + + //添加默认的匹配父元素的字段 + if(!proxy.get('url') && pidField){ + proxy.get('matchFields').push(pidField); + } + + if(autoLoad && !root.children){ + //params = root.id ? {id : root.id}: {}; + _self.loadNode(root); + } + }, + /** + * @protected + * 初始化根节点 + */ + initRoot : function(){ + var _self = this, + map = _self.get('map'), + root = _self.get('root'); + if(!root){ + root = {}; + } + if(!root.isNode){ + root = new Node(root,map); + //root.children= []; + } + root.path = [root.id]; + root.level = 0; + if(root.children){ + _self.setChildren(root,root.children); + } + _self.set('root',root); + }, + /** + * 添加节点,触发{@link BUI.Data.TreeStore#event-add} 事件 + *

+     *  //添加到根节点下
+     *  store.add({id : '1',text : '1'});
+     *  //添加到指定节点
+     *  var node = store.findNode('1'),
+     *    subNode = store.add({id : '11',text : '11'},node);
+     *  //插入到节点的指定位置
+     *  var node = store.findNode('1'),
+     *    subNode = store.add({id : '12',text : '12'},node,0);
+     * 
+ * @param {BUI.Data.Node|Object} node 节点或者数据对象 + * @param {BUI.Data.Node} [parent] 父节点,如果未指定则为根节点 + * @param {Number} [index] 添加节点的位置 + * @return {BUI.Data.Node} 添加完成的节点 + */ + add : function(node,parent,index){ + var _self = this; + + node = _self._add(node,parent,index); + _self.fire('add',{node : node,record : node,index : index}); + return node; + }, + // + _add : function(node,parent,index){ + parent = parent || this.get('root'); //如果未指定父元素,添加到跟节点 + var _self = this, + map = _self.get('map'), + nodes = parent.children, + nodeChildren; + + if(!node.isNode){ + node = new Node(node,map); + } + + nodeChildren = node.children || [] + + if(nodeChildren.length == 0 && node.leaf == null){ + node.leaf = true; + } + if(parent){ + parent.leaf = false; + } + + node.parent = parent; + node.level = parent.level + 1; + node.path = parent.path.concat(node.id); + index = index == null ? parent.children.length : index; + BUI.Array.addAt(nodes,node,index); + + _self.setChildren(node,nodeChildren); + return node; + }, + /** + * 移除节点,触发{@link BUI.Data.TreeStore#event-remove} 事件 + * + *

+     *  var node = store.findNode('1'); //根据节点id 获取节点
+     *  store.remove(node);
+     * 
+ * + * @param {BUI.Data.Node} node 节点或者数据对象 + * @return {BUI.Data.Node} 删除的节点 + */ + remove : function(node){ + var parent = node.parent || _self.get('root'), + index = BUI.Array.indexOf(node,parent.children) ; + + BUI.Array.remove(parent.children,node); + if(parent.children.length === 0){ + parent.leaf = true; + } + this.fire('remove',{node : node ,record : node , index : index}); + node.parent = null; + return node; + }, + /** + * 设置记录的值 ,触发 update 事件 + *

+    *  store.setValue(obj,'value','new value');
+    * 
+ * @param {Object} obj 修改的记录 + * @param {String} field 修改的字段名 + * @param {Object} value 修改的值 + */ + setValue : function(node,field,value){ + var + _self = this; + node[field] = value; + + _self.fire('update',{node:node,record : node,field:field,value:value}); + }, + /** + * 更新节点 + *

+     *  var node = store.findNode('1'); //根据节点id 获取节点
+     *  node.text = 'modify text'; //修改文本
+     *  store.update(node);        //此时会触发update事件,绑定了store的控件会更新对应的DOM
+     * 
+ * @return {BUI.Data.Node} 更新节点 + */ + update : function(node){ + this.fire('update',{node : node,record : node}); + }, + /** + * 返回缓存的数据,根节点的直接子节点集合 + *

+     *   //获取根节点的所有子节点
+     *   var data = store.getResult();
+     *   //获取根节点
+     *   var root = store.get('root');
+     * 
+ * @return {Array} 根节点下面的数据 + */ + getResult : function(){ + return this.get('root').children; + }, + /** + * 设置缓存的数据,设置为根节点的数据 + *

+    *     var data = [
+    *       {id : '1',text : '文本1'},
+    *       {id : '2',text : '文本2',children:[
+    *         {id : '21',text : '文本21'}
+    *       ]},
+    *       {id : '3',text : '文本3'}
+    *     ];
+    *     store.setResult(data); //会对数据进行格式化,添加leaf等字段:
+    *                            //[{id : '1',text : '文本1',leaf : true},{id : '2',text : '文本2',leaf : false,children:[...]}....]
+    *   
+ * @param {Array} data 缓存的数据 + */ + setResult : function(data){ + var _self = this, + proxy = _self.get('proxy'), + root = _self.get('root'); + if(proxy instanceof Proxy.Memery){ + _self.set('data',data); + _self.load({id : root.id}); + }else{ + _self.setChildren(root,data); + } + }, + /** + * 设置子节点 + * @protected + * @param {BUI.Data.Node} node 节点 + * @param {Array} children 子节点 + */ + setChildren : function(node,children){ + var _self = this; + node.children = []; + if(!children.length){ + return; + } + BUI.each(children,function(item){ + _self._add(item,node); + }); + }, + /** + * 查找节点 + *

+     *  var node = store.findNode('1');//从根节点开始查找节点
+     *  
+     *  var subNode = store.findNode('123',node); //从指定节点开始查找
+     * 
+ * @param {String} id 节点Id + * @param {BUI.Data.Node} [parent] 父节点 + * @param {Boolean} [deep = true] 是否递归查找 + * @return {BUI.Data.Node} 节点 + */ + findNode : function(id,parent,deep){ + return this.findNodeBy(function(node){ + return node.id === id; + },parent,deep); + }, + /** + * 根据匹配函数查找节点 + * @param {Function} fn 匹配函数 + * @param {BUI.Data.Node} [parent] 父节点 + * @param {Boolean} [deep = true] 是否递归查找 + * @return {BUI.Data.Node} 节点 + */ + findNodeBy : function(fn,parent,deep){ + var _self = this; + deep = deep == null ? true : deep; + if(!parent){ + var root = _self.get('root'); + if(fn(root)){ + return root; + } + return _self.findNodeBy(fn,root); + } + var children = parent.children, + rst = null; + BUI.each(children,function(item){ + if(fn(item)){ + rst = item; + }else if(deep){ + rst = _self.findNodeBy(fn,item); + } + if(rst){ + return false; + } + }); + return rst; + }, + /** + * 查找节点,根据匹配函数查找 + *

+     *  var nodes = store.findNodesBy(function(node){
+     *   if(node.status == '0'){
+     *     return true;
+     *   }
+     *   return false;
+     *  });
+     * 
+ * @param {Function} func 匹配函数 + * @param {BUI.Data.Node} [parent] 父元素,如果不存在,则从根节点查找 + * @return {Array} 节点数组 + */ + findNodesBy : function(func,parent){ + var _self = this, + root, + rst = []; + + if(!parent){ + parent = _self.get('root'); + } + + BUI.each(parent.children,function(item){ + if(func(item)){ + rst.push(item); + } + rst = rst.concat(_self.findNodesBy(func,item)); + }); + + return rst; + }, + /** + * 根据path查找节点 + * @return {BUI.Data.Node} 节点 + * @ignore + */ + findNodeByPath : function(path){ + if(!path){ + return null; + } + var _self = this, + root = _self.get('root'), + pathArr = path.split(','), + node, + i, + tempId = pathArr[0]; + if(!tempId){ + return null; + } + if(root.id == tempId){ + node = root; + }else{ + node = _self.findNode(tempId,root,false); + } + if(!node){ + return; + } + for(i = 1 ; i < pathArr.length ; i = i + 1){ + var tempId = pathArr[i]; + node = _self.findNode(tempId,node,false); + if(!node){ + break; + } + } + return node; + }, + /** + * 是否包含指定节点,如果未指定父节点,从根节点开始搜索 + *

+     *  store.contains(node); //是否存在节点
+     *
+     *  store.contains(subNode,node); //节点是否存在指定子节点
+     * 
+ * @param {BUI.Data.Node} node 节点 + * @param {BUI.Data.Node} parent 父节点 + * @return {Boolean} 是否包含指定节点 + */ + contains : function(node,parent){ + var _self = this, + findNode = _self.findNode(node.id,parent); + return !!findNode; + }, + /** + * 加载完数据 + * @protected + * @override + */ + afterProcessLoad : function(data,params){ + var _self = this, + pidField = _self.get('pidField'), + id = params.id || params[pidField], + dataProperty = _self.get('dataProperty'), + node = _self.findNode(id) || _self.get('root');//如果找不到父元素,则放置在跟节点 + + if(BUI.isArray(data)){ + _self.setChildren(node,data); + }else{ + _self.setChildren(node,data[dataProperty]); + } + node.loaded = true; //标识已经加载过 + _self.fire('load',{node : node,params : params}); + }, + /** + * 是否包含数据 + * @return {Boolean} + */ + hasData : function(){ + //return true; + return this.get('root').children && this.get('root').children.length !== 0; + }, + /** + * 是否已经加载过,叶子节点或者存在字节点的节点 + * @param {BUI.Data.Node} node 节点 + * @return {Boolean} 是否加载过 + */ + isLoaded : function(node){ + var root = this.get('root'); + if(node == root && !root.children){ + return false; + } + if(!this.get('url') && !this.get('pidField')){ //如果不从远程加载数据,默认已经加载 + return true; + } + + return node.loaded || node.leaf || !!(node.children && node.children.length); + }, + /** + * 加载节点的子节点 + * @param {BUI.Data.Node} node 节点 + * @param {Boolean} forceLoad 是否强迫重新加载节点,如果设置成true,不判断是否加载过 + */ + loadNode : function(node,forceLoad){ + var _self = this, + pidField = _self.get('pidField'), + params; + //如果已经加载过,或者节点是叶子节点 + if(!forceLoad && _self.isLoaded(node)){ + return ; + } + params = {id : node.id}; + if(pidField){ + params[pidField] = node.id; + } + _self.load(params); + }, + /** + * 重新加载节点 + * @param {BUI.Data.Node} node node节点 + */ + reloadNode : function(node){ + var _self = this; + node = node || _self.get('root'); + node.loaded = false; + //node.children = []; + _self.loadNode(node,true); + }, + /** + * 加载节点,根据path + * @param {String} path 加载路径 + * @ignore + */ + loadPath : function(path){ + var _self = this, + arr = path.split(','), + id = arr[0]; + if(_self.findNodeByPath(path)){ //加载过 + return; + } + _self.load({id : id,path : path}); + } + }); + +module.exports = TreeStore; diff --git a/tests/data-spec.js b/tests/data-spec.js new file mode 100644 index 0000000..223a3a5 --- /dev/null +++ b/tests/data-spec.js @@ -0,0 +1,10 @@ +var expect = require('expect.js'); +var data = require('../index'); + +describe('data', function() { + + it('normal usage', function() { + + }); + +});