angular-multi-select-tree-0.1.0.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. /*
  2. The MIT License (MIT)
  3. Copyright (c) 2014 Muhammed Ashik
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in all
  11. copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  18. SOFTWARE.
  19. */
  20. /*jshint indent: 2 */
  21. /*global angular: false */
  22. (function () {
  23. 'use strict';
  24. angular.module('multi-select-tree', []);
  25. }());
  26. /*jshint indent: 2 */
  27. (function () {
  28. 'use strict';
  29. angular.module('multi-select-tree').run(['$templateCache', function ($templateCache) {
  30. 'use strict';
  31. $templateCache.put('src/multi-select-tree.tpl.html',
  32. "<div class=\"tree-control\">\n" +
  33. "\n" +
  34. " <div class=\"tree-input form-control\" ng-click=\"onControlClicked($event)\">\n" +
  35. " <span ng-if=\"selectedItems.length == 0\" class=\"noselected-items\">\n" +
  36. " <span ng-bind=\"defaultLabel\" class=\"pull-left\"></span>\n" +
  37. " </span>\n" +
  38. " <span ng-if=\"selectedItems.length > 0\" class=\"selected-items\">\n" +
  39. " <span ng-repeat=\"selectedItem in selectedItems\" class=\"selected-item pull-left\">{{selectedItem.name||selectedItem[transLabel]}} <span class=\"selected-item-close\"\n" +
  40. " ng-click=\"deselectItem(selectedItem, $event)\"></span></span>\n" +
  41. " <span class=\"caret\"></span>\n" +
  42. " </span>\n" +
  43. " <!-- <input type=\"text\" class=\"blend-in\" /> -->\n" +
  44. " <i class=\"caret pull-right\"></i>\n" +
  45. " </div>\n" +
  46. " <div class=\"tree-view\" ng-show=\"showTree\">\n" +
  47. " <div class=\"helper-container\">\n" +
  48. " <div class=\"line\" data-ng-if=\"switchView\">\n" +
  49. " <button type=\"button\" ng-click=\"switchCurrentView($event);\" class=\"helper-button\">{{switchViewLabel}}</button>\n" +
  50. " </div>\n" +
  51. " <div class=\"line\">\n" +
  52. " <input placeholder=\"查询...\" type=\"text\" ng-model=\"filterKeyword\" ng-click=\"onFilterClicked($event)\"\n" +
  53. " class=\"input-filter\">\n" +
  54. " <span class=\"clear-button\" ng-click=\"clearFilter($event)\"><span class=\"item-close\"></span></span>\n" +
  55. " </div>\n" +
  56. " </div>\n" +
  57. " <ul class=\"tree-container\">\n" +
  58. " <tree-item class=\"top-level\" ng-repeat=\"item in inputModel\" item=\"item\" ng-show=\"!item.isFiltered\"\n" +
  59. " use-callback=\"useCallback\" can-select-item=\"canSelectItem\"\n" +
  60. " multi-select=\"multiSelect\" item-selected=\"itemSelected(item)\"\n" +
  61. " on-active-item=\"onActiveItem(item)\" select-only-leafs=\"selectOnlyLeafs\" trans-label=\"labelProp\"></tree-item>\n" +
  62. " </ul>\n" +
  63. " </div>\n" +
  64. "</div>\n"
  65. );
  66. $templateCache.put('src/tree-item.tpl.html',
  67. "<li>\n" +
  68. " <div class=\"item-container\" ng-class=\"{active: item.isActive, selected: item.selected}\"\n" +
  69. " ng-click=\"clickSelectItem(item, $event)\" ng-mouseover=\"onMouseOver(item, $event)\">\n" +
  70. " <span ng-if=\"showExpand(item)\" class=\"expand\" ng-class=\"{'expand-opened': item.isExpanded}\"\n" +
  71. " ng-click=\"onExpandClicked(item, $event)\"></span>\n" +
  72. "\n" +
  73. " <div class=\"item-details\"><input class=\"tree-checkbox\" type=\"checkbox\" ng-if=\"showCheckbox()\"\n" +
  74. " ng-checked=\"item.selected\"/>{{item.name||item[transLabel]}}\n" +
  75. " </div>\n" +
  76. " </div>\n" +
  77. " <ul ng-repeat=\"child in item.children\" ng-if=\"item.isExpanded\">\n" +
  78. " <tree-item item=\"child\" ng-if=\"!child.show\" item-selected=\"subItemSelected(item)\" use-callback=\"useCallback\"\n" +
  79. " can-select-item=\"canSelectItem\" multi-select=\"multiSelect\"\n" +
  80. " on-active-item=\"activeSubItem(item, $event)\" trans-label=\"transLabel\"></tree-item>\n" +
  81. " </ul>\n" +
  82. "</li>\n"
  83. );
  84. }]);
  85. }());
  86. /*global angular: false */
  87. (function () {
  88. 'use strict';
  89. var mainModule = angular.module('multi-select-tree');
  90. /**
  91. * Controller for multi select tree.
  92. */
  93. mainModule.controller('multiSelectTreeCtrl', [
  94. '$scope',
  95. '$document',
  96. '$timeout',
  97. function ($scope, $document, $timeout) {
  98. var activeItem;
  99. console.log($document)
  100. $scope.showTree = false;
  101. $scope.selectedItems = [];
  102. $scope.multiSelect = $scope.multiSelect || false;
  103. var _refreshDelayPromise;
  104. /**
  105. * refresh
  106. */
  107. $scope._refresh = function (refreshAttr) {
  108. if (refreshAttr !== undefined) {
  109. // Debounce
  110. // See https://github.com/angular-ui/bootstrap/blob/0.10.0/src/typeahead/typeahead.js#L155
  111. // FYI AngularStrap typeahead does not have debouncing: https://github.com/mgcrea/angular-strap/blob/v2.0.0-rc.4/src/typeahead/typeahead.js#L177
  112. if (_refreshDelayPromise) {
  113. $timeout.cancel(_refreshDelayPromise);
  114. }
  115. _refreshDelayPromise = $timeout(function () {
  116. $scope.refresh({
  117. items: $scope
  118. });
  119. }, $scope.refreshDelay);
  120. }
  121. };
  122. /**
  123. * Clicking on document will hide the tree.
  124. */
  125. function docClickHide() {
  126. closePopup();
  127. $scope.$apply();
  128. }
  129. /**
  130. * Closes the tree popup.
  131. */
  132. function closePopup() {
  133. $scope.showTree = false;
  134. if (activeItem) {
  135. activeItem.isActive = false;
  136. activeItem = undefined;
  137. }
  138. $document.off('click', docClickHide);
  139. }
  140. /**
  141. * Sets the active item.
  142. *
  143. * @param item the item element.
  144. */
  145. $scope.onActiveItem = function (item) {
  146. if (activeItem !== item) {
  147. if (activeItem) {
  148. activeItem.isActive = false;
  149. }
  150. activeItem = item;
  151. activeItem.isActive = true;
  152. }
  153. };
  154. /**
  155. * Copies the selectedItems in to output model.
  156. */
  157. $scope.refreshOutputModel = function () {
  158. var outModel;
  159. if ($scope.multiSelect) {
  160. outModel = angular.copy($scope.selectedItems);
  161. angular.forEach(outModel, function (item) {
  162. delete item.selected;
  163. })
  164. $scope.outputModel = outModel;
  165. } else {
  166. if ($scope.selectedItems.length > 0) {
  167. outModel = angular.copy($scope.selectedItems[0]);
  168. delete outModel.selected;
  169. }
  170. }
  171. $scope.outputModel = outModel; //angular.copy($scope.selectedItems);
  172. };
  173. /**
  174. * Refreshes the selected Items model.
  175. */
  176. $scope.$root.refreshSelectedItems =$scope.refreshSelectedItems = function () {
  177. $scope.selectedItems = [];
  178. if ($scope.inputModel) {
  179. setSelectedChildren($scope.inputModel);
  180. }
  181. };
  182. /**
  183. * Iterates over children and sets the selected items.
  184. *
  185. * @param children the children element.
  186. */
  187. function setSelectedChildren(children) {
  188. for (var i = 0, len = children.length; i < len; i++) {
  189. if (!isItemSelected(children[i]) && children[i].selected === true) {
  190. $scope.selectedItems.push(children[i]);
  191. } else if (isItemSelected(children[i]) && children[i].selected === false) {
  192. children[i].selected = true;
  193. }
  194. if (children[i] && children[i].children) {
  195. setSelectedChildren(children[i].children);
  196. }
  197. }
  198. }
  199. /**
  200. * Checks of the item is already selected.
  201. *
  202. * @param item the item to be checked.
  203. * @return {boolean} if the item is already selected.
  204. */
  205. function isItemSelected(item) {
  206. var isSelected = false;
  207. if ($scope.selectedItems) {
  208. for (var i = 0; i < $scope.selectedItems.length; i++) {
  209. if ($scope.selectedItems[i].id === item.id) {
  210. isSelected = true;
  211. break;
  212. }
  213. }
  214. }
  215. return isSelected;
  216. }
  217. /**
  218. * Deselect the item.
  219. *
  220. * @param item the item element
  221. * @param $event
  222. */
  223. $scope.deselectItem = function (item, $event) {
  224. $event.stopPropagation();
  225. if ($scope.itemSelected) {
  226. $scope.itemSelected({
  227. item: item
  228. });
  229. }
  230. $scope.selectedItems.splice($scope.selectedItems.indexOf(item), 1);
  231. item.selected = false;
  232. this.refreshOutputModel();
  233. };
  234. /**
  235. * Swap the tree popup on control click event.
  236. *
  237. * @param $event the click event.
  238. */
  239. $scope.onControlClicked = function ($event) {
  240. $event.stopPropagation();
  241. $scope.showTree = !$scope.showTree;
  242. if ($scope.showTree) {
  243. $document.on('click', docClickHide);
  244. }
  245. };
  246. /**
  247. * Stop the event on filter clicked.
  248. *
  249. * @param $event the click event
  250. */
  251. $scope.onFilterClicked = function ($event) {
  252. $event.stopPropagation();
  253. };
  254. /**
  255. * Clears the filter text.
  256. *
  257. * @param $event the click event
  258. */
  259. $scope.clearFilter = function ($event) {
  260. $event.stopPropagation();
  261. $scope.filterKeyword = '';
  262. };
  263. /**
  264. * Wrapper function for can select item callback.
  265. *
  266. * @param item the item
  267. */
  268. $scope.canSelectItem = function (item) {
  269. return $scope.callback({
  270. item: item,
  271. selectedItems: $scope.selectedItems
  272. });
  273. };
  274. /**
  275. * The callback is used to switch the views.
  276. * based on the view type.
  277. *
  278. * @param $event the event object.
  279. */
  280. $scope.switchCurrentView = function ($event) {
  281. $event.stopPropagation();
  282. $scope.switchViewCallback({
  283. scopeObj: $scope
  284. });
  285. };
  286. /**
  287. * Handles the item select event.
  288. *
  289. * @param item the selected item.
  290. */
  291. $scope.itemSelected = function (item) {
  292. if ($scope.useCallback && $scope.canSelectItem(item) === false || $scope.selectOnlyLeafs && item.children && item.children.length > 0) {
  293. return;
  294. }
  295. if (!$scope.multiSelect) {
  296. closePopup();
  297. for (var i = 0; i < $scope.selectedItems.length; i++) {
  298. $scope.selectedItems[i].selected = false;
  299. }
  300. item.selected = true;
  301. $scope.selectedItems = [];
  302. $scope.selectedItems.push(item);
  303. } else {
  304. item.selected = true;
  305. var indexOfItem = $scope.selectedItems.indexOf(item);
  306. if (isItemSelected(item)) {
  307. item.selected = false;
  308. $scope.selectedItems.splice(indexOfItem, 1);
  309. } else {
  310. $scope.selectedItems.push(item);
  311. }
  312. }
  313. this.refreshOutputModel();
  314. };
  315. }
  316. ]);
  317. /**
  318. * sortableItem directive.
  319. */
  320. mainModule.directive('multiSelectTree', function () {
  321. return {
  322. restrict: 'E',
  323. templateUrl: 'src/multi-select-tree.tpl.html',
  324. scope: {
  325. inputModel: '=',
  326. outputModel: '=?',
  327. multiSelect: '=?',
  328. switchView: '=?',
  329. switchViewLabel: '@',
  330. switchViewCallback: '&',
  331. selectOnlyLeafs: '=?',
  332. callback: '&',
  333. refresh: '&',
  334. refreshDelay: '@',
  335. defaultLabel: '@',
  336. transLabel: '@'
  337. },
  338. link: function (scope, element, attrs) {
  339. if (attrs.callback) {
  340. scope.useCallback = true;
  341. }
  342. if (attrs.transLabel) {
  343. scope.labelProp = attrs.transLabel;
  344. }
  345. //refresh
  346. attrs.$observe('refreshDelay', function () {
  347. // $eval() is needed otherwise we get a string instead of a number
  348. var refreshDelay = scope.$eval(attrs.refreshDelay);
  349. scope.refreshDelay = refreshDelay !== undefined ? refreshDelay : 100;
  350. //refresh
  351. scope._refresh(attrs.refresh);
  352. });
  353. // watch for changes in input model as a whole
  354. // this on updates the multi-select when a user load a whole new input-model.
  355. scope.$watch('inputModel', function (newVal) {
  356. if (newVal) {
  357. scope.refreshSelectedItems();
  358. scope.refreshOutputModel();
  359. }
  360. });
  361. /**
  362. * Checks whether any of children match the keyword.
  363. *
  364. * @param item the parent item
  365. * @param keyword the filter keyword
  366. * @returns {boolean} false if matches.
  367. */
  368. function isChildrenFiltered(item, keyword) {
  369. var childNodes = getAllChildNodesFromNode(item, []);
  370. for (var i = 0, len = childNodes.length; i < len; i++) {
  371. if (childNodes[i][scope.labelProp || 'name'].indexOf(keyword) !== -1) {
  372. //if (childNodes[i].name.toLowerCase().indexOf(keyword.toLowerCase()) !== -1) {
  373. return false;
  374. }
  375. }
  376. return true;
  377. }
  378. /**
  379. * Return all childNodes of a given node (as Array of Nodes)
  380. */
  381. function getAllChildNodesFromNode(node, childNodes) {
  382. for (var i = 0; i < node.children.length; i++) {
  383. childNodes.push(node.children[i]);
  384. // add the childNodes from the children if available
  385. getAllChildNodesFromNode(node.children[i], childNodes);
  386. }
  387. return childNodes;
  388. }
  389. // 树形组件输入框输入
  390. scope.$watch('filterKeyword', function () {
  391. if (scope.filterKeyword !== undefined) {
  392. if (scope.$root.bala1) {
  393. scope.$root.bala1(scope, function (list) {
  394. scope.inputModel = scope.selectModel = list;
  395. var vals = [];
  396. if (scope.filterKeyword !== undefined) {
  397. setTimeout(() => {
  398. angular.forEach(scope.inputModel, function (item, index) {
  399. item.isExpanded = true;
  400. });
  401. }, 1);
  402. //console.log(vals);
  403. //if (vals && vals.length > 0) {
  404. // console.log(vals);
  405. setTimeout(() => {
  406. angular.forEach(scope.inputModel, function (item, index) {
  407. //item.isExpanded=true;
  408. if (item.children && item.children.length > 0) {
  409. item.children.forEach((val, idx) => {
  410. vals.push(val);
  411. })
  412. }
  413. });
  414. setTimeout(() => {
  415. if (vals && vals.length > 0) {
  416. vals.forEach((val, idx) => {
  417. val.isExpanded = true;
  418. })
  419. }
  420. }, 600);
  421. }, 1);
  422. }
  423. })
  424. }
  425. }
  426. // console.log(scope.$root.bala1)
  427. });
  428. },
  429. controller: 'multiSelectTreeCtrl'
  430. };
  431. });
  432. }());
  433. /*jshint indent: 2 */
  434. /*global angular: false */
  435. (function () {
  436. 'use strict';
  437. var mainModule = angular.module('multi-select-tree');
  438. /**
  439. * Controller for sortable item.
  440. *
  441. * @param $scope - drag item scope
  442. */
  443. mainModule.controller('treeItemCtrl', [
  444. '$scope',
  445. function ($scope) {
  446. $scope.item.isExpanded = false;
  447. /**
  448. * Shows the expand option.
  449. *
  450. * @param item the item
  451. * @returns {*|boolean}
  452. */
  453. $scope.showExpand = function (item) {
  454. return item.children && item.children.length > 0;
  455. };
  456. /**
  457. * On expand clicked toggle the option.
  458. *
  459. * @param item the item
  460. * @param $event
  461. */
  462. $scope.onExpandClicked = function (item, $event) {
  463. if ($scope.$root.checkClicked) {
  464. $scope.$root.checkClicked(item)
  465. }
  466. $event.stopPropagation();
  467. item.isExpanded = !item.isExpanded;
  468. };
  469. /**
  470. * Event on click of select item.
  471. *
  472. * @param item the item
  473. * @param $event
  474. */
  475. $scope.clickSelectItem = function (item, $event) {
  476. $scope.onExpandClicked(item, $event);
  477. $event.stopPropagation();
  478. if ($scope.itemSelected) {
  479. $scope.itemSelected({
  480. item: item
  481. });
  482. }
  483. };
  484. /**
  485. * Is leaf selected.
  486. *
  487. * @param item the item
  488. * @param $event
  489. */
  490. $scope.subItemSelected = function (item, $event) {
  491. if ($scope.itemSelected) {
  492. $scope.itemSelected({
  493. item: item
  494. });
  495. }
  496. };
  497. /**
  498. * Active sub item.
  499. *
  500. * @param item the item
  501. * @param $event
  502. */
  503. $scope.activeSubItem = function (item, $event) {
  504. if ($scope.onActiveItem) {
  505. $scope.onActiveItem({
  506. item: item
  507. });
  508. }
  509. };
  510. /**
  511. * On mouse over event.
  512. *
  513. * @param item the item
  514. * @param $event
  515. */
  516. $scope.onMouseOver = function (item, $event) {
  517. $event.stopPropagation();
  518. if ($scope.onActiveItem) {
  519. $scope.onActiveItem({
  520. item: item
  521. });
  522. }
  523. };
  524. /**
  525. * Can select item.
  526. *
  527. * @returns {*}
  528. */
  529. $scope.showCheckbox = function () {
  530. if (!$scope.multiSelect) {
  531. return false;
  532. }
  533. if ($scope.selectOnlyLeafs) {
  534. return false;
  535. }
  536. if ($scope.useCallback) {
  537. return $scope.canSelectItem($scope.item);
  538. }
  539. };
  540. }
  541. ]);
  542. /**
  543. * sortableItem directive.
  544. */
  545. mainModule.directive('treeItem', [
  546. '$compile',
  547. function ($compile) {
  548. return {
  549. restrict: 'E',
  550. templateUrl: 'src/tree-item.tpl.html',
  551. scope: {
  552. item: '=',
  553. itemSelected: '&',
  554. onActiveItem: '&',
  555. multiSelect: '=?',
  556. selectOnlyLeafs: '=?',
  557. isActive: '=',
  558. useCallback: '=',
  559. canSelectItem: '=',
  560. transLabel: '='
  561. },
  562. controller: 'treeItemCtrl',
  563. compile: function (element, attrs, link) {
  564. // Normalize the link parameter
  565. if (angular.isFunction(link)) {
  566. link = {
  567. post: link
  568. };
  569. }
  570. // Break the recursion loop by removing the contents
  571. var contents = element.contents().remove();
  572. var compiledContents;
  573. return {
  574. pre: link && link.pre ? link.pre : null,
  575. post: function (scope, element, attrs) {
  576. // Compile the contents
  577. if (!compiledContents) {
  578. compiledContents = $compile(contents);
  579. }
  580. // Re-add the compiled contents to the element
  581. compiledContents(scope, function (clone) {
  582. element.append(clone);
  583. });
  584. // Call the post-linking function, if any
  585. if (link && link.post) {
  586. link.post.apply(null, arguments);
  587. }
  588. }
  589. };
  590. }
  591. };
  592. }
  593. ]);
  594. }());