PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/List.js

https://github.com/revin/dgrid
JavaScript | 854 lines | 537 code | 98 blank | 219 comment | 141 complexity | f80ff14e6d389848da7a8b7dd9cbb395 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. define(["dojo/_base/kernel", "dojo/_base/declare", "dojo/on", "dojo/has", "./util/misc", "dojo/has!touch?./TouchScroll", "xstyle/has-class", "put-selector/put", "dojo/_base/sniff", "xstyle/css!./css/dgrid.css"],
  2. function(kernel, declare, listen, has, miscUtil, TouchScroll, hasClass, put){
  3. // Add user agent/feature CSS classes
  4. hasClass("mozilla", "opera", "webkit", "ie", "ie-6", "ie-6-7", "quirks", "no-quirks", "touch");
  5. var oddClass = "dgrid-row-odd",
  6. evenClass = "dgrid-row-even",
  7. scrollbarWidth, scrollbarHeight;
  8. function byId(id){
  9. return document.getElementById(id);
  10. }
  11. function getScrollbarSize(node, dimension){
  12. // Used by has tests for scrollbar width/height
  13. var body = document.body,
  14. size;
  15. put(body, node, ".dgrid-scrollbar-measure");
  16. size = node["offset" + dimension] - node["client" + dimension];
  17. put(node, "!dgrid-scrollbar-measure");
  18. body.removeChild(node);
  19. return size;
  20. }
  21. has.add("dom-scrollbar-width", function(global, doc, element){
  22. return getScrollbarSize(element, "Width");
  23. });
  24. has.add("dom-scrollbar-height", function(global, doc, element){
  25. return getScrollbarSize(element, "Height");
  26. });
  27. // var and function for autogenerating ID when one isn't provided
  28. var autogen = 0;
  29. function generateId(){
  30. return "dgrid_" + autogen++;
  31. }
  32. // common functions for class and className setters/getters
  33. // (these are run in instance context)
  34. var spaceRx = / +/g;
  35. function setClass(cls){
  36. // Format input appropriately for use with put...
  37. var putClass = cls ? "." + cls.replace(spaceRx, ".") : "";
  38. // Remove any old classes, and add new ones.
  39. if(this._class){
  40. putClass = "!" + this._class.replace(spaceRx, "!") + putClass;
  41. }
  42. put(this.domNode, putClass);
  43. // Store for later retrieval/removal.
  44. this._class = cls;
  45. }
  46. function getClass(){
  47. return this._class;
  48. }
  49. // window resize event handler, run in context of List instance
  50. var winResizeHandler = has("ie") < 7 && !has("quirks") ? function(){
  51. // IE6 triggers window.resize on any element resize;
  52. // avoid useless calls (and infinite loop if height: auto).
  53. // The measurement logic here is based on dojo/window logic.
  54. var root, w, h, dims;
  55. if(!this._started){ return; } // no sense calling resize yet
  56. root = document.documentElement;
  57. w = root.clientWidth;
  58. h = root.clientHeight;
  59. dims = this._prevWinDims || [];
  60. if(dims[0] !== w || dims[1] !== h){
  61. this.resize();
  62. this._prevWinDims = [w, h];
  63. }
  64. } :
  65. function(){
  66. if(this._started){ this.resize(); }
  67. };
  68. return declare(TouchScroll ? TouchScroll : null, {
  69. tabableHeader: false,
  70. // showHeader: Boolean
  71. // Whether to render header (sub)rows.
  72. showHeader: false,
  73. // showFooter: Boolean
  74. // Whether to render footer area. Extensions which display content
  75. // in the footer area should set this to true.
  76. showFooter: false,
  77. // maintainOddEven: Boolean
  78. // Whether to maintain the odd/even classes when new rows are inserted.
  79. // This can be disabled to improve insertion performance if odd/even styling is not employed.
  80. maintainOddEven: true,
  81. // cleanAddedRules: Boolean
  82. // Whether to track rules added via the addCssRule method to be removed
  83. // when the list is destroyed. Note this is effective at the time of
  84. // the call to addCssRule, not at the time of destruction.
  85. cleanAddedRules: true,
  86. postscript: function(params, srcNodeRef){
  87. // perform setup and invoke create in postScript to allow descendants to
  88. // perform logic before create/postCreate happen (a la dijit/_WidgetBase)
  89. var grid = this;
  90. (this._Row = function(id, object, element){
  91. this.id = id;
  92. this.data = object;
  93. this.element = element;
  94. }).prototype.remove = function(){
  95. grid.removeRow(this.element);
  96. };
  97. if(srcNodeRef){
  98. // normalize srcNodeRef and store on instance during create process.
  99. // Doing this in postscript is a bit earlier than dijit would do it,
  100. // but allows subclasses to access it pre-normalized during create.
  101. this.srcNodeRef = srcNodeRef =
  102. srcNodeRef.nodeType ? srcNodeRef : byId(srcNodeRef);
  103. }
  104. this.create(params, srcNodeRef);
  105. },
  106. listType: "list",
  107. create: function(params, srcNodeRef){
  108. var domNode = this.domNode = srcNodeRef || put("div"),
  109. cls;
  110. if(params){
  111. this.params = params;
  112. declare.safeMixin(this, params);
  113. // Check for initial class or className in params or on domNode
  114. cls = params["class"] || params.className || domNode.className;
  115. // handle sort param - TODO: revise @ 1.0 when _sort -> sort
  116. this._sort = params.sort || [];
  117. delete this.sort; // ensure back-compat method isn't shadowed
  118. }else{
  119. this._sort = [];
  120. }
  121. // ensure arrays and hashes are initialized
  122. this.observers = [];
  123. this._listeners = [];
  124. this._rowIdToObject = {};
  125. this.postMixInProperties && this.postMixInProperties();
  126. // Apply id to widget and domNode,
  127. // from incoming node, widget params, or autogenerated.
  128. this.id = domNode.id = domNode.id || this.id || generateId();
  129. // Perform initial rendering, and apply classes if any were specified.
  130. this.buildRendering();
  131. if(cls){ setClass.call(this, cls); }
  132. this.postCreate && this.postCreate();
  133. // remove srcNodeRef instance property post-create
  134. delete this.srcNodeRef;
  135. // to preserve "it just works" behavior, call startup if we're visible
  136. if(this.domNode.offsetHeight){
  137. this.startup();
  138. }
  139. },
  140. buildRendering: function(){
  141. var domNode = this.domNode,
  142. self = this,
  143. headerNode, spacerNode, bodyNode, footerNode, isRTL;
  144. // Detect RTL on html/body nodes; taken from dojo/dom-geometry
  145. isRTL = this.isRTL = (document.body.dir || document.documentElement.dir ||
  146. document.body.style.direction).toLowerCase() == "rtl";
  147. // Clear out className (any pre-applied classes will be re-applied via the
  148. // class / className setter), then apply standard classes/attributes
  149. domNode.className = "";
  150. put(domNode, "[role=grid].ui-widget.dgrid.dgrid-" + this.listType);
  151. // Place header node (initially hidden if showHeader is false).
  152. headerNode = this.headerNode = put(domNode,
  153. "div.dgrid-header.dgrid-header-row.ui-widget-header" +
  154. (this.showHeader ? "" : ".dgrid-header-hidden"));
  155. if(has("quirks") || has("ie") < 8){
  156. spacerNode = put(domNode, "div.dgrid-spacer");
  157. }
  158. bodyNode = this.bodyNode = put(domNode, "div.dgrid-scroller");
  159. // firefox 4 until at least 10 adds overflow: auto elements to the tab index by default for some
  160. // reason; force them to be not tabbable
  161. bodyNode.tabIndex = -1;
  162. this.headerScrollNode = put(domNode, "div.dgrid-header-scroll.dgrid-scrollbar-width.ui-widget-header");
  163. // Place footer node (initially hidden if showFooter is false).
  164. footerNode = this.footerNode = put("div.dgrid-footer" +
  165. (this.showFooter ? "" : ".dgrid-footer-hidden"));
  166. put(domNode, footerNode);
  167. if(isRTL){
  168. domNode.className += " dgrid-rtl" + (has("webkit") ? "" : " dgrid-rtl-nonwebkit");
  169. }
  170. listen(bodyNode, "scroll", function(event){
  171. if(self.showHeader){
  172. // keep the header aligned with the body
  173. headerNode.scrollLeft = event.scrollLeft || bodyNode.scrollLeft;
  174. }
  175. // re-fire, since browsers are not consistent about propagation here
  176. event.stopPropagation();
  177. listen.emit(domNode, "scroll", {scrollTarget: bodyNode});
  178. });
  179. this.configStructure();
  180. this.renderHeader();
  181. this.contentNode = this.touchNode = put(this.bodyNode, "div.dgrid-content.ui-widget-content");
  182. // add window resize handler, with reference for later removal if needed
  183. this._listeners.push(this._resizeHandle = listen(window, "resize",
  184. miscUtil.throttleDelayed(winResizeHandler, this)));
  185. },
  186. startup: function(){
  187. // summary:
  188. // Called automatically after postCreate if the component is already
  189. // visible; otherwise, should be called manually once placed.
  190. if(this._started){ return; } // prevent double-triggering
  191. this.inherited(arguments);
  192. this._started = true;
  193. this.resize();
  194. // apply sort (and refresh) now that we're ready to render
  195. this.set("sort", this._sort);
  196. },
  197. configStructure: function(){
  198. // does nothing in List, this is more of a hook for the Grid
  199. },
  200. resize: function(){
  201. var
  202. bodyNode = this.bodyNode,
  203. headerNode = this.headerNode,
  204. footerNode = this.footerNode,
  205. headerHeight = headerNode.offsetHeight,
  206. footerHeight = this.showFooter ? footerNode.offsetHeight : 0,
  207. quirks = has("quirks") || has("ie") < 7;
  208. this.headerScrollNode.style.height = bodyNode.style.marginTop = headerHeight + "px";
  209. bodyNode.style.marginBottom = footerHeight + "px";
  210. if(quirks){
  211. // in IE6 and quirks mode, the "bottom" CSS property is ignored.
  212. // We guard against negative values in case of issues with external CSS.
  213. bodyNode.style.height = ""; // reset first
  214. bodyNode.style.height =
  215. Math.max((this.domNode.offsetHeight - headerHeight - footerHeight), 0) + "px";
  216. if (footerHeight) {
  217. // Work around additional glitch where IE 6 / quirks fails to update
  218. // the position of the bottom-aligned footer; this jogs its memory.
  219. footerNode.style.bottom = '1px';
  220. setTimeout(function(){ footerNode.style.bottom = ''; }, 0);
  221. }
  222. }
  223. if(!scrollbarWidth){
  224. // Measure the browser's scrollbar width using a DIV we'll delete right away
  225. scrollbarWidth = has("dom-scrollbar-width");
  226. scrollbarHeight = has("dom-scrollbar-height");
  227. // Avoid issues with certain widgets inside in IE7, and
  228. // ColumnSet scroll issues with all supported IE versions
  229. if(has("ie")){
  230. scrollbarWidth++;
  231. scrollbarHeight++;
  232. }
  233. // add rules that can be used where scrollbar width/height is needed
  234. miscUtil.addCssRule(".dgrid-scrollbar-width", "width: " + scrollbarWidth + "px");
  235. miscUtil.addCssRule(".dgrid-scrollbar-height", "height: " + scrollbarHeight + "px");
  236. if(scrollbarWidth != 17 && !quirks){
  237. // for modern browsers, we can perform a one-time operation which adds
  238. // a rule to account for scrollbar width in all grid headers.
  239. miscUtil.addCssRule(".dgrid-header", "right: " + scrollbarWidth + "px");
  240. // add another for RTL grids
  241. miscUtil.addCssRule(".dgrid-rtl-nonwebkit .dgrid-header", "left: " + scrollbarWidth + "px");
  242. }
  243. }
  244. if(quirks){
  245. // old IE doesn't support left + right + width:auto; set width directly
  246. headerNode.style.width = bodyNode.clientWidth + "px";
  247. setTimeout(function(){
  248. // sync up (after the browser catches up with the new width)
  249. headerNode.scrollLeft = bodyNode.scrollLeft;
  250. }, 0);
  251. }
  252. },
  253. addCssRule: function(selector, css){
  254. // summary:
  255. // Version of util/misc.addCssRule which tracks added rules and removes
  256. // them when the List is destroyed.
  257. var rule = miscUtil.addCssRule(selector, css);
  258. if(this.cleanAddedRules){
  259. // Although this isn't a listener, it shares the same remove contract
  260. this._listeners.push(rule);
  261. }
  262. return rule;
  263. },
  264. on: function(eventType, listener){
  265. // delegate events to the domNode
  266. var signal = listen(this.domNode, eventType, listener);
  267. if(!has("dom-addeventlistener")){
  268. this._listeners.push(signal);
  269. }
  270. return signal;
  271. },
  272. cleanup: function(){
  273. // summary:
  274. // Clears out all rows currently in the list.
  275. var observers = this.observers,
  276. i;
  277. for(i in this._rowIdToObject){
  278. if(this._rowIdToObject[i] != this.columns){
  279. var rowElement = byId(i);
  280. if(rowElement){
  281. this.removeRow(rowElement, true);
  282. }
  283. }
  284. }
  285. // remove any store observers
  286. for(i = 0;i < observers.length; i++){
  287. var observer = observers[i];
  288. observer && observer.cancel();
  289. }
  290. this.observers = [];
  291. this.preload = null;
  292. },
  293. destroy: function(){
  294. // summary:
  295. // Destroys this grid
  296. // Remove any event listeners and other such removables
  297. if(this._listeners){ // Guard against accidental subsequent calls to destroy
  298. for(var i = this._listeners.length; i--;){
  299. this._listeners[i].remove();
  300. }
  301. delete this._listeners;
  302. }
  303. this.cleanup();
  304. // destroy DOM
  305. put(this.domNode, "!");
  306. },
  307. refresh: function(){
  308. // summary:
  309. // refreshes the contents of the grid
  310. this.cleanup();
  311. this._rowIdToObject = {};
  312. this._autoId = 0;
  313. // make sure all the content has been removed so it can be recreated
  314. this.contentNode.innerHTML = "";
  315. // Ensure scroll position always resets (especially for TouchScroll).
  316. this.scrollTo({ x: 0, y: 0 });
  317. },
  318. newRow: function(object, parentNode, beforeNode, i, options){
  319. if(parentNode){
  320. var row = this.insertRow(object, parentNode, beforeNode, i, options);
  321. put(row, ".ui-state-highlight");
  322. setTimeout(function(){
  323. put(row, "!ui-state-highlight");
  324. }, 250);
  325. return row;
  326. }
  327. },
  328. adjustRowIndices: function(firstRow){
  329. if(this.maintainOddEven){
  330. // this traverses through rows to maintain odd/even classes on the rows when indexes shift;
  331. var next = firstRow;
  332. var rowIndex = next.rowIndex;
  333. if(rowIndex > -1){ // make sure we have a real number in case this is called on a non-row
  334. do{
  335. if(next.rowIndex > -1){
  336. // skip non-numeric, non-rows
  337. if((next.className + ' ').indexOf("dgrid-row ") > -1){
  338. put(next, '.' + (rowIndex % 2 == 1 ? oddClass : evenClass) + '!' + (rowIndex % 2 == 0 ? oddClass : evenClass));
  339. }
  340. next.rowIndex = rowIndex++;
  341. }
  342. }while((next = next.nextSibling) && next.rowIndex != rowIndex && !next.blockRowIndex);
  343. }
  344. }
  345. },
  346. renderArray: function(results, beforeNode, options){
  347. // summary:
  348. // This renders an array or collection of objects as rows in the grid, before the
  349. // given node. This will listen for changes in the collection if an observe method
  350. // is available (as it should be if it comes from an Observable data store).
  351. options = options || {};
  352. var self = this,
  353. start = options.start || 0,
  354. row, rows, container;
  355. if(!beforeNode){
  356. this._lastCollection = results;
  357. }
  358. if(results.observe){
  359. // observe the results for changes
  360. var observerIndex = this.observers.push(results.observe(function(object, from, to){
  361. var firstRow, nextNode, parentNode;
  362. // a change in the data took place
  363. if(from > -1 && rows[from]){
  364. // remove from old slot
  365. row = rows.splice(from, 1)[0];
  366. // check to make the sure the node is still there before we try to remove it, (in case it was moved to a different place in the DOM)
  367. if(row.parentNode == container){
  368. firstRow = row.nextSibling;
  369. if(firstRow){ // it's possible for this to have been already removed if it is in overlapping query results
  370. if(from != to){ // if from and to are identical, it is an in-place update and we don't want to alter the rowIndex at all
  371. firstRow.rowIndex--; // adjust the rowIndex so adjustRowIndices has the right starting point
  372. }
  373. }
  374. self.removeRow(row); // now remove
  375. }
  376. // the removal of rows could cause us to need to page in more items
  377. if(self._processScroll){
  378. self._processScroll();
  379. }
  380. }
  381. if(to > -1){
  382. // Add to new slot (either before an existing row, or at the end)
  383. // First determine the DOM node that this should be placed before.
  384. nextNode = rows[to];
  385. if(!nextNode){
  386. nextNode = rows[to - 1];
  387. if(nextNode){
  388. // Make sure to skip connected nodes, so we don't accidentally
  389. // insert a row in between a parent and its children.
  390. nextNode = (nextNode.connected || nextNode).nextSibling;
  391. }
  392. }
  393. parentNode = (beforeNode && beforeNode.parentNode) ||
  394. (nextNode && nextNode.parentNode) || self.contentNode;
  395. row = self.newRow(object, parentNode, nextNode, options.start + to, options);
  396. if(row){
  397. row.observerIndex = observerIndex;
  398. rows.splice(to, 0, row);
  399. if(!firstRow || to < from){
  400. // the inserted row is first, so we update firstRow to point to it
  401. var previous = row.previousSibling;
  402. // if we are not in sync with the previous row, roll the firstRow back one so adjustRowIndices can sync everything back up.
  403. firstRow = !previous || previous.rowIndex + 1 == row.rowIndex || row.rowIndex == 0 ?
  404. row : previous;
  405. }
  406. }
  407. options.count++;
  408. }
  409. from != to && firstRow && self.adjustRowIndices(firstRow);
  410. self._onNotification(rows, object, from, to);
  411. }, true)) - 1;
  412. }
  413. var rowsFragment = document.createDocumentFragment();
  414. // now render the results
  415. if(results.map){
  416. rows = results.map(mapEach, console.error);
  417. if(rows.then){
  418. return rows.then(whenDone);
  419. }
  420. }else{
  421. rows = [];
  422. for(var i = 0, l = results.length; i < l; i++){
  423. rows[i] = mapEach(results[i]);
  424. }
  425. }
  426. var lastRow;
  427. function mapEach(object){
  428. lastRow = self.insertRow(object, rowsFragment, null, start++, options);
  429. lastRow.observerIndex = observerIndex;
  430. return lastRow;
  431. }
  432. function whenDone(resolvedRows){
  433. container = beforeNode ? beforeNode.parentNode : self.contentNode;
  434. if(container){
  435. container.insertBefore(rowsFragment, beforeNode || null);
  436. lastRow = resolvedRows[resolvedRows.length - 1];
  437. lastRow && self.adjustRowIndices(lastRow);
  438. }
  439. return (rows = resolvedRows);
  440. }
  441. return whenDone(rows);
  442. },
  443. _onNotification: function(rows, object, from, to){
  444. // summary:
  445. // Protected method called whenever a store notification is observed.
  446. // Intended to be extended as necessary by mixins/extensions.
  447. },
  448. renderHeader: function(){
  449. // no-op in a plain list
  450. },
  451. _autoId: 0,
  452. insertRow: function(object, parent, beforeNode, i, options){
  453. // summary:
  454. // Creates a single row in the grid.
  455. // Include parentId within row identifier if one was specified in options.
  456. // (This is used by tree to allow the same object to appear under
  457. // multiple parents.)
  458. var parentId = options.parentId,
  459. id = this.id + "-row-" + (parentId ? parentId + "-" : "") +
  460. ((this.store && this.store.getIdentity) ?
  461. this.store.getIdentity(object) : this._autoId++),
  462. row = byId(id),
  463. previousRow = row && row.previousSibling;
  464. if(row){// if it existed elsewhere in the DOM, we will remove it, so we can recreate it
  465. this.removeRow(row);
  466. }
  467. row = this.renderRow(object, options);
  468. row.className = (row.className || "") + " ui-state-default dgrid-row " + (i % 2 == 1 ? oddClass : evenClass);
  469. // get the row id for easy retrieval
  470. this._rowIdToObject[row.id = id] = object;
  471. parent.insertBefore(row, beforeNode || null);
  472. if(previousRow){
  473. // in this case, we are pulling the row from another location in the grid, and we need to readjust the rowIndices from the point it was removed
  474. this.adjustRowIndices(previousRow);
  475. }
  476. row.rowIndex = i;
  477. return row;
  478. },
  479. renderRow: function(value, options){
  480. // summary:
  481. // Responsible for returning the DOM for a single row in the grid.
  482. return put("div", "" + value);
  483. },
  484. removeRow: function(rowElement, justCleanup){
  485. // summary:
  486. // Simply deletes the node in a plain List.
  487. // Column plugins may aspect this to implement their own cleanup routines.
  488. // rowElement: Object|DOMNode
  489. // Object or element representing the row to be removed.
  490. // justCleanup: Boolean
  491. // If true, the row element will not be removed from the DOM; this can
  492. // be used by extensions/plugins in cases where the DOM will be
  493. // massively cleaned up at a later point in time.
  494. rowElement = rowElement.element || rowElement;
  495. delete this._rowIdToObject[rowElement.id];
  496. if(!justCleanup){
  497. put(rowElement, "!");
  498. }
  499. },
  500. row: function(target){
  501. // summary:
  502. // Get the row object by id, object, node, or event
  503. var id;
  504. if(target instanceof this._Row){ return target; } // no-op; already a row
  505. if(target.target && target.target.nodeType){
  506. // event
  507. target = target.target;
  508. }
  509. if(target.nodeType){
  510. var object;
  511. do{
  512. var rowId = target.id;
  513. if((object = this._rowIdToObject[rowId])){
  514. return new this._Row(rowId.substring(this.id.length + 5), object, target);
  515. }
  516. target = target.parentNode;
  517. }while(target && target != this.domNode);
  518. return;
  519. }
  520. if(typeof target == "object"){
  521. // assume target represents a store item
  522. id = this.store.getIdentity(target);
  523. }else{
  524. // assume target is a row ID
  525. id = target;
  526. target = this._rowIdToObject[this.id + "-row-" + id];
  527. }
  528. return new this._Row(id, target, byId(this.id + "-row-" + id));
  529. },
  530. cell: function(target){
  531. // this doesn't do much in a plain list
  532. return {
  533. row: this.row(target)
  534. };
  535. },
  536. _move: function(item, steps, targetClass, visible){
  537. var nextSibling, current, element;
  538. // Start at the element indicated by the provided row or cell object.
  539. element = current = item.element;
  540. steps = steps || 1;
  541. do{
  542. // Outer loop: move in the appropriate direction.
  543. if((nextSibling = current[steps < 0 ? "previousSibling" : "nextSibling"])){
  544. do{
  545. // Inner loop: advance, and dig into children if applicable.
  546. current = nextSibling;
  547. if(current && (current.className + " ").indexOf(targetClass + " ") > -1){
  548. // Element with the appropriate class name; count step, stop digging.
  549. element = current;
  550. steps += steps < 0 ? 1 : -1;
  551. break;
  552. }
  553. // If the next sibling isn't a match, drill down to search, unless
  554. // visible is true and children are hidden.
  555. }while((nextSibling = (!visible || !current.hidden) && current[steps < 0 ? "lastChild" : "firstChild"]));
  556. }else{
  557. current = current.parentNode;
  558. if(current === this.bodyNode || current === this.headerNode){
  559. // Break out if we step out of the navigation area entirely.
  560. break;
  561. }
  562. }
  563. }while(steps);
  564. // Return the final element we arrived at, which might still be the
  565. // starting element if we couldn't navigate further in that direction.
  566. return element;
  567. },
  568. up: function(row, steps, visible){
  569. // summary:
  570. // Returns the row that is the given number of steps (1 by default)
  571. // above the row represented by the given object.
  572. // row:
  573. // The row to navigate upward from.
  574. // steps:
  575. // Number of steps to navigate up from the given row; default is 1.
  576. // visible:
  577. // If true, rows that are currently hidden (i.e. children of
  578. // collapsed tree rows) will not be counted in the traversal.
  579. // returns:
  580. // A row object representing the appropriate row. If the top of the
  581. // list is reached before the given number of steps, the first row will
  582. // be returned.
  583. if(!row.element){ row = this.row(row); }
  584. return this.row(this._move(row, -(steps || 1), "dgrid-row", visible));
  585. },
  586. down: function(row, steps, visible){
  587. // summary:
  588. // Returns the row that is the given number of steps (1 by default)
  589. // below the row represented by the given object.
  590. // row:
  591. // The row to navigate downward from.
  592. // steps:
  593. // Number of steps to navigate down from the given row; default is 1.
  594. // visible:
  595. // If true, rows that are currently hidden (i.e. children of
  596. // collapsed tree rows) will not be counted in the traversal.
  597. // returns:
  598. // A row object representing the appropriate row. If the bottom of the
  599. // list is reached before the given number of steps, the last row will
  600. // be returned.
  601. if(!row.element){ row = this.row(row); }
  602. return this.row(this._move(row, steps || 1, "dgrid-row", visible));
  603. },
  604. scrollTo: has("touch") ? function(){
  605. // If TouchScroll is the superclass, defer to its implementation.
  606. return this.inherited(arguments);
  607. } : function(options){
  608. // No TouchScroll; simple implementation which sets scrollLeft/Top.
  609. if(typeof options.x !== "undefined"){
  610. this.bodyNode.scrollLeft = options.x;
  611. }
  612. if(typeof options.y !== "undefined"){
  613. this.bodyNode.scrollTop = options.y;
  614. }
  615. },
  616. getScrollPosition: has("touch") ? function(){
  617. // If TouchScroll is the superclass, defer to its implementation.
  618. return this.inherited(arguments);
  619. } : function(){
  620. // No TouchScroll; return based on scrollLeft/Top.
  621. return {
  622. x: this.bodyNode.scrollLeft,
  623. y: this.bodyNode.scrollTop
  624. };
  625. },
  626. get: function(/*String*/ name /*, ... */){
  627. // summary:
  628. // Get a property on a List instance.
  629. // name:
  630. // The property to get.
  631. // returns:
  632. // The property value on this List instance.
  633. // description:
  634. // Get a named property on a List object. The property may
  635. // potentially be retrieved via a getter method in subclasses. In the base class
  636. // this just retrieves the object's property.
  637. var fn = "_get" + name.charAt(0).toUpperCase() + name.slice(1);
  638. if(typeof this[fn] === "function"){
  639. return this[fn].apply(this, [].slice.call(arguments, 1));
  640. }
  641. // Alert users that try to use Dijit-style getter/setters so they don’t get confused
  642. // if they try to use them and it does not work
  643. if(!has("dojo-built") && typeof this[fn + "Attr"] === "function"){
  644. console.warn("dgrid: Use " + fn + " instead of " + fn + "Attr for getting " + name);
  645. }
  646. return this[name];
  647. },
  648. set: function(/*String*/ name, /*Object*/ value /*, ... */){
  649. // summary:
  650. // Set a property on a List instance
  651. // name:
  652. // The property to set.
  653. // value:
  654. // The value to set in the property.
  655. // returns:
  656. // The function returns this List instance.
  657. // description:
  658. // Sets named properties on a List object.
  659. // A programmatic setter may be defined in subclasses.
  660. //
  661. // set() may also be called with a hash of name/value pairs, ex:
  662. // | myObj.set({
  663. // | foo: "Howdy",
  664. // | bar: 3
  665. // | })
  666. // This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
  667. if(typeof name === "object"){
  668. for(var k in name){
  669. this.set(k, name[k]);
  670. }
  671. }else{
  672. var fn = "_set" + name.charAt(0).toUpperCase() + name.slice(1);
  673. if(typeof this[fn] === "function"){
  674. this[fn].apply(this, [].slice.call(arguments, 1));
  675. }else{
  676. // Alert users that try to use Dijit-style getter/setters so they don’t get confused
  677. // if they try to use them and it does not work
  678. if(!has("dojo-built") && typeof this[fn + "Attr"] === "function"){
  679. console.warn("dgrid: Use " + fn + " instead of " + fn + "Attr for setting " + name);
  680. }
  681. this[name] = value;
  682. }
  683. }
  684. return this;
  685. },
  686. // Accept both class and className programmatically to set domNode class.
  687. _getClass: getClass,
  688. _setClass: setClass,
  689. _getClassName: getClass,
  690. _setClassName: setClass,
  691. _setSort: function(property, descending){
  692. // summary:
  693. // Sort the content
  694. // property: String|Array
  695. // String specifying field to sort by, or actual array of objects
  696. // with attribute and descending properties
  697. // descending: boolean
  698. // In the case where property is a string, this argument
  699. // specifies whether to sort ascending (false) or descending (true)
  700. this._sort = typeof property != "string" ? property :
  701. [{attribute: property, descending: descending}];
  702. this.refresh();
  703. if(this._lastCollection){
  704. if(property.length){
  705. // if an array was passed in, flatten to just first sort attribute
  706. // for default array sort logic
  707. if(typeof property != "string"){
  708. descending = property[0].descending;
  709. property = property[0].attribute;
  710. }
  711. this._lastCollection.sort(function(a,b){
  712. var aVal = a[property], bVal = b[property];
  713. // fall back undefined values to "" for more consistent behavior
  714. if(aVal === undefined){ aVal = ""; }
  715. if(bVal === undefined){ bVal = ""; }
  716. return aVal == bVal ? 0 : (aVal > bVal == !descending ? 1 : -1);
  717. });
  718. }
  719. this.renderArray(this._lastCollection);
  720. }
  721. },
  722. // TODO: remove the following two (and rename _sort to sort) in 1.0
  723. sort: function(property, descending){
  724. kernel.deprecated("sort(...)", 'use set("sort", ...) instead', "dgrid 1.0");
  725. this.set("sort", property, descending);
  726. },
  727. _getSort: function(){
  728. return this._sort;
  729. },
  730. _setShowHeader: function(show){
  731. // this is in List rather than just in Grid, primarily for two reasons:
  732. // (1) just in case someone *does* want to show a header in a List
  733. // (2) helps address IE < 8 header display issue in List
  734. var headerNode = this.headerNode;
  735. this.showHeader = show;
  736. // add/remove class which has styles for "hiding" header
  737. put(headerNode, (show ? "!" : ".") + "dgrid-header-hidden");
  738. this.renderHeader();
  739. this.resize(); // resize to account for (dis)appearance of header
  740. if(show){
  741. // Update scroll position of header to make sure it's in sync.
  742. headerNode.scrollLeft = this.getScrollPosition().x;
  743. }
  744. },
  745. setShowHeader: function(show){
  746. kernel.deprecated("setShowHeader(...)", 'use set("showHeader", ...) instead', "dgrid 1.0");
  747. this.set("showHeader", show);
  748. },
  749. _setShowFooter: function(show){
  750. this.showFooter = show;
  751. // add/remove class which has styles for hiding footer
  752. put(this.footerNode, (show ? "!" : ".") + "dgrid-footer-hidden");
  753. this.resize(); // to account for (dis)appearance of footer
  754. }
  755. });
  756. });