PageRenderTime 21ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/ajax/libs/angular-strap/2.1.4/modules/typeahead.js

https://gitlab.com/Blueprint-Marketing/cdnjs
JavaScript | 266 lines | 182 code | 47 blank | 37 comment | 42 complexity | b5b103cfa40be774ff390735d82a09fe MD5 | raw file
  1. /**
  2. * angular-strap
  3. * @version v2.1.4 - 2014-11-26
  4. * @link http://mgcrea.github.io/angular-strap
  5. * @author Olivier Louvignes (olivier@mg-crea.com)
  6. * @license MIT License, http://www.opensource.org/licenses/MIT
  7. */
  8. 'use strict';
  9. angular.module('mgcrea.ngStrap.typeahead', ['mgcrea.ngStrap.tooltip', 'mgcrea.ngStrap.helpers.parseOptions'])
  10. .provider('$typeahead', function() {
  11. var defaults = this.defaults = {
  12. animation: 'am-fade',
  13. prefixClass: 'typeahead',
  14. prefixEvent: '$typeahead',
  15. placement: 'bottom-left',
  16. template: 'typeahead/typeahead.tpl.html',
  17. trigger: 'focus',
  18. container: false,
  19. keyboard: true,
  20. html: false,
  21. delay: 0,
  22. minLength: 1,
  23. filter: 'filter',
  24. limit: 6,
  25. comparator: ''
  26. };
  27. this.$get = ["$window", "$rootScope", "$tooltip", "$timeout", function($window, $rootScope, $tooltip, $timeout) {
  28. var bodyEl = angular.element($window.document.body);
  29. function TypeaheadFactory(element, controller, config) {
  30. var $typeahead = {};
  31. // Common vars
  32. var options = angular.extend({}, defaults, config);
  33. $typeahead = $tooltip(element, options);
  34. var parentScope = config.scope;
  35. var scope = $typeahead.$scope;
  36. scope.$resetMatches = function(){
  37. scope.$matches = [];
  38. scope.$activeIndex = 0;
  39. };
  40. scope.$resetMatches();
  41. scope.$activate = function(index) {
  42. scope.$$postDigest(function() {
  43. $typeahead.activate(index);
  44. });
  45. };
  46. scope.$select = function(index, evt) {
  47. scope.$$postDigest(function() {
  48. $typeahead.select(index);
  49. });
  50. };
  51. scope.$isVisible = function() {
  52. return $typeahead.$isVisible();
  53. };
  54. // Public methods
  55. $typeahead.update = function(matches) {
  56. scope.$matches = matches;
  57. if(scope.$activeIndex >= matches.length) {
  58. scope.$activeIndex = 0;
  59. }
  60. };
  61. $typeahead.activate = function(index) {
  62. scope.$activeIndex = index;
  63. };
  64. $typeahead.select = function(index) {
  65. var value = scope.$matches[index].value;
  66. // console.log('$setViewValue', value);
  67. controller.$setViewValue(value);
  68. controller.$render();
  69. scope.$resetMatches();
  70. if(parentScope) parentScope.$digest();
  71. // Emit event
  72. scope.$emit(options.prefixEvent + '.select', value, index);
  73. };
  74. // Protected methods
  75. $typeahead.$isVisible = function() {
  76. if(!options.minLength || !controller) {
  77. return !!scope.$matches.length;
  78. }
  79. // minLength support
  80. return scope.$matches.length && angular.isString(controller.$viewValue) && controller.$viewValue.length >= options.minLength;
  81. };
  82. $typeahead.$getIndex = function(value) {
  83. var l = scope.$matches.length, i = l;
  84. if(!l) return;
  85. for(i = l; i--;) {
  86. if(scope.$matches[i].value === value) break;
  87. }
  88. if(i < 0) return;
  89. return i;
  90. };
  91. $typeahead.$onMouseDown = function(evt) {
  92. // Prevent blur on mousedown
  93. evt.preventDefault();
  94. evt.stopPropagation();
  95. };
  96. $typeahead.$onKeyDown = function(evt) {
  97. if(!/(38|40|13)/.test(evt.keyCode)) return;
  98. // Let ngSubmit pass if the typeahead tip is hidden
  99. if($typeahead.$isVisible()) {
  100. evt.preventDefault();
  101. evt.stopPropagation();
  102. }
  103. // Select with enter
  104. if(evt.keyCode === 13 && scope.$matches.length) {
  105. $typeahead.select(scope.$activeIndex);
  106. }
  107. // Navigate with keyboard
  108. else if(evt.keyCode === 38 && scope.$activeIndex > 0) scope.$activeIndex--;
  109. else if(evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1) scope.$activeIndex++;
  110. else if(angular.isUndefined(scope.$activeIndex)) scope.$activeIndex = 0;
  111. scope.$digest();
  112. };
  113. // Overrides
  114. var show = $typeahead.show;
  115. $typeahead.show = function() {
  116. show();
  117. // use timeout to hookup the events to prevent
  118. // event bubbling from being processed imediately.
  119. $timeout(function() {
  120. $typeahead.$element.on('mousedown', $typeahead.$onMouseDown);
  121. if(options.keyboard) {
  122. element.on('keydown', $typeahead.$onKeyDown);
  123. }
  124. }, 0, false);
  125. };
  126. var hide = $typeahead.hide;
  127. $typeahead.hide = function() {
  128. $typeahead.$element.off('mousedown', $typeahead.$onMouseDown);
  129. if(options.keyboard) {
  130. element.off('keydown', $typeahead.$onKeyDown);
  131. }
  132. hide();
  133. };
  134. return $typeahead;
  135. }
  136. TypeaheadFactory.defaults = defaults;
  137. return TypeaheadFactory;
  138. }];
  139. })
  140. .directive('bsTypeahead', ["$window", "$parse", "$q", "$typeahead", "$parseOptions", function($window, $parse, $q, $typeahead, $parseOptions) {
  141. var defaults = $typeahead.defaults;
  142. return {
  143. restrict: 'EAC',
  144. require: 'ngModel',
  145. link: function postLink(scope, element, attr, controller) {
  146. // Directive options
  147. var options = {scope: scope};
  148. angular.forEach(['placement', 'container', 'delay', 'trigger', 'keyboard', 'html', 'animation', 'template', 'filter', 'limit', 'minLength', 'watchOptions', 'selectMode', 'comparator'], function(key) {
  149. if(angular.isDefined(attr[key])) options[key] = attr[key];
  150. });
  151. // Build proper ngOptions
  152. var filter = options.filter || defaults.filter;
  153. var limit = options.limit || defaults.limit;
  154. var comparator = options.comparator || defaults.comparator;
  155. var ngOptions = attr.ngOptions;
  156. if(filter) ngOptions += ' | ' + filter + ':$viewValue';
  157. if (comparator) ngOptions += ':' + comparator;
  158. if(limit) ngOptions += ' | limitTo:' + limit;
  159. var parsedOptions = $parseOptions(ngOptions);
  160. // Initialize typeahead
  161. var typeahead = $typeahead(element, controller, options);
  162. // Watch options on demand
  163. if(options.watchOptions) {
  164. // Watch ngOptions values before filtering for changes, drop function calls
  165. var watchedOptions = parsedOptions.$match[7].replace(/\|.+/, '').replace(/\(.*\)/g, '').trim();
  166. scope.$watch(watchedOptions, function (newValue, oldValue) {
  167. // console.warn('scope.$watch(%s)', watchedOptions, newValue, oldValue);
  168. parsedOptions.valuesFn(scope, controller).then(function (values) {
  169. typeahead.update(values);
  170. controller.$render();
  171. });
  172. }, true);
  173. }
  174. // Watch model for changes
  175. scope.$watch(attr.ngModel, function(newValue, oldValue) {
  176. // console.warn('$watch', element.attr('ng-model'), newValue);
  177. scope.$modelValue = newValue; // Publish modelValue on scope for custom templates
  178. parsedOptions.valuesFn(scope, controller)
  179. .then(function(values) {
  180. // Prevent input with no future prospect if selectMode is truthy
  181. // @TODO test selectMode
  182. if(options.selectMode && !values.length && newValue.length > 0) {
  183. controller.$setViewValue(controller.$viewValue.substring(0, controller.$viewValue.length - 1));
  184. return;
  185. }
  186. if(values.length > limit) values = values.slice(0, limit);
  187. var isVisible = typeahead.$isVisible();
  188. isVisible && typeahead.update(values);
  189. // Do not re-queue an update if a correct value has been selected
  190. if(values.length === 1 && values[0].value === newValue) return;
  191. !isVisible && typeahead.update(values);
  192. // Queue a new rendering that will leverage collection loading
  193. controller.$render();
  194. });
  195. });
  196. // modelValue -> $formatters -> viewValue
  197. controller.$formatters.push(function(modelValue) {
  198. // console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue);
  199. var displayValue = parsedOptions.displayValue(modelValue);
  200. return displayValue === undefined ? '' : displayValue;
  201. });
  202. // Model rendering in view
  203. controller.$render = function () {
  204. // console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
  205. if(controller.$isEmpty(controller.$viewValue)) return element.val('');
  206. var index = typeahead.$getIndex(controller.$modelValue);
  207. var selected = angular.isDefined(index) ? typeahead.$scope.$matches[index].label : controller.$viewValue;
  208. selected = angular.isObject(selected) ? parsedOptions.displayValue(selected) : selected;
  209. element.val(selected ? selected.toString().replace(/<(?:.|\n)*?>/gm, '').trim() : '');
  210. };
  211. // Garbage collection
  212. scope.$on('$destroy', function() {
  213. if (typeahead) typeahead.destroy();
  214. options = null;
  215. typeahead = null;
  216. });
  217. }
  218. };
  219. }]);