collapse.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. 'use strict';
  2. angular.module('mgcrea.ngStrap.collapse', [])
  3. .provider('$collapse', function () {
  4. var defaults = this.defaults = {
  5. animation: 'am-collapse',
  6. disallowToggle: false,
  7. activeClass: 'in',
  8. startCollapsed: false,
  9. allowMultiple: false
  10. };
  11. var controller = this.controller = function ($scope, $element, $attrs) {
  12. var self = this;
  13. // Attributes options
  14. self.$options = angular.copy(defaults);
  15. angular.forEach(['animation', 'disallowToggle', 'activeClass', 'startCollapsed', 'allowMultiple'], function (key) {
  16. if (angular.isDefined($attrs[key])) self.$options[key] = $attrs[key];
  17. });
  18. // use string regex match boolean attr falsy values, leave truthy values be
  19. var falseValueRegExp = /^(false|0|)$/i;
  20. angular.forEach(['disallowToggle', 'startCollapsed', 'allowMultiple'], function (key) {
  21. if (angular.isDefined($attrs[key]) && falseValueRegExp.test($attrs[key])) {
  22. self.$options[key] = false;
  23. }
  24. });
  25. self.$toggles = [];
  26. self.$targets = [];
  27. self.$viewChangeListeners = [];
  28. self.$registerToggle = function (element) {
  29. self.$toggles.push(element);
  30. };
  31. self.$registerTarget = function (element) {
  32. self.$targets.push(element);
  33. };
  34. self.$unregisterToggle = function (element) {
  35. var index = self.$toggles.indexOf(element);
  36. // remove toggle from $toggles array
  37. self.$toggles.splice(index, 1);
  38. };
  39. self.$unregisterTarget = function (element) {
  40. var index = self.$targets.indexOf(element);
  41. // remove element from $targets array
  42. self.$targets.splice(index, 1);
  43. if (self.$options.allowMultiple) {
  44. // remove target index from $active array values
  45. deactivateItem(element);
  46. }
  47. // fix active item indexes
  48. fixActiveItemIndexes(index);
  49. self.$viewChangeListeners.forEach(function (fn) {
  50. fn();
  51. });
  52. };
  53. // use array to store all the currently open panels
  54. self.$targets.$active = !self.$options.startCollapsed ? [0] : [];
  55. self.$setActive = $scope.$setActive = function (value) {
  56. if (angular.isArray(value)) {
  57. self.$targets.$active = value;
  58. } else if (!self.$options.disallowToggle && isActive(value)) {
  59. deactivateItem(value);
  60. } else {
  61. activateItem(value);
  62. }
  63. self.$viewChangeListeners.forEach(function (fn) {
  64. fn();
  65. });
  66. };
  67. self.$activeIndexes = function () {
  68. if (self.$options.allowMultiple) {
  69. return self.$targets.$active;
  70. }
  71. return self.$targets.$active.length === 1 ? self.$targets.$active[0] : -1;
  72. };
  73. function fixActiveItemIndexes (index) {
  74. // item with index was removed, so we
  75. // need to adjust other items index values
  76. var activeIndexes = self.$targets.$active;
  77. for (var i = 0; i < activeIndexes.length; i++) {
  78. if (index < activeIndexes[i]) {
  79. activeIndexes[i] = activeIndexes[i] - 1;
  80. }
  81. // the last item is active, so we need to
  82. // adjust its index
  83. if (activeIndexes[i] === self.$targets.length) {
  84. activeIndexes[i] = self.$targets.length - 1;
  85. }
  86. }
  87. }
  88. function isActive (value) {
  89. var activeItems = self.$targets.$active;
  90. return activeItems.indexOf(value) !== -1;
  91. }
  92. function deactivateItem (value) {
  93. var index = self.$targets.$active.indexOf(value);
  94. if (index !== -1) {
  95. self.$targets.$active.splice(index, 1);
  96. }
  97. }
  98. function activateItem (value) {
  99. if (!self.$options.allowMultiple) {
  100. // remove current selected item
  101. self.$targets.$active.splice(0, 1);
  102. }
  103. if (self.$targets.$active.indexOf(value) === -1) {
  104. self.$targets.$active.push(value);
  105. }
  106. }
  107. };
  108. this.$get = function () {
  109. var $collapse = {};
  110. $collapse.defaults = defaults;
  111. $collapse.controller = controller;
  112. return $collapse;
  113. };
  114. })
  115. .directive('bsCollapse', function ($window, $animate, $collapse) {
  116. return {
  117. require: ['?ngModel', 'bsCollapse'],
  118. controller: ['$scope', '$element', '$attrs', $collapse.controller],
  119. link: function postLink (scope, element, attrs, controllers) {
  120. var ngModelCtrl = controllers[0];
  121. var bsCollapseCtrl = controllers[1];
  122. if (ngModelCtrl) {
  123. // Update the modelValue following
  124. bsCollapseCtrl.$viewChangeListeners.push(function () {
  125. ngModelCtrl.$setViewValue(bsCollapseCtrl.$activeIndexes());
  126. });
  127. // modelValue -> $formatters -> viewValue
  128. ngModelCtrl.$formatters.push(function (modelValue) {
  129. // console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue);
  130. if (angular.isArray(modelValue)) {
  131. // model value is an array, so just replace
  132. // the active items directly
  133. bsCollapseCtrl.$setActive(modelValue);
  134. } else {
  135. var activeIndexes = bsCollapseCtrl.$activeIndexes();
  136. if (angular.isArray(activeIndexes)) {
  137. // we have an array of selected indexes
  138. if (activeIndexes.indexOf(modelValue * 1) === -1) {
  139. // item with modelValue index is not active
  140. bsCollapseCtrl.$setActive(modelValue * 1);
  141. }
  142. } else if (activeIndexes !== modelValue * 1) {
  143. bsCollapseCtrl.$setActive(modelValue * 1);
  144. }
  145. }
  146. return modelValue;
  147. });
  148. }
  149. }
  150. };
  151. })
  152. .directive('bsCollapseToggle', function () {
  153. return {
  154. require: ['^?ngModel', '^bsCollapse'],
  155. link: function postLink (scope, element, attrs, controllers) {
  156. // var ngModelCtrl = controllers[0];
  157. var bsCollapseCtrl = controllers[1];
  158. // Add base attr
  159. element.attr('data-toggle', 'collapse');
  160. // Push pane to parent bsCollapse controller
  161. bsCollapseCtrl.$registerToggle(element);
  162. // remove toggle from collapse controller when toggle is destroyed
  163. scope.$on('$destroy', function () {
  164. bsCollapseCtrl.$unregisterToggle(element);
  165. });
  166. element.on('click', function () {
  167. if (!attrs.disabled) {
  168. var index = attrs.bsCollapseToggle && attrs.bsCollapseToggle !== 'bs-collapse-toggle' ? attrs.bsCollapseToggle : bsCollapseCtrl.$toggles.indexOf(element);
  169. bsCollapseCtrl.$setActive(index * 1);
  170. scope.$apply();
  171. }
  172. });
  173. }
  174. };
  175. })
  176. .directive('bsCollapseTarget', function ($animate) {
  177. return {
  178. require: ['^?ngModel', '^bsCollapse'],
  179. // scope: true,
  180. link: function postLink (scope, element, attrs, controllers) {
  181. // var ngModelCtrl = controllers[0];
  182. var bsCollapseCtrl = controllers[1];
  183. // Add base class
  184. element.addClass('collapse');
  185. // Add animation class
  186. if (bsCollapseCtrl.$options.animation) {
  187. element.addClass(bsCollapseCtrl.$options.animation);
  188. }
  189. // Push pane to parent bsCollapse controller
  190. bsCollapseCtrl.$registerTarget(element);
  191. // remove pane target from collapse controller when target is destroyed
  192. scope.$on('$destroy', function () {
  193. bsCollapseCtrl.$unregisterTarget(element);
  194. });
  195. function render () {
  196. var index = bsCollapseCtrl.$targets.indexOf(element);
  197. var active = bsCollapseCtrl.$activeIndexes();
  198. var action = 'removeClass';
  199. if (angular.isArray(active)) {
  200. if (active.indexOf(index) !== -1) {
  201. action = 'addClass';
  202. }
  203. } else if (index === active) {
  204. action = 'addClass';
  205. }
  206. $animate[action](element, bsCollapseCtrl.$options.activeClass);
  207. }
  208. bsCollapseCtrl.$viewChangeListeners.push(function () {
  209. render();
  210. });
  211. render();
  212. }
  213. };
  214. });