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