Ext.lingo.JsonCheckBoxTree.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. /* Extending/depending on:
  2. ~ = modified function (when updating from SVN be sure to check these for changes, especially to Ext.tree.TreeNodeUI.render() )
  3. + = added function
  4. TreeSelectionModel.js
  5. Ext.tree.CheckNodeMultiSelectionModel : ~init(), ~onNodeClick(), +extendSelection(), ~onKeyDown()
  6. TreeNodeUI.js
  7. Ext.tree.CheckboxNodeUI : ~render(), +checked(), +check(), +toggleCheck()
  8. TreePanel.js
  9. Ext.tree.TreePanel : +getChecked()
  10. TreeLoader.js
  11. Ext.tree.TreeLoader : ~createNode()
  12. */
  13. /**
  14. * 原始功能只能实现选中或不选两种状态
  15. * 但我们现在需要非叶子节点可以实现三种状态,既:
  16. * 节点的子节点都被选中时,显示都被选中的图标
  17. * 节点的子节点都没选中时,显示都没选中的图标
  18. * 节点的子节点部分选中时,显示部分选中的图标
  19. *
  20. * 显示都被选中,和部分选中时,当前节点的状态是被选中
  21. * 显示都没选中时,当前节点的状态是没选中
  22. *
  23. * 显示都被选中时,单击当前节点,触发的事件是,当前节点变成未选中状态,所有子节点变成未选中状态
  24. * 显示都没选中时,单击当前节点,触发的事件是,当前节点变成都选中状态,所有子节点变成选中状态
  25. * 显示部分选中时,单击当前节点,触发的事件是,当前节点变成都选中状态,所有子节点变成选中状态
  26. *
  27. * 子节点变成未选中状态时,对应父节点判断所有子节点是否未选中,如果都未选中,变成未选中状态,否则变成部分选中状态
  28. * 子节点变成选中状态时,对应父节点判断所有子节点是否都选中,如果都选中,变成都选中状态,否则变成部分选中状态
  29. *
  30. * @return {Array} 选中节点的id数组
  31. */
  32. Ext.tree.TreePanel.prototype.getChecked = function(node){
  33. var checked = [], i;
  34. if( typeof node == 'undefined' ) {
  35. node = this.rootVisible ? this.getRootNode() : this.getRootNode().firstChild;
  36. }
  37. if( node.attributes.checked ) {
  38. checked.push(node.id);
  39. if( !node.isLeaf() ) {
  40. for( i = 0; i < node.childNodes.length; i++ ) {
  41. checked = checked.concat( this.getChecked(node.childNodes[i]) );
  42. }
  43. }
  44. }
  45. return checked;
  46. };
  47. /**
  48. * @class Ext.tree.CustomUITreeLoader
  49. * @extends Ext.tree.TreeLoader
  50. * 重写createNode(),强制uiProvider是任意的TreeNodeUI来保存广度
  51. */
  52. Ext.tree.CustomUITreeLoader = function() {
  53. Ext.tree.CustomUITreeLoader.superclass.constructor.apply(this, arguments);
  54. };
  55. Ext.extend(Ext.tree.CustomUITreeLoader, Ext.tree.TreeLoader, {
  56. createNode : function(attr){
  57. Ext.apply(attr, this.baseAttr || {});
  58. if(this.applyLoader !== false){
  59. attr.loader = this;
  60. }
  61. // 如果uiProvider是字符串,那么要么从uiProviders数组里取一个对应的,要么解析字符串获得uiProvider
  62. if(typeof attr.uiProvider == 'string'){
  63. attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
  64. }
  65. // 返回的时候,如果是叶子,就返回普通TreeNode,如果不是叶子,就返回异步读取树
  66. return(attr.leaf ? new Ext.tree.TreeNode(attr) : new Ext.tree.AsyncTreeNode(attr));
  67. }
  68. });
  69. /**
  70. * @class Ext.tree.CheckboxNodeUI
  71. * @extends Ext.tree.TreeNodeUI
  72. * 给所有节点添加checkbox
  73. */
  74. Ext.tree.CheckboxNodeUI = function() {
  75. Ext.tree.CheckboxNodeUI.superclass.constructor.apply(this, arguments);
  76. };
  77. Ext.extend(Ext.tree.CheckboxNodeUI, Ext.tree.TreeNodeUI, {
  78. /**
  79. * 重写renderElements
  80. */
  81. renderElements : function(n, a, targetNode, bulkRender){
  82. // add some indent caching, this helps performance when rendering a large tree
  83. this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
  84. var cb = typeof a.checked == 'boolean';
  85. cb = true;
  86. var href = a.href ? a.href : Ext.isGecko ? "" : "#";
  87. /*
  88. var buf = ['<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">',
  89. '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
  90. '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
  91. '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
  92. cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',
  93. '<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
  94. a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '><span unselectable="on">',n.text,"</span></a></div>",
  95. '<ul class="x-tree-node-ct" style="display:none;"></ul>',
  96. "</li>"].join('');
  97. */
  98. // modification,添加checkbox
  99. var buf = ['<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">',
  100. '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
  101. '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
  102. '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
  103. '<img src="',this.emptyIcon,'" class="',this.isAllChildrenChecked(),'" />',
  104. '<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
  105. a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '><span unselectable="on">',n.text,"</span></a></div>",
  106. '<ul class="x-tree-node-ct" style="display:none;"></ul>',
  107. "</li>"].join('');
  108. var nel;
  109. if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
  110. this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
  111. }else{
  112. this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
  113. }
  114. this.elNode = this.wrap.childNodes[0];
  115. this.ctNode = this.wrap.childNodes[1];
  116. var cs = this.elNode.childNodes;
  117. this.indentNode = cs[0];
  118. this.ecNode = cs[1];
  119. this.iconNode = cs[2];
  120. var index = 3;
  121. if(cb){
  122. this.checkbox = cs[3]; // modification,添加checkbox
  123. this.checkboxImg = cs[3]; // 修改,添加仿造checkbox的图片
  124. index++;
  125. //Ext.log(this.checkbox);
  126. //Ext.log(index);
  127. // modification: 添加handlers,避免修改Ext.tree.TreeNodeUI
  128. Ext.fly(this.checkbox).on('click', this.check.createDelegate(this, [null]));
  129. n.on('dblclick', function(e) {
  130. if (this.isLeaf()) {
  131. this.getUI().toggleCheck();
  132. }
  133. });
  134. }
  135. this.anchor = cs[index];
  136. this.textNode = cs[index].firstChild;
  137. }
  138. // 这个节点是否被选中了
  139. , checked : function() {
  140. // return this.checkbox.checked;
  141. return this.checkboxImg.className != "x-tree-node-checkbox-none";
  142. },
  143. // flag可能是:(NULL)
  144. // 以当前节点状态为准判断,ALL,SOME -> NONE -> ALL,SOME
  145. // 否则按照设置的flag为准:SOME,ALL,NONE
  146. check : function(forParent, forChildren) {
  147. var flag = null;
  148. var cls = this.checkboxImg.className;
  149. flag = (cls == "x-tree-node-checkbox-none") ? "x-tree-node-checkbox-all" : "x-tree-node-checkbox-none";
  150. /*
  151. if (this.node.isLeaf) {
  152. flag = (cls == "x-tree-node-checkbox-all") ? "x-tree-node-checkbox-none" : "x-tree-node-checkbox-all";
  153. } else {
  154. if (cls == 'x-tree-node-checkbox-none') {
  155. // 全部选中
  156. flag = 'x-tree-node-checkbox-all';
  157. } else {
  158. // 全部反选
  159. flag = 'x-tree-node-checkbox-none';
  160. }
  161. }
  162. */
  163. if (typeof forParent == "undefined" || forParent == null) {
  164. forParent = (typeof this.node.parentNode != "undefined") && (this.node.parentNode != null);
  165. }
  166. if (typeof forChildren == "undefined" || forChildren == null) {
  167. forChildren = !this.node.ifLeaf;
  168. }
  169. //Ext.log(this);
  170. //Ext.log(flag + "," + forParent + "," + forChildren);
  171. var n = this.node;
  172. var tree = n.getOwnerTree();
  173. var parentNode = n.parentNode;
  174. // 如果下级节点都尚未渲染过,就展开当前节点,并渲染下面的所有节点
  175. if (!n.isLeaf && !n.expanded && !n.childrenRendered) {
  176. n.expand(false, false, this.check.createDelegate(this, [forParent, forChildren]));
  177. return;
  178. }
  179. // 如果包含子节点
  180. if (forChildren && !n.isLeaf) {
  181. var cs = n.childNodes;
  182. for(var i = 0; i < cs.length; i++) {
  183. cs[i].getUI().checkChild(flag == "x-tree-node-checkbox-all");
  184. }
  185. }
  186. //this.checkboxImg.className = "x-tree-node-checkbox-" + this.isAllChildrenChecked();
  187. this.checkboxImg.className = flag;
  188. if (this.checkboxImg.className == "x-tree-node-checkbox-none") {
  189. this.node.attributes.checked = false;
  190. } else {
  191. this.node.attributes.checked = true;
  192. }
  193. if (parentNode.getUI().checkParent) {
  194. parentNode.getUI().checkParent();
  195. }
  196. }
  197. , checkParent : function() {
  198. this.checkboxImg.className = "x-tree-node-checkbox-" + this.isAllChildrenChecked();
  199. if (this.checkboxImg.className == "x-tree-node-checkbox-none") {
  200. this.node.attributes.checked = false;
  201. } else {
  202. this.node.attributes.checked = true;
  203. }
  204. if (this.node.parentNode.getUI().checkParent) {
  205. this.node.parentNode.getUI().checkParent();
  206. }
  207. }
  208. , checkChild : function(flag) {
  209. this.node.attributes.checked = flag;
  210. if (!node.isLeaf) {
  211. node.getUI().checkChild(flag);
  212. }
  213. }
  214. , onDblClick : function(e){
  215. e.preventDefault();
  216. if(this.disabled){
  217. return;
  218. }
  219. if(this.checkbox){
  220. this.toggleCheck();
  221. }
  222. //if(!this.animating && this.node.hasChildNodes()){
  223. // this.node.toggle();
  224. //}
  225. this.fireEvent("dblclick", this.node, e);
  226. }
  227. , toggleCheck : function(state) {
  228. this.check();
  229. }
  230. // 是否为叶子节点
  231. , isLeaf : function() {
  232. return this.node.attributes.leaf;
  233. }
  234. // 获得子节点
  235. , getChildren : function() {
  236. return this.node.attributes.children;
  237. }
  238. // 是否为选中状态
  239. , isChecked : function() {
  240. return this.node.attributes.checked;
  241. }
  242. // 获得className
  243. , getClassName : function() {
  244. return this.checkboxImg.className;
  245. }
  246. // 初始化时,决定显示的图片
  247. , isAllChildrenChecked : function() {
  248. if (!this.isLeaf()) {
  249. try{
  250. var status = this.alreadyCheckAllChildren(this.getChildren(), this.isChecked());
  251. return 'x-tree-node-checkbox-' + status;
  252. } catch(e){
  253. console.debug(e);
  254. }
  255. } else {
  256. return this.isChecked() ? "x-tree-node-checkbox-all" : "x-tree-node-checkbox-none";
  257. }
  258. }
  259. // 计算点击后,应该显示的图片
  260. , getNextStatus : function() {
  261. return this.getClassName() == 'x-tree-node-checkbox-none' ?
  262. 'x-tree-node-checkbox-all' :
  263. 'x-tree-node-checkbox-none';
  264. /*
  265. if (this.isLeaf()) {
  266. return this.getClassName() == "x-tree-node-checkbox-all" ? "x-tree-node-checkbox-none" : "x-tree-node-checkbox-all";
  267. } else {
  268. if (this.getClassName() == "x-tree-node-checkbox-none") {
  269. return "x-tree-node-checkbox-all";
  270. } else {
  271. return "x-tree-node-checkbox-none";
  272. }
  273. if (this.getClassName() == "x-tree-node-checkbox-all") {
  274. return "x-tree-node-checkbox-none";
  275. } else if (this.getClassName() == "x-tree-node-checkbox-none") {
  276. return "x-tree-node-checkbox-some";
  277. } else {
  278. return "x-tree-node-checkbox-all";
  279. }
  280. }
  281. */
  282. }
  283. , alreadyCheckAllChildren : function(children, isChecked) {
  284. //console.error(children);
  285. //console.debug(isChecked);
  286. var all = 0;
  287. var some = 0;
  288. if (children) {
  289. for (var i = 0; i < children.length; i++) {
  290. var c = children[i];
  291. if (c.leaf) {
  292. if (c.checked) {
  293. all++;
  294. }
  295. } else {
  296. if (this.alreadyCheckAllChildren(c.children, c.checked) == 'all') {
  297. all++;
  298. } else if (this.alreadyCheckAllChildren(c.children, c.checked) == 'some') {
  299. some++;
  300. }
  301. }
  302. }
  303. if (all == children.length) {
  304. return 'all'
  305. } if (isChecked || (all + some > 0)) {
  306. return 'some';
  307. } else {
  308. return 'none';
  309. }
  310. } else {
  311. if (isChecked) {
  312. return 'all';
  313. } else {
  314. return 'none';
  315. }
  316. }
  317. }
  318. // 改变父节点的className
  319. , changeParentStatus : function() {
  320. var status = this.alreadyCheckAllChildren(this.getChildren());
  321. return 'x-tree-node-checkbox-' + status;
  322. }
  323. // 处理点击节点事件
  324. , check : function() {
  325. this.checkboxImg.className = this.getNextStatus();
  326. this.node.attributes.checked = "x-tree-node-checkbox-none" != this.getClassName();
  327. if (this.node.parentNode.getUI().checkParent) {
  328. this.node.parentNode.getUI().checkParent();
  329. }
  330. if (!this.isLeaf()) {
  331. this.node.expand(false, false);
  332. var children = this.node.childNodes;
  333. for (var i = 0; i < children.length; i++) {
  334. children[i].getUI().checkChild("x-tree-node-checkbox-all" == this.getClassName());
  335. }
  336. }
  337. }
  338. // 点击节点后,事件传播到父节点
  339. // 向父节点传播的事件可能是all, none, some
  340. , checkParent : function() {
  341. this.checkboxImg.className = this.changeParentStatus();
  342. this.node.attributes.checked = "x-tree-node-checkbox-none" != this.getClassName();
  343. if (this.node.parentNode.getUI().checkParent) {
  344. this.node.parentNode.getUI().checkParent();
  345. }
  346. }
  347. // 点击节点后,事件传播到子节点
  348. // 向子节点传播的事件,只有all和none两种
  349. , checkChild : function(isCheck) {
  350. if (this.isLeaf()) {
  351. this.checkboxImg.className = isCheck ? "x-tree-node-checkbox-all" : "x-tree-node-checkbox-none";
  352. this.node.attributes.checked = "x-tree-node-checkbox-none" != this.checkboxImg.className;
  353. } else {
  354. if (isCheck) {
  355. // 只当下面节点没选中的时候,才强制改变成some
  356. // 否则保持some或all状态
  357. if (this.checkboxImg.className == "x-tree-node-checkbox-none") {
  358. this.checkboxImg.className = "x-tree-node-checkbox-all";
  359. }
  360. this.node.attributes.checked = true;
  361. } else {
  362. this.checkboxImg.className = "x-tree-node-checkbox-none";
  363. this.node.attributes.checked = false;
  364. this.node.expand(false, false);
  365. }
  366. var children = this.node.childNodes;
  367. for (var i = 0; i < children.length; i++) {
  368. children[i].getUI().checkChild("x-tree-node-checkbox-all" == this.getClassName());
  369. }
  370. }
  371. }
  372. });
  373. /**
  374. * @class Ext.tree.CheckNodeMultiSelectionModel
  375. * @extends Ext.tree.MultiSelectionModel
  376. * Multi selection for a TreePanel containing Ext.tree.CheckboxNodeUI.
  377. * Adds enhanced selection routines for selecting multiple items
  378. * and key processing to check/clear checkboxes.
  379. */
  380. Ext.tree.CheckNodeMultiSelectionModel = function(){
  381. Ext.tree.CheckNodeMultiSelectionModel.superclass.constructor.call(this);
  382. };
  383. Ext.extend(Ext.tree.CheckNodeMultiSelectionModel, Ext.tree.MultiSelectionModel, {
  384. init : function(tree){
  385. this.tree = tree;
  386. tree.el.on("keydown", this.onKeyDown, this);
  387. tree.on("click", this.onNodeClick, this);
  388. },
  389. /**
  390. * Handle a node click
  391. * If ctrl key is down and node is selected will unselect the node.
  392. * If the shift key is down it will create a contiguous selection
  393. * (see {@link Ext.tree.CheckNodeMultiSelectionModel#extendSelection} for the limitations)
  394. */
  395. onNodeClick : function(node, e){
  396. if( e.shiftKey && this.extendSelection(node) ) {
  397. return true;
  398. }
  399. if( e.ctrlKey && this.isSelected(node) ) {
  400. this.unselect(node);
  401. } else {
  402. this.select(node, e, e.ctrlKey);
  403. }
  404. },
  405. /**
  406. * Selects all nodes between the previously selected node and the one that the user has just selected.
  407. * Will not span multiple depths, so only children of the same parent will be selected.
  408. */
  409. extendSelection : function(node) {
  410. var last = this.lastSelNode;
  411. if( node == last || !last ) {
  412. return false; /* same selection, process normally normally */
  413. }
  414. if( node.parentNode == last.parentNode ) {
  415. var cs = node.parentNode.childNodes;
  416. var i = 0, attr='id', selecting=false, lastSelect=false;
  417. this.clearSelections(true);
  418. for( i = 0; i < cs.length; i++ ) {
  419. // We have to traverse the entire tree b/c don't know of a way to find
  420. // a numerical representation of a nodes position in a tree.
  421. if( cs[i].attributes[attr] == last.attributes[attr] || cs[i].attributes[attr] == node.attributes[attr] ) {
  422. // lastSelect ensures that we select the final node in the list
  423. lastSelect = selecting;
  424. selecting = !selecting;
  425. }
  426. if( selecting || lastSelect ) {
  427. this.select(cs[i], null, true);
  428. // if we're selecting the last node break to avoid traversing the entire tree
  429. if( lastSelect ) {
  430. break;
  431. }
  432. }
  433. }
  434. return true;
  435. } else {
  436. return false;
  437. }
  438. }
  439. });