123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394 |
- /*
- Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
- Available via Academic Free License >= 2.1 OR the modified BSD license.
- see: http://dojotoolkit.org/license for details
- */
- if(!dojo._hasResource["dojox.grid.enhanced.plugins._StoreLayer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
- dojo._hasResource["dojox.grid.enhanced.plugins._StoreLayer"] = true;
- dojo.provide("dojox.grid.enhanced.plugins._StoreLayer");
- // 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.
- (function(){
- var ns = dojox.grid.enhanced.plugins,
-
- getPrevTags = function(tags){
- var tagList = ["reorder", "sizeChange", "normal", "presentation"];
- var idx = tagList.length;
- for(var i = tags.length - 1; i >= 0; --i){
- var p = dojo.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 = dojo.hitch(store, getLayer);
- store.unwrap = dojo.hitch(store, unwrap);
- store.forEachLayer = dojo.hitch(store, forEachLayer);
- }
- var prevTags = getPrevTags(layer.tags);
- if(!dojo.some(store._layers, function(lyr, i){
- if(dojo.some(lyr.tags, function(tag){
- return dojo.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
- };
- dojo.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 = dojo.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 (dojo.hitch(this._store, this._originFetch)).apply(this, arguments);
- }
- });
- dojo.declare("dojox.grid.enhanced.plugins._ServerSideLayer", ns._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.
- dojo.xhrPost({
- url: this._url || this._store.url,
- content: this.__cmds,
- load: dojo.hitch(this, function(responce){
- this.onCommandLoad(responce, userRequest);
- this.originFetch(userRequest);
- }),
- error: dojo.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;
- }
- });
- })();
- }
|