dojo.provide("dojox.data.CssRuleStore"); dojo.require("dojo.data.util.filter"); dojo.require("dojo.data.util.sorter"); dojo.require("dojox.data.css"); dojo.declare("dojox.data.CssRuleStore", null, { // summary: // Basic store to display CSS information. // description: // The CssRuleStore allows users to get information about active CSS rules in the page running the CssRuleStore. // It can also filter out rules from specific stylesheets. The attributes it exposes on rules are as follows: // selector: The selector text. // classes: An array of classes present in this selector. // rule: The actual DOM Rule object. // style: The actual DOM CSSStyleDeclaration object. // cssText: The cssText string provided on the rule object. // styleSheet: The originating DOM Stylesheet object. // parentStyleSheet: The parent stylesheet to the sheet this rule originates from. // parentStyleSheetHref: The href of the parent stylesheet. // AND every style attribute denoted as style.*, such as style.textAlign or style.backgroundColor _storeRef: '_S', _labelAttribute: 'selector', // text representation of the Item [label and identifier may need to stay due to method names] _cache: null, _browserMap: null, _cName: "dojox.data.CssRuleStore", constructor: function(/* Object */ keywordParameters){ // Initializes this store if(keywordParameters){ dojo.mixin(this, keywordParameters); } this._cache = {}; this._allItems = null; this._waiting = []; this.gatherHandle = null; var self = this; // CSS files may not be finished loading by the time the store is constructed. We need to // give them a little time, so setting the stylesheet loading to retry every 250ms. function gatherRules(){ try { // Funkiness here is due to css that may still be loading. This throws an DOM Access // error if css isnt completely loaded. self.context = dojox.data.css.determineContext(self.context); if(self.gatherHandle){ clearInterval(self.gatherHandle); self.gatherHandle = null; } // Handle any fetches that have been queued while we've been waiting on the CSS files // to finish while(self._waiting.length){ var item = self._waiting.pop(); dojox.data.css.rules.forEach(item.forFunc, null, self.context); item.finishFunc(); } }catch(e){} } this.gatherHandle = setInterval(gatherRules,250); }, setContext: function(/* Array */ context){ // Sets the context in which queries are executed // context: Array - Array of CSS string paths to execute queries within if(context){ this.close(); this.context = dojox.data.css.determineContext(context); } }, getFeatures: function(){ // summary: // See dojo.data.api.Read.getFeatures() return { "dojo.data.api.Read" : true }; }, isItem: function(item){ // summary: // See dojo.data.api.Read.isItem() if(item && item[this._storeRef] == this){ return true; } return false; }, hasAttribute: function(item, attribute){ // summary: // See dojo.data.api.Read.hasAttribute() this._assertIsItem(item); this._assertIsAttribute(attribute); var attrs = this.getAttributes(item); if(dojo.indexOf(attrs, attribute) != -1) { return true; } return false; }, getAttributes: function(item){ // summary: // See dojo.data.api.Read.getAttributes() this._assertIsItem(item); var attrs = ['selector', 'classes', 'rule', 'style', 'cssText', 'styleSheet', 'parentStyleSheet', 'parentStyleSheetHref']; var style = item.rule.style; if(style){ var key; for(key in style){ attrs.push("style." + key); } } return attrs; }, getValue: function(item, attribute, defaultValue){ // summary: // See dojo.data.api.Read.getValue() var values = this.getValues(item, attribute); var value = defaultValue; if(values && values.length > 0){ return values[0]; } return defaultValue; }, getValues: function(item, attribute){ // summary: // See dojo.data.api.Read.getValues() this._assertIsItem(item); this._assertIsAttribute(attribute); var value = null; if(attribute === "selector"){ value = item.rule["selectorText"]; if(value && dojo.isString(value)){ value = value.split(","); } }else if(attribute === "classes"){ value = item.classes; }else if(attribute === "rule"){ value = item.rule.rule; }else if(attribute === "style"){ value = item.rule.style; }else if(attribute === "cssText"){ if (dojo.isIE) { if(item.rule.style){ value = item.rule.style.cssText; if(value){ value = "{ " + value.toLowerCase() + " }"; } } }else{ value = item.rule.cssText; if(value){ value = value.substring(value.indexOf("{"), value.length); } } }else if(attribute === "styleSheet"){ value = item.rule.styleSheet; }else if(attribute === "parentStyleSheet"){ value = item.rule.parentStyleSheet; }else if(attribute === "parentStyleSheetHref"){ if(item.href){ value = item.href; } }else if(attribute.indexOf("style.") === 0){ var attr = attribute.substring(attribute.indexOf("."), attribute.length); value = item.rule.style[attr]; }else{ value = []; } if(value !== undefined){ if(!dojo.isArray(value)){ value = [value]; } } return value; }, getLabel: function(item){ // summary: // See dojo.data.api.Read.getLabel() this._assertIsItem(item); return this.getValue(item, this._labelAttribute); }, getLabelAttributes: function(item){ // summary: // See dojo.data.api.Read.getLabelAttributes() return [this._labelAttribute]; }, containsValue: function(/* item */ item, /* attribute-name-string */ attribute, /* anything */ value){ // summary: // See dojo.data.api.Read.containsValue() var regexp = undefined; if(typeof value === "string"){ regexp = dojo.data.util.filter.patternToRegExp(value, false); } return this._containsValue(item, attribute, value, regexp); //boolean. }, isItemLoaded: function(/* anything */ something){ // summary: // See dojo.data.api.Read.isItemLoaded() return this.isItem(something); //boolean }, loadItem: function(/* object */ keywordArgs){ // summary: // See dojo.data.api.Read.loadItem() this._assertIsItem(keywordArgs.item); }, fetch: function(request){ // summary: // See dojo.data.api.Read.fetch() request = request || {}; if(!request.store){ request.store = this; } var scope = request.scope || dojo.global; if(this._pending && this._pending.length > 0){ this._pending.push({request: request, fetch: true}); }else{ this._pending = [{request: request, fetch: true}]; this._fetch(request); } return request; }, _fetch: function(request){ // summary: // Populates the _allItems object with unique class names var scope = request.scope || dojo.global; if(this._allItems === null){ this._allItems = {}; try{ if(this.gatherHandle){ this._waiting.push({'forFunc': dojo.hitch(this, this._handleRule), 'finishFunc': dojo.hitch(this, this._handleReturn)}); }else{ dojox.data.css.rules.forEach(dojo.hitch(this, this._handleRule), null, this.context); this._handleReturn(); } }catch(e){ if(request.onError){ request.onError.call(scope, e, request); } } }else{ this._handleReturn(); } }, _handleRule: function(rule, styleSheet, href){ // summary: // Handles the creation of an item based on the passed rule. In this store, this implies // parsing out all available class names. var selector = rule['selectorText']; var s = selector.split(" "); var classes = []; for(var j=0; j 0 && first !== -1){ var last = tmp.indexOf(',') || tmp.indexOf('['); tmp = tmp.substring(first, ((last !== -1 && last > first)?last:tmp.length)); classes.push(tmp); } } var item = {}; item.rule = rule; item.styleSheet = styleSheet; item.href = href; item.classes = classes; item[this._storeRef] = this; if(!this._allItems[selector]){ this._allItems[selector] = []; } this._allItems[selector].push(item); }, _handleReturn: function(){ // summary: // Handles the return from a fetching action. Delegates requests to act on the resulting // item set to eitehr the _handleFetchReturn or _handleFetchByIdentityReturn depending on // where the request originated. var _inProgress = []; var items = []; var item = null; for(var i in this._allItems){ item = this._allItems[i]; for(var j in item){ items.push(item[j]); } } var requestInfo; // One-level deep clone (can't use dojo.clone, since we don't want to clone all those store refs!) while(this._pending.length){ requestInfo = this._pending.pop(); requestInfo.request._items = items; _inProgress.push(requestInfo); } while(_inProgress.length){ requestInfo = _inProgress.pop(); this._handleFetchReturn(requestInfo.request); } }, _handleFetchReturn: function(/*Request */ request){ // summary: // Handles a fetchByIdentity request by finding the correct items. var scope = request.scope || dojo.global; var items = []; //Check to see if we've looked this query up before //If so, just reuse it, much faster. Only regen if query changes. var cacheKey = "all"; var i; if(request.query){ cacheKey = dojo.toJson(request.query); } if(this._cache[cacheKey]){ items = this._cache[cacheKey]; }else if(request.query){ for(i in request._items){ var item = request._items[i]; // Per https://bugs.webkit.org/show_bug.cgi?id=17935 , Safari 3.x always returns the selectorText // of a rule in full lowercase. var ignoreCase = dojo.isWebKit ? true : (request.queryOptions ? request.queryOptions.ignoreCase : false); var regexpList = {}; var key; var value; for(key in request.query){ value = request.query[key]; if(typeof value === "string"){ regexpList[key] = dojo.data.util.filter.patternToRegExp(value, ignoreCase); } } var match = true; for(key in request.query){ value = request.query[key]; if(!this._containsValue(item, key, value, regexpList[key])){ match = false; } } if(match){ items.push(item); } } this._cache[cacheKey] = items; }else{ for(i in request._items){ items.push(request._items[i]); } } var total = items.length; //Sort it if we need to. if(request.sort){ items.sort(dojo.data.util.sorter.createSortFunction(request.sort, this)); } var start = 0; var count = items.length; if(request.start > 0 && request.start < items.length){ start = request.start; } if(request.count && request.count){ count = request.count; } var endIdx = start + count; if(endIdx > items.length){ endIdx = items.length; } items = items.slice(start, endIdx); if(request.onBegin){ request.onBegin.call(scope, total, request); } if(request.onItem){ if(dojo.isArray(items)){ for(i = 0; i < items.length; i++){ request.onItem.call(scope, items[i], request); } if(request.onComplete){ request.onComplete.call(scope, null, request); } } }else if(request.onComplete){ request.onComplete.call(scope, items, request); } return request; }, close: function(){ // summary: // See dojo.data.api.Read.close() // Clears out the cache and allItems objects, meaning all future fetches will requery // the stylesheets. this._cache = {}; this._allItems = null; }, _assertIsItem: function(/* item */ item){ // summary: // This function tests whether the item passed in is indeed an item in the store. // item: // The item to test for being contained by the store. if(!this.isItem(item)){ throw new Error(this._cName + ": Invalid item argument."); } }, _assertIsAttribute: function(/* attribute-name-string */ attribute){ // summary: // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store. // attribute: // The attribute to test for being contained by the store. if(typeof attribute !== "string"){ throw new Error(this._cName + ": Invalid attribute argument."); } }, _containsValue: function( /* item */ item, /* attribute-name-string */ attribute, /* anything */ value, /* RegExp?*/ regexp){ // summary: // Internal function for looking at the values contained by the item. // description: // Internal function for looking at the values contained by the item. This // function allows for denoting if the comparison should be case sensitive for // strings or not (for handling filtering cases where string case should not matter) // // item: // The data item to examine for attribute values. // attribute: // The attribute to inspect. // value: // The value to match. // regexp: // Optional regular expression generated off value if value was of string type to handle wildcarding. // If present and attribute values are string, then it can be used for comparison instead of 'value' return dojo.some(this.getValues(item, attribute), function(possibleValue){ if(possibleValue !== null && !dojo.isObject(possibleValue) && regexp){ if(possibleValue.toString().match(regexp)){ return true; // Boolean } }else if(value === possibleValue){ return true; // Boolean } return false; }); } });