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

  • Property svn:executable set to *
File size: 13.3 KB
Line 
1define(["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/array", "dojo/_base/json","dojo/_base/window", "dojo/_base/sniff", "dojo/data/util/sorter", "dojo/data/util/filter", "./css"],
2 function(lang, declare, array, jsonUtil, winUtil, has, sorter, filter, css) {
3
4return declare("dojox.data.CssRuleStore", null, {
5        //      summary:
6        //              Basic store to display CSS information.
7        //      description:
8        //              The CssRuleStore allows users to get information about active CSS rules in the page running the CssRuleStore.
9        //              It can also filter out rules from specific stylesheets.  The attributes it exposes on rules are as follows:
10        //                      selector:                               The selector text.
11        //                      classes:                                An array of classes present in this selector.
12        //                      rule:                                   The actual DOM Rule object.
13        //                      style:                                  The actual DOM CSSStyleDeclaration object.
14        //                      cssText:                                The cssText string provided on the rule object.
15        //                      styleSheet:                             The originating DOM Stylesheet object.
16        //                      parentStyleSheet:               The parent stylesheet to the sheet this rule originates from.
17        //                      parentStyleSheetHref:   The href of the parent stylesheet.
18        //              AND every style attribute denoted as style.*, such as style.textAlign or style.backgroundColor
19
20        _storeRef: '_S',
21        _labelAttribute: 'selector', // text representation of the Item [label and identifier may need to stay due to method names]
22
23        _cache: null,
24
25        _browserMap: null,
26
27        _cName: "dojox.data.CssRuleStore",
28
29        constructor: function(/* Object */ keywordParameters){
30                // Initializes this store
31                if(keywordParameters){
32                        lang.mixin(this, keywordParameters);
33                }
34                this._cache = {};
35                this._allItems = null;
36                this._waiting = [];
37                this.gatherHandle = null;
38                var self = this;
39                // CSS files may not be finished loading by the time the store is constructed.  We need to
40                // give them a little time, so setting the stylesheet loading to retry every 250ms.
41                function gatherRules(){
42                        try{
43                                // Funkiness here is due to css that may still be loading.  This throws an DOM Access
44                                // error if css isnt completely loaded.
45                                self.context = css.determineContext(self.context);
46                                if(self.gatherHandle){
47                                        clearInterval(self.gatherHandle);
48                                        self.gatherHandle = null;
49                                }
50                                // Handle any fetches that have been queued while we've been waiting on the CSS files
51                                // to finish
52                                while(self._waiting.length){
53                                        var item = self._waiting.pop();
54                                        css.rules.forEach(item.forFunc, null, self.context);
55                                        item.finishFunc();
56                                }
57                        }catch(e){}
58                }
59                this.gatherHandle = setInterval(gatherRules,250);
60        },
61       
62        setContext: function(/* Array */ context){
63                // Sets the context in which queries are executed
64                // context: Array - Array of CSS string paths to execute queries within
65                if(context){
66                        this.close();
67                        this.context = css.determineContext(context);
68                }
69        },
70
71        getFeatures: function(){
72                //      summary:
73                //              See dojo.data.api.Read.getFeatures()
74                return {
75                        "dojo.data.api.Read" : true
76                };
77        },
78
79        isItem: function(item){
80                //      summary:
81                //              See dojo.data.api.Read.isItem()
82                if(item && item[this._storeRef] == this){
83                        return true;
84                }
85                return false;
86        },
87
88        hasAttribute: function(item, attribute){
89                //      summary:
90                //              See dojo.data.api.Read.hasAttribute()
91                this._assertIsItem(item);
92                this._assertIsAttribute(attribute);
93                var attrs = this.getAttributes(item);
94                if(array.indexOf(attrs, attribute) != -1){
95                        return true;
96                }
97                return false;
98        },
99
100        getAttributes: function(item){
101                //      summary:
102                //              See dojo.data.api.Read.getAttributes()
103                this._assertIsItem(item);
104                var attrs = ['selector', 'classes', 'rule', 'style', 'cssText', 'styleSheet', 'parentStyleSheet', 'parentStyleSheetHref'];
105                var style = item.rule.style;
106                if(style){
107                        var key;
108                        for(key in style){
109                                attrs.push("style." + key);
110                        }
111                }
112                return attrs;
113        },
114
115        getValue: function(item, attribute, defaultValue){
116                //      summary:
117                //              See dojo.data.api.Read.getValue()
118                var values = this.getValues(item, attribute);
119                var value = defaultValue;
120                if(values && values.length > 0){
121                        return values[0];
122                }
123                return defaultValue;
124        },
125
126        getValues: function(item, attribute){
127                //      summary:
128                //              See dojo.data.api.Read.getValues()
129                this._assertIsItem(item);
130                this._assertIsAttribute(attribute);
131                var value = null;
132                if(attribute === "selector"){
133                        value = item.rule["selectorText"];
134                        if(value && lang.isString(value)){
135                                value = value.split(",");
136                        }
137                }else if(attribute === "classes"){
138                        value = item.classes;
139                }else if(attribute === "rule"){
140                        value = item.rule.rule;
141                }else if(attribute === "style"){
142                        value = item.rule.style;
143                }else if(attribute === "cssText"){
144                        if(has("ie")){
145                                if(item.rule.style){
146                                        value = item.rule.style.cssText;
147                                        if(value){
148                                                value = "{ " + value.toLowerCase() + " }";
149                                        }
150                                }
151                        }else{
152                                value = item.rule.cssText;
153                                if(value){
154                                        value = value.substring(value.indexOf("{"), value.length);
155                                }
156                        }
157                }else if(attribute === "styleSheet"){
158                        value = item.rule.styleSheet;
159                }else if(attribute === "parentStyleSheet"){
160                        value = item.rule.parentStyleSheet;
161                }else if(attribute === "parentStyleSheetHref"){
162                        if(item.href){
163                                value = item.href;
164                        }
165                }else if(attribute.indexOf("style.") === 0){
166                        var attr = attribute.substring(attribute.indexOf("."), attribute.length);
167                        value = item.rule.style[attr];
168                }else{
169                        value = [];
170                }
171                if(value !== undefined){
172                        if(!lang.isArray(value)){
173                                value = [value];
174                        }
175                }
176                return value;
177        },
178
179        getLabel: function(item){
180                //      summary:
181                //              See dojo.data.api.Read.getLabel()
182                this._assertIsItem(item);
183                return this.getValue(item, this._labelAttribute);
184        },
185
186        getLabelAttributes: function(item){
187                //      summary:
188                //              See dojo.data.api.Read.getLabelAttributes()
189                return [this._labelAttribute];
190        },
191
192        containsValue: function(/* item */ item,
193                                                        /* attribute-name-string */ attribute,
194                                                        /* anything */ value){
195                //      summary:
196                //              See dojo.data.api.Read.containsValue()
197                var regexp = undefined;
198                if(typeof value === "string"){
199                        regexp = filter.patternToRegExp(value, false);
200                }
201                return this._containsValue(item, attribute, value, regexp); //boolean.
202        },
203
204        isItemLoaded: function(/* anything */ something){
205                //      summary:
206                //              See dojo.data.api.Read.isItemLoaded()
207                return this.isItem(something); //boolean
208        },
209
210        loadItem: function(/* object */ keywordArgs){
211                //      summary:
212                //              See dojo.data.api.Read.loadItem()
213                this._assertIsItem(keywordArgs.item);
214        },
215
216        fetch: function(request){
217                //      summary:
218                //              See dojo.data.api.Read.fetch()
219                request = request || {};
220                if(!request.store){
221                        request.store = this;
222                }
223
224                var scope = request.scope || winUtil.global;
225                if(this._pending && this._pending.length > 0){
226                        this._pending.push({request: request, fetch: true});
227                }else{
228                        this._pending = [{request: request, fetch: true}];
229                        this._fetch(request);
230                }
231                return request;
232        },
233
234        _fetch: function(request){
235                //      summary:
236                //              Populates the _allItems object with unique class names
237                var scope = request.scope || winUtil.global;
238                if(this._allItems === null){
239                        this._allItems = {};
240                        try{
241                                if(this.gatherHandle){
242                                        this._waiting.push({'forFunc': lang.hitch(this, this._handleRule), 'finishFunc': lang.hitch(this, this._handleReturn)});
243                                }else{
244                                        css.rules.forEach(lang.hitch(this, this._handleRule), null, this.context);
245                                        this._handleReturn();
246                                }
247                        }catch(e){
248                                if(request.onError){
249                                        request.onError.call(scope, e, request);
250                                }
251                        }
252                }else{
253                        this._handleReturn();
254                }
255        },
256
257        _handleRule: function(rule, styleSheet, href){
258                //      summary:
259                //              Handles the creation of an item based on the passed rule.  In this store, this implies
260                //              parsing out all available class names.
261                var selector = rule['selectorText'];
262                var s = selector.split(" ");
263                var classes = [];
264                for(var j=0; j<s.length; j++){
265                        var tmp = s[j];
266                        var first = tmp.indexOf('.');
267                        if(tmp && tmp.length > 0 && first !== -1){
268                                var last = tmp.indexOf(',') || tmp.indexOf('[');
269                                tmp = tmp.substring(first, ((last !== -1 && last > first)?last:tmp.length));
270                                classes.push(tmp);
271                        }
272                }
273                var item = {};
274                item.rule = rule;
275                item.styleSheet = styleSheet;
276                item.href = href;
277                item.classes = classes;
278                item[this._storeRef] = this;
279                if(!this._allItems[selector]){
280                        this._allItems[selector] = [];
281                }
282                this._allItems[selector].push(item);
283        },
284
285        _handleReturn: function(){
286                //      summary:
287                //              Handles the return from a fetching action.  Delegates requests to act on the resulting
288                //              item set to eitehr the _handleFetchReturn or _handleFetchByIdentityReturn depending on
289                //              where the request originated.
290                var _inProgress = [];
291               
292                var items = [];
293                var item = null;
294                for(var i in this._allItems){
295                        item = this._allItems[i];
296                        for(var j in item){
297                                items.push(item[j]);
298                        }
299                }
300
301                var requestInfo;
302                // One-level deep clone (can't use dojo.clone, since we don't want to clone all those store refs!)
303                while(this._pending.length){
304                        requestInfo = this._pending.pop();
305                        requestInfo.request._items = items;
306                        _inProgress.push(requestInfo);
307                }
308
309                while(_inProgress.length){
310                        requestInfo = _inProgress.pop();
311                        this._handleFetchReturn(requestInfo.request);
312                }
313        },
314
315        _handleFetchReturn: function(/*Request */ request){
316                //      summary:
317                //              Handles a fetchByIdentity request by finding the correct items.
318                var scope = request.scope || winUtil.global;
319                var items = [];
320                //Check to see if we've looked this query up before
321                //If so, just reuse it, much faster.  Only regen if query changes.
322                var cacheKey = "all";
323                var i;
324                if(request.query){
325                        cacheKey = jsonUtil.toJson(request.query);
326                }
327                if(this._cache[cacheKey]){
328                        items = this._cache[cacheKey];
329                }else if(request.query){
330                        for(i in request._items){
331                                var item = request._items[i];
332                                // Per https://bugs.webkit.org/show_bug.cgi?id=17935 , Safari 3.x always returns the selectorText
333                                // of a rule in full lowercase.
334                                var ignoreCase = (request.queryOptions ? request.queryOptions.ignoreCase : false);
335                                var regexpList = {};
336                                var key;
337                                var value;
338                                for(key in request.query){
339                                        value = request.query[key];
340                                        if(typeof value === "string"){
341                                                regexpList[key] = filter.patternToRegExp(value, ignoreCase);
342                                        }
343                                }
344                                var match = true;
345                                for(key in request.query){
346                                        value = request.query[key];
347                                        if(!this._containsValue(item, key, value, regexpList[key])){
348                                                match = false;
349                                        }
350                                }
351                                if(match){
352                                        items.push(item);
353                                }
354                        }
355                        this._cache[cacheKey] = items;
356                }else{
357                        for(i in request._items){
358                                items.push(request._items[i]);
359                        }
360                }
361                var total = items.length;
362
363                //Sort it if we need to.
364                if(request.sort){
365                        items.sort(sorter.createSortFunction(request.sort, this));
366                }
367                var start = 0;
368                var count = items.length;
369                if(request.start > 0 && request.start < items.length){
370                        start = request.start;
371                }
372                if(request.count && request.count){
373                        count = request.count;
374                }
375                var endIdx = start + count;
376                if(endIdx > items.length){
377                        endIdx = items.length;
378                }
379
380                items = items.slice(start, endIdx);
381
382                if(request.onBegin){
383                        request.onBegin.call(scope, total, request);
384                }
385                if(request.onItem){
386                        if(lang.isArray(items)){
387                                for(i = 0; i < items.length; i++){
388                                        request.onItem.call(scope, items[i], request);
389                                }
390                                if(request.onComplete){
391                                        request.onComplete.call(scope, null, request);
392                                }
393                        }
394                }else if(request.onComplete){
395                        request.onComplete.call(scope, items, request);
396                }
397                return request;
398        },
399
400        close: function(){
401                //      summary:
402                //              See dojo.data.api.Read.close()
403                //              Clears out the cache and allItems objects, meaning all future fetches will requery
404                //              the stylesheets.
405                this._cache = {};
406                this._allItems = null;
407        },
408       
409        _assertIsItem: function(/* item */ item){
410                //      summary:
411                //      This function tests whether the item passed in is indeed an item in the store.
412                //      item:
413                //              The item to test for being contained by the store.
414                if(!this.isItem(item)){
415                        throw new Error(this._cName + ": Invalid item argument.");
416                }
417        },
418
419        _assertIsAttribute: function(/* attribute-name-string */ attribute){
420                //      summary:
421                //              This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
422                //      attribute:
423                //              The attribute to test for being contained by the store.
424                if(typeof attribute !== "string"){
425                        throw new Error(this._cName + ": Invalid attribute argument.");
426                }
427        },
428
429        _containsValue: function(       /* item */ item,
430                                                                /* attribute-name-string */ attribute,
431                                                                /* anything */ value,
432                                                                /* RegExp?*/ regexp){
433                //      summary:
434                //              Internal function for looking at the values contained by the item.
435                //      description:
436                //              Internal function for looking at the values contained by the item.  This
437                //              function allows for denoting if the comparison should be case sensitive for
438                //              strings or not (for handling filtering cases where string case should not matter)
439                //
440                //      item:
441                //              The data item to examine for attribute values.
442                //      attribute:
443                //              The attribute to inspect.
444                //      value:
445                //              The value to match.
446                //      regexp:
447                //              Optional regular expression generated off value if value was of string type to handle wildcarding.
448                //              If present and attribute values are string, then it can be used for comparison instead of 'value'
449                return array.some(this.getValues(item, attribute), function(possibleValue){
450                        if(possibleValue !== null && !lang.isObject(possibleValue) && regexp){
451                                if(possibleValue.toString().match(regexp)){
452                                        return true; // Boolean
453                                }
454                        }else if(value === possibleValue){
455                                return true; // Boolean
456                        }
457                        return false;
458                });
459        }
460});
461});
Note: See TracBrowser for help on using the repository browser.