source: Dev/trunk/src/client/dojox/data/CssRuleStore.js @ 532

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

Added Dojo 1.9.3 release.

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