source: Dev/trunk/src/client/dojox/data/CdfStore.js @ 532

Last change on this file since 532 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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