source: Dev/trunk/src/client/dojox/data/XmlStore.js @ 529

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

Added Dojo 1.9.3 release.

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