CssRuleStore.js 13 KB

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