CssRuleStore.js 14 KB

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