source: Dev/branches/rest-dojo-ui/client/dojox/data/XmlStore.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).

File size: 43.7 KB
Line 
1define(["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/xhr", "dojo/data/util/simpleFetch",
2                "dojo/_base/query", "dojo/_base/array", "dojo/_base/window", "dojo/data/util/filter", "dojox/xml/parser",
3                "dojox/data/XmlItem"],
4  function(lang, declare, xhr, simpleFetch, domQuery, array, winUtil, filter, xmlParser, XmlItem) {
5
6var XmlStore = declare("dojox.data.XmlStore", null, {
7        //      summary:
8        //              A data store for XML based services or documents
9        //      description:
10        //              A data store for XML based services or documents
11       
12        constructor: function(/* object */ args){
13                //      summary:
14                //              Constructor for the XML store.
15                //      args:
16                //              An anonymous object to initialize properties.  It expects the following values:
17                //              url:            The url to a service or an XML document that represents the store
18                //              rootItem:       A tag name for root items
19                //              keyAttribute:   An attribute name for a key or an identity (unique identifier)
20                //                                              Required for serverside fetchByIdentity, etc.  Not required for
21                //                                              client side fetchItemBIdentity, as it will use an XPath-like
22                //                                              structure if keyAttribute was not specified.  Recommended to always
23                //                                              set this, though, for consistent identity behavior.
24                //              attributeMap:   An anonymous object contains properties for attribute mapping,
25                //                                              {"tag_name.item_attribute_name": "@xml_attribute_name", ...}
26                //              sendQuery:              A boolean indicate to add a query string to the service URL.
27                //                                              Default is false.
28                //              urlPreventCache: Parameter to indicate whether or not URL calls should apply
29                //                               the preventCache option to the xhr request.
30                if(args){
31                        this.url = args.url;
32                        this.rootItem = (args.rootItem || args.rootitem || this.rootItem);
33                        this.keyAttribute = (args.keyAttribute || args.keyattribute || this.keyAttribute);
34                        this._attributeMap = (args.attributeMap || args.attributemap);
35                        this.label = args.label || this.label;
36                        this.sendQuery = (args.sendQuery || args.sendquery || this.sendQuery);
37                        if("urlPreventCache" in args){
38                                this.urlPreventCache = args.urlPreventCache?true:false;
39                        }
40                }
41                this._newItems = [];
42                this._deletedItems = [];
43                this._modifiedItems = [];
44        },
45
46        //Values that may be set by the parser.
47        //Ergo, have to be instantiated to something
48        //So the parser knows how to set them.
49        url: "",
50
51        //      A tag name for XML tags to be considered root items in the hierarchy
52        rootItem: "",
53
54        //      An attribute name for a key or an identity (unique identifier)
55        //      Required for serverside fetchByIdentity, etc.  Not required for
56        //      client side fetchItemBIdentity, as it will use an XPath-like
57        //      structure if keyAttribute was not specified.  Recommended to always
58        //      set this, though, for consistent identity behavior.
59        keyAttribute: "",
60
61        //      An attribute of the item to use as the label.
62        label: "",
63
64        //      A boolean indicate to add a query string to the service URL.
65        //      Default is false.
66        sendQuery: false,
67
68        //      An anonymous object that contains properties for attribute mapping,
69        //      for example {"tag_name.item_attribute_name": "@xml_attribute_name", ...}.
70        //      This is optional. This is done so that attributes which are actual
71        //      XML tag attributes (and not sub-tags of an XML tag), can be referenced.
72        attributeMap: null,
73
74        //      Parameter to indicate whether or not URL calls should apply the preventCache option to the xhr request.
75        urlPreventCache: true,
76
77        /* dojo.data.api.Read */
78
79        getValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* value? */ defaultValue){
80                //      summary:
81                //              Return an attribute value
82                //      description:
83                //              'item' must be an instance of a dojox.data.XmlItem from the store instance.
84                //              If 'attribute' specifies "tagName", the tag name of the element is
85                //              returned.
86                //              If 'attribute' specifies "childNodes", the first element child is
87                //              returned.
88                //              If 'attribute' specifies "text()", the value of the first text
89                //              child is returned.
90                //              For generic attributes, if '_attributeMap' is specified,
91                //              an actual attribute name is looked up with the tag name of
92                //              the element and 'attribute' (concatenated with '.').
93                //              Then, if 'attribute' starts with "@", the value of the XML
94                //              attribute is returned.
95                //              Otherwise, the first child element of the tag name specified with
96                //              'attribute' is returned.
97                //      item:
98                //              An XML element that holds the attribute
99                //      attribute:
100                //              A tag name of a child element, An XML attribute name or one of
101                //              special names
102                //      defaultValue:
103                //              A default value
104                //      returns:
105                //              An attribute value found, otherwise 'defaultValue'
106                var element = item.element;
107                var i;
108                var node;
109                if(attribute === "tagName"){
110                        return element.nodeName;
111                }else if(attribute === "childNodes"){
112                        for(i = 0; i < element.childNodes.length; i++){
113                                node = element.childNodes[i];
114                                if(node.nodeType === 1 /*ELEMENT_NODE*/){
115                                        return this._getItem(node); //object
116                                }
117                        }
118                        return defaultValue;
119                }else if(attribute === "text()"){
120                        for(i = 0; i < element.childNodes.length; i++){
121                                node = element.childNodes[i];
122                                if(node.nodeType === 3 /*TEXT_NODE*/ ||
123                                        node.nodeType === 4 /*CDATA_SECTION_NODE*/){
124                                        return node.nodeValue; //string
125                                }
126                        }
127                        return defaultValue;
128                }else{
129                        attribute = this._getAttribute(element.nodeName, attribute);
130                        if(attribute.charAt(0) === '@'){
131                                var name = attribute.substring(1);
132                                var value = element.getAttribute(name);
133                                //Note that getAttribute will return null or empty string for undefined/unset
134                                //attributes, therefore, we should just check the return was valid
135                                //non-empty string and not null.
136                                return (value) ? value : defaultValue; //object
137                        }else{
138                                for(i = 0; i < element.childNodes.length; i++){
139                                        node = element.childNodes[i];
140                                        if(     node.nodeType === 1 /*ELEMENT_NODE*/ &&
141                                                node.nodeName === attribute){
142                                                return this._getItem(node); //object
143                                        }
144                                }
145                                return defaultValue; //object
146                        }
147                }
148        },
149
150        getValues: function(/* item */ item, /* attribute || attribute-name-string */ attribute){
151                //      summary:
152                //              Return an array of attribute values
153                //      description:
154                //              'item' must be an instance of a dojox.data.XmlItem from the store instance.
155                //              If 'attribute' specifies "tagName", the tag name of the element is
156                //              returned.
157                //              If 'attribute' specifies "childNodes", child elements are returned.
158                //              If 'attribute' specifies "text()", the values of child text nodes
159                //              are returned.
160                //              For generic attributes, if 'attributeMap' is specified,
161                //              an actual attribute name is looked up with the tag name of
162                //              the element and 'attribute' (concatenated with '.').
163                //              Then, if 'attribute' starts with "@", the value of the XML
164                //              attribute is returned.
165                //              Otherwise, child elements of the tag name specified with
166                //              'attribute' are returned.
167                //      item:
168                //              An XML element that holds the attribute
169                //      attribute:
170                //              A tag name of child elements, An XML attribute name or one of
171                //              special names
172                //      returns:
173                //              An array of attribute values found, otherwise an empty array
174                var element = item.element;
175                var values = [];
176                var i;
177                var node;
178                if(attribute === "tagName"){
179                        return [element.nodeName];
180                }else if(attribute === "childNodes"){
181                        for(i = 0; i < element.childNodes.length; i++){
182                                node = element.childNodes[i];
183                                if(node.nodeType === 1 /*ELEMENT_NODE*/){
184                                        values.push(this._getItem(node));
185                                }
186                        }
187                        return values; //array
188                }else if(attribute === "text()"){
189                        var ec = element.childNodes;
190                        for(i = 0; i < ec.length; i++){
191                                node = ec[i];
192                                if(node.nodeType === 3 || node.nodeType === 4){
193                                        values.push(node.nodeValue);
194                                }
195                        }
196                        return values; //array
197                }else{
198                        attribute = this._getAttribute(element.nodeName, attribute);
199                        if(attribute.charAt(0) === '@'){
200                                var name = attribute.substring(1);
201                                var value = element.getAttribute(name);
202                                return (value !== undefined) ? [value] : []; //array
203                        }else{
204                                for(i = 0; i < element.childNodes.length; i++){
205                                        node = element.childNodes[i];
206                                        if(     node.nodeType === 1 /*ELEMENT_NODE*/ &&
207                                                node.nodeName === attribute){
208                                                values.push(this._getItem(node));
209                                        }
210                                }
211                                return values; //array
212                        }
213                }
214        },
215
216        getAttributes: function(/* item */ item){
217                //      summary:
218                //              Return an array of attribute names
219                //      description:
220                //              'item' must be an instance of a dojox.data.XmlItem from the store instance.
221                //              tag names of child elements and XML attribute names of attributes
222                //              specified to the element are returned along with special attribute
223                //              names applicable to the element including "tagName", "childNodes"
224                //              if the element has child elements, "text()" if the element has
225                //              child text nodes, and attribute names in '_attributeMap' that match
226                //              the tag name of the element.
227                //      item:
228                //              An XML element
229                //      returns:
230                //              An array of attributes found
231                var element = item.element;
232                var attributes = [];
233                var i;
234                attributes.push("tagName");
235                if(element.childNodes.length > 0){
236                        var names = {};
237                        var childNodes = true;
238                        var text = false;
239                        for(i = 0; i < element.childNodes.length; i++){
240                                var node = element.childNodes[i];
241                                if(node.nodeType === 1 /*ELEMENT_NODE*/){
242                                        var name = node.nodeName;
243                                        if(!names[name]){
244                                                attributes.push(name);
245                                                names[name] = name;
246                                        }
247                                        childNodes = true;
248                                }else if(node.nodeType === 3){
249                                        text = true;
250                                }
251                        }
252                        if(childNodes){
253                                attributes.push("childNodes");
254                        }
255                        if(text){
256                                attributes.push("text()");
257                        }
258                }
259                for(i = 0; i < element.attributes.length; i++){
260                        attributes.push("@" + element.attributes[i].nodeName);
261                }
262                if(this._attributeMap){
263                        for(var key in this._attributeMap){
264                                i = key.indexOf('.');
265                                if(i > 0){
266                                        var tagName = key.substring(0, i);
267                                        if(tagName === element.nodeName){
268                                                attributes.push(key.substring(i + 1));
269                                        }
270                                }else{ // global attribute
271                                        attributes.push(key);
272                                }
273                        }
274                }
275                return attributes; //array
276        },
277
278        hasAttribute: function(/* item */ item, /* attribute || attribute-name-string */ attribute){
279                //      summary:
280                //              Check whether an element has the attribute
281                //      item:
282                //              'item' must be an instance of a dojox.data.XmlItem from the store instance.
283                //      attribute:
284                //              A tag name of a child element, An XML attribute name or one of
285                //              special names
286                //      returns:
287                //              True if the element has the attribute, otherwise false
288                return (this.getValue(item, attribute) !== undefined); //boolean
289        },
290
291        containsValue: function(/* item */ item, /* attribute || attribute-name-string */ attribute, /* anything */ value){
292                //      summary:
293                //              Check whether the attribute values contain the value
294                //      item:
295                //              'item' must be an instance of a dojox.data.XmlItem from the store instance.
296                //      attribute:
297                //              A tag name of a child element, An XML attribute name or one of
298                //              special names
299                //      returns:
300                //              True if the attribute values contain the value, otherwise false
301                var values = this.getValues(item, attribute);
302                for(var i = 0; i < values.length; i++){
303                        if((typeof value === "string")){
304                                if(values[i].toString && values[i].toString() === value){
305                                        return true;
306                                }
307                        }else if(values[i] === value){
308                                return true; //boolean
309                        }
310                }
311                return false;//boolean
312        },
313
314        isItem: function(/* anything */ something){
315                //      summary:
316                //              Check whether the object is an item (XML element)
317                //      item:
318                //              An object to check
319                //      returns:
320                //              True if the object is an XML element, otherwise false
321                if(something && something.element && something.store && something.store === this){
322                        return true; //boolean
323                }
324                return false; //boolran
325        },
326
327        isItemLoaded: function(/* anything */ something){
328                //      summary:
329                //              Check whether the object is an item (XML element) and loaded
330                //      item:
331                //              An object to check
332                //      returns:
333                //              True if the object is an XML element, otherwise false
334                return this.isItem(something); //boolean
335        },
336
337        loadItem: function(/* object */ keywordArgs){
338                //      summary:
339                //              Load an item (XML element)
340                //      keywordArgs:
341                //              object containing the args for loadItem.  See dojo.data.api.Read.loadItem()
342        },
343
344        getFeatures: function(){
345                //      summary:
346                //              Return supported data APIs
347                //      returns:
348                //              "dojo.data.api.Read" and "dojo.data.api.Write"
349                var features = {
350                        "dojo.data.api.Read": true,
351                        "dojo.data.api.Write": true
352                };
353
354                //Local XML parsing can implement Identity fairly simple via
355                if(!this.sendQuery || this.keyAttribute !== ""){
356                        features["dojo.data.api.Identity"] = true;
357                }
358                return features; //array
359        },
360
361        getLabel: function(/* item */ item){
362                //      summary:
363                //              See dojo.data.api.Read.getLabel()
364                if((this.label !== "") && this.isItem(item)){
365                        var label = this.getValue(item,this.label);
366                        if(label){
367                                return label.toString();
368                        }
369                }
370                return undefined; //undefined
371        },
372
373        getLabelAttributes: function(/* item */ item){
374                //      summary:
375                //              See dojo.data.api.Read.getLabelAttributes()
376                if(this.label !== ""){
377                        return [this.label]; //array
378                }
379                return null; //null
380        },
381
382        _fetchItems: function(request, fetchHandler, errorHandler){
383                //      summary:
384                //              Fetch items (XML elements) that match to a query
385                //      description:
386                //              If 'sendQuery' is true, an XML document is loaded from
387                //              'url' with a query string.
388                //              Otherwise, an XML document is loaded and list XML elements that
389                //              match to a query (set of element names and their text attribute
390                //              values that the items to contain).
391                //              A wildcard, "*" can be used to query values to match all
392                //              occurrences.
393                //              If 'rootItem' is specified, it is used to fetch items.
394                //      request:
395                //              A request object
396                //      fetchHandler:
397                //              A function to call for fetched items
398                //      errorHandler:
399                //              A function to call on error
400                var url = this._getFetchUrl(request);
401                if(!url){
402                        errorHandler(new Error("No URL specified."), request);
403                        return;
404                }
405                var localRequest = (!this.sendQuery ? request : {}); // use request for _getItems()
406
407                var self = this;
408                var getArgs = {
409                                url: url,
410                                handleAs: "xml",
411                                preventCache: self.urlPreventCache
412                        };
413                var getHandler = xhr.get(getArgs);
414                getHandler.addCallback(function(data){
415                        var items = self._getItems(data, localRequest);
416                        if(items && items.length > 0){
417                                fetchHandler(items, request);
418                        }else{
419                                fetchHandler([], request);
420                        }
421                });
422                getHandler.addErrback(function(data){
423                        errorHandler(data, request);
424                });
425        },
426
427        _getFetchUrl: function(request){
428                //      summary:
429                //              Generate a URL for fetch
430                //      description:
431                //              This default implementation generates a query string in the form of
432                //              "?name1=value1&name2=value2..." off properties of 'query' object
433                //              specified in 'request' and appends it to 'url', if 'sendQuery'
434                //              is set to false.
435                //              Otherwise, 'url' is returned as is.
436                //              Sub-classes may override this method for the custom URL generation.
437                //      request:
438                //              A request object
439                //      returns:
440                //              A fetch URL
441                if(!this.sendQuery){
442                        return this.url;
443                }
444                var query = request.query;
445                if(!query){
446                        return this.url;
447                }
448                if(lang.isString(query)){
449                        return this.url + query;
450                }
451                var queryString = "";
452                for(var name in query){
453                        var value = query[name];
454                        if(value){
455                                if(queryString){
456                                        queryString += "&";
457                                }
458                                queryString += (name + "=" + value);
459                        }
460                }
461                if(!queryString){
462                        return this.url;
463                }
464                //Check to see if the URL already has query params or not.
465                var fullUrl = this.url;
466                if(fullUrl.indexOf("?") < 0){
467                        fullUrl += "?";
468                }else{
469                        fullUrl += "&";
470                }
471                return fullUrl + queryString;
472        },
473
474        _getItems: function(document, request){
475                //      summary:
476                //              Fetch items (XML elements) in an XML document based on a request
477                //      description:
478                //              This default implementation walks through child elements of
479                //              the document element to see if all properties of 'query' object
480                //              match corresponding attributes of the element (item).
481                //              If 'request' is not specified, all child elements are returned.
482                //              Sub-classes may override this method for the custom search in
483                //              an XML document.
484                //      document:
485                //              An XML document
486                //      request:
487                //              A request object
488                //      returns:
489                //              An array of items
490                var query = null;
491                if(request){
492                        query = request.query;
493                }
494                var items = [];
495                var nodes = null;
496
497                if(this.rootItem !== ""){
498                        nodes = domQuery(this.rootItem, document);
499                }else{
500                        nodes = document.documentElement.childNodes;
501                }
502
503                var deep = request.queryOptions ? request.queryOptions.deep : false;
504                if(deep){
505                        nodes = this._flattenNodes(nodes);
506                }
507                for(var i = 0; i < nodes.length; i++){
508                        var node = nodes[i];
509                        if(node.nodeType != 1 /*ELEMENT_NODE*/){
510                                continue;
511                        }
512                        var item = this._getItem(node);
513                        if(query){
514                                var ignoreCase = request.queryOptions ? request.queryOptions.ignoreCase : false;
515                                var value;
516                                var match = false;
517                                var j;
518                                var emptyQuery = true;
519
520                                //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the
521                                //same value for each item examined.  Much more efficient.
522                                var regexpList = {};
523                                for(var key in query){
524                                        value = query[key];
525                                        if(typeof value === "string"){
526                                                regexpList[key] = filter.patternToRegExp(value, ignoreCase);
527                                        }else if(value){
528                                                // It's an object, possibly regexp, so treat it as one.
529                                                regexpList[key] = value;
530                                        }
531                                }
532                                for(var attribute in query){
533                                        emptyQuery = false;
534                                        var values = this.getValues(item, attribute);
535                                        for(j = 0; j < values.length; j++){
536                                                value = values[j];
537                                                if(value){
538                                                        var queryValue = query[attribute];
539                                                        if((typeof value) === "string" &&
540                                                                (regexpList[attribute])){
541                                                                if((value.match(regexpList[attribute])) !== null){
542                                                                        match = true;
543                                                                }else{
544                                                                        match = false;
545                                                                }
546                                                        }else if((typeof value) === "object"){
547                                                                if(     value.toString &&
548                                                                        (regexpList[attribute])){
549                                                                        var stringValue = value.toString();
550                                                                        if((stringValue.match(regexpList[attribute])) !== null){
551                                                                                match = true;
552                                                                        }else{
553                                                                                match = false;
554                                                                        }
555                                                                }else{
556                                                                        if(queryValue === "*" || queryValue === value){
557                                                                                match = true;
558                                                                        }else{
559                                                                                match = false;
560                                                                        }
561                                                                }
562                                                        }
563                                                }
564                                                //One of the multiValue values matched,
565                                                //so quit looking.
566                                                if(match){
567                                                        break;
568                                                }
569                                        }
570                                        if(!match){
571                                                break;
572                                        }
573                                }
574                                //Either the query was an empty object {}, which is match all, or
575                                //was an actual match.
576                                if(emptyQuery || match){
577                                        items.push(item);
578                                }
579                        }else{
580                                //No query, everything matches.
581                                items.push(item);
582                        }
583                }
584                array.forEach(items,function(item){
585                        if(item.element.parentNode){
586                                item.element.parentNode.removeChild(item.element); // make it root
587                        }
588                },this);
589                return items;
590        },
591
592        _flattenNodes: function(nodes){
593                //      Summary:
594                //              Function used to flatten a hierarchy of XML nodes into a single list for
595                //              querying over.  Used when deep = true;
596                var flattened = [];
597                if(nodes){
598                        var i;
599                        for(i = 0; i < nodes.length; i++){
600                                var node = nodes[i];
601                                flattened.push(node);
602                                if(node.childNodes && node.childNodes.length > 0){
603                                        flattened = flattened.concat(this._flattenNodes(node.childNodes));
604                                }
605                        }
606                }
607                return flattened;
608        },
609
610        close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
611                 //     summary:
612                 //             See dojo.data.api.Read.close()
613        },
614
615/* dojo.data.api.Write */
616
617        newItem: function(/* object? */ keywordArgs, parentInfo){
618                //      summary:
619                //              Return a new dojox.data.XmlItem
620                //      description:
621                //              At least, 'keywordArgs' must contain "tagName" to be used for
622                //              the new element.
623                //              Other attributes in 'keywordArgs' are set to the new element,
624                //              including "text()", but excluding "childNodes".
625                //      keywordArgs:
626                //              An object containing initial attributes
627                //      returns:
628                //              An XML element
629                keywordArgs = (keywordArgs || {});
630                var tagName = keywordArgs.tagName;
631                if(!tagName){
632                        tagName = this.rootItem;
633                        if(tagName === ""){
634                                return null;
635                        }
636                }
637
638                var document = this._getDocument();
639                var element = document.createElement(tagName);
640                for(var attribute in keywordArgs){
641                        var text;
642                        if(attribute === "tagName"){
643                                continue;
644                        }else if(attribute === "text()"){
645                                text = document.createTextNode(keywordArgs[attribute]);
646                                element.appendChild(text);
647                        }else{
648                                attribute = this._getAttribute(tagName, attribute);
649                                if(attribute.charAt(0) === '@'){
650                                        var name = attribute.substring(1);
651                                        element.setAttribute(name, keywordArgs[attribute]);
652                                }else{
653                                        var child = document.createElement(attribute);
654                                        text = document.createTextNode(keywordArgs[attribute]);
655                                        child.appendChild(text);
656                                        element.appendChild(child);
657                                }
658                        }
659                }
660
661                var item = this._getItem(element);
662                this._newItems.push(item);
663
664                var pInfo = null;
665                if(parentInfo && parentInfo.parent && parentInfo.attribute){
666                        pInfo = {
667                                item: parentInfo.parent,
668                                attribute: parentInfo.attribute,
669                                oldValue: undefined
670                        };
671
672                        //See if it is multi-valued or not and handle appropriately
673                        //Generally, all attributes are multi-valued for this store
674                        //So, we only need to append if there are already values present.
675                        var values = this.getValues(parentInfo.parent, parentInfo.attribute);
676                        if(values && values.length > 0){
677                                var tempValues = values.slice(0, values.length);
678                                if(values.length === 1){
679                                        pInfo.oldValue = values[0];
680                                }else{
681                                        pInfo.oldValue = values.slice(0, values.length);
682                                }
683                                tempValues.push(item);
684                                this.setValues(parentInfo.parent, parentInfo.attribute, tempValues);
685                                pInfo.newValue = this.getValues(parentInfo.parent, parentInfo.attribute);
686                        }else{
687                                this.setValue(parentInfo.parent, parentInfo.attribute, item);
688                                pInfo.newValue = item;
689                        }
690                }
691                return item; //object
692        },
693       
694        deleteItem: function(/* item */ item){
695                //      summary:
696                //              Delete an dojox.data.XmlItem (wrapper to a XML element).
697                //      item:
698                //              An XML element to delete
699                //      returns:
700                //              True
701                var element = item.element;
702                if(element.parentNode){
703                        this._backupItem(item);
704                        element.parentNode.removeChild(element);
705                        return true;
706                }
707                this._forgetItem(item);
708                this._deletedItems.push(item);
709                return true; //boolean
710        },
711       
712        setValue: function(/* item */ item, /* attribute || string */ attribute, /* almost anything */ value){
713                //      summary:
714                //              Set an attribute value
715                //      description:
716                //              'item' must be an instance of a dojox.data.XmlItem from the store instance.
717                //              If 'attribute' specifies "tagName", nothing is set and false is
718                //              returned.
719                //              If 'attribute' specifies "childNodes", the value (XML element) is
720                //              added to the element.
721                //              If 'attribute' specifies "text()", a text node is created with
722                //              the value and set it to the element as a child.
723                //              For generic attributes, if '_attributeMap' is specified,
724                //              an actual attribute name is looked up with the tag name of
725                //              the element and 'attribute' (concatenated with '.').
726                //              Then, if 'attribute' starts with "@", the value is set to the XML
727                //              attribute.
728                //              Otherwise, a text node is created with the value and set it to
729                //              the first child element of the tag name specified with 'attribute'.
730                //              If the child element does not exist, it is created.
731                //      item:
732                //              An XML element that holds the attribute
733                //      attribute:
734                //              A tag name of a child element, An XML attribute name or one of
735                //              special names
736                //      value:
737                //              A attribute value to set
738                //      returns:
739                //              False for "tagName", otherwise true
740                if(attribute === "tagName"){
741                        return false; //boolean
742                }
743
744                this._backupItem(item);
745
746                var element = item.element;
747                var child;
748                var text;
749                if(attribute === "childNodes"){
750                        child = value.element;
751                        element.appendChild(child);
752                }else if(attribute === "text()"){
753                        while(element.firstChild){
754                                element.removeChild(element.firstChild);
755                        }
756                        text = this._getDocument(element).createTextNode(value);
757                        element.appendChild(text);
758                }else{
759                        attribute = this._getAttribute(element.nodeName, attribute);
760                        if(attribute.charAt(0) === '@'){
761                                var name = attribute.substring(1);
762                                element.setAttribute(name, value);
763                        }else{
764                                for(var i = 0; i < element.childNodes.length; i++){
765                                        var node = element.childNodes[i];
766                                        if(     node.nodeType === 1 /*ELEMENT_NODE*/ &&
767                                                node.nodeName === attribute){
768                                                child = node;
769                                                break;
770                                        }
771                                }
772                                var document = this._getDocument(element);
773                                if(child){
774                                        while(child.firstChild){
775                                                child.removeChild(child.firstChild);
776                                        }
777                                }else{
778                                        child = document.createElement(attribute);
779                                        element.appendChild(child);
780                                }
781                                text = document.createTextNode(value);
782                                child.appendChild(text);
783                        }
784                }
785                return true; //boolean
786        },
787               
788        setValues: function(/* item */ item, /* attribute || string */ attribute, /*array*/ values){
789                //      summary:
790                //              Set attribute values
791                //      description:
792                //              'item' must be an instance of a dojox.data.XmlItem from the store instance.
793                //              If 'attribute' specifies "tagName", nothing is set and false is
794                //              returned.
795                //              If 'attribute' specifies "childNodes", the value (array of XML
796                //              elements) is set to the element's childNodes.
797                //              If 'attribute' specifies "text()", a text node is created with
798                //              the values and set it to the element as a child.
799                //              For generic attributes, if '_attributeMap' is specified,
800                //              an actual attribute name is looked up with the tag name of
801                //              the element and 'attribute' (concatenated with '.').
802                //              Then, if 'attribute' starts with "@", the first value is set to
803                //              the XML attribute.
804                //              Otherwise, child elements of the tag name specified with
805                //              'attribute' are replaced with new child elements and their
806                //              child text nodes of values.
807                //      item:
808                //              An XML element that holds the attribute
809                //      attribute:
810                //              A tag name of child elements, an XML attribute name or one of
811                //              special names
812                //      value:
813                //              A attribute value to set
814                //      notify:
815                //              A non-API optional argument, used to indicate if notification API should be called
816                //              or not.
817
818                //      returns:
819                //              False for "tagName", otherwise true
820                if(attribute === "tagName"){
821                        return false; //boolean
822                }
823
824                this._backupItem(item);
825
826                var element = item.element;
827                var i;
828                var child;
829                var text;
830                if(attribute === "childNodes"){
831                        while(element.firstChild){
832                                element.removeChild(element.firstChild);
833                        }
834                        for(i = 0; i < values.length; i++){
835                                child = values[i].element;
836                                element.appendChild(child);
837                        }
838                }else if(attribute === "text()"){
839                        while(element.firstChild){
840                                element.removeChild(element.firstChild);
841                        }
842                        var value = "";
843                        for(i = 0; i < values.length; i++){
844                                value += values[i];
845                        }
846                        text = this._getDocument(element).createTextNode(value);
847                        element.appendChild(text);
848                }else{
849                        attribute = this._getAttribute(element.nodeName, attribute);
850                        if(attribute.charAt(0) === '@'){
851                                var name = attribute.substring(1);
852                                element.setAttribute(name, values[0]);
853                        }else{
854                                for(i = element.childNodes.length - 1; i >= 0; i--){
855                                        var node = element.childNodes[i];
856                                        if(     node.nodeType === 1 /*ELEMENT_NODE*/ &&
857                                                node.nodeName === attribute){
858                                                element.removeChild(node);
859                                        }
860                                }
861                                var document = this._getDocument(element);
862                                for(i = 0; i < values.length; i++){
863                                        child = document.createElement(attribute);
864                                        text = document.createTextNode(values[i]);
865                                        child.appendChild(text);
866                                        element.appendChild(child);
867                                }
868                        }
869                }
870                return true; //boolean
871        },
872       
873        unsetAttribute: function(/* item */ item, /* attribute || string */ attribute){
874                //      summary:
875                //              Remove an attribute
876                //      description:
877                //              'item' must be an instance of a dojox.data.XmlItem from the store instance.
878                //              'attribute' can be an XML attribute name of the element or one of
879                //              special names described below.
880                //              If 'attribute' specifies "tagName", nothing is removed and false is
881                //              returned.
882                //              If 'attribute' specifies "childNodes" or "text()", all child nodes
883                //              are removed.
884                //              For generic attributes, if '_attributeMap' is specified,
885                //              an actual attribute name is looked up with the tag name of
886                //              the element and 'attribute' (concatenated with '.').
887                //              Then, if 'attribute' starts with "@", the XML attribute is removed.
888                //              Otherwise, child elements of the tag name specified with
889                //              'attribute' are removed.
890                //      item:
891                //              An XML element that holds the attribute
892                //      attribute:
893                //              A tag name of child elements, an XML attribute name or one of
894                //              special names
895                //      returns:
896                //              False for "tagName", otherwise true
897                if(attribute === "tagName"){
898                        return false; //boolean
899                }
900
901                this._backupItem(item);
902
903                var element = item.element;
904                if(attribute === "childNodes" || attribute === "text()"){
905                        while(element.firstChild){
906                                element.removeChild(element.firstChild);
907                        }
908                }else{
909                        attribute = this._getAttribute(element.nodeName, attribute);
910                        if(attribute.charAt(0) === '@'){
911                                var name = attribute.substring(1);
912                                element.removeAttribute(name);
913                        }else{
914                                for(var i = element.childNodes.length - 1; i >= 0; i--){
915                                        var node = element.childNodes[i];
916                                        if(     node.nodeType === 1 /*ELEMENT_NODE*/ &&
917                                                node.nodeName === attribute){
918                                                element.removeChild(node);
919                                        }
920                                }
921                        }
922                }
923                return true; //boolean
924        },
925       
926        save: function(/* object */ keywordArgs){
927                //      summary:
928                //              Save new and/or modified items (XML elements)
929                //      description:
930                //              'url' is used to save XML documents for new, modified and/or
931                //              deleted XML elements.
932                //      keywordArgs:
933                //              An object for callbacks
934                if(!keywordArgs){
935                        keywordArgs = {};
936                }
937                var i;
938                for(i = 0; i < this._modifiedItems.length; i++){
939                        this._saveItem(this._modifiedItems[i], keywordArgs, "PUT");
940                }
941                for(i = 0; i < this._newItems.length; i++){
942                        var item = this._newItems[i];
943                        if(item.element.parentNode){ // reparented
944                                this._newItems.splice(i, 1);
945                                i--;
946                                continue;
947                        }
948                        this._saveItem(this._newItems[i], keywordArgs, "POST");
949                }
950                for(i = 0; i < this._deletedItems.length; i++){
951                        this._saveItem(this._deletedItems[i], keywordArgs, "DELETE");
952                }
953        },
954
955        revert: function(){
956                // summary:
957                //      Invalidate changes (new and/or modified elements)
958                // returns:
959                //      True
960                this._newItems = [];
961                this._restoreItems(this._deletedItems);
962                this._deletedItems = [];
963                this._restoreItems(this._modifiedItems);
964                this._modifiedItems = [];
965                return true; //boolean
966        },
967       
968        isDirty: function(/* item? */ item){
969                //      summary:
970                //              Check whether an item is new, modified or deleted
971                //      description:
972                //              If 'item' is specified, true is returned if the item is new,
973                //              modified or deleted.
974                //              Otherwise, true is returned if there are any new, modified
975                //              or deleted items.
976                //      item:
977                //              An item (XML element) to check
978                //      returns:
979                //              True if an item or items are new, modified or deleted, otherwise
980                //              false
981                if(item){
982                        var element = this._getRootElement(item.element);
983                        return (this._getItemIndex(this._newItems, element) >= 0 ||
984                                this._getItemIndex(this._deletedItems, element) >= 0 ||
985                                this._getItemIndex(this._modifiedItems, element) >= 0); //boolean
986                }else{
987                        return (this._newItems.length > 0 ||
988                                this._deletedItems.length > 0 ||
989                                this._modifiedItems.length > 0); //boolean
990                }
991        },
992
993        _saveItem: function(item, keywordArgs, method){
994                var url;
995                var scope;
996                if(method === "PUT"){
997                        url = this._getPutUrl(item);
998                }else if(method === "DELETE"){
999                        url = this._getDeleteUrl(item);
1000                }else{ // POST
1001                        url = this._getPostUrl(item);
1002                }
1003                if(!url){
1004                        if(keywordArgs.onError){
1005                                scope = keywordArgs.scope || winUtil.global;
1006                                keywordArgs.onError.call(scope, new Error("No URL for saving content: " + this._getPostContent(item)));
1007                        }
1008                        return;
1009                }
1010
1011                var saveArgs = {
1012                        url: url,
1013                        method: (method || "POST"),
1014                        contentType: "text/xml",
1015                        handleAs: "xml"
1016                };
1017                var saveHandler;
1018                if(method === "PUT"){
1019                        saveArgs.putData = this._getPutContent(item);
1020                        saveHandler = xhr.put(saveArgs);
1021                }else if(method === "DELETE"){
1022                        saveHandler = xhr.del(saveArgs);
1023                }else{ // POST
1024                        saveArgs.postData = this._getPostContent(item);
1025                        saveHandler = xhr.post(saveArgs);
1026                }
1027                scope = (keywordArgs.scope || winUtil. global);
1028                var self = this;
1029                saveHandler.addCallback(function(data){
1030                        self._forgetItem(item);
1031                        if(keywordArgs.onComplete){
1032                                keywordArgs.onComplete.call(scope);
1033                        }
1034                });
1035                saveHandler.addErrback(function(error){
1036                        if(keywordArgs.onError){
1037                                keywordArgs.onError.call(scope, error);
1038                        }
1039                });
1040        },
1041
1042        _getPostUrl: function(item){
1043                //      summary:
1044                //              Generate a URL for post
1045                //      description:
1046                //              This default implementation just returns 'url'.
1047                //              Sub-classes may override this method for the custom URL.
1048                //      item:
1049                //              An item to save
1050                //      returns:
1051                //              A post URL
1052                return this.url; //string
1053        },
1054
1055        _getPutUrl: function(item){
1056                //      summary:
1057                //              Generate a URL for put
1058                //      description:
1059                //              This default implementation just returns 'url'.
1060                //              Sub-classes may override this method for the custom URL.
1061                //      item:
1062                //              An item to save
1063                //      returns:
1064                //              A put URL
1065                return this.url; //string
1066        },
1067
1068        _getDeleteUrl: function(item){
1069                //      summary:
1070                //              Generate a URL for delete
1071                //      description:
1072                //              This default implementation returns 'url' with 'keyAttribute'
1073                //              as a query string.
1074                //              Sub-classes may override this method for the custom URL based on
1075                //              changes (new, deleted, or modified).
1076                //      item:
1077                //              An item to delete
1078                //      returns:
1079                //              A delete URL
1080                var url = this.url;
1081                if(item && this.keyAttribute !== ""){
1082                        var value = this.getValue(item, this.keyAttribute);
1083                        if(value){
1084                                var key = this.keyAttribute.charAt(0) ==='@' ? this.keyAttribute.substring(1): this.keyAttribute;
1085                                url += url.indexOf('?') < 0 ? '?' : '&';
1086                                url += key + '=' + value;
1087                        }
1088                }
1089                return url;     //string
1090        },
1091
1092        _getPostContent: function(item){
1093                //      summary:
1094                //              Generate a content to post
1095                //      description:
1096                //              This default implementation generates an XML document for one
1097                //              (the first only) new or modified element.
1098                //              Sub-classes may override this method for the custom post content
1099                //              generation.
1100                //      item:
1101                //              An item to save
1102                //      returns:
1103                //              A post content
1104                return "<?xml version=\'1.0\'?>" + xmlParser.innerXML(item.element); //XML string
1105        },
1106
1107        _getPutContent: function(item){
1108                //      summary:
1109                //              Generate a content to put
1110                //      description:
1111                //              This default implementation generates an XML document for one
1112                //              (the first only) new or modified element.
1113                //              Sub-classes may override this method for the custom put content
1114                //              generation.
1115                //      item:
1116                //              An item to save
1117                //      returns:
1118                //              A post content
1119                return "<?xml version='1.0'?>" + xmlParser.innerXML(item.element); //XML string
1120        },
1121
1122/* internal API */
1123
1124        _getAttribute: function(tagName, attribute){
1125                if(this._attributeMap){
1126                        var key = tagName + "." + attribute;
1127                        var value = this._attributeMap[key];
1128                        if(value){
1129                                attribute = value;
1130                        }else{ // look for global attribute
1131                                value = this._attributeMap[attribute];
1132                                if(value){
1133                                        attribute = value;
1134                                }
1135                        }
1136                }
1137                return attribute; //object
1138        },
1139
1140        _getItem: function(element){
1141                try{
1142                        var q = null;
1143                        //Avoid function call if possible.
1144                        if(this.keyAttribute === ""){
1145                                q = this._getXPath(element);
1146                        }
1147                        return new XmlItem(element, this, q); //object
1148                }catch (e){
1149                        console.log(e);
1150                }
1151                return null;
1152        },
1153
1154        _getItemIndex: function(items, element){
1155                for(var i = 0; i < items.length; i++){
1156                        if(items[i].element === element){
1157                                return i; //int
1158                        }
1159                }
1160                return -1; //int
1161        },
1162
1163        _backupItem: function(item){
1164                var element = this._getRootElement(item.element);
1165                if(     this._getItemIndex(this._newItems, element) >= 0 ||
1166                        this._getItemIndex(this._modifiedItems, element) >= 0){
1167                        return; // new or already modified
1168                }
1169                if(element != item.element){
1170                        item = this._getItem(element);
1171                }
1172                item._backup = element.cloneNode(true);
1173                this._modifiedItems.push(item);
1174        },
1175
1176        _restoreItems: function(items){
1177
1178                array.forEach(items,function(item){
1179                        if(item._backup){
1180                                item.element = item._backup;
1181                                item._backup = null;
1182                        }
1183                },this);
1184        },
1185
1186        _forgetItem: function(item){
1187                var element = item.element;
1188                var index = this._getItemIndex(this._newItems, element);
1189                if(index >= 0){
1190                        this._newItems.splice(index, 1);
1191                }
1192                index = this._getItemIndex(this._deletedItems, element);
1193                if(index >= 0){
1194                        this._deletedItems.splice(index, 1);
1195                }
1196                index = this._getItemIndex(this._modifiedItems, element);
1197                if(index >= 0){
1198                        this._modifiedItems.splice(index, 1);
1199                }
1200        },
1201
1202        _getDocument: function(element){
1203                if(element){
1204                        return element.ownerDocument; //DOMDocument
1205                }else if(!this._document){
1206                        return xmlParser.parse(); // DOMDocument
1207                }
1208                return null; //null
1209        },
1210
1211        _getRootElement: function(element){
1212                while(element.parentNode){
1213                        element = element.parentNode;
1214                }
1215                return element; //DOMElement
1216        },
1217
1218        _getXPath: function(element){
1219                //      summary:
1220                //              A function to compute the xpath of a node in a DOM document.
1221                //      description:
1222                //              A function to compute the xpath of a node in a DOM document.  Used for
1223                //              Client side query handling and identity.
1224                var xpath = null;
1225                if(!this.sendQuery){
1226                        //xpath should be null for any server queries, as we don't have the entire
1227                        //XML dom to figure it out.
1228                        var node = element;
1229                        xpath = "";
1230                        while(node && node != element.ownerDocument){
1231                                var pos = 0;
1232                                var sibling = node;
1233                                var name = node.nodeName;
1234                                while(sibling){
1235                                        sibling = sibling.previousSibling;
1236                                        if(sibling && sibling.nodeName === name){
1237                                                pos++;
1238                                        }
1239                                }
1240                                var temp = "/" + name + "[" + pos + "]";
1241                                if(xpath){
1242                                        xpath = temp + xpath;
1243                                }else{
1244                                        xpath = temp;
1245                                }
1246                                node = node.parentNode;
1247                        }
1248                }
1249                return xpath; //string
1250        },
1251
1252        /*************************************
1253         * Dojo.data Identity implementation *
1254         *************************************/
1255        getIdentity: function(/* item */ item){
1256                //      summary:
1257                //              Returns a unique identifier for an item.
1258                //      item:
1259                //              The XML Item from the store from which to obtain its identifier.
1260                if(!this.isItem(item)){
1261                        throw new Error("dojox.data.XmlStore: Object supplied to getIdentity is not an item");
1262                }else{
1263                        var id = null;
1264                        if(this.sendQuery && this.keyAttribute !== ""){
1265                                id = this.getValue(item, this.keyAttribute).toString();
1266                        }else if(!this.serverQuery){
1267                                if(this.keyAttribute !== ""){
1268                                        id = this.getValue(item,this.keyAttribute).toString();
1269                                }else{
1270                                        //No specified identity, so return the dojo.query/xpath
1271                                        //for the node as fallback.
1272                                        id = item.q;
1273                                }
1274                        }
1275                        return id; //String.
1276                }
1277        },
1278
1279        getIdentityAttributes: function(/* item */ item){
1280                //      summary:
1281                //              Returns an array of attribute names that are used to generate the identity.
1282                //      description:
1283                //              For XmlStore, if sendQuery is false and no keyAttribute was set, then this function
1284                //              returns null, as xpath is used for the identity, which is not a public attribute of
1285                //              the item.  If sendQuery is true and keyAttribute is set, then this function
1286                //              returns an array of one attribute name: keyAttribute.   This means the server side
1287                //              implementation must apply a keyAttribute to a returned node that always allows
1288                //              it to be looked up again.
1289                //      item:
1290                //              The item from the store from which to obtain the array of public attributes that
1291                //              compose the identifier, if any.
1292                if(!this.isItem(item)){
1293                        throw new Error("dojox.data.XmlStore: Object supplied to getIdentity is not an item");
1294                }else{
1295                        if(this.keyAttribute !== ""){
1296                                return [this.keyAttribute]; //array
1297                        }else{
1298                                //Otherwise it's either using xpath (not an attribute), or the remote store
1299                                //doesn't support identity.
1300                                return null; //null
1301                        }
1302                }
1303        },
1304
1305
1306        fetchItemByIdentity: function(/* object */ keywordArgs){
1307                //      summary:
1308                //              See dojo.data.api.Identity.fetchItemByIdentity(keywordArgs)
1309                var handleDocument = null;
1310                var scope = null;
1311                var self = this;
1312                var url = null;
1313                var getArgs = null;
1314                var getHandler = null;
1315
1316                if(!self.sendQuery){
1317                        handleDocument = function(data){
1318                                if(data){
1319                                        if(self.keyAttribute !== ""){
1320                                                //We have a key attribute specified.  So ... we can process the items and locate the item
1321                                                //that contains a matching key attribute.  Its identity, as it were.
1322                                                var request = {};
1323                                                request.query={};
1324                                                request.query[self.keyAttribute] = keywordArgs.identity;
1325                                                request.queryOptions = {deep: true};
1326                                                var items = self._getItems(data,request);
1327                                                scope = keywordArgs.scope || winUtil.global;
1328                                                if(items.length === 1){
1329                                                        if(keywordArgs.onItem){
1330                                                                keywordArgs.onItem.call(scope, items[0]);
1331                                                        }
1332                                                }else if(items.length === 0){
1333                                                        if(keywordArgs.onItem){
1334                                                                keywordArgs.onItem.call(scope, null);
1335                                                        }
1336                                                }else{
1337                                                        if(keywordArgs.onError){
1338                                                                keywordArgs.onError.call(scope, new Error("Items array size for identity lookup greater than 1, invalid keyAttribute."));
1339                                                        }
1340                                                }
1341                                        }else{
1342                                                //Since dojo.query doesn't really support the functions needed
1343                                                //to do child node selection on IE well and since xpath support
1344                                                //is flakey across browsers, it's simpler to implement a
1345                                                //pseudo-xpath parser here.
1346                                                var qArgs = keywordArgs.identity.split("/");
1347                                                var i;
1348                                                var node = data;
1349                                                for(i = 0; i < qArgs.length; i++){
1350                                                        if(qArgs[i] && qArgs[i] !== ""){
1351                                                                var section = qArgs[i];
1352                                                                section = section.substring(0,section.length - 1);
1353                                                                var vals = section.split("[");
1354                                                                var tag = vals[0];
1355                                                                var index = parseInt(vals[1], 10);
1356                                                                var pos = 0;
1357                                                                if(node){
1358                                                                        var cNodes = node.childNodes;
1359                                                                        if(cNodes){
1360                                                                                var j;
1361                                                                                var foundNode = null;
1362                                                                                for(j = 0; j < cNodes.length; j++){
1363                                                                                        var pNode = cNodes[j];
1364                                                                                        if(pNode.nodeName === tag){
1365                                                                                                if(pos < index){
1366                                                                                                        pos++;
1367                                                                                                }else{
1368                                                                                                        foundNode = pNode;
1369                                                                                                        break;
1370                                                                                                }
1371                                                                                        }
1372                                                                                }
1373                                                                                if(foundNode){
1374                                                                                        node = foundNode;
1375                                                                                }else{
1376                                                                                        node = null;
1377                                                                                }
1378                                                                        }else{
1379                                                                                node = null;
1380                                                                        }
1381                                                                }else{
1382                                                                        break;
1383                                                                }
1384                                                        }
1385                                                }
1386                                                //Return what we found, if any.
1387                                                var item = null;
1388                                                if(node){
1389                                                        item = self._getItem(node);
1390                                                        if(item.element.parentNode){
1391                                                                item.element.parentNode.removeChild(item.element);
1392                                                        }
1393                                                }
1394                                                if(keywordArgs.onItem){
1395                                                        scope = keywordArgs.scope || winUtil.global;
1396                                                        keywordArgs.onItem.call(scope, item);
1397                                                }
1398                                        }
1399                                }
1400                        };
1401                        url = this._getFetchUrl(null);
1402                        getArgs = {
1403                                url: url,
1404                                handleAs: "xml",
1405                                preventCache: self.urlPreventCache
1406                        };
1407                        getHandler = xhr.get(getArgs);
1408                       
1409                        //Add in the callbacks for completion of data load.
1410                        getHandler.addCallback(handleDocument);
1411                        if(keywordArgs.onError){
1412                                getHandler.addErrback(function(error){
1413                                        var s = keywordArgs.scope || winUtil.global;
1414                                        keywordArgs.onError.call(s, error);
1415                                });
1416                        }
1417                }else{
1418                        //Server side querying, so need to pass the keyAttribute back to the server and let it return
1419                        //what it will.  It SHOULD be only one item.
1420                        if(self.keyAttribute !== ""){
1421                                var request = {query:{}};
1422                                request.query[self.keyAttribute] = keywordArgs.identity;
1423                                url = this._getFetchUrl(request);
1424                                handleDocument = function(data){
1425                                        var item = null;
1426                                        if(data){
1427                                                var items = self._getItems(data, {});
1428                                                if(items.length === 1){
1429                                                        item = items[0];
1430                                                }else{
1431                                                        if(keywordArgs.onError){
1432                                                                var scope = keywordArgs.scope || winUtil.global;
1433                                                                keywordArgs.onError.call(scope, new Error("More than one item was returned from the server for the denoted identity"));
1434                                                        }
1435                                                }
1436                                        }
1437                                        if(keywordArgs.onItem){
1438                                                scope = keywordArgs.scope || winUtil.global;
1439                                                keywordArgs.onItem.call(scope, item);
1440                                        }
1441                                };
1442
1443                                getArgs = {
1444                                        url: url,
1445                                        handleAs: "xml",
1446                                        preventCache: self.urlPreventCache
1447                                };
1448                                getHandler = xhr.get(getArgs);
1449
1450                                //Add in the callbacks for completion of data load.
1451                                getHandler.addCallback(handleDocument);
1452                                if(keywordArgs.onError){
1453                                        getHandler.addErrback(function(error){
1454                                                var s = keywordArgs.scope || winUtil.global;
1455                                                keywordArgs.onError.call(s, error);
1456                                        });
1457                                }
1458                        }else{
1459                                if(keywordArgs.onError){
1460                                        var s = keywordArgs.scope || winUtil.global;
1461                                        keywordArgs.onError.call(s, new Error("XmlStore is not told that the server to provides identity support.  No keyAttribute specified."));
1462                                }
1463                        }
1464                }
1465        }
1466});
1467
1468lang.extend(XmlStore,simpleFetch);
1469
1470return XmlStore;
1471});
Note: See TracBrowser for help on using the repository browser.