select.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. /**
  2. * angular-strap
  3. * @version v2.3.9 - 2016-06-10
  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. toggle: false
  32. };
  33. this.$get = [ '$window', '$document', '$rootScope', '$tooltip', '$timeout', function($window, $document, $rootScope, $tooltip, $timeout) {
  34. var isNative = /(ip[ao]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. if ($select.$isActive(index)) {
  89. scope.$activeIndex.splice(scope.$activeIndex.indexOf(index), 1);
  90. } else {
  91. scope.$activeIndex.push(index);
  92. }
  93. if (options.sort) scope.$activeIndex.sort(function(a, b) {
  94. return a - b;
  95. });
  96. } else {
  97. scope.$activeIndex = index;
  98. }
  99. return scope.$activeIndex;
  100. };
  101. $select.select = function(index) {
  102. if (angular.isUndefined(index) || index < 0 || index >= scope.$matches.length) {
  103. return;
  104. }
  105. var value = scope.$matches[index].value;
  106. scope.$apply(function() {
  107. $select.activate(index);
  108. if (options.multiple) {
  109. controller.$setViewValue(scope.$activeIndex.map(function(index) {
  110. if (angular.isUndefined(scope.$matches[index])) {
  111. return null;
  112. }
  113. return scope.$matches[index].value;
  114. }));
  115. } else {
  116. if (options.toggle) {
  117. controller.$setViewValue(value === controller.$modelValue ? undefined : value);
  118. } else {
  119. controller.$setViewValue(value);
  120. }
  121. $select.hide();
  122. }
  123. });
  124. scope.$emit(options.prefixEvent + '.select', value, index, $select);
  125. if (angular.isDefined(options.onSelect) && angular.isFunction(options.onSelect)) {
  126. options.onSelect(value, index, $select);
  127. }
  128. };
  129. $select.$updateActiveIndex = function() {
  130. if (options.multiple) {
  131. if (angular.isArray(controller.$modelValue)) {
  132. scope.$activeIndex = controller.$modelValue.map(function(value) {
  133. return $select.$getIndex(value);
  134. });
  135. } else {
  136. scope.$activeIndex = [];
  137. }
  138. } else {
  139. if (angular.isDefined(controller.$modelValue) && scope.$matches.length) {
  140. scope.$activeIndex = $select.$getIndex(controller.$modelValue);
  141. } else {
  142. scope.$activeIndex = -1;
  143. }
  144. }
  145. };
  146. $select.$isVisible = function() {
  147. if (!options.minLength || !controller) {
  148. return scope.$matches.length;
  149. }
  150. return scope.$matches.length && controller.$viewValue.length >= options.minLength;
  151. };
  152. $select.$isActive = function(index) {
  153. if (options.multiple) {
  154. return scope.$activeIndex.indexOf(index) !== -1;
  155. }
  156. return scope.$activeIndex === index;
  157. };
  158. $select.$getIndex = function(value) {
  159. var index;
  160. for (index = scope.$matches.length; index--; ) {
  161. if (angular.equals(scope.$matches[index].value, value)) break;
  162. }
  163. return index;
  164. };
  165. $select.$onMouseDown = function(evt) {
  166. evt.preventDefault();
  167. evt.stopPropagation();
  168. if (isTouch) {
  169. var targetEl = angular.element(evt.target);
  170. targetEl.triggerHandler('click');
  171. }
  172. };
  173. $select.$onKeyDown = function(evt) {
  174. if (!/(9|13|38|40)/.test(evt.keyCode)) return;
  175. if (evt.keyCode !== 9) {
  176. evt.preventDefault();
  177. evt.stopPropagation();
  178. }
  179. if (options.multiple && evt.keyCode === 9) {
  180. return $select.hide();
  181. }
  182. if (!options.multiple && (evt.keyCode === 13 || evt.keyCode === 9)) {
  183. return $select.select(scope.$activeIndex);
  184. }
  185. if (!options.multiple) {
  186. 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;
  187. scope.$digest();
  188. }
  189. };
  190. $select.$isIE = function() {
  191. var ua = $window.navigator.userAgent;
  192. return ua.indexOf('MSIE ') > 0 || ua.indexOf('Trident/') > 0 || ua.indexOf('Edge/') > 0;
  193. };
  194. $select.$selectScrollFix = function(e) {
  195. if ($document[0].activeElement.tagName === 'UL') {
  196. e.preventDefault();
  197. e.stopImmediatePropagation();
  198. e.target.focus();
  199. }
  200. };
  201. var _show = $select.show;
  202. $select.show = function() {
  203. _show();
  204. if (options.multiple) {
  205. $select.$element.addClass('select-multiple');
  206. }
  207. $timeout(function() {
  208. $select.$element.on(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown);
  209. if (options.keyboard) {
  210. element.on('keydown', $select.$onKeyDown);
  211. }
  212. }, 0, false);
  213. };
  214. var _hide = $select.hide;
  215. $select.hide = function() {
  216. if (!options.multiple && angular.isUndefined(controller.$modelValue)) {
  217. scope.$activeIndex = -1;
  218. }
  219. $select.$element.off(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown);
  220. if (options.keyboard) {
  221. element.off('keydown', $select.$onKeyDown);
  222. }
  223. _hide(true);
  224. };
  225. return $select;
  226. }
  227. SelectFactory.defaults = defaults;
  228. return SelectFactory;
  229. } ];
  230. }).directive('bsSelect', [ '$window', '$parse', '$q', '$select', '$parseOptions', function($window, $parse, $q, $select, $parseOptions) {
  231. var defaults = $select.defaults;
  232. return {
  233. restrict: 'EAC',
  234. require: 'ngModel',
  235. link: function postLink(scope, element, attr, controller) {
  236. var options = {
  237. scope: scope,
  238. placeholder: defaults.placeholder
  239. };
  240. 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', 'toggle' ], function(key) {
  241. if (angular.isDefined(attr[key])) options[key] = attr[key];
  242. });
  243. var falseValueRegExp = /^(false|0|)$/i;
  244. angular.forEach([ 'html', 'container', 'allNoneButtons', 'sort' ], function(key) {
  245. if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) {
  246. options[key] = false;
  247. }
  248. });
  249. angular.forEach([ 'onBeforeShow', 'onShow', 'onBeforeHide', 'onHide', 'onSelect' ], function(key) {
  250. var bsKey = 'bs' + key.charAt(0).toUpperCase() + key.slice(1);
  251. if (angular.isDefined(attr[bsKey])) {
  252. options[key] = scope.$eval(attr[bsKey]);
  253. }
  254. });
  255. var dataMultiple = element.attr('data-multiple');
  256. if (angular.isDefined(dataMultiple)) {
  257. if (falseValueRegExp.test(dataMultiple)) {
  258. options.multiple = false;
  259. } else {
  260. options.multiple = dataMultiple;
  261. }
  262. }
  263. if (element[0].nodeName.toLowerCase() === 'select') {
  264. var inputEl = element;
  265. inputEl.css('display', 'none');
  266. element = angular.element('<button type="button" class="btn btn-default"></button>');
  267. inputEl.after(element);
  268. }
  269. var parsedOptions = $parseOptions(attr.bsOptions);
  270. var select = $select(element, controller, options);
  271. if (select.$isIE()) {
  272. element[0].addEventListener('blur', select.$selectScrollFix);
  273. }
  274. var watchedOptions = parsedOptions.$match[7].replace(/\|.+/, '').trim();
  275. scope.$watch(watchedOptions, function(newValue, oldValue) {
  276. parsedOptions.valuesFn(scope, controller).then(function(values) {
  277. select.update(values);
  278. controller.$render();
  279. });
  280. }, true);
  281. scope.$watch(attr.ngModel, function(newValue, oldValue) {
  282. select.$updateActiveIndex();
  283. controller.$render();
  284. }, true);
  285. controller.$render = function() {
  286. var selected;
  287. var index;
  288. if (options.multiple && angular.isArray(controller.$modelValue)) {
  289. selected = controller.$modelValue.map(function(value) {
  290. index = select.$getIndex(value);
  291. return index !== -1 ? select.$scope.$matches[index].label : false;
  292. }).filter(angular.isDefined);
  293. if (selected.length > (options.maxLength || defaults.maxLength)) {
  294. selected = selected.length + ' ' + (options.maxLengthHtml || defaults.maxLengthHtml);
  295. } else {
  296. selected = selected.join(', ');
  297. }
  298. } else {
  299. index = select.$getIndex(controller.$modelValue);
  300. selected = index !== -1 ? select.$scope.$matches[index].label : false;
  301. }
  302. element.html((selected || options.placeholder) + (options.caretHtml || defaults.caretHtml));
  303. };
  304. if (options.multiple) {
  305. controller.$isEmpty = function(value) {
  306. return !value || value.length === 0;
  307. };
  308. }
  309. scope.$on('$destroy', function() {
  310. if (select) select.destroy();
  311. options = null;
  312. select = null;
  313. });
  314. }
  315. };
  316. } ]);