source: Dev/branches/rest-dojo-ui/client/dojox/data/CdfStore.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

  • Property svn:executable set to *
File size: 16.4 KB
Line 
1define(["dojo", "dojox", "dojo/data/util/sorter"], function(dojo, dojox) {
2
3dojox.data.ASYNC_MODE = 0;
4dojox.data.SYNC_MODE = 1;
5
6dojo.declare("dojox.data.CdfStore", null, {
7        //      summary:
8        //              IMPORTANT: The CDF Store is designed to work with Tibco GI, and references Tibco's
9        //              JSX3 JavaScript library and will not work without it.
10        //
11        //              The CDF Store implements dojo.data.Read, Write, and Identity api's.  It is a local
12        //              (in memory) store that handles XML documents formatted according to the
13        //              Common Data Format (CDF) spec:
14        //              http://www.tibco.com/devnet/resources/gi/3_1/tips_and_techniques/CommonDataFormatCDF.pdf
15        //
16        //              The purpose of this store is to provide a glue between a jsx3 CDF file and a Dijit.
17        //
18        //              While a CDF document is an XML file, other than the initial input, all data returned
19        //              from and written to this store should be in object format.
20        //
21        // identity: [const] String
22        //              The unique identifier for each item. Defaults to "jsxid" which is standard for a CDF
23        //              document. Should not be changed.
24        identity: "jsxid",
25        //
26        //      url : String
27        //              The location from which to fetch the XML (CDF) document.
28        url: "",
29        //
30        //      xmlStr: String
31        //              A string that can be parsed into an XML document and should be formatted according
32        //              to the CDF spec.
33        //      example:
34        //              |       '<data jsxid="jsxroot"><record jsxtext="A"/><record jsxtext="B" jsxid="2" jsxid="2"/></data>'
35        xmlStr:"",
36        //
37        //      data:   Object
38        //              A object that will be converted into the xmlStr property, and then parsed into a CDF.
39        data:null,
40        //
41        //      label:  String
42        //              The property within each item used to define the item.
43        label: "",
44        //
45        //      mode [const]: dojox.data.ASYNC_MODE | dojox.data.SYNC_MODE
46        //              This store supports syncronous fetches if this property is set to dojox.data.SYNC_MODE.
47        mode:dojox.data.ASYNC_MODE,
48       
49        constructor: function(/* Object */ args){
50                // summary:
51                //      Constructor for the CDF store. Instantiate a new CdfStore.
52                //
53                if(args){
54                        this.url = args.url;
55                        this.xmlStr = args.xmlStr || args.str;
56                        if(args.data){
57                                this.xmlStr = this._makeXmlString(args.data);
58                        }
59                        this.identity = args.identity || this.identity;
60                        this.label = args.label || this.label;
61                        this.mode = args.mode !== undefined ? args.mode : this.mode;
62                }
63                this._modifiedItems = {};
64               
65                this.byId = this.fetchItemByIdentity;
66        },
67       
68        /* dojo.data.api.Read */
69
70        getValue: function(/* jsx3.xml.Entity */ item, /* String */ property, /* value? */ defaultValue){
71                //      summary:
72                //              Return an property value of an item
73                //
74                return item.getAttribute(property) || defaultValue; // anything
75        },
76
77        getValues: function(/* jsx3.xml.Entity */ item, /* String */ property){
78                //      summary:
79                //              Return an array of values
80                //
81                //      TODO!!! Can't find an example of an array in any CDF files
82                //
83                var v = this.getValue(item, property, []);
84                return dojo.isArray(v) ? v : [v];
85        },
86
87        getAttributes: function(/* jsx3.xml.Entity */ item){
88                //      summary:
89                //              Return an array of property names
90                //
91                return item.getAttributeNames(); // Array
92        },
93
94        hasAttribute: function(/* jsx3.xml.Entity */ item, /* String */ property){
95                //      summary:
96                //              Check whether an item has a property
97                //
98                return (this.getValue(item, property) !== undefined); // Boolean
99        },
100       
101        hasProperty: function(/* jsx3.xml.Entity */ item, /* String */ property){
102                // summary:
103                //      Alias for hasAttribute
104                return this.hasAttribute(item, property);
105        },
106       
107        containsValue: function(/* jsx3.xml.Entity */ item, /* String */ property, /* anything */ value){
108                //      summary:
109                //              Check whether an item contains a value
110                //
111                var values = this.getValues(item, property);
112                for(var i = 0; i < values.length; i++){
113                        if(values[i] === null){ continue; }
114                        if((typeof value === "string")){
115                                if(values[i].toString && values[i].toString() === value){
116                                        return true;
117                                }
118                        }else if(values[i] === value){
119                                return true; //boolean
120                        }
121                }
122                return false;//boolean
123        },
124
125        isItem: function(/* anything */ something){
126                //      summary:
127                //              Check whether the object is an item (jsx3.xml.Entity)
128                //
129                if(something.getClass && something.getClass().equals(jsx3.xml.Entity.jsxclass)){
130                        return true; //boolean
131                }
132                return false; //boolran
133        },
134
135        isItemLoaded: function(/* anything */ something){
136                //      summary:
137                //              Check whether the object is a jsx3.xml.Entity object and loaded
138                //
139                return this.isItem(something); // Boolean
140        },
141
142        loadItem: function(/* object */ keywordArgs){
143                //      summary:
144                //              Load an item
145                //      description:
146                //              The store always loads all items, so if it's an item, then it's loaded.
147        },
148
149        getFeatures: function(){
150                //      summary:
151                //              Return supported data APIs
152                //
153                return {
154                        "dojo.data.api.Read": true,
155                        "dojo.data.api.Write": true,
156                        "dojo.data.api.Identity":true
157                }; // Object
158        },
159
160        getLabel: function(/* jsx3.xml.Entity */ item){
161                //      summary:
162                //              See dojo.data.api.Read.getLabel()
163                //
164                if((this.label !== "") && this.isItem(item)){
165                        var label = this.getValue(item,this.label);
166                        if(label){
167                                return label.toString();
168                        }
169                }
170                return undefined; //undefined
171        },
172
173        getLabelAttributes: function(/* jsx3.xml.Entity */ item){
174                //      summary:
175                //              returns an array of what properties of the item that were used
176                //      to generate its label
177                //              See dojo.data.api.Read.getLabelAttributes()
178                //
179                if(this.label !== ""){
180                        return [this.label]; //array
181                }
182                return null; //null
183        },
184
185       
186        fetch: function(/* Object? */ request){
187                // summary:
188                //              Returns an Array of items based on the request arguments.
189                // description:
190                //              Returns an Array of items based on the request arguments.
191                //              If the store is in ASYNC mode, the items should be expected in an onComplete
192                //              method passed in the request object. If store is in SYNC mode, the items will
193                //              be return directly as well as within the onComplete method.
194                //      note:
195                //              The mode can be set on store initialization or during a fetch as one of the
196                //              parameters.
197                //
198                //      query: String
199                //              The items in the store are treated as objects, but this is reading an XML
200                //              document. Further, the actual querying of the items takes place in Tibco GI's
201                //              jsx3.xml.Entity. Therefore, we are using their syntax which is xpath.
202                //      Note:
203                //              As conforming to a CDF document, most, if not all nodes are considered "records"
204                //              and their tagNames are as such. The root node is named "data".
205                //
206                //      examples:
207                //              All items:
208                //              |       store.fetch({query:"*"});
209                //              Item with a jsxid attribute equal to "1" (note you could use byId for this)
210                //              |       store.fetch({query:"//record[@jsxid='1']"});
211                //              All items with any jsxid attribute:
212                //              |       "//record[@jsxid='*']"
213                //              The items with a jsxid of '1' or '4':
214                //              |       "//record[@jsxid='4' or @jsxid='1']"
215                //              All children within a "group" node (could be multiple group nodes):
216                //              "//group/record"
217                //              All children within a specific group node:
218                //              "//group[@name='mySecondGroup']/record"
219                //              Any record, anywhere in the document:
220                //              |       "//record"
221                //              Only the records beneath the root (data) node:
222                //              |       "//data/record"
223                //
224                //      See:
225                //      http://www.tibco.com/devnet/resources/gi/3_7/api/html/jsx3/xml/Entity.html#method:selectNodes
226                //      http://www.w3.org/TR/xpath
227                //      http://msdn.microsoft.com/en-us/library/ms256086.aspx
228                //
229                //      See dojo.data.Read.fetch():
230                //      onBegin
231                //      onComplete
232                //      onItem
233                //      onError
234                //      scope
235                //      start
236                //      count
237                //      sort
238                //
239                request = request || {};
240                if(!request.store){
241                        request.store = this;
242                }
243                if(request.mode !== undefined){
244                        this.mode = request.mode;
245                }
246                var self = this;
247       
248                var errorHandler = function(errorData){
249                        if(request.onError){
250                                var scope = request.scope || dojo.global;
251                                request.onError.call(scope, errorData, request);
252                        }else{
253                                console.error("cdfStore Error:", errorData);
254                        }
255                };
256       
257                var fetchHandler = function(items, requestObject){
258                        requestObject = requestObject || request;
259                        var oldAbortFunction = requestObject.abort || null;
260                        var aborted = false;
261       
262                        var startIndex = requestObject.start?requestObject.start:0;
263                        var endIndex = (requestObject.count && (requestObject.count !== Infinity))?(startIndex + requestObject.count):items.length;
264       
265                        requestObject.abort = function(){
266                                aborted = true;
267                                if(oldAbortFunction){
268                                        oldAbortFunction.call(requestObject);
269                                }
270                        };
271       
272                        var scope = requestObject.scope || dojo.global;
273                        if(!requestObject.store){
274                                requestObject.store = self;
275                        }
276                        if(requestObject.onBegin){
277                                requestObject.onBegin.call(scope, items.length, requestObject);
278                        }
279                        if(requestObject.sort){
280                                items.sort(dojo.data.util.sorter.createSortFunction(requestObject.sort, self));
281                        }
282                       
283                        if(requestObject.onItem){
284                                for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){
285                                        var item = items[i];
286                                        if(!aborted){
287                                                requestObject.onItem.call(scope, item, requestObject);
288                                        }
289                                }
290                        }
291                        if(requestObject.onComplete && !aborted){
292                                if(!requestObject.onItem){
293                                        items = items.slice(startIndex, endIndex);
294                                        if(requestObject.byId){
295                                                items = items[0];
296                                        }
297                                }
298                                requestObject.onComplete.call(scope, items, requestObject);
299                        }else{
300                                items = items.slice(startIndex, endIndex);
301                                if(requestObject.byId){
302                                        items = items[0];
303                                }
304                        }
305                        return items;
306                };
307               
308                if(!this.url && !this.data && !this.xmlStr){
309                        errorHandler(new Error("No URL or data specified."));
310                        return false;
311                }
312                var localRequest = request || "*"; // use request for _getItems()
313               
314                if(this.mode == dojox.data.SYNC_MODE){
315                        // sync mode. items returned directly
316                        var res = this._loadCDF();
317                        if(res instanceof Error){
318                                if(request.onError){
319                                        request.onError.call(request.scope || dojo.global, res, request);
320                                }else{
321                                        console.error("CdfStore Error:", res);
322                                }
323                                return res;
324                        }
325                        this.cdfDoc = res;
326                       
327                        var items = this._getItems(this.cdfDoc, localRequest);
328                        if(items && items.length > 0){
329                                items = fetchHandler(items, request);
330                        }else{
331                                items = fetchHandler([], request);
332                        }
333                        return items;
334               
335                }else{
336                       
337                        // async mode. Return a Deferred.
338                        var dfd = this._loadCDF();
339                        dfd.addCallbacks(dojo.hitch(this, function(cdfDoc){
340                                var items = this._getItems(this.cdfDoc, localRequest);
341                                if(items && items.length > 0){
342                                        fetchHandler(items, request);
343                                }else{
344                                        fetchHandler([], request);
345                                }
346                        }),
347                        dojo.hitch(this, function(err){
348                                errorHandler(err, request);
349                        }));
350                       
351                        return dfd;     // Object
352                }
353        },
354
355       
356        _loadCDF: function(){
357                //      summary:
358                //              Internal method.
359                //              If a cdfDoc exists, return it. Otherwise, get one from JSX3,
360                //              load the data or url, and return the doc or a deferred.
361                var dfd = new dojo.Deferred();
362                if(this.cdfDoc){
363                        if(this.mode == dojox.data.SYNC_MODE){
364                                return this.cdfDoc; // jsx3.xml.CDF
365                        }else{
366                                setTimeout(dojo.hitch(this, function(){
367                                        dfd.callback(this.cdfDoc);
368                                }), 0);
369                                return dfd; // dojo.Deferred
370                        }
371                }
372               
373                this.cdfDoc = jsx3.xml.CDF.Document.newDocument();
374                this.cdfDoc.subscribe("response", this, function(evt){
375                        dfd.callback(this.cdfDoc);
376                });
377                this.cdfDoc.subscribe("error", this, function(err){
378                        dfd.errback(err);
379                });
380               
381                this.cdfDoc.setAsync(!this.mode);
382                if(this.url){
383                        this.cdfDoc.load(this.url);
384                }else if(this.xmlStr){
385                        this.cdfDoc.loadXML(this.xmlStr);
386                        if(this.cdfDoc.getError().code){
387                                return new Error(this.cdfDoc.getError().description); // Error
388                        }
389                }
390               
391                if(this.mode == dojox.data.SYNC_MODE){
392                        return this.cdfDoc; // jsx3.xml.CDF
393                }else{
394                        return dfd;                     // dojo.Deferred
395                }
396        },
397       
398        _getItems: function(/* jsx3.xml.Entity */cdfDoc, /* Object */request){
399                // summary:
400                //              Internal method.
401                //              Requests the items from jsx3.xml.Entity with an xpath query.
402                //
403                var itr = cdfDoc.selectNodes(request.query, false, 1);
404                var items = [];
405                while(itr.hasNext()){
406                        items.push(itr.next());
407                }
408                return items;
409        },
410
411        close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
412                 //     summary:
413                 //             See dojo.data.api.Read.close()
414        },
415
416/* dojo.data.api.Write */
417
418        newItem: function(/* object? */ keywordArgs, /* object? || String? */parentInfo){
419                //      summary:
420                //              Creates a jsx3.xml.Entity item and inserts it either inside the
421                //              parent or appends it to the root
422                //
423                keywordArgs = (keywordArgs || {});
424                if(keywordArgs.tagName){
425                        // record tagName is automatic and this would add it
426                        // as a property
427                        if(keywordArgs.tagName!="record"){
428                                // TODO: How about some sort of group?
429                                console.warn("Only record inserts are supported at this time");
430                        }
431                        delete keywordArgs.tagName;
432                }
433                keywordArgs.jsxid = keywordArgs.jsxid || this.cdfDoc.getKey();
434                if(this.isItem(parentInfo)){
435                        parentInfo = this.getIdentity(parentInfo);
436                }
437                var item = this.cdfDoc.insertRecord(keywordArgs, parentInfo);
438
439                this._makeDirty(item);
440               
441                return item; // jsx3.xml.Entity
442        },
443       
444        deleteItem: function(/* jsx3.xml.Entity */ item){
445                //      summary:
446                //              Delete an jsx3.xml.Entity (wrapper to a XML element).
447                //
448                this.cdfDoc.deleteRecord(this.getIdentity(item));
449                this._makeDirty(item);
450                return true; //boolean
451        },
452       
453        setValue: function(/* jsx3.xml.Entity */ item, /* String */ property, /* almost anything */ value){
454                //      summary:
455                //              Set an property value
456                //
457                this._makeDirty(item);
458                item.setAttribute(property, value);
459                return true; // Boolean
460        },
461               
462        setValues: function(/* jsx3.xml.Entity */ item, /* String */ property, /*array*/ values){
463                //      summary:
464                //              Set property values
465                //              TODO: Needs to be fully implemented.
466                //
467                this._makeDirty(item);
468                console.warn("cdfStore.setValues only partially implemented.");
469                return item.setAttribute(property, values);
470               
471        },
472       
473        unsetAttribute: function(/* jsx3.xml.Entity */ item, /* String */ property){
474                //      summary:
475                //              Remove an property
476                //
477                this._makeDirty(item);
478                item.removeAttribute(property);
479                return true; // Boolean
480        },
481       
482        revert: function(){
483                // summary:
484                //              Invalidate changes (new and/or modified elements)
485                //              Resets data by simply deleting the reference to the cdfDoc.
486                //              Subsequent fetches will load the new data.
487                // Note:
488                //              Any items outside the store will no longer be valid and may cause errors.
489                //
490                delete this.cdfDoc;
491                this._modifiedItems = {};
492                return true; //boolean
493        },
494       
495        isDirty: function(/* jsx3.xml.Entity ? */ item){
496                //      summary:
497                //              Check whether an item is new, modified or deleted.
498                //              If no item is passed, checks if anything in the store has changed.
499                //
500                if(item){
501                        return !!this._modifiedItems[this.getIdentity(item)]; // Boolean
502                }else{
503                        var _dirty = false;
504                        for(var nm in this._modifiedItems){ _dirty = true; break; }
505                        return _dirty; // Boolean
506                }
507        },
508
509       
510
511/* internal API */
512
513        _makeDirty: function(item){
514                // summary:
515                //              Internal method.
516                //              Marks items as modified, deleted or new.
517                var id = this.getIdentity(item);
518                this._modifiedItems[id] = item;
519        },
520       
521       
522        _makeXmlString: function(obj){
523                // summary:
524                //              Internal method.
525                //              Converts an object into an XML string.
526                //
527                var parseObj = function(obj, name){
528                        var xmlStr = "";
529                        var nm;
530                        if(dojo.isArray(obj)){
531                                for(var i=0;i<obj.length;i++){
532                                        xmlStr += parseObj(obj[i], name);
533                                }
534                        }else if(dojo.isObject(obj)){
535                                xmlStr += '<'+name+' ';
536                                for(nm in obj){
537                                        if(!dojo.isObject(obj[nm])){
538                                                xmlStr += nm+'="'+obj[nm]+'" ';
539                                        }
540                                }
541                                xmlStr +='>';
542                                for(nm in obj){
543                                        if(dojo.isObject(obj[nm])){
544                                                xmlStr += parseObj(obj[nm], nm);
545                                        }
546                                }
547                                xmlStr += '</'+name+'>';
548                        }
549                        return xmlStr;
550                };
551                return parseObj(obj, "data");
552        },
553
554        /*************************************
555         * Dojo.data Identity implementation *
556         *************************************/
557        getIdentity: function(/* jsx3.xml.Entity */ item){
558                //      summary:
559                //              Returns the identifier for an item.
560                //
561                return this.getValue(item, this.identity); // String
562        },
563
564        getIdentityAttributes: function(/* jsx3.xml.Entity */ item){
565                //      summary:
566                //              Returns the property used for the identity.
567                //
568                return [this.identity]; // Array
569        },
570
571
572        fetchItemByIdentity: function(/* Object || String */ args){
573                //      summary:
574                //              See dojo.data.api.Identity.fetchItemByIdentity(keywordArgs)
575                //
576                //      Note:
577                //              This method can be synchronous if mode is set.
578                //              Also, there is a more finger friendly alias of this method, byId();
579                if(dojo.isString(args)){
580                        var id = args;
581                        args = {query:"//record[@jsxid='"+id+"']", mode: dojox.data.SYNC_MODE};
582                }else{
583                        if(args){
584                                args.query = "//record[@jsxid='"+args.identity+"']";
585                        }
586                        if(!args.mode){args.mode = this.mode;}
587                }
588                args.byId = true;
589                return this.fetch(args); // dojo.Deferred || Array
590        },
591        byId: function(/* Object || String */ args){
592                // stub. See fetchItemByIdentity
593        }
594       
595});
596
597return dojox.data.CdfStore;
598});
599
Note: See TracBrowser for help on using the repository browser.