ColumnHider.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. define(["dojo/_base/declare", "dojo/has", "dojo/on", "../util/misc", "put-selector/put", "dojo/i18n!./nls/columnHider", "xstyle/css!../css/extensions/ColumnHider.css"],
  2. function(declare, has, listen, miscUtil, put, i18n){
  3. /*
  4. * Column Hider plugin for dgrid
  5. * Originally contributed by TRT 2011-09-28
  6. *
  7. * A dGrid plugin that attaches a menu to a dgrid, along with a way of opening it,
  8. * that will allow you to show and hide columns. A few caveats:
  9. *
  10. * 1. Menu placement is entirely based on CSS definitions.
  11. * 2. If you want columns initially hidden, you must add "hidden: true" to your
  12. * column definition.
  13. * 3. This implementation does NOT support ColumnSet, and has not been tested
  14. * with multi-subrow records.
  15. * 4. Column show/hide is controlled via straight up HTML checkboxes. If you
  16. * are looking for something more fancy, you'll probably need to use this
  17. * definition as a template to write your own plugin.
  18. *
  19. */
  20. var activeGrid, // references grid for which the menu is currently open
  21. bodyListener, // references pausable event handler for body mousedown
  22. // Need to handle old IE specially for checkbox listener and for attribute.
  23. hasIE = has("ie"),
  24. hasIEQuirks = hasIE && has("quirks"),
  25. forAttr = hasIE < 8 || hasIEQuirks ? "htmlFor" : "for";
  26. function getColumnIdFromCheckbox(cb, grid){
  27. // Given one of the checkboxes from the hider menu,
  28. // return the id of the corresponding column.
  29. // (e.g. gridIDhere-hider-menu-check-colIDhere -> colIDhere)
  30. return cb.id.substr(grid.id.length + 18);
  31. }
  32. return declare(null, {
  33. // hiderMenuNode: DOMNode
  34. // The node for the menu to show/hide columns.
  35. hiderMenuNode: null,
  36. // hiderToggleNode: DOMNode
  37. // The node for the toggler to open the menu.
  38. hiderToggleNode: null,
  39. // i18nColumnHider: Object
  40. // This object contains all of the internationalized strings for
  41. // the ColumnHider extension as key/value pairs.
  42. i18nColumnHider: i18n,
  43. // _hiderMenuOpened: Boolean
  44. // Records the current open/closed state of the menu.
  45. _hiderMenuOpened: false,
  46. // _columnHiderRules: Object
  47. // Hash containing handles returned from addCssRule.
  48. _columnHiderRules: null,
  49. // _columnHiderCheckboxes: Object
  50. // Hash containing checkboxes generated for menu items.
  51. _columnHiderCheckboxes: null,
  52. _renderHiderMenuEntries: function(){
  53. // summary:
  54. // Iterates over subRows for the sake of adding items to the
  55. // column hider menu.
  56. var subRows = this.subRows,
  57. first = true,
  58. srLength, cLength, sr, c, checkbox;
  59. delete this._columnHiderFirstCheckbox;
  60. for(sr = 0, srLength = subRows.length; sr < srLength; sr++){
  61. for(c = 0, cLength = subRows[sr].length; c < cLength; c++){
  62. this._renderHiderMenuEntry(subRows[sr][c]);
  63. if(first){
  64. first = false;
  65. this._columnHiderFirstCheckbox =
  66. this._columnHiderCheckboxes[subRows[sr][c].id];
  67. }
  68. }
  69. }
  70. },
  71. _renderHiderMenuEntry: function(col){
  72. var id = col.id,
  73. div, checkId, checkbox;
  74. if(col.hidden){
  75. // Hidden state is true; hide the column.
  76. this._hideColumn(id);
  77. }
  78. // Allow cols to opt out of the hider (e.g. for selector column).
  79. if(col.unhidable){ return; }
  80. // Create the checkbox and label for each column selector.
  81. div = put("div.dgrid-hider-menu-row");
  82. checkId = this.domNode.id + "-hider-menu-check-" + id;
  83. this._columnHiderCheckboxes[id] = checkbox = put(div, "input#" + checkId +
  84. ".dgrid-hider-menu-check.hider-menu-check-" + id + "[type=checkbox]");
  85. put(div, "label.dgrid-hider-menu-label.hider-menu-label" + id +
  86. "[" + forAttr + "=" + checkId + "]", col.label || col.field || "");
  87. put(this.hiderMenuNode, div);
  88. if(!col.hidden){
  89. // Hidden state is false; checkbox should be initially checked.
  90. // (Need to do this after adding to DOM to avoid IE6 clobbering it.)
  91. checkbox.checked = true;
  92. }
  93. },
  94. renderHeader: function(){
  95. var grid = this,
  96. hiderMenuNode = this.hiderMenuNode,
  97. hiderToggleNode = this.hiderToggleNode,
  98. id;
  99. function stopPropagation(event){
  100. event.stopPropagation();
  101. }
  102. this.inherited(arguments);
  103. if(!hiderMenuNode){ // first run
  104. // Assume that if this plugin is used, then columns are hidable.
  105. // Create the toggle node.
  106. hiderToggleNode = this.hiderToggleNode =
  107. put(this.headerScrollNode, "button.ui-icon.dgrid-hider-toggle[type=button]");
  108. this._listeners.push(listen(hiderToggleNode, "click", function(e){
  109. grid._toggleColumnHiderMenu(e);
  110. }));
  111. // Create the column list, with checkboxes.
  112. hiderMenuNode = this.hiderMenuNode =
  113. put("div#dgrid-hider-menu-" + this.id +
  114. ".dgrid-hider-menu[role=dialog][aria-label=" + this.i18nColumnHider.popupLabel + "]");
  115. this._listeners.push(listen(hiderMenuNode, "keyup", function (e) {
  116. var charOrCode = e.charCode || e.keyCode;
  117. if(charOrCode === /*ESCAPE*/ 27){
  118. grid._toggleColumnHiderMenu(e);
  119. hiderToggleNode.focus();
  120. }
  121. }));
  122. // Make sure our menu is initially hidden, then attach to the document.
  123. hiderMenuNode.style.display = "none";
  124. put(this.domNode, hiderMenuNode);
  125. // Hook up delegated listener for modifications to checkboxes.
  126. this._listeners.push(listen(hiderMenuNode,
  127. ".dgrid-hider-menu-check:" + (hasIE < 9 || hasIEQuirks ? "click" : "change"),
  128. function(e){
  129. grid._updateColumnHiddenState(
  130. getColumnIdFromCheckbox(e.target, grid), !e.target.checked);
  131. }
  132. ));
  133. // Stop click events from propagating from menu or trigger nodes,
  134. // so that we can simply track body clicks for hide without
  135. // having to drill-up to check.
  136. this._listeners.push(
  137. listen(hiderMenuNode, "mousedown", stopPropagation),
  138. listen(hiderToggleNode, "mousedown", stopPropagation)
  139. );
  140. // Hook up top-level mousedown listener if it hasn't been yet.
  141. if(!bodyListener){
  142. bodyListener = listen.pausable(document, "mousedown", function(e){
  143. // If an event reaches this listener, the menu is open,
  144. // but a click occurred outside, so close the dropdown.
  145. activeGrid && activeGrid._toggleColumnHiderMenu(e);
  146. });
  147. bodyListener.pause(); // pause initially; will resume when menu opens
  148. }
  149. }else{ // subsequent run
  150. // Remove active rules, and clear out the menu (to be repopulated).
  151. for(id in this._columnHiderRules){
  152. this._columnHiderRules[id].remove();
  153. }
  154. hiderMenuNode.innerHTML = "";
  155. }
  156. this._columnHiderCheckboxes = {};
  157. this._columnHiderRules = {};
  158. // Populate menu with checkboxes/labels based on current columns.
  159. this._renderHiderMenuEntries();
  160. },
  161. destroy: function(){
  162. this.inherited(arguments);
  163. // Remove any remaining rules applied to hidden columns.
  164. for(var id in this._columnHiderRules){
  165. this._columnHiderRules[id].remove();
  166. }
  167. },
  168. isColumnHidden: function(id){
  169. // summary:
  170. // Convenience method to determine current hidden state of a column
  171. return !!this._columnHiderRules[id];
  172. },
  173. _toggleColumnHiderMenu: function(){
  174. var hidden = this._hiderMenuOpened, // reflects hidden state after toggle
  175. hiderMenuNode = this.hiderMenuNode,
  176. domNode = this.domNode,
  177. firstCheckbox;
  178. // Show or hide the hider menu
  179. hiderMenuNode.style.display = (hidden ? "none" : "");
  180. // Adjust height of menu
  181. if (hidden) {
  182. // Clear the set size
  183. hiderMenuNode.style.height = "";
  184. } else {
  185. // Adjust height of the menu if necessary
  186. // Why 12? Based on menu default paddings and border, we need
  187. // to adjust to be 12 pixels shorter. Given the infrequency of
  188. // this style changing, we're assuming it will remain this
  189. // static value of 12 for now, to avoid pulling in any sort of
  190. // computed styles.
  191. if (hiderMenuNode.offsetHeight > domNode.offsetHeight - 12) {
  192. hiderMenuNode.style.height = (domNode.offsetHeight - 12) + "px";
  193. }
  194. // focus on the first checkbox
  195. (firstCheckbox = this._columnHiderFirstCheckbox) && firstCheckbox.focus();
  196. }
  197. // Pause or resume the listener for clicks outside the menu
  198. bodyListener[hidden ? "pause" : "resume"]();
  199. // Update activeGrid appropriately
  200. activeGrid = hidden ? null : this;
  201. // Toggle the instance property
  202. this._hiderMenuOpened = !hidden;
  203. },
  204. _hideColumn: function(id){
  205. // summary:
  206. // Hides the column indicated by the given id.
  207. // Use miscUtil function directly, since we clean these up ourselves anyway
  208. var selectorPrefix = "#" + miscUtil.escapeCssIdentifier(this.domNode.id) + " .dgrid-column-",
  209. tableRule; // used in IE8 code path
  210. if (this._columnHiderRules[id]) {
  211. return;
  212. }
  213. this._columnHiderRules[id] =
  214. miscUtil.addCssRule(selectorPrefix + id, "display: none;");
  215. if(has("ie") === 8 && !has("quirks")){
  216. tableRule = miscUtil.addCssRule(".dgrid-row-table", "display: inline-table;");
  217. window.setTimeout(function(){
  218. tableRule.remove();
  219. }, 0);
  220. }
  221. },
  222. _updateColumnHiddenState: function(id, hidden){
  223. // summary:
  224. // Performs internal work for toggleColumnHiddenState; see the public
  225. // method for more information.
  226. if(!hidden){
  227. this._columnHiderRules[id] && this._columnHiderRules[id].remove();
  228. delete this._columnHiderRules[id];
  229. }else{
  230. this._hideColumn(id);
  231. }
  232. // Update hidden state in actual column definition,
  233. // in case columns are re-rendered.
  234. this.columns[id].hidden = hidden;
  235. // Emit event to notify of column state change.
  236. listen.emit(this.domNode, "dgrid-columnstatechange", {
  237. grid: this,
  238. column: this.columns[id],
  239. hidden: hidden,
  240. bubbles: true
  241. });
  242. // Adjust the size of the header.
  243. this.resize();
  244. },
  245. toggleColumnHiddenState: function(id, hidden){
  246. // summary:
  247. // Shows or hides the column with the given id.
  248. // id: String
  249. // ID of column to show/hide.
  250. // hide: Boolean?
  251. // If specified, explicitly sets the hidden state of the specified
  252. // column. If unspecified, toggles the column from the current state.
  253. if(typeof hidden === "undefined"){ hidden = !this._columnHiderRules[id]; }
  254. this._updateColumnHiddenState(id, hidden);
  255. // Since this can be called directly, re-sync the appropriate checkbox.
  256. this._columnHiderCheckboxes[id].checked = !hidden;
  257. }
  258. });
  259. });