angular-strap-rails 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +17 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +45 -0
  7. data/Rakefile +1 -0
  8. data/angular-strap-rails.gemspec +15 -0
  9. data/lib/angular-strap-rails.rb +8 -0
  10. data/lib/angular-strap-rails/version.rb +5 -0
  11. data/vendor/.DS_Store +0 -0
  12. data/vendor/assets/.DS_Store +0 -0
  13. data/vendor/assets/javascripts/.DS_Store +0 -0
  14. data/vendor/assets/javascripts/angular-strap.coffee +0 -0
  15. data/vendor/assets/javascripts/angular-strap/.DS_Store +0 -0
  16. data/vendor/assets/javascripts/angular-strap/datepicker.coffee +11 -0
  17. data/vendor/assets/javascripts/angular-strap/modal.coffee +9 -0
  18. data/vendor/assets/javascripts/dist/angular-strap.js +3682 -0
  19. data/vendor/assets/javascripts/dist/angular-strap.tpl.js +100 -0
  20. data/vendor/assets/javascripts/dist/modules/affix.js +191 -0
  21. data/vendor/assets/javascripts/dist/modules/alert.js +114 -0
  22. data/vendor/assets/javascripts/dist/modules/alert.tpl.js +14 -0
  23. data/vendor/assets/javascripts/dist/modules/aside.js +96 -0
  24. data/vendor/assets/javascripts/dist/modules/aside.tpl.js +14 -0
  25. data/vendor/assets/javascripts/dist/modules/button.js +141 -0
  26. data/vendor/assets/javascripts/dist/modules/date-parser.js +150 -0
  27. data/vendor/assets/javascripts/dist/modules/datepicker.js +583 -0
  28. data/vendor/assets/javascripts/dist/modules/datepicker.tpl.js +14 -0
  29. data/vendor/assets/javascripts/dist/modules/debounce.js +60 -0
  30. data/vendor/assets/javascripts/dist/modules/dimensions.js +142 -0
  31. data/vendor/assets/javascripts/dist/modules/dropdown.js +124 -0
  32. data/vendor/assets/javascripts/dist/modules/dropdown.tpl.js +14 -0
  33. data/vendor/assets/javascripts/dist/modules/modal.js +282 -0
  34. data/vendor/assets/javascripts/dist/modules/modal.tpl.js +14 -0
  35. data/vendor/assets/javascripts/dist/modules/navbar.js +55 -0
  36. data/vendor/assets/javascripts/dist/modules/parse-options.js +51 -0
  37. data/vendor/assets/javascripts/dist/modules/popover.js +100 -0
  38. data/vendor/assets/javascripts/dist/modules/popover.tpl.js +14 -0
  39. data/vendor/assets/javascripts/dist/modules/raf.js +45 -0
  40. data/vendor/assets/javascripts/dist/modules/scrollspy.js +229 -0
  41. data/vendor/assets/javascripts/dist/modules/select.js +281 -0
  42. data/vendor/assets/javascripts/dist/modules/select.tpl.js +14 -0
  43. data/vendor/assets/javascripts/dist/modules/tab.js +69 -0
  44. data/vendor/assets/javascripts/dist/modules/tab.tpl.js +14 -0
  45. data/vendor/assets/javascripts/dist/modules/timepicker.js +430 -0
  46. data/vendor/assets/javascripts/dist/modules/timepicker.tpl.js +14 -0
  47. data/vendor/assets/javascripts/dist/modules/tooltip.js +405 -0
  48. data/vendor/assets/javascripts/dist/modules/tooltip.tpl.js +14 -0
  49. data/vendor/assets/javascripts/dist/modules/typeahead.js +225 -0
  50. data/vendor/assets/javascripts/dist/modules/typeahead.tpl.js +14 -0
  51. data/vendor/assets/stylesheets/angular-strap.css +564 -0
  52. metadata +94 -0
@@ -0,0 +1,14 @@
1
+ /**
2
+ * angular-strap
3
+ * @version v2.0.1 - 2014-04-10
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.timepicker').run([
10
+ '$templateCache',
11
+ function ($templateCache) {
12
+ $templateCache.put('timepicker/timepicker.tpl.html', '<div class="dropdown-menu timepicker" style="min-width: 0px;width: auto"><table height="100%"><thead><tr class="text-center"><th><button tabindex="-1" type="button" class="btn btn-default pull-left" ng-click="$moveIndex(-1, 0)"><i class="glyphicon glyphicon-chevron-up"></i></button></th><th>&nbsp;</th><th><button tabindex="-1" type="button" class="btn btn-default pull-left" ng-click="$moveIndex(-1, 1)"><i class="glyphicon glyphicon-chevron-up"></i></button></th></tr></thead><tbody><tr ng-repeat="(i, row) in rows"><td class="text-center"><button tabindex="-1" style="width: 100%" type="button" class="btn btn-default" ng-class="{\'btn-primary\': row[0].selected}" ng-click="$select(row[0].date, 0)" ng-disabled="row[0].disabled"><span ng-class="{\'text-muted\': row[0].muted}" ng-bind="row[0].label"></span></button></td><td><span ng-bind="i == midIndex ? \':\' : \' \'"></span></td><td class="text-center"><button tabindex="-1" ng-if="row[1].date" style="width: 100%" type="button" class="btn btn-default" ng-class="{\'btn-primary\': row[1].selected}" ng-click="$select(row[1].date, 1)" ng-disabled="row[1].disabled"><span ng-class="{\'text-muted\': row[1].muted}" ng-bind="row[1].label"></span></button></td><td ng-if="showAM">&nbsp;</td><td ng-if="showAM"><button tabindex="-1" ng-show="i == midIndex - !isAM * 1" style="width: 100%" type="button" ng-class="{\'btn-primary\': !!isAM}" class="btn btn-default" ng-click="$switchMeridian()" ng-disabled="el.disabled">AM</button> <button tabindex="-1" ng-show="i == midIndex + 1 - !isAM * 1" style="width: 100%" type="button" ng-class="{\'btn-primary\': !isAM}" class="btn btn-default" ng-click="$switchMeridian()" ng-disabled="el.disabled">PM</button></td></tr></tbody><tfoot><tr class="text-center"><th><button tabindex="-1" type="button" class="btn btn-default pull-left" ng-click="$moveIndex(1, 0)"><i class="glyphicon glyphicon-chevron-down"></i></button></th><th>&nbsp;</th><th><button tabindex="-1" type="button" class="btn btn-default pull-left" ng-click="$moveIndex(1, 1)"><i class="glyphicon glyphicon-chevron-down"></i></button></th></tr></tfoot></table></div>');
13
+ }
14
+ ]);
@@ -0,0 +1,405 @@
1
+ /**
2
+ * angular-strap
3
+ * @version v2.0.1 - 2014-04-10
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.tooltip', ['mgcrea.ngStrap.helpers.dimensions']).provider('$tooltip', function () {
10
+ var defaults = this.defaults = {
11
+ animation: 'am-fade',
12
+ prefixClass: 'tooltip',
13
+ prefixEvent: 'tooltip',
14
+ container: false,
15
+ placement: 'top',
16
+ template: 'tooltip/tooltip.tpl.html',
17
+ contentTemplate: false,
18
+ trigger: 'hover focus',
19
+ keyboard: false,
20
+ html: false,
21
+ show: false,
22
+ title: '',
23
+ type: '',
24
+ delay: 0
25
+ };
26
+ this.$get = [
27
+ '$window',
28
+ '$rootScope',
29
+ '$compile',
30
+ '$q',
31
+ '$templateCache',
32
+ '$http',
33
+ '$animate',
34
+ '$timeout',
35
+ 'dimensions',
36
+ '$$rAF',
37
+ function ($window, $rootScope, $compile, $q, $templateCache, $http, $animate, $timeout, dimensions, $$rAF) {
38
+ var trim = String.prototype.trim;
39
+ var isTouch = 'createTouch' in $window.document;
40
+ var htmlReplaceRegExp = /ng-bind="/gi;
41
+ function TooltipFactory(element, config) {
42
+ var $tooltip = {};
43
+ // Common vars
44
+ var options = $tooltip.$options = angular.extend({}, defaults, config);
45
+ $tooltip.$promise = fetchTemplate(options.template);
46
+ var scope = $tooltip.$scope = options.scope && options.scope.$new() || $rootScope.$new();
47
+ if (options.delay && angular.isString(options.delay)) {
48
+ options.delay = parseFloat(options.delay);
49
+ }
50
+ // Support scope as string options
51
+ if (options.title) {
52
+ $tooltip.$scope.title = options.title;
53
+ }
54
+ // Provide scope helpers
55
+ scope.$hide = function () {
56
+ scope.$$postDigest(function () {
57
+ $tooltip.hide();
58
+ });
59
+ };
60
+ scope.$show = function () {
61
+ scope.$$postDigest(function () {
62
+ $tooltip.show();
63
+ });
64
+ };
65
+ scope.$toggle = function () {
66
+ scope.$$postDigest(function () {
67
+ $tooltip.toggle();
68
+ });
69
+ };
70
+ $tooltip.$isShown = scope.$isShown = false;
71
+ // Private vars
72
+ var timeout, hoverState;
73
+ // Support contentTemplate option
74
+ if (options.contentTemplate) {
75
+ $tooltip.$promise = $tooltip.$promise.then(function (template) {
76
+ var templateEl = angular.element(template);
77
+ return fetchTemplate(options.contentTemplate).then(function (contentTemplate) {
78
+ var contentEl = findElement('[ng-bind="content"]', templateEl[0]);
79
+ if (!contentEl.length)
80
+ contentEl = findElement('[ng-bind="title"]', templateEl[0]);
81
+ contentEl.removeAttr('ng-bind').html(contentTemplate);
82
+ return templateEl[0].outerHTML;
83
+ });
84
+ });
85
+ }
86
+ // Fetch, compile then initialize tooltip
87
+ var tipLinker, tipElement, tipTemplate, tipContainer;
88
+ $tooltip.$promise.then(function (template) {
89
+ if (angular.isObject(template))
90
+ template = template.data;
91
+ if (options.html)
92
+ template = template.replace(htmlReplaceRegExp, 'ng-bind-html="');
93
+ template = trim.apply(template);
94
+ tipTemplate = template;
95
+ tipLinker = $compile(template);
96
+ $tooltip.init();
97
+ });
98
+ $tooltip.init = function () {
99
+ // Options: delay
100
+ if (options.delay && angular.isNumber(options.delay)) {
101
+ options.delay = {
102
+ show: options.delay,
103
+ hide: options.delay
104
+ };
105
+ }
106
+ // Replace trigger on touch devices ?
107
+ // if(isTouch && options.trigger === defaults.trigger) {
108
+ // options.trigger.replace(/hover/g, 'click');
109
+ // }
110
+ // Options : container
111
+ if (options.container === 'self') {
112
+ tipContainer = element;
113
+ } else if (options.container) {
114
+ tipContainer = findElement(options.container);
115
+ }
116
+ // Options: trigger
117
+ var triggers = options.trigger.split(' ');
118
+ angular.forEach(triggers, function (trigger) {
119
+ if (trigger === 'click') {
120
+ element.on('click', $tooltip.toggle);
121
+ } else if (trigger !== 'manual') {
122
+ element.on(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
123
+ element.on(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
124
+ trigger !== 'hover' && element.on(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
125
+ }
126
+ });
127
+ // Options: show
128
+ if (options.show) {
129
+ scope.$$postDigest(function () {
130
+ options.trigger === 'focus' ? element[0].focus() : $tooltip.show();
131
+ });
132
+ }
133
+ };
134
+ $tooltip.destroy = function () {
135
+ // Unbind events
136
+ var triggers = options.trigger.split(' ');
137
+ for (var i = triggers.length; i--;) {
138
+ var trigger = triggers[i];
139
+ if (trigger === 'click') {
140
+ element.off('click', $tooltip.toggle);
141
+ } else if (trigger !== 'manual') {
142
+ element.off(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
143
+ element.off(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
144
+ trigger !== 'hover' && element.off(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
145
+ }
146
+ }
147
+ // Remove element
148
+ if (tipElement) {
149
+ tipElement.remove();
150
+ tipElement = null;
151
+ }
152
+ // Destroy scope
153
+ scope.$destroy();
154
+ };
155
+ $tooltip.enter = function () {
156
+ clearTimeout(timeout);
157
+ hoverState = 'in';
158
+ if (!options.delay || !options.delay.show) {
159
+ return $tooltip.show();
160
+ }
161
+ timeout = setTimeout(function () {
162
+ if (hoverState === 'in')
163
+ $tooltip.show();
164
+ }, options.delay.show);
165
+ };
166
+ $tooltip.show = function () {
167
+ scope.$emit(options.prefixEvent + '.show.before', $tooltip);
168
+ var parent = options.container ? tipContainer : null;
169
+ var after = options.container ? null : element;
170
+ // Hide any existing tipElement
171
+ if (tipElement)
172
+ tipElement.remove();
173
+ // Fetch a cloned element linked from template
174
+ tipElement = $tooltip.$element = tipLinker(scope, function (clonedElement, scope) {
175
+ });
176
+ // Set the initial positioning.
177
+ tipElement.css({
178
+ top: '0px',
179
+ left: '0px',
180
+ display: 'block'
181
+ }).addClass(options.placement);
182
+ // Options: animation
183
+ if (options.animation)
184
+ tipElement.addClass(options.animation);
185
+ // Options: type
186
+ if (options.type)
187
+ tipElement.addClass(options.prefixClass + '-' + options.type);
188
+ $animate.enter(tipElement, parent, after, function () {
189
+ scope.$emit(options.prefixEvent + '.show', $tooltip);
190
+ });
191
+ $tooltip.$isShown = scope.$isShown = true;
192
+ scope.$$phase || scope.$root.$$phase || scope.$digest();
193
+ $$rAF($tooltip.$applyPlacement);
194
+ // var a = bodyEl.offsetWidth + 1; ?
195
+ // Bind events
196
+ if (options.keyboard) {
197
+ if (options.trigger !== 'focus') {
198
+ $tooltip.focus();
199
+ tipElement.on('keyup', $tooltip.$onKeyUp);
200
+ } else {
201
+ element.on('keyup', $tooltip.$onFocusKeyUp);
202
+ }
203
+ }
204
+ };
205
+ $tooltip.leave = function () {
206
+ clearTimeout(timeout);
207
+ hoverState = 'out';
208
+ if (!options.delay || !options.delay.hide) {
209
+ return $tooltip.hide();
210
+ }
211
+ timeout = setTimeout(function () {
212
+ if (hoverState === 'out') {
213
+ $tooltip.hide();
214
+ }
215
+ }, options.delay.hide);
216
+ };
217
+ $tooltip.hide = function (blur) {
218
+ if (!$tooltip.$isShown)
219
+ return;
220
+ scope.$emit(options.prefixEvent + '.hide.before', $tooltip);
221
+ $animate.leave(tipElement, function () {
222
+ scope.$emit(options.prefixEvent + '.hide', $tooltip);
223
+ });
224
+ $tooltip.$isShown = scope.$isShown = false;
225
+ scope.$$phase || scope.$root.$$phase || scope.$digest();
226
+ // Unbind events
227
+ if (options.keyboard && tipElement !== null) {
228
+ tipElement.off('keyup', $tooltip.$onKeyUp);
229
+ }
230
+ // Allow to blur the input when hidden, like when pressing enter key
231
+ if (blur && options.trigger === 'focus') {
232
+ return element[0].blur();
233
+ }
234
+ };
235
+ $tooltip.toggle = function () {
236
+ $tooltip.$isShown ? $tooltip.leave() : $tooltip.enter();
237
+ };
238
+ $tooltip.focus = function () {
239
+ tipElement[0].focus();
240
+ };
241
+ // Protected methods
242
+ $tooltip.$applyPlacement = function () {
243
+ if (!tipElement)
244
+ return;
245
+ // Get the position of the tooltip element.
246
+ var elementPosition = getPosition();
247
+ // Get the height and width of the tooltip so we can center it.
248
+ var tipWidth = tipElement.prop('offsetWidth'), tipHeight = tipElement.prop('offsetHeight');
249
+ // Get the tooltip's top and left coordinates to center it with this directive.
250
+ var tipPosition = getCalculatedOffset(options.placement, elementPosition, tipWidth, tipHeight);
251
+ // Now set the calculated positioning.
252
+ tipPosition.top += 'px';
253
+ tipPosition.left += 'px';
254
+ tipElement.css(tipPosition);
255
+ };
256
+ $tooltip.$onKeyUp = function (evt) {
257
+ evt.which === 27 && $tooltip.hide();
258
+ };
259
+ $tooltip.$onFocusKeyUp = function (evt) {
260
+ evt.which === 27 && element[0].blur();
261
+ };
262
+ $tooltip.$onFocusElementMouseDown = function (evt) {
263
+ evt.preventDefault();
264
+ evt.stopPropagation();
265
+ // Some browsers do not auto-focus buttons (eg. Safari)
266
+ $tooltip.$isShown ? element[0].blur() : element[0].focus();
267
+ };
268
+ // Private methods
269
+ function getPosition() {
270
+ if (options.container === 'body') {
271
+ return dimensions.offset(element[0]);
272
+ } else {
273
+ return dimensions.position(element[0]);
274
+ }
275
+ }
276
+ function getCalculatedOffset(placement, position, actualWidth, actualHeight) {
277
+ var offset;
278
+ var split = placement.split('-');
279
+ switch (split[0]) {
280
+ case 'right':
281
+ offset = {
282
+ top: position.top + position.height / 2 - actualHeight / 2,
283
+ left: position.left + position.width
284
+ };
285
+ break;
286
+ case 'bottom':
287
+ offset = {
288
+ top: position.top + position.height,
289
+ left: position.left + position.width / 2 - actualWidth / 2
290
+ };
291
+ break;
292
+ case 'left':
293
+ offset = {
294
+ top: position.top + position.height / 2 - actualHeight / 2,
295
+ left: position.left - actualWidth
296
+ };
297
+ break;
298
+ default:
299
+ offset = {
300
+ top: position.top - actualHeight,
301
+ left: position.left + position.width / 2 - actualWidth / 2
302
+ };
303
+ break;
304
+ }
305
+ if (!split[1]) {
306
+ return offset;
307
+ }
308
+ // Add support for corners @todo css
309
+ if (split[0] === 'top' || split[0] === 'bottom') {
310
+ switch (split[1]) {
311
+ case 'left':
312
+ offset.left = position.left;
313
+ break;
314
+ case 'right':
315
+ offset.left = position.left + position.width - actualWidth;
316
+ }
317
+ } else if (split[0] === 'left' || split[0] === 'right') {
318
+ switch (split[1]) {
319
+ case 'top':
320
+ offset.top = position.top - actualHeight;
321
+ break;
322
+ case 'bottom':
323
+ offset.top = position.top + position.height;
324
+ }
325
+ }
326
+ return offset;
327
+ }
328
+ return $tooltip;
329
+ }
330
+ // Helper functions
331
+ function findElement(query, element) {
332
+ return angular.element((element || document).querySelectorAll(query));
333
+ }
334
+ function fetchTemplate(template) {
335
+ return $q.when($templateCache.get(template) || $http.get(template)).then(function (res) {
336
+ if (angular.isObject(res)) {
337
+ $templateCache.put(template, res.data);
338
+ return res.data;
339
+ }
340
+ return res;
341
+ });
342
+ }
343
+ return TooltipFactory;
344
+ }
345
+ ];
346
+ }).directive('bsTooltip', [
347
+ '$window',
348
+ '$location',
349
+ '$sce',
350
+ '$tooltip',
351
+ '$$rAF',
352
+ function ($window, $location, $sce, $tooltip, $$rAF) {
353
+ return {
354
+ restrict: 'EAC',
355
+ scope: true,
356
+ link: function postLink(scope, element, attr, transclusion) {
357
+ // Directive options
358
+ var options = { scope: scope };
359
+ angular.forEach([
360
+ 'template',
361
+ 'contentTemplate',
362
+ 'placement',
363
+ 'container',
364
+ 'delay',
365
+ 'trigger',
366
+ 'keyboard',
367
+ 'html',
368
+ 'animation',
369
+ 'type'
370
+ ], function (key) {
371
+ if (angular.isDefined(attr[key]))
372
+ options[key] = attr[key];
373
+ });
374
+ // Observe scope attributes for change
375
+ angular.forEach(['title'], function (key) {
376
+ attr[key] && attr.$observe(key, function (newValue, oldValue) {
377
+ scope[key] = $sce.trustAsHtml(newValue);
378
+ angular.isDefined(oldValue) && $$rAF(function () {
379
+ tooltip && tooltip.$applyPlacement();
380
+ });
381
+ });
382
+ });
383
+ // Support scope as an object
384
+ attr.bsTooltip && scope.$watch(attr.bsTooltip, function (newValue, oldValue) {
385
+ if (angular.isObject(newValue)) {
386
+ angular.extend(scope, newValue);
387
+ } else {
388
+ scope.title = newValue;
389
+ }
390
+ angular.isDefined(oldValue) && $$rAF(function () {
391
+ tooltip && tooltip.$applyPlacement();
392
+ });
393
+ }, true);
394
+ // Initialize popover
395
+ var tooltip = $tooltip(element, options);
396
+ // Garbage collection
397
+ scope.$on('$destroy', function () {
398
+ tooltip.destroy();
399
+ options = null;
400
+ tooltip = null;
401
+ });
402
+ }
403
+ };
404
+ }
405
+ ]);
@@ -0,0 +1,14 @@
1
+ /**
2
+ * angular-strap
3
+ * @version v2.0.1 - 2014-04-10
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.tooltip').run([
10
+ '$templateCache',
11
+ function ($templateCache) {
12
+ $templateCache.put('tooltip/tooltip.tpl.html', '<div class="tooltip in" ng-show="title"><div class="tooltip-arrow"></div><div class="tooltip-inner" ng-bind="title"></div></div>');
13
+ }
14
+ ]);
@@ -0,0 +1,225 @@
1
+ /**
2
+ * angular-strap
3
+ * @version v2.0.1 - 2014-04-10
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', [
10
+ 'mgcrea.ngStrap.tooltip',
11
+ 'mgcrea.ngStrap.helpers.parseOptions'
12
+ ]).provider('$typeahead', function () {
13
+ var defaults = this.defaults = {
14
+ animation: 'am-fade',
15
+ prefixClass: 'typeahead',
16
+ placement: 'bottom-left',
17
+ template: 'typeahead/typeahead.tpl.html',
18
+ trigger: 'focus',
19
+ container: false,
20
+ keyboard: true,
21
+ html: false,
22
+ delay: 0,
23
+ minLength: 1,
24
+ filter: 'filter',
25
+ limit: 6
26
+ };
27
+ this.$get = [
28
+ '$window',
29
+ '$rootScope',
30
+ '$tooltip',
31
+ function ($window, $rootScope, $tooltip) {
32
+ var bodyEl = angular.element($window.document.body);
33
+ function TypeaheadFactory(element, config) {
34
+ var $typeahead = {};
35
+ // Common vars
36
+ var options = angular.extend({}, defaults, config);
37
+ var controller = options.controller;
38
+ $typeahead = $tooltip(element, options);
39
+ var parentScope = config.scope;
40
+ var scope = $typeahead.$scope;
41
+ scope.$resetMatches = function () {
42
+ scope.$matches = [];
43
+ scope.$activeIndex = 0;
44
+ };
45
+ scope.$resetMatches();
46
+ scope.$activate = function (index) {
47
+ scope.$$postDigest(function () {
48
+ $typeahead.activate(index);
49
+ });
50
+ };
51
+ scope.$select = function (index, evt) {
52
+ scope.$$postDigest(function () {
53
+ $typeahead.select(index);
54
+ });
55
+ };
56
+ scope.$isVisible = function () {
57
+ return $typeahead.$isVisible();
58
+ };
59
+ // Public methods
60
+ $typeahead.update = function (matches) {
61
+ scope.$matches = matches;
62
+ if (scope.$activeIndex >= matches.length) {
63
+ scope.$activeIndex = 0;
64
+ }
65
+ };
66
+ $typeahead.activate = function (index) {
67
+ scope.$activeIndex = index;
68
+ };
69
+ $typeahead.select = function (index) {
70
+ var value = scope.$matches[index].value;
71
+ if (controller) {
72
+ controller.$setViewValue(value);
73
+ controller.$render();
74
+ if (parentScope)
75
+ parentScope.$digest();
76
+ }
77
+ scope.$resetMatches();
78
+ // Emit event
79
+ scope.$emit('$typeahead.select', value, index);
80
+ };
81
+ // Protected methods
82
+ $typeahead.$isVisible = function () {
83
+ if (!options.minLength || !controller) {
84
+ return !!scope.$matches.length;
85
+ }
86
+ // minLength support
87
+ return scope.$matches.length && angular.isString(controller.$viewValue) && controller.$viewValue.length >= options.minLength;
88
+ };
89
+ $typeahead.$getIndex = function (value) {
90
+ var l = scope.$matches.length, i = l;
91
+ if (!l)
92
+ return;
93
+ for (i = l; i--;) {
94
+ if (scope.$matches[i].value === value)
95
+ break;
96
+ }
97
+ if (i < 0)
98
+ return;
99
+ return i;
100
+ };
101
+ $typeahead.$onMouseDown = function (evt) {
102
+ // Prevent blur on mousedown
103
+ evt.preventDefault();
104
+ evt.stopPropagation();
105
+ };
106
+ $typeahead.$onKeyDown = function (evt) {
107
+ if (!/(38|40|13)/.test(evt.keyCode))
108
+ return;
109
+ evt.preventDefault();
110
+ evt.stopPropagation();
111
+ // Select with enter
112
+ if (evt.keyCode === 13 && scope.$matches.length) {
113
+ return $typeahead.select(scope.$activeIndex);
114
+ }
115
+ // Navigate with keyboard
116
+ if (evt.keyCode === 38 && scope.$activeIndex > 0)
117
+ scope.$activeIndex--;
118
+ else if (evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1)
119
+ scope.$activeIndex++;
120
+ else if (angular.isUndefined(scope.$activeIndex))
121
+ scope.$activeIndex = 0;
122
+ scope.$digest();
123
+ };
124
+ // Overrides
125
+ var show = $typeahead.show;
126
+ $typeahead.show = function () {
127
+ show();
128
+ setTimeout(function () {
129
+ $typeahead.$element.on('mousedown', $typeahead.$onMouseDown);
130
+ if (options.keyboard) {
131
+ element.on('keydown', $typeahead.$onKeyDown);
132
+ }
133
+ });
134
+ };
135
+ var hide = $typeahead.hide;
136
+ $typeahead.hide = function () {
137
+ $typeahead.$element.off('mousedown', $typeahead.$onMouseDown);
138
+ if (options.keyboard) {
139
+ element.off('keydown', $typeahead.$onKeyDown);
140
+ }
141
+ hide();
142
+ };
143
+ return $typeahead;
144
+ }
145
+ TypeaheadFactory.defaults = defaults;
146
+ return TypeaheadFactory;
147
+ }
148
+ ];
149
+ }).directive('bsTypeahead', [
150
+ '$window',
151
+ '$parse',
152
+ '$q',
153
+ '$typeahead',
154
+ '$parseOptions',
155
+ function ($window, $parse, $q, $typeahead, $parseOptions) {
156
+ var defaults = $typeahead.defaults;
157
+ return {
158
+ restrict: 'EAC',
159
+ require: 'ngModel',
160
+ link: function postLink(scope, element, attr, controller) {
161
+ // Directive options
162
+ var options = {
163
+ scope: scope,
164
+ controller: controller
165
+ };
166
+ angular.forEach([
167
+ 'placement',
168
+ 'container',
169
+ 'delay',
170
+ 'trigger',
171
+ 'keyboard',
172
+ 'html',
173
+ 'animation',
174
+ 'template',
175
+ 'filter',
176
+ 'limit',
177
+ 'minLength'
178
+ ], function (key) {
179
+ if (angular.isDefined(attr[key]))
180
+ options[key] = attr[key];
181
+ });
182
+ // Build proper ngOptions
183
+ var filter = options.filter || defaults.filter;
184
+ var limit = options.limit || defaults.limit;
185
+ var ngOptions = attr.ngOptions;
186
+ if (filter)
187
+ ngOptions += ' | ' + filter + ':$viewValue';
188
+ if (limit)
189
+ ngOptions += ' | limitTo:' + limit;
190
+ var parsedOptions = $parseOptions(ngOptions);
191
+ // Initialize typeahead
192
+ var typeahead = $typeahead(element, options);
193
+ // if(!dump) var dump = console.error.bind(console);
194
+ // Watch model for changes
195
+ scope.$watch(attr.ngModel, function (newValue, oldValue) {
196
+ scope.$modelValue = newValue;
197
+ //Set model value on the scope to custom templates can use it.
198
+ parsedOptions.valuesFn(scope, controller).then(function (values) {
199
+ if (values.length > limit)
200
+ values = values.slice(0, limit);
201
+ // if(matches.length === 1 && matches[0].value === newValue) return;
202
+ typeahead.update(values);
203
+ // Queue a new rendering that will leverage collection loading
204
+ controller.$render();
205
+ });
206
+ });
207
+ // Model rendering in view
208
+ controller.$render = function () {
209
+ // console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
210
+ if (controller.$isEmpty(controller.$viewValue))
211
+ return element.val('');
212
+ var index = typeahead.$getIndex(controller.$modelValue);
213
+ var selected = angular.isDefined(index) ? typeahead.$scope.$matches[index].label : controller.$viewValue;
214
+ element.val(selected.replace(/<(?:.|\n)*?>/gm, '').trim());
215
+ };
216
+ // Garbage collection
217
+ scope.$on('$destroy', function () {
218
+ typeahead.destroy();
219
+ options = null;
220
+ typeahead = null;
221
+ });
222
+ }
223
+ };
224
+ }
225
+ ]);