_StoreLayer.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.grid.enhanced.plugins._StoreLayer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.grid.enhanced.plugins._StoreLayer"] = true;
  8. dojo.provide("dojox.grid.enhanced.plugins._StoreLayer");
  9. // summary:
  10. // The dojo.data.api.Read API is powerful, but it's difficult to give the store some special commands before
  11. // fetch, so that the store content can be temporarily modified or transformed, and acts as another store. The
  12. // parameter *query* or *queryOptions* in keywordArgs for *fetch* is not enough because:
  13. // 1. users do not have the opportunity to response to the store actions when these options or queries are applied,
  14. // especially when the real store is at server side.
  15. // 2. the store implementation must be changed to support any new options in 'query' or 'queryOptions', so it'll be
  16. // difficult if this implementation is not able to or very hard to be changed, or some new options are required to
  17. // be valid for all stores.
  18. // This *StoreLayer* framework is dedicated to provide a uniform way for configuring an existing store, so that
  19. // it can be easily extended to have special behaviors or act like a totally different store.
  20. // The major approach is to wrap the *fetch* function of store, layer by layer. Every layer treats the incoming
  21. // store.fetch as a 'black box', thus maintaining the independence between layers.
  22. // *fetch* is the most important data retriever in the Read API, almost all other functions are used for a single
  23. // item, and require that this item is already retrieved (by and only by *fetch*). So once we've controlled this
  24. // *fetch* function, we've controlled almost the whole store. This fact simplifies our implementation of StoreLayer.
  25. // example:
  26. // //ns is for namespace, i.e.:dojox.grid.enhanced.plugins
  27. // ns.wrap(ns.wrap(ns.wrap(store, new ns.FilterLayer()), new ns.UniqueLayer()), new ns.TransformLayer());
  28. //
  29. // //every layer has a name, it should be given in the document of this layer.
  30. // //if you don't know it's name, you can get it by: ns.SomeLayer.prototype.name();
  31. // store.layer("filter").filterDef(...);
  32. // store.layer("unique").setUniqueColumns(...);
  33. // store.layer("transform").setScheme(...);
  34. //
  35. // //now use the store as usual...
  36. //
  37. // store.unwrap("transform"); //remove the transform layer but retain the other two.
  38. //
  39. // //now use the store as usual...
  40. //
  41. // store.unwrap(); //remove all the layers, get the original store back.
  42. (function(){
  43. var ns = dojox.grid.enhanced.plugins,
  44. getPrevTags = function(tags){
  45. var tagList = ["reorder", "sizeChange", "normal", "presentation"];
  46. var idx = tagList.length;
  47. for(var i = tags.length - 1; i >= 0; --i){
  48. var p = dojo.indexOf(tagList, tags[i]);
  49. if(p >= 0 && p <= idx){
  50. idx = p;
  51. }
  52. }
  53. if(idx < tagList.length - 1){
  54. return tagList.slice(0, idx + 1);
  55. }else{
  56. return tagList;
  57. }
  58. },
  59. unwrap = function(/* string? */layerName){
  60. // summary:
  61. // Unwrap the layers of the store
  62. // tags:
  63. // public
  64. // returns:
  65. // The unwrapped store, for nested use only.
  66. var i, layers = this._layers, len = layers.length;
  67. if(layerName){
  68. for(i = len-1; i >= 0; --i){
  69. if(layers[i].name() == layerName){
  70. layers[i]._unwrap(layers[i + 1]);
  71. break;
  72. }
  73. }
  74. layers.splice(i, 1);
  75. }else{
  76. for(i = len - 1; i >= 0; --i){
  77. layers[i]._unwrap();
  78. }
  79. }
  80. if(!layers.length){
  81. delete this._layers;
  82. delete this.layer;
  83. delete this.unwrap;
  84. delete this.forEachLayer;
  85. }
  86. //console.log("layers:",this._layers);
  87. return this; //Read-store
  88. },
  89. getLayer = function(layerName){
  90. // summary:
  91. // Get a layer of the store, so we can configure that layer.
  92. // tags:
  93. // public (scope is store)
  94. // layerName: string
  95. // the name of the layer
  96. // returns:
  97. // the store layer object
  98. var i, layers = this._layers;
  99. if(typeof layerName == "undefined"){
  100. return layers.length; //Integer
  101. }
  102. if(typeof layerName == "number"){
  103. return layers[layerName]; //_StoreLayer
  104. }
  105. for(i = layers.length - 1; i >= 0; --i){
  106. if(layers[i].name() == layerName){
  107. return layers[i]; //_StoreLayer
  108. }
  109. }
  110. return null; //_StoreLayer
  111. },
  112. forEachLayer = function(callback, isInnerToOuter){
  113. // summary:
  114. // Visit the layers one by one. From the outer most to inner most by default.
  115. // callback: Function
  116. // The function to callback.
  117. // If return false, break the loop.
  118. // isInnerToOuter: Boolean
  119. // Whether visit from the inner most layer to the outer most layer.
  120. var len = this._layers.length, start, end, dir;
  121. if(isInnerToOuter){
  122. start = 0;
  123. end = len;
  124. dir = 1;
  125. }else{
  126. start = len - 1;
  127. end = -1;
  128. dir = -1;
  129. }
  130. for(var i = start; i != end; i += dir){
  131. if(callback(this._layers[i], i) === false){
  132. return i;
  133. }
  134. }
  135. return end;
  136. };
  137. ns.wrap = function(store, funcName, layer, layerFuncName){
  138. // summary:
  139. // Wrap the store with the given layer.
  140. // tags:
  141. // public
  142. // store: Read-store
  143. // The store to be wrapped.
  144. // layer: _StoreLayer
  145. // The layer to be used
  146. // returns
  147. // The wrapped store, for nested use only.
  148. if(!store._layers){
  149. store._layers = [];
  150. store.layer = dojo.hitch(store, getLayer);
  151. store.unwrap = dojo.hitch(store, unwrap);
  152. store.forEachLayer = dojo.hitch(store, forEachLayer);
  153. }
  154. var prevTags = getPrevTags(layer.tags);
  155. if(!dojo.some(store._layers, function(lyr, i){
  156. if(dojo.some(lyr.tags, function(tag){
  157. return dojo.indexOf(prevTags, tag) >= 0;
  158. })){
  159. return false;
  160. }else{
  161. store._layers.splice(i, 0, layer);
  162. layer._wrap(store, funcName, layerFuncName, lyr);
  163. return true;
  164. }
  165. })){
  166. store._layers.push(layer);
  167. layer._wrap(store, funcName, layerFuncName);
  168. }
  169. //console.log("wrapped layers:", dojo.map(store._layers, function(lyr){return lyr.name();}));
  170. return store; //Read-store
  171. };
  172. dojo.declare("dojox.grid.enhanced.plugins._StoreLayer", null, {
  173. // summary:
  174. // The most abstract class of store layers, provides basic utilities and some interfaces.
  175. // tags:
  176. // abstract
  177. /*=====
  178. // _store: [protected] Read-store
  179. // The wrapped store.
  180. _store: null,
  181. // _originFetch: [protected] function
  182. // The original fetch function of the store.
  183. _originFetch: null,
  184. // __enabled: [private] Boolean
  185. // To control whether this layer is valid.
  186. __enabled: true,
  187. =====*/
  188. tags: ["normal"],
  189. layerFuncName: "_fetch",
  190. constructor: function(){
  191. this._store = null;
  192. this._originFetch = null;
  193. this.__enabled = true;
  194. },
  195. initialize: function(store){
  196. // summary:
  197. //
  198. },
  199. uninitialize: function(store){
  200. // summary:
  201. //
  202. },
  203. invalidate: function(){
  204. },
  205. _wrap: function(store, funcName, layerFuncName, nextLayer){
  206. // summary:
  207. // Do the actual wrapping (or 'hacking' if you like) to the store.
  208. // tags:
  209. // internal
  210. // store: Read-store
  211. // The store to be wrapped.
  212. this._store = store;
  213. this._funcName = funcName;
  214. var fetchFunc = dojo.hitch(this, function(){
  215. return (this.enabled() ? this[layerFuncName || this.layerFuncName] : this.originFetch).apply(this, arguments);
  216. });
  217. if(nextLayer){
  218. this._originFetch = nextLayer._originFetch;
  219. nextLayer._originFetch = fetchFunc;
  220. }else{
  221. this._originFetch = store[funcName] || function(){};
  222. store[funcName] = fetchFunc;
  223. }
  224. this.initialize(store);
  225. },
  226. _unwrap: function(nextLayer){
  227. // summary:
  228. // Do the actual unwrapping to the store.
  229. // tags:
  230. // internal
  231. // store: Read-store
  232. // The store to be unwrapped.
  233. this.uninitialize(this._store);
  234. if(nextLayer){
  235. nextLayer._originFetch = this._originFetch;
  236. }else{
  237. this._store[this._funcName] = this._originFetch;
  238. }
  239. this._originFetch = null;
  240. this._store = null;
  241. },
  242. enabled: function(/* bool? */toEnable){
  243. // summary:
  244. // The get/set function of the enabled status of this layer
  245. // tags:
  246. // public
  247. // toEnable: Boolean?
  248. // If given, is a setter, otherwise, it's getter.
  249. if(typeof toEnable != "undefined"){
  250. this.__enabled = !!toEnable;
  251. }
  252. return this.__enabled; //Boolean
  253. },
  254. name: function(){
  255. // summary:
  256. // Get the name of this store layer.
  257. // The default name retrieved from class name, which should have a pattern of "{name}Layer".
  258. // If this pattern does not exist, the whole class name will be this layer's name.
  259. // It's better to override this method if your class name is too complicated.
  260. // tags:
  261. // public extension
  262. // returns:
  263. // The name of this layer.
  264. if(!this.__name){
  265. var m = this.declaredClass.match(/(?:\.(?:_*)([^\.]+)Layer$)|(?:\.([^\.]+)$)/i);
  266. this.__name = m ? (m[1] || m[2]).toLowerCase() : this.declaredClass;
  267. }
  268. return this.__name;
  269. },
  270. originFetch: function(){
  271. return (dojo.hitch(this._store, this._originFetch)).apply(this, arguments);
  272. }
  273. });
  274. dojo.declare("dojox.grid.enhanced.plugins._ServerSideLayer", ns._StoreLayer, {
  275. // summary:
  276. // The most abstract class for all server side store layers.
  277. // tags:
  278. // abstract
  279. /*=====
  280. // _url: [protected] string
  281. // The url of the server
  282. _url: "",
  283. // __cmds [private] object
  284. // The command object to be sent to server.
  285. __cmds: {},
  286. =====*/
  287. constructor: function(args){
  288. args = args || {};
  289. this._url = args.url || "";
  290. this._isStateful = !!args.isStateful;
  291. this._onUserCommandLoad = args.onCommandLoad || function(){};
  292. this.__cmds = {cmdlayer:this.name(), enable:true};
  293. //Only for stateful server, sending commands before fetch makes sense.
  294. this.useCommands(this._isStateful);
  295. },
  296. enabled: function(/* bool? */toEnable){
  297. // summary:
  298. // Overrided from _StoreLayer.enabled
  299. var res = this.inherited(arguments);
  300. this.__cmds.enable = this.__enabled;
  301. return res;
  302. },
  303. useCommands: function(/* bool? */toUse){
  304. // summary:
  305. // If you only want to modify the user request, instead of sending a separate command
  306. // to server before fetch, just call:
  307. // this.useCommand(false);
  308. // tags:
  309. // public
  310. // toUse: Boolean?
  311. // If provided, it's a setter, otherwise, it's a getter
  312. if(typeof toUse != "undefined"){
  313. this.__cmds.cmdlayer = (toUse && this._isStateful) ? this.name() : null;
  314. }
  315. return !!(this.__cmds.cmdlayer); //Boolean
  316. },
  317. _fetch: function(/* keywordArgs */userRequest){
  318. // summary:
  319. // Implementation of _StoreLayer._fetch
  320. if(this.__cmds.cmdlayer){
  321. //We're gonna send command to server before fetch.
  322. dojo.xhrPost({
  323. url: this._url || this._store.url,
  324. content: this.__cmds,
  325. load: dojo.hitch(this, function(responce){
  326. this.onCommandLoad(responce, userRequest);
  327. this.originFetch(userRequest);
  328. }),
  329. error: dojo.hitch(this, this.onCommandError)
  330. });
  331. }else{
  332. //The user only wants to modify the request object.
  333. this.onCommandLoad("", userRequest);
  334. this.originFetch(userRequest);
  335. }
  336. return userRequest; //dojo.data.api.Request
  337. },
  338. command: function(/* string */cmdName,/* (string|number|bool|...)? */cmdContent){
  339. // summary:
  340. // get/set a command (a name-value pair)
  341. // tags:
  342. // public
  343. // cmdName: string
  344. // The name of the command
  345. // cmdContent: anything
  346. // The content of the command
  347. // returns:
  348. // The content of the command if cmdContent is undefined
  349. var cmds = this.__cmds;
  350. if(cmdContent === null){
  351. delete cmds[cmdName];
  352. }else if(typeof cmdContent !== "undefined"){
  353. cmds[cmdName] = cmdContent;
  354. }
  355. return cmds[cmdName]; //anything
  356. },
  357. onCommandLoad: function(/* string */response, /* keywordArgs */userRequest){
  358. // summary:
  359. // When the server gives back *response* for the commands, you can do something here.
  360. // tags:
  361. // callback extension
  362. // response: string
  363. // server response
  364. // userRequest: [in|out] dojo.data.api.Request
  365. // The request object for *fetch*. You can modify this object according to the *response*
  366. // so as to change the behavior of *fetch*
  367. this._onUserCommandLoad(this.__cmds, userRequest, response);
  368. },
  369. onCommandError: function(error){
  370. // summary:
  371. // handle errors when sending commands.
  372. // tags:
  373. // callback extension
  374. // error: Error
  375. console.log(error);
  376. throw error;
  377. }
  378. });
  379. })();
  380. }