ext-tree-checkbox.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. /**
  2. * Retrieve an array of ids of checked nodes
  3. * @return {Array} array of ids of checked nodes
  4. */
  5. Ext.tree.TreePanel.prototype.getChecked = function(node){
  6. var checked = [], i;
  7. if( typeof node == 'undefined' ) {
  8. //node = this.rootVisible ? this.getRootNode() : this.getRootNode().firstChild;
  9. node = this.getRootNode();
  10. }
  11. if( node.attributes.checked ) {
  12. checked.push(node.id);
  13. }
  14. if( node.childNodes.length ) {
  15. for( i = 0; i < node.childNodes.length; i++ ) {
  16. checked = checked.concat( this.getChecked(node.childNodes[i]) );
  17. }
  18. }
  19. return checked;
  20. };
  21. /**
  22. * @class Ext.tree.CustomUITreeLoader
  23. * @extends Ext.tree.TreeLoader
  24. * Overrides createNode to force uiProvider to be an arbitrary TreeNodeUI to save bandwidth
  25. */
  26. Ext.tree.CustomUITreeLoader = function() {
  27. Ext.tree.CustomUITreeLoader.superclass.constructor.apply(this, arguments);
  28. };
  29. Ext.extend(Ext.tree.CustomUITreeLoader, Ext.tree.TreeLoader, {
  30. createNode : function(attr){
  31. Ext.apply(attr, this.baseAttr || {});
  32. if(this.applyLoader !== false){
  33. attr.loader = this;
  34. }
  35. if(typeof attr.uiProvider == 'string'){
  36. attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
  37. }
  38. return(attr.leaf ?
  39. new Ext.tree.TreeNode(attr) :
  40. new Ext.tree.AsyncTreeNode(attr));
  41. }
  42. });
  43. /**
  44. * @class Ext.tree.CheckboxNodeUI
  45. * @extends Ext.tree.TreeNodeUI
  46. * Adds a checkbox to all nodes
  47. */
  48. Ext.tree.CheckboxNodeUI = function() {
  49. Ext.tree.CheckboxNodeUI.superclass.constructor.apply(this, arguments);
  50. };
  51. Ext.extend(Ext.tree.CheckboxNodeUI, Ext.tree.TreeNodeUI, {
  52. /**
  53. * This is virtually identical to Ext.tree.TreeNodeUI.render, modifications are indicated inline
  54. */
  55. render : function(bulkRender){
  56. var n = this.node;
  57. var targetNode = n.parentNode ?
  58. n.parentNode.ui.getContainer() : n.ownerTree.container.dom; /* in later svn builds this changes to n.ownerTree.innerCt.dom */
  59. if(!this.rendered){
  60. this.rendered = true;
  61. var a = n.attributes;
  62. // add some indent caching, this helps performance when rendering a large tree
  63. this.indentMarkup = "";
  64. if(n.parentNode){
  65. this.indentMarkup = n.parentNode.ui.getChildIndent();
  66. }
  67. // modification: added checkbox
  68. var buf = ['<li class="x-tree-node"><div class="x-tree-node-el ', n.attributes.cls,'">',
  69. '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
  70. '<img src="', this.emptyIcon, '" class="x-tree-ec-icon">',
  71. '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on">',
  72. '<input class="l-tcb" '+ (n.disabled ? 'disabled="disabled" ' : '') +' type="checkbox" ', (a.checked ? "checked>" : '>'),
  73. '<a hidefocus="on" href="',a.href ? a.href : "#",'" ',
  74. a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '>',
  75. '<span unselectable="on">',n.text,"</span></a></div>",
  76. '<ul class="x-tree-node-ct" style="display:none;"></ul>',
  77. "</li>"];
  78. if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
  79. this.wrap = Ext.DomHelper.insertHtml("beforeBegin",
  80. n.nextSibling.ui.getEl(), buf.join(""));
  81. }else{
  82. this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));
  83. }
  84. this.elNode = this.wrap.childNodes[0];
  85. this.ctNode = this.wrap.childNodes[1];
  86. var cs = this.elNode.childNodes;
  87. this.indentNode = cs[0];
  88. this.ecNode = cs[1];
  89. this.iconNode = cs[2];
  90. this.checkbox = cs[3]; // modification: inserted checkbox
  91. this.anchor = cs[4];
  92. this.textNode = cs[4].firstChild;
  93. if(a.qtip){
  94. if(this.textNode.setAttributeNS){
  95. this.textNode.setAttributeNS("ext", "qtip", a.qtip);
  96. if(a.qtipTitle){
  97. this.textNode.setAttributeNS("ext", "qtitle", a.qtipTitle);
  98. }
  99. }else{
  100. this.textNode.setAttribute("ext:qtip", a.qtip);
  101. if(a.qtipTitle){
  102. this.textNode.setAttribute("ext:qtitle", a.qtipTitle);
  103. }
  104. }
  105. } else if(a.qtipCfg) {
  106. a.qtipCfg.target = Ext.id(this.textNode);
  107. Ext.QuickTips.register(a.qtipCfg);
  108. }
  109. this.initEvents();
  110. // modification: Add additional handlers here to avoid modifying Ext.tree.TreeNodeUI
  111. Ext.fly(this.checkbox).on('click', this.check.createDelegate(this, [null]));
  112. n.on('dblclick', function(e) {
  113. if( this.isLeaf() ) {
  114. this.getUI().toggleCheck();
  115. }
  116. });
  117. if(!this.node.expanded){
  118. this.updateExpandIcon();
  119. }
  120. }else{
  121. if(bulkRender === true) {
  122. targetNode.appendChild(this.wrap);
  123. }
  124. }
  125. },
  126. checked : function() {
  127. return this.checkbox.checked;
  128. },
  129. /**
  130. * Sets a checkbox appropriately. By default only walks down through child nodes
  131. * if called with no arguments (onchange event from the checkbox), otherwise
  132. * it's assumed the call is being made programatically and the correct arguments are provided.
  133. * @param {Boolean} state true to check the checkbox, false to clear it. (defaults to the opposite of the checkbox.checked)
  134. * @param {Boolean} descend true to walk through the nodes children and set their checkbox values. (defaults to false)
  135. */
  136. check : function(state, descend, bulk) {
  137. if (this.node.disabled) {
  138. return;
  139. }
  140. var n = this.node;
  141. var tree = n.getOwnerTree();
  142. var parentNode = n.parentNode;n
  143. if( !n.expanded && !n.childrenRendered ) {
  144. n.expand(false, false, this.check.createDelegate(this, arguments));
  145. }
  146. if( typeof bulk == 'undefined' ) {
  147. bulk = false;
  148. }
  149. if( typeof state == 'undefined' || state === null ) {
  150. state = this.checkbox.checked;
  151. descend = !state;
  152. if( state ) {
  153. n.expand(false, false);
  154. }
  155. } else {
  156. this.checkbox.checked = state;
  157. }
  158. n.attributes.checked = state;
  159. // do we have parents?
  160. if( parentNode !== null && state ) {
  161. // if we're checking the box, check it all the way up
  162. if( parentNode.getUI().check ) {
  163. //parentNode.getUI().check(state, false, true);
  164. }
  165. }
  166. if( descend && !n.isLeaf() ) {
  167. var cs = n.childNodes;
  168. for(var i = 0; i < cs.length; i++) {
  169. //cs[i].getUI().check(state, true, true);
  170. }
  171. }
  172. if( !bulk ) {
  173. tree.fireEvent('check', n, state);
  174. }
  175. },
  176. toggleCheck : function(state) {
  177. this.check(!this.checkbox.checked, true);
  178. }
  179. });
  180. /**
  181. * @class Ext.tree.CheckNodeMultiSelectionModel
  182. * @extends Ext.tree.MultiSelectionModel
  183. * Multi selection for a TreePanel containing Ext.tree.CheckboxNodeUI.
  184. * Adds enhanced selection routines for selecting multiple items
  185. * and key processing to check/clear checkboxes.
  186. */
  187. Ext.tree.CheckNodeMultiSelectionModel = function(){
  188. Ext.tree.CheckNodeMultiSelectionModel.superclass.constructor.call(this);
  189. };
  190. Ext.extend(Ext.tree.CheckNodeMultiSelectionModel, Ext.tree.MultiSelectionModel, {
  191. init : function(tree){
  192. this.tree = tree;
  193. tree.el.on("keydown", this.onKeyDown, this);
  194. tree.on("click", this.onNodeClick, this);
  195. },
  196. /**
  197. * Handle a node click
  198. * If ctrl key is down and node is selected will unselect the node.
  199. * If the shift key is down it will create a contiguous selection
  200. * (see {@link Ext.tree.CheckNodeMultiSelectionModel#extendSelection} for the limitations)
  201. */
  202. onNodeClick : function(node, e){
  203. if (node.disabled) {
  204. return;
  205. }
  206. if( e.shiftKey && this.extendSelection(node) ) {
  207. return true;
  208. }
  209. if( e.ctrlKey && this.isSelected(node) ) {
  210. this.unselect(node);
  211. } else {
  212. this.select(node, e, e.ctrlKey);
  213. }
  214. },
  215. /**
  216. * Selects all nodes between the previously selected node and the one that the user has just selected.
  217. * Will not span multiple depths, so only children of the same parent will be selected.
  218. */
  219. extendSelection : function(node) {
  220. var last = this.lastSelNode;
  221. if( node == last || !last ) {
  222. return false; /* same selection, process normally normally */
  223. }
  224. if( node.parentNode == last.parentNode ) {
  225. var cs = node.parentNode.childNodes;
  226. var i = 0, attr='id', selecting=false, lastSelect=false;
  227. this.clearSelections(true);
  228. for( i = 0; i < cs.length; i++ ) {
  229. // We have to traverse the entire tree b/c don't know of a way to find
  230. // a numerical representation of a nodes position in a tree.
  231. if( cs[i].attributes[attr] == last.attributes[attr] || cs[i].attributes[attr] == node.attributes[attr] ) {
  232. // lastSelect ensures that we select the final node in the list
  233. lastSelect = selecting;
  234. selecting = !selecting;
  235. }
  236. if( selecting || lastSelect ) {
  237. this.select(cs[i], null, true);
  238. // if we're selecting the last node break to avoid traversing the entire tree
  239. if( lastSelect ) {
  240. break;
  241. }
  242. }
  243. }
  244. return true;
  245. } else {
  246. return false;
  247. }
  248. },
  249. /**
  250. * Traps the press of the SPACE bar and sets the check state of selected nodes to the opposite state of
  251. * the selected or last selected node. Assume you have the following five Ext.tree.CheckboxNodeUIs:
  252. * [X] One, [X] Two, [X] Three, [ ] Four, [ ] Five
  253. * If you select them in this order: One, Two, Three, Four, Five and press the space bar they all
  254. * will be <b>checked</b> (the opposite of the checkbox state of Five).
  255. * If you select them in this order: Five, Four, Three, Two, One and press the space bar they all
  256. * will be <b>unchecked</b> which is the opposite of the checkbox state of One.
  257. */
  258. onKeyDown : Ext.tree.DefaultSelectionModel.prototype.onKeyDown.createInterceptor(function(e) {
  259. var s = this.selNode || this.lastSelNode;
  260. // undesirable, but required
  261. var sm = this;
  262. if(!s){
  263. return;
  264. }
  265. var k = e.getKey();
  266. switch(k){
  267. case e.SPACE:
  268. e.stopEvent();
  269. var sel = this.getSelectedNodes();
  270. var state = !s.getUI().checked();
  271. if( sel.length == 1 ) {
  272. s.getUI().check(state, !s.isLeaf());
  273. } else {
  274. for( var i = 0; i < sel.length; i++ ) {
  275. sel[i].getUI().check(state, !sel[i].isLeaf() );
  276. }
  277. }
  278. break;
  279. }
  280. return true;
  281. })
  282. });