_StoreLayer.js 12 KB

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