source: Dev/branches/rest-dojo-ui/client/dojox/data/HtmlStore.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: 16.4 KB
Line 
1define(["dojo/_base/declare", "dojo/_base/array", "dojo/_base/lang", "dojo/dom", "dojo/_base/xhr", "dojo/_base/window",
2                "dojo/data/util/simpleFetch", "dojo/data/util/filter", "dojox/xml/parser"],
3  function(declare, array, lang, dom, xhr, winUtil, simpleFetch, filter, xmlParser) {
4
5var HtmlStore = declare("dojox.data.HtmlStore", null, {
6        constructor: function(/*Object*/args){
7                //      summary:
8                //              Initializer for the HTML table store.
9                //      description:
10                //              The HtmlStore can be created in one of two ways: a) by parsing an existing
11                //              table or list DOM node on the current page or b) by referencing an external url and giving
12                //              the id of the table or list in that page.  The remote url will be parsed as an html page.
13                //
14                //              The HTML table or list should be of the following form:
15                //
16                //              |       <table id="myTable">
17                //              |               <thead>
18                //              |                       <tr>
19                //              |                               <th>Attribute1</th>
20                //              |                               <th>Attribute2</th>
21                //              |                       </tr>
22                //              |               </thead>
23                //              |               <tbody>
24                //              |                       <tr>
25                //              |                               <td>Value1.1</td>
26                //              |                               <td>Value1.2</td>
27                //              |                       </tr>
28                //              |                       <tr>
29                //              |                               <td>Value2.1</td>
30                //              |                               <td>Value2.2</td>
31                //              |                       </tr>
32                //              |               </tbody>
33                //              |       </table>
34                //
35                // -or-
36                //
37                //              |       <ul id="myUnorderedList">
38                //              |               <li>Value.1</li>
39                //              |               <li>Value.2</li>
40                //              |       </ul>
41                //
42                // -or-
43                //
44                //              |       <ol id="myOrderedList">
45                //              |               <li>Value.1</li>
46                //              |               <li>Value.2</li>
47                //              |       </ol>
48                //
49                //      args:
50                //              An anonymous object to initialize properties.  It expects the following values:
51                //              dataId: The id of the HTML table to use.
52                //              OR
53                //              url:    The url of the remote page to load
54                //              dataId: The id of the table element in the remote page
55                //              and the option:
56                //              trimWhitespace:  Trim off any surrounding whitespace from the headers (attribute
57                //                      names) and text content of the items in question.  Default is false for
58                //                      backwards compatibility.
59                if(args && "urlPreventCache" in args){
60                        this.urlPreventCache = args.urlPreventCache?true:false;
61                }
62                if(args && "trimWhitespace" in args){
63                        this.trimWhitespace = args.trimWhitespace?true:false;
64                }
65                if(args.url){
66                        if(!args.dataId){
67                                throw new Error("dojo.data.HtmlStore: Cannot instantiate using url without an id!");
68                        }
69                        this.url = args.url;
70                        this.dataId = args.dataId;
71                }else{
72                        if(args.dataId){
73                                this.dataId = args.dataId;
74                        }
75                }
76                if(args && "fetchOnCreate" in args){
77                        this.fetchOnCreate = args.fetchOnCreate?true:false;
78                }
79                if(this.fetchOnCreate && this.dataId){
80                        this.fetch();
81                }
82        },
83
84        // url: [public] string
85        //              The URL from which to load an HTML document for data loading
86        url: "",
87       
88        // dataId: [public] string
89        //              The id in the document for an element from which to get the data.
90        dataId: "",
91
92        // trimWhitepace: [public] boolean
93        //              Boolean flag to denote if the store should trim whitepace around
94        //              header and data content of a node.  This matters if reformatters
95        //              alter the white spacing around the tags.  The default is false for
96        //              backwards compat.
97        trimWhitespace: false,
98
99        // urlPreventCache: [public] boolean
100        //              Flag to denote if peventCache should be used on xhrGet calls.
101        urlPreventCache: false,
102       
103        // fetchOnCreate: [public] boolean
104        //              Flag to denote if it should try to load from a data id (nested in the page)
105        //              The moment the store is created, instead of waiting for first
106        //              fetch call.
107        fetchOnCreate: false,
108       
109        _indexItems: function(){
110                // summary:
111                //              Function to index items found under the id.
112                // tags:
113                //              private
114                this._getHeadings();
115                if(this._rootNode.rows){//tables
116                        if(this._rootNode.tBodies && this._rootNode.tBodies.length > 0){
117                                this._rootNode = this._rootNode.tBodies[0];
118                        }
119                        var i;
120                        for(i=0; i<this._rootNode.rows.length; i++){
121                                this._rootNode.rows[i]._ident = i+1;
122                        }
123                }else{//lists
124                        var c=1;
125                        for(i=0; i<this._rootNode.childNodes.length; i++){
126                                if(this._rootNode.childNodes[i].nodeType === 1){
127                                        this._rootNode.childNodes[i]._ident = c;
128                                        c++;
129                                }
130                        }
131                }
132        },
133
134        _getHeadings: function(){
135                //      summary:
136                //              Function to load the attribute names from the table header so that the
137                //              attributes (cells in a row), can have a reasonable name.
138                //      For list items, returns single implicit heading, ["name"]
139                this._headings = [];
140                if(this._rootNode.tHead){
141                        array.forEach(this._rootNode.tHead.rows[0].cells, lang.hitch(this, function(th){
142                                var text = xmlParser.textContent(th);
143                                this._headings.push(this.trimWhitespace?lang.trim(text):text);
144                        }));
145                }else{
146                        this._headings = ["name"];
147                }
148        },
149       
150        _getAllItems: function(){
151                //      summary:
152                //              Function to return all rows in the table as an array of items.
153                var items = [];
154                var i;
155                if(this._rootNode.rows){//table
156                        for(i=0; i<this._rootNode.rows.length; i++){
157                                items.push(this._rootNode.rows[i]);
158                        }
159                }else{ //list
160                        for(i=0; i<this._rootNode.childNodes.length; i++){
161                                if(this._rootNode.childNodes[i].nodeType === 1){
162                                        items.push(this._rootNode.childNodes[i]);
163                                }
164                        }
165                }
166                return items; //array
167        },
168       
169        _assertIsItem: function(/* item */ item){
170                //      summary:
171                //      This function tests whether the item passed in is indeed an item in the store.
172                //      item:
173                //              The item to test for being contained by the store.
174                if(!this.isItem(item)){
175                        throw new Error("dojo.data.HtmlStore: a function was passed an item argument that was not an item");
176                }
177        },
178
179        _assertIsAttribute: function(/* String */ attribute){
180                //      summary:
181                //      This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
182                //      attribute:
183                //              The attribute to test for being contained by the store.
184                //
185                //      returns:
186                //              Returns the index (column) that the attribute resides in the row.
187                if(typeof attribute !== "string"){
188                        throw new Error("dojo.data.HtmlStore: a function was passed an attribute argument that was not an attribute name string");
189                        return -1;
190                }
191                return array.indexOf(this._headings, attribute); //int
192        },
193
194/***************************************
195         dojo.data.api.Read API
196***************************************/
197       
198        getValue: function(     /* item */ item,
199                                                /* attribute-name-string */ attribute,
200                                                /* value? */ defaultValue){
201                //      summary:
202                //      See dojo.data.api.Read.getValue()
203                var values = this.getValues(item, attribute);
204                return (values.length > 0)?values[0]:defaultValue; //Object || int || Boolean
205        },
206
207        getValues: function(/* item */ item,
208                                                /* attribute-name-string */ attribute){
209                //      summary:
210                //              See dojo.data.api.Read.getValues()
211
212                this._assertIsItem(item);
213                var index = this._assertIsAttribute(attribute);
214                if(index>-1){
215                        var text;
216                        if(item.cells){
217                                text = xmlParser.textContent(item.cells[index]);
218                        }else{//return Value for lists
219                                text = xmlParser.textContent(item);
220                        }
221                        return [this.trimWhitespace?lang.trim(text):text];
222                }
223                return []; //Array
224        },
225
226        getAttributes: function(/* item */ item){
227                //      summary:
228                //              See dojo.data.api.Read.getAttributes()
229                this._assertIsItem(item);
230                var attributes = [];
231                for(var i=0; i<this._headings.length; i++){
232                        if(this.hasAttribute(item, this._headings[i]))
233                                attributes.push(this._headings[i]);
234                }
235                return attributes; //Array
236        },
237
238        hasAttribute: function( /* item */ item,
239                                                        /* attribute-name-string */ attribute){
240                //      summary:
241                //              See dojo.data.api.Read.hasAttribute()
242                return this.getValues(item, attribute).length > 0;
243        },
244
245        containsValue: function(/* item */ item,
246                                                        /* attribute-name-string */ attribute,
247                                                        /* anything */ value){
248                //      summary:
249                //              See dojo.data.api.Read.containsValue()
250                var regexp = undefined;
251                if(typeof value === "string"){
252                        regexp = filter.patternToRegExp(value, false);
253                }
254                return this._containsValue(item, attribute, value, regexp); //boolean.
255        },
256
257        _containsValue: function(       /* item */ item,
258                                                                /* attribute-name-string */ attribute,
259                                                                /* anything */ value,
260                                                                /* RegExp?*/ regexp){
261                //      summary:
262                //              Internal function for looking at the values contained by the item.
263                //      description:
264                //              Internal function for looking at the values contained by the item.  This
265                //              function allows for denoting if the comparison should be case sensitive for
266                //              strings or not (for handling filtering cases where string case should not matter)
267                //
268                //      item:
269                //              The data item to examine for attribute values.
270                //      attribute:
271                //              The attribute to inspect.
272                //      value:
273                //              The value to match.
274                //      regexp:
275                //              Optional regular expression generated off value if value was of string type to handle wildcarding.
276                //              If present and attribute values are string, then it can be used for comparison instead of 'value'
277                var values = this.getValues(item, attribute);
278                for(var i = 0; i < values.length; ++i){
279                        var possibleValue = values[i];
280                        if(typeof possibleValue === "string" && regexp){
281                                return (possibleValue.match(regexp) !== null);
282                        }else{
283                                //Non-string matching.
284                                if(value === possibleValue){
285                                        return true; // Boolean
286                                }
287                        }
288                }
289                return false; // Boolean
290        },
291
292        isItem: function(/* anything */ something){
293                //      summary:
294                //              See dojo.data.api.Read.isItem()
295                return something && dom.isDescendant(something, this._rootNode);
296        },
297
298        isItemLoaded: function(/* anything */ something){
299                //      summary:
300                //              See dojo.data.api.Read.isItemLoaded()
301                return this.isItem(something);
302        },
303
304        loadItem: function(/* Object */ keywordArgs){
305                //      summary:
306                //              See dojo.data.api.Read.loadItem()
307                this._assertIsItem(keywordArgs.item);
308        },
309       
310        _fetchItems: function(request, fetchHandler, errorHandler){
311                //      summary:
312                //              Fetch items (XML elements) that match to a query
313                //      description:
314                //              If '_fetchUrl' is specified, it is used to load an XML document
315                //              with a query string.
316                //              Otherwise and if 'url' is specified, the XML document is
317                //              loaded and list XML elements that match to a query (set of element
318                //              names and their text attribute values that the items to contain).
319                //              A wildcard, "*" can be used to query values to match all
320                //              occurrences.
321                //              If '_rootItem' is specified, it is used to fetch items.
322                //      request:
323                //              A request object
324                //      fetchHandler:
325                //              A function to call for fetched items
326                //      errorHandler:
327                //              A function to call on error
328               
329                if(this._rootNode){
330                        this._finishFetchItems(request, fetchHandler, errorHandler);
331                }else{
332                        if(!this.url){
333                                this._rootNode = dom.byId(this.dataId);
334                                this._indexItems();
335                                this._finishFetchItems(request, fetchHandler, errorHandler);
336                        }else{
337                                var getArgs = {
338                                                url: this.url,
339                                                handleAs: "text",
340                                                preventCache: this.urlPreventCache
341                                        };
342                                var self = this;
343                                var getHandler = xhr.get(getArgs);
344                                getHandler.addCallback(function(data){
345                                        var findNode = function(node, id){
346                                                if(node.id == id){
347                                                        return node; //object
348                                                }
349                                                if(node.childNodes){
350                                                        for(var i=0; i<node.childNodes.length; i++){
351                                                                var returnNode = findNode(node.childNodes[i], id);
352                                                                if(returnNode){
353                                                                        return returnNode; //object
354                                                                }
355                                                        }
356                                                }
357                                                return null; //null
358                                        }
359
360                                        var d = document.createElement("div");
361                                        d.innerHTML = data;
362                                        self._rootNode = findNode(d, self.dataId);
363                                        self._indexItems();
364                                        self._finishFetchItems(request, fetchHandler, errorHandler);
365                                });
366                                getHandler.addErrback(function(error){
367                                        errorHandler(error, request);
368                                });
369                        }
370                }
371        },
372       
373        _finishFetchItems: function(request, fetchHandler, errorHandler){
374                //      summary:
375                //              Internal function for processing the passed in request and locating the requested items.
376                var items = [];
377                var arrayOfAllItems = this._getAllItems();
378                if(request.query){
379                        var ignoreCase = request.queryOptions ? request.queryOptions.ignoreCase : false;
380                        items = [];
381
382                        //See if there are any string values that can be regexp parsed first to avoid multiple regexp gens on the
383                        //same value for each item examined.  Much more efficient.
384                        var regexpList = {};
385                        var key;
386                                                var value;
387                        for(key in request.query){
388                                value = request.query[key]+'';
389                                if(typeof value === "string"){
390                                        regexpList[key] = filter.patternToRegExp(value, ignoreCase);
391                                }
392                        }
393
394                        for(var i = 0; i < arrayOfAllItems.length; ++i){
395                                var match = true;
396                                var candidateItem = arrayOfAllItems[i];
397                                for(key in request.query){
398                                        value = request.query[key]+'';
399                                        if(!this._containsValue(candidateItem, key, value, regexpList[key])){
400                                                match = false;
401                                        }
402                                }
403                                if(match){
404                                        items.push(candidateItem);
405                                }
406                        }
407                        fetchHandler(items, request);
408                }else{
409                        // We want a copy to pass back in case the parent wishes to sort the array.  We shouldn't allow resort
410                        // of the internal list so that multiple callers can get listsand sort without affecting each other.
411                        if(arrayOfAllItems.length> 0){
412                                items = arrayOfAllItems.slice(0,arrayOfAllItems.length);
413                        }
414                        fetchHandler(items, request);
415                }
416        },
417
418        getFeatures: function(){
419                //      summary:
420                //              See dojo.data.api.Read.getFeatures()
421                return {
422                        'dojo.data.api.Read': true,
423                        'dojo.data.api.Identity': true
424                };
425        },
426       
427        close: function(/*dojo.data.api.Request || keywordArgs || null */ request){
428                //      summary:
429                //              See dojo.data.api.Read.close()
430                // nothing to do here!
431        },
432
433        getLabel: function(/* item */ item){
434                //      summary:
435                //              See dojo.data.api.Read.getLabel()
436                if(this.isItem(item)){
437                        if(item.cells){
438                                return "Item #" + this.getIdentity(item);
439                        }else{
440                                return this.getValue(item,"name");
441                        }
442                }
443                return undefined;
444        },
445
446        getLabelAttributes: function(/* item */ item){
447                //      summary:
448                //              See dojo.data.api.Read.getLabelAttributes()
449                if(item.cells){
450                        return null;
451                }else{
452                        return ["name"];
453                }
454        },
455
456/***************************************
457         dojo.data.api.Identity API
458***************************************/
459
460        getIdentity: function(/* item */ item){
461                //      summary:
462                //              See dojo.data.api.Identity.getIdentity()
463                this._assertIsItem(item);
464                if(this.hasAttribute(item, "name")){
465                        return this.getValue(item,"name");
466                }else{
467                        return item._ident;
468                }
469        },
470
471        getIdentityAttributes: function(/* item */ item){
472                 //     summary:
473                 //             See dojo.data.api.Identity.getIdentityAttributes()
474                 //Identity isn't taken from a public attribute.
475                 return null;
476        },
477
478        fetchItemByIdentity: function(keywordArgs){
479                //      summary:
480                //              See dojo.data.api.Identity.fetchItemByIdentity()
481                var identity = keywordArgs.identity;
482                var self = this;
483                var item = null;
484                var scope = null;
485                if(!this._rootNode){
486                        if(!this.url){
487                                this._rootNode = dom.byId(this.dataId);
488                                this._indexItems();
489                                if(self._rootNode.rows){ //Table
490                                        item = this._rootNode.rows[identity + 1];
491                                }else{ //Lists
492                                        for(var i = 0; i < self._rootNode.childNodes.length; i++){
493                                                if(self._rootNode.childNodes[i].nodeType === 1 && identity === xmlParser.textContent(self._rootNode.childNodes[i])){
494                                                        item = self._rootNode.childNodes[i];
495                                                }
496                                        }
497                                }
498                                if(keywordArgs.onItem){
499                                        scope = keywordArgs.scope?keywordArgs.scope:winUtil.global;
500                                        keywordArgs.onItem.call(scope, item);
501                                }
502
503                        }else{
504                                var getArgs = {
505                                                url: this.url,
506                                                handleAs: "text"
507                                        };
508                                var getHandler = xhr.get(getArgs);
509                                getHandler.addCallback(function(data){
510                                        var findNode = function(node, id){
511                                                if(node.id == id){
512                                                        return node; //object
513                                                }
514                                                if(node.childNodes){
515                                                        for(var i=0; i<node.childNodes.length; i++){
516                                                                var returnNode = findNode(node.childNodes[i], id);
517                                                                if(returnNode){
518                                                                        return returnNode; //object
519                                                                }
520                                                        }
521                                                }
522                                                return null; //null
523                                        }
524                                        var d = document.createElement("div");
525                                        d.innerHTML = data;
526                                        self._rootNode = findNode(d, self.dataId);
527                                        self._indexItems();
528                                        if(self._rootNode.rows && identity <= self._rootNode.rows.length){ //Table
529                                                item = self._rootNode.rows[identity-1];
530                                        }else{ //List
531                                                for(var i = 0; i < self._rootNode.childNodes.length; i++){
532                                                        if(self._rootNode.childNodes[i].nodeType === 1 && identity === xmlParser.textContent(self._rootNode.childNodes[i])){
533                                                                        item = self._rootNode.childNodes[i];
534                                                                        break;
535                                                        }
536                                                }
537                                        }
538                                        if(keywordArgs.onItem){
539                                                scope = keywordArgs.scope?keywordArgs.scope:winUtil.global;
540                                                keywordArgs.onItem.call(scope, item);
541                                        }
542                                });
543                                getHandler.addErrback(function(error){
544                                        if(keywordArgs.onError){
545                                                scope = keywordArgs.scope?keywordArgs.scope:winUtil.global;
546                                                keywordArgs.onError.call(scope, error);
547
548                                        }
549                                });
550                        }
551                }else{
552                        if(this._rootNode.rows[identity+1]){
553                                item = this._rootNode.rows[identity+1];
554                                if(keywordArgs.onItem){
555                                        scope = keywordArgs.scope?keywordArgs.scope:winUtil.global;
556                                        keywordArgs.onItem.call(scope, item);
557                                }
558                        }
559                }
560        }
561});
562lang.extend(HtmlStore, simpleFetch);
563return HtmlStore;
564});
Note: See TracBrowser for help on using the repository browser.