396 lines
12 KiB
JavaScript
396 lines
12 KiB
JavaScript
//>>built
|
|
define("dojox/grid/enhanced/plugins/_StoreLayer", [
|
|
"dojo/_base/declare",
|
|
"dojo/_base/array",
|
|
"dojo/_base/lang",
|
|
"dojo/_base/xhr"
|
|
], function(declare, array, lang, xhr){
|
|
// summary:
|
|
// The dojo.data.api.Read API is powerful, but it's difficult to give the store some special commands before
|
|
// fetch, so that the store content can be temporarily modified or transformed, and acts as another store. The
|
|
// parameter *query* or *queryOptions* in keywordArgs for *fetch* is not enough because:
|
|
// 1. users do not have the opportunity to response to the store actions when these options or queries are applied,
|
|
// especially when the real store is at server side.
|
|
// 2. the store implementation must be changed to support any new options in 'query' or 'queryOptions', so it'll be
|
|
// difficult if this implementation is not able to or very hard to be changed, or some new options are required to
|
|
// be valid for all stores.
|
|
// This *StoreLayer* framework is dedicated to provide a uniform way for configuring an existing store, so that
|
|
// it can be easily extended to have special behaviors or act like a totally different store.
|
|
// The major approach is to wrap the *fetch* function of store, layer by layer. Every layer treats the incoming
|
|
// store.fetch as a 'black box', thus maintaining the independence between layers.
|
|
// *fetch* is the most important data retriever in the Read API, almost all other functions are used for a single
|
|
// item, and require that this item is already retrieved (by and only by *fetch*). So once we've controlled this
|
|
// *fetch* function, we've controlled almost the whole store. This fact simplifies our implementation of StoreLayer.
|
|
// example:
|
|
// //ns is for namespace, i.e.:dojox.grid.enhanced.plugins
|
|
// ns.wrap(ns.wrap(ns.wrap(store, new ns.FilterLayer()), new ns.UniqueLayer()), new ns.TransformLayer());
|
|
//
|
|
// //every layer has a name, it should be given in the document of this layer.
|
|
// //if you don't know it's name, you can get it by: ns.SomeLayer.prototype.name();
|
|
// store.layer("filter").filterDef(...);
|
|
// store.layer("unique").setUniqueColumns(...);
|
|
// store.layer("transform").setScheme(...);
|
|
//
|
|
// //now use the store as usual...
|
|
//
|
|
// store.unwrap("transform"); //remove the transform layer but retain the other two.
|
|
//
|
|
// //now use the store as usual...
|
|
//
|
|
// store.unwrap(); //remove all the layers, get the original store back.
|
|
|
|
var ns = lang.getObject("grid.enhanced.plugins", true, dojox);
|
|
|
|
var getPrevTags = function(tags){
|
|
var tagList = ["reorder", "sizeChange", "normal", "presentation"];
|
|
var idx = tagList.length;
|
|
for(var i = tags.length - 1; i >= 0; --i){
|
|
var p = array.indexOf(tagList, tags[i]);
|
|
if(p >= 0 && p <= idx){
|
|
idx = p;
|
|
}
|
|
}
|
|
if(idx < tagList.length - 1){
|
|
return tagList.slice(0, idx + 1);
|
|
}else{
|
|
return tagList;
|
|
}
|
|
},
|
|
|
|
unwrap = function(/* string? */layerName){
|
|
// summary:
|
|
// Unwrap the layers of the store
|
|
// tags:
|
|
// public
|
|
// returns:
|
|
// The unwrapped store, for nested use only.
|
|
var i, layers = this._layers, len = layers.length;
|
|
if(layerName){
|
|
for(i = len-1; i >= 0; --i){
|
|
if(layers[i].name() == layerName){
|
|
layers[i]._unwrap(layers[i + 1]);
|
|
break;
|
|
}
|
|
}
|
|
layers.splice(i, 1);
|
|
}else{
|
|
for(i = len - 1; i >= 0; --i){
|
|
layers[i]._unwrap();
|
|
}
|
|
}
|
|
if(!layers.length){
|
|
delete this._layers;
|
|
delete this.layer;
|
|
delete this.unwrap;
|
|
delete this.forEachLayer;
|
|
}
|
|
//console.log("layers:",this._layers);
|
|
return this; //Read-store
|
|
},
|
|
|
|
getLayer = function(layerName){
|
|
// summary:
|
|
// Get a layer of the store, so we can configure that layer.
|
|
// tags:
|
|
// public (scope is store)
|
|
// layerName: string
|
|
// the name of the layer
|
|
// returns:
|
|
// the store layer object
|
|
var i, layers = this._layers;
|
|
if(typeof layerName == "undefined"){
|
|
return layers.length; //Integer
|
|
}
|
|
if(typeof layerName == "number"){
|
|
return layers[layerName]; //_StoreLayer
|
|
}
|
|
for(i = layers.length - 1; i >= 0; --i){
|
|
if(layers[i].name() == layerName){
|
|
return layers[i]; //_StoreLayer
|
|
}
|
|
}
|
|
return null; //_StoreLayer
|
|
},
|
|
|
|
forEachLayer = function(callback, isInnerToOuter){
|
|
// summary:
|
|
// Visit the layers one by one. From the outer most to inner most by default.
|
|
// callback: Function
|
|
// The function to callback.
|
|
// If return false, break the loop.
|
|
// isInnerToOuter: Boolean
|
|
// Whether visit from the inner most layer to the outer most layer.
|
|
var len = this._layers.length, start, end, dir;
|
|
if(isInnerToOuter){
|
|
start = 0;
|
|
end = len;
|
|
dir = 1;
|
|
}else{
|
|
start = len - 1;
|
|
end = -1;
|
|
dir = -1;
|
|
}
|
|
for(var i = start; i != end; i += dir){
|
|
if(callback(this._layers[i], i) === false){
|
|
return i;
|
|
}
|
|
}
|
|
return end;
|
|
};
|
|
ns.wrap = function(store, funcName, layer, layerFuncName){
|
|
// summary:
|
|
// Wrap the store with the given layer.
|
|
// tags:
|
|
// public
|
|
// store: Read-store
|
|
// The store to be wrapped.
|
|
// layer: _StoreLayer
|
|
// The layer to be used
|
|
// returns
|
|
// The wrapped store, for nested use only.
|
|
if(!store._layers){
|
|
store._layers = [];
|
|
store.layer = lang.hitch(store, getLayer);
|
|
store.unwrap = lang.hitch(store, unwrap);
|
|
store.forEachLayer = lang.hitch(store, forEachLayer);
|
|
}
|
|
var prevTags = getPrevTags(layer.tags);
|
|
if(!array.some(store._layers, function(lyr, i){
|
|
if(array.some(lyr.tags, function(tag){
|
|
return array.indexOf(prevTags, tag) >= 0;
|
|
})){
|
|
return false;
|
|
}else{
|
|
store._layers.splice(i, 0, layer);
|
|
layer._wrap(store, funcName, layerFuncName, lyr);
|
|
return true;
|
|
}
|
|
})){
|
|
store._layers.push(layer);
|
|
layer._wrap(store, funcName, layerFuncName);
|
|
}
|
|
//console.log("wrapped layers:", dojo.map(store._layers, function(lyr){return lyr.name();}));
|
|
return store; //Read-store
|
|
};
|
|
|
|
var _StoreLayer = declare("dojox.grid.enhanced.plugins._StoreLayer", null, {
|
|
// summary:
|
|
// The most abstract class of store layers, provides basic utilities and some interfaces.
|
|
// tags:
|
|
// abstract
|
|
/*=====
|
|
// _store: [protected] Read-store
|
|
// The wrapped store.
|
|
_store: null,
|
|
|
|
// _originFetch: [protected] function
|
|
// The original fetch function of the store.
|
|
_originFetch: null,
|
|
|
|
// __enabled: [private] Boolean
|
|
// To control whether this layer is valid.
|
|
__enabled: true,
|
|
=====*/
|
|
tags: ["normal"],
|
|
|
|
layerFuncName: "_fetch",
|
|
|
|
constructor: function(){
|
|
this._store = null;
|
|
this._originFetch = null;
|
|
this.__enabled = true;
|
|
},
|
|
initialize: function(store){
|
|
// summary:
|
|
//
|
|
},
|
|
uninitialize: function(store){
|
|
// summary:
|
|
//
|
|
},
|
|
invalidate: function(){
|
|
|
|
},
|
|
_wrap: function(store, funcName, layerFuncName, nextLayer){
|
|
// summary:
|
|
// Do the actual wrapping (or 'hacking' if you like) to the store.
|
|
// tags:
|
|
// internal
|
|
// store: Read-store
|
|
// The store to be wrapped.
|
|
this._store = store;
|
|
this._funcName = funcName;
|
|
var fetchFunc = lang.hitch(this, function(){
|
|
return (this.enabled() ? this[layerFuncName || this.layerFuncName] : this.originFetch).apply(this, arguments);
|
|
});
|
|
if(nextLayer){
|
|
this._originFetch = nextLayer._originFetch;
|
|
nextLayer._originFetch = fetchFunc;
|
|
}else{
|
|
this._originFetch = store[funcName] || function(){};
|
|
store[funcName] = fetchFunc;
|
|
}
|
|
this.initialize(store);
|
|
},
|
|
_unwrap: function(nextLayer){
|
|
// summary:
|
|
// Do the actual unwrapping to the store.
|
|
// tags:
|
|
// internal
|
|
// store: Read-store
|
|
// The store to be unwrapped.
|
|
this.uninitialize(this._store);
|
|
if(nextLayer){
|
|
nextLayer._originFetch = this._originFetch;
|
|
}else{
|
|
this._store[this._funcName] = this._originFetch;
|
|
}
|
|
this._originFetch = null;
|
|
this._store = null;
|
|
},
|
|
enabled: function(/* bool? */toEnable){
|
|
// summary:
|
|
// The get/set function of the enabled status of this layer
|
|
// tags:
|
|
// public
|
|
// toEnable: Boolean?
|
|
// If given, is a setter, otherwise, it's getter.
|
|
if(typeof toEnable != "undefined"){
|
|
this.__enabled = !!toEnable;
|
|
}
|
|
return this.__enabled; //Boolean
|
|
},
|
|
name: function(){
|
|
// summary:
|
|
// Get the name of this store layer.
|
|
// The default name retrieved from class name, which should have a pattern of "{name}Layer".
|
|
// If this pattern does not exist, the whole class name will be this layer's name.
|
|
// It's better to override this method if your class name is too complicated.
|
|
// tags:
|
|
// public extension
|
|
// returns:
|
|
// The name of this layer.
|
|
if(!this.__name){
|
|
var m = this.declaredClass.match(/(?:\.(?:_*)([^\.]+)Layer$)|(?:\.([^\.]+)$)/i);
|
|
this.__name = m ? (m[1] || m[2]).toLowerCase() : this.declaredClass;
|
|
}
|
|
return this.__name;
|
|
},
|
|
originFetch: function(){
|
|
return (lang.hitch(this._store, this._originFetch)).apply(this, arguments);
|
|
}
|
|
});
|
|
var _ServerSideLayer = declare("dojox.grid.enhanced.plugins._ServerSideLayer", _StoreLayer, {
|
|
// summary:
|
|
// The most abstract class for all server side store layers.
|
|
// tags:
|
|
// abstract
|
|
/*=====
|
|
// _url: [protected] string
|
|
// The url of the server
|
|
_url: "",
|
|
// __cmds [private] object
|
|
// The command object to be sent to server.
|
|
__cmds: {},
|
|
=====*/
|
|
constructor: function(args){
|
|
args = args || {};
|
|
this._url = args.url || "";
|
|
this._isStateful = !!args.isStateful;
|
|
this._onUserCommandLoad = args.onCommandLoad || function(){};
|
|
this.__cmds = {cmdlayer:this.name(), enable:true};
|
|
|
|
//Only for stateful server, sending commands before fetch makes sense.
|
|
this.useCommands(this._isStateful);
|
|
},
|
|
enabled: function(/* bool? */toEnable){
|
|
// summary:
|
|
// Overrided from _StoreLayer.enabled
|
|
var res = this.inherited(arguments);
|
|
this.__cmds.enable = this.__enabled;
|
|
return res;
|
|
},
|
|
useCommands: function(/* bool? */toUse){
|
|
// summary:
|
|
// If you only want to modify the user request, instead of sending a separate command
|
|
// to server before fetch, just call:
|
|
// this.useCommand(false);
|
|
// tags:
|
|
// public
|
|
// toUse: Boolean?
|
|
// If provided, it's a setter, otherwise, it's a getter
|
|
if(typeof toUse != "undefined"){
|
|
this.__cmds.cmdlayer = (toUse && this._isStateful) ? this.name() : null;
|
|
}
|
|
return !!(this.__cmds.cmdlayer); //Boolean
|
|
},
|
|
_fetch: function(/* keywordArgs */userRequest){
|
|
// summary:
|
|
// Implementation of _StoreLayer._fetch
|
|
if(this.__cmds.cmdlayer){
|
|
//We're gonna send command to server before fetch.
|
|
xhr.post({
|
|
url: this._url || this._store.url,
|
|
content: this.__cmds,
|
|
load: lang.hitch(this, function(responce){
|
|
this.onCommandLoad(responce, userRequest);
|
|
this.originFetch(userRequest);
|
|
}),
|
|
error: lang.hitch(this, this.onCommandError)
|
|
});
|
|
}else{
|
|
//The user only wants to modify the request object.
|
|
this.onCommandLoad("", userRequest);
|
|
this.originFetch(userRequest);
|
|
}
|
|
return userRequest; //dojo.data.api.Request
|
|
},
|
|
command: function(/* string */cmdName,/* (string|number|bool|...)? */cmdContent){
|
|
// summary:
|
|
// get/set a command (a name-value pair)
|
|
// tags:
|
|
// public
|
|
// cmdName: string
|
|
// The name of the command
|
|
// cmdContent: anything
|
|
// The content of the command
|
|
// returns:
|
|
// The content of the command if cmdContent is undefined
|
|
var cmds = this.__cmds;
|
|
if(cmdContent === null){
|
|
delete cmds[cmdName];
|
|
}else if(typeof cmdContent !== "undefined"){
|
|
cmds[cmdName] = cmdContent;
|
|
}
|
|
return cmds[cmdName]; //anything
|
|
},
|
|
onCommandLoad: function(/* string */response, /* keywordArgs */userRequest){
|
|
// summary:
|
|
// When the server gives back *response* for the commands, you can do something here.
|
|
// tags:
|
|
// callback extension
|
|
// response: string
|
|
// server response
|
|
// userRequest: [in|out] dojo.data.api.Request
|
|
// The request object for *fetch*. You can modify this object according to the *response*
|
|
// so as to change the behavior of *fetch*
|
|
this._onUserCommandLoad(this.__cmds, userRequest, response);
|
|
},
|
|
onCommandError: function(error){
|
|
// summary:
|
|
// handle errors when sending commands.
|
|
// tags:
|
|
// callback extension
|
|
// error: Error
|
|
console.log(error);
|
|
throw error;
|
|
}
|
|
});
|
|
|
|
return {
|
|
_StoreLayer: _StoreLayer,
|
|
_ServerSideLayer: _ServerSideLayer,
|
|
wrap: ns.wrap
|
|
};
|
|
});
|