463 lines
13 KiB
JavaScript
463 lines
13 KiB
JavaScript
//>>built
|
|
define("dojox/data/CssRuleStore", ["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"],
|
|
function(lang, declare, array, jsonUtil, winUtil, has, sorter, filter, css) {
|
|
|
|
return 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){
|
|
lang.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 = 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();
|
|
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 = 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(array.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 && lang.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(has("ie")){
|
|
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(!lang.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 = 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 || winUtil.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 || winUtil.global;
|
|
if(this._allItems === null){
|
|
this._allItems = {};
|
|
try{
|
|
if(this.gatherHandle){
|
|
this._waiting.push({'forFunc': lang.hitch(this, this._handleRule), 'finishFunc': lang.hitch(this, this._handleReturn)});
|
|
}else{
|
|
css.rules.forEach(lang.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<s.length; j++){
|
|
var tmp = s[j];
|
|
var first = tmp.indexOf('.');
|
|
if(tmp && tmp.length > 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 || winUtil.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 = jsonUtil.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 = (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] = 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(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(lang.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 array.some(this.getValues(item, attribute), function(possibleValue){
|
|
if(possibleValue !== null && !lang.isObject(possibleValue) && regexp){
|
|
if(possibleValue.toString().match(regexp)){
|
|
return true; // Boolean
|
|
}
|
|
}else if(value === possibleValue){
|
|
return true; // Boolean
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
});
|
|
});
|