angular-nestable.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /**
  2. * Angular nestable 0.0.1
  3. * Copyright (c) 2014 Kamil Pekala
  4. * https://github.com/kamilkp/ng-nestable
  5. */
  6. /**
  7. * Sample output HTML
  8. <div class="dd">
  9. <ol class="dd-list">
  10. <li class="dd-item" data-id="1">
  11. <!-- item element -->
  12. </li>
  13. <li class="dd-item" data-id="2">
  14. <!-- item element -->
  15. </li>
  16. <li class="dd-item" data-id="3">
  17. <!-- item element -->
  18. <ol class="dd-list">
  19. <li class="dd-item" data-id="4">
  20. <!-- item element -->
  21. </li>
  22. <li class="dd-item" data-id="5">
  23. <!-- item element -->
  24. </li>
  25. </ol>
  26. </li>
  27. </ol>
  28. </div>
  29. */
  30. /**
  31. * Sample model object
  32. [
  33. {
  34. item: {},
  35. children: []
  36. },
  37. {
  38. item: {},
  39. children: [
  40. {
  41. item: {},
  42. children: []
  43. }
  44. ]
  45. },
  46. {
  47. item: {},
  48. children: []
  49. }
  50. ]
  51. */
  52. ;(function(window, document, angular, undefined){
  53. angular.module('ng-nestable', [])
  54. .provider('$nestable', function(){
  55. var modelName = '$item';
  56. var defaultOptions = {};
  57. this.$get = function(){
  58. return {
  59. modelName: modelName,
  60. defaultOptions: defaultOptions
  61. };
  62. };
  63. /**
  64. * Method to set model variable for nestable elements
  65. * @param {[string]} value
  66. */
  67. this.modelName = function(value){
  68. modelName = value;
  69. };
  70. /**
  71. * Method to set default nestable options
  72. * @param {[object]} value
  73. * You can change the follow options:
  74. maxDepth : number of levels an item can be nested (default 5)
  75. group : group ID to allow dragging between lists (default 0)
  76. listNodeName : The HTML element to create for lists (default 'ol')
  77. itemNodeName : The HTML element to create for list items (default 'li')
  78. rootClass : The class of the root element .nestable() was used on (default 'dd')
  79. listClass : The class of all list elements (default 'dd-list')
  80. itemClass : The class of all list item elements (default 'dd-item')
  81. dragClass : The class applied to the list element that is being dragged (default 'dd-dragel')
  82. handleClass : The class of the content element inside each list item (default 'dd-handle')
  83. collapsedClass : The class applied to lists that have been collapsed (default 'dd-collapsed')
  84. placeClass : The class of the placeholder element (default 'dd-placeholder')
  85. emptyClass : The class used for empty list placeholder elements (default 'dd-empty')
  86. expandBtnHTML : The HTML text used to generate a list item expand button (default '<button data-action="expand">Expand></button>')
  87. collapseBtnHTML : The HTML text used to generate a list item collapse button (default '<button data-action="collapse">Collapse</button>')
  88. */
  89. this.defaultOptions = function(value){
  90. defaultOptions = value;
  91. };
  92. })
  93. .directive('ngNestable', ['$compile', '$nestable', function($compile, $nestable){
  94. return {
  95. restrict: 'A',
  96. require: 'ngModel',
  97. compile: function(element){
  98. var itemTemplate = element.html();
  99. element.empty();
  100. return function($scope, $element, $attrs, $ngModel){
  101. var options = $.extend(
  102. {},
  103. $nestable.defaultOptions,
  104. $scope.$eval($attrs.ngNestable)
  105. );
  106. $scope.$watchCollection(function(){
  107. return $ngModel.$modelValue;
  108. }, function(model){
  109. if(model){
  110. /**
  111. * we are running the formatters here instead of watching on $viewValue because our model is an Array
  112. * and angularjs ngModel watcher watches for "shallow" changes and otherwise the possible formatters wouldn't
  113. * get executed
  114. */
  115. model = runFormatters(model, $ngModel);
  116. // TODO: optimize as rebuilding is not necessary here
  117. var root = buildNestableHtml(model, itemTemplate);
  118. $element.empty().append(root);
  119. $compile(root)($scope);
  120. root.nestable(options);
  121. root.on('change', function(){
  122. $ngModel.$setViewValue(root.nestable('serialize'));
  123. $scope && $scope.$root && $scope.$root.$$phase || $scope.$apply();
  124. });
  125. }
  126. });
  127. };
  128. },
  129. controller: angular.noop
  130. };
  131. function buildNestableHtml(model, tpl){
  132. var root = $('<div class="dd"></div>');
  133. var rootList = $('<ol class="dd-list"></ol>').appendTo(root);
  134. model.forEach(function f(item){
  135. var list = Array.prototype.slice.call(arguments).slice(-1)[0];
  136. if(!(list instanceof $)) list = rootList;
  137. var listItem = $('<li class="dd-item"></li>');
  138. var listElement = $('<div ng-nestable-item class="dd-handle"></div>');
  139. listElement.append(tpl).appendTo(listItem);
  140. list.append(listItem);
  141. listItem.data('item', item.item);
  142. if(isArray(item.children) && item.children.length > 0){
  143. var subRoot = $('<ol class="dd-list"></ol>').appendTo(listItem);
  144. item.children.forEach(function(item){
  145. f.apply(this, Array.prototype.slice.call(arguments).concat([subRoot]));
  146. });
  147. }
  148. });
  149. return root;
  150. }
  151. function isArray(arr){
  152. return Object.prototype.toString.call(arr) === '[object Array]';
  153. }
  154. function runFormatters(value, ctrl){
  155. var formatters = ctrl.$formatters,
  156. idx = formatters.length;
  157. ctrl.$modelValue = value;
  158. while(idx--) {
  159. value = formatters[idx](value);
  160. }
  161. return value;
  162. }
  163. }])
  164. .directive('ngNestableItem', ['$nestable', function($nestable){
  165. return {
  166. scope: true,
  167. require: '^ngNestable',
  168. link: function($scope, $element){
  169. $scope[$nestable.modelName] = $element.parent().data('item');
  170. }
  171. };
  172. }]);
  173. })(window, document, window.angular);