commit | author | age
|
1759c2
|
1 |
/** |
RM |
2 |
* angular-strap |
|
3 |
* @version v2.0.3 - 2014-05-30 |
|
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 |
(function(window, document, undefined) { |
|
9 |
'use strict'; |
|
10 |
// Source: module.js |
|
11 |
angular.module('mgcrea.ngStrap', [ |
|
12 |
'mgcrea.ngStrap.modal', |
|
13 |
'mgcrea.ngStrap.aside', |
|
14 |
'mgcrea.ngStrap.alert', |
|
15 |
'mgcrea.ngStrap.button', |
|
16 |
'mgcrea.ngStrap.select', |
|
17 |
'mgcrea.ngStrap.datepicker', |
|
18 |
'mgcrea.ngStrap.timepicker', |
|
19 |
'mgcrea.ngStrap.navbar', |
|
20 |
'mgcrea.ngStrap.tooltip', |
|
21 |
'mgcrea.ngStrap.popover', |
|
22 |
'mgcrea.ngStrap.dropdown', |
|
23 |
'mgcrea.ngStrap.typeahead', |
|
24 |
'mgcrea.ngStrap.scrollspy', |
|
25 |
'mgcrea.ngStrap.affix', |
|
26 |
'mgcrea.ngStrap.tab' |
|
27 |
]); |
|
28 |
|
|
29 |
// Source: affix.js |
|
30 |
angular.module('mgcrea.ngStrap.affix', [ |
|
31 |
'mgcrea.ngStrap.helpers.dimensions', |
|
32 |
'mgcrea.ngStrap.helpers.debounce' |
|
33 |
]).provider('$affix', function () { |
|
34 |
var defaults = this.defaults = { offsetTop: 'auto' }; |
|
35 |
this.$get = [ |
|
36 |
'$window', |
|
37 |
'debounce', |
|
38 |
'dimensions', |
|
39 |
function ($window, debounce, dimensions) { |
|
40 |
var bodyEl = angular.element($window.document.body); |
|
41 |
var windowEl = angular.element($window); |
|
42 |
function AffixFactory(element, config) { |
|
43 |
var $affix = {}; |
|
44 |
// Common vars |
|
45 |
var options = angular.extend({}, defaults, config); |
|
46 |
var targetEl = options.target; |
|
47 |
// Initial private vars |
|
48 |
var reset = 'affix affix-top affix-bottom', initialAffixTop = 0, initialOffsetTop = 0, offsetTop = 0, offsetBottom = 0, affixed = null, unpin = null; |
|
49 |
var parent = element.parent(); |
|
50 |
// Options: custom parent |
|
51 |
if (options.offsetParent) { |
|
52 |
if (options.offsetParent.match(/^\d+$/)) { |
|
53 |
for (var i = 0; i < options.offsetParent * 1 - 1; i++) { |
|
54 |
parent = parent.parent(); |
|
55 |
} |
|
56 |
} else { |
|
57 |
parent = angular.element(options.offsetParent); |
|
58 |
} |
|
59 |
} |
|
60 |
$affix.init = function () { |
|
61 |
$affix.$parseOffsets(); |
|
62 |
initialOffsetTop = dimensions.offset(element[0]).top + initialAffixTop; |
|
63 |
// Bind events |
|
64 |
targetEl.on('scroll', $affix.checkPosition); |
|
65 |
targetEl.on('click', $affix.checkPositionWithEventLoop); |
|
66 |
windowEl.on('resize', $affix.$debouncedOnResize); |
|
67 |
// Both of these checkPosition() calls are necessary for the case where |
|
68 |
// the user hits refresh after scrolling to the bottom of the page. |
|
69 |
$affix.checkPosition(); |
|
70 |
$affix.checkPositionWithEventLoop(); |
|
71 |
}; |
|
72 |
$affix.destroy = function () { |
|
73 |
// Unbind events |
|
74 |
targetEl.off('scroll', $affix.checkPosition); |
|
75 |
targetEl.off('click', $affix.checkPositionWithEventLoop); |
|
76 |
windowEl.off('resize', $affix.$debouncedOnResize); |
|
77 |
}; |
|
78 |
$affix.checkPositionWithEventLoop = function () { |
|
79 |
setTimeout($affix.checkPosition, 1); |
|
80 |
}; |
|
81 |
$affix.checkPosition = function () { |
|
82 |
// if (!this.$element.is(':visible')) return |
|
83 |
var scrollTop = getScrollTop(); |
|
84 |
var position = dimensions.offset(element[0]); |
|
85 |
var elementHeight = dimensions.height(element[0]); |
|
86 |
// Get required affix class according to position |
|
87 |
var affix = getRequiredAffixClass(unpin, position, elementHeight); |
|
88 |
// Did affix status changed this last check? |
|
89 |
if (affixed === affix) |
|
90 |
return; |
|
91 |
affixed = affix; |
|
92 |
// Add proper affix class |
|
93 |
element.removeClass(reset).addClass('affix' + (affix !== 'middle' ? '-' + affix : '')); |
|
94 |
if (affix === 'top') { |
|
95 |
unpin = null; |
|
96 |
element.css('position', options.offsetParent ? '' : 'relative'); |
|
97 |
element.css('top', ''); |
|
98 |
} else if (affix === 'bottom') { |
|
99 |
if (options.offsetUnpin) { |
|
100 |
unpin = -(options.offsetUnpin * 1); |
|
101 |
} else { |
|
102 |
// Calculate unpin threshold when affixed to bottom. |
|
103 |
// Hopefully the browser scrolls pixel by pixel. |
|
104 |
unpin = position.top - scrollTop; |
|
105 |
} |
|
106 |
element.css('position', options.offsetParent ? '' : 'relative'); |
|
107 |
element.css('top', options.offsetParent ? '' : bodyEl[0].offsetHeight - offsetBottom - elementHeight - initialOffsetTop + 'px'); |
|
108 |
} else { |
|
109 |
// affix === 'middle' |
|
110 |
unpin = null; |
|
111 |
element.css('position', 'fixed'); |
|
112 |
element.css('top', initialAffixTop + 'px'); |
|
113 |
} |
|
114 |
}; |
|
115 |
$affix.$onResize = function () { |
|
116 |
$affix.$parseOffsets(); |
|
117 |
$affix.checkPosition(); |
|
118 |
}; |
|
119 |
$affix.$debouncedOnResize = debounce($affix.$onResize, 50); |
|
120 |
$affix.$parseOffsets = function () { |
|
121 |
// Reset position to calculate correct offsetTop |
|
122 |
element.css('position', options.offsetParent ? '' : 'relative'); |
|
123 |
if (options.offsetTop) { |
|
124 |
if (options.offsetTop === 'auto') { |
|
125 |
options.offsetTop = '+0'; |
|
126 |
} |
|
127 |
if (options.offsetTop.match(/^[-+]\d+$/)) { |
|
128 |
initialAffixTop = -options.offsetTop * 1; |
|
129 |
if (options.offsetParent) { |
|
130 |
offsetTop = dimensions.offset(parent[0]).top + options.offsetTop * 1; |
|
131 |
} else { |
|
132 |
offsetTop = dimensions.offset(element[0]).top - dimensions.css(element[0], 'marginTop', true) + options.offsetTop * 1; |
|
133 |
} |
|
134 |
} else { |
|
135 |
offsetTop = options.offsetTop * 1; |
|
136 |
} |
|
137 |
} |
|
138 |
if (options.offsetBottom) { |
|
139 |
if (options.offsetParent && options.offsetBottom.match(/^[-+]\d+$/)) { |
|
140 |
// add 1 pixel due to rounding problems... |
|
141 |
offsetBottom = getScrollHeight() - (dimensions.offset(parent[0]).top + dimensions.height(parent[0])) + options.offsetBottom * 1 + 1; |
|
142 |
} else { |
|
143 |
offsetBottom = options.offsetBottom * 1; |
|
144 |
} |
|
145 |
} |
|
146 |
}; |
|
147 |
// Private methods |
|
148 |
function getRequiredAffixClass(unpin, position, elementHeight) { |
|
149 |
var scrollTop = getScrollTop(); |
|
150 |
var scrollHeight = getScrollHeight(); |
|
151 |
if (scrollTop <= offsetTop) { |
|
152 |
return 'top'; |
|
153 |
} else if (unpin !== null && scrollTop + unpin <= position.top) { |
|
154 |
return 'middle'; |
|
155 |
} else if (offsetBottom !== null && position.top + elementHeight + initialAffixTop >= scrollHeight - offsetBottom) { |
|
156 |
return 'bottom'; |
|
157 |
} else { |
|
158 |
return 'middle'; |
|
159 |
} |
|
160 |
} |
|
161 |
function getScrollTop() { |
|
162 |
return targetEl[0] === $window ? $window.pageYOffset : targetEl[0] === $window; |
|
163 |
} |
|
164 |
function getScrollHeight() { |
|
165 |
return targetEl[0] === $window ? $window.document.body.scrollHeight : targetEl[0].scrollHeight; |
|
166 |
} |
|
167 |
$affix.init(); |
|
168 |
return $affix; |
|
169 |
} |
|
170 |
return AffixFactory; |
|
171 |
} |
|
172 |
]; |
|
173 |
}).directive('bsAffix', [ |
|
174 |
'$affix', |
|
175 |
'$window', |
|
176 |
function ($affix, $window) { |
|
177 |
return { |
|
178 |
restrict: 'EAC', |
|
179 |
require: '^?bsAffixTarget', |
|
180 |
link: function postLink(scope, element, attr, affixTarget) { |
|
181 |
var options = { |
|
182 |
scope: scope, |
|
183 |
offsetTop: 'auto', |
|
184 |
target: affixTarget ? affixTarget.$element : angular.element($window) |
|
185 |
}; |
|
186 |
angular.forEach([ |
|
187 |
'offsetTop', |
|
188 |
'offsetBottom', |
|
189 |
'offsetParent', |
|
190 |
'offsetUnpin' |
|
191 |
], function (key) { |
|
192 |
if (angular.isDefined(attr[key])) |
|
193 |
options[key] = attr[key]; |
|
194 |
}); |
|
195 |
var affix = $affix(element, options); |
|
196 |
scope.$on('$destroy', function () { |
|
197 |
options = null; |
|
198 |
affix = null; |
|
199 |
}); |
|
200 |
} |
|
201 |
}; |
|
202 |
} |
|
203 |
]).directive('bsAffixTarget', function () { |
|
204 |
return { |
|
205 |
controller: [ |
|
206 |
'$element', |
|
207 |
function ($element) { |
|
208 |
this.$element = $element; |
|
209 |
} |
|
210 |
] |
|
211 |
}; |
|
212 |
}); |
|
213 |
|
|
214 |
// Source: alert.js |
|
215 |
// @BUG: following snippet won't compile correctly |
|
216 |
// @TODO: submit issue to core |
|
217 |
// '<span ng-if="title"><strong ng-bind="title"></strong> </span><span ng-bind-html="content"></span>' + |
|
218 |
angular.module('mgcrea.ngStrap.alert', ['mgcrea.ngStrap.modal']).provider('$alert', function () { |
|
219 |
var defaults = this.defaults = { |
|
220 |
animation: 'am-fade', |
|
221 |
prefixClass: 'alert', |
|
222 |
placement: null, |
|
223 |
template: 'alert/alert.tpl.html', |
|
224 |
container: false, |
|
225 |
element: null, |
|
226 |
backdrop: false, |
|
227 |
keyboard: true, |
|
228 |
show: true, |
|
229 |
duration: false, |
|
230 |
type: false, |
|
231 |
dismissable: true |
|
232 |
}; |
|
233 |
this.$get = [ |
|
234 |
'$modal', |
|
235 |
'$timeout', |
|
236 |
function ($modal, $timeout) { |
|
237 |
function AlertFactory(config) { |
|
238 |
var $alert = {}; |
|
239 |
// Common vars |
|
240 |
var options = angular.extend({}, defaults, config); |
|
241 |
$alert = $modal(options); |
|
242 |
// Support scope as string options [/*title, content, */ type, dismissable] |
|
243 |
$alert.$scope.dismissable = !!options.dismissable; |
|
244 |
if (options.type) { |
|
245 |
$alert.$scope.type = options.type; |
|
246 |
} |
|
247 |
// Support auto-close duration |
|
248 |
var show = $alert.show; |
|
249 |
if (options.duration) { |
|
250 |
$alert.show = function () { |
|
251 |
show(); |
|
252 |
$timeout(function () { |
|
253 |
$alert.hide(); |
|
254 |
}, options.duration * 1000); |
|
255 |
}; |
|
256 |
} |
|
257 |
return $alert; |
|
258 |
} |
|
259 |
return AlertFactory; |
|
260 |
} |
|
261 |
]; |
|
262 |
}).directive('bsAlert', [ |
|
263 |
'$window', |
|
264 |
'$location', |
|
265 |
'$sce', |
|
266 |
'$alert', |
|
267 |
function ($window, $location, $sce, $alert) { |
|
268 |
var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout; |
|
269 |
return { |
|
270 |
restrict: 'EAC', |
|
271 |
scope: true, |
|
272 |
link: function postLink(scope, element, attr, transclusion) { |
|
273 |
// Directive options |
|
274 |
var options = { |
|
275 |
scope: scope, |
|
276 |
element: element, |
|
277 |
show: false |
|
278 |
}; |
|
279 |
angular.forEach([ |
|
280 |
'template', |
|
281 |
'placement', |
|
282 |
'keyboard', |
|
283 |
'html', |
|
284 |
'container', |
|
285 |
'animation', |
|
286 |
'duration', |
|
287 |
'dismissable' |
|
288 |
], function (key) { |
|
289 |
if (angular.isDefined(attr[key])) |
|
290 |
options[key] = attr[key]; |
|
291 |
}); |
|
292 |
// Support scope as data-attrs |
|
293 |
angular.forEach([ |
|
294 |
'title', |
|
295 |
'content', |
|
296 |
'type' |
|
297 |
], function (key) { |
|
298 |
attr[key] && attr.$observe(key, function (newValue, oldValue) { |
|
299 |
scope[key] = $sce.trustAsHtml(newValue); |
|
300 |
}); |
|
301 |
}); |
|
302 |
// Support scope as an object |
|
303 |
attr.bsAlert && scope.$watch(attr.bsAlert, function (newValue, oldValue) { |
|
304 |
if (angular.isObject(newValue)) { |
|
305 |
angular.extend(scope, newValue); |
|
306 |
} else { |
|
307 |
scope.content = newValue; |
|
308 |
} |
|
309 |
}, true); |
|
310 |
// Initialize alert |
|
311 |
var alert = $alert(options); |
|
312 |
// Trigger |
|
313 |
element.on(attr.trigger || 'click', alert.toggle); |
|
314 |
// Garbage collection |
|
315 |
scope.$on('$destroy', function () { |
|
316 |
alert.destroy(); |
|
317 |
options = null; |
|
318 |
alert = null; |
|
319 |
}); |
|
320 |
} |
|
321 |
}; |
|
322 |
} |
|
323 |
]); |
|
324 |
|
|
325 |
// Source: button.js |
|
326 |
angular.module('mgcrea.ngStrap.button', []).provider('$button', function () { |
|
327 |
var defaults = this.defaults = { |
|
328 |
activeClass: 'active', |
|
329 |
toggleEvent: 'click' |
|
330 |
}; |
|
331 |
this.$get = function () { |
|
332 |
return { defaults: defaults }; |
|
333 |
}; |
|
334 |
}).directive('bsCheckboxGroup', function () { |
|
335 |
return { |
|
336 |
restrict: 'A', |
|
337 |
require: 'ngModel', |
|
338 |
compile: function postLink(element, attr) { |
|
339 |
element.attr('data-toggle', 'buttons'); |
|
340 |
element.removeAttr('ng-model'); |
|
341 |
var children = element[0].querySelectorAll('input[type="checkbox"]'); |
|
342 |
angular.forEach(children, function (child) { |
|
343 |
var childEl = angular.element(child); |
|
344 |
childEl.attr('bs-checkbox', ''); |
|
345 |
childEl.attr('ng-model', attr.ngModel + '.' + childEl.attr('value')); |
|
346 |
}); |
|
347 |
} |
|
348 |
}; |
|
349 |
}).directive('bsCheckbox', [ |
|
350 |
'$button', |
|
351 |
'$$rAF', |
|
352 |
function ($button, $$rAF) { |
|
353 |
var defaults = $button.defaults; |
|
354 |
var constantValueRegExp = /^(true|false|\d+)$/; |
|
355 |
return { |
|
356 |
restrict: 'A', |
|
357 |
require: 'ngModel', |
|
358 |
link: function postLink(scope, element, attr, controller) { |
|
359 |
var options = defaults; |
|
360 |
// Support label > input[type="checkbox"] |
|
361 |
var isInput = element[0].nodeName === 'INPUT'; |
|
362 |
var activeElement = isInput ? element.parent() : element; |
|
363 |
var trueValue = angular.isDefined(attr.trueValue) ? attr.trueValue : true; |
|
364 |
if (constantValueRegExp.test(attr.trueValue)) { |
|
365 |
trueValue = scope.$eval(attr.trueValue); |
|
366 |
} |
|
367 |
var falseValue = angular.isDefined(attr.falseValue) ? attr.falseValue : false; |
|
368 |
if (constantValueRegExp.test(attr.falseValue)) { |
|
369 |
falseValue = scope.$eval(attr.falseValue); |
|
370 |
} |
|
371 |
// Parse exotic values |
|
372 |
var hasExoticValues = typeof trueValue !== 'boolean' || typeof falseValue !== 'boolean'; |
|
373 |
if (hasExoticValues) { |
|
374 |
controller.$parsers.push(function (viewValue) { |
|
375 |
// console.warn('$parser', element.attr('ng-model'), 'viewValue', viewValue); |
|
376 |
return viewValue ? trueValue : falseValue; |
|
377 |
}); |
|
378 |
// Fix rendering for exotic values |
|
379 |
scope.$watch(attr.ngModel, function (newValue, oldValue) { |
|
380 |
controller.$render(); |
|
381 |
}); |
|
382 |
} |
|
383 |
// model -> view |
|
384 |
controller.$render = function () { |
|
385 |
// console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue); |
|
386 |
var isActive = angular.equals(controller.$modelValue, trueValue); |
|
387 |
$$rAF(function () { |
|
388 |
if (isInput) |
|
389 |
element[0].checked = isActive; |
|
390 |
activeElement.toggleClass(options.activeClass, isActive); |
|
391 |
}); |
|
392 |
}; |
|
393 |
// view -> model |
|
394 |
element.bind(options.toggleEvent, function () { |
|
395 |
scope.$apply(function () { |
|
396 |
// console.warn('!click', element.attr('ng-model'), 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue, 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue); |
|
397 |
if (!isInput) { |
|
398 |
controller.$setViewValue(!activeElement.hasClass('active')); |
|
399 |
} |
|
400 |
if (!hasExoticValues) { |
|
401 |
controller.$render(); |
|
402 |
} |
|
403 |
}); |
|
404 |
}); |
|
405 |
} |
|
406 |
}; |
|
407 |
} |
|
408 |
]).directive('bsRadioGroup', function () { |
|
409 |
return { |
|
410 |
restrict: 'A', |
|
411 |
require: 'ngModel', |
|
412 |
compile: function postLink(element, attr) { |
|
413 |
element.attr('data-toggle', 'buttons'); |
|
414 |
element.removeAttr('ng-model'); |
|
415 |
var children = element[0].querySelectorAll('input[type="radio"]'); |
|
416 |
angular.forEach(children, function (child) { |
|
417 |
angular.element(child).attr('bs-radio', ''); |
|
418 |
angular.element(child).attr('ng-model', attr.ngModel); |
|
419 |
}); |
|
420 |
} |
|
421 |
}; |
|
422 |
}).directive('bsRadio', [ |
|
423 |
'$button', |
|
424 |
'$$rAF', |
|
425 |
function ($button, $$rAF) { |
|
426 |
var defaults = $button.defaults; |
|
427 |
var constantValueRegExp = /^(true|false|\d+)$/; |
|
428 |
return { |
|
429 |
restrict: 'A', |
|
430 |
require: 'ngModel', |
|
431 |
link: function postLink(scope, element, attr, controller) { |
|
432 |
var options = defaults; |
|
433 |
// Support `label > input[type="radio"]` markup |
|
434 |
var isInput = element[0].nodeName === 'INPUT'; |
|
435 |
var activeElement = isInput ? element.parent() : element; |
|
436 |
var value = constantValueRegExp.test(attr.value) ? scope.$eval(attr.value) : attr.value; |
|
437 |
// model -> view |
|
438 |
controller.$render = function () { |
|
439 |
// console.warn('$render', element.attr('value'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue); |
|
440 |
var isActive = angular.equals(controller.$modelValue, value); |
|
441 |
$$rAF(function () { |
|
442 |
if (isInput) |
|
443 |
element[0].checked = isActive; |
|
444 |
activeElement.toggleClass(options.activeClass, isActive); |
|
445 |
}); |
|
446 |
}; |
|
447 |
// view -> model |
|
448 |
element.bind(options.toggleEvent, function () { |
|
449 |
scope.$apply(function () { |
|
450 |
// console.warn('!click', element.attr('value'), 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue, 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue); |
|
451 |
controller.$setViewValue(value); |
|
452 |
controller.$render(); |
|
453 |
}); |
|
454 |
}); |
|
455 |
} |
|
456 |
}; |
|
457 |
} |
|
458 |
]); |
|
459 |
|
|
460 |
// Source: aside.js |
|
461 |
angular.module('mgcrea.ngStrap.aside', ['mgcrea.ngStrap.modal']).provider('$aside', function () { |
|
462 |
var defaults = this.defaults = { |
|
463 |
animation: 'am-fade-and-slide-right', |
|
464 |
prefixClass: 'aside', |
|
465 |
placement: 'right', |
|
466 |
template: 'aside/aside.tpl.html', |
|
467 |
contentTemplate: false, |
|
468 |
container: false, |
|
469 |
element: null, |
|
470 |
backdrop: true, |
|
471 |
keyboard: true, |
|
472 |
html: false, |
|
473 |
show: true |
|
474 |
}; |
|
475 |
this.$get = [ |
|
476 |
'$modal', |
|
477 |
function ($modal) { |
|
478 |
function AsideFactory(config) { |
|
479 |
var $aside = {}; |
|
480 |
// Common vars |
|
481 |
var options = angular.extend({}, defaults, config); |
|
482 |
$aside = $modal(options); |
|
483 |
return $aside; |
|
484 |
} |
|
485 |
return AsideFactory; |
|
486 |
} |
|
487 |
]; |
|
488 |
}).directive('bsAside', [ |
|
489 |
'$window', |
|
490 |
'$sce', |
|
491 |
'$aside', |
|
492 |
function ($window, $sce, $aside) { |
|
493 |
var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout; |
|
494 |
return { |
|
495 |
restrict: 'EAC', |
|
496 |
scope: true, |
|
497 |
link: function postLink(scope, element, attr, transclusion) { |
|
498 |
// Directive options |
|
499 |
var options = { |
|
500 |
scope: scope, |
|
501 |
element: element, |
|
502 |
show: false |
|
503 |
}; |
|
504 |
angular.forEach([ |
|
505 |
'template', |
|
506 |
'contentTemplate', |
|
507 |
'placement', |
|
508 |
'backdrop', |
|
509 |
'keyboard', |
|
510 |
'html', |
|
511 |
'container', |
|
512 |
'animation' |
|
513 |
], function (key) { |
|
514 |
if (angular.isDefined(attr[key])) |
|
515 |
options[key] = attr[key]; |
|
516 |
}); |
|
517 |
// Support scope as data-attrs |
|
518 |
angular.forEach([ |
|
519 |
'title', |
|
520 |
'content' |
|
521 |
], function (key) { |
|
522 |
attr[key] && attr.$observe(key, function (newValue, oldValue) { |
|
523 |
scope[key] = $sce.trustAsHtml(newValue); |
|
524 |
}); |
|
525 |
}); |
|
526 |
// Support scope as an object |
|
527 |
attr.bsAside && scope.$watch(attr.bsAside, function (newValue, oldValue) { |
|
528 |
if (angular.isObject(newValue)) { |
|
529 |
angular.extend(scope, newValue); |
|
530 |
} else { |
|
531 |
scope.content = newValue; |
|
532 |
} |
|
533 |
}, true); |
|
534 |
// Initialize aside |
|
535 |
var aside = $aside(options); |
|
536 |
// Trigger |
|
537 |
element.on(attr.trigger || 'click', aside.toggle); |
|
538 |
// Garbage collection |
|
539 |
scope.$on('$destroy', function () { |
|
540 |
aside.destroy(); |
|
541 |
options = null; |
|
542 |
aside = null; |
|
543 |
}); |
|
544 |
} |
|
545 |
}; |
|
546 |
} |
|
547 |
]); |
|
548 |
|
|
549 |
// Source: datepicker.js |
|
550 |
angular.module('mgcrea.ngStrap.datepicker', [ |
|
551 |
'mgcrea.ngStrap.helpers.dateParser', |
|
552 |
'mgcrea.ngStrap.tooltip' |
|
553 |
]).provider('$datepicker', function () { |
|
554 |
var defaults = this.defaults = { |
|
555 |
animation: 'am-fade', |
|
556 |
prefixClass: 'datepicker', |
|
557 |
placement: 'bottom-left', |
|
558 |
template: 'datepicker/datepicker.tpl.html', |
|
559 |
trigger: 'focus', |
|
560 |
container: false, |
|
561 |
keyboard: true, |
|
562 |
html: false, |
|
563 |
delay: 0, |
|
564 |
useNative: false, |
|
565 |
dateType: 'date', |
|
566 |
dateFormat: 'shortDate', |
|
567 |
modelDateFormat: null, |
|
568 |
dayFormat: 'dd', |
|
569 |
strictFormat: false, |
|
570 |
autoclose: false, |
|
571 |
minDate: -Infinity, |
|
572 |
maxDate: +Infinity, |
|
573 |
startView: 0, |
|
574 |
minView: 0, |
|
575 |
startWeek: 0, |
|
576 |
iconLeft: 'glyphicon glyphicon-chevron-left', |
|
577 |
iconRight: 'glyphicon glyphicon-chevron-right' |
|
578 |
}; |
|
579 |
this.$get = [ |
|
580 |
'$window', |
|
581 |
'$document', |
|
582 |
'$rootScope', |
|
583 |
'$sce', |
|
584 |
'$locale', |
|
585 |
'dateFilter', |
|
586 |
'datepickerViews', |
|
587 |
'$tooltip', |
|
588 |
function ($window, $document, $rootScope, $sce, $locale, dateFilter, datepickerViews, $tooltip) { |
|
589 |
var bodyEl = angular.element($window.document.body); |
|
590 |
var isTouch = 'createTouch' in $window.document; |
|
591 |
var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent); |
|
592 |
if (!defaults.lang) |
|
593 |
defaults.lang = $locale.id; |
|
594 |
function DatepickerFactory(element, controller, config) { |
|
595 |
var $datepicker = $tooltip(element, angular.extend({}, defaults, config)); |
|
596 |
var parentScope = config.scope; |
|
597 |
var options = $datepicker.$options; |
|
598 |
var scope = $datepicker.$scope; |
|
599 |
if (options.startView) |
|
600 |
options.startView -= options.minView; |
|
601 |
// View vars |
|
602 |
var pickerViews = datepickerViews($datepicker); |
|
603 |
$datepicker.$views = pickerViews.views; |
|
604 |
var viewDate = pickerViews.viewDate; |
|
605 |
scope.$mode = options.startView; |
|
606 |
scope.$iconLeft = options.iconLeft; |
|
607 |
scope.$iconRight = options.iconRight; |
|
608 |
var $picker = $datepicker.$views[scope.$mode]; |
|
609 |
// Scope methods |
|
610 |
scope.$select = function (date) { |
|
611 |
$datepicker.select(date); |
|
612 |
}; |
|
613 |
scope.$selectPane = function (value) { |
|
614 |
$datepicker.$selectPane(value); |
|
615 |
}; |
|
616 |
scope.$toggleMode = function () { |
|
617 |
$datepicker.setMode((scope.$mode + 1) % $datepicker.$views.length); |
|
618 |
}; |
|
619 |
// Public methods |
|
620 |
$datepicker.update = function (date) { |
|
621 |
// console.warn('$datepicker.update() newValue=%o', date); |
|
622 |
if (angular.isDate(date) && !isNaN(date.getTime())) { |
|
623 |
$datepicker.$date = date; |
|
624 |
$picker.update.call($picker, date); |
|
625 |
} |
|
626 |
// Build only if pristine |
|
627 |
$datepicker.$build(true); |
|
628 |
}; |
|
629 |
$datepicker.select = function (date, keep) { |
|
630 |
// console.warn('$datepicker.select', date, scope.$mode); |
|
631 |
if (!angular.isDate(controller.$dateValue)) |
|
632 |
controller.$dateValue = new Date(date); |
|
633 |
controller.$dateValue.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); |
|
634 |
if (!scope.$mode || keep) { |
|
635 |
controller.$setViewValue(controller.$dateValue); |
|
636 |
controller.$render(); |
|
637 |
if (options.autoclose && !keep) { |
|
638 |
$datepicker.hide(true); |
|
639 |
} |
|
640 |
} else { |
|
641 |
angular.extend(viewDate, { |
|
642 |
year: date.getFullYear(), |
|
643 |
month: date.getMonth(), |
|
644 |
date: date.getDate() |
|
645 |
}); |
|
646 |
$datepicker.setMode(scope.$mode - 1); |
|
647 |
$datepicker.$build(); |
|
648 |
} |
|
649 |
}; |
|
650 |
$datepicker.setMode = function (mode) { |
|
651 |
// console.warn('$datepicker.setMode', mode); |
|
652 |
scope.$mode = mode; |
|
653 |
$picker = $datepicker.$views[scope.$mode]; |
|
654 |
$datepicker.$build(); |
|
655 |
}; |
|
656 |
// Protected methods |
|
657 |
$datepicker.$build = function (pristine) { |
|
658 |
// console.warn('$datepicker.$build() viewDate=%o', viewDate); |
|
659 |
if (pristine === true && $picker.built) |
|
660 |
return; |
|
661 |
if (pristine === false && !$picker.built) |
|
662 |
return; |
|
663 |
$picker.build.call($picker); |
|
664 |
}; |
|
665 |
$datepicker.$updateSelected = function () { |
|
666 |
for (var i = 0, l = scope.rows.length; i < l; i++) { |
|
667 |
angular.forEach(scope.rows[i], updateSelected); |
|
668 |
} |
|
669 |
}; |
|
670 |
$datepicker.$isSelected = function (date) { |
|
671 |
return $picker.isSelected(date); |
|
672 |
}; |
|
673 |
$datepicker.$selectPane = function (value) { |
|
674 |
var steps = $picker.steps; |
|
675 |
var targetDate = new Date(Date.UTC(viewDate.year + (steps.year || 0) * value, viewDate.month + (steps.month || 0) * value, viewDate.date + (steps.day || 0) * value)); |
|
676 |
angular.extend(viewDate, { |
|
677 |
year: targetDate.getUTCFullYear(), |
|
678 |
month: targetDate.getUTCMonth(), |
|
679 |
date: targetDate.getUTCDate() |
|
680 |
}); |
|
681 |
$datepicker.$build(); |
|
682 |
}; |
|
683 |
$datepicker.$onMouseDown = function (evt) { |
|
684 |
// Prevent blur on mousedown on .dropdown-menu |
|
685 |
evt.preventDefault(); |
|
686 |
evt.stopPropagation(); |
|
687 |
// Emulate click for mobile devices |
|
688 |
if (isTouch) { |
|
689 |
var targetEl = angular.element(evt.target); |
|
690 |
if (targetEl[0].nodeName.toLowerCase() !== 'button') { |
|
691 |
targetEl = targetEl.parent(); |
|
692 |
} |
|
693 |
targetEl.triggerHandler('click'); |
|
694 |
} |
|
695 |
}; |
|
696 |
$datepicker.$onKeyDown = function (evt) { |
|
697 |
if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey) |
|
698 |
return; |
|
699 |
evt.preventDefault(); |
|
700 |
evt.stopPropagation(); |
|
701 |
if (evt.keyCode === 13) { |
|
702 |
if (!scope.$mode) { |
|
703 |
return $datepicker.hide(true); |
|
704 |
} else { |
|
705 |
return scope.$apply(function () { |
|
706 |
$datepicker.setMode(scope.$mode - 1); |
|
707 |
}); |
|
708 |
} |
|
709 |
} |
|
710 |
// Navigate with keyboard |
|
711 |
$picker.onKeyDown(evt); |
|
712 |
parentScope.$digest(); |
|
713 |
}; |
|
714 |
// Private |
|
715 |
function updateSelected(el) { |
|
716 |
el.selected = $datepicker.$isSelected(el.date); |
|
717 |
} |
|
718 |
function focusElement() { |
|
719 |
element[0].focus(); |
|
720 |
} |
|
721 |
// Overrides |
|
722 |
var _init = $datepicker.init; |
|
723 |
$datepicker.init = function () { |
|
724 |
if (isNative && options.useNative) { |
|
725 |
element.prop('type', 'date'); |
|
726 |
element.css('-webkit-appearance', 'textfield'); |
|
727 |
return; |
|
728 |
} else if (isTouch) { |
|
729 |
element.prop('type', 'text'); |
|
730 |
element.attr('readonly', 'true'); |
|
731 |
element.on('click', focusElement); |
|
732 |
} |
|
733 |
_init(); |
|
734 |
}; |
|
735 |
var _destroy = $datepicker.destroy; |
|
736 |
$datepicker.destroy = function () { |
|
737 |
if (isNative && options.useNative) { |
|
738 |
element.off('click', focusElement); |
|
739 |
} |
|
740 |
_destroy(); |
|
741 |
}; |
|
742 |
var _show = $datepicker.show; |
|
743 |
$datepicker.show = function () { |
|
744 |
_show(); |
|
745 |
setTimeout(function () { |
|
746 |
$datepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown); |
|
747 |
if (options.keyboard) { |
|
748 |
element.on('keydown', $datepicker.$onKeyDown); |
|
749 |
} |
|
750 |
}); |
|
751 |
}; |
|
752 |
var _hide = $datepicker.hide; |
|
753 |
$datepicker.hide = function (blur) { |
|
754 |
$datepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown); |
|
755 |
if (options.keyboard) { |
|
756 |
element.off('keydown', $datepicker.$onKeyDown); |
|
757 |
} |
|
758 |
_hide(blur); |
|
759 |
}; |
|
760 |
return $datepicker; |
|
761 |
} |
|
762 |
DatepickerFactory.defaults = defaults; |
|
763 |
return DatepickerFactory; |
|
764 |
} |
|
765 |
]; |
|
766 |
}).directive('bsDatepicker', [ |
|
767 |
'$window', |
|
768 |
'$parse', |
|
769 |
'$q', |
|
770 |
'$locale', |
|
771 |
'dateFilter', |
|
772 |
'$datepicker', |
|
773 |
'$dateParser', |
|
774 |
'$timeout', |
|
775 |
function ($window, $parse, $q, $locale, dateFilter, $datepicker, $dateParser, $timeout) { |
|
776 |
var defaults = $datepicker.defaults; |
|
777 |
var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent); |
|
778 |
var isNumeric = function (n) { |
|
779 |
return !isNaN(parseFloat(n)) && isFinite(n); |
|
780 |
}; |
|
781 |
return { |
|
782 |
restrict: 'EAC', |
|
783 |
require: 'ngModel', |
|
784 |
link: function postLink(scope, element, attr, controller) { |
|
785 |
// Directive options |
|
786 |
var options = { |
|
787 |
scope: scope, |
|
788 |
controller: controller |
|
789 |
}; |
|
790 |
angular.forEach([ |
|
791 |
'placement', |
|
792 |
'container', |
|
793 |
'delay', |
|
794 |
'trigger', |
|
795 |
'keyboard', |
|
796 |
'html', |
|
797 |
'animation', |
|
798 |
'template', |
|
799 |
'autoclose', |
|
800 |
'dateType', |
|
801 |
'dateFormat', |
|
802 |
'modelDateFormat', |
|
803 |
'dayFormat', |
|
804 |
'strictFormat', |
|
805 |
'startWeek', |
|
806 |
'useNative', |
|
807 |
'lang', |
|
808 |
'startView', |
|
809 |
'minView' |
|
810 |
], function (key) { |
|
811 |
if (angular.isDefined(attr[key])) |
|
812 |
options[key] = attr[key]; |
|
813 |
}); |
|
814 |
// Initialize datepicker |
|
815 |
if (isNative && options.useNative) |
|
816 |
options.dateFormat = 'yyyy-MM-dd'; |
|
817 |
var datepicker = $datepicker(element, controller, options); |
|
818 |
options = datepicker.$options; |
|
819 |
// Observe attributes for changes |
|
820 |
angular.forEach([ |
|
821 |
'minDate', |
|
822 |
'maxDate' |
|
823 |
], function (key) { |
|
824 |
// console.warn('attr.$observe(%s)', key, attr[key]); |
|
825 |
angular.isDefined(attr[key]) && attr.$observe(key, function (newValue) { |
|
826 |
// console.warn('attr.$observe(%s)=%o', key, newValue); |
|
827 |
if (newValue === 'today') { |
|
828 |
var today = new Date(); |
|
829 |
datepicker.$options[key] = +new Date(today.getFullYear(), today.getMonth(), today.getDate() + (key === 'maxDate' ? 1 : 0), 0, 0, 0, key === 'minDate' ? 0 : -1); |
|
830 |
} else if (angular.isString(newValue) && newValue.match(/^".+"$/)) { |
|
831 |
// Support {{ dateObj }} |
|
832 |
datepicker.$options[key] = +new Date(newValue.substr(1, newValue.length - 2)); |
|
833 |
} else if (isNumeric(newValue)) { |
|
834 |
datepicker.$options[key] = +new Date(parseInt(newValue, 10)); |
|
835 |
} else { |
|
836 |
datepicker.$options[key] = +new Date(newValue); |
|
837 |
} |
|
838 |
// Build only if dirty |
|
839 |
!isNaN(datepicker.$options[key]) && datepicker.$build(false); |
|
840 |
}); |
|
841 |
}); |
|
842 |
// Watch model for changes |
|
843 |
scope.$watch(attr.ngModel, function (newValue, oldValue) { |
|
844 |
datepicker.update(controller.$dateValue); |
|
845 |
}, true); |
|
846 |
var dateParser = $dateParser({ |
|
847 |
format: options.dateFormat, |
|
848 |
lang: options.lang, |
|
849 |
strict: options.strictFormat |
|
850 |
}); |
|
851 |
// viewValue -> $parsers -> modelValue |
|
852 |
controller.$parsers.unshift(function (viewValue) { |
|
853 |
// console.warn('$parser("%s"): viewValue=%o', element.attr('ng-model'), viewValue); |
|
854 |
// Null values should correctly reset the model value & validity |
|
855 |
if (!viewValue) { |
|
856 |
controller.$setValidity('date', true); |
|
857 |
return; |
|
858 |
} |
|
859 |
var parsedDate = dateParser.parse(viewValue, controller.$dateValue); |
|
860 |
if (!parsedDate || isNaN(parsedDate.getTime())) { |
|
861 |
controller.$setValidity('date', false); |
|
862 |
return; |
|
863 |
} else { |
|
864 |
var isMinValid = isNaN(datepicker.$options.minDate) || parsedDate.getTime() >= datepicker.$options.minDate; |
|
865 |
var isMaxValid = isNaN(datepicker.$options.maxDate) || parsedDate.getTime() <= datepicker.$options.maxDate; |
|
866 |
var isValid = isMinValid && isMaxValid; |
|
867 |
controller.$setValidity('date', isValid); |
|
868 |
controller.$setValidity('min', isMinValid); |
|
869 |
controller.$setValidity('max', isMaxValid); |
|
870 |
// Only update the model when we have a valid date |
|
871 |
if (isValid) |
|
872 |
controller.$dateValue = parsedDate; |
|
873 |
} |
|
874 |
if (options.dateType === 'string') { |
|
875 |
return dateFilter(parsedDate, options.modelDateFormat || options.dateFormat); |
|
876 |
} else if (options.dateType === 'number') { |
|
877 |
return controller.$dateValue.getTime(); |
|
878 |
} else if (options.dateType === 'iso') { |
|
879 |
return controller.$dateValue.toISOString(); |
|
880 |
} else { |
|
881 |
return new Date(controller.$dateValue); |
|
882 |
} |
|
883 |
}); |
|
884 |
// modelValue -> $formatters -> viewValue |
|
885 |
controller.$formatters.push(function (modelValue) { |
|
886 |
// console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue); |
|
887 |
var date; |
|
888 |
if (angular.isUndefined(modelValue) || modelValue === null) { |
|
889 |
date = NaN; |
|
890 |
} else if (angular.isDate(modelValue)) { |
|
891 |
date = modelValue; |
|
892 |
} else if (options.dateType === 'string') { |
|
893 |
date = dateParser.parse(modelValue, null, options.modelDateFormat); |
|
894 |
} else { |
|
895 |
date = new Date(modelValue); |
|
896 |
} |
|
897 |
// Setup default value? |
|
898 |
// if(isNaN(date.getTime())) { |
|
899 |
// var today = new Date(); |
|
900 |
// date = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0, 0); |
|
901 |
// } |
|
902 |
controller.$dateValue = date; |
|
903 |
return controller.$dateValue; |
|
904 |
}); |
|
905 |
// viewValue -> element |
|
906 |
controller.$render = function () { |
|
907 |
// console.warn('$render("%s"): viewValue=%o', element.attr('ng-model'), controller.$viewValue); |
|
908 |
element.val(!controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : dateFilter(controller.$dateValue, options.dateFormat)); |
|
909 |
}; |
|
910 |
// Garbage collection |
|
911 |
scope.$on('$destroy', function () { |
|
912 |
datepicker.destroy(); |
|
913 |
options = null; |
|
914 |
datepicker = null; |
|
915 |
}); |
|
916 |
} |
|
917 |
}; |
|
918 |
} |
|
919 |
]).provider('datepickerViews', function () { |
|
920 |
var defaults = this.defaults = { |
|
921 |
dayFormat: 'dd', |
|
922 |
daySplit: 7 |
|
923 |
}; |
|
924 |
// Split array into smaller arrays |
|
925 |
function split(arr, size) { |
|
926 |
var arrays = []; |
|
927 |
while (arr.length > 0) { |
|
928 |
arrays.push(arr.splice(0, size)); |
|
929 |
} |
|
930 |
return arrays; |
|
931 |
} |
|
932 |
// Modulus operator |
|
933 |
function mod(n, m) { |
|
934 |
return (n % m + m) % m; |
|
935 |
} |
|
936 |
this.$get = [ |
|
937 |
'$locale', |
|
938 |
'$sce', |
|
939 |
'dateFilter', |
|
940 |
function ($locale, $sce, dateFilter) { |
|
941 |
return function (picker) { |
|
942 |
var scope = picker.$scope; |
|
943 |
var options = picker.$options; |
|
944 |
var weekDaysMin = $locale.DATETIME_FORMATS.SHORTDAY; |
|
945 |
var weekDaysLabels = weekDaysMin.slice(options.startWeek).concat(weekDaysMin.slice(0, options.startWeek)); |
|
946 |
var weekDaysLabelsHtml = $sce.trustAsHtml('<th class="dow text-center">' + weekDaysLabels.join('</th><th class="dow text-center">') + '</th>'); |
|
947 |
var startDate = picker.$date || new Date(); |
|
948 |
var viewDate = { |
|
949 |
year: startDate.getFullYear(), |
|
950 |
month: startDate.getMonth(), |
|
951 |
date: startDate.getDate() |
|
952 |
}; |
|
953 |
var timezoneOffset = startDate.getTimezoneOffset() * 60000; |
|
954 |
var views = [ |
|
955 |
{ |
|
956 |
format: options.dayFormat, |
|
957 |
split: 7, |
|
958 |
steps: { month: 1 }, |
|
959 |
update: function (date, force) { |
|
960 |
if (!this.built || force || date.getFullYear() !== viewDate.year || date.getMonth() !== viewDate.month) { |
|
961 |
angular.extend(viewDate, { |
|
962 |
year: picker.$date.getFullYear(), |
|
963 |
month: picker.$date.getMonth(), |
|
964 |
date: picker.$date.getDate() |
|
965 |
}); |
|
966 |
picker.$build(); |
|
967 |
} else if (date.getDate() !== viewDate.date) { |
|
968 |
viewDate.date = picker.$date.getDate(); |
|
969 |
picker.$updateSelected(); |
|
970 |
} |
|
971 |
}, |
|
972 |
build: function () { |
|
973 |
var firstDayOfMonth = new Date(viewDate.year, viewDate.month, 1), firstDayOfMonthOffset = firstDayOfMonth.getTimezoneOffset(); |
|
974 |
var firstDate = new Date(+firstDayOfMonth - mod(firstDayOfMonth.getDay() - options.startWeek, 7) * 86400000), firstDateOffset = firstDate.getTimezoneOffset(); |
|
975 |
// Handle daylight time switch |
|
976 |
if (firstDateOffset !== firstDayOfMonthOffset) |
|
977 |
firstDate = new Date(+firstDate + (firstDateOffset - firstDayOfMonthOffset) * 60000); |
|
978 |
var days = [], day; |
|
979 |
for (var i = 0; i < 42; i++) { |
|
980 |
// < 7 * 6 |
|
981 |
day = new Date(firstDate.getFullYear(), firstDate.getMonth(), firstDate.getDate() + i); |
|
982 |
days.push({ |
|
983 |
date: day, |
|
984 |
label: dateFilter(day, this.format), |
|
985 |
selected: picker.$date && this.isSelected(day), |
|
986 |
muted: day.getMonth() !== viewDate.month, |
|
987 |
disabled: this.isDisabled(day) |
|
988 |
}); |
|
989 |
} |
|
990 |
scope.title = dateFilter(firstDayOfMonth, 'MMMM yyyy'); |
|
991 |
scope.showLabels = true; |
|
992 |
scope.labels = weekDaysLabelsHtml; |
|
993 |
scope.rows = split(days, this.split); |
|
994 |
this.built = true; |
|
995 |
}, |
|
996 |
isSelected: function (date) { |
|
997 |
return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth() && date.getDate() === picker.$date.getDate(); |
|
998 |
}, |
|
999 |
isDisabled: function (date) { |
|
1000 |
return date.getTime() < options.minDate || date.getTime() > options.maxDate; |
|
1001 |
}, |
|
1002 |
onKeyDown: function (evt) { |
|
1003 |
var actualTime = picker.$date.getTime(); |
|
1004 |
var newDate; |
|
1005 |
if (evt.keyCode === 37) |
|
1006 |
newDate = new Date(actualTime - 1 * 86400000); |
|
1007 |
else if (evt.keyCode === 38) |
|
1008 |
newDate = new Date(actualTime - 7 * 86400000); |
|
1009 |
else if (evt.keyCode === 39) |
|
1010 |
newDate = new Date(actualTime + 1 * 86400000); |
|
1011 |
else if (evt.keyCode === 40) |
|
1012 |
newDate = new Date(actualTime + 7 * 86400000); |
|
1013 |
if (!this.isDisabled(newDate)) |
|
1014 |
picker.select(newDate, true); |
|
1015 |
} |
|
1016 |
}, |
|
1017 |
{ |
|
1018 |
name: 'month', |
|
1019 |
format: 'MMM', |
|
1020 |
split: 4, |
|
1021 |
steps: { year: 1 }, |
|
1022 |
update: function (date, force) { |
|
1023 |
if (!this.built || date.getFullYear() !== viewDate.year) { |
|
1024 |
angular.extend(viewDate, { |
|
1025 |
year: picker.$date.getFullYear(), |
|
1026 |
month: picker.$date.getMonth(), |
|
1027 |
date: picker.$date.getDate() |
|
1028 |
}); |
|
1029 |
picker.$build(); |
|
1030 |
} else if (date.getMonth() !== viewDate.month) { |
|
1031 |
angular.extend(viewDate, { |
|
1032 |
month: picker.$date.getMonth(), |
|
1033 |
date: picker.$date.getDate() |
|
1034 |
}); |
|
1035 |
picker.$updateSelected(); |
|
1036 |
} |
|
1037 |
}, |
|
1038 |
build: function () { |
|
1039 |
var firstMonth = new Date(viewDate.year, 0, 1); |
|
1040 |
var months = [], month; |
|
1041 |
for (var i = 0; i < 12; i++) { |
|
1042 |
month = new Date(viewDate.year, i, 1); |
|
1043 |
months.push({ |
|
1044 |
date: month, |
|
1045 |
label: dateFilter(month, this.format), |
|
1046 |
selected: picker.$isSelected(month), |
|
1047 |
disabled: this.isDisabled(month) |
|
1048 |
}); |
|
1049 |
} |
|
1050 |
scope.title = dateFilter(month, 'yyyy'); |
|
1051 |
scope.showLabels = false; |
|
1052 |
scope.rows = split(months, this.split); |
|
1053 |
this.built = true; |
|
1054 |
}, |
|
1055 |
isSelected: function (date) { |
|
1056 |
return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth(); |
|
1057 |
}, |
|
1058 |
isDisabled: function (date) { |
|
1059 |
var lastDate = +new Date(date.getFullYear(), date.getMonth() + 1, 0); |
|
1060 |
return lastDate < options.minDate || date.getTime() > options.maxDate; |
|
1061 |
}, |
|
1062 |
onKeyDown: function (evt) { |
|
1063 |
var actualMonth = picker.$date.getMonth(); |
|
1064 |
var newDate = new Date(picker.$date); |
|
1065 |
if (evt.keyCode === 37) |
|
1066 |
newDate.setMonth(actualMonth - 1); |
|
1067 |
else if (evt.keyCode === 38) |
|
1068 |
newDate.setMonth(actualMonth - 4); |
|
1069 |
else if (evt.keyCode === 39) |
|
1070 |
newDate.setMonth(actualMonth + 1); |
|
1071 |
else if (evt.keyCode === 40) |
|
1072 |
newDate.setMonth(actualMonth + 4); |
|
1073 |
if (!this.isDisabled(newDate)) |
|
1074 |
picker.select(newDate, true); |
|
1075 |
} |
|
1076 |
}, |
|
1077 |
{ |
|
1078 |
name: 'year', |
|
1079 |
format: 'yyyy', |
|
1080 |
split: 4, |
|
1081 |
steps: { year: 12 }, |
|
1082 |
update: function (date, force) { |
|
1083 |
if (!this.built || force || parseInt(date.getFullYear() / 20, 10) !== parseInt(viewDate.year / 20, 10)) { |
|
1084 |
angular.extend(viewDate, { |
|
1085 |
year: picker.$date.getFullYear(), |
|
1086 |
month: picker.$date.getMonth(), |
|
1087 |
date: picker.$date.getDate() |
|
1088 |
}); |
|
1089 |
picker.$build(); |
|
1090 |
} else if (date.getFullYear() !== viewDate.year) { |
|
1091 |
angular.extend(viewDate, { |
|
1092 |
year: picker.$date.getFullYear(), |
|
1093 |
month: picker.$date.getMonth(), |
|
1094 |
date: picker.$date.getDate() |
|
1095 |
}); |
|
1096 |
picker.$updateSelected(); |
|
1097 |
} |
|
1098 |
}, |
|
1099 |
build: function () { |
|
1100 |
var firstYear = viewDate.year - viewDate.year % (this.split * 3); |
|
1101 |
var years = [], year; |
|
1102 |
for (var i = 0; i < 12; i++) { |
|
1103 |
year = new Date(firstYear + i, 0, 1); |
|
1104 |
years.push({ |
|
1105 |
date: year, |
|
1106 |
label: dateFilter(year, this.format), |
|
1107 |
selected: picker.$isSelected(year), |
|
1108 |
disabled: this.isDisabled(year) |
|
1109 |
}); |
|
1110 |
} |
|
1111 |
scope.title = years[0].label + '-' + years[years.length - 1].label; |
|
1112 |
scope.showLabels = false; |
|
1113 |
scope.rows = split(years, this.split); |
|
1114 |
this.built = true; |
|
1115 |
}, |
|
1116 |
isSelected: function (date) { |
|
1117 |
return picker.$date && date.getFullYear() === picker.$date.getFullYear(); |
|
1118 |
}, |
|
1119 |
isDisabled: function (date) { |
|
1120 |
var lastDate = +new Date(date.getFullYear() + 1, 0, 0); |
|
1121 |
return lastDate < options.minDate || date.getTime() > options.maxDate; |
|
1122 |
}, |
|
1123 |
onKeyDown: function (evt) { |
|
1124 |
var actualYear = picker.$date.getFullYear(), newDate = new Date(picker.$date); |
|
1125 |
if (evt.keyCode === 37) |
|
1126 |
newDate.setYear(actualYear - 1); |
|
1127 |
else if (evt.keyCode === 38) |
|
1128 |
newDate.setYear(actualYear - 4); |
|
1129 |
else if (evt.keyCode === 39) |
|
1130 |
newDate.setYear(actualYear + 1); |
|
1131 |
else if (evt.keyCode === 40) |
|
1132 |
newDate.setYear(actualYear + 4); |
|
1133 |
if (!this.isDisabled(newDate)) |
|
1134 |
picker.select(newDate, true); |
|
1135 |
} |
|
1136 |
} |
|
1137 |
]; |
|
1138 |
return { |
|
1139 |
views: options.minView ? Array.prototype.slice.call(views, options.minView) : views, |
|
1140 |
viewDate: viewDate |
|
1141 |
}; |
|
1142 |
}; |
|
1143 |
} |
|
1144 |
]; |
|
1145 |
}); |
|
1146 |
|
|
1147 |
// Source: date-parser.js |
|
1148 |
angular.module('mgcrea.ngStrap.helpers.dateParser', []).provider('$dateParser', [ |
|
1149 |
'$localeProvider', |
|
1150 |
function ($localeProvider) { |
|
1151 |
var proto = Date.prototype; |
|
1152 |
function isNumeric(n) { |
|
1153 |
return !isNaN(parseFloat(n)) && isFinite(n); |
|
1154 |
} |
|
1155 |
var defaults = this.defaults = { |
|
1156 |
format: 'shortDate', |
|
1157 |
strict: false |
|
1158 |
}; |
|
1159 |
this.$get = [ |
|
1160 |
'$locale', |
|
1161 |
function ($locale) { |
|
1162 |
var DateParserFactory = function (config) { |
|
1163 |
var options = angular.extend({}, defaults, config); |
|
1164 |
var $dateParser = {}; |
|
1165 |
var regExpMap = { |
|
1166 |
'sss': '[0-9]{3}', |
|
1167 |
'ss': '[0-5][0-9]', |
|
1168 |
's': options.strict ? '[1-5]?[0-9]' : '[0-9]|[0-5][0-9]', |
|
1169 |
'mm': '[0-5][0-9]', |
|
1170 |
'm': options.strict ? '[1-5]?[0-9]' : '[0-9]|[0-5][0-9]', |
|
1171 |
'HH': '[01][0-9]|2[0-3]', |
|
1172 |
'H': options.strict ? '1?[0-9]|2[0-3]' : '[01]?[0-9]|2[0-3]', |
|
1173 |
'hh': '[0][1-9]|[1][012]', |
|
1174 |
'h': options.strict ? '[1-9]|1[012]' : '0?[1-9]|1[012]', |
|
1175 |
'a': 'AM|PM', |
|
1176 |
'EEEE': $locale.DATETIME_FORMATS.DAY.join('|'), |
|
1177 |
'EEE': $locale.DATETIME_FORMATS.SHORTDAY.join('|'), |
|
1178 |
'dd': '0[1-9]|[12][0-9]|3[01]', |
|
1179 |
'd': options.strict ? '[1-9]|[1-2][0-9]|3[01]' : '0?[1-9]|[1-2][0-9]|3[01]', |
|
1180 |
'MMMM': $locale.DATETIME_FORMATS.MONTH.join('|'), |
|
1181 |
'MMM': $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), |
|
1182 |
'MM': '0[1-9]|1[012]', |
|
1183 |
'M': options.strict ? '[1-9]|1[012]' : '0?[1-9]|1[012]', |
|
1184 |
'yyyy': '[1]{1}[0-9]{3}|[2]{1}[0-9]{3}', |
|
1185 |
'yy': '[0-9]{2}', |
|
1186 |
'y': options.strict ? '-?(0|[1-9][0-9]{0,3})' : '-?0*[0-9]{1,4}' |
|
1187 |
}; |
|
1188 |
var setFnMap = { |
|
1189 |
'sss': proto.setMilliseconds, |
|
1190 |
'ss': proto.setSeconds, |
|
1191 |
's': proto.setSeconds, |
|
1192 |
'mm': proto.setMinutes, |
|
1193 |
'm': proto.setMinutes, |
|
1194 |
'HH': proto.setHours, |
|
1195 |
'H': proto.setHours, |
|
1196 |
'hh': proto.setHours, |
|
1197 |
'h': proto.setHours, |
|
1198 |
'dd': proto.setDate, |
|
1199 |
'd': proto.setDate, |
|
1200 |
'a': function (value) { |
|
1201 |
var hours = this.getHours(); |
|
1202 |
return this.setHours(value.match(/pm/i) ? hours + 12 : hours); |
|
1203 |
}, |
|
1204 |
'MMMM': function (value) { |
|
1205 |
return this.setMonth($locale.DATETIME_FORMATS.MONTH.indexOf(value)); |
|
1206 |
}, |
|
1207 |
'MMM': function (value) { |
|
1208 |
return this.setMonth($locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value)); |
|
1209 |
}, |
|
1210 |
'MM': function (value) { |
|
1211 |
return this.setMonth(1 * value - 1); |
|
1212 |
}, |
|
1213 |
'M': function (value) { |
|
1214 |
return this.setMonth(1 * value - 1); |
|
1215 |
}, |
|
1216 |
'yyyy': proto.setFullYear, |
|
1217 |
'yy': function (value) { |
|
1218 |
return this.setFullYear(2000 + 1 * value); |
|
1219 |
}, |
|
1220 |
'y': proto.setFullYear |
|
1221 |
}; |
|
1222 |
var regex, setMap; |
|
1223 |
$dateParser.init = function () { |
|
1224 |
$dateParser.$format = $locale.DATETIME_FORMATS[options.format] || options.format; |
|
1225 |
regex = regExpForFormat($dateParser.$format); |
|
1226 |
setMap = setMapForFormat($dateParser.$format); |
|
1227 |
}; |
|
1228 |
$dateParser.isValid = function (date) { |
|
1229 |
if (angular.isDate(date)) |
|
1230 |
return !isNaN(date.getTime()); |
|
1231 |
return regex.test(date); |
|
1232 |
}; |
|
1233 |
$dateParser.parse = function (value, baseDate, format) { |
|
1234 |
var formatRegex = format ? regExpForFormat(format) : regex; |
|
1235 |
var formatSetMap = format ? setMapForFormat(format) : setMap; |
|
1236 |
if (angular.isDate(value)) |
|
1237 |
return value; |
|
1238 |
var matches = formatRegex.exec(value); |
|
1239 |
if (!matches) |
|
1240 |
return false; |
|
1241 |
var date = baseDate || new Date(0, 0, 1); |
|
1242 |
for (var i = 0; i < matches.length - 1; i++) { |
|
1243 |
formatSetMap[i] && formatSetMap[i].call(date, matches[i + 1]); |
|
1244 |
} |
|
1245 |
return date; |
|
1246 |
}; |
|
1247 |
// Private functions |
|
1248 |
function setMapForFormat(format) { |
|
1249 |
var keys = Object.keys(setFnMap), i; |
|
1250 |
var map = [], sortedMap = []; |
|
1251 |
// Map to setFn |
|
1252 |
var clonedFormat = format; |
|
1253 |
for (i = 0; i < keys.length; i++) { |
|
1254 |
if (format.split(keys[i]).length > 1) { |
|
1255 |
var index = clonedFormat.search(keys[i]); |
|
1256 |
format = format.split(keys[i]).join(''); |
|
1257 |
if (setFnMap[keys[i]]) |
|
1258 |
map[index] = setFnMap[keys[i]]; |
|
1259 |
} |
|
1260 |
} |
|
1261 |
// Sort result map |
|
1262 |
angular.forEach(map, function (v) { |
|
1263 |
sortedMap.push(v); |
|
1264 |
}); |
|
1265 |
return sortedMap; |
|
1266 |
} |
|
1267 |
function escapeReservedSymbols(text) { |
|
1268 |
return text.replace(/\//g, '[\\/]').replace('/-/g', '[-]').replace(/\./g, '[.]').replace(/\\s/g, '[\\s]'); |
|
1269 |
} |
|
1270 |
function regExpForFormat(format) { |
|
1271 |
var keys = Object.keys(regExpMap), i; |
|
1272 |
var re = format; |
|
1273 |
// Abstract replaces to avoid collisions |
|
1274 |
for (i = 0; i < keys.length; i++) { |
|
1275 |
re = re.split(keys[i]).join('${' + i + '}'); |
|
1276 |
} |
|
1277 |
// Replace abstracted values |
|
1278 |
for (i = 0; i < keys.length; i++) { |
|
1279 |
re = re.split('${' + i + '}').join('(' + regExpMap[keys[i]] + ')'); |
|
1280 |
} |
|
1281 |
format = escapeReservedSymbols(format); |
|
1282 |
return new RegExp('^' + re + '$', ['i']); |
|
1283 |
} |
|
1284 |
$dateParser.init(); |
|
1285 |
return $dateParser; |
|
1286 |
}; |
|
1287 |
return DateParserFactory; |
|
1288 |
} |
|
1289 |
]; |
|
1290 |
} |
|
1291 |
]); |
|
1292 |
|
|
1293 |
// Source: debounce.js |
|
1294 |
angular.module('mgcrea.ngStrap.helpers.debounce', []).constant('debounce', function (func, wait, immediate) { |
|
1295 |
var timeout, args, context, timestamp, result; |
|
1296 |
return function () { |
|
1297 |
context = this; |
|
1298 |
args = arguments; |
|
1299 |
timestamp = new Date(); |
|
1300 |
var later = function () { |
|
1301 |
var last = new Date() - timestamp; |
|
1302 |
if (last < wait) { |
|
1303 |
timeout = setTimeout(later, wait - last); |
|
1304 |
} else { |
|
1305 |
timeout = null; |
|
1306 |
if (!immediate) |
|
1307 |
result = func.apply(context, args); |
|
1308 |
} |
|
1309 |
}; |
|
1310 |
var callNow = immediate && !timeout; |
|
1311 |
if (!timeout) { |
|
1312 |
timeout = setTimeout(later, wait); |
|
1313 |
} |
|
1314 |
if (callNow) |
|
1315 |
result = func.apply(context, args); |
|
1316 |
return result; |
|
1317 |
}; |
|
1318 |
}).constant('throttle', function (func, wait, options) { |
|
1319 |
var context, args, result; |
|
1320 |
var timeout = null; |
|
1321 |
var previous = 0; |
|
1322 |
options || (options = {}); |
|
1323 |
var later = function () { |
|
1324 |
previous = options.leading === false ? 0 : new Date(); |
|
1325 |
timeout = null; |
|
1326 |
result = func.apply(context, args); |
|
1327 |
}; |
|
1328 |
return function () { |
|
1329 |
var now = new Date(); |
|
1330 |
if (!previous && options.leading === false) |
|
1331 |
previous = now; |
|
1332 |
var remaining = wait - (now - previous); |
|
1333 |
context = this; |
|
1334 |
args = arguments; |
|
1335 |
if (remaining <= 0) { |
|
1336 |
clearTimeout(timeout); |
|
1337 |
timeout = null; |
|
1338 |
previous = now; |
|
1339 |
result = func.apply(context, args); |
|
1340 |
} else if (!timeout && options.trailing !== false) { |
|
1341 |
timeout = setTimeout(later, remaining); |
|
1342 |
} |
|
1343 |
return result; |
|
1344 |
}; |
|
1345 |
}); |
|
1346 |
|
|
1347 |
// Source: dimensions.js |
|
1348 |
angular.module('mgcrea.ngStrap.helpers.dimensions', []).factory('dimensions', [ |
|
1349 |
'$document', |
|
1350 |
'$window', |
|
1351 |
function ($document, $window) { |
|
1352 |
var jqLite = angular.element; |
|
1353 |
var fn = {}; |
|
1354 |
/** |
|
1355 |
* Test the element nodeName |
|
1356 |
* @param element |
|
1357 |
* @param name |
|
1358 |
*/ |
|
1359 |
var nodeName = fn.nodeName = function (element, name) { |
|
1360 |
return element.nodeName && element.nodeName.toLowerCase() === name.toLowerCase(); |
|
1361 |
}; |
|
1362 |
/** |
|
1363 |
* Returns the element computed style |
|
1364 |
* @param element |
|
1365 |
* @param prop |
|
1366 |
* @param extra |
|
1367 |
*/ |
|
1368 |
fn.css = function (element, prop, extra) { |
|
1369 |
var value; |
|
1370 |
if (element.currentStyle) { |
|
1371 |
//IE |
|
1372 |
value = element.currentStyle[prop]; |
|
1373 |
} else if (window.getComputedStyle) { |
|
1374 |
value = window.getComputedStyle(element)[prop]; |
|
1375 |
} else { |
|
1376 |
value = element.style[prop]; |
|
1377 |
} |
|
1378 |
return extra === true ? parseFloat(value) || 0 : value; |
|
1379 |
}; |
|
1380 |
/** |
|
1381 |
* Provides read-only equivalent of jQuery's offset function: |
|
1382 |
* @required-by bootstrap-tooltip, bootstrap-affix |
|
1383 |
* @url http://api.jquery.com/offset/ |
|
1384 |
* @param element |
|
1385 |
*/ |
|
1386 |
fn.offset = function (element) { |
|
1387 |
var boxRect = element.getBoundingClientRect(); |
|
1388 |
var docElement = element.ownerDocument; |
|
1389 |
return { |
|
1390 |
width: boxRect.width || element.offsetWidth, |
|
1391 |
height: boxRect.height || element.offsetHeight, |
|
1392 |
top: boxRect.top + (window.pageYOffset || docElement.documentElement.scrollTop) - (docElement.documentElement.clientTop || 0), |
|
1393 |
left: boxRect.left + (window.pageXOffset || docElement.documentElement.scrollLeft) - (docElement.documentElement.clientLeft || 0) |
|
1394 |
}; |
|
1395 |
}; |
|
1396 |
/** |
|
1397 |
* Provides read-only equivalent of jQuery's position function |
|
1398 |
* @required-by bootstrap-tooltip, bootstrap-affix |
|
1399 |
* @url http://api.jquery.com/offset/ |
|
1400 |
* @param element |
|
1401 |
*/ |
|
1402 |
fn.position = function (element) { |
|
1403 |
var offsetParentRect = { |
|
1404 |
top: 0, |
|
1405 |
left: 0 |
|
1406 |
}, offsetParentElement, offset; |
|
1407 |
// Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent |
|
1408 |
if (fn.css(element, 'position') === 'fixed') { |
|
1409 |
// We assume that getBoundingClientRect is available when computed position is fixed |
|
1410 |
offset = element.getBoundingClientRect(); |
|
1411 |
} else { |
|
1412 |
// Get *real* offsetParentElement |
|
1413 |
offsetParentElement = offsetParent(element); |
|
1414 |
offset = fn.offset(element); |
|
1415 |
// Get correct offsets |
|
1416 |
offset = fn.offset(element); |
|
1417 |
if (!nodeName(offsetParentElement, 'html')) { |
|
1418 |
offsetParentRect = fn.offset(offsetParentElement); |
|
1419 |
} |
|
1420 |
// Add offsetParent borders |
|
1421 |
offsetParentRect.top += fn.css(offsetParentElement, 'borderTopWidth', true); |
|
1422 |
offsetParentRect.left += fn.css(offsetParentElement, 'borderLeftWidth', true); |
|
1423 |
} |
|
1424 |
// Subtract parent offsets and element margins |
|
1425 |
return { |
|
1426 |
width: element.offsetWidth, |
|
1427 |
height: element.offsetHeight, |
|
1428 |
top: offset.top - offsetParentRect.top - fn.css(element, 'marginTop', true), |
|
1429 |
left: offset.left - offsetParentRect.left - fn.css(element, 'marginLeft', true) |
|
1430 |
}; |
|
1431 |
}; |
|
1432 |
/** |
|
1433 |
* Returns the closest, non-statically positioned offsetParent of a given element |
|
1434 |
* @required-by fn.position |
|
1435 |
* @param element |
|
1436 |
*/ |
|
1437 |
var offsetParent = function offsetParentElement(element) { |
|
1438 |
var docElement = element.ownerDocument; |
|
1439 |
var offsetParent = element.offsetParent || docElement; |
|
1440 |
if (nodeName(offsetParent, '#document')) |
|
1441 |
return docElement.documentElement; |
|
1442 |
while (offsetParent && !nodeName(offsetParent, 'html') && fn.css(offsetParent, 'position') === 'static') { |
|
1443 |
offsetParent = offsetParent.offsetParent; |
|
1444 |
} |
|
1445 |
return offsetParent || docElement.documentElement; |
|
1446 |
}; |
|
1447 |
/** |
|
1448 |
* Provides equivalent of jQuery's height function |
|
1449 |
* @required-by bootstrap-affix |
|
1450 |
* @url http://api.jquery.com/height/ |
|
1451 |
* @param element |
|
1452 |
* @param outer |
|
1453 |
*/ |
|
1454 |
fn.height = function (element, outer) { |
|
1455 |
var value = element.offsetHeight; |
|
1456 |
if (outer) { |
|
1457 |
value += fn.css(element, 'marginTop', true) + fn.css(element, 'marginBottom', true); |
|
1458 |
} else { |
|
1459 |
value -= fn.css(element, 'paddingTop', true) + fn.css(element, 'paddingBottom', true) + fn.css(element, 'borderTopWidth', true) + fn.css(element, 'borderBottomWidth', true); |
|
1460 |
} |
|
1461 |
return value; |
|
1462 |
}; |
|
1463 |
/** |
|
1464 |
* Provides equivalent of jQuery's width function |
|
1465 |
* @required-by bootstrap-affix |
|
1466 |
* @url http://api.jquery.com/width/ |
|
1467 |
* @param element |
|
1468 |
* @param outer |
|
1469 |
*/ |
|
1470 |
fn.width = function (element, outer) { |
|
1471 |
var value = element.offsetWidth; |
|
1472 |
if (outer) { |
|
1473 |
value += fn.css(element, 'marginLeft', true) + fn.css(element, 'marginRight', true); |
|
1474 |
} else { |
|
1475 |
value -= fn.css(element, 'paddingLeft', true) + fn.css(element, 'paddingRight', true) + fn.css(element, 'borderLeftWidth', true) + fn.css(element, 'borderRightWidth', true); |
|
1476 |
} |
|
1477 |
return value; |
|
1478 |
}; |
|
1479 |
return fn; |
|
1480 |
} |
|
1481 |
]); |
|
1482 |
|
|
1483 |
// Source: parse-options.js |
|
1484 |
angular.module('mgcrea.ngStrap.helpers.parseOptions', []).provider('$parseOptions', function () { |
|
1485 |
var defaults = this.defaults = { regexp: /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/ }; |
|
1486 |
this.$get = [ |
|
1487 |
'$parse', |
|
1488 |
'$q', |
|
1489 |
function ($parse, $q) { |
|
1490 |
function ParseOptionsFactory(attr, config) { |
|
1491 |
var $parseOptions = {}; |
|
1492 |
// Common vars |
|
1493 |
var options = angular.extend({}, defaults, config); |
|
1494 |
$parseOptions.$values = []; |
|
1495 |
// Private vars |
|
1496 |
var match, displayFn, valueName, keyName, groupByFn, valueFn, valuesFn; |
|
1497 |
$parseOptions.init = function () { |
|
1498 |
$parseOptions.$match = match = attr.match(options.regexp); |
|
1499 |
displayFn = $parse(match[2] || match[1]), valueName = match[4] || match[6], keyName = match[5], groupByFn = $parse(match[3] || ''), valueFn = $parse(match[2] ? match[1] : valueName), valuesFn = $parse(match[7]); |
|
1500 |
}; |
|
1501 |
$parseOptions.valuesFn = function (scope, controller) { |
|
1502 |
return $q.when(valuesFn(scope, controller)).then(function (values) { |
|
1503 |
$parseOptions.$values = values ? parseValues(values, scope) : {}; |
|
1504 |
return $parseOptions.$values; |
|
1505 |
}); |
|
1506 |
}; |
|
1507 |
// Private functions |
|
1508 |
function parseValues(values, scope) { |
|
1509 |
return values.map(function (match, index) { |
|
1510 |
var locals = {}, label, value; |
|
1511 |
locals[valueName] = match; |
|
1512 |
label = displayFn(scope, locals); |
|
1513 |
value = valueFn(scope, locals) || index; |
|
1514 |
return { |
|
1515 |
label: label, |
|
1516 |
value: value |
|
1517 |
}; |
|
1518 |
}); |
|
1519 |
} |
|
1520 |
$parseOptions.init(); |
|
1521 |
return $parseOptions; |
|
1522 |
} |
|
1523 |
return ParseOptionsFactory; |
|
1524 |
} |
|
1525 |
]; |
|
1526 |
}); |
|
1527 |
|
|
1528 |
// Source: raf.js |
|
1529 |
angular.version.minor < 3 && angular.version.dot < 14 && angular.module('ng').factory('$$rAF', [ |
|
1530 |
'$window', |
|
1531 |
'$timeout', |
|
1532 |
function ($window, $timeout) { |
|
1533 |
var requestAnimationFrame = $window.requestAnimationFrame || $window.webkitRequestAnimationFrame || $window.mozRequestAnimationFrame; |
|
1534 |
var cancelAnimationFrame = $window.cancelAnimationFrame || $window.webkitCancelAnimationFrame || $window.mozCancelAnimationFrame || $window.webkitCancelRequestAnimationFrame; |
|
1535 |
var rafSupported = !!requestAnimationFrame; |
|
1536 |
var raf = rafSupported ? function (fn) { |
|
1537 |
var id = requestAnimationFrame(fn); |
|
1538 |
return function () { |
|
1539 |
cancelAnimationFrame(id); |
|
1540 |
}; |
|
1541 |
} : function (fn) { |
|
1542 |
var timer = $timeout(fn, 16.66, false); |
|
1543 |
// 1000 / 60 = 16.666 |
|
1544 |
return function () { |
|
1545 |
$timeout.cancel(timer); |
|
1546 |
}; |
|
1547 |
}; |
|
1548 |
raf.supported = rafSupported; |
|
1549 |
return raf; |
|
1550 |
} |
|
1551 |
]); // .factory('$$animateReflow', function($$rAF, $document) { |
|
1552 |
// var bodyEl = $document[0].body; |
|
1553 |
// return function(fn) { |
|
1554 |
// //the returned function acts as the cancellation function |
|
1555 |
// return $$rAF(function() { |
|
1556 |
// //the line below will force the browser to perform a repaint |
|
1557 |
// //so that all the animated elements within the animation frame |
|
1558 |
// //will be properly updated and drawn on screen. This is |
|
1559 |
// //required to perform multi-class CSS based animations with |
|
1560 |
// //Firefox. DO NOT REMOVE THIS LINE. |
|
1561 |
// var a = bodyEl.offsetWidth + 1; |
|
1562 |
// fn(); |
|
1563 |
// }); |
|
1564 |
// }; |
|
1565 |
// }); |
|
1566 |
|
|
1567 |
// Source: dropdown.js |
|
1568 |
angular.module('mgcrea.ngStrap.dropdown', ['mgcrea.ngStrap.tooltip']).provider('$dropdown', function () { |
|
1569 |
var defaults = this.defaults = { |
|
1570 |
animation: 'am-fade', |
|
1571 |
prefixClass: 'dropdown', |
|
1572 |
placement: 'bottom-left', |
|
1573 |
template: 'dropdown/dropdown.tpl.html', |
|
1574 |
trigger: 'click', |
|
1575 |
container: false, |
|
1576 |
keyboard: true, |
|
1577 |
html: false, |
|
1578 |
delay: 0 |
|
1579 |
}; |
|
1580 |
this.$get = [ |
|
1581 |
'$window', |
|
1582 |
'$rootScope', |
|
1583 |
'$tooltip', |
|
1584 |
function ($window, $rootScope, $tooltip) { |
|
1585 |
var bodyEl = angular.element($window.document.body); |
|
1586 |
var matchesSelector = Element.prototype.matchesSelector || Element.prototype.webkitMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector; |
|
1587 |
function DropdownFactory(element, config) { |
|
1588 |
var $dropdown = {}; |
|
1589 |
// Common vars |
|
1590 |
var options = angular.extend({}, defaults, config); |
|
1591 |
var scope = $dropdown.$scope = options.scope && options.scope.$new() || $rootScope.$new(); |
|
1592 |
$dropdown = $tooltip(element, options); |
|
1593 |
// Protected methods |
|
1594 |
$dropdown.$onKeyDown = function (evt) { |
|
1595 |
if (!/(38|40)/.test(evt.keyCode)) |
|
1596 |
return; |
|
1597 |
evt.preventDefault(); |
|
1598 |
evt.stopPropagation(); |
|
1599 |
// Retrieve focused index |
|
1600 |
var items = angular.element($dropdown.$element[0].querySelectorAll('li:not(.divider) a')); |
|
1601 |
if (!items.length) |
|
1602 |
return; |
|
1603 |
var index; |
|
1604 |
angular.forEach(items, function (el, i) { |
|
1605 |
if (matchesSelector && matchesSelector.call(el, ':focus')) |
|
1606 |
index = i; |
|
1607 |
}); |
|
1608 |
// Navigate with keyboard |
|
1609 |
if (evt.keyCode === 38 && index > 0) |
|
1610 |
index--; |
|
1611 |
else if (evt.keyCode === 40 && index < items.length - 1) |
|
1612 |
index++; |
|
1613 |
else if (angular.isUndefined(index)) |
|
1614 |
index = 0; |
|
1615 |
items.eq(index)[0].focus(); |
|
1616 |
}; |
|
1617 |
// Overrides |
|
1618 |
var show = $dropdown.show; |
|
1619 |
$dropdown.show = function () { |
|
1620 |
show(); |
|
1621 |
setTimeout(function () { |
|
1622 |
options.keyboard && $dropdown.$element.on('keydown', $dropdown.$onKeyDown); |
|
1623 |
bodyEl.on('click', onBodyClick); |
|
1624 |
}); |
|
1625 |
}; |
|
1626 |
var hide = $dropdown.hide; |
|
1627 |
$dropdown.hide = function () { |
|
1628 |
options.keyboard && $dropdown.$element.off('keydown', $dropdown.$onKeyDown); |
|
1629 |
bodyEl.off('click', onBodyClick); |
|
1630 |
hide(); |
|
1631 |
}; |
|
1632 |
// Private functions |
|
1633 |
function onBodyClick(evt) { |
|
1634 |
if (evt.target === element[0]) |
|
1635 |
return; |
|
1636 |
return evt.target !== element[0] && $dropdown.hide(); |
|
1637 |
} |
|
1638 |
return $dropdown; |
|
1639 |
} |
|
1640 |
return DropdownFactory; |
|
1641 |
} |
|
1642 |
]; |
|
1643 |
}).directive('bsDropdown', [ |
|
1644 |
'$window', |
|
1645 |
'$location', |
|
1646 |
'$sce', |
|
1647 |
'$dropdown', |
|
1648 |
function ($window, $location, $sce, $dropdown) { |
|
1649 |
return { |
|
1650 |
restrict: 'EAC', |
|
1651 |
scope: true, |
|
1652 |
link: function postLink(scope, element, attr, transclusion) { |
|
1653 |
// Directive options |
|
1654 |
var options = { scope: scope }; |
|
1655 |
angular.forEach([ |
|
1656 |
'placement', |
|
1657 |
'container', |
|
1658 |
'delay', |
|
1659 |
'trigger', |
|
1660 |
'keyboard', |
|
1661 |
'html', |
|
1662 |
'animation', |
|
1663 |
'template' |
|
1664 |
], function (key) { |
|
1665 |
if (angular.isDefined(attr[key])) |
|
1666 |
options[key] = attr[key]; |
|
1667 |
}); |
|
1668 |
// Support scope as an object |
|
1669 |
attr.bsDropdown && scope.$watch(attr.bsDropdown, function (newValue, oldValue) { |
|
1670 |
scope.content = newValue; |
|
1671 |
}, true); |
|
1672 |
// Initialize dropdown |
|
1673 |
var dropdown = $dropdown(element, options); |
|
1674 |
// Garbage collection |
|
1675 |
scope.$on('$destroy', function () { |
|
1676 |
dropdown.destroy(); |
|
1677 |
options = null; |
|
1678 |
dropdown = null; |
|
1679 |
}); |
|
1680 |
} |
|
1681 |
}; |
|
1682 |
} |
|
1683 |
]); |
|
1684 |
|
|
1685 |
// Source: modal.js |
|
1686 |
angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions']).provider('$modal', function () { |
|
1687 |
var defaults = this.defaults = { |
|
1688 |
animation: 'am-fade', |
|
1689 |
backdropAnimation: 'am-fade', |
|
1690 |
prefixClass: 'modal', |
|
1691 |
prefixEvent: 'modal', |
|
1692 |
placement: 'top', |
|
1693 |
template: 'modal/modal.tpl.html', |
|
1694 |
contentTemplate: false, |
|
1695 |
container: false, |
|
1696 |
element: null, |
|
1697 |
backdrop: true, |
|
1698 |
keyboard: true, |
|
1699 |
html: false, |
|
1700 |
show: true |
|
1701 |
}; |
|
1702 |
this.$get = [ |
|
1703 |
'$window', |
|
1704 |
'$rootScope', |
|
1705 |
'$compile', |
|
1706 |
'$q', |
|
1707 |
'$templateCache', |
|
1708 |
'$http', |
|
1709 |
'$animate', |
|
1710 |
'$timeout', |
|
1711 |
'$sce', |
|
1712 |
'dimensions', |
|
1713 |
function ($window, $rootScope, $compile, $q, $templateCache, $http, $animate, $timeout, $sce, dimensions) { |
|
1714 |
var forEach = angular.forEach; |
|
1715 |
var trim = String.prototype.trim; |
|
1716 |
var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout; |
|
1717 |
var bodyElement = angular.element($window.document.body); |
|
1718 |
var htmlReplaceRegExp = /ng-bind="/gi; |
|
1719 |
function ModalFactory(config) { |
|
1720 |
var $modal = {}; |
|
1721 |
// Common vars |
|
1722 |
var options = $modal.$options = angular.extend({}, defaults, config); |
|
1723 |
$modal.$promise = fetchTemplate(options.template); |
|
1724 |
var scope = $modal.$scope = options.scope && options.scope.$new() || $rootScope.$new(); |
|
1725 |
if (!options.element && !options.container) { |
|
1726 |
options.container = 'body'; |
|
1727 |
} |
|
1728 |
// Support scope as string options |
|
1729 |
forEach([ |
|
1730 |
'title', |
|
1731 |
'content' |
|
1732 |
], function (key) { |
|
1733 |
if (options[key]) |
|
1734 |
scope[key] = $sce.trustAsHtml(options[key]); |
|
1735 |
}); |
|
1736 |
// Provide scope helpers |
|
1737 |
scope.$hide = function () { |
|
1738 |
scope.$$postDigest(function () { |
|
1739 |
$modal.hide(); |
|
1740 |
}); |
|
1741 |
}; |
|
1742 |
scope.$show = function () { |
|
1743 |
scope.$$postDigest(function () { |
|
1744 |
$modal.show(); |
|
1745 |
}); |
|
1746 |
}; |
|
1747 |
scope.$toggle = function () { |
|
1748 |
scope.$$postDigest(function () { |
|
1749 |
$modal.toggle(); |
|
1750 |
}); |
|
1751 |
}; |
|
1752 |
// Support contentTemplate option |
|
1753 |
if (options.contentTemplate) { |
|
1754 |
$modal.$promise = $modal.$promise.then(function (template) { |
|
1755 |
var templateEl = angular.element(template); |
|
1756 |
return fetchTemplate(options.contentTemplate).then(function (contentTemplate) { |
|
1757 |
var contentEl = findElement('[ng-bind="content"]', templateEl[0]).removeAttr('ng-bind').html(contentTemplate); |
|
1758 |
// Drop the default footer as you probably don't want it if you use a custom contentTemplate |
|
1759 |
if (!config.template) |
|
1760 |
contentEl.next().remove(); |
|
1761 |
return templateEl[0].outerHTML; |
|
1762 |
}); |
|
1763 |
}); |
|
1764 |
} |
|
1765 |
// Fetch, compile then initialize modal |
|
1766 |
var modalLinker, modalElement; |
|
1767 |
var backdropElement = angular.element('<div class="' + options.prefixClass + '-backdrop"/>'); |
|
1768 |
$modal.$promise.then(function (template) { |
|
1769 |
if (angular.isObject(template)) |
|
1770 |
template = template.data; |
|
1771 |
if (options.html) |
|
1772 |
template = template.replace(htmlReplaceRegExp, 'ng-bind-html="'); |
|
1773 |
template = trim.apply(template); |
|
1774 |
modalLinker = $compile(template); |
|
1775 |
$modal.init(); |
|
1776 |
}); |
|
1777 |
$modal.init = function () { |
|
1778 |
// Options: show |
|
1779 |
if (options.show) { |
|
1780 |
scope.$$postDigest(function () { |
|
1781 |
$modal.show(); |
|
1782 |
}); |
|
1783 |
} |
|
1784 |
}; |
|
1785 |
$modal.destroy = function () { |
|
1786 |
// Remove element |
|
1787 |
if (modalElement) { |
|
1788 |
modalElement.remove(); |
|
1789 |
modalElement = null; |
|
1790 |
} |
|
1791 |
if (backdropElement) { |
|
1792 |
backdropElement.remove(); |
|
1793 |
backdropElement = null; |
|
1794 |
} |
|
1795 |
// Destroy scope |
|
1796 |
scope.$destroy(); |
|
1797 |
}; |
|
1798 |
$modal.show = function () { |
|
1799 |
scope.$emit(options.prefixEvent + '.show.before', $modal); |
|
1800 |
var parent = options.container ? findElement(options.container) : null; |
|
1801 |
var after = options.container ? null : options.element; |
|
1802 |
// Fetch a cloned element linked from template |
|
1803 |
modalElement = $modal.$element = modalLinker(scope, function (clonedElement, scope) { |
|
1804 |
}); |
|
1805 |
// Set the initial positioning. |
|
1806 |
modalElement.css({ display: 'block' }).addClass(options.placement); |
|
1807 |
// Options: animation |
|
1808 |
if (options.animation) { |
|
1809 |
if (options.backdrop) { |
|
1810 |
backdropElement.addClass(options.backdropAnimation); |
|
1811 |
} |
|
1812 |
modalElement.addClass(options.animation); |
|
1813 |
} |
|
1814 |
if (options.backdrop) { |
|
1815 |
$animate.enter(backdropElement, bodyElement, null, function () { |
|
1816 |
}); |
|
1817 |
} |
|
1818 |
$animate.enter(modalElement, parent, after, function () { |
|
1819 |
scope.$emit(options.prefixEvent + '.show', $modal); |
|
1820 |
}); |
|
1821 |
scope.$isShown = true; |
|
1822 |
scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest(); |
|
1823 |
// Focus once the enter-animation has started |
|
1824 |
// Weird PhantomJS bug hack |
|
1825 |
var el = modalElement[0]; |
|
1826 |
requestAnimationFrame(function () { |
|
1827 |
el.focus(); |
|
1828 |
}); |
|
1829 |
bodyElement.addClass(options.prefixClass + '-open'); |
|
1830 |
if (options.animation) { |
|
1831 |
bodyElement.addClass(options.prefixClass + '-with-' + options.animation); |
|
1832 |
} |
|
1833 |
// Bind events |
|
1834 |
if (options.backdrop) { |
|
1835 |
modalElement.on('click', hideOnBackdropClick); |
|
1836 |
backdropElement.on('click', hideOnBackdropClick); |
|
1837 |
} |
|
1838 |
if (options.keyboard) { |
|
1839 |
modalElement.on('keyup', $modal.$onKeyUp); |
|
1840 |
} |
|
1841 |
}; |
|
1842 |
$modal.hide = function () { |
|
1843 |
scope.$emit(options.prefixEvent + '.hide.before', $modal); |
|
1844 |
$animate.leave(modalElement, function () { |
|
1845 |
scope.$emit(options.prefixEvent + '.hide', $modal); |
|
1846 |
bodyElement.removeClass(options.prefixClass + '-open'); |
|
1847 |
if (options.animation) { |
|
1848 |
bodyElement.addClass(options.prefixClass + '-with-' + options.animation); |
|
1849 |
} |
|
1850 |
}); |
|
1851 |
if (options.backdrop) { |
|
1852 |
$animate.leave(backdropElement, function () { |
|
1853 |
}); |
|
1854 |
} |
|
1855 |
scope.$isShown = false; |
|
1856 |
scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest(); |
|
1857 |
// Unbind events |
|
1858 |
if (options.backdrop) { |
|
1859 |
modalElement.off('click', hideOnBackdropClick); |
|
1860 |
backdropElement.off('click', hideOnBackdropClick); |
|
1861 |
} |
|
1862 |
if (options.keyboard) { |
|
1863 |
modalElement.off('keyup', $modal.$onKeyUp); |
|
1864 |
} |
|
1865 |
}; |
|
1866 |
$modal.toggle = function () { |
|
1867 |
scope.$isShown ? $modal.hide() : $modal.show(); |
|
1868 |
}; |
|
1869 |
$modal.focus = function () { |
|
1870 |
modalElement[0].focus(); |
|
1871 |
}; |
|
1872 |
// Protected methods |
|
1873 |
$modal.$onKeyUp = function (evt) { |
|
1874 |
evt.which === 27 && $modal.hide(); |
|
1875 |
}; |
|
1876 |
// Private methods |
|
1877 |
function hideOnBackdropClick(evt) { |
|
1878 |
if (evt.target !== evt.currentTarget) |
|
1879 |
return; |
|
1880 |
options.backdrop === 'static' ? $modal.focus() : $modal.hide(); |
|
1881 |
} |
|
1882 |
return $modal; |
|
1883 |
} |
|
1884 |
// Helper functions |
|
1885 |
function findElement(query, element) { |
|
1886 |
return angular.element((element || document).querySelectorAll(query)); |
|
1887 |
} |
|
1888 |
function fetchTemplate(template) { |
|
1889 |
return $q.when($templateCache.get(template) || $http.get(template)).then(function (res) { |
|
1890 |
if (angular.isObject(res)) { |
|
1891 |
$templateCache.put(template, res.data); |
|
1892 |
return res.data; |
|
1893 |
} |
|
1894 |
return res; |
|
1895 |
}); |
|
1896 |
} |
|
1897 |
return ModalFactory; |
|
1898 |
} |
|
1899 |
]; |
|
1900 |
}).directive('bsModal', [ |
|
1901 |
'$window', |
|
1902 |
'$location', |
|
1903 |
'$sce', |
|
1904 |
'$modal', |
|
1905 |
function ($window, $location, $sce, $modal) { |
|
1906 |
return { |
|
1907 |
restrict: 'EAC', |
|
1908 |
scope: true, |
|
1909 |
link: function postLink(scope, element, attr, transclusion) { |
|
1910 |
// Directive options |
|
1911 |
var options = { |
|
1912 |
scope: scope, |
|
1913 |
element: element, |
|
1914 |
show: false |
|
1915 |
}; |
|
1916 |
angular.forEach([ |
|
1917 |
'template', |
|
1918 |
'contentTemplate', |
|
1919 |
'placement', |
|
1920 |
'backdrop', |
|
1921 |
'keyboard', |
|
1922 |
'html', |
|
1923 |
'container', |
|
1924 |
'animation' |
|
1925 |
], function (key) { |
|
1926 |
if (angular.isDefined(attr[key])) |
|
1927 |
options[key] = attr[key]; |
|
1928 |
}); |
|
1929 |
// Support scope as data-attrs |
|
1930 |
angular.forEach([ |
|
1931 |
'title', |
|
1932 |
'content' |
|
1933 |
], function (key) { |
|
1934 |
attr[key] && attr.$observe(key, function (newValue, oldValue) { |
|
1935 |
scope[key] = $sce.trustAsHtml(newValue); |
|
1936 |
}); |
|
1937 |
}); |
|
1938 |
// Support scope as an object |
|
1939 |
attr.bsModal && scope.$watch(attr.bsModal, function (newValue, oldValue) { |
|
1940 |
if (angular.isObject(newValue)) { |
|
1941 |
angular.extend(scope, newValue); |
|
1942 |
} else { |
|
1943 |
scope.content = newValue; |
|
1944 |
} |
|
1945 |
}, true); |
|
1946 |
// Initialize modal |
|
1947 |
var modal = $modal(options); |
|
1948 |
// Trigger |
|
1949 |
element.on(attr.trigger || 'click', modal.toggle); |
|
1950 |
// Garbage collection |
|
1951 |
scope.$on('$destroy', function () { |
|
1952 |
modal.destroy(); |
|
1953 |
options = null; |
|
1954 |
modal = null; |
|
1955 |
}); |
|
1956 |
} |
|
1957 |
}; |
|
1958 |
} |
|
1959 |
]); |
|
1960 |
|
|
1961 |
// Source: navbar.js |
|
1962 |
angular.module('mgcrea.ngStrap.navbar', []).provider('$navbar', function () { |
|
1963 |
var defaults = this.defaults = { |
|
1964 |
activeClass: 'active', |
|
1965 |
routeAttr: 'data-match-route', |
|
1966 |
strict: false |
|
1967 |
}; |
|
1968 |
this.$get = function () { |
|
1969 |
return { defaults: defaults }; |
|
1970 |
}; |
|
1971 |
}).directive('bsNavbar', [ |
|
1972 |
'$window', |
|
1973 |
'$location', |
|
1974 |
'$navbar', |
|
1975 |
function ($window, $location, $navbar) { |
|
1976 |
var defaults = $navbar.defaults; |
|
1977 |
return { |
|
1978 |
restrict: 'A', |
|
1979 |
link: function postLink(scope, element, attr, controller) { |
|
1980 |
// Directive options |
|
1981 |
var options = angular.copy(defaults); |
|
1982 |
angular.forEach(Object.keys(defaults), function (key) { |
|
1983 |
if (angular.isDefined(attr[key])) |
|
1984 |
options[key] = attr[key]; |
|
1985 |
}); |
|
1986 |
// Watch for the $location |
|
1987 |
scope.$watch(function () { |
|
1988 |
return $location.path(); |
|
1989 |
}, function (newValue, oldValue) { |
|
1990 |
var liElements = element[0].querySelectorAll('li[' + options.routeAttr + ']'); |
|
1991 |
angular.forEach(liElements, function (li) { |
|
1992 |
var liElement = angular.element(li); |
|
1993 |
var pattern = liElement.attr(options.routeAttr).replace('/', '\\/'); |
|
1994 |
if (options.strict) { |
|
1995 |
pattern = '^' + pattern + '$'; |
|
1996 |
} |
|
1997 |
var regexp = new RegExp(pattern, ['i']); |
|
1998 |
if (regexp.test(newValue)) { |
|
1999 |
liElement.addClass(options.activeClass); |
|
2000 |
} else { |
|
2001 |
liElement.removeClass(options.activeClass); |
|
2002 |
} |
|
2003 |
}); |
|
2004 |
}); |
|
2005 |
} |
|
2006 |
}; |
|
2007 |
} |
|
2008 |
]); |
|
2009 |
|
|
2010 |
// Source: scrollspy.js |
|
2011 |
angular.module('mgcrea.ngStrap.scrollspy', [ |
|
2012 |
'mgcrea.ngStrap.helpers.debounce', |
|
2013 |
'mgcrea.ngStrap.helpers.dimensions' |
|
2014 |
]).provider('$scrollspy', function () { |
|
2015 |
// Pool of registered spies |
|
2016 |
var spies = this.$$spies = {}; |
|
2017 |
var defaults = this.defaults = { |
|
2018 |
debounce: 150, |
|
2019 |
throttle: 100, |
|
2020 |
offset: 100 |
|
2021 |
}; |
|
2022 |
this.$get = [ |
|
2023 |
'$window', |
|
2024 |
'$document', |
|
2025 |
'$rootScope', |
|
2026 |
'dimensions', |
|
2027 |
'debounce', |
|
2028 |
'throttle', |
|
2029 |
function ($window, $document, $rootScope, dimensions, debounce, throttle) { |
|
2030 |
var windowEl = angular.element($window); |
|
2031 |
var docEl = angular.element($document.prop('documentElement')); |
|
2032 |
var bodyEl = angular.element($window.document.body); |
|
2033 |
// Helper functions |
|
2034 |
function nodeName(element, name) { |
|
2035 |
return element[0].nodeName && element[0].nodeName.toLowerCase() === name.toLowerCase(); |
|
2036 |
} |
|
2037 |
function ScrollSpyFactory(config) { |
|
2038 |
// Common vars |
|
2039 |
var options = angular.extend({}, defaults, config); |
|
2040 |
if (!options.element) |
|
2041 |
options.element = bodyEl; |
|
2042 |
var isWindowSpy = nodeName(options.element, 'body'); |
|
2043 |
var scrollEl = isWindowSpy ? windowEl : options.element; |
|
2044 |
var scrollId = isWindowSpy ? 'window' : options.id; |
|
2045 |
// Use existing spy |
|
2046 |
if (spies[scrollId]) { |
|
2047 |
spies[scrollId].$$count++; |
|
2048 |
return spies[scrollId]; |
|
2049 |
} |
|
2050 |
var $scrollspy = {}; |
|
2051 |
// Private vars |
|
2052 |
var unbindViewContentLoaded, unbindIncludeContentLoaded; |
|
2053 |
var trackedElements = $scrollspy.$trackedElements = []; |
|
2054 |
var sortedElements = []; |
|
2055 |
var activeTarget; |
|
2056 |
var debouncedCheckPosition; |
|
2057 |
var throttledCheckPosition; |
|
2058 |
var debouncedCheckOffsets; |
|
2059 |
var viewportHeight; |
|
2060 |
var scrollTop; |
|
2061 |
$scrollspy.init = function () { |
|
2062 |
// Setup internal ref counter |
|
2063 |
this.$$count = 1; |
|
2064 |
// Bind events |
|
2065 |
debouncedCheckPosition = debounce(this.checkPosition, options.debounce); |
|
2066 |
throttledCheckPosition = throttle(this.checkPosition, options.throttle); |
|
2067 |
scrollEl.on('click', this.checkPositionWithEventLoop); |
|
2068 |
windowEl.on('resize', debouncedCheckPosition); |
|
2069 |
scrollEl.on('scroll', throttledCheckPosition); |
|
2070 |
debouncedCheckOffsets = debounce(this.checkOffsets, options.debounce); |
|
2071 |
unbindViewContentLoaded = $rootScope.$on('$viewContentLoaded', debouncedCheckOffsets); |
|
2072 |
unbindIncludeContentLoaded = $rootScope.$on('$includeContentLoaded', debouncedCheckOffsets); |
|
2073 |
debouncedCheckOffsets(); |
|
2074 |
// Register spy for reuse |
|
2075 |
if (scrollId) { |
|
2076 |
spies[scrollId] = $scrollspy; |
|
2077 |
} |
|
2078 |
}; |
|
2079 |
$scrollspy.destroy = function () { |
|
2080 |
// Check internal ref counter |
|
2081 |
this.$$count--; |
|
2082 |
if (this.$$count > 0) { |
|
2083 |
return; |
|
2084 |
} |
|
2085 |
// Unbind events |
|
2086 |
scrollEl.off('click', this.checkPositionWithEventLoop); |
|
2087 |
windowEl.off('resize', debouncedCheckPosition); |
|
2088 |
scrollEl.off('scroll', debouncedCheckPosition); |
|
2089 |
unbindViewContentLoaded(); |
|
2090 |
unbindIncludeContentLoaded(); |
|
2091 |
if (scrollId) { |
|
2092 |
delete spies[scrollId]; |
|
2093 |
} |
|
2094 |
}; |
|
2095 |
$scrollspy.checkPosition = function () { |
|
2096 |
// Not ready yet |
|
2097 |
if (!sortedElements.length) |
|
2098 |
return; |
|
2099 |
// Calculate the scroll position |
|
2100 |
scrollTop = (isWindowSpy ? $window.pageYOffset : scrollEl.prop('scrollTop')) || 0; |
|
2101 |
// Calculate the viewport height for use by the components |
|
2102 |
viewportHeight = Math.max($window.innerHeight, docEl.prop('clientHeight')); |
|
2103 |
// Activate first element if scroll is smaller |
|
2104 |
if (scrollTop < sortedElements[0].offsetTop && activeTarget !== sortedElements[0].target) { |
|
2105 |
return $scrollspy.$activateElement(sortedElements[0]); |
|
2106 |
} |
|
2107 |
// Activate proper element |
|
2108 |
for (var i = sortedElements.length; i--;) { |
|
2109 |
if (angular.isUndefined(sortedElements[i].offsetTop) || sortedElements[i].offsetTop === null) |
|
2110 |
continue; |
|
2111 |
if (activeTarget === sortedElements[i].target) |
|
2112 |
continue; |
|
2113 |
if (scrollTop < sortedElements[i].offsetTop) |
|
2114 |
continue; |
|
2115 |
if (sortedElements[i + 1] && scrollTop > sortedElements[i + 1].offsetTop) |
|
2116 |
continue; |
|
2117 |
return $scrollspy.$activateElement(sortedElements[i]); |
|
2118 |
} |
|
2119 |
}; |
|
2120 |
$scrollspy.checkPositionWithEventLoop = function () { |
|
2121 |
setTimeout(this.checkPosition, 1); |
|
2122 |
}; |
|
2123 |
// Protected methods |
|
2124 |
$scrollspy.$activateElement = function (element) { |
|
2125 |
if (activeTarget) { |
|
2126 |
var activeElement = $scrollspy.$getTrackedElement(activeTarget); |
|
2127 |
if (activeElement) { |
|
2128 |
activeElement.source.removeClass('active'); |
|
2129 |
if (nodeName(activeElement.source, 'li') && nodeName(activeElement.source.parent().parent(), 'li')) { |
|
2130 |
activeElement.source.parent().parent().removeClass('active'); |
|
2131 |
} |
|
2132 |
} |
|
2133 |
} |
|
2134 |
activeTarget = element.target; |
|
2135 |
element.source.addClass('active'); |
|
2136 |
if (nodeName(element.source, 'li') && nodeName(element.source.parent().parent(), 'li')) { |
|
2137 |
element.source.parent().parent().addClass('active'); |
|
2138 |
} |
|
2139 |
}; |
|
2140 |
$scrollspy.$getTrackedElement = function (target) { |
|
2141 |
return trackedElements.filter(function (obj) { |
|
2142 |
return obj.target === target; |
|
2143 |
})[0]; |
|
2144 |
}; |
|
2145 |
// Track offsets behavior |
|
2146 |
$scrollspy.checkOffsets = function () { |
|
2147 |
angular.forEach(trackedElements, function (trackedElement) { |
|
2148 |
var targetElement = document.querySelector(trackedElement.target); |
|
2149 |
trackedElement.offsetTop = targetElement ? dimensions.offset(targetElement).top : null; |
|
2150 |
if (options.offset && trackedElement.offsetTop !== null) |
|
2151 |
trackedElement.offsetTop -= options.offset * 1; |
|
2152 |
}); |
|
2153 |
sortedElements = trackedElements.filter(function (el) { |
|
2154 |
return el.offsetTop !== null; |
|
2155 |
}).sort(function (a, b) { |
|
2156 |
return a.offsetTop - b.offsetTop; |
|
2157 |
}); |
|
2158 |
debouncedCheckPosition(); |
|
2159 |
}; |
|
2160 |
$scrollspy.trackElement = function (target, source) { |
|
2161 |
trackedElements.push({ |
|
2162 |
target: target, |
|
2163 |
source: source |
|
2164 |
}); |
|
2165 |
}; |
|
2166 |
$scrollspy.untrackElement = function (target, source) { |
|
2167 |
var toDelete; |
|
2168 |
for (var i = trackedElements.length; i--;) { |
|
2169 |
if (trackedElements[i].target === target && trackedElements[i].source === source) { |
|
2170 |
toDelete = i; |
|
2171 |
break; |
|
2172 |
} |
|
2173 |
} |
|
2174 |
trackedElements = trackedElements.splice(toDelete, 1); |
|
2175 |
}; |
|
2176 |
$scrollspy.activate = function (i) { |
|
2177 |
trackedElements[i].addClass('active'); |
|
2178 |
}; |
|
2179 |
// Initialize plugin |
|
2180 |
$scrollspy.init(); |
|
2181 |
return $scrollspy; |
|
2182 |
} |
|
2183 |
return ScrollSpyFactory; |
|
2184 |
} |
|
2185 |
]; |
|
2186 |
}).directive('bsScrollspy', [ |
|
2187 |
'$rootScope', |
|
2188 |
'debounce', |
|
2189 |
'dimensions', |
|
2190 |
'$scrollspy', |
|
2191 |
function ($rootScope, debounce, dimensions, $scrollspy) { |
|
2192 |
return { |
|
2193 |
restrict: 'EAC', |
|
2194 |
link: function postLink(scope, element, attr) { |
|
2195 |
var options = { scope: scope }; |
|
2196 |
angular.forEach([ |
|
2197 |
'offset', |
|
2198 |
'target' |
|
2199 |
], function (key) { |
|
2200 |
if (angular.isDefined(attr[key])) |
|
2201 |
options[key] = attr[key]; |
|
2202 |
}); |
|
2203 |
var scrollspy = $scrollspy(options); |
|
2204 |
scrollspy.trackElement(options.target, element); |
|
2205 |
scope.$on('$destroy', function () { |
|
2206 |
scrollspy.untrackElement(options.target, element); |
|
2207 |
scrollspy.destroy(); |
|
2208 |
options = null; |
|
2209 |
scrollspy = null; |
|
2210 |
}); |
|
2211 |
} |
|
2212 |
}; |
|
2213 |
} |
|
2214 |
]).directive('bsScrollspyList', [ |
|
2215 |
'$rootScope', |
|
2216 |
'debounce', |
|
2217 |
'dimensions', |
|
2218 |
'$scrollspy', |
|
2219 |
function ($rootScope, debounce, dimensions, $scrollspy) { |
|
2220 |
return { |
|
2221 |
restrict: 'A', |
|
2222 |
compile: function postLink(element, attr) { |
|
2223 |
var children = element[0].querySelectorAll('li > a[href]'); |
|
2224 |
angular.forEach(children, function (child) { |
|
2225 |
var childEl = angular.element(child); |
|
2226 |
childEl.parent().attr('bs-scrollspy', '').attr('data-target', childEl.attr('href')); |
|
2227 |
}); |
|
2228 |
} |
|
2229 |
}; |
|
2230 |
} |
|
2231 |
]); |
|
2232 |
|
|
2233 |
// Source: popover.js |
|
2234 |
angular.module('mgcrea.ngStrap.popover', ['mgcrea.ngStrap.tooltip']).provider('$popover', function () { |
|
2235 |
var defaults = this.defaults = { |
|
2236 |
animation: 'am-fade', |
|
2237 |
container: false, |
|
2238 |
target: false, |
|
2239 |
placement: 'right', |
|
2240 |
template: 'popover/popover.tpl.html', |
|
2241 |
contentTemplate: false, |
|
2242 |
trigger: 'click', |
|
2243 |
keyboard: true, |
|
2244 |
html: false, |
|
2245 |
title: '', |
|
2246 |
content: '', |
|
2247 |
delay: 0 |
|
2248 |
}; |
|
2249 |
this.$get = [ |
|
2250 |
'$tooltip', |
|
2251 |
function ($tooltip) { |
|
2252 |
function PopoverFactory(element, config) { |
|
2253 |
// Common vars |
|
2254 |
var options = angular.extend({}, defaults, config); |
|
2255 |
var $popover = $tooltip(element, options); |
|
2256 |
// Support scope as string options [/*title, */content] |
|
2257 |
if (options.content) { |
|
2258 |
$popover.$scope.content = options.content; |
|
2259 |
} |
|
2260 |
return $popover; |
|
2261 |
} |
|
2262 |
return PopoverFactory; |
|
2263 |
} |
|
2264 |
]; |
|
2265 |
}).directive('bsPopover', [ |
|
2266 |
'$window', |
|
2267 |
'$location', |
|
2268 |
'$sce', |
|
2269 |
'$popover', |
|
2270 |
function ($window, $location, $sce, $popover) { |
|
2271 |
var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout; |
|
2272 |
return { |
|
2273 |
restrict: 'EAC', |
|
2274 |
scope: true, |
|
2275 |
link: function postLink(scope, element, attr) { |
|
2276 |
// Directive options |
|
2277 |
var options = { scope: scope }; |
|
2278 |
angular.forEach([ |
|
2279 |
'template', |
|
2280 |
'contentTemplate', |
|
2281 |
'placement', |
|
2282 |
'container', |
|
2283 |
'target', |
|
2284 |
'delay', |
|
2285 |
'trigger', |
|
2286 |
'keyboard', |
|
2287 |
'html', |
|
2288 |
'animation' |
|
2289 |
], function (key) { |
|
2290 |
if (angular.isDefined(attr[key])) |
|
2291 |
options[key] = attr[key]; |
|
2292 |
}); |
|
2293 |
// Support scope as data-attrs |
|
2294 |
angular.forEach([ |
|
2295 |
'title', |
|
2296 |
'content' |
|
2297 |
], function (key) { |
|
2298 |
attr[key] && attr.$observe(key, function (newValue, oldValue) { |
|
2299 |
scope[key] = $sce.trustAsHtml(newValue); |
|
2300 |
angular.isDefined(oldValue) && requestAnimationFrame(function () { |
|
2301 |
popover && popover.$applyPlacement(); |
|
2302 |
}); |
|
2303 |
}); |
|
2304 |
}); |
|
2305 |
// Support scope as an object |
|
2306 |
attr.bsPopover && scope.$watch(attr.bsPopover, function (newValue, oldValue) { |
|
2307 |
if (angular.isObject(newValue)) { |
|
2308 |
angular.extend(scope, newValue); |
|
2309 |
} else { |
|
2310 |
scope.content = newValue; |
|
2311 |
} |
|
2312 |
angular.isDefined(oldValue) && requestAnimationFrame(function () { |
|
2313 |
popover && popover.$applyPlacement(); |
|
2314 |
}); |
|
2315 |
}, true); |
|
2316 |
// Initialize popover |
|
2317 |
var popover = $popover(element, options); |
|
2318 |
// Garbage collection |
|
2319 |
scope.$on('$destroy', function () { |
|
2320 |
popover.destroy(); |
|
2321 |
options = null; |
|
2322 |
popover = null; |
|
2323 |
}); |
|
2324 |
} |
|
2325 |
}; |
|
2326 |
} |
|
2327 |
]); |
|
2328 |
|
|
2329 |
// Source: select.js |
|
2330 |
angular.module('mgcrea.ngStrap.select', [ |
|
2331 |
'mgcrea.ngStrap.tooltip', |
|
2332 |
'mgcrea.ngStrap.helpers.parseOptions' |
|
2333 |
]).provider('$select', function () { |
|
2334 |
var defaults = this.defaults = { |
|
2335 |
animation: 'am-fade', |
|
2336 |
prefixClass: 'select', |
|
2337 |
placement: 'bottom-left', |
|
2338 |
template: 'select/select.tpl.html', |
|
2339 |
trigger: 'focus', |
|
2340 |
container: false, |
|
2341 |
keyboard: true, |
|
2342 |
html: false, |
|
2343 |
delay: 0, |
|
2344 |
multiple: false, |
|
2345 |
sort: true, |
|
2346 |
caretHtml: ' <span class="caret"></span>', |
|
2347 |
placeholder: 'Choose among the following...', |
|
2348 |
maxLength: 3, |
|
2349 |
maxLengthHtml: 'selected', |
|
2350 |
iconCheckmark: 'glyphicon glyphicon-ok' |
|
2351 |
}; |
|
2352 |
this.$get = [ |
|
2353 |
'$window', |
|
2354 |
'$document', |
|
2355 |
'$rootScope', |
|
2356 |
'$tooltip', |
|
2357 |
function ($window, $document, $rootScope, $tooltip) { |
|
2358 |
var bodyEl = angular.element($window.document.body); |
|
2359 |
var isTouch = 'createTouch' in $window.document; |
|
2360 |
function SelectFactory(element, controller, config) { |
|
2361 |
var $select = {}; |
|
2362 |
// Common vars |
|
2363 |
var options = angular.extend({}, defaults, config); |
|
2364 |
$select = $tooltip(element, options); |
|
2365 |
var scope = $select.$scope; |
|
2366 |
scope.$matches = []; |
|
2367 |
scope.$activeIndex = 0; |
|
2368 |
scope.$isMultiple = options.multiple; |
|
2369 |
scope.$iconCheckmark = options.iconCheckmark; |
|
2370 |
scope.$activate = function (index) { |
|
2371 |
scope.$$postDigest(function () { |
|
2372 |
$select.activate(index); |
|
2373 |
}); |
|
2374 |
}; |
|
2375 |
scope.$select = function (index, evt) { |
|
2376 |
scope.$$postDigest(function () { |
|
2377 |
$select.select(index); |
|
2378 |
}); |
|
2379 |
}; |
|
2380 |
scope.$isVisible = function () { |
|
2381 |
return $select.$isVisible(); |
|
2382 |
}; |
|
2383 |
scope.$isActive = function (index) { |
|
2384 |
return $select.$isActive(index); |
|
2385 |
}; |
|
2386 |
// Public methods |
|
2387 |
$select.update = function (matches) { |
|
2388 |
scope.$matches = matches; |
|
2389 |
$select.$updateActiveIndex(); |
|
2390 |
}; |
|
2391 |
$select.activate = function (index) { |
|
2392 |
if (options.multiple) { |
|
2393 |
scope.$activeIndex.sort(); |
|
2394 |
$select.$isActive(index) ? scope.$activeIndex.splice(scope.$activeIndex.indexOf(index), 1) : scope.$activeIndex.push(index); |
|
2395 |
if (options.sort) |
|
2396 |
scope.$activeIndex.sort(); |
|
2397 |
} else { |
|
2398 |
scope.$activeIndex = index; |
|
2399 |
} |
|
2400 |
return scope.$activeIndex; |
|
2401 |
}; |
|
2402 |
$select.select = function (index) { |
|
2403 |
var value = scope.$matches[index].value; |
|
2404 |
scope.$apply(function () { |
|
2405 |
$select.activate(index); |
|
2406 |
if (options.multiple) { |
|
2407 |
controller.$setViewValue(scope.$activeIndex.map(function (index) { |
|
2408 |
return scope.$matches[index].value; |
|
2409 |
})); |
|
2410 |
} else { |
|
2411 |
controller.$setViewValue(value); |
|
2412 |
// Hide if single select |
|
2413 |
$select.hide(); |
|
2414 |
} |
|
2415 |
}); |
|
2416 |
// Emit event |
|
2417 |
scope.$emit('$select.select', value, index); |
|
2418 |
}; |
|
2419 |
// Protected methods |
|
2420 |
$select.$updateActiveIndex = function () { |
|
2421 |
if (controller.$modelValue && scope.$matches.length) { |
|
2422 |
if (options.multiple && angular.isArray(controller.$modelValue)) { |
|
2423 |
scope.$activeIndex = controller.$modelValue.map(function (value) { |
|
2424 |
return $select.$getIndex(value); |
|
2425 |
}); |
|
2426 |
} else { |
|
2427 |
scope.$activeIndex = $select.$getIndex(controller.$modelValue); |
|
2428 |
} |
|
2429 |
} else if (scope.$activeIndex >= scope.$matches.length) { |
|
2430 |
scope.$activeIndex = options.multiple ? [] : 0; |
|
2431 |
} |
|
2432 |
}; |
|
2433 |
$select.$isVisible = function () { |
|
2434 |
if (!options.minLength || !controller) { |
|
2435 |
return scope.$matches.length; |
|
2436 |
} |
|
2437 |
// minLength support |
|
2438 |
return scope.$matches.length && controller.$viewValue.length >= options.minLength; |
|
2439 |
}; |
|
2440 |
$select.$isActive = function (index) { |
|
2441 |
if (options.multiple) { |
|
2442 |
return scope.$activeIndex.indexOf(index) !== -1; |
|
2443 |
} else { |
|
2444 |
return scope.$activeIndex === index; |
|
2445 |
} |
|
2446 |
}; |
|
2447 |
$select.$getIndex = function (value) { |
|
2448 |
var l = scope.$matches.length, i = l; |
|
2449 |
if (!l) |
|
2450 |
return; |
|
2451 |
for (i = l; i--;) { |
|
2452 |
if (scope.$matches[i].value === value) |
|
2453 |
break; |
|
2454 |
} |
|
2455 |
if (i < 0) |
|
2456 |
return; |
|
2457 |
return i; |
|
2458 |
}; |
|
2459 |
$select.$onMouseDown = function (evt) { |
|
2460 |
// Prevent blur on mousedown on .dropdown-menu |
|
2461 |
evt.preventDefault(); |
|
2462 |
evt.stopPropagation(); |
|
2463 |
// Emulate click for mobile devices |
|
2464 |
if (isTouch) { |
|
2465 |
var targetEl = angular.element(evt.target); |
|
2466 |
targetEl.triggerHandler('click'); |
|
2467 |
} |
|
2468 |
}; |
|
2469 |
$select.$onKeyDown = function (evt) { |
|
2470 |
if (!/(9|13|38|40)/.test(evt.keyCode)) |
|
2471 |
return; |
|
2472 |
evt.preventDefault(); |
|
2473 |
evt.stopPropagation(); |
|
2474 |
// Select with enter |
|
2475 |
if (!options.multiple && (evt.keyCode === 13 || evt.keyCode === 9)) { |
|
2476 |
return $select.select(scope.$activeIndex); |
|
2477 |
} |
|
2478 |
// Navigate with keyboard |
|
2479 |
if (evt.keyCode === 38 && scope.$activeIndex > 0) |
|
2480 |
scope.$activeIndex--; |
|
2481 |
else if (evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1) |
|
2482 |
scope.$activeIndex++; |
|
2483 |
else if (angular.isUndefined(scope.$activeIndex)) |
|
2484 |
scope.$activeIndex = 0; |
|
2485 |
scope.$digest(); |
|
2486 |
}; |
|
2487 |
// Overrides |
|
2488 |
var _show = $select.show; |
|
2489 |
$select.show = function () { |
|
2490 |
_show(); |
|
2491 |
if (options.multiple) { |
|
2492 |
$select.$element.addClass('select-multiple'); |
|
2493 |
} |
|
2494 |
setTimeout(function () { |
|
2495 |
$select.$element.on(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown); |
|
2496 |
if (options.keyboard) { |
|
2497 |
element.on('keydown', $select.$onKeyDown); |
|
2498 |
} |
|
2499 |
}); |
|
2500 |
}; |
|
2501 |
var _hide = $select.hide; |
|
2502 |
$select.hide = function () { |
|
2503 |
$select.$element.off(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown); |
|
2504 |
if (options.keyboard) { |
|
2505 |
element.off('keydown', $select.$onKeyDown); |
|
2506 |
} |
|
2507 |
_hide(true); |
|
2508 |
}; |
|
2509 |
return $select; |
|
2510 |
} |
|
2511 |
SelectFactory.defaults = defaults; |
|
2512 |
return SelectFactory; |
|
2513 |
} |
|
2514 |
]; |
|
2515 |
}).directive('bsSelect', [ |
|
2516 |
'$window', |
|
2517 |
'$parse', |
|
2518 |
'$q', |
|
2519 |
'$select', |
|
2520 |
'$parseOptions', |
|
2521 |
function ($window, $parse, $q, $select, $parseOptions) { |
|
2522 |
var defaults = $select.defaults; |
|
2523 |
return { |
|
2524 |
restrict: 'EAC', |
|
2525 |
require: 'ngModel', |
|
2526 |
link: function postLink(scope, element, attr, controller) { |
|
2527 |
// Directive options |
|
2528 |
var options = { scope: scope }; |
|
2529 |
angular.forEach([ |
|
2530 |
'placement', |
|
2531 |
'container', |
|
2532 |
'delay', |
|
2533 |
'trigger', |
|
2534 |
'keyboard', |
|
2535 |
'html', |
|
2536 |
'animation', |
|
2537 |
'template', |
|
2538 |
'placeholder', |
|
2539 |
'multiple', |
|
2540 |
'maxLength', |
|
2541 |
'maxLengthHtml' |
|
2542 |
], function (key) { |
|
2543 |
if (angular.isDefined(attr[key])) |
|
2544 |
options[key] = attr[key]; |
|
2545 |
}); |
|
2546 |
// Add support for select markup |
|
2547 |
if (element[0].nodeName.toLowerCase() === 'select') { |
|
2548 |
var inputEl = element; |
|
2549 |
inputEl.css('display', 'none'); |
|
2550 |
element = angular.element('<button type="button" class="btn btn-default"></button>'); |
|
2551 |
inputEl.after(element); |
|
2552 |
} |
|
2553 |
// Build proper ngOptions |
|
2554 |
var parsedOptions = $parseOptions(attr.ngOptions); |
|
2555 |
// Initialize select |
|
2556 |
var select = $select(element, controller, options); |
|
2557 |
// Watch ngOptions values before filtering for changes |
|
2558 |
var watchedOptions = parsedOptions.$match[7].replace(/\|.+/, '').trim(); |
|
2559 |
scope.$watch(watchedOptions, function (newValue, oldValue) { |
|
2560 |
// console.warn('scope.$watch(%s)', watchedOptions, newValue, oldValue); |
|
2561 |
parsedOptions.valuesFn(scope, controller).then(function (values) { |
|
2562 |
select.update(values); |
|
2563 |
controller.$render(); |
|
2564 |
}); |
|
2565 |
}, true); |
|
2566 |
// Watch model for changes |
|
2567 |
scope.$watch(attr.ngModel, function (newValue, oldValue) { |
|
2568 |
// console.warn('scope.$watch(%s)', attr.ngModel, newValue, oldValue); |
|
2569 |
select.$updateActiveIndex(); |
|
2570 |
controller.$render(); |
|
2571 |
}, true); |
|
2572 |
// Model rendering in view |
|
2573 |
controller.$render = function () { |
|
2574 |
// console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue); |
|
2575 |
var selected, index; |
|
2576 |
if (options.multiple && angular.isArray(controller.$modelValue)) { |
|
2577 |
selected = controller.$modelValue.map(function (value) { |
|
2578 |
index = select.$getIndex(value); |
|
2579 |
return angular.isDefined(index) ? select.$scope.$matches[index].label : false; |
|
2580 |
}).filter(angular.isDefined); |
|
2581 |
if (selected.length > (options.maxLength || defaults.maxLength)) { |
|
2582 |
selected = selected.length + ' ' + (options.maxLengthHtml || defaults.maxLengthHtml); |
|
2583 |
} else { |
|
2584 |
selected = selected.join(', '); |
|
2585 |
} |
|
2586 |
} else { |
|
2587 |
index = select.$getIndex(controller.$modelValue); |
|
2588 |
selected = angular.isDefined(index) ? select.$scope.$matches[index].label : false; |
|
2589 |
} |
|
2590 |
element.html((selected ? selected : attr.placeholder || defaults.placeholder) + defaults.caretHtml); |
|
2591 |
}; |
|
2592 |
// Garbage collection |
|
2593 |
scope.$on('$destroy', function () { |
|
2594 |
select.destroy(); |
|
2595 |
options = null; |
|
2596 |
select = null; |
|
2597 |
}); |
|
2598 |
} |
|
2599 |
}; |
|
2600 |
} |
|
2601 |
]); |
|
2602 |
|
|
2603 |
// Source: tab.js |
|
2604 |
angular.module('mgcrea.ngStrap.tab', []).run([ |
|
2605 |
'$templateCache', |
|
2606 |
function ($templateCache) { |
|
2607 |
$templateCache.put('$pane', '{{pane.content}}'); |
|
2608 |
} |
|
2609 |
]).provider('$tab', function () { |
|
2610 |
var defaults = this.defaults = { |
|
2611 |
animation: 'am-fade', |
|
2612 |
template: 'tab/tab.tpl.html' |
|
2613 |
}; |
|
2614 |
this.$get = function () { |
|
2615 |
return { defaults: defaults }; |
|
2616 |
}; |
|
2617 |
}).directive('bsTabs', [ |
|
2618 |
'$window', |
|
2619 |
'$animate', |
|
2620 |
'$tab', |
|
2621 |
function ($window, $animate, $tab) { |
|
2622 |
var defaults = $tab.defaults; |
|
2623 |
return { |
|
2624 |
restrict: 'EAC', |
|
2625 |
scope: true, |
|
2626 |
require: '?ngModel', |
|
2627 |
templateUrl: function (element, attr) { |
|
2628 |
return attr.template || defaults.template; |
|
2629 |
}, |
|
2630 |
link: function postLink(scope, element, attr, controller) { |
|
2631 |
// Directive options |
|
2632 |
var options = defaults; |
|
2633 |
angular.forEach(['animation'], function (key) { |
|
2634 |
if (angular.isDefined(attr[key])) |
|
2635 |
options[key] = attr[key]; |
|
2636 |
}); |
|
2637 |
// Require scope as an object |
|
2638 |
attr.bsTabs && scope.$watch(attr.bsTabs, function (newValue, oldValue) { |
|
2639 |
scope.panes = newValue; |
|
2640 |
}, true); |
|
2641 |
// Add base class |
|
2642 |
element.addClass('tabs'); |
|
2643 |
// Support animations |
|
2644 |
if (options.animation) { |
|
2645 |
element.addClass(options.animation); |
|
2646 |
} |
|
2647 |
scope.active = scope.activePane = 0; |
|
2648 |
// view -> model |
|
2649 |
scope.setActive = function (index, ev) { |
|
2650 |
scope.active = index; |
|
2651 |
if (controller) { |
|
2652 |
controller.$setViewValue(index); |
|
2653 |
} |
|
2654 |
}; |
|
2655 |
// model -> view |
|
2656 |
if (controller) { |
|
2657 |
controller.$render = function () { |
|
2658 |
scope.active = controller.$modelValue * 1; |
|
2659 |
}; |
|
2660 |
} |
|
2661 |
} |
|
2662 |
}; |
|
2663 |
} |
|
2664 |
]); |
|
2665 |
|
|
2666 |
// Source: timepicker.js |
|
2667 |
angular.module('mgcrea.ngStrap.timepicker', [ |
|
2668 |
'mgcrea.ngStrap.helpers.dateParser', |
|
2669 |
'mgcrea.ngStrap.tooltip' |
|
2670 |
]).provider('$timepicker', function () { |
|
2671 |
var defaults = this.defaults = { |
|
2672 |
animation: 'am-fade', |
|
2673 |
prefixClass: 'timepicker', |
|
2674 |
placement: 'bottom-left', |
|
2675 |
template: 'timepicker/timepicker.tpl.html', |
|
2676 |
trigger: 'focus', |
|
2677 |
container: false, |
|
2678 |
keyboard: true, |
|
2679 |
html: false, |
|
2680 |
delay: 0, |
|
2681 |
useNative: true, |
|
2682 |
timeType: 'date', |
|
2683 |
timeFormat: 'shortTime', |
|
2684 |
modelTimeFormat: null, |
|
2685 |
autoclose: false, |
|
2686 |
minTime: -Infinity, |
|
2687 |
maxTime: +Infinity, |
|
2688 |
length: 5, |
|
2689 |
hourStep: 1, |
|
2690 |
minuteStep: 5, |
|
2691 |
iconUp: 'glyphicon glyphicon-chevron-up', |
|
2692 |
iconDown: 'glyphicon glyphicon-chevron-down' |
|
2693 |
}; |
|
2694 |
this.$get = [ |
|
2695 |
'$window', |
|
2696 |
'$document', |
|
2697 |
'$rootScope', |
|
2698 |
'$sce', |
|
2699 |
'$locale', |
|
2700 |
'dateFilter', |
|
2701 |
'$tooltip', |
|
2702 |
function ($window, $document, $rootScope, $sce, $locale, dateFilter, $tooltip) { |
|
2703 |
var bodyEl = angular.element($window.document.body); |
|
2704 |
var isTouch = 'createTouch' in $window.document; |
|
2705 |
var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent); |
|
2706 |
if (!defaults.lang) |
|
2707 |
defaults.lang = $locale.id; |
|
2708 |
function timepickerFactory(element, controller, config) { |
|
2709 |
var $timepicker = $tooltip(element, angular.extend({}, defaults, config)); |
|
2710 |
var parentScope = config.scope; |
|
2711 |
var options = $timepicker.$options; |
|
2712 |
var scope = $timepicker.$scope; |
|
2713 |
// View vars |
|
2714 |
var selectedIndex = 0; |
|
2715 |
var startDate = controller.$dateValue || new Date(); |
|
2716 |
var viewDate = { |
|
2717 |
hour: startDate.getHours(), |
|
2718 |
meridian: startDate.getHours() < 12, |
|
2719 |
minute: startDate.getMinutes(), |
|
2720 |
second: startDate.getSeconds(), |
|
2721 |
millisecond: startDate.getMilliseconds() |
|
2722 |
}; |
|
2723 |
var format = $locale.DATETIME_FORMATS[options.timeFormat] || options.timeFormat; |
|
2724 |
var formats = /(h+)([:\.])?(m+)[ ]?(a?)/i.exec(format).slice(1); |
|
2725 |
scope.$iconUp = options.iconUp; |
|
2726 |
scope.$iconDown = options.iconDown; |
|
2727 |
// Scope methods |
|
2728 |
scope.$select = function (date, index) { |
|
2729 |
$timepicker.select(date, index); |
|
2730 |
}; |
|
2731 |
scope.$moveIndex = function (value, index) { |
|
2732 |
$timepicker.$moveIndex(value, index); |
|
2733 |
}; |
|
2734 |
scope.$switchMeridian = function (date) { |
|
2735 |
$timepicker.switchMeridian(date); |
|
2736 |
}; |
|
2737 |
// Public methods |
|
2738 |
$timepicker.update = function (date) { |
|
2739 |
// console.warn('$timepicker.update() newValue=%o', date); |
|
2740 |
if (angular.isDate(date) && !isNaN(date.getTime())) { |
|
2741 |
$timepicker.$date = date; |
|
2742 |
angular.extend(viewDate, { |
|
2743 |
hour: date.getHours(), |
|
2744 |
minute: date.getMinutes(), |
|
2745 |
second: date.getSeconds(), |
|
2746 |
millisecond: date.getMilliseconds() |
|
2747 |
}); |
|
2748 |
$timepicker.$build(); |
|
2749 |
} else if (!$timepicker.$isBuilt) { |
|
2750 |
$timepicker.$build(); |
|
2751 |
} |
|
2752 |
}; |
|
2753 |
$timepicker.select = function (date, index, keep) { |
|
2754 |
// console.warn('$timepicker.select', date, scope.$mode); |
|
2755 |
if (!controller.$dateValue || isNaN(controller.$dateValue.getTime())) |
|
2756 |
controller.$dateValue = new Date(1970, 0, 1); |
|
2757 |
if (!angular.isDate(date)) |
|
2758 |
date = new Date(date); |
|
2759 |
if (index === 0) |
|
2760 |
controller.$dateValue.setHours(date.getHours()); |
|
2761 |
else if (index === 1) |
|
2762 |
controller.$dateValue.setMinutes(date.getMinutes()); |
|
2763 |
controller.$setViewValue(controller.$dateValue); |
|
2764 |
controller.$render(); |
|
2765 |
if (options.autoclose && !keep) { |
|
2766 |
$timepicker.hide(true); |
|
2767 |
} |
|
2768 |
}; |
|
2769 |
$timepicker.switchMeridian = function (date) { |
|
2770 |
var hours = (date || controller.$dateValue).getHours(); |
|
2771 |
controller.$dateValue.setHours(hours < 12 ? hours + 12 : hours - 12); |
|
2772 |
controller.$setViewValue(controller.$dateValue); |
|
2773 |
controller.$render(); |
|
2774 |
}; |
|
2775 |
// Protected methods |
|
2776 |
$timepicker.$build = function () { |
|
2777 |
// console.warn('$timepicker.$build() viewDate=%o', viewDate); |
|
2778 |
var i, midIndex = scope.midIndex = parseInt(options.length / 2, 10); |
|
2779 |
var hours = [], hour; |
|
2780 |
for (i = 0; i < options.length; i++) { |
|
2781 |
hour = new Date(1970, 0, 1, viewDate.hour - (midIndex - i) * options.hourStep); |
|
2782 |
hours.push({ |
|
2783 |
date: hour, |
|
2784 |
label: dateFilter(hour, formats[0]), |
|
2785 |
selected: $timepicker.$date && $timepicker.$isSelected(hour, 0), |
|
2786 |
disabled: $timepicker.$isDisabled(hour, 0) |
|
2787 |
}); |
|
2788 |
} |
|
2789 |
var minutes = [], minute; |
|
2790 |
for (i = 0; i < options.length; i++) { |
|
2791 |
minute = new Date(1970, 0, 1, 0, viewDate.minute - (midIndex - i) * options.minuteStep); |
|
2792 |
minutes.push({ |
|
2793 |
date: minute, |
|
2794 |
label: dateFilter(minute, formats[2]), |
|
2795 |
selected: $timepicker.$date && $timepicker.$isSelected(minute, 1), |
|
2796 |
disabled: $timepicker.$isDisabled(minute, 1) |
|
2797 |
}); |
|
2798 |
} |
|
2799 |
var rows = []; |
|
2800 |
for (i = 0; i < options.length; i++) { |
|
2801 |
rows.push([ |
|
2802 |
hours[i], |
|
2803 |
minutes[i] |
|
2804 |
]); |
|
2805 |
} |
|
2806 |
scope.rows = rows; |
|
2807 |
scope.showAM = !!formats[3]; |
|
2808 |
scope.isAM = ($timepicker.$date || hours[midIndex].date).getHours() < 12; |
|
2809 |
scope.timeSeparator = formats[1]; |
|
2810 |
$timepicker.$isBuilt = true; |
|
2811 |
}; |
|
2812 |
$timepicker.$isSelected = function (date, index) { |
|
2813 |
if (!$timepicker.$date) |
|
2814 |
return false; |
|
2815 |
else if (index === 0) { |
|
2816 |
return date.getHours() === $timepicker.$date.getHours(); |
|
2817 |
} else if (index === 1) { |
|
2818 |
return date.getMinutes() === $timepicker.$date.getMinutes(); |
|
2819 |
} |
|
2820 |
}; |
|
2821 |
$timepicker.$isDisabled = function (date, index) { |
|
2822 |
var selectedTime; |
|
2823 |
if (index === 0) { |
|
2824 |
selectedTime = date.getTime() + viewDate.minute * 60000; |
|
2825 |
} else if (index === 1) { |
|
2826 |
selectedTime = date.getTime() + viewDate.hour * 3600000; |
|
2827 |
} |
|
2828 |
return selectedTime < options.minTime * 1 || selectedTime > options.maxTime * 1; |
|
2829 |
}; |
|
2830 |
$timepicker.$moveIndex = function (value, index) { |
|
2831 |
var targetDate; |
|
2832 |
if (index === 0) { |
|
2833 |
targetDate = new Date(1970, 0, 1, viewDate.hour + value * options.length, viewDate.minute); |
|
2834 |
angular.extend(viewDate, { hour: targetDate.getHours() }); |
|
2835 |
} else if (index === 1) { |
|
2836 |
targetDate = new Date(1970, 0, 1, viewDate.hour, viewDate.minute + value * options.length * options.minuteStep); |
|
2837 |
angular.extend(viewDate, { minute: targetDate.getMinutes() }); |
|
2838 |
} |
|
2839 |
$timepicker.$build(); |
|
2840 |
}; |
|
2841 |
$timepicker.$onMouseDown = function (evt) { |
|
2842 |
// Prevent blur on mousedown on .dropdown-menu |
|
2843 |
if (evt.target.nodeName.toLowerCase() !== 'input') |
|
2844 |
evt.preventDefault(); |
|
2845 |
evt.stopPropagation(); |
|
2846 |
// Emulate click for mobile devices |
|
2847 |
if (isTouch) { |
|
2848 |
var targetEl = angular.element(evt.target); |
|
2849 |
if (targetEl[0].nodeName.toLowerCase() !== 'button') { |
|
2850 |
targetEl = targetEl.parent(); |
|
2851 |
} |
|
2852 |
targetEl.triggerHandler('click'); |
|
2853 |
} |
|
2854 |
}; |
|
2855 |
$timepicker.$onKeyDown = function (evt) { |
|
2856 |
if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey) |
|
2857 |
return; |
|
2858 |
evt.preventDefault(); |
|
2859 |
evt.stopPropagation(); |
|
2860 |
// Close on enter |
|
2861 |
if (evt.keyCode === 13) |
|
2862 |
return $timepicker.hide(true); |
|
2863 |
// Navigate with keyboard |
|
2864 |
var newDate = new Date($timepicker.$date); |
|
2865 |
var hours = newDate.getHours(), hoursLength = dateFilter(newDate, 'h').length; |
|
2866 |
var minutes = newDate.getMinutes(), minutesLength = dateFilter(newDate, 'mm').length; |
|
2867 |
var lateralMove = /(37|39)/.test(evt.keyCode); |
|
2868 |
var count = 2 + !!formats[3] * 1; |
|
2869 |
// Navigate indexes (left, right) |
|
2870 |
if (lateralMove) { |
|
2871 |
if (evt.keyCode === 37) |
|
2872 |
selectedIndex = selectedIndex < 1 ? count - 1 : selectedIndex - 1; |
|
2873 |
else if (evt.keyCode === 39) |
|
2874 |
selectedIndex = selectedIndex < count - 1 ? selectedIndex + 1 : 0; |
|
2875 |
} |
|
2876 |
// Update values (up, down) |
|
2877 |
var selectRange = [ |
|
2878 |
0, |
|
2879 |
hoursLength |
|
2880 |
]; |
|
2881 |
if (selectedIndex === 0) { |
|
2882 |
if (evt.keyCode === 38) |
|
2883 |
newDate.setHours(hours - parseInt(options.hourStep, 10)); |
|
2884 |
else if (evt.keyCode === 40) |
|
2885 |
newDate.setHours(hours + parseInt(options.hourStep, 10)); |
|
2886 |
selectRange = [ |
|
2887 |
0, |
|
2888 |
hoursLength |
|
2889 |
]; |
|
2890 |
} else if (selectedIndex === 1) { |
|
2891 |
if (evt.keyCode === 38) |
|
2892 |
newDate.setMinutes(minutes - parseInt(options.minuteStep, 10)); |
|
2893 |
else if (evt.keyCode === 40) |
|
2894 |
newDate.setMinutes(minutes + parseInt(options.minuteStep, 10)); |
|
2895 |
selectRange = [ |
|
2896 |
hoursLength + 1, |
|
2897 |
hoursLength + 1 + minutesLength |
|
2898 |
]; |
|
2899 |
} else if (selectedIndex === 2) { |
|
2900 |
if (!lateralMove) |
|
2901 |
$timepicker.switchMeridian(); |
|
2902 |
selectRange = [ |
|
2903 |
hoursLength + 1 + minutesLength + 1, |
|
2904 |
hoursLength + 1 + minutesLength + 3 |
|
2905 |
]; |
|
2906 |
} |
|
2907 |
$timepicker.select(newDate, selectedIndex, true); |
|
2908 |
createSelection(selectRange[0], selectRange[1]); |
|
2909 |
parentScope.$digest(); |
|
2910 |
}; |
|
2911 |
// Private |
|
2912 |
function createSelection(start, end) { |
|
2913 |
if (element[0].createTextRange) { |
|
2914 |
var selRange = element[0].createTextRange(); |
|
2915 |
selRange.collapse(true); |
|
2916 |
selRange.moveStart('character', start); |
|
2917 |
selRange.moveEnd('character', end); |
|
2918 |
selRange.select(); |
|
2919 |
} else if (element[0].setSelectionRange) { |
|
2920 |
element[0].setSelectionRange(start, end); |
|
2921 |
} else if (angular.isUndefined(element[0].selectionStart)) { |
|
2922 |
element[0].selectionStart = start; |
|
2923 |
element[0].selectionEnd = end; |
|
2924 |
} |
|
2925 |
} |
|
2926 |
function focusElement() { |
|
2927 |
element[0].focus(); |
|
2928 |
} |
|
2929 |
// Overrides |
|
2930 |
var _init = $timepicker.init; |
|
2931 |
$timepicker.init = function () { |
|
2932 |
if (isNative && options.useNative) { |
|
2933 |
element.prop('type', 'time'); |
|
2934 |
element.css('-webkit-appearance', 'textfield'); |
|
2935 |
return; |
|
2936 |
} else if (isTouch) { |
|
2937 |
element.prop('type', 'text'); |
|
2938 |
element.attr('readonly', 'true'); |
|
2939 |
element.on('click', focusElement); |
|
2940 |
} |
|
2941 |
_init(); |
|
2942 |
}; |
|
2943 |
var _destroy = $timepicker.destroy; |
|
2944 |
$timepicker.destroy = function () { |
|
2945 |
if (isNative && options.useNative) { |
|
2946 |
element.off('click', focusElement); |
|
2947 |
} |
|
2948 |
_destroy(); |
|
2949 |
}; |
|
2950 |
var _show = $timepicker.show; |
|
2951 |
$timepicker.show = function () { |
|
2952 |
_show(); |
|
2953 |
setTimeout(function () { |
|
2954 |
$timepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown); |
|
2955 |
if (options.keyboard) { |
|
2956 |
element.on('keydown', $timepicker.$onKeyDown); |
|
2957 |
} |
|
2958 |
}); |
|
2959 |
}; |
|
2960 |
var _hide = $timepicker.hide; |
|
2961 |
$timepicker.hide = function (blur) { |
|
2962 |
$timepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown); |
|
2963 |
if (options.keyboard) { |
|
2964 |
element.off('keydown', $timepicker.$onKeyDown); |
|
2965 |
} |
|
2966 |
_hide(blur); |
|
2967 |
}; |
|
2968 |
return $timepicker; |
|
2969 |
} |
|
2970 |
timepickerFactory.defaults = defaults; |
|
2971 |
return timepickerFactory; |
|
2972 |
} |
|
2973 |
]; |
|
2974 |
}).directive('bsTimepicker', [ |
|
2975 |
'$window', |
|
2976 |
'$parse', |
|
2977 |
'$q', |
|
2978 |
'$locale', |
|
2979 |
'dateFilter', |
|
2980 |
'$timepicker', |
|
2981 |
'$dateParser', |
|
2982 |
'$timeout', |
|
2983 |
function ($window, $parse, $q, $locale, dateFilter, $timepicker, $dateParser, $timeout) { |
|
2984 |
var defaults = $timepicker.defaults; |
|
2985 |
var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent); |
|
2986 |
var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout; |
|
2987 |
return { |
|
2988 |
restrict: 'EAC', |
|
2989 |
require: 'ngModel', |
|
2990 |
link: function postLink(scope, element, attr, controller) { |
|
2991 |
// Directive options |
|
2992 |
var options = { |
|
2993 |
scope: scope, |
|
2994 |
controller: controller |
|
2995 |
}; |
|
2996 |
angular.forEach([ |
|
2997 |
'placement', |
|
2998 |
'container', |
|
2999 |
'delay', |
|
3000 |
'trigger', |
|
3001 |
'keyboard', |
|
3002 |
'html', |
|
3003 |
'animation', |
|
3004 |
'template', |
|
3005 |
'autoclose', |
|
3006 |
'timeType', |
|
3007 |
'timeFormat', |
|
3008 |
'modelTimeFormat', |
|
3009 |
'useNative', |
|
3010 |
'hourStep', |
|
3011 |
'minuteStep', |
|
3012 |
'length' |
|
3013 |
], function (key) { |
|
3014 |
if (angular.isDefined(attr[key])) |
|
3015 |
options[key] = attr[key]; |
|
3016 |
}); |
|
3017 |
// Initialize timepicker |
|
3018 |
if (isNative && (options.useNative || defaults.useNative)) |
|
3019 |
options.timeFormat = 'HH:mm'; |
|
3020 |
var timepicker = $timepicker(element, controller, options); |
|
3021 |
options = timepicker.$options; |
|
3022 |
// Initialize parser |
|
3023 |
var dateParser = $dateParser({ |
|
3024 |
format: options.timeFormat, |
|
3025 |
lang: options.lang |
|
3026 |
}); |
|
3027 |
// Observe attributes for changes |
|
3028 |
angular.forEach([ |
|
3029 |
'minTime', |
|
3030 |
'maxTime' |
|
3031 |
], function (key) { |
|
3032 |
// console.warn('attr.$observe(%s)', key, attr[key]); |
|
3033 |
angular.isDefined(attr[key]) && attr.$observe(key, function (newValue) { |
|
3034 |
if (newValue === 'now') { |
|
3035 |
timepicker.$options[key] = new Date().setFullYear(1970, 0, 1); |
|
3036 |
} else if (angular.isString(newValue) && newValue.match(/^".+"$/)) { |
|
3037 |
timepicker.$options[key] = +new Date(newValue.substr(1, newValue.length - 2)); |
|
3038 |
} else { |
|
3039 |
timepicker.$options[key] = dateParser.parse(newValue, new Date(1970, 0, 1, 0)); |
|
3040 |
} |
|
3041 |
!isNaN(timepicker.$options[key]) && timepicker.$build(); |
|
3042 |
}); |
|
3043 |
}); |
|
3044 |
// Watch model for changes |
|
3045 |
scope.$watch(attr.ngModel, function (newValue, oldValue) { |
|
3046 |
// console.warn('scope.$watch(%s)', attr.ngModel, newValue, oldValue, controller.$dateValue); |
|
3047 |
timepicker.update(controller.$dateValue); |
|
3048 |
}, true); |
|
3049 |
// viewValue -> $parsers -> modelValue |
|
3050 |
controller.$parsers.unshift(function (viewValue) { |
|
3051 |
// console.warn('$parser("%s"): viewValue=%o', element.attr('ng-model'), viewValue); |
|
3052 |
// Null values should correctly reset the model value & validity |
|
3053 |
if (!viewValue) { |
|
3054 |
controller.$setValidity('date', true); |
|
3055 |
return; |
|
3056 |
} |
|
3057 |
var parsedTime = dateParser.parse(viewValue, controller.$dateValue); |
|
3058 |
if (!parsedTime || isNaN(parsedTime.getTime())) { |
|
3059 |
controller.$setValidity('date', false); |
|
3060 |
} else { |
|
3061 |
var isValid = parsedTime.getTime() >= options.minTime && parsedTime.getTime() <= options.maxTime; |
|
3062 |
controller.$setValidity('date', isValid); |
|
3063 |
// Only update the model when we have a valid date |
|
3064 |
if (isValid) |
|
3065 |
controller.$dateValue = parsedTime; |
|
3066 |
} |
|
3067 |
if (options.timeType === 'string') { |
|
3068 |
return dateFilter(parsedTime, options.modelTimeFormat || options.timeFormat); |
|
3069 |
} else if (options.timeType === 'number') { |
|
3070 |
return controller.$dateValue.getTime(); |
|
3071 |
} else if (options.timeType === 'iso') { |
|
3072 |
return controller.$dateValue.toISOString(); |
|
3073 |
} else { |
|
3074 |
return new Date(controller.$dateValue); |
|
3075 |
} |
|
3076 |
}); |
|
3077 |
// modelValue -> $formatters -> viewValue |
|
3078 |
controller.$formatters.push(function (modelValue) { |
|
3079 |
// console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue); |
|
3080 |
var date; |
|
3081 |
if (angular.isUndefined(modelValue) || modelValue === null) { |
|
3082 |
date = NaN; |
|
3083 |
} else if (angular.isDate(modelValue)) { |
|
3084 |
date = modelValue; |
|
3085 |
} else if (options.timeType === 'string') { |
|
3086 |
date = dateParser.parse(modelValue, null, options.modelTimeFormat); |
|
3087 |
} else { |
|
3088 |
date = new Date(modelValue); |
|
3089 |
} |
|
3090 |
// Setup default value? |
|
3091 |
// if(isNaN(date.getTime())) date = new Date(new Date().setMinutes(0) + 36e5); |
|
3092 |
controller.$dateValue = date; |
|
3093 |
return controller.$dateValue; |
|
3094 |
}); |
|
3095 |
// viewValue -> element |
|
3096 |
controller.$render = function () { |
|
3097 |
// console.warn('$render("%s"): viewValue=%o', element.attr('ng-model'), controller.$viewValue); |
|
3098 |
element.val(!controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : dateFilter(controller.$dateValue, options.timeFormat)); |
|
3099 |
}; |
|
3100 |
// Garbage collection |
|
3101 |
scope.$on('$destroy', function () { |
|
3102 |
timepicker.destroy(); |
|
3103 |
options = null; |
|
3104 |
timepicker = null; |
|
3105 |
}); |
|
3106 |
} |
|
3107 |
}; |
|
3108 |
} |
|
3109 |
]); |
|
3110 |
|
|
3111 |
// Source: tooltip.js |
|
3112 |
angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions']).provider('$tooltip', function () { |
|
3113 |
var defaults = this.defaults = { |
|
3114 |
animation: 'am-fade', |
|
3115 |
prefixClass: 'tooltip', |
|
3116 |
prefixEvent: 'tooltip', |
|
3117 |
container: false, |
|
3118 |
target: false, |
|
3119 |
placement: 'top', |
|
3120 |
template: 'tooltip/tooltip.tpl.html', |
|
3121 |
contentTemplate: false, |
|
3122 |
trigger: 'hover focus', |
|
3123 |
keyboard: false, |
|
3124 |
html: false, |
|
3125 |
show: false, |
|
3126 |
title: '', |
|
3127 |
type: '', |
|
3128 |
delay: 0 |
|
3129 |
}; |
|
3130 |
this.$get = [ |
|
3131 |
'$window', |
|
3132 |
'$rootScope', |
|
3133 |
'$compile', |
|
3134 |
'$q', |
|
3135 |
'$templateCache', |
|
3136 |
'$http', |
|
3137 |
'$animate', |
|
3138 |
'dimensions', |
|
3139 |
'$$rAF', |
|
3140 |
function ($window, $rootScope, $compile, $q, $templateCache, $http, $animate, dimensions, $$rAF) { |
|
3141 |
var trim = String.prototype.trim; |
|
3142 |
var isTouch = 'createTouch' in $window.document; |
|
3143 |
var htmlReplaceRegExp = /ng-bind="/gi; |
|
3144 |
function TooltipFactory(element, config) { |
|
3145 |
var $tooltip = {}; |
|
3146 |
// Common vars |
|
3147 |
var nodeName = element[0].nodeName.toLowerCase(); |
|
3148 |
var options = $tooltip.$options = angular.extend({}, defaults, config); |
|
3149 |
$tooltip.$promise = fetchTemplate(options.template); |
|
3150 |
var scope = $tooltip.$scope = options.scope && options.scope.$new() || $rootScope.$new(); |
|
3151 |
if (options.delay && angular.isString(options.delay)) { |
|
3152 |
options.delay = parseFloat(options.delay); |
|
3153 |
} |
|
3154 |
// Support scope as string options |
|
3155 |
if (options.title) { |
|
3156 |
$tooltip.$scope.title = options.title; |
|
3157 |
} |
|
3158 |
// Provide scope helpers |
|
3159 |
scope.$hide = function () { |
|
3160 |
scope.$$postDigest(function () { |
|
3161 |
$tooltip.hide(); |
|
3162 |
}); |
|
3163 |
}; |
|
3164 |
scope.$show = function () { |
|
3165 |
scope.$$postDigest(function () { |
|
3166 |
$tooltip.show(); |
|
3167 |
}); |
|
3168 |
}; |
|
3169 |
scope.$toggle = function () { |
|
3170 |
scope.$$postDigest(function () { |
|
3171 |
$tooltip.toggle(); |
|
3172 |
}); |
|
3173 |
}; |
|
3174 |
$tooltip.$isShown = scope.$isShown = false; |
|
3175 |
// Private vars |
|
3176 |
var timeout, hoverState; |
|
3177 |
// Support contentTemplate option |
|
3178 |
if (options.contentTemplate) { |
|
3179 |
$tooltip.$promise = $tooltip.$promise.then(function (template) { |
|
3180 |
var templateEl = angular.element(template); |
|
3181 |
return fetchTemplate(options.contentTemplate).then(function (contentTemplate) { |
|
3182 |
var contentEl = findElement('[ng-bind="content"]', templateEl[0]); |
|
3183 |
if (!contentEl.length) |
|
3184 |
contentEl = findElement('[ng-bind="title"]', templateEl[0]); |
|
3185 |
contentEl.removeAttr('ng-bind').html(contentTemplate); |
|
3186 |
return templateEl[0].outerHTML; |
|
3187 |
}); |
|
3188 |
}); |
|
3189 |
} |
|
3190 |
// Fetch, compile then initialize tooltip |
|
3191 |
var tipLinker, tipElement, tipTemplate, tipContainer; |
|
3192 |
$tooltip.$promise.then(function (template) { |
|
3193 |
if (angular.isObject(template)) |
|
3194 |
template = template.data; |
|
3195 |
if (options.html) |
|
3196 |
template = template.replace(htmlReplaceRegExp, 'ng-bind-html="'); |
|
3197 |
template = trim.apply(template); |
|
3198 |
tipTemplate = template; |
|
3199 |
tipLinker = $compile(template); |
|
3200 |
$tooltip.init(); |
|
3201 |
}); |
|
3202 |
$tooltip.init = function () { |
|
3203 |
// Options: delay |
|
3204 |
if (options.delay && angular.isNumber(options.delay)) { |
|
3205 |
options.delay = { |
|
3206 |
show: options.delay, |
|
3207 |
hide: options.delay |
|
3208 |
}; |
|
3209 |
} |
|
3210 |
// Replace trigger on touch devices ? |
|
3211 |
// if(isTouch && options.trigger === defaults.trigger) { |
|
3212 |
// options.trigger.replace(/hover/g, 'click'); |
|
3213 |
// } |
|
3214 |
// Options : container |
|
3215 |
if (options.container === 'self') { |
|
3216 |
tipContainer = element; |
|
3217 |
} else if (options.container) { |
|
3218 |
tipContainer = findElement(options.container); |
|
3219 |
} |
|
3220 |
// Options: trigger |
|
3221 |
var triggers = options.trigger.split(' '); |
|
3222 |
angular.forEach(triggers, function (trigger) { |
|
3223 |
if (trigger === 'click') { |
|
3224 |
element.on('click', $tooltip.toggle); |
|
3225 |
} else if (trigger !== 'manual') { |
|
3226 |
element.on(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter); |
|
3227 |
element.on(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave); |
|
3228 |
nodeName === 'button' && trigger !== 'hover' && element.on(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown); |
|
3229 |
} |
|
3230 |
}); |
|
3231 |
// Options: target |
|
3232 |
if (options.target) { |
|
3233 |
options.target = angular.isElement(options.target) ? options.target : findElement(options.target)[0]; |
|
3234 |
} |
|
3235 |
// Options: show |
|
3236 |
if (options.show) { |
|
3237 |
scope.$$postDigest(function () { |
|
3238 |
options.trigger === 'focus' ? element[0].focus() : $tooltip.show(); |
|
3239 |
}); |
|
3240 |
} |
|
3241 |
}; |
|
3242 |
$tooltip.destroy = function () { |
|
3243 |
// Unbind events |
|
3244 |
var triggers = options.trigger.split(' '); |
|
3245 |
for (var i = triggers.length; i--;) { |
|
3246 |
var trigger = triggers[i]; |
|
3247 |
if (trigger === 'click') { |
|
3248 |
element.off('click', $tooltip.toggle); |
|
3249 |
} else if (trigger !== 'manual') { |
|
3250 |
element.off(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter); |
|
3251 |
element.off(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave); |
|
3252 |
nodeName === 'button' && trigger !== 'hover' && element.off(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown); |
|
3253 |
} |
|
3254 |
} |
|
3255 |
// Remove element |
|
3256 |
if (tipElement) { |
|
3257 |
tipElement.remove(); |
|
3258 |
tipElement = null; |
|
3259 |
} |
|
3260 |
// Cancel pending callbacks |
|
3261 |
clearTimeout(timeout); |
|
3262 |
// Destroy scope |
|
3263 |
scope.$destroy(); |
|
3264 |
}; |
|
3265 |
$tooltip.enter = function () { |
|
3266 |
clearTimeout(timeout); |
|
3267 |
hoverState = 'in'; |
|
3268 |
if (!options.delay || !options.delay.show) { |
|
3269 |
return $tooltip.show(); |
|
3270 |
} |
|
3271 |
timeout = setTimeout(function () { |
|
3272 |
if (hoverState === 'in') |
|
3273 |
$tooltip.show(); |
|
3274 |
}, options.delay.show); |
|
3275 |
}; |
|
3276 |
$tooltip.show = function () { |
|
3277 |
scope.$emit(options.prefixEvent + '.show.before', $tooltip); |
|
3278 |
var parent = options.container ? tipContainer : null; |
|
3279 |
var after = options.container ? null : element; |
|
3280 |
// Hide any existing tipElement |
|
3281 |
if (tipElement) |
|
3282 |
tipElement.remove(); |
|
3283 |
// Fetch a cloned element linked from template |
|
3284 |
tipElement = $tooltip.$element = tipLinker(scope, function (clonedElement, scope) { |
|
3285 |
}); |
|
3286 |
// Set the initial positioning. |
|
3287 |
tipElement.css({ |
|
3288 |
top: '-9999px', |
|
3289 |
left: '-9999px', |
|
3290 |
display: 'block' |
|
3291 |
}).addClass(options.placement); |
|
3292 |
// Options: animation |
|
3293 |
if (options.animation) |
|
3294 |
tipElement.addClass(options.animation); |
|
3295 |
// Options: type |
|
3296 |
if (options.type) |
|
3297 |
tipElement.addClass(options.prefixClass + '-' + options.type); |
|
3298 |
$animate.enter(tipElement, parent, after, function () { |
|
3299 |
scope.$emit(options.prefixEvent + '.show', $tooltip); |
|
3300 |
}); |
|
3301 |
$tooltip.$isShown = scope.$isShown = true; |
|
3302 |
scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest(); |
|
3303 |
$$rAF($tooltip.$applyPlacement); |
|
3304 |
// var a = bodyEl.offsetWidth + 1; ? |
|
3305 |
// Bind events |
|
3306 |
if (options.keyboard) { |
|
3307 |
if (options.trigger !== 'focus') { |
|
3308 |
$tooltip.focus(); |
|
3309 |
tipElement.on('keyup', $tooltip.$onKeyUp); |
|
3310 |
} else { |
|
3311 |
element.on('keyup', $tooltip.$onFocusKeyUp); |
|
3312 |
} |
|
3313 |
} |
|
3314 |
}; |
|
3315 |
$tooltip.leave = function () { |
|
3316 |
clearTimeout(timeout); |
|
3317 |
hoverState = 'out'; |
|
3318 |
if (!options.delay || !options.delay.hide) { |
|
3319 |
return $tooltip.hide(); |
|
3320 |
} |
|
3321 |
timeout = setTimeout(function () { |
|
3322 |
if (hoverState === 'out') { |
|
3323 |
$tooltip.hide(); |
|
3324 |
} |
|
3325 |
}, options.delay.hide); |
|
3326 |
}; |
|
3327 |
$tooltip.hide = function (blur) { |
|
3328 |
if (!$tooltip.$isShown) |
|
3329 |
return; |
|
3330 |
scope.$emit(options.prefixEvent + '.hide.before', $tooltip); |
|
3331 |
$animate.leave(tipElement, function () { |
|
3332 |
scope.$emit(options.prefixEvent + '.hide', $tooltip); |
|
3333 |
}); |
|
3334 |
$tooltip.$isShown = scope.$isShown = false; |
|
3335 |
scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest(); |
|
3336 |
// Unbind events |
|
3337 |
if (options.keyboard && tipElement !== null) { |
|
3338 |
tipElement.off('keyup', $tooltip.$onKeyUp); |
|
3339 |
} |
|
3340 |
// Allow to blur the input when hidden, like when pressing enter key |
|
3341 |
if (blur && options.trigger === 'focus') { |
|
3342 |
return element[0].blur(); |
|
3343 |
} |
|
3344 |
}; |
|
3345 |
$tooltip.toggle = function () { |
|
3346 |
$tooltip.$isShown ? $tooltip.leave() : $tooltip.enter(); |
|
3347 |
}; |
|
3348 |
$tooltip.focus = function () { |
|
3349 |
tipElement[0].focus(); |
|
3350 |
}; |
|
3351 |
// Protected methods |
|
3352 |
$tooltip.$applyPlacement = function () { |
|
3353 |
if (!tipElement) |
|
3354 |
return; |
|
3355 |
// Get the position of the tooltip element. |
|
3356 |
var elementPosition = getPosition(); |
|
3357 |
// Get the height and width of the tooltip so we can center it. |
|
3358 |
var tipWidth = tipElement.prop('offsetWidth'), tipHeight = tipElement.prop('offsetHeight'); |
|
3359 |
// Get the tooltip's top and left coordinates to center it with this directive. |
|
3360 |
var tipPosition = getCalculatedOffset(options.placement, elementPosition, tipWidth, tipHeight); |
|
3361 |
// Now set the calculated positioning. |
|
3362 |
tipPosition.top += 'px'; |
|
3363 |
tipPosition.left += 'px'; |
|
3364 |
tipElement.css(tipPosition); |
|
3365 |
}; |
|
3366 |
$tooltip.$onKeyUp = function (evt) { |
|
3367 |
evt.which === 27 && $tooltip.hide(); |
|
3368 |
}; |
|
3369 |
$tooltip.$onFocusKeyUp = function (evt) { |
|
3370 |
evt.which === 27 && element[0].blur(); |
|
3371 |
}; |
|
3372 |
$tooltip.$onFocusElementMouseDown = function (evt) { |
|
3373 |
evt.preventDefault(); |
|
3374 |
evt.stopPropagation(); |
|
3375 |
// Some browsers do not auto-focus buttons (eg. Safari) |
|
3376 |
$tooltip.$isShown ? element[0].blur() : element[0].focus(); |
|
3377 |
}; |
|
3378 |
// Private methods |
|
3379 |
function getPosition() { |
|
3380 |
if (options.container === 'body') { |
|
3381 |
return dimensions.offset(options.target || element[0]); |
|
3382 |
} else { |
|
3383 |
return dimensions.position(options.target || element[0]); |
|
3384 |
} |
|
3385 |
} |
|
3386 |
function getCalculatedOffset(placement, position, actualWidth, actualHeight) { |
|
3387 |
var offset; |
|
3388 |
var split = placement.split('-'); |
|
3389 |
switch (split[0]) { |
|
3390 |
case 'right': |
|
3391 |
offset = { |
|
3392 |
top: position.top + position.height / 2 - actualHeight / 2, |
|
3393 |
left: position.left + position.width |
|
3394 |
}; |
|
3395 |
break; |
|
3396 |
case 'bottom': |
|
3397 |
offset = { |
|
3398 |
top: position.top + position.height, |
|
3399 |
left: position.left + position.width / 2 - actualWidth / 2 |
|
3400 |
}; |
|
3401 |
break; |
|
3402 |
case 'left': |
|
3403 |
offset = { |
|
3404 |
top: position.top + position.height / 2 - actualHeight / 2, |
|
3405 |
left: position.left - actualWidth |
|
3406 |
}; |
|
3407 |
break; |
|
3408 |
default: |
|
3409 |
offset = { |
|
3410 |
top: position.top - actualHeight, |
|
3411 |
left: position.left + position.width / 2 - actualWidth / 2 |
|
3412 |
}; |
|
3413 |
break; |
|
3414 |
} |
|
3415 |
if (!split[1]) { |
|
3416 |
return offset; |
|
3417 |
} |
|
3418 |
// Add support for corners @todo css |
|
3419 |
if (split[0] === 'top' || split[0] === 'bottom') { |
|
3420 |
switch (split[1]) { |
|
3421 |
case 'left': |
|
3422 |
offset.left = position.left; |
|
3423 |
break; |
|
3424 |
case 'right': |
|
3425 |
offset.left = position.left + position.width - actualWidth; |
|
3426 |
} |
|
3427 |
} else if (split[0] === 'left' || split[0] === 'right') { |
|
3428 |
switch (split[1]) { |
|
3429 |
case 'top': |
|
3430 |
offset.top = position.top - actualHeight; |
|
3431 |
break; |
|
3432 |
case 'bottom': |
|
3433 |
offset.top = position.top + position.height; |
|
3434 |
} |
|
3435 |
} |
|
3436 |
return offset; |
|
3437 |
} |
|
3438 |
return $tooltip; |
|
3439 |
} |
|
3440 |
// Helper functions |
|
3441 |
function findElement(query, element) { |
|
3442 |
return angular.element((element || document).querySelectorAll(query)); |
|
3443 |
} |
|
3444 |
function fetchTemplate(template) { |
|
3445 |
return $q.when($templateCache.get(template) || $http.get(template)).then(function (res) { |
|
3446 |
if (angular.isObject(res)) { |
|
3447 |
$templateCache.put(template, res.data); |
|
3448 |
return res.data; |
|
3449 |
} |
|
3450 |
return res; |
|
3451 |
}); |
|
3452 |
} |
|
3453 |
return TooltipFactory; |
|
3454 |
} |
|
3455 |
]; |
|
3456 |
}).directive('bsTooltip', [ |
|
3457 |
'$window', |
|
3458 |
'$location', |
|
3459 |
'$sce', |
|
3460 |
'$tooltip', |
|
3461 |
'$$rAF', |
|
3462 |
function ($window, $location, $sce, $tooltip, $$rAF) { |
|
3463 |
return { |
|
3464 |
restrict: 'EAC', |
|
3465 |
scope: true, |
|
3466 |
link: function postLink(scope, element, attr, transclusion) { |
|
3467 |
// Directive options |
|
3468 |
var options = { scope: scope }; |
|
3469 |
angular.forEach([ |
|
3470 |
'template', |
|
3471 |
'contentTemplate', |
|
3472 |
'placement', |
|
3473 |
'container', |
|
3474 |
'target', |
|
3475 |
'delay', |
|
3476 |
'trigger', |
|
3477 |
'keyboard', |
|
3478 |
'html', |
|
3479 |
'animation', |
|
3480 |
'type' |
|
3481 |
], function (key) { |
|
3482 |
if (angular.isDefined(attr[key])) |
|
3483 |
options[key] = attr[key]; |
|
3484 |
}); |
|
3485 |
// Observe scope attributes for change |
|
3486 |
angular.forEach(['title'], function (key) { |
|
3487 |
attr[key] && attr.$observe(key, function (newValue, oldValue) { |
|
3488 |
scope[key] = $sce.trustAsHtml(newValue); |
|
3489 |
angular.isDefined(oldValue) && $$rAF(function () { |
|
3490 |
tooltip && tooltip.$applyPlacement(); |
|
3491 |
}); |
|
3492 |
}); |
|
3493 |
}); |
|
3494 |
// Support scope as an object |
|
3495 |
attr.bsTooltip && scope.$watch(attr.bsTooltip, function (newValue, oldValue) { |
|
3496 |
if (angular.isObject(newValue)) { |
|
3497 |
angular.extend(scope, newValue); |
|
3498 |
} else { |
|
3499 |
scope.title = newValue; |
|
3500 |
} |
|
3501 |
angular.isDefined(oldValue) && $$rAF(function () { |
|
3502 |
tooltip && tooltip.$applyPlacement(); |
|
3503 |
}); |
|
3504 |
}, true); |
|
3505 |
// Initialize popover |
|
3506 |
var tooltip = $tooltip(element, options); |
|
3507 |
// Garbage collection |
|
3508 |
scope.$on('$destroy', function () { |
|
3509 |
tooltip.destroy(); |
|
3510 |
options = null; |
|
3511 |
tooltip = null; |
|
3512 |
}); |
|
3513 |
} |
|
3514 |
}; |
|
3515 |
} |
|
3516 |
]); |
|
3517 |
|
|
3518 |
// Source: typeahead.js |
|
3519 |
angular.module('mgcrea.ngStrap.typeahead', [ |
|
3520 |
'mgcrea.ngStrap.tooltip', |
|
3521 |
'mgcrea.ngStrap.helpers.parseOptions' |
|
3522 |
]).provider('$typeahead', function () { |
|
3523 |
var defaults = this.defaults = { |
|
3524 |
animation: 'am-fade', |
|
3525 |
prefixClass: 'typeahead', |
|
3526 |
placement: 'bottom-left', |
|
3527 |
template: 'typeahead/typeahead.tpl.html', |
|
3528 |
trigger: 'focus', |
|
3529 |
container: false, |
|
3530 |
keyboard: true, |
|
3531 |
html: false, |
|
3532 |
delay: 0, |
|
3533 |
minLength: 1, |
|
3534 |
filter: 'filter', |
|
3535 |
limit: 6 |
|
3536 |
}; |
|
3537 |
this.$get = [ |
|
3538 |
'$window', |
|
3539 |
'$rootScope', |
|
3540 |
'$tooltip', |
|
3541 |
function ($window, $rootScope, $tooltip) { |
|
3542 |
var bodyEl = angular.element($window.document.body); |
|
3543 |
function TypeaheadFactory(element, controller, config) { |
|
3544 |
var $typeahead = {}; |
|
3545 |
// Common vars |
|
3546 |
var options = angular.extend({}, defaults, config); |
|
3547 |
$typeahead = $tooltip(element, options); |
|
3548 |
var parentScope = config.scope; |
|
3549 |
var scope = $typeahead.$scope; |
|
3550 |
scope.$resetMatches = function () { |
|
3551 |
scope.$matches = []; |
|
3552 |
scope.$activeIndex = 0; |
|
3553 |
}; |
|
3554 |
scope.$resetMatches(); |
|
3555 |
scope.$activate = function (index) { |
|
3556 |
scope.$$postDigest(function () { |
|
3557 |
$typeahead.activate(index); |
|
3558 |
}); |
|
3559 |
}; |
|
3560 |
scope.$select = function (index, evt) { |
|
3561 |
scope.$$postDigest(function () { |
|
3562 |
$typeahead.select(index); |
|
3563 |
}); |
|
3564 |
}; |
|
3565 |
scope.$isVisible = function () { |
|
3566 |
return $typeahead.$isVisible(); |
|
3567 |
}; |
|
3568 |
// Public methods |
|
3569 |
$typeahead.update = function (matches) { |
|
3570 |
scope.$matches = matches; |
|
3571 |
if (scope.$activeIndex >= matches.length) { |
|
3572 |
scope.$activeIndex = 0; |
|
3573 |
} |
|
3574 |
}; |
|
3575 |
$typeahead.activate = function (index) { |
|
3576 |
scope.$activeIndex = index; |
|
3577 |
}; |
|
3578 |
$typeahead.select = function (index) { |
|
3579 |
var value = scope.$matches[index].value; |
|
3580 |
controller.$setViewValue(value); |
|
3581 |
controller.$render(); |
|
3582 |
scope.$resetMatches(); |
|
3583 |
if (parentScope) |
|
3584 |
parentScope.$digest(); |
|
3585 |
// Emit event |
|
3586 |
scope.$emit('$typeahead.select', value, index); |
|
3587 |
}; |
|
3588 |
// Protected methods |
|
3589 |
$typeahead.$isVisible = function () { |
|
3590 |
if (!options.minLength || !controller) { |
|
3591 |
return !!scope.$matches.length; |
|
3592 |
} |
|
3593 |
// minLength support |
|
3594 |
return scope.$matches.length && angular.isString(controller.$viewValue) && controller.$viewValue.length >= options.minLength; |
|
3595 |
}; |
|
3596 |
$typeahead.$getIndex = function (value) { |
|
3597 |
var l = scope.$matches.length, i = l; |
|
3598 |
if (!l) |
|
3599 |
return; |
|
3600 |
for (i = l; i--;) { |
|
3601 |
if (scope.$matches[i].value === value) |
|
3602 |
break; |
|
3603 |
} |
|
3604 |
if (i < 0) |
|
3605 |
return; |
|
3606 |
return i; |
|
3607 |
}; |
|
3608 |
$typeahead.$onMouseDown = function (evt) { |
|
3609 |
// Prevent blur on mousedown |
|
3610 |
evt.preventDefault(); |
|
3611 |
evt.stopPropagation(); |
|
3612 |
}; |
|
3613 |
$typeahead.$onKeyDown = function (evt) { |
|
3614 |
if (!/(38|40|13)/.test(evt.keyCode)) |
|
3615 |
return; |
|
3616 |
evt.preventDefault(); |
|
3617 |
evt.stopPropagation(); |
|
3618 |
// Select with enter |
|
3619 |
if (evt.keyCode === 13 && scope.$matches.length) { |
|
3620 |
$typeahead.select(scope.$activeIndex); |
|
3621 |
} // Navigate with keyboard |
|
3622 |
else if (evt.keyCode === 38 && scope.$activeIndex > 0) |
|
3623 |
scope.$activeIndex--; |
|
3624 |
else if (evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1) |
|
3625 |
scope.$activeIndex++; |
|
3626 |
else if (angular.isUndefined(scope.$activeIndex)) |
|
3627 |
scope.$activeIndex = 0; |
|
3628 |
scope.$digest(); |
|
3629 |
}; |
|
3630 |
// Overrides |
|
3631 |
var show = $typeahead.show; |
|
3632 |
$typeahead.show = function () { |
|
3633 |
show(); |
|
3634 |
setTimeout(function () { |
|
3635 |
$typeahead.$element.on('mousedown', $typeahead.$onMouseDown); |
|
3636 |
if (options.keyboard) { |
|
3637 |
element.on('keydown', $typeahead.$onKeyDown); |
|
3638 |
} |
|
3639 |
}); |
|
3640 |
}; |
|
3641 |
var hide = $typeahead.hide; |
|
3642 |
$typeahead.hide = function () { |
|
3643 |
$typeahead.$element.off('mousedown', $typeahead.$onMouseDown); |
|
3644 |
if (options.keyboard) { |
|
3645 |
element.off('keydown', $typeahead.$onKeyDown); |
|
3646 |
} |
|
3647 |
hide(); |
|
3648 |
}; |
|
3649 |
return $typeahead; |
|
3650 |
} |
|
3651 |
TypeaheadFactory.defaults = defaults; |
|
3652 |
return TypeaheadFactory; |
|
3653 |
} |
|
3654 |
]; |
|
3655 |
}).directive('bsTypeahead', [ |
|
3656 |
'$window', |
|
3657 |
'$parse', |
|
3658 |
'$q', |
|
3659 |
'$typeahead', |
|
3660 |
'$parseOptions', |
|
3661 |
function ($window, $parse, $q, $typeahead, $parseOptions) { |
|
3662 |
var defaults = $typeahead.defaults; |
|
3663 |
return { |
|
3664 |
restrict: 'EAC', |
|
3665 |
require: 'ngModel', |
|
3666 |
link: function postLink(scope, element, attr, controller) { |
|
3667 |
// Directive options |
|
3668 |
var options = { scope: scope }; |
|
3669 |
angular.forEach([ |
|
3670 |
'placement', |
|
3671 |
'container', |
|
3672 |
'delay', |
|
3673 |
'trigger', |
|
3674 |
'keyboard', |
|
3675 |
'html', |
|
3676 |
'animation', |
|
3677 |
'template', |
|
3678 |
'filter', |
|
3679 |
'limit', |
|
3680 |
'minLength' |
|
3681 |
], function (key) { |
|
3682 |
if (angular.isDefined(attr[key])) |
|
3683 |
options[key] = attr[key]; |
|
3684 |
}); |
|
3685 |
// Build proper ngOptions |
|
3686 |
var filter = options.filter || defaults.filter; |
|
3687 |
var limit = options.limit || defaults.limit; |
|
3688 |
var ngOptions = attr.ngOptions; |
|
3689 |
if (filter) |
|
3690 |
ngOptions += ' | ' + filter + ':$viewValue'; |
|
3691 |
if (limit) |
|
3692 |
ngOptions += ' | limitTo:' + limit; |
|
3693 |
var parsedOptions = $parseOptions(ngOptions); |
|
3694 |
// Initialize typeahead |
|
3695 |
var typeahead = $typeahead(element, controller, options); |
|
3696 |
// Watch model for changes |
|
3697 |
scope.$watch(attr.ngModel, function (newValue, oldValue) { |
|
3698 |
// console.warn('$watch', element.attr('ng-model'), newValue); |
|
3699 |
scope.$modelValue = newValue; |
|
3700 |
// Publish modelValue on scope for custom templates |
|
3701 |
parsedOptions.valuesFn(scope, controller).then(function (values) { |
|
3702 |
if (values.length > limit) |
|
3703 |
values = values.slice(0, limit); |
|
3704 |
// Do not re-queue an update if a correct value has been selected |
|
3705 |
if (values.length === 1 && values[0].value === newValue) |
|
3706 |
return; |
|
3707 |
typeahead.update(values); |
|
3708 |
// Queue a new rendering that will leverage collection loading |
|
3709 |
controller.$render(); |
|
3710 |
}); |
|
3711 |
}); |
|
3712 |
// Model rendering in view |
|
3713 |
controller.$render = function () { |
|
3714 |
// console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue); |
|
3715 |
if (controller.$isEmpty(controller.$viewValue)) |
|
3716 |
return element.val(''); |
|
3717 |
var index = typeahead.$getIndex(controller.$modelValue); |
|
3718 |
var selected = angular.isDefined(index) ? typeahead.$scope.$matches[index].label : controller.$viewValue; |
|
3719 |
selected = angular.isObject(selected) ? selected.label : selected; |
|
3720 |
element.val(selected.replace(/<(?:.|\n)*?>/gm, '').trim()); |
|
3721 |
}; |
|
3722 |
// Garbage collection |
|
3723 |
scope.$on('$destroy', function () { |
|
3724 |
typeahead.destroy(); |
|
3725 |
options = null; |
|
3726 |
typeahead = null; |
|
3727 |
}); |
|
3728 |
} |
|
3729 |
}; |
|
3730 |
} |
|
3731 |
]); |
|
3732 |
|
|
3733 |
})(window, document); |