select.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. /**
  2. * angular-strap
  3. * @version v2.3.5 - 2015-10-29
  4. * @link http://mgcrea.github.io/angular-strap
  5. * @author Olivier Louvignes <olivier@mg-crea.com> (https://github.com/mgcrea)
  6. * @license MIT License, http://www.opensource.org/licenses/MIT
  7. */
  8. 'use strict';
  9. angular.module('mgcrea.ngStrap.select', [ 'mgcrea.ngStrap.tooltip', 'mgcrea.ngStrap.helpers.parseOptions' ]).provider('$select', function() {
  10. var defaults = this.defaults = {
  11. animation: 'am-fade',
  12. prefixClass: 'select',
  13. prefixEvent: '$select',
  14. placement: 'bottom-left',
  15. templateUrl: 'select/select.tpl.html',
  16. trigger: 'focus',
  17. container: false,
  18. keyboard: true,
  19. html: false,
  20. delay: 0,
  21. multiple: false,
  22. allNoneButtons: false,
  23. sort: true,
  24. caretHtml: '&nbsp;<span class="caret"></span>',
  25. placeholder: 'Choose among the following...',
  26. allText: 'All',
  27. noneText: 'None',
  28. maxLength: 3,
  29. maxLengthHtml: 'selected',
  30. iconCheckmark: 'glyphicon glyphicon-ok'
  31. };
  32. this.$get = [ '$window', '$document', '$rootScope', '$tooltip', '$timeout', function($window, $document, $rootScope, $tooltip, $timeout) {
  33. var bodyEl = angular.element($window.document.body);
  34. var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
  35. var isTouch = 'createTouch' in $window.document && isNative;
  36. function SelectFactory(element, controller, config) {
  37. var $select = {};
  38. var options = angular.extend({}, defaults, config);
  39. $select = $tooltip(element, options);
  40. var scope = $select.$scope;
  41. scope.$matches = [];
  42. if (options.multiple) {
  43. scope.$activeIndex = [];
  44. } else {
  45. scope.$activeIndex = -1;
  46. }
  47. scope.$isMultiple = options.multiple;
  48. scope.$showAllNoneButtons = options.allNoneButtons && options.multiple;
  49. scope.$iconCheckmark = options.iconCheckmark;
  50. scope.$allText = options.allText;
  51. scope.$noneText = options.noneText;
  52. scope.$activate = function(index) {
  53. scope.$$postDigest(function() {
  54. $select.activate(index);
  55. });
  56. };
  57. scope.$select = function(index, evt) {
  58. scope.$$postDigest(function() {
  59. $select.select(index);
  60. });
  61. };
  62. scope.$isVisible = function() {
  63. return $select.$isVisible();
  64. };
  65. scope.$isActive = function(index) {
  66. return $select.$isActive(index);
  67. };
  68. scope.$selectAll = function() {
  69. for (var i = 0; i < scope.$matches.length; i++) {
  70. if (!scope.$isActive(i)) {
  71. scope.$select(i);
  72. }
  73. }
  74. };
  75. scope.$selectNone = function() {
  76. for (var i = 0; i < scope.$matches.length; i++) {
  77. if (scope.$isActive(i)) {
  78. scope.$select(i);
  79. }
  80. }
  81. };
  82. $select.update = function(matches) {
  83. scope.$matches = matches;
  84. $select.$updateActiveIndex();
  85. };
  86. $select.activate = function(index) {
  87. if (options.multiple) {
  88. $select.$isActive(index) ? scope.$activeIndex.splice(scope.$activeIndex.indexOf(index), 1) : scope.$activeIndex.push(index);
  89. if (options.sort) scope.$activeIndex.sort(function(a, b) {
  90. return a - b;
  91. });
  92. } else {
  93. scope.$activeIndex = index;
  94. }
  95. return scope.$activeIndex;
  96. };
  97. $select.select = function(index) {
  98. var value = scope.$matches[index].value;
  99. scope.$apply(function() {
  100. $select.activate(index);
  101. if (options.multiple) {
  102. controller.$setViewValue(scope.$activeIndex.map(function(index) {
  103. if (angular.isUndefined(scope.$matches[index])) {
  104. return null;
  105. }
  106. return scope.$matches[index].value;
  107. }));
  108. } else {
  109. controller.$setViewValue(value);
  110. $select.hide();
  111. }
  112. });
  113. scope.$emit(options.prefixEvent + '.select', value, index, $select);
  114. };
  115. $select.$updateActiveIndex = function() {
  116. if (options.multiple) {
  117. if (angular.isArray(controller.$modelValue)) {
  118. scope.$activeIndex = controller.$modelValue.map(function(value) {
  119. return $select.$getIndex(value);
  120. });
  121. } else {
  122. scope.$activeIndex = [];
  123. }
  124. } else {
  125. if (angular.isDefined(controller.$modelValue) && scope.$matches.length) {
  126. scope.$activeIndex = $select.$getIndex(controller.$modelValue);
  127. } else {
  128. scope.$activeIndex = -1;
  129. }
  130. }
  131. };
  132. $select.$isVisible = function() {
  133. if (!options.minLength || !controller) {
  134. return scope.$matches.length;
  135. }
  136. return scope.$matches.length && controller.$viewValue.length >= options.minLength;
  137. };
  138. $select.$isActive = function(index) {
  139. if (options.multiple) {
  140. return scope.$activeIndex.indexOf(index) !== -1;
  141. } else {
  142. return scope.$activeIndex === index;
  143. }
  144. };
  145. $select.$getIndex = function(value) {
  146. var l = scope.$matches.length, i = l;
  147. if (!l) return;
  148. for (i = l; i--; ) {
  149. if (scope.$matches[i].value === value) break;
  150. }
  151. if (i < 0) return;
  152. return i;
  153. };
  154. $select.$onMouseDown = function(evt) {
  155. evt.preventDefault();
  156. evt.stopPropagation();
  157. if (isTouch) {
  158. var targetEl = angular.element(evt.target);
  159. targetEl.triggerHandler('click');
  160. }
  161. };
  162. $select.$onKeyDown = function(evt) {
  163. if (!/(9|13|38|40)/.test(evt.keyCode)) return;
  164. if (evt.keyCode !== 9) {
  165. evt.preventDefault();
  166. evt.stopPropagation();
  167. }
  168. if (options.multiple && evt.keyCode === 9) {
  169. return $select.hide();
  170. }
  171. if (!options.multiple && (evt.keyCode === 13 || evt.keyCode === 9)) {
  172. return $select.select(scope.$activeIndex);
  173. }
  174. if (!options.multiple) {
  175. if (evt.keyCode === 38 && scope.$activeIndex > 0) scope.$activeIndex--; else if (evt.keyCode === 38 && scope.$activeIndex < 0) scope.$activeIndex = scope.$matches.length - 1; else if (evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1) scope.$activeIndex++; else if (angular.isUndefined(scope.$activeIndex)) scope.$activeIndex = 0;
  176. scope.$digest();
  177. }
  178. };
  179. $select.$isIE = function() {
  180. var ua = $window.navigator.userAgent;
  181. return ua.indexOf('MSIE ') > 0 || ua.indexOf('Trident/') > 0 || ua.indexOf('Edge/') > 0;
  182. };
  183. $select.$selectScrollFix = function(e) {
  184. if ($document[0].activeElement.tagName === 'UL') {
  185. e.preventDefault();
  186. e.stopImmediatePropagation();
  187. e.target.focus();
  188. }
  189. };
  190. var _show = $select.show;
  191. $select.show = function() {
  192. _show();
  193. if (options.multiple) {
  194. $select.$element.addClass('select-multiple');
  195. }
  196. $timeout(function() {
  197. $select.$element.on(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown);
  198. if (options.keyboard) {
  199. element.on('keydown', $select.$onKeyDown);
  200. }
  201. }, 0, false);
  202. };
  203. var _hide = $select.hide;
  204. $select.hide = function() {
  205. if (!options.multiple && angular.isUndefined(controller.$modelValue)) {
  206. scope.$activeIndex = -1;
  207. }
  208. $select.$element.off(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown);
  209. if (options.keyboard) {
  210. element.off('keydown', $select.$onKeyDown);
  211. }
  212. _hide(true);
  213. };
  214. return $select;
  215. }
  216. SelectFactory.defaults = defaults;
  217. return SelectFactory;
  218. } ];
  219. }).directive('bsSelect', [ '$window', '$parse', '$q', '$select', '$parseOptions', function($window, $parse, $q, $select, $parseOptions) {
  220. var defaults = $select.defaults;
  221. return {
  222. restrict: 'EAC',
  223. require: 'ngModel',
  224. link: function postLink(scope, element, attr, controller) {
  225. var options = {
  226. scope: scope,
  227. placeholder: defaults.placeholder
  228. };
  229. angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'placeholder', 'allNoneButtons', 'maxLength', 'maxLengthHtml', 'allText', 'noneText', 'iconCheckmark', 'autoClose', 'id', 'sort', 'caretHtml', 'prefixClass', 'prefixEvent' ], function(key) {
  230. if (angular.isDefined(attr[key])) options[key] = attr[key];
  231. });
  232. var falseValueRegExp = /^(false|0|)$/i;
  233. angular.forEach([ 'html', 'container', 'allNoneButtons', 'sort' ], function(key) {
  234. if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;
  235. });
  236. var dataMultiple = element.attr('data-multiple');
  237. if (angular.isDefined(dataMultiple)) {
  238. if (falseValueRegExp.test(dataMultiple)) options.multiple = false; else options.multiple = dataMultiple;
  239. }
  240. if (element[0].nodeName.toLowerCase() === 'select') {
  241. var inputEl = element;
  242. inputEl.css('display', 'none');
  243. element = angular.element('<button type="button" class="btn btn-default"></button>');
  244. inputEl.after(element);
  245. }
  246. var parsedOptions = $parseOptions(attr.bsOptions);
  247. var select = $select(element, controller, options);
  248. if (select.$isIE()) {
  249. element[0].addEventListener('blur', select.$selectScrollFix);
  250. }
  251. var watchedOptions = parsedOptions.$match[7].replace(/\|.+/, '').trim();
  252. scope.$watchCollection(watchedOptions, function(newValue, oldValue) {
  253. parsedOptions.valuesFn(scope, controller).then(function(values) {
  254. select.update(values);
  255. controller.$render();
  256. });
  257. });
  258. scope.$watch(attr.ngModel, function(newValue, oldValue) {
  259. select.$updateActiveIndex();
  260. controller.$render();
  261. }, true);
  262. controller.$render = function() {
  263. var selected, index;
  264. if (options.multiple && angular.isArray(controller.$modelValue)) {
  265. selected = controller.$modelValue.map(function(value) {
  266. index = select.$getIndex(value);
  267. return angular.isDefined(index) ? select.$scope.$matches[index].label : false;
  268. }).filter(angular.isDefined);
  269. if (selected.length > (options.maxLength || defaults.maxLength)) {
  270. selected = selected.length + ' ' + (options.maxLengthHtml || defaults.maxLengthHtml);
  271. } else {
  272. selected = selected.join(', ');
  273. }
  274. } else {
  275. index = select.$getIndex(controller.$modelValue);
  276. selected = angular.isDefined(index) ? select.$scope.$matches[index].label : false;
  277. }
  278. element.html((selected ? selected : options.placeholder) + (options.caretHtml ? options.caretHtml : defaults.caretHtml));
  279. };
  280. if (options.multiple) {
  281. controller.$isEmpty = function(value) {
  282. return !value || value.length === 0;
  283. };
  284. }
  285. scope.$on('$destroy', function() {
  286. if (select) select.destroy();
  287. options = null;
  288. select = null;
  289. });
  290. }
  291. };
  292. } ]);