1 |
41837
|
michele.ar
|
/*
|
2 |
|
|
* angular-ui-bootstrap
|
3 |
|
|
* http://angular-ui.github.io/bootstrap/
|
4 |
|
|
|
5 |
|
|
* Version: 0.10.0 - 2014-01-13
|
6 |
|
|
* License: MIT
|
7 |
|
|
*/
|
8 |
|
|
angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
|
9 |
|
|
angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/popup.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]);
|
10 |
|
|
angular.module('ui.bootstrap.transition', [])
|
11 |
|
|
|
12 |
|
|
/**
|
13 |
|
|
* $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
|
14 |
|
|
* @param {DOMElement} element The DOMElement that will be animated.
|
15 |
|
|
* @param {string|object|function} trigger The thing that will cause the transition to start:
|
16 |
|
|
* - As a string, it represents the css class to be added to the element.
|
17 |
|
|
* - As an object, it represents a hash of style attributes to be applied to the element.
|
18 |
|
|
* - As a function, it represents a function to be called that will cause the transition to occur.
|
19 |
|
|
* @return {Promise} A promise that is resolved when the transition finishes.
|
20 |
|
|
*/
|
21 |
|
|
.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
|
22 |
|
|
|
23 |
|
|
var $transition = function(element, trigger, options) {
|
24 |
|
|
options = options || {};
|
25 |
|
|
var deferred = $q.defer();
|
26 |
|
|
var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
|
27 |
|
|
|
28 |
|
|
var transitionEndHandler = function(event) {
|
29 |
|
|
$rootScope.$apply(function() {
|
30 |
|
|
element.unbind(endEventName, transitionEndHandler);
|
31 |
|
|
deferred.resolve(element);
|
32 |
|
|
});
|
33 |
|
|
};
|
34 |
|
|
|
35 |
|
|
if (endEventName) {
|
36 |
|
|
element.bind(endEventName, transitionEndHandler);
|
37 |
|
|
}
|
38 |
|
|
|
39 |
|
|
// Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
|
40 |
|
|
$timeout(function() {
|
41 |
|
|
if ( angular.isString(trigger) ) {
|
42 |
|
|
element.addClass(trigger);
|
43 |
|
|
} else if ( angular.isFunction(trigger) ) {
|
44 |
|
|
trigger(element);
|
45 |
|
|
} else if ( angular.isObject(trigger) ) {
|
46 |
|
|
element.css(trigger);
|
47 |
|
|
}
|
48 |
|
|
//If browser does not support transitions, instantly resolve
|
49 |
|
|
if ( !endEventName ) {
|
50 |
|
|
deferred.resolve(element);
|
51 |
|
|
}
|
52 |
|
|
});
|
53 |
|
|
|
54 |
|
|
// Add our custom cancel function to the promise that is returned
|
55 |
|
|
// We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
|
56 |
|
|
// i.e. it will therefore never raise a transitionEnd event for that transition
|
57 |
|
|
deferred.promise.cancel = function() {
|
58 |
|
|
if ( endEventName ) {
|
59 |
|
|
element.unbind(endEventName, transitionEndHandler);
|
60 |
|
|
}
|
61 |
|
|
deferred.reject('Transition cancelled');
|
62 |
|
|
};
|
63 |
|
|
|
64 |
|
|
return deferred.promise;
|
65 |
|
|
};
|
66 |
|
|
|
67 |
|
|
// Work out the name of the transitionEnd event
|
68 |
|
|
var transElement = document.createElement('trans');
|
69 |
|
|
var transitionEndEventNames = {
|
70 |
|
|
'WebkitTransition': 'webkitTransitionEnd',
|
71 |
|
|
'MozTransition': 'transitionend',
|
72 |
|
|
'OTransition': 'oTransitionEnd',
|
73 |
|
|
'transition': 'transitionend'
|
74 |
|
|
};
|
75 |
|
|
var animationEndEventNames = {
|
76 |
|
|
'WebkitTransition': 'webkitAnimationEnd',
|
77 |
|
|
'MozTransition': 'animationend',
|
78 |
|
|
'OTransition': 'oAnimationEnd',
|
79 |
|
|
'transition': 'animationend'
|
80 |
|
|
};
|
81 |
|
|
function findEndEventName(endEventNames) {
|
82 |
|
|
for (var name in endEventNames){
|
83 |
|
|
if (transElement.style[name] !== undefined) {
|
84 |
|
|
return endEventNames[name];
|
85 |
|
|
}
|
86 |
|
|
}
|
87 |
|
|
}
|
88 |
|
|
$transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
|
89 |
|
|
$transition.animationEndEventName = findEndEventName(animationEndEventNames);
|
90 |
|
|
return $transition;
|
91 |
|
|
}]);
|
92 |
|
|
|
93 |
|
|
angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition'])
|
94 |
|
|
|
95 |
|
|
.directive('collapse', ['$transition', function ($transition, $timeout) {
|
96 |
|
|
|
97 |
|
|
return {
|
98 |
|
|
link: function (scope, element, attrs) {
|
99 |
|
|
|
100 |
|
|
var initialAnimSkip = true;
|
101 |
|
|
var currentTransition;
|
102 |
|
|
|
103 |
|
|
function doTransition(change) {
|
104 |
|
|
var newTransition = $transition(element, change);
|
105 |
|
|
if (currentTransition) {
|
106 |
|
|
currentTransition.cancel();
|
107 |
|
|
}
|
108 |
|
|
currentTransition = newTransition;
|
109 |
|
|
newTransition.then(newTransitionDone, newTransitionDone);
|
110 |
|
|
return newTransition;
|
111 |
|
|
|
112 |
|
|
function newTransitionDone() {
|
113 |
|
|
// Make sure it's this transition, otherwise, leave it alone.
|
114 |
|
|
if (currentTransition === newTransition) {
|
115 |
|
|
currentTransition = undefined;
|
116 |
|
|
}
|
117 |
|
|
}
|
118 |
|
|
}
|
119 |
|
|
|
120 |
|
|
function expand() {
|
121 |
|
|
if (initialAnimSkip) {
|
122 |
|
|
initialAnimSkip = false;
|
123 |
|
|
expandDone();
|
124 |
|
|
} else {
|
125 |
|
|
element.removeClass('collapse').addClass('collapsing');
|
126 |
|
|
doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone);
|
127 |
|
|
}
|
128 |
|
|
}
|
129 |
|
|
|
130 |
|
|
function expandDone() {
|
131 |
|
|
element.removeClass('collapsing');
|
132 |
|
|
element.addClass('collapse in');
|
133 |
|
|
element.css({height: 'auto'});
|
134 |
|
|
}
|
135 |
|
|
|
136 |
|
|
function collapse() {
|
137 |
|
|
if (initialAnimSkip) {
|
138 |
|
|
initialAnimSkip = false;
|
139 |
|
|
collapseDone();
|
140 |
|
|
element.css({height: 0});
|
141 |
|
|
} else {
|
142 |
|
|
// CSS transitions don't work with height: auto, so we have to manually change the height to a specific value
|
143 |
|
|
element.css({ height: element[0].scrollHeight + 'px' });
|
144 |
|
|
//trigger reflow so a browser realizes that height was updated from auto to a specific value
|
145 |
|
|
var x = element[0].offsetWidth;
|
146 |
|
|
|
147 |
|
|
element.removeClass('collapse in').addClass('collapsing');
|
148 |
|
|
|
149 |
|
|
doTransition({ height: 0 }).then(collapseDone);
|
150 |
|
|
}
|
151 |
|
|
}
|
152 |
|
|
|
153 |
|
|
function collapseDone() {
|
154 |
|
|
element.removeClass('collapsing');
|
155 |
|
|
element.addClass('collapse');
|
156 |
|
|
}
|
157 |
|
|
|
158 |
|
|
scope.$watch(attrs.collapse, function (shouldCollapse) {
|
159 |
|
|
if (shouldCollapse) {
|
160 |
|
|
collapse();
|
161 |
|
|
} else {
|
162 |
|
|
expand();
|
163 |
|
|
}
|
164 |
|
|
});
|
165 |
|
|
}
|
166 |
|
|
};
|
167 |
|
|
}]);
|
168 |
|
|
|
169 |
|
|
angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
|
170 |
|
|
|
171 |
|
|
.constant('accordionConfig', {
|
172 |
|
|
closeOthers: true
|
173 |
|
|
})
|
174 |
|
|
|
175 |
|
|
.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {
|
176 |
|
|
|
177 |
|
|
// This array keeps track of the accordion groups
|
178 |
|
|
this.groups = [];
|
179 |
|
|
|
180 |
|
|
// Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
|
181 |
|
|
this.closeOthers = function(openGroup) {
|
182 |
|
|
var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
|
183 |
|
|
if ( closeOthers ) {
|
184 |
|
|
angular.forEach(this.groups, function (group) {
|
185 |
|
|
if ( group !== openGroup ) {
|
186 |
|
|
group.isOpen = false;
|
187 |
|
|
}
|
188 |
|
|
});
|
189 |
|
|
}
|
190 |
|
|
};
|
191 |
|
|
|
192 |
|
|
// This is called from the accordion-group directive to add itself to the accordion
|
193 |
|
|
this.addGroup = function(groupScope) {
|
194 |
|
|
var that = this;
|
195 |
|
|
this.groups.push(groupScope);
|
196 |
|
|
|
197 |
|
|
groupScope.$on('$destroy', function (event) {
|
198 |
|
|
that.removeGroup(groupScope);
|
199 |
|
|
});
|
200 |
|
|
};
|
201 |
|
|
|
202 |
|
|
// This is called from the accordion-group directive when to remove itself
|
203 |
|
|
this.removeGroup = function(group) {
|
204 |
|
|
var index = this.groups.indexOf(group);
|
205 |
|
|
if ( index !== -1 ) {
|
206 |
|
|
this.groups.splice(this.groups.indexOf(group), 1);
|
207 |
|
|
}
|
208 |
|
|
};
|
209 |
|
|
|
210 |
|
|
}])
|
211 |
|
|
|
212 |
|
|
// The accordion directive simply sets up the directive controller
|
213 |
|
|
// and adds an accordion CSS class to itself element.
|
214 |
|
|
.directive('accordion', function () {
|
215 |
|
|
return {
|
216 |
|
|
restrict:'EA',
|
217 |
|
|
controller:'AccordionController',
|
218 |
|
|
transclude: true,
|
219 |
|
|
replace: false,
|
220 |
|
|
templateUrl: 'template/accordion/accordion.html'
|
221 |
|
|
};
|
222 |
|
|
})
|
223 |
|
|
|
224 |
|
|
// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
|
225 |
|
|
.directive('accordionGroup', ['$parse', function($parse) {
|
226 |
|
|
return {
|
227 |
|
|
require:'^accordion', // We need this directive to be inside an accordion
|
228 |
|
|
restrict:'EA',
|
229 |
|
|
transclude:true, // It transcludes the contents of the directive into the template
|
230 |
|
|
replace: true, // The element containing the directive will be replaced with the template
|
231 |
|
|
templateUrl:'template/accordion/accordion-group.html',
|
232 |
|
|
scope:{ heading:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope
|
233 |
|
|
controller: function() {
|
234 |
|
|
this.setHeading = function(element) {
|
235 |
|
|
this.heading = element;
|
236 |
|
|
};
|
237 |
|
|
},
|
238 |
|
|
link: function(scope, element, attrs, accordionCtrl) {
|
239 |
|
|
var getIsOpen, setIsOpen;
|
240 |
|
|
|
241 |
|
|
accordionCtrl.addGroup(scope);
|
242 |
|
|
|
243 |
|
|
scope.isOpen = false;
|
244 |
|
|
|
245 |
|
|
if ( attrs.isOpen ) {
|
246 |
|
|
getIsOpen = $parse(attrs.isOpen);
|
247 |
|
|
setIsOpen = getIsOpen.assign;
|
248 |
|
|
|
249 |
|
|
scope.$parent.$watch(getIsOpen, function(value) {
|
250 |
|
|
scope.isOpen = !!value;
|
251 |
|
|
});
|
252 |
|
|
}
|
253 |
|
|
|
254 |
|
|
scope.$watch('isOpen', function(value) {
|
255 |
|
|
if ( value ) {
|
256 |
|
|
accordionCtrl.closeOthers(scope);
|
257 |
|
|
}
|
258 |
|
|
if ( setIsOpen ) {
|
259 |
|
|
setIsOpen(scope.$parent, value);
|
260 |
|
|
}
|
261 |
|
|
});
|
262 |
|
|
}
|
263 |
|
|
};
|
264 |
|
|
}])
|
265 |
|
|
|
266 |
|
|
// Use accordion-heading below an accordion-group to provide a heading containing HTML
|
267 |
|
|
// <accordion-group>
|
268 |
|
|
// <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading>
|
269 |
|
|
// </accordion-group>
|
270 |
|
|
.directive('accordionHeading', function() {
|
271 |
|
|
return {
|
272 |
|
|
restrict: 'EA',
|
273 |
|
|
transclude: true, // Grab the contents to be used as the heading
|
274 |
|
|
template: '', // In effect remove this element!
|
275 |
|
|
replace: true,
|
276 |
|
|
require: '^accordionGroup',
|
277 |
|
|
compile: function(element, attr, transclude) {
|
278 |
|
|
return function link(scope, element, attr, accordionGroupCtrl) {
|
279 |
|
|
// Pass the heading to the accordion-group controller
|
280 |
|
|
// so that it can be transcluded into the right place in the template
|
281 |
|
|
// [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
|
282 |
|
|
accordionGroupCtrl.setHeading(transclude(scope, function() {}));
|
283 |
|
|
};
|
284 |
|
|
}
|
285 |
|
|
};
|
286 |
|
|
})
|
287 |
|
|
|
288 |
|
|
// Use in the accordion-group template to indicate where you want the heading to be transcluded
|
289 |
|
|
// You must provide the property on the accordion-group controller that will hold the transcluded element
|
290 |
|
|
// <div class="accordion-group">
|
291 |
|
|
// <div class="accordion-heading" ><a ... accordion-transclude="heading">...</a></div>
|
292 |
|
|
// ...
|
293 |
|
|
// </div>
|
294 |
|
|
.directive('accordionTransclude', function() {
|
295 |
|
|
return {
|
296 |
|
|
require: '^accordionGroup',
|
297 |
|
|
link: function(scope, element, attr, controller) {
|
298 |
|
|
scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
|
299 |
|
|
if ( heading ) {
|
300 |
|
|
element.html('');
|
301 |
|
|
element.append(heading);
|
302 |
|
|
}
|
303 |
|
|
});
|
304 |
|
|
}
|
305 |
|
|
};
|
306 |
|
|
});
|
307 |
|
|
|
308 |
|
|
angular.module("ui.bootstrap.alert", [])
|
309 |
|
|
|
310 |
|
|
.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) {
|
311 |
|
|
$scope.closeable = 'close' in $attrs;
|
312 |
|
|
}])
|
313 |
|
|
|
314 |
|
|
.directive('alert', function () {
|
315 |
|
|
return {
|
316 |
|
|
restrict:'EA',
|
317 |
|
|
controller:'AlertController',
|
318 |
|
|
templateUrl:'template/alert/alert.html',
|
319 |
|
|
transclude:true,
|
320 |
|
|
replace:true,
|
321 |
|
|
scope: {
|
322 |
|
|
type: '=',
|
323 |
|
|
close: '&'
|
324 |
|
|
}
|
325 |
|
|
};
|
326 |
|
|
});
|
327 |
|
|
|
328 |
|
|
angular.module('ui.bootstrap.bindHtml', [])
|
329 |
|
|
|
330 |
|
|
.directive('bindHtmlUnsafe', function () {
|
331 |
|
|
return function (scope, element, attr) {
|
332 |
|
|
element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe);
|
333 |
|
|
scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) {
|
334 |
|
|
element.html(value || '');
|
335 |
|
|
});
|
336 |
|
|
};
|
337 |
|
|
});
|
338 |
|
|
angular.module('ui.bootstrap.buttons', [])
|
339 |
|
|
|
340 |
|
|
.constant('buttonConfig', {
|
341 |
|
|
activeClass: 'active',
|
342 |
|
|
toggleEvent: 'click'
|
343 |
|
|
})
|
344 |
|
|
|
345 |
|
|
.controller('ButtonsController', ['buttonConfig', function(buttonConfig) {
|
346 |
|
|
this.activeClass = buttonConfig.activeClass || 'active';
|
347 |
|
|
this.toggleEvent = buttonConfig.toggleEvent || 'click';
|
348 |
|
|
}])
|
349 |
|
|
|
350 |
|
|
.directive('btnRadio', function () {
|
351 |
|
|
return {
|
352 |
|
|
require: ['btnRadio', 'ngModel'],
|
353 |
|
|
controller: 'ButtonsController',
|
354 |
|
|
link: function (scope, element, attrs, ctrls) {
|
355 |
|
|
var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
356 |
|
|
|
357 |
|
|
//model -> UI
|
358 |
|
|
ngModelCtrl.$render = function () {
|
359 |
|
|
element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
|
360 |
|
|
};
|
361 |
|
|
|
362 |
|
|
//ui->model
|
363 |
|
|
element.bind(buttonsCtrl.toggleEvent, function () {
|
364 |
|
|
if (!element.hasClass(buttonsCtrl.activeClass)) {
|
365 |
|
|
scope.$apply(function () {
|
366 |
|
|
ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio));
|
367 |
|
|
ngModelCtrl.$render();
|
368 |
|
|
});
|
369 |
|
|
}
|
370 |
|
|
});
|
371 |
|
|
}
|
372 |
|
|
};
|
373 |
|
|
})
|
374 |
|
|
|
375 |
|
|
.directive('btnCheckbox', function () {
|
376 |
|
|
return {
|
377 |
|
|
require: ['btnCheckbox', 'ngModel'],
|
378 |
|
|
controller: 'ButtonsController',
|
379 |
|
|
link: function (scope, element, attrs, ctrls) {
|
380 |
|
|
var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
381 |
|
|
|
382 |
|
|
function getTrueValue() {
|
383 |
|
|
return getCheckboxValue(attrs.btnCheckboxTrue, true);
|
384 |
|
|
}
|
385 |
|
|
|
386 |
|
|
function getFalseValue() {
|
387 |
|
|
return getCheckboxValue(attrs.btnCheckboxFalse, false);
|
388 |
|
|
}
|
389 |
|
|
|
390 |
|
|
function getCheckboxValue(attributeValue, defaultValue) {
|
391 |
|
|
var val = scope.$eval(attributeValue);
|
392 |
|
|
return angular.isDefined(val) ? val : defaultValue;
|
393 |
|
|
}
|
394 |
|
|
|
395 |
|
|
//model -> UI
|
396 |
|
|
ngModelCtrl.$render = function () {
|
397 |
|
|
element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
|
398 |
|
|
};
|
399 |
|
|
|
400 |
|
|
//ui->model
|
401 |
|
|
element.bind(buttonsCtrl.toggleEvent, function () {
|
402 |
|
|
scope.$apply(function () {
|
403 |
|
|
ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
|
404 |
|
|
ngModelCtrl.$render();
|
405 |
|
|
});
|
406 |
|
|
});
|
407 |
|
|
}
|
408 |
|
|
};
|
409 |
|
|
});
|
410 |
|
|
|
411 |
|
|
/**
|
412 |
|
|
* @ngdoc overview
|
413 |
|
|
* @name ui.bootstrap.carousel
|
414 |
|
|
*
|
415 |
|
|
* @description
|
416 |
|
|
* AngularJS version of an image carousel.
|
417 |
|
|
*
|
418 |
|
|
*/
|
419 |
|
|
angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
|
420 |
|
|
.controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope, $timeout, $transition, $q) {
|
421 |
|
|
var self = this,
|
422 |
|
|
slides = self.slides = [],
|
423 |
|
|
currentIndex = -1,
|
424 |
|
|
currentTimeout, isPlaying;
|
425 |
|
|
self.currentSlide = null;
|
426 |
|
|
|
427 |
|
|
var destroyed = false;
|
428 |
|
|
/* direction: "prev" or "next" */
|
429 |
|
|
self.select = function(nextSlide, direction) {
|
430 |
|
|
var nextIndex = slides.indexOf(nextSlide);
|
431 |
|
|
//Decide direction if it's not given
|
432 |
|
|
if (direction === undefined) {
|
433 |
|
|
direction = nextIndex > currentIndex ? "next" : "prev";
|
434 |
|
|
}
|
435 |
|
|
if (nextSlide && nextSlide !== self.currentSlide) {
|
436 |
|
|
if ($scope.$currentTransition) {
|
437 |
|
|
$scope.$currentTransition.cancel();
|
438 |
|
|
//Timeout so ng-class in template has time to fix classes for finished slide
|
439 |
|
|
$timeout(goNext);
|
440 |
|
|
} else {
|
441 |
|
|
goNext();
|
442 |
|
|
}
|
443 |
|
|
}
|
444 |
|
|
function goNext() {
|
445 |
|
|
// Scope has been destroyed, stop here.
|
446 |
|
|
if (destroyed) { return; }
|
447 |
|
|
//If we have a slide to transition from and we have a transition type and we're allowed, go
|
448 |
|
|
if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) {
|
449 |
|
|
//We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime
|
450 |
|
|
nextSlide.$element.addClass(direction);
|
451 |
|
|
var reflow = nextSlide.$element[0].offsetWidth; //force reflow
|
452 |
|
|
|
453 |
|
|
//Set all other slides to stop doing their stuff for the new transition
|
454 |
|
|
angular.forEach(slides, function(slide) {
|
455 |
|
|
angular.extend(slide, {direction: '', entering: false, leaving: false, active: false});
|
456 |
|
|
});
|
457 |
|
|
angular.extend(nextSlide, {direction: direction, active: true, entering: true});
|
458 |
|
|
angular.extend(self.currentSlide||{}, {direction: direction, leaving: true});
|
459 |
|
|
|
460 |
|
|
$scope.$currentTransition = $transition(nextSlide.$element, {});
|
461 |
|
|
//We have to create new pointers inside a closure since next & current will change
|
462 |
|
|
(function(next,current) {
|
463 |
|
|
$scope.$currentTransition.then(
|
464 |
|
|
function(){ transitionDone(next, current); },
|
465 |
|
|
function(){ transitionDone(next, current); }
|
466 |
|
|
);
|
467 |
|
|
}(nextSlide, self.currentSlide));
|
468 |
|
|
} else {
|
469 |
|
|
transitionDone(nextSlide, self.currentSlide);
|
470 |
|
|
}
|
471 |
|
|
self.currentSlide = nextSlide;
|
472 |
|
|
currentIndex = nextIndex;
|
473 |
|
|
//every time you change slides, reset the timer
|
474 |
|
|
restartTimer();
|
475 |
|
|
}
|
476 |
|
|
function transitionDone(next, current) {
|
477 |
|
|
angular.extend(next, {direction: '', active: true, leaving: false, entering: false});
|
478 |
|
|
angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false});
|
479 |
|
|
$scope.$currentTransition = null;
|
480 |
|
|
}
|
481 |
|
|
};
|
482 |
|
|
$scope.$on('$destroy', function () {
|
483 |
|
|
destroyed = true;
|
484 |
|
|
});
|
485 |
|
|
|
486 |
|
|
/* Allow outside people to call indexOf on slides array */
|
487 |
|
|
self.indexOfSlide = function(slide) {
|
488 |
|
|
return slides.indexOf(slide);
|
489 |
|
|
};
|
490 |
|
|
|
491 |
|
|
$scope.next = function() {
|
492 |
|
|
var newIndex = (currentIndex + 1) % slides.length;
|
493 |
|
|
|
494 |
|
|
//Prevent this user-triggered transition from occurring if there is already one in progress
|
495 |
|
|
if (!$scope.$currentTransition) {
|
496 |
|
|
return self.select(slides[newIndex], 'next');
|
497 |
|
|
}
|
498 |
|
|
};
|
499 |
|
|
|
500 |
|
|
$scope.prev = function() {
|
501 |
|
|
var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1;
|
502 |
|
|
|
503 |
|
|
//Prevent this user-triggered transition from occurring if there is already one in progress
|
504 |
|
|
if (!$scope.$currentTransition) {
|
505 |
|
|
return self.select(slides[newIndex], 'prev');
|
506 |
|
|
}
|
507 |
|
|
};
|
508 |
|
|
|
509 |
|
|
$scope.select = function(slide) {
|
510 |
|
|
self.select(slide);
|
511 |
|
|
};
|
512 |
|
|
|
513 |
|
|
$scope.isActive = function(slide) {
|
514 |
|
|
return self.currentSlide === slide;
|
515 |
|
|
};
|
516 |
|
|
|
517 |
|
|
$scope.slides = function() {
|
518 |
|
|
return slides;
|
519 |
|
|
};
|
520 |
|
|
|
521 |
|
|
$scope.$watch('interval', restartTimer);
|
522 |
|
|
$scope.$on('$destroy', resetTimer);
|
523 |
|
|
|
524 |
|
|
function restartTimer() {
|
525 |
|
|
resetTimer();
|
526 |
|
|
var interval = +$scope.interval;
|
527 |
|
|
if (!isNaN(interval) && interval>=0) {
|
528 |
|
|
currentTimeout = $timeout(timerFn, interval);
|
529 |
|
|
}
|
530 |
|
|
}
|
531 |
|
|
|
532 |
|
|
function resetTimer() {
|
533 |
|
|
if (currentTimeout) {
|
534 |
|
|
$timeout.cancel(currentTimeout);
|
535 |
|
|
currentTimeout = null;
|
536 |
|
|
}
|
537 |
|
|
}
|
538 |
|
|
|
539 |
|
|
function timerFn() {
|
540 |
|
|
if (isPlaying) {
|
541 |
|
|
$scope.next();
|
542 |
|
|
restartTimer();
|
543 |
|
|
} else {
|
544 |
|
|
$scope.pause();
|
545 |
|
|
}
|
546 |
|
|
}
|
547 |
|
|
|
548 |
|
|
$scope.play = function() {
|
549 |
|
|
if (!isPlaying) {
|
550 |
|
|
isPlaying = true;
|
551 |
|
|
restartTimer();
|
552 |
|
|
}
|
553 |
|
|
};
|
554 |
|
|
$scope.pause = function() {
|
555 |
|
|
if (!$scope.noPause) {
|
556 |
|
|
isPlaying = false;
|
557 |
|
|
resetTimer();
|
558 |
|
|
}
|
559 |
|
|
};
|
560 |
|
|
|
561 |
|
|
self.addSlide = function(slide, element) {
|
562 |
|
|
slide.$element = element;
|
563 |
|
|
slides.push(slide);
|
564 |
|
|
//if this is the first slide or the slide is set to active, select it
|
565 |
|
|
if(slides.length === 1 || slide.active) {
|
566 |
|
|
self.select(slides[slides.length-1]);
|
567 |
|
|
if (slides.length == 1) {
|
568 |
|
|
$scope.play();
|
569 |
|
|
}
|
570 |
|
|
} else {
|
571 |
|
|
slide.active = false;
|
572 |
|
|
}
|
573 |
|
|
};
|
574 |
|
|
|
575 |
|
|
self.removeSlide = function(slide) {
|
576 |
|
|
//get the index of the slide inside the carousel
|
577 |
|
|
var index = slides.indexOf(slide);
|
578 |
|
|
slides.splice(index, 1);
|
579 |
|
|
if (slides.length > 0 && slide.active) {
|
580 |
|
|
if (index >= slides.length) {
|
581 |
|
|
self.select(slides[index-1]);
|
582 |
|
|
} else {
|
583 |
|
|
self.select(slides[index]);
|
584 |
|
|
}
|
585 |
|
|
} else if (currentIndex > index) {
|
586 |
|
|
currentIndex--;
|
587 |
|
|
}
|
588 |
|
|
};
|
589 |
|
|
|
590 |
|
|
}])
|
591 |
|
|
|
592 |
|
|
/**
|
593 |
|
|
* @ngdoc directive
|
594 |
|
|
* @name ui.bootstrap.carousel.directive:carousel
|
595 |
|
|
* @restrict EA
|
596 |
|
|
*
|
597 |
|
|
* @description
|
598 |
|
|
* Carousel is the outer container for a set of image 'slides' to showcase.
|
599 |
|
|
*
|
600 |
|
|
* @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide.
|
601 |
|
|
* @param {boolean=} noTransition Whether to disable transitions on the carousel.
|
602 |
|
|
* @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover).
|
603 |
|
|
*
|
604 |
|
|
* @example
|
605 |
|
|
<example module="ui.bootstrap">
|
606 |
|
|
<file name="index.html">
|
607 |
|
|
<carousel>
|
608 |
|
|
<slide>
|
609 |
|
|
<img src="http://placekitten.com/150/150" style="margin:auto;">
|
610 |
|
|
<div class="carousel-caption">
|
611 |
|
|
<p>Beautiful!</p>
|
612 |
|
|
</div>
|
613 |
|
|
</slide>
|
614 |
|
|
<slide>
|
615 |
|
|
<img src="http://placekitten.com/100/150" style="margin:auto;">
|
616 |
|
|
<div class="carousel-caption">
|
617 |
|
|
<p>D'aww!</p>
|
618 |
|
|
</div>
|
619 |
|
|
</slide>
|
620 |
|
|
</carousel>
|
621 |
|
|
</file>
|
622 |
|
|
<file name="demo.css">
|
623 |
|
|
.carousel-indicators {
|
624 |
|
|
top: auto;
|
625 |
|
|
bottom: 15px;
|
626 |
|
|
}
|
627 |
|
|
</file>
|
628 |
|
|
</example>
|
629 |
|
|
*/
|
630 |
|
|
.directive('carousel', [function() {
|
631 |
|
|
return {
|
632 |
|
|
restrict: 'EA',
|
633 |
|
|
transclude: true,
|
634 |
|
|
replace: true,
|
635 |
|
|
controller: 'CarouselController',
|
636 |
|
|
require: 'carousel',
|
637 |
|
|
templateUrl: 'template/carousel/carousel.html',
|
638 |
|
|
scope: {
|
639 |
|
|
interval: '=',
|
640 |
|
|
noTransition: '=',
|
641 |
|
|
noPause: '='
|
642 |
|
|
}
|
643 |
|
|
};
|
644 |
|
|
}])
|
645 |
|
|
|
646 |
|
|
/**
|
647 |
|
|
* @ngdoc directive
|
648 |
|
|
* @name ui.bootstrap.carousel.directive:slide
|
649 |
|
|
* @restrict EA
|
650 |
|
|
*
|
651 |
|
|
* @description
|
652 |
|
|
* Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element.
|
653 |
|
|
*
|
654 |
|
|
* @param {boolean=} active Model binding, whether or not this slide is currently active.
|
655 |
|
|
*
|
656 |
|
|
* @example
|
657 |
|
|
<example module="ui.bootstrap">
|
658 |
|
|
<file name="index.html">
|
659 |
|
|
<div ng-controller="CarouselDemoCtrl">
|
660 |
|
|
<carousel>
|
661 |
|
|
<slide ng-repeat="slide in slides" active="slide.active">
|
662 |
|
|
<img ng-src="{{slide.image}}" style="margin:auto;">
|
663 |
|
|
<div class="carousel-caption">
|
664 |
|
|
<h4>Slide {{$index}}</h4>
|
665 |
|
|
<p>{{slide.text}}</p>
|
666 |
|
|
</div>
|
667 |
|
|
</slide>
|
668 |
|
|
</carousel>
|
669 |
|
|
<div class="row-fluid">
|
670 |
|
|
<div class="span6">
|
671 |
|
|
<ul>
|
672 |
|
|
<li ng-repeat="slide in slides">
|
673 |
|
|
<button class="btn btn-mini" ng-class="{'btn-info': !slide.active, 'btn-success': slide.active}" ng-disabled="slide.active" ng-click="slide.active = true">select</button>
|
674 |
|
|
{{$index}}: {{slide.text}}
|
675 |
|
|
</li>
|
676 |
|
|
</ul>
|
677 |
|
|
<a class="btn" ng-click="addSlide()">Add Slide</a>
|
678 |
|
|
</div>
|
679 |
|
|
<div class="span6">
|
680 |
|
|
Interval, in milliseconds: <input type="number" ng-model="myInterval">
|
681 |
|
|
<br />Enter a negative number to stop the interval.
|
682 |
|
|
</div>
|
683 |
|
|
</div>
|
684 |
|
|
</div>
|
685 |
|
|
</file>
|
686 |
|
|
<file name="script.js">
|
687 |
|
|
function CarouselDemoCtrl($scope) {
|
688 |
|
|
$scope.myInterval = 5000;
|
689 |
|
|
var slides = $scope.slides = [];
|
690 |
|
|
$scope.addSlide = function() {
|
691 |
|
|
var newWidth = 200 + ((slides.length + (25 * slides.length)) % 150);
|
692 |
|
|
slides.push({
|
693 |
|
|
image: 'http://placekitten.com/' + newWidth + '/200',
|
694 |
|
|
text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' '
|
695 |
|
|
['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4]
|
696 |
|
|
});
|
697 |
|
|
};
|
698 |
|
|
for (var i=0; i<4; i++) $scope.addSlide();
|
699 |
|
|
}
|
700 |
|
|
</file>
|
701 |
|
|
<file name="demo.css">
|
702 |
|
|
.carousel-indicators {
|
703 |
|
|
top: auto;
|
704 |
|
|
bottom: 15px;
|
705 |
|
|
}
|
706 |
|
|
</file>
|
707 |
|
|
</example>
|
708 |
|
|
*/
|
709 |
|
|
|
710 |
|
|
.directive('slide', ['$parse', function($parse) {
|
711 |
|
|
return {
|
712 |
|
|
require: '^carousel',
|
713 |
|
|
restrict: 'EA',
|
714 |
|
|
transclude: true,
|
715 |
|
|
replace: true,
|
716 |
|
|
templateUrl: 'template/carousel/slide.html',
|
717 |
|
|
scope: {
|
718 |
|
|
},
|
719 |
|
|
link: function (scope, element, attrs, carouselCtrl) {
|
720 |
|
|
//Set up optional 'active' = binding
|
721 |
|
|
if (attrs.active) {
|
722 |
|
|
var getActive = $parse(attrs.active);
|
723 |
|
|
var setActive = getActive.assign;
|
724 |
|
|
var lastValue = scope.active = getActive(scope.$parent);
|
725 |
|
|
scope.$watch(function parentActiveWatch() {
|
726 |
|
|
var parentActive = getActive(scope.$parent);
|
727 |
|
|
|
728 |
|
|
if (parentActive !== scope.active) {
|
729 |
|
|
// we are out of sync and need to copy
|
730 |
|
|
if (parentActive !== lastValue) {
|
731 |
|
|
// parent changed and it has precedence
|
732 |
|
|
lastValue = scope.active = parentActive;
|
733 |
|
|
} else {
|
734 |
|
|
// if the parent can be assigned then do so
|
735 |
|
|
setActive(scope.$parent, parentActive = lastValue = scope.active);
|
736 |
|
|
}
|
737 |
|
|
}
|
738 |
|
|
return parentActive;
|
739 |
|
|
});
|
740 |
|
|
}
|
741 |
|
|
|
742 |
|
|
carouselCtrl.addSlide(scope, element);
|
743 |
|
|
//when the scope is destroyed then remove the slide from the current slides array
|
744 |
|
|
scope.$on('$destroy', function() {
|
745 |
|
|
carouselCtrl.removeSlide(scope);
|
746 |
|
|
});
|
747 |
|
|
|
748 |
|
|
scope.$watch('active', function(active) {
|
749 |
|
|
if (active) {
|
750 |
|
|
carouselCtrl.select(scope);
|
751 |
|
|
}
|
752 |
|
|
});
|
753 |
|
|
}
|
754 |
|
|
};
|
755 |
|
|
}]);
|
756 |
|
|
|
757 |
|
|
angular.module('ui.bootstrap.position', [])
|
758 |
|
|
|
759 |
|
|
/**
|
760 |
|
|
* A set of utility methods that can be use to retrieve position of DOM elements.
|
761 |
|
|
* It is meant to be used where we need to absolute-position DOM elements in
|
762 |
|
|
* relation to other, existing elements (this is the case for tooltips, popovers,
|
763 |
|
|
* typeahead suggestions etc.).
|
764 |
|
|
*/
|
765 |
|
|
.factory('$position', ['$document', '$window', function ($document, $window) {
|
766 |
|
|
|
767 |
|
|
function getStyle(el, cssprop) {
|
768 |
|
|
if (el.currentStyle) { //IE
|
769 |
|
|
return el.currentStyle[cssprop];
|
770 |
|
|
} else if ($window.getComputedStyle) {
|
771 |
|
|
return $window.getComputedStyle(el)[cssprop];
|
772 |
|
|
}
|
773 |
|
|
// finally try and get inline style
|
774 |
|
|
return el.style[cssprop];
|
775 |
|
|
}
|
776 |
|
|
|
777 |
|
|
/**
|
778 |
|
|
* Checks if a given element is statically positioned
|
779 |
|
|
* @param element - raw DOM element
|
780 |
|
|
*/
|
781 |
|
|
function isStaticPositioned(element) {
|
782 |
|
|
return (getStyle(element, "position") || 'static' ) === 'static';
|
783 |
|
|
}
|
784 |
|
|
|
785 |
|
|
/**
|
786 |
|
|
* returns the closest, non-statically positioned parentOffset of a given element
|
787 |
|
|
* @param element
|
788 |
|
|
*/
|
789 |
|
|
var parentOffsetEl = function (element) {
|
790 |
|
|
var docDomEl = $document[0];
|
791 |
|
|
var offsetParent = element.offsetParent || docDomEl;
|
792 |
|
|
while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
|
793 |
|
|
offsetParent = offsetParent.offsetParent;
|
794 |
|
|
}
|
795 |
|
|
return offsetParent || docDomEl;
|
796 |
|
|
};
|
797 |
|
|
|
798 |
|
|
return {
|
799 |
|
|
/**
|
800 |
|
|
* Provides read-only equivalent of jQuery's position function:
|
801 |
|
|
* http://api.jquery.com/position/
|
802 |
|
|
*/
|
803 |
|
|
position: function (element) {
|
804 |
|
|
var elBCR = this.offset(element);
|
805 |
|
|
var offsetParentBCR = { top: 0, left: 0 };
|
806 |
|
|
var offsetParentEl = parentOffsetEl(element[0]);
|
807 |
|
|
if (offsetParentEl != $document[0]) {
|
808 |
|
|
offsetParentBCR = this.offset(angular.element(offsetParentEl));
|
809 |
|
|
offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
|
810 |
|
|
offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
|
811 |
|
|
}
|
812 |
|
|
|
813 |
|
|
var boundingClientRect = element[0].getBoundingClientRect();
|
814 |
|
|
return {
|
815 |
|
|
width: boundingClientRect.width || element.prop('offsetWidth'),
|
816 |
|
|
height: boundingClientRect.height || element.prop('offsetHeight'),
|
817 |
|
|
top: elBCR.top - offsetParentBCR.top,
|
818 |
|
|
left: elBCR.left - offsetParentBCR.left
|
819 |
|
|
};
|
820 |
|
|
},
|
821 |
|
|
|
822 |
|
|
/**
|
823 |
|
|
* Provides read-only equivalent of jQuery's offset function:
|
824 |
|
|
* http://api.jquery.com/offset/
|
825 |
|
|
*/
|
826 |
|
|
offset: function (element) {
|
827 |
|
|
var boundingClientRect = element[0].getBoundingClientRect();
|
828 |
|
|
return {
|
829 |
|
|
width: boundingClientRect.width || element.prop('offsetWidth'),
|
830 |
|
|
height: boundingClientRect.height || element.prop('offsetHeight'),
|
831 |
|
|
top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
|
832 |
|
|
left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)
|
833 |
|
|
};
|
834 |
|
|
}
|
835 |
|
|
};
|
836 |
|
|
}]);
|
837 |
|
|
|
838 |
|
|
angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position'])
|
839 |
|
|
|
840 |
|
|
.constant('datepickerConfig', {
|
841 |
|
|
dayFormat: 'dd',
|
842 |
|
|
monthFormat: 'MMMM',
|
843 |
|
|
yearFormat: 'yyyy',
|
844 |
|
|
dayHeaderFormat: 'EEE',
|
845 |
|
|
dayTitleFormat: 'MMMM yyyy',
|
846 |
|
|
monthTitleFormat: 'yyyy',
|
847 |
|
|
showWeeks: true,
|
848 |
|
|
startingDay: 0,
|
849 |
|
|
yearRange: 20,
|
850 |
|
|
minDate: null,
|
851 |
|
|
maxDate: null
|
852 |
|
|
})
|
853 |
|
|
|
854 |
|
|
.controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function($scope, $attrs, dateFilter, dtConfig) {
|
855 |
|
|
var format = {
|
856 |
|
|
day: getValue($attrs.dayFormat, dtConfig.dayFormat),
|
857 |
|
|
month: getValue($attrs.monthFormat, dtConfig.monthFormat),
|
858 |
|
|
year: getValue($attrs.yearFormat, dtConfig.yearFormat),
|
859 |
|
|
dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat),
|
860 |
|
|
dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat),
|
861 |
|
|
monthTitle: getValue($attrs.monthTitleFormat, dtConfig.monthTitleFormat)
|
862 |
|
|
},
|
863 |
|
|
startingDay = getValue($attrs.startingDay, dtConfig.startingDay),
|
864 |
|
|
yearRange = getValue($attrs.yearRange, dtConfig.yearRange);
|
865 |
|
|
|
866 |
|
|
this.minDate = dtConfig.minDate ? new Date(dtConfig.minDate) : null;
|
867 |
|
|
this.maxDate = dtConfig.maxDate ? new Date(dtConfig.maxDate) : null;
|
868 |
|
|
|
869 |
|
|
function getValue(value, defaultValue) {
|
870 |
|
|
return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue;
|
871 |
|
|
}
|
872 |
|
|
|
873 |
|
|
function getDaysInMonth( year, month ) {
|
874 |
|
|
return new Date(year, month, 0).getDate();
|
875 |
|
|
}
|
876 |
|
|
|
877 |
|
|
function getDates(startDate, n) {
|
878 |
|
|
var dates = new Array(n);
|
879 |
|
|
var current = startDate, i = 0;
|
880 |
|
|
while (i < n) {
|
881 |
|
|
dates[i++] = new Date(current);
|
882 |
|
|
current.setDate( current.getDate() + 1 );
|
883 |
|
|
}
|
884 |
|
|
return dates;
|
885 |
|
|
}
|
886 |
|
|
|
887 |
|
|
function makeDate(date, format, isSelected, isSecondary) {
|
888 |
|
|
return { date: date, label: dateFilter(date, format), selected: !!isSelected, secondary: !!isSecondary };
|
889 |
|
|
}
|
890 |
|
|
|
891 |
|
|
this.modes = [
|
892 |
|
|
{
|
893 |
|
|
name: 'day',
|
894 |
|
|
getVisibleDates: function(date, selected) {
|
895 |
|
|
var year = date.getFullYear(), month = date.getMonth(), firstDayOfMonth = new Date(year, month, 1);
|
896 |
|
|
var difference = startingDay - firstDayOfMonth.getDay(),
|
897 |
|
|
numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
|
898 |
|
|
firstDate = new Date(firstDayOfMonth), numDates = 0;
|
899 |
|
|
|
900 |
|
|
if ( numDisplayedFromPreviousMonth > 0 ) {
|
901 |
|
|
firstDate.setDate( - numDisplayedFromPreviousMonth + 1 );
|
902 |
|
|
numDates += numDisplayedFromPreviousMonth; // Previous
|
903 |
|
|
}
|
904 |
|
|
numDates += getDaysInMonth(year, month + 1); // Current
|
905 |
|
|
numDates += (7 - numDates % 7) % 7; // Next
|
906 |
|
|
|
907 |
|
|
var days = getDates(firstDate, numDates), labels = new Array(7);
|
908 |
|
|
for (var i = 0; i < numDates; i ++) {
|
909 |
|
|
var dt = new Date(days[i]);
|
910 |
|
|
days[i] = makeDate(dt, format.day, (selected && selected.getDate() === dt.getDate() && selected.getMonth() === dt.getMonth() && selected.getFullYear() === dt.getFullYear()), dt.getMonth() !== month);
|
911 |
|
|
}
|
912 |
|
|
for (var j = 0; j < 7; j++) {
|
913 |
|
|
labels[j] = dateFilter(days[j].date, format.dayHeader);
|
914 |
|
|
}
|
915 |
|
|
return { objects: days, title: dateFilter(date, format.dayTitle), labels: labels };
|
916 |
|
|
},
|
917 |
|
|
compare: function(date1, date2) {
|
918 |
|
|
return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) );
|
919 |
|
|
},
|
920 |
|
|
split: 7,
|
921 |
|
|
step: { months: 1 }
|
922 |
|
|
},
|
923 |
|
|
{
|
924 |
|
|
name: 'month',
|
925 |
|
|
getVisibleDates: function(date, selected) {
|
926 |
|
|
var months = new Array(12), year = date.getFullYear();
|
927 |
|
|
for ( var i = 0; i < 12; i++ ) {
|
928 |
|
|
var dt = new Date(year, i, 1);
|
929 |
|
|
months[i] = makeDate(dt, format.month, (selected && selected.getMonth() === i && selected.getFullYear() === year));
|
930 |
|
|
}
|
931 |
|
|
return { objects: months, title: dateFilter(date, format.monthTitle) };
|
932 |
|
|
},
|
933 |
|
|
compare: function(date1, date2) {
|
934 |
|
|
return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() );
|
935 |
|
|
},
|
936 |
|
|
split: 3,
|
937 |
|
|
step: { years: 1 }
|
938 |
|
|
},
|
939 |
|
|
{
|
940 |
|
|
name: 'year',
|
941 |
|
|
getVisibleDates: function(date, selected) {
|
942 |
|
|
var years = new Array(yearRange), year = date.getFullYear(), startYear = parseInt((year - 1) / yearRange, 10) * yearRange + 1;
|
943 |
|
|
for ( var i = 0; i < yearRange; i++ ) {
|
944 |
|
|
var dt = new Date(startYear + i, 0, 1);
|
945 |
|
|
years[i] = makeDate(dt, format.year, (selected && selected.getFullYear() === dt.getFullYear()));
|
946 |
|
|
}
|
947 |
|
|
return { objects: years, title: [years[0].label, years[yearRange - 1].label].join(' - ') };
|
948 |
|
|
},
|
949 |
|
|
compare: function(date1, date2) {
|
950 |
|
|
return date1.getFullYear() - date2.getFullYear();
|
951 |
|
|
},
|
952 |
|
|
split: 5,
|
953 |
|
|
step: { years: yearRange }
|
954 |
|
|
}
|
955 |
|
|
];
|
956 |
|
|
|
957 |
|
|
this.isDisabled = function(date, mode) {
|
958 |
|
|
var currentMode = this.modes[mode || 0];
|
959 |
|
|
return ((this.minDate && currentMode.compare(date, this.minDate) < 0) || (this.maxDate && currentMode.compare(date, this.maxDate) > 0) || ($scope.dateDisabled && $scope.dateDisabled({date: date, mode: currentMode.name})));
|
960 |
|
|
};
|
961 |
|
|
}])
|
962 |
|
|
|
963 |
|
|
.directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter, $parse, datepickerConfig, $log) {
|
964 |
|
|
return {
|
965 |
|
|
restrict: 'EA',
|
966 |
|
|
replace: true,
|
967 |
|
|
templateUrl: 'template/datepicker/datepicker.html',
|
968 |
|
|
scope: {
|
969 |
|
|
dateDisabled: '&'
|
970 |
|
|
},
|
971 |
|
|
require: ['datepicker', '?^ngModel'],
|
972 |
|
|
controller: 'DatepickerController',
|
973 |
|
|
link: function(scope, element, attrs, ctrls) {
|
974 |
|
|
var datepickerCtrl = ctrls[0], ngModel = ctrls[1];
|
975 |
|
|
|
976 |
|
|
if (!ngModel) {
|
977 |
|
|
return; // do nothing if no ng-model
|
978 |
|
|
}
|
979 |
|
|
|
980 |
|
|
// Configuration parameters
|
981 |
|
|
var mode = 0, selected = new Date(), showWeeks = datepickerConfig.showWeeks;
|
982 |
|
|
|
983 |
|
|
if (attrs.showWeeks) {
|
984 |
|
|
scope.$parent.$watch($parse(attrs.showWeeks), function(value) {
|
985 |
|
|
showWeeks = !! value;
|
986 |
|
|
updateShowWeekNumbers();
|
987 |
|
|
});
|
988 |
|
|
} else {
|
989 |
|
|
updateShowWeekNumbers();
|
990 |
|
|
}
|
991 |
|
|
|
992 |
|
|
if (attrs.min) {
|
993 |
|
|
scope.$parent.$watch($parse(attrs.min), function(value) {
|
994 |
|
|
datepickerCtrl.minDate = value ? new Date(value) : null;
|
995 |
|
|
refill();
|
996 |
|
|
});
|
997 |
|
|
}
|
998 |
|
|
if (attrs.max) {
|
999 |
|
|
scope.$parent.$watch($parse(attrs.max), function(value) {
|
1000 |
|
|
datepickerCtrl.maxDate = value ? new Date(value) : null;
|
1001 |
|
|
refill();
|
1002 |
|
|
});
|
1003 |
|
|
}
|
1004 |
|
|
|
1005 |
|
|
function updateShowWeekNumbers() {
|
1006 |
|
|
scope.showWeekNumbers = mode === 0 && showWeeks;
|
1007 |
|
|
}
|
1008 |
|
|
|
1009 |
|
|
// Split array into smaller arrays
|
1010 |
|
|
function split(arr, size) {
|
1011 |
|
|
var arrays = [];
|
1012 |
|
|
while (arr.length > 0) {
|
1013 |
|
|
arrays.push(arr.splice(0, size));
|
1014 |
|
|
}
|
1015 |
|
|
return arrays;
|
1016 |
|
|
}
|
1017 |
|
|
|
1018 |
|
|
function refill( updateSelected ) {
|
1019 |
|
|
var date = null, valid = true;
|
1020 |
|
|
|
1021 |
|
|
if ( ngModel.$modelValue ) {
|
1022 |
|
|
date = new Date( ngModel.$modelValue );
|
1023 |
|
|
|
1024 |
|
|
if ( isNaN(date) ) {
|
1025 |
|
|
valid = false;
|
1026 |
|
|
$log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
|
1027 |
|
|
} else if ( updateSelected ) {
|
1028 |
|
|
selected = date;
|
1029 |
|
|
}
|
1030 |
|
|
}
|
1031 |
|
|
ngModel.$setValidity('date', valid);
|
1032 |
|
|
|
1033 |
|
|
var currentMode = datepickerCtrl.modes[mode], data = currentMode.getVisibleDates(selected, date);
|
1034 |
|
|
angular.forEach(data.objects, function(obj) {
|
1035 |
|
|
obj.disabled = datepickerCtrl.isDisabled(obj.date, mode);
|
1036 |
|
|
});
|
1037 |
|
|
|
1038 |
|
|
ngModel.$setValidity('date-disabled', (!date || !datepickerCtrl.isDisabled(date)));
|
1039 |
|
|
|
1040 |
|
|
scope.rows = split(data.objects, currentMode.split);
|
1041 |
|
|
scope.labels = data.labels || [];
|
1042 |
|
|
scope.title = data.title;
|
1043 |
|
|
}
|
1044 |
|
|
|
1045 |
|
|
function setMode(value) {
|
1046 |
|
|
mode = value;
|
1047 |
|
|
updateShowWeekNumbers();
|
1048 |
|
|
refill();
|
1049 |
|
|
}
|
1050 |
|
|
|
1051 |
|
|
ngModel.$render = function() {
|
1052 |
|
|
refill( true );
|
1053 |
|
|
};
|
1054 |
|
|
|
1055 |
|
|
scope.select = function( date ) {
|
1056 |
|
|
if ( mode === 0 ) {
|
1057 |
|
|
var dt = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0);
|
1058 |
|
|
dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() );
|
1059 |
|
|
ngModel.$setViewValue( dt );
|
1060 |
|
|
refill( true );
|
1061 |
|
|
} else {
|
1062 |
|
|
selected = date;
|
1063 |
|
|
setMode( mode - 1 );
|
1064 |
|
|
}
|
1065 |
|
|
};
|
1066 |
|
|
scope.move = function(direction) {
|
1067 |
|
|
var step = datepickerCtrl.modes[mode].step;
|
1068 |
|
|
selected.setMonth( selected.getMonth() + direction * (step.months || 0) );
|
1069 |
|
|
selected.setFullYear( selected.getFullYear() + direction * (step.years || 0) );
|
1070 |
|
|
refill();
|
1071 |
|
|
};
|
1072 |
|
|
scope.toggleMode = function() {
|
1073 |
|
|
setMode( (mode + 1) % datepickerCtrl.modes.length );
|
1074 |
|
|
};
|
1075 |
|
|
scope.getWeekNumber = function(row) {
|
1076 |
|
|
return ( mode === 0 && scope.showWeekNumbers && row.length === 7 ) ? getISO8601WeekNumber(row[0].date) : null;
|
1077 |
|
|
};
|
1078 |
|
|
|
1079 |
|
|
function getISO8601WeekNumber(date) {
|
1080 |
|
|
var checkDate = new Date(date);
|
1081 |
|
|
checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
|
1082 |
|
|
var time = checkDate.getTime();
|
1083 |
|
|
checkDate.setMonth(0); // Compare with Jan 1
|
1084 |
|
|
checkDate.setDate(1);
|
1085 |
|
|
return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
|
1086 |
|
|
}
|
1087 |
|
|
}
|
1088 |
|
|
};
|
1089 |
|
|
}])
|
1090 |
|
|
|
1091 |
|
|
.constant('datepickerPopupConfig', {
|
1092 |
|
|
dateFormat: 'yyyy-MM-dd',
|
1093 |
|
|
currentText: 'Today',
|
1094 |
|
|
toggleWeeksText: 'Weeks',
|
1095 |
|
|
clearText: 'Clear',
|
1096 |
|
|
closeText: 'Done',
|
1097 |
|
|
closeOnDateSelection: true,
|
1098 |
|
|
appendToBody: false,
|
1099 |
|
|
showButtonBar: true
|
1100 |
|
|
})
|
1101 |
|
|
|
1102 |
|
|
.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig', 'datepickerConfig',
|
1103 |
|
|
function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig, datepickerConfig) {
|
1104 |
|
|
return {
|
1105 |
|
|
restrict: 'EA',
|
1106 |
|
|
require: 'ngModel',
|
1107 |
|
|
link: function(originalScope, element, attrs, ngModel) {
|
1108 |
|
|
var scope = originalScope.$new(), // create a child scope so we are not polluting original one
|
1109 |
|
|
dateFormat,
|
1110 |
|
|
closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? originalScope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection,
|
1111 |
|
|
appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? originalScope.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
|
1112 |
|
|
|
1113 |
|
|
attrs.$observe('datepickerPopup', function(value) {
|
1114 |
|
|
dateFormat = value || datepickerPopupConfig.dateFormat;
|
1115 |
|
|
ngModel.$render();
|
1116 |
|
|
});
|
1117 |
|
|
|
1118 |
|
|
scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? originalScope.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
|
1119 |
|
|
|
1120 |
|
|
originalScope.$on('$destroy', function() {
|
1121 |
|
|
$popup.remove();
|
1122 |
|
|
scope.$destroy();
|
1123 |
|
|
});
|
1124 |
|
|
|
1125 |
|
|
attrs.$observe('currentText', function(text) {
|
1126 |
|
|
scope.currentText = angular.isDefined(text) ? text : datepickerPopupConfig.currentText;
|
1127 |
|
|
});
|
1128 |
|
|
attrs.$observe('toggleWeeksText', function(text) {
|
1129 |
|
|
scope.toggleWeeksText = angular.isDefined(text) ? text : datepickerPopupConfig.toggleWeeksText;
|
1130 |
|
|
});
|
1131 |
|
|
attrs.$observe('clearText', function(text) {
|
1132 |
|
|
scope.clearText = angular.isDefined(text) ? text : datepickerPopupConfig.clearText;
|
1133 |
|
|
});
|
1134 |
|
|
attrs.$observe('closeText', function(text) {
|
1135 |
|
|
scope.closeText = angular.isDefined(text) ? text : datepickerPopupConfig.closeText;
|
1136 |
|
|
});
|
1137 |
|
|
|
1138 |
|
|
var getIsOpen, setIsOpen;
|
1139 |
|
|
if ( attrs.isOpen ) {
|
1140 |
|
|
getIsOpen = $parse(attrs.isOpen);
|
1141 |
|
|
setIsOpen = getIsOpen.assign;
|
1142 |
|
|
|
1143 |
|
|
originalScope.$watch(getIsOpen, function updateOpen(value) {
|
1144 |
|
|
scope.isOpen = !! value;
|
1145 |
|
|
});
|
1146 |
|
|
}
|
1147 |
|
|
scope.isOpen = getIsOpen ? getIsOpen(originalScope) : false; // Initial state
|
1148 |
|
|
|
1149 |
|
|
function setOpen( value ) {
|
1150 |
|
|
if (setIsOpen) {
|
1151 |
|
|
setIsOpen(originalScope, !!value);
|
1152 |
|
|
} else {
|
1153 |
|
|
scope.isOpen = !!value;
|
1154 |
|
|
}
|
1155 |
|
|
}
|
1156 |
|
|
|
1157 |
|
|
var documentClickBind = function(event) {
|
1158 |
|
|
if (scope.isOpen && event.target !== element[0]) {
|
1159 |
|
|
scope.$apply(function() {
|
1160 |
|
|
setOpen(false);
|
1161 |
|
|
});
|
1162 |
|
|
}
|
1163 |
|
|
};
|
1164 |
|
|
|
1165 |
|
|
var elementFocusBind = function() {
|
1166 |
|
|
scope.$apply(function() {
|
1167 |
|
|
setOpen( true );
|
1168 |
|
|
});
|
1169 |
|
|
};
|
1170 |
|
|
|
1171 |
|
|
// popup element used to display calendar
|
1172 |
|
|
var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>');
|
1173 |
|
|
popupEl.attr({
|
1174 |
|
|
'ng-model': 'date',
|
1175 |
|
|
'ng-change': 'dateSelection()'
|
1176 |
|
|
});
|
1177 |
|
|
var datepickerEl = angular.element(popupEl.children()[0]),
|
1178 |
|
|
datepickerOptions = {};
|
1179 |
|
|
if (attrs.datepickerOptions) {
|
1180 |
|
|
datepickerOptions = originalScope.$eval(attrs.datepickerOptions);
|
1181 |
|
|
datepickerEl.attr(angular.extend({}, datepickerOptions));
|
1182 |
|
|
}
|
1183 |
|
|
|
1184 |
|
|
// TODO: reverse from dateFilter string to Date object
|
1185 |
|
|
function parseDate(viewValue) {
|
1186 |
|
|
if (!viewValue) {
|
1187 |
|
|
ngModel.$setValidity('date', true);
|
1188 |
|
|
return null;
|
1189 |
|
|
} else if (angular.isDate(viewValue)) {
|
1190 |
|
|
ngModel.$setValidity('date', true);
|
1191 |
|
|
return viewValue;
|
1192 |
|
|
} else if (angular.isString(viewValue)) {
|
1193 |
|
|
var date = new Date(viewValue);
|
1194 |
|
|
if (isNaN(date)) {
|
1195 |
|
|
ngModel.$setValidity('date', false);
|
1196 |
|
|
return undefined;
|
1197 |
|
|
} else {
|
1198 |
|
|
ngModel.$setValidity('date', true);
|
1199 |
|
|
return date;
|
1200 |
|
|
}
|
1201 |
|
|
} else {
|
1202 |
|
|
ngModel.$setValidity('date', false);
|
1203 |
|
|
return undefined;
|
1204 |
|
|
}
|
1205 |
|
|
}
|
1206 |
|
|
ngModel.$parsers.unshift(parseDate);
|
1207 |
|
|
|
1208 |
|
|
// Inner change
|
1209 |
|
|
scope.dateSelection = function(dt) {
|
1210 |
|
|
if (angular.isDefined(dt)) {
|
1211 |
|
|
scope.date = dt;
|
1212 |
|
|
}
|
1213 |
|
|
ngModel.$setViewValue(scope.date);
|
1214 |
|
|
ngModel.$render();
|
1215 |
|
|
|
1216 |
|
|
if (closeOnDateSelection) {
|
1217 |
|
|
setOpen( false );
|
1218 |
|
|
}
|
1219 |
|
|
};
|
1220 |
|
|
|
1221 |
|
|
element.bind('input change keyup', function() {
|
1222 |
|
|
scope.$apply(function() {
|
1223 |
|
|
scope.date = ngModel.$modelValue;
|
1224 |
|
|
});
|
1225 |
|
|
});
|
1226 |
|
|
|
1227 |
|
|
// Outter change
|
1228 |
|
|
ngModel.$render = function() {
|
1229 |
|
|
var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : '';
|
1230 |
|
|
element.val(date);
|
1231 |
|
|
scope.date = ngModel.$modelValue;
|
1232 |
|
|
};
|
1233 |
|
|
|
1234 |
|
|
function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) {
|
1235 |
|
|
if (attribute) {
|
1236 |
|
|
originalScope.$watch($parse(attribute), function(value){
|
1237 |
|
|
scope[scopeProperty] = value;
|
1238 |
|
|
});
|
1239 |
|
|
datepickerEl.attr(datepickerAttribute || scopeProperty, scopeProperty);
|
1240 |
|
|
}
|
1241 |
|
|
}
|
1242 |
|
|
addWatchableAttribute(attrs.min, 'min');
|
1243 |
|
|
addWatchableAttribute(attrs.max, 'max');
|
1244 |
|
|
if (attrs.showWeeks) {
|
1245 |
|
|
addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks');
|
1246 |
|
|
} else {
|
1247 |
|
|
scope.showWeeks = 'show-weeks' in datepickerOptions ? datepickerOptions['show-weeks'] : datepickerConfig.showWeeks;
|
1248 |
|
|
datepickerEl.attr('show-weeks', 'showWeeks');
|
1249 |
|
|
}
|
1250 |
|
|
if (attrs.dateDisabled) {
|
1251 |
|
|
datepickerEl.attr('date-disabled', attrs.dateDisabled);
|
1252 |
|
|
}
|
1253 |
|
|
|
1254 |
|
|
function updatePosition() {
|
1255 |
|
|
scope.position = appendToBody ? $position.offset(element) : $position.position(element);
|
1256 |
|
|
scope.position.top = scope.position.top + element.prop('offsetHeight');
|
1257 |
|
|
}
|
1258 |
|
|
|
1259 |
|
|
var documentBindingInitialized = false, elementFocusInitialized = false;
|
1260 |
|
|
scope.$watch('isOpen', function(value) {
|
1261 |
|
|
if (value) {
|
1262 |
|
|
updatePosition();
|
1263 |
|
|
$document.bind('click', documentClickBind);
|
1264 |
|
|
if(elementFocusInitialized) {
|
1265 |
|
|
element.unbind('focus', elementFocusBind);
|
1266 |
|
|
}
|
1267 |
|
|
element[0].focus();
|
1268 |
|
|
documentBindingInitialized = true;
|
1269 |
|
|
} else {
|
1270 |
|
|
if(documentBindingInitialized) {
|
1271 |
|
|
$document.unbind('click', documentClickBind);
|
1272 |
|
|
}
|
1273 |
|
|
element.bind('focus', elementFocusBind);
|
1274 |
|
|
elementFocusInitialized = true;
|
1275 |
|
|
}
|
1276 |
|
|
|
1277 |
|
|
if ( setIsOpen ) {
|
1278 |
|
|
setIsOpen(originalScope, value);
|
1279 |
|
|
}
|
1280 |
|
|
});
|
1281 |
|
|
|
1282 |
|
|
scope.today = function() {
|
1283 |
|
|
scope.dateSelection(new Date());
|
1284 |
|
|
};
|
1285 |
|
|
scope.clear = function() {
|
1286 |
|
|
scope.dateSelection(null);
|
1287 |
|
|
};
|
1288 |
|
|
|
1289 |
|
|
var $popup = $compile(popupEl)(scope);
|
1290 |
|
|
if ( appendToBody ) {
|
1291 |
|
|
$document.find('body').append($popup);
|
1292 |
|
|
} else {
|
1293 |
|
|
element.after($popup);
|
1294 |
|
|
}
|
1295 |
|
|
}
|
1296 |
|
|
};
|
1297 |
|
|
}])
|
1298 |
|
|
|
1299 |
|
|
.directive('datepickerPopupWrap', function() {
|
1300 |
|
|
return {
|
1301 |
|
|
restrict:'EA',
|
1302 |
|
|
replace: true,
|
1303 |
|
|
transclude: true,
|
1304 |
|
|
templateUrl: 'template/datepicker/popup.html',
|
1305 |
|
|
link:function (scope, element, attrs) {
|
1306 |
|
|
element.bind('click', function(event) {
|
1307 |
|
|
event.preventDefault();
|
1308 |
|
|
event.stopPropagation();
|
1309 |
|
|
});
|
1310 |
|
|
}
|
1311 |
|
|
};
|
1312 |
|
|
});
|
1313 |
|
|
|
1314 |
|
|
/*
|
1315 |
|
|
* dropdownToggle - Provides dropdown menu functionality in place of bootstrap js
|
1316 |
|
|
* @restrict class or attribute
|
1317 |
|
|
* @example:
|
1318 |
|
|
<li class="dropdown">
|
1319 |
|
|
<a class="dropdown-toggle">My Dropdown Menu</a>
|
1320 |
|
|
<ul class="dropdown-menu">
|
1321 |
|
|
<li ng-repeat="choice in dropChoices">
|
1322 |
|
|
<a ng-href="{{choice.href}}">{{choice.text}}</a>
|
1323 |
|
|
</li>
|
1324 |
|
|
</ul>
|
1325 |
|
|
</li>
|
1326 |
|
|
*/
|
1327 |
|
|
|
1328 |
|
|
angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document, $location) {
|
1329 |
|
|
var openElement = null,
|
1330 |
|
|
closeMenu = angular.noop;
|
1331 |
|
|
return {
|
1332 |
|
|
restrict: 'CA',
|
1333 |
|
|
link: function(scope, element, attrs) {
|
1334 |
|
|
scope.$watch('$location.path', function() { closeMenu(); });
|
1335 |
|
|
element.parent().bind('click', function() { closeMenu(); });
|
1336 |
|
|
element.bind('click', function (event) {
|
1337 |
|
|
|
1338 |
|
|
var elementWasOpen = (element === openElement);
|
1339 |
|
|
|
1340 |
|
|
event.preventDefault();
|
1341 |
|
|
event.stopPropagation();
|
1342 |
|
|
|
1343 |
|
|
if (!!openElement) {
|
1344 |
|
|
closeMenu();
|
1345 |
|
|
}
|
1346 |
|
|
|
1347 |
|
|
if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {
|
1348 |
|
|
element.parent().addClass('open');
|
1349 |
|
|
openElement = element;
|
1350 |
|
|
closeMenu = function (event) {
|
1351 |
|
|
if (event) {
|
1352 |
|
|
event.preventDefault();
|
1353 |
|
|
event.stopPropagation();
|
1354 |
|
|
}
|
1355 |
|
|
$document.unbind('click', closeMenu);
|
1356 |
|
|
element.parent().removeClass('open');
|
1357 |
|
|
closeMenu = angular.noop;
|
1358 |
|
|
openElement = null;
|
1359 |
|
|
};
|
1360 |
|
|
$document.bind('click', closeMenu);
|
1361 |
|
|
}
|
1362 |
|
|
});
|
1363 |
|
|
}
|
1364 |
|
|
};
|
1365 |
|
|
}]);
|
1366 |
|
|
|
1367 |
|
|
angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
|
1368 |
|
|
|
1369 |
|
|
/**
|
1370 |
|
|
* A helper, internal data structure that acts as a map but also allows getting / removing
|
1371 |
|
|
* elements in the LIFO order
|
1372 |
|
|
*/
|
1373 |
|
|
.factory('$$stackedMap', function () {
|
1374 |
|
|
return {
|
1375 |
|
|
createNew: function () {
|
1376 |
|
|
var stack = [];
|
1377 |
|
|
|
1378 |
|
|
return {
|
1379 |
|
|
add: function (key, value) {
|
1380 |
|
|
stack.push({
|
1381 |
|
|
key: key,
|
1382 |
|
|
value: value
|
1383 |
|
|
});
|
1384 |
|
|
},
|
1385 |
|
|
get: function (key) {
|
1386 |
|
|
for (var i = 0; i < stack.length; i++) {
|
1387 |
|
|
if (key == stack[i].key) {
|
1388 |
|
|
return stack[i];
|
1389 |
|
|
}
|
1390 |
|
|
}
|
1391 |
|
|
},
|
1392 |
|
|
keys: function() {
|
1393 |
|
|
var keys = [];
|
1394 |
|
|
for (var i = 0; i < stack.length; i++) {
|
1395 |
|
|
keys.push(stack[i].key);
|
1396 |
|
|
}
|
1397 |
|
|
return keys;
|
1398 |
|
|
},
|
1399 |
|
|
top: function () {
|
1400 |
|
|
return stack[stack.length - 1];
|
1401 |
|
|
},
|
1402 |
|
|
remove: function (key) {
|
1403 |
|
|
var idx = -1;
|
1404 |
|
|
for (var i = 0; i < stack.length; i++) {
|
1405 |
|
|
if (key == stack[i].key) {
|
1406 |
|
|
idx = i;
|
1407 |
|
|
break;
|
1408 |
|
|
}
|
1409 |
|
|
}
|
1410 |
|
|
return stack.splice(idx, 1)[0];
|
1411 |
|
|
},
|
1412 |
|
|
removeTop: function () {
|
1413 |
|
|
return stack.splice(stack.length - 1, 1)[0];
|
1414 |
|
|
},
|
1415 |
|
|
length: function () {
|
1416 |
|
|
return stack.length;
|
1417 |
|
|
}
|
1418 |
|
|
};
|
1419 |
|
|
}
|
1420 |
|
|
};
|
1421 |
|
|
})
|
1422 |
|
|
|
1423 |
|
|
/**
|
1424 |
|
|
* A helper directive for the $modal service. It creates a backdrop element.
|
1425 |
|
|
*/
|
1426 |
|
|
.directive('modalBackdrop', ['$timeout', function ($timeout) {
|
1427 |
|
|
return {
|
1428 |
|
|
restrict: 'EA',
|
1429 |
|
|
replace: true,
|
1430 |
|
|
templateUrl: 'template/modal/backdrop.html',
|
1431 |
|
|
link: function (scope) {
|
1432 |
|
|
|
1433 |
|
|
scope.animate = false;
|
1434 |
|
|
|
1435 |
|
|
//trigger CSS transitions
|
1436 |
|
|
$timeout(function () {
|
1437 |
|
|
scope.animate = true;
|
1438 |
|
|
});
|
1439 |
|
|
}
|
1440 |
|
|
};
|
1441 |
|
|
}])
|
1442 |
|
|
|
1443 |
|
|
.directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
|
1444 |
|
|
return {
|
1445 |
|
|
restrict: 'EA',
|
1446 |
|
|
scope: {
|
1447 |
|
|
index: '@',
|
1448 |
|
|
animate: '='
|
1449 |
|
|
},
|
1450 |
|
|
replace: true,
|
1451 |
|
|
transclude: true,
|
1452 |
|
|
templateUrl: 'template/modal/window.html',
|
1453 |
|
|
link: function (scope, element, attrs) {
|
1454 |
|
|
scope.windowClass = attrs.windowClass || '';
|
1455 |
|
|
|
1456 |
|
|
$timeout(function () {
|
1457 |
|
|
// trigger CSS transitions
|
1458 |
|
|
scope.animate = true;
|
1459 |
|
|
// focus a freshly-opened modal
|
1460 |
|
|
element[0].focus();
|
1461 |
|
|
});
|
1462 |
|
|
|
1463 |
|
|
scope.close = function (evt) {
|
1464 |
|
|
var modal = $modalStack.getTop();
|
1465 |
|
|
if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) {
|
1466 |
|
|
evt.preventDefault();
|
1467 |
|
|
evt.stopPropagation();
|
1468 |
|
|
$modalStack.dismiss(modal.key, 'backdrop click');
|
1469 |
|
|
}
|
1470 |
|
|
};
|
1471 |
|
|
}
|
1472 |
|
|
};
|
1473 |
|
|
}])
|
1474 |
|
|
|
1475 |
|
|
.factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap',
|
1476 |
|
|
function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) {
|
1477 |
|
|
|
1478 |
|
|
var OPENED_MODAL_CLASS = 'modal-open';
|
1479 |
|
|
|
1480 |
|
|
var backdropDomEl, backdropScope;
|
1481 |
|
|
var openedWindows = $$stackedMap.createNew();
|
1482 |
|
|
var $modalStack = {};
|
1483 |
|
|
|
1484 |
|
|
function backdropIndex() {
|
1485 |
|
|
var topBackdropIndex = -1;
|
1486 |
|
|
var opened = openedWindows.keys();
|
1487 |
|
|
for (var i = 0; i < opened.length; i++) {
|
1488 |
|
|
if (openedWindows.get(opened[i]).value.backdrop) {
|
1489 |
|
|
topBackdropIndex = i;
|
1490 |
|
|
}
|
1491 |
|
|
}
|
1492 |
|
|
return topBackdropIndex;
|
1493 |
|
|
}
|
1494 |
|
|
|
1495 |
|
|
$rootScope.$watch(backdropIndex, function(newBackdropIndex){
|
1496 |
|
|
if (backdropScope) {
|
1497 |
|
|
backdropScope.index = newBackdropIndex;
|
1498 |
|
|
}
|
1499 |
|
|
});
|
1500 |
|
|
|
1501 |
|
|
function removeModalWindow(modalInstance) {
|
1502 |
|
|
|
1503 |
|
|
var body = $document.find('body').eq(0);
|
1504 |
|
|
var modalWindow = openedWindows.get(modalInstance).value;
|
1505 |
|
|
|
1506 |
|
|
//clean up the stack
|
1507 |
|
|
openedWindows.remove(modalInstance);
|
1508 |
|
|
|
1509 |
|
|
//remove window DOM element
|
1510 |
|
|
removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, checkRemoveBackdrop);
|
1511 |
|
|
body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0);
|
1512 |
|
|
}
|
1513 |
|
|
|
1514 |
|
|
function checkRemoveBackdrop() {
|
1515 |
|
|
//remove backdrop if no longer needed
|
1516 |
|
|
if (backdropDomEl && backdropIndex() == -1) {
|
1517 |
|
|
var backdropScopeRef = backdropScope;
|
1518 |
|
|
removeAfterAnimate(backdropDomEl, backdropScope, 150, function () {
|
1519 |
|
|
backdropScopeRef.$destroy();
|
1520 |
|
|
backdropScopeRef = null;
|
1521 |
|
|
});
|
1522 |
|
|
backdropDomEl = undefined;
|
1523 |
|
|
backdropScope = undefined;
|
1524 |
|
|
}
|
1525 |
|
|
}
|
1526 |
|
|
|
1527 |
|
|
function removeAfterAnimate(domEl, scope, emulateTime, done) {
|
1528 |
|
|
// Closing animation
|
1529 |
|
|
scope.animate = false;
|
1530 |
|
|
|
1531 |
|
|
var transitionEndEventName = $transition.transitionEndEventName;
|
1532 |
|
|
if (transitionEndEventName) {
|
1533 |
|
|
// transition out
|
1534 |
|
|
var timeout = $timeout(afterAnimating, emulateTime);
|
1535 |
|
|
|
1536 |
|
|
domEl.bind(transitionEndEventName, function () {
|
1537 |
|
|
$timeout.cancel(timeout);
|
1538 |
|
|
afterAnimating();
|
1539 |
|
|
scope.$apply();
|
1540 |
|
|
});
|
1541 |
|
|
} else {
|
1542 |
|
|
// Ensure this call is async
|
1543 |
|
|
$timeout(afterAnimating, 0);
|
1544 |
|
|
}
|
1545 |
|
|
|
1546 |
|
|
function afterAnimating() {
|
1547 |
|
|
if (afterAnimating.done) {
|
1548 |
|
|
return;
|
1549 |
|
|
}
|
1550 |
|
|
afterAnimating.done = true;
|
1551 |
|
|
|
1552 |
|
|
domEl.remove();
|
1553 |
|
|
if (done) {
|
1554 |
|
|
done();
|
1555 |
|
|
}
|
1556 |
|
|
}
|
1557 |
|
|
}
|
1558 |
|
|
|
1559 |
|
|
$document.bind('keydown', function (evt) {
|
1560 |
|
|
var modal;
|
1561 |
|
|
|
1562 |
|
|
if (evt.which === 27) {
|
1563 |
|
|
modal = openedWindows.top();
|
1564 |
|
|
if (modal && modal.value.keyboard) {
|
1565 |
|
|
$rootScope.$apply(function () {
|
1566 |
|
|
$modalStack.dismiss(modal.key);
|
1567 |
|
|
});
|
1568 |
|
|
}
|
1569 |
|
|
}
|
1570 |
|
|
});
|
1571 |
|
|
|
1572 |
|
|
$modalStack.open = function (modalInstance, modal) {
|
1573 |
|
|
|
1574 |
|
|
openedWindows.add(modalInstance, {
|
1575 |
|
|
deferred: modal.deferred,
|
1576 |
|
|
modalScope: modal.scope,
|
1577 |
|
|
backdrop: modal.backdrop,
|
1578 |
|
|
keyboard: modal.keyboard
|
1579 |
|
|
});
|
1580 |
|
|
|
1581 |
|
|
var body = $document.find('body').eq(0),
|
1582 |
|
|
currBackdropIndex = backdropIndex();
|
1583 |
|
|
|
1584 |
|
|
if (currBackdropIndex >= 0 && !backdropDomEl) {
|
1585 |
|
|
backdropScope = $rootScope.$new(true);
|
1586 |
|
|
backdropScope.index = currBackdropIndex;
|
1587 |
|
|
backdropDomEl = $compile('<div modal-backdrop></div>')(backdropScope);
|
1588 |
|
|
body.append(backdropDomEl);
|
1589 |
|
|
}
|
1590 |
|
|
|
1591 |
|
|
var angularDomEl = angular.element('<div modal-window></div>');
|
1592 |
|
|
angularDomEl.attr('window-class', modal.windowClass);
|
1593 |
|
|
angularDomEl.attr('index', openedWindows.length() - 1);
|
1594 |
|
|
angularDomEl.attr('animate', 'animate');
|
1595 |
|
|
angularDomEl.html(modal.content);
|
1596 |
|
|
|
1597 |
|
|
var modalDomEl = $compile(angularDomEl)(modal.scope);
|
1598 |
|
|
openedWindows.top().value.modalDomEl = modalDomEl;
|
1599 |
|
|
body.append(modalDomEl);
|
1600 |
|
|
body.addClass(OPENED_MODAL_CLASS);
|
1601 |
|
|
};
|
1602 |
|
|
|
1603 |
|
|
$modalStack.close = function (modalInstance, result) {
|
1604 |
|
|
var modalWindow = openedWindows.get(modalInstance).value;
|
1605 |
|
|
if (modalWindow) {
|
1606 |
|
|
modalWindow.deferred.resolve(result);
|
1607 |
|
|
removeModalWindow(modalInstance);
|
1608 |
|
|
}
|
1609 |
|
|
};
|
1610 |
|
|
|
1611 |
|
|
$modalStack.dismiss = function (modalInstance, reason) {
|
1612 |
|
|
var modalWindow = openedWindows.get(modalInstance).value;
|
1613 |
|
|
if (modalWindow) {
|
1614 |
|
|
modalWindow.deferred.reject(reason);
|
1615 |
|
|
removeModalWindow(modalInstance);
|
1616 |
|
|
}
|
1617 |
|
|
};
|
1618 |
|
|
|
1619 |
|
|
$modalStack.dismissAll = function (reason) {
|
1620 |
|
|
var topModal = this.getTop();
|
1621 |
|
|
while (topModal) {
|
1622 |
|
|
this.dismiss(topModal.key, reason);
|
1623 |
|
|
topModal = this.getTop();
|
1624 |
|
|
}
|
1625 |
|
|
};
|
1626 |
|
|
|
1627 |
|
|
$modalStack.getTop = function () {
|
1628 |
|
|
return openedWindows.top();
|
1629 |
|
|
};
|
1630 |
|
|
|
1631 |
|
|
return $modalStack;
|
1632 |
|
|
}])
|
1633 |
|
|
|
1634 |
|
|
.provider('$modal', function () {
|
1635 |
|
|
|
1636 |
|
|
var $modalProvider = {
|
1637 |
|
|
options: {
|
1638 |
|
|
backdrop: true, //can be also false or 'static'
|
1639 |
|
|
keyboard: true
|
1640 |
|
|
},
|
1641 |
|
|
$get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
|
1642 |
|
|
function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
|
1643 |
|
|
|
1644 |
|
|
var $modal = {};
|
1645 |
|
|
|
1646 |
|
|
function getTemplatePromise(options) {
|
1647 |
|
|
return options.template ? $q.when(options.template) :
|
1648 |
|
|
$http.get(options.templateUrl, {cache: $templateCache}).then(function (result) {
|
1649 |
|
|
return result.data;
|
1650 |
|
|
});
|
1651 |
|
|
}
|
1652 |
|
|
|
1653 |
|
|
function getResolvePromises(resolves) {
|
1654 |
|
|
var promisesArr = [];
|
1655 |
|
|
angular.forEach(resolves, function (value, key) {
|
1656 |
|
|
if (angular.isFunction(value) || angular.isArray(value)) {
|
1657 |
|
|
promisesArr.push($q.when($injector.invoke(value)));
|
1658 |
|
|
}
|
1659 |
|
|
});
|
1660 |
|
|
return promisesArr;
|
1661 |
|
|
}
|
1662 |
|
|
|
1663 |
|
|
$modal.open = function (modalOptions) {
|
1664 |
|
|
|
1665 |
|
|
var modalResultDeferred = $q.defer();
|
1666 |
|
|
var modalOpenedDeferred = $q.defer();
|
1667 |
|
|
|
1668 |
|
|
//prepare an instance of a modal to be injected into controllers and returned to a caller
|
1669 |
|
|
var modalInstance = {
|
1670 |
|
|
result: modalResultDeferred.promise,
|
1671 |
|
|
opened: modalOpenedDeferred.promise,
|
1672 |
|
|
close: function (result) {
|
1673 |
|
|
$modalStack.close(modalInstance, result);
|
1674 |
|
|
},
|
1675 |
|
|
dismiss: function (reason) {
|
1676 |
|
|
$modalStack.dismiss(modalInstance, reason);
|
1677 |
|
|
}
|
1678 |
|
|
};
|
1679 |
|
|
|
1680 |
|
|
//merge and clean up options
|
1681 |
|
|
modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
|
1682 |
|
|
modalOptions.resolve = modalOptions.resolve || {};
|
1683 |
|
|
|
1684 |
|
|
//verify options
|
1685 |
|
|
if (!modalOptions.template && !modalOptions.templateUrl) {
|
1686 |
|
|
throw new Error('One of template or templateUrl options is required.');
|
1687 |
|
|
}
|
1688 |
|
|
|
1689 |
|
|
var templateAndResolvePromise =
|
1690 |
|
|
$q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
|
1691 |
|
|
|
1692 |
|
|
|
1693 |
|
|
templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
|
1694 |
|
|
|
1695 |
|
|
var modalScope = (modalOptions.scope || $rootScope).$new();
|
1696 |
|
|
modalScope.$close = modalInstance.close;
|
1697 |
|
|
modalScope.$dismiss = modalInstance.dismiss;
|
1698 |
|
|
|
1699 |
|
|
var ctrlInstance, ctrlLocals = {};
|
1700 |
|
|
var resolveIter = 1;
|
1701 |
|
|
|
1702 |
|
|
//controllers
|
1703 |
|
|
if (modalOptions.controller) {
|
1704 |
|
|
ctrlLocals.$scope = modalScope;
|
1705 |
|
|
ctrlLocals.$modalInstance = modalInstance;
|
1706 |
|
|
angular.forEach(modalOptions.resolve, function (value, key) {
|
1707 |
|
|
ctrlLocals[key] = tplAndVars[resolveIter++];
|
1708 |
|
|
});
|
1709 |
|
|
|
1710 |
|
|
ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
|
1711 |
|
|
}
|
1712 |
|
|
|
1713 |
|
|
$modalStack.open(modalInstance, {
|
1714 |
|
|
scope: modalScope,
|
1715 |
|
|
deferred: modalResultDeferred,
|
1716 |
|
|
content: tplAndVars[0],
|
1717 |
|
|
backdrop: modalOptions.backdrop,
|
1718 |
|
|
keyboard: modalOptions.keyboard,
|
1719 |
|
|
windowClass: modalOptions.windowClass
|
1720 |
|
|
});
|
1721 |
|
|
|
1722 |
|
|
}, function resolveError(reason) {
|
1723 |
|
|
modalResultDeferred.reject(reason);
|
1724 |
|
|
});
|
1725 |
|
|
|
1726 |
|
|
templateAndResolvePromise.then(function () {
|
1727 |
|
|
modalOpenedDeferred.resolve(true);
|
1728 |
|
|
}, function () {
|
1729 |
|
|
modalOpenedDeferred.reject(false);
|
1730 |
|
|
});
|
1731 |
|
|
|
1732 |
|
|
return modalInstance;
|
1733 |
|
|
};
|
1734 |
|
|
|
1735 |
|
|
return $modal;
|
1736 |
|
|
}]
|
1737 |
|
|
};
|
1738 |
|
|
|
1739 |
|
|
return $modalProvider;
|
1740 |
|
|
});
|
1741 |
|
|
|
1742 |
|
|
angular.module('ui.bootstrap.pagination', [])
|
1743 |
|
|
|
1744 |
|
|
.controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) {
|
1745 |
|
|
var self = this,
|
1746 |
|
|
setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
|
1747 |
|
|
|
1748 |
|
|
this.init = function(defaultItemsPerPage) {
|
1749 |
|
|
if ($attrs.itemsPerPage) {
|
1750 |
|
|
$scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
|
1751 |
|
|
self.itemsPerPage = parseInt(value, 10);
|
1752 |
|
|
$scope.totalPages = self.calculateTotalPages();
|
1753 |
|
|
});
|
1754 |
|
|
} else {
|
1755 |
|
|
this.itemsPerPage = defaultItemsPerPage;
|
1756 |
|
|
}
|
1757 |
|
|
};
|
1758 |
|
|
|
1759 |
|
|
this.noPrevious = function() {
|
1760 |
|
|
return this.page === 1;
|
1761 |
|
|
};
|
1762 |
|
|
this.noNext = function() {
|
1763 |
|
|
return this.page === $scope.totalPages;
|
1764 |
|
|
};
|
1765 |
|
|
|
1766 |
|
|
this.isActive = function(page) {
|
1767 |
|
|
return this.page === page;
|
1768 |
|
|
};
|
1769 |
|
|
|
1770 |
|
|
this.calculateTotalPages = function() {
|
1771 |
|
|
var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
|
1772 |
|
|
return Math.max(totalPages || 0, 1);
|
1773 |
|
|
};
|
1774 |
|
|
|
1775 |
|
|
this.getAttributeValue = function(attribute, defaultValue, interpolate) {
|
1776 |
|
|
return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
|
1777 |
|
|
};
|
1778 |
|
|
|
1779 |
|
|
this.render = function() {
|
1780 |
|
|
this.page = parseInt($scope.page, 10) || 1;
|
1781 |
|
|
if (this.page > 0 && this.page <= $scope.totalPages) {
|
1782 |
|
|
$scope.pages = this.getPages(this.page, $scope.totalPages);
|
1783 |
|
|
}
|
1784 |
|
|
};
|
1785 |
|
|
|
1786 |
|
|
$scope.selectPage = function(page) {
|
1787 |
|
|
if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) {
|
1788 |
|
|
$scope.page = page;
|
1789 |
|
|
$scope.onSelectPage({ page: page });
|
1790 |
|
|
}
|
1791 |
|
|
};
|
1792 |
|
|
|
1793 |
|
|
$scope.$watch('page', function() {
|
1794 |
|
|
self.render();
|
1795 |
|
|
});
|
1796 |
|
|
|
1797 |
|
|
$scope.$watch('totalItems', function() {
|
1798 |
|
|
$scope.totalPages = self.calculateTotalPages();
|
1799 |
|
|
});
|
1800 |
|
|
|
1801 |
|
|
$scope.$watch('totalPages', function(value) {
|
1802 |
|
|
setNumPages($scope.$parent, value); // Readonly variable
|
1803 |
|
|
|
1804 |
|
|
if ( self.page > value ) {
|
1805 |
|
|
$scope.selectPage(value);
|
1806 |
|
|
} else {
|
1807 |
|
|
self.render();
|
1808 |
|
|
}
|
1809 |
|
|
});
|
1810 |
|
|
}])
|
1811 |
|
|
|
1812 |
|
|
.constant('paginationConfig', {
|
1813 |
|
|
itemsPerPage: 10,
|
1814 |
|
|
boundaryLinks: false,
|
1815 |
|
|
directionLinks: true,
|
1816 |
|
|
firstText: 'First',
|
1817 |
|
|
previousText: 'Previous',
|
1818 |
|
|
nextText: 'Next',
|
1819 |
|
|
lastText: 'Last',
|
1820 |
|
|
rotate: true
|
1821 |
|
|
})
|
1822 |
|
|
|
1823 |
|
|
.directive('pagination', ['$parse', 'paginationConfig', function($parse, config) {
|
1824 |
|
|
return {
|
1825 |
|
|
restrict: 'EA',
|
1826 |
|
|
scope: {
|
1827 |
|
|
page: '=',
|
1828 |
|
|
totalItems: '=',
|
1829 |
|
|
onSelectPage:' &'
|
1830 |
|
|
},
|
1831 |
|
|
controller: 'PaginationController',
|
1832 |
|
|
templateUrl: 'template/pagination/pagination.html',
|
1833 |
|
|
replace: true,
|
1834 |
|
|
link: function(scope, element, attrs, paginationCtrl) {
|
1835 |
|
|
|
1836 |
|
|
// Setup configuration parameters
|
1837 |
|
|
var maxSize,
|
1838 |
|
|
boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ),
|
1839 |
|
|
directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ),
|
1840 |
|
|
firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true),
|
1841 |
|
|
previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
|
1842 |
|
|
nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
|
1843 |
|
|
lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true),
|
1844 |
|
|
rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate);
|
1845 |
|
|
|
1846 |
|
|
paginationCtrl.init(config.itemsPerPage);
|
1847 |
|
|
|
1848 |
|
|
if (attrs.maxSize) {
|
1849 |
|
|
scope.$parent.$watch($parse(attrs.maxSize), function(value) {
|
1850 |
|
|
maxSize = parseInt(value, 10);
|
1851 |
|
|
paginationCtrl.render();
|
1852 |
|
|
});
|
1853 |
|
|
}
|
1854 |
|
|
|
1855 |
|
|
// Create page object used in template
|
1856 |
|
|
function makePage(number, text, isActive, isDisabled) {
|
1857 |
|
|
return {
|
1858 |
|
|
number: number,
|
1859 |
|
|
text: text,
|
1860 |
|
|
active: isActive,
|
1861 |
|
|
disabled: isDisabled
|
1862 |
|
|
};
|
1863 |
|
|
}
|
1864 |
|
|
|
1865 |
|
|
paginationCtrl.getPages = function(currentPage, totalPages) {
|
1866 |
|
|
var pages = [];
|
1867 |
|
|
|
1868 |
|
|
// Default page limits
|
1869 |
|
|
var startPage = 1, endPage = totalPages;
|
1870 |
|
|
var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages );
|
1871 |
|
|
|
1872 |
|
|
// recompute if maxSize
|
1873 |
|
|
if ( isMaxSized ) {
|
1874 |
|
|
if ( rotate ) {
|
1875 |
|
|
// Current page is displayed in the middle of the visible ones
|
1876 |
|
|
startPage = Math.max(currentPage - Math.floor(maxSize/2), 1);
|
1877 |
|
|
endPage = startPage + maxSize - 1;
|
1878 |
|
|
|
1879 |
|
|
// Adjust if limit is exceeded
|
1880 |
|
|
if (endPage > totalPages) {
|
1881 |
|
|
endPage = totalPages;
|
1882 |
|
|
startPage = endPage - maxSize + 1;
|
1883 |
|
|
}
|
1884 |
|
|
} else {
|
1885 |
|
|
// Visible pages are paginated with maxSize
|
1886 |
|
|
startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;
|
1887 |
|
|
|
1888 |
|
|
// Adjust last page if limit is exceeded
|
1889 |
|
|
endPage = Math.min(startPage + maxSize - 1, totalPages);
|
1890 |
|
|
}
|
1891 |
|
|
}
|
1892 |
|
|
|
1893 |
|
|
// Add page number links
|
1894 |
|
|
for (var number = startPage; number <= endPage; number++) {
|
1895 |
|
|
var page = makePage(number, number, paginationCtrl.isActive(number), false);
|
1896 |
|
|
pages.push(page);
|
1897 |
|
|
}
|
1898 |
|
|
|
1899 |
|
|
// Add links to move between page sets
|
1900 |
|
|
if ( isMaxSized && ! rotate ) {
|
1901 |
|
|
if ( startPage > 1 ) {
|
1902 |
|
|
var previousPageSet = makePage(startPage - 1, '...', false, false);
|
1903 |
|
|
pages.unshift(previousPageSet);
|
1904 |
|
|
}
|
1905 |
|
|
|
1906 |
|
|
if ( endPage < totalPages ) {
|
1907 |
|
|
var nextPageSet = makePage(endPage + 1, '...', false, false);
|
1908 |
|
|
pages.push(nextPageSet);
|
1909 |
|
|
}
|
1910 |
|
|
}
|
1911 |
|
|
|
1912 |
|
|
// Add previous & next links
|
1913 |
|
|
if (directionLinks) {
|
1914 |
|
|
var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious());
|
1915 |
|
|
pages.unshift(previousPage);
|
1916 |
|
|
|
1917 |
|
|
var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext());
|
1918 |
|
|
pages.push(nextPage);
|
1919 |
|
|
}
|
1920 |
|
|
|
1921 |
|
|
// Add first & last links
|
1922 |
|
|
if (boundaryLinks) {
|
1923 |
|
|
var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious());
|
1924 |
|
|
pages.unshift(firstPage);
|
1925 |
|
|
|
1926 |
|
|
var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext());
|
1927 |
|
|
pages.push(lastPage);
|
1928 |
|
|
}
|
1929 |
|
|
|
1930 |
|
|
return pages;
|
1931 |
|
|
};
|
1932 |
|
|
}
|
1933 |
|
|
};
|
1934 |
|
|
}])
|
1935 |
|
|
|
1936 |
|
|
.constant('pagerConfig', {
|
1937 |
|
|
itemsPerPage: 10,
|
1938 |
|
|
previousText: '« Previous',
|
1939 |
|
|
nextText: 'Next »',
|
1940 |
|
|
align: true
|
1941 |
|
|
})
|
1942 |
|
|
|
1943 |
|
|
.directive('pager', ['pagerConfig', function(config) {
|
1944 |
|
|
return {
|
1945 |
|
|
restrict: 'EA',
|
1946 |
|
|
scope: {
|
1947 |
|
|
page: '=',
|
1948 |
|
|
totalItems: '=',
|
1949 |
|
|
onSelectPage:' &'
|
1950 |
|
|
},
|
1951 |
|
|
controller: 'PaginationController',
|
1952 |
|
|
templateUrl: 'template/pagination/pager.html',
|
1953 |
|
|
replace: true,
|
1954 |
|
|
link: function(scope, element, attrs, paginationCtrl) {
|
1955 |
|
|
|
1956 |
|
|
// Setup configuration parameters
|
1957 |
|
|
var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
|
1958 |
|
|
nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
|
1959 |
|
|
align = paginationCtrl.getAttributeValue(attrs.align, config.align);
|
1960 |
|
|
|
1961 |
|
|
paginationCtrl.init(config.itemsPerPage);
|
1962 |
|
|
|
1963 |
|
|
// Create page object used in template
|
1964 |
|
|
function makePage(number, text, isDisabled, isPrevious, isNext) {
|
1965 |
|
|
return {
|
1966 |
|
|
number: number,
|
1967 |
|
|
text: text,
|
1968 |
|
|
disabled: isDisabled,
|
1969 |
|
|
previous: ( align && isPrevious ),
|
1970 |
|
|
next: ( align && isNext )
|
1971 |
|
|
};
|
1972 |
|
|
}
|
1973 |
|
|
|
1974 |
|
|
paginationCtrl.getPages = function(currentPage) {
|
1975 |
|
|
return [
|
1976 |
|
|
makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false),
|
1977 |
|
|
makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true)
|
1978 |
|
|
];
|
1979 |
|
|
};
|
1980 |
|
|
}
|
1981 |
|
|
};
|
1982 |
|
|
}]);
|
1983 |
|
|
|
1984 |
|
|
/**
|
1985 |
|
|
* The following features are still outstanding: animation as a
|
1986 |
|
|
* function, placement as a function, inside, support for more triggers than
|
1987 |
|
|
* just mouse enter/leave, html tooltips, and selector delegation.
|
1988 |
|
|
*/
|
1989 |
|
|
angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] )
|
1990 |
|
|
|
1991 |
|
|
/**
|
1992 |
|
|
* The $tooltip service creates tooltip- and popover-like directives as well as
|
1993 |
|
|
* houses global options for them.
|
1994 |
|
|
*/
|
1995 |
|
|
.provider( '$tooltip', function () {
|
1996 |
|
|
// The default options tooltip and popover.
|
1997 |
|
|
var defaultOptions = {
|
1998 |
|
|
placement: 'top',
|
1999 |
|
|
animation: true,
|
2000 |
|
|
popupDelay: 0
|
2001 |
|
|
};
|
2002 |
|
|
|
2003 |
|
|
// Default hide triggers for each show trigger
|
2004 |
|
|
var triggerMap = {
|
2005 |
|
|
'mouseenter': 'mouseleave',
|
2006 |
|
|
'click': 'click',
|
2007 |
|
|
'focus': 'blur'
|
2008 |
|
|
};
|
2009 |
|
|
|
2010 |
|
|
// The options specified to the provider globally.
|
2011 |
|
|
var globalOptions = {};
|
2012 |
|
|
|
2013 |
|
|
/**
|
2014 |
|
|
* `options({})` allows global configuration of all tooltips in the
|
2015 |
|
|
* application.
|
2016 |
|
|
*
|
2017 |
|
|
* var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
|
2018 |
|
|
* // place tooltips left instead of top by default
|
2019 |
|
|
* $tooltipProvider.options( { placement: 'left' } );
|
2020 |
|
|
* });
|
2021 |
|
|
*/
|
2022 |
|
|
this.options = function( value ) {
|
2023 |
|
|
angular.extend( globalOptions, value );
|
2024 |
|
|
};
|
2025 |
|
|
|
2026 |
|
|
/**
|
2027 |
|
|
* This allows you to extend the set of trigger mappings available. E.g.:
|
2028 |
|
|
*
|
2029 |
|
|
* $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
|
2030 |
|
|
*/
|
2031 |
|
|
this.setTriggers = function setTriggers ( triggers ) {
|
2032 |
|
|
angular.extend( triggerMap, triggers );
|
2033 |
|
|
};
|
2034 |
|
|
|
2035 |
|
|
/**
|
2036 |
|
|
* This is a helper function for translating camel-case to snake-case.
|
2037 |
|
|
*/
|
2038 |
|
|
function snake_case(name){
|
2039 |
|
|
var regexp = /[A-Z]/g;
|
2040 |
|
|
var separator = '-';
|
2041 |
|
|
return name.replace(regexp, function(letter, pos) {
|
2042 |
|
|
return (pos ? separator : '') + letter.toLowerCase();
|
2043 |
|
|
});
|
2044 |
|
|
}
|
2045 |
|
|
|
2046 |
|
|
/**
|
2047 |
|
|
* Returns the actual instance of the $tooltip service.
|
2048 |
|
|
* TODO support multiple triggers
|
2049 |
|
|
*/
|
2050 |
|
|
this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) {
|
2051 |
|
|
return function $tooltip ( type, prefix, defaultTriggerShow ) {
|
2052 |
|
|
var options = angular.extend( {}, defaultOptions, globalOptions );
|
2053 |
|
|
|
2054 |
|
|
/**
|
2055 |
|
|
* Returns an object of show and hide triggers.
|
2056 |
|
|
*
|
2057 |
|
|
* If a trigger is supplied,
|
2058 |
|
|
* it is used to show the tooltip; otherwise, it will use the `trigger`
|
2059 |
|
|
* option passed to the `$tooltipProvider.options` method; else it will
|
2060 |
|
|
* default to the trigger supplied to this directive factory.
|
2061 |
|
|
*
|
2062 |
|
|
* The hide trigger is based on the show trigger. If the `trigger` option
|
2063 |
|
|
* was passed to the `$tooltipProvider.options` method, it will use the
|
2064 |
|
|
* mapped trigger from `triggerMap` or the passed trigger if the map is
|
2065 |
|
|
* undefined; otherwise, it uses the `triggerMap` value of the show
|
2066 |
|
|
* trigger; else it will just use the show trigger.
|
2067 |
|
|
*/
|
2068 |
|
|
function getTriggers ( trigger ) {
|
2069 |
|
|
var show = trigger || options.trigger || defaultTriggerShow;
|
2070 |
|
|
var hide = triggerMap[show] || show;
|
2071 |
|
|
return {
|
2072 |
|
|
show: show,
|
2073 |
|
|
hide: hide
|
2074 |
|
|
};
|
2075 |
|
|
}
|
2076 |
|
|
|
2077 |
|
|
var directiveName = snake_case( type );
|
2078 |
|
|
|
2079 |
|
|
var startSym = $interpolate.startSymbol();
|
2080 |
|
|
var endSym = $interpolate.endSymbol();
|
2081 |
|
|
var template =
|
2082 |
|
|
'<div '+ directiveName +'-popup '+
|
2083 |
|
|
'title="'+startSym+'tt_title'+endSym+'" '+
|
2084 |
|
|
'content="'+startSym+'tt_content'+endSym+'" '+
|
2085 |
|
|
'placement="'+startSym+'tt_placement'+endSym+'" '+
|
2086 |
|
|
'animation="tt_animation" '+
|
2087 |
|
|
'is-open="tt_isOpen"'+
|
2088 |
|
|
'>'+
|
2089 |
|
|
'</div>';
|
2090 |
|
|
|
2091 |
|
|
return {
|
2092 |
|
|
restrict: 'EA',
|
2093 |
|
|
scope: true,
|
2094 |
|
|
compile: function (tElem, tAttrs) {
|
2095 |
|
|
var tooltipLinker = $compile( template );
|
2096 |
|
|
|
2097 |
|
|
return function link ( scope, element, attrs ) {
|
2098 |
|
|
var tooltip;
|
2099 |
|
|
var transitionTimeout;
|
2100 |
|
|
var popupTimeout;
|
2101 |
|
|
var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
|
2102 |
|
|
var triggers = getTriggers( undefined );
|
2103 |
|
|
var hasRegisteredTriggers = false;
|
2104 |
|
|
var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);
|
2105 |
|
|
|
2106 |
|
|
var positionTooltip = function (){
|
2107 |
|
|
var position,
|
2108 |
|
|
ttWidth,
|
2109 |
|
|
ttHeight,
|
2110 |
|
|
ttPosition;
|
2111 |
|
|
// Get the position of the directive element.
|
2112 |
|
|
position = appendToBody ? $position.offset( element ) : $position.position( element );
|
2113 |
|
|
|
2114 |
|
|
// Get the height and width of the tooltip so we can center it.
|
2115 |
|
|
ttWidth = tooltip.prop( 'offsetWidth' );
|
2116 |
|
|
ttHeight = tooltip.prop( 'offsetHeight' );
|
2117 |
|
|
|
2118 |
|
|
// Calculate the tooltip's top and left coordinates to center it with
|
2119 |
|
|
// this directive.
|
2120 |
|
|
switch ( scope.tt_placement ) {
|
2121 |
|
|
case 'right':
|
2122 |
|
|
ttPosition = {
|
2123 |
|
|
top: position.top + position.height / 2 - ttHeight / 2,
|
2124 |
|
|
left: position.left + position.width
|
2125 |
|
|
};
|
2126 |
|
|
break;
|
2127 |
|
|
case 'bottom':
|
2128 |
|
|
ttPosition = {
|
2129 |
|
|
top: position.top + position.height,
|
2130 |
|
|
left: position.left + position.width / 2 - ttWidth / 2
|
2131 |
|
|
};
|
2132 |
|
|
break;
|
2133 |
|
|
case 'left':
|
2134 |
|
|
ttPosition = {
|
2135 |
|
|
top: position.top + position.height / 2 - ttHeight / 2,
|
2136 |
|
|
left: position.left - ttWidth
|
2137 |
|
|
};
|
2138 |
|
|
break;
|
2139 |
|
|
default:
|
2140 |
|
|
ttPosition = {
|
2141 |
|
|
top: position.top - ttHeight,
|
2142 |
|
|
left: position.left + position.width / 2 - ttWidth / 2
|
2143 |
|
|
};
|
2144 |
|
|
break;
|
2145 |
|
|
}
|
2146 |
|
|
|
2147 |
|
|
ttPosition.top += 'px';
|
2148 |
|
|
ttPosition.left += 'px';
|
2149 |
|
|
|
2150 |
|
|
// Now set the calculated positioning.
|
2151 |
|
|
tooltip.css( ttPosition );
|
2152 |
|
|
|
2153 |
|
|
};
|
2154 |
|
|
|
2155 |
|
|
// By default, the tooltip is not open.
|
2156 |
|
|
// TODO add ability to start tooltip opened
|
2157 |
|
|
scope.tt_isOpen = false;
|
2158 |
|
|
|
2159 |
|
|
function toggleTooltipBind () {
|
2160 |
|
|
if ( ! scope.tt_isOpen ) {
|
2161 |
|
|
showTooltipBind();
|
2162 |
|
|
} else {
|
2163 |
|
|
hideTooltipBind();
|
2164 |
|
|
}
|
2165 |
|
|
}
|
2166 |
|
|
|
2167 |
|
|
// Show the tooltip with delay if specified, otherwise show it immediately
|
2168 |
|
|
function showTooltipBind() {
|
2169 |
|
|
if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
|
2170 |
|
|
return;
|
2171 |
|
|
}
|
2172 |
|
|
if ( scope.tt_popupDelay ) {
|
2173 |
|
|
popupTimeout = $timeout( show, scope.tt_popupDelay, false );
|
2174 |
|
|
popupTimeout.then(function(reposition){reposition();});
|
2175 |
|
|
} else {
|
2176 |
|
|
show()();
|
2177 |
|
|
}
|
2178 |
|
|
}
|
2179 |
|
|
|
2180 |
|
|
function hideTooltipBind () {
|
2181 |
|
|
scope.$apply(function () {
|
2182 |
|
|
hide();
|
2183 |
|
|
});
|
2184 |
|
|
}
|
2185 |
|
|
|
2186 |
|
|
// Show the tooltip popup element.
|
2187 |
|
|
function show() {
|
2188 |
|
|
|
2189 |
|
|
|
2190 |
|
|
// Don't show empty tooltips.
|
2191 |
|
|
if ( ! scope.tt_content ) {
|
2192 |
|
|
return angular.noop;
|
2193 |
|
|
}
|
2194 |
|
|
|
2195 |
|
|
createTooltip();
|
2196 |
|
|
|
2197 |
|
|
// If there is a pending remove transition, we must cancel it, lest the
|
2198 |
|
|
// tooltip be mysteriously removed.
|
2199 |
|
|
if ( transitionTimeout ) {
|
2200 |
|
|
$timeout.cancel( transitionTimeout );
|
2201 |
|
|
}
|
2202 |
|
|
|
2203 |
|
|
// Set the initial positioning.
|
2204 |
|
|
tooltip.css({ top: 0, left: 0, display: 'block' });
|
2205 |
|
|
|
2206 |
|
|
// Now we add it to the DOM because need some info about it. But it's not
|
2207 |
|
|
// visible yet anyway.
|
2208 |
|
|
if ( appendToBody ) {
|
2209 |
|
|
$document.find( 'body' ).append( tooltip );
|
2210 |
|
|
} else {
|
2211 |
|
|
element.after( tooltip );
|
2212 |
|
|
}
|
2213 |
|
|
|
2214 |
|
|
positionTooltip();
|
2215 |
|
|
|
2216 |
|
|
// And show the tooltip.
|
2217 |
|
|
scope.tt_isOpen = true;
|
2218 |
|
|
scope.$digest(); // digest required as $apply is not called
|
2219 |
|
|
|
2220 |
|
|
// Return positioning function as promise callback for correct
|
2221 |
|
|
// positioning after draw.
|
2222 |
|
|
return positionTooltip;
|
2223 |
|
|
}
|
2224 |
|
|
|
2225 |
|
|
// Hide the tooltip popup element.
|
2226 |
|
|
function hide() {
|
2227 |
|
|
// First things first: we don't show it anymore.
|
2228 |
|
|
scope.tt_isOpen = false;
|
2229 |
|
|
|
2230 |
|
|
//if tooltip is going to be shown after delay, we must cancel this
|
2231 |
|
|
$timeout.cancel( popupTimeout );
|
2232 |
|
|
|
2233 |
|
|
// And now we remove it from the DOM. However, if we have animation, we
|
2234 |
|
|
// need to wait for it to expire beforehand.
|
2235 |
|
|
// FIXME: this is a placeholder for a port of the transitions library.
|
2236 |
|
|
if ( scope.tt_animation ) {
|
2237 |
|
|
transitionTimeout = $timeout(removeTooltip, 500);
|
2238 |
|
|
} else {
|
2239 |
|
|
removeTooltip();
|
2240 |
|
|
}
|
2241 |
|
|
}
|
2242 |
|
|
|
2243 |
|
|
function createTooltip() {
|
2244 |
|
|
// There can only be one tooltip element per directive shown at once.
|
2245 |
|
|
if (tooltip) {
|
2246 |
|
|
removeTooltip();
|
2247 |
|
|
}
|
2248 |
|
|
tooltip = tooltipLinker(scope, function () {});
|
2249 |
|
|
|
2250 |
|
|
// Get contents rendered into the tooltip
|
2251 |
|
|
scope.$digest();
|
2252 |
|
|
}
|
2253 |
|
|
|
2254 |
|
|
function removeTooltip() {
|
2255 |
|
|
if (tooltip) {
|
2256 |
|
|
tooltip.remove();
|
2257 |
|
|
tooltip = null;
|
2258 |
|
|
}
|
2259 |
|
|
}
|
2260 |
|
|
|
2261 |
|
|
/**
|
2262 |
|
|
* Observe the relevant attributes.
|
2263 |
|
|
*/
|
2264 |
|
|
attrs.$observe( type, function ( val ) {
|
2265 |
|
|
scope.tt_content = val;
|
2266 |
|
|
|
2267 |
|
|
if (!val && scope.tt_isOpen ) {
|
2268 |
|
|
hide();
|
2269 |
|
|
}
|
2270 |
|
|
});
|
2271 |
|
|
|
2272 |
|
|
attrs.$observe( prefix+'Title', function ( val ) {
|
2273 |
|
|
scope.tt_title = val;
|
2274 |
|
|
});
|
2275 |
|
|
|
2276 |
|
|
attrs.$observe( prefix+'Placement', function ( val ) {
|
2277 |
|
|
scope.tt_placement = angular.isDefined( val ) ? val : options.placement;
|
2278 |
|
|
});
|
2279 |
|
|
|
2280 |
|
|
attrs.$observe( prefix+'PopupDelay', function ( val ) {
|
2281 |
|
|
var delay = parseInt( val, 10 );
|
2282 |
|
|
scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
|
2283 |
|
|
});
|
2284 |
|
|
|
2285 |
|
|
var unregisterTriggers = function() {
|
2286 |
|
|
if (hasRegisteredTriggers) {
|
2287 |
|
|
element.unbind( triggers.show, showTooltipBind );
|
2288 |
|
|
element.unbind( triggers.hide, hideTooltipBind );
|
2289 |
|
|
}
|
2290 |
|
|
};
|
2291 |
|
|
|
2292 |
|
|
attrs.$observe( prefix+'Trigger', function ( val ) {
|
2293 |
|
|
unregisterTriggers();
|
2294 |
|
|
|
2295 |
|
|
triggers = getTriggers( val );
|
2296 |
|
|
|
2297 |
|
|
if ( triggers.show === triggers.hide ) {
|
2298 |
|
|
element.bind( triggers.show, toggleTooltipBind );
|
2299 |
|
|
} else {
|
2300 |
|
|
element.bind( triggers.show, showTooltipBind );
|
2301 |
|
|
element.bind( triggers.hide, hideTooltipBind );
|
2302 |
|
|
}
|
2303 |
|
|
|
2304 |
|
|
hasRegisteredTriggers = true;
|
2305 |
|
|
});
|
2306 |
|
|
|
2307 |
|
|
var animation = scope.$eval(attrs[prefix + 'Animation']);
|
2308 |
|
|
scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation;
|
2309 |
|
|
|
2310 |
|
|
attrs.$observe( prefix+'AppendToBody', function ( val ) {
|
2311 |
|
|
appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody;
|
2312 |
|
|
});
|
2313 |
|
|
|
2314 |
|
|
// if a tooltip is attached to <body> we need to remove it on
|
2315 |
|
|
// location change as its parent scope will probably not be destroyed
|
2316 |
|
|
// by the change.
|
2317 |
|
|
if ( appendToBody ) {
|
2318 |
|
|
scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
|
2319 |
|
|
if ( scope.tt_isOpen ) {
|
2320 |
|
|
hide();
|
2321 |
|
|
}
|
2322 |
|
|
});
|
2323 |
|
|
}
|
2324 |
|
|
|
2325 |
|
|
// Make sure tooltip is destroyed and removed.
|
2326 |
|
|
scope.$on('$destroy', function onDestroyTooltip() {
|
2327 |
|
|
$timeout.cancel( transitionTimeout );
|
2328 |
|
|
$timeout.cancel( popupTimeout );
|
2329 |
|
|
unregisterTriggers();
|
2330 |
|
|
removeTooltip();
|
2331 |
|
|
});
|
2332 |
|
|
};
|
2333 |
|
|
}
|
2334 |
|
|
};
|
2335 |
|
|
};
|
2336 |
|
|
}];
|
2337 |
|
|
})
|
2338 |
|
|
|
2339 |
|
|
.directive( 'tooltipPopup', function () {
|
2340 |
|
|
return {
|
2341 |
|
|
restrict: 'EA',
|
2342 |
|
|
replace: true,
|
2343 |
|
|
scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
|
2344 |
|
|
templateUrl: 'template/tooltip/tooltip-popup.html'
|
2345 |
|
|
};
|
2346 |
|
|
})
|
2347 |
|
|
|
2348 |
|
|
.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
|
2349 |
|
|
return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
|
2350 |
|
|
}])
|
2351 |
|
|
|
2352 |
|
|
.directive( 'tooltipHtmlUnsafePopup', function () {
|
2353 |
|
|
return {
|
2354 |
|
|
restrict: 'EA',
|
2355 |
|
|
replace: true,
|
2356 |
|
|
scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
|
2357 |
|
|
templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
|
2358 |
|
|
};
|
2359 |
|
|
})
|
2360 |
|
|
|
2361 |
|
|
.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
|
2362 |
|
|
return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
|
2363 |
|
|
}]);
|
2364 |
|
|
|
2365 |
|
|
/**
|
2366 |
|
|
* The following features are still outstanding: popup delay, animation as a
|
2367 |
|
|
* function, placement as a function, inside, support for more triggers than
|
2368 |
|
|
* just mouse enter/leave, html popovers, and selector delegatation.
|
2369 |
|
|
*/
|
2370 |
|
|
angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
|
2371 |
|
|
|
2372 |
|
|
.directive( 'popoverPopup', function () {
|
2373 |
|
|
return {
|
2374 |
|
|
restrict: 'EA',
|
2375 |
|
|
replace: true,
|
2376 |
|
|
scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
|
2377 |
|
|
templateUrl: 'template/popover/popover.html'
|
2378 |
|
|
};
|
2379 |
|
|
})
|
2380 |
|
|
|
2381 |
|
|
.directive( 'popover', [ '$tooltip', function ( $tooltip ) {
|
2382 |
|
|
return $tooltip( 'popover', 'popover', 'click' );
|
2383 |
|
|
}]);
|
2384 |
|
|
|
2385 |
|
|
angular.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition'])
|
2386 |
|
|
|
2387 |
|
|
.constant('progressConfig', {
|
2388 |
|
|
animate: true,
|
2389 |
|
|
max: 100
|
2390 |
|
|
})
|
2391 |
|
|
|
2392 |
|
|
.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', '$transition', function($scope, $attrs, progressConfig, $transition) {
|
2393 |
|
|
var self = this,
|
2394 |
|
|
bars = [],
|
2395 |
|
|
max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max,
|
2396 |
|
|
animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
|
2397 |
|
|
|
2398 |
|
|
this.addBar = function(bar, element) {
|
2399 |
|
|
var oldValue = 0, index = bar.$parent.$index;
|
2400 |
|
|
if ( angular.isDefined(index) && bars[index] ) {
|
2401 |
|
|
oldValue = bars[index].value;
|
2402 |
|
|
}
|
2403 |
|
|
bars.push(bar);
|
2404 |
|
|
|
2405 |
|
|
this.update(element, bar.value, oldValue);
|
2406 |
|
|
|
2407 |
|
|
bar.$watch('value', function(value, oldValue) {
|
2408 |
|
|
if (value !== oldValue) {
|
2409 |
|
|
self.update(element, value, oldValue);
|
2410 |
|
|
}
|
2411 |
|
|
});
|
2412 |
|
|
|
2413 |
|
|
bar.$on('$destroy', function() {
|
2414 |
|
|
self.removeBar(bar);
|
2415 |
|
|
});
|
2416 |
|
|
};
|
2417 |
|
|
|
2418 |
|
|
// Update bar element width
|
2419 |
|
|
this.update = function(element, newValue, oldValue) {
|
2420 |
|
|
var percent = this.getPercentage(newValue);
|
2421 |
|
|
|
2422 |
|
|
if (animate) {
|
2423 |
|
|
element.css('width', this.getPercentage(oldValue) + '%');
|
2424 |
|
|
$transition(element, {width: percent + '%'});
|
2425 |
|
|
} else {
|
2426 |
|
|
element.css({'transition': 'none', 'width': percent + '%'});
|
2427 |
|
|
}
|
2428 |
|
|
};
|
2429 |
|
|
|
2430 |
|
|
this.removeBar = function(bar) {
|
2431 |
|
|
bars.splice(bars.indexOf(bar), 1);
|
2432 |
|
|
};
|
2433 |
|
|
|
2434 |
|
|
this.getPercentage = function(value) {
|
2435 |
|
|
return Math.round(100 * value / max);
|
2436 |
|
|
};
|
2437 |
|
|
}])
|
2438 |
|
|
|
2439 |
|
|
.directive('progress', function() {
|
2440 |
|
|
return {
|
2441 |
|
|
restrict: 'EA',
|
2442 |
|
|
replace: true,
|
2443 |
|
|
transclude: true,
|
2444 |
|
|
controller: 'ProgressController',
|
2445 |
|
|
require: 'progress',
|
2446 |
|
|
scope: {},
|
2447 |
|
|
template: '<div class="progress" ng-transclude></div>'
|
2448 |
|
|
//templateUrl: 'template/progressbar/progress.html' // Works in AngularJS 1.2
|
2449 |
|
|
};
|
2450 |
|
|
})
|
2451 |
|
|
|
2452 |
|
|
.directive('bar', function() {
|
2453 |
|
|
return {
|
2454 |
|
|
restrict: 'EA',
|
2455 |
|
|
replace: true,
|
2456 |
|
|
transclude: true,
|
2457 |
|
|
require: '^progress',
|
2458 |
|
|
scope: {
|
2459 |
|
|
value: '=',
|
2460 |
|
|
type: '@'
|
2461 |
|
|
},
|
2462 |
|
|
templateUrl: 'template/progressbar/bar.html',
|
2463 |
|
|
link: function(scope, element, attrs, progressCtrl) {
|
2464 |
|
|
progressCtrl.addBar(scope, element);
|
2465 |
|
|
}
|
2466 |
|
|
};
|
2467 |
|
|
})
|
2468 |
|
|
|
2469 |
|
|
.directive('progressbar', function() {
|
2470 |
|
|
return {
|
2471 |
|
|
restrict: 'EA',
|
2472 |
|
|
replace: true,
|
2473 |
|
|
transclude: true,
|
2474 |
|
|
controller: 'ProgressController',
|
2475 |
|
|
scope: {
|
2476 |
|
|
value: '=',
|
2477 |
|
|
type: '@'
|
2478 |
|
|
},
|
2479 |
|
|
templateUrl: 'template/progressbar/progressbar.html',
|
2480 |
|
|
link: function(scope, element, attrs, progressCtrl) {
|
2481 |
|
|
progressCtrl.addBar(scope, angular.element(element.children()[0]));
|
2482 |
|
|
}
|
2483 |
|
|
};
|
2484 |
|
|
});
|
2485 |
|
|
angular.module('ui.bootstrap.rating', [])
|
2486 |
|
|
|
2487 |
|
|
.constant('ratingConfig', {
|
2488 |
|
|
max: 5,
|
2489 |
|
|
stateOn: null,
|
2490 |
|
|
stateOff: null
|
2491 |
|
|
})
|
2492 |
|
|
|
2493 |
|
|
.controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope, $attrs, $parse, ratingConfig) {
|
2494 |
|
|
|
2495 |
|
|
this.maxRange = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max;
|
2496 |
|
|
this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
|
2497 |
|
|
this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
|
2498 |
|
|
|
2499 |
|
|
this.createRateObjects = function(states) {
|
2500 |
|
|
var defaultOptions = {
|
2501 |
|
|
stateOn: this.stateOn,
|
2502 |
|
|
stateOff: this.stateOff
|
2503 |
|
|
};
|
2504 |
|
|
|
2505 |
|
|
for (var i = 0, n = states.length; i < n; i++) {
|
2506 |
|
|
states[i] = angular.extend({ index: i }, defaultOptions, states[i]);
|
2507 |
|
|
}
|
2508 |
|
|
return states;
|
2509 |
|
|
};
|
2510 |
|
|
|
2511 |
|
|
// Get objects used in template
|
2512 |
|
|
$scope.range = angular.isDefined($attrs.ratingStates) ? this.createRateObjects(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createRateObjects(new Array(this.maxRange));
|
2513 |
|
|
|
2514 |
|
|
$scope.rate = function(value) {
|
2515 |
|
|
if ( $scope.value !== value && !$scope.readonly ) {
|
2516 |
|
|
$scope.value = value;
|
2517 |
|
|
}
|
2518 |
|
|
};
|
2519 |
|
|
|
2520 |
|
|
$scope.enter = function(value) {
|
2521 |
|
|
if ( ! $scope.readonly ) {
|
2522 |
|
|
$scope.val = value;
|
2523 |
|
|
}
|
2524 |
|
|
$scope.onHover({value: value});
|
2525 |
|
|
};
|
2526 |
|
|
|
2527 |
|
|
$scope.reset = function() {
|
2528 |
|
|
$scope.val = angular.copy($scope.value);
|
2529 |
|
|
$scope.onLeave();
|
2530 |
|
|
};
|
2531 |
|
|
|
2532 |
|
|
$scope.$watch('value', function(value) {
|
2533 |
|
|
$scope.val = value;
|
2534 |
|
|
});
|
2535 |
|
|
|
2536 |
|
|
$scope.readonly = false;
|
2537 |
|
|
if ($attrs.readonly) {
|
2538 |
|
|
$scope.$parent.$watch($parse($attrs.readonly), function(value) {
|
2539 |
|
|
$scope.readonly = !!value;
|
2540 |
|
|
});
|
2541 |
|
|
}
|
2542 |
|
|
}])
|
2543 |
|
|
|
2544 |
|
|
.directive('rating', function() {
|
2545 |
|
|
return {
|
2546 |
|
|
restrict: 'EA',
|
2547 |
|
|
scope: {
|
2548 |
|
|
value: '=',
|
2549 |
|
|
onHover: '&',
|
2550 |
|
|
onLeave: '&'
|
2551 |
|
|
},
|
2552 |
|
|
controller: 'RatingController',
|
2553 |
|
|
templateUrl: 'template/rating/rating.html',
|
2554 |
|
|
replace: true
|
2555 |
|
|
};
|
2556 |
|
|
});
|
2557 |
|
|
|
2558 |
|
|
/**
|
2559 |
|
|
* @ngdoc overview
|
2560 |
|
|
* @name ui.bootstrap.tabs
|
2561 |
|
|
*
|
2562 |
|
|
* @description
|
2563 |
|
|
* AngularJS version of the tabs directive.
|
2564 |
|
|
*/
|
2565 |
|
|
|
2566 |
|
|
angular.module('ui.bootstrap.tabs', [])
|
2567 |
|
|
|
2568 |
|
|
.controller('TabsetController', ['$scope', function TabsetCtrl($scope) {
|
2569 |
|
|
var ctrl = this,
|
2570 |
|
|
tabs = ctrl.tabs = $scope.tabs = [];
|
2571 |
|
|
|
2572 |
|
|
ctrl.select = function(tab) {
|
2573 |
|
|
angular.forEach(tabs, function(tab) {
|
2574 |
|
|
tab.active = false;
|
2575 |
|
|
});
|
2576 |
|
|
tab.active = true;
|
2577 |
|
|
};
|
2578 |
|
|
|
2579 |
|
|
ctrl.addTab = function addTab(tab) {
|
2580 |
|
|
tabs.push(tab);
|
2581 |
|
|
if (tabs.length === 1 || tab.active) {
|
2582 |
|
|
ctrl.select(tab);
|
2583 |
|
|
}
|
2584 |
|
|
};
|
2585 |
|
|
|
2586 |
|
|
ctrl.removeTab = function removeTab(tab) {
|
2587 |
|
|
var index = tabs.indexOf(tab);
|
2588 |
|
|
//Select a new tab if the tab to be removed is selected
|
2589 |
|
|
if (tab.active && tabs.length > 1) {
|
2590 |
|
|
//If this is the last tab, select the previous tab. else, the next tab.
|
2591 |
|
|
var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
|
2592 |
|
|
ctrl.select(tabs[newActiveIndex]);
|
2593 |
|
|
}
|
2594 |
|
|
tabs.splice(index, 1);
|
2595 |
|
|
};
|
2596 |
|
|
}])
|
2597 |
|
|
|
2598 |
|
|
/**
|
2599 |
|
|
* @ngdoc directive
|
2600 |
|
|
* @name ui.bootstrap.tabs.directive:tabset
|
2601 |
|
|
* @restrict EA
|
2602 |
|
|
*
|
2603 |
|
|
* @description
|
2604 |
|
|
* Tabset is the outer container for the tabs directive
|
2605 |
|
|
*
|
2606 |
|
|
* @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
|
2607 |
|
|
* @param {boolean=} justified Whether or not to use justified styling for the tabs.
|
2608 |
|
|
*
|
2609 |
|
|
* @example
|
2610 |
|
|
<example module="ui.bootstrap">
|
2611 |
|
|
<file name="index.html">
|
2612 |
|
|
<tabset>
|
2613 |
|
|
<tab heading="Tab 1"><b>First</b> Content!</tab>
|
2614 |
|
|
<tab heading="Tab 2"><i>Second</i> Content!</tab>
|
2615 |
|
|
</tabset>
|
2616 |
|
|
<hr />
|
2617 |
|
|
<tabset vertical="true">
|
2618 |
|
|
<tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab>
|
2619 |
|
|
<tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab>
|
2620 |
|
|
</tabset>
|
2621 |
|
|
<tabset justified="true">
|
2622 |
|
|
<tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab>
|
2623 |
|
|
<tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab>
|
2624 |
|
|
</tabset>
|
2625 |
|
|
</file>
|
2626 |
|
|
</example>
|
2627 |
|
|
*/
|
2628 |
|
|
.directive('tabset', function() {
|
2629 |
|
|
return {
|
2630 |
|
|
restrict: 'EA',
|
2631 |
|
|
transclude: true,
|
2632 |
|
|
replace: true,
|
2633 |
|
|
scope: {},
|
2634 |
|
|
controller: 'TabsetController',
|
2635 |
|
|
templateUrl: 'template/tabs/tabset.html',
|
2636 |
|
|
link: function(scope, element, attrs) {
|
2637 |
|
|
scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
|
2638 |
|
|
scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
|
2639 |
|
|
scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
|
2640 |
|
|
}
|
2641 |
|
|
};
|
2642 |
|
|
})
|
2643 |
|
|
|
2644 |
|
|
/**
|
2645 |
|
|
* @ngdoc directive
|
2646 |
|
|
* @name ui.bootstrap.tabs.directive:tab
|
2647 |
|
|
* @restrict EA
|
2648 |
|
|
*
|
2649 |
|
|
* @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}.
|
2650 |
|
|
* @param {string=} select An expression to evaluate when the tab is selected.
|
2651 |
|
|
* @param {boolean=} active A binding, telling whether or not this tab is selected.
|
2652 |
|
|
* @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
|
2653 |
|
|
*
|
2654 |
|
|
* @description
|
2655 |
|
|
* Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
|
2656 |
|
|
*
|
2657 |
|
|
* @example
|
2658 |
|
|
<example module="ui.bootstrap">
|
2659 |
|
|
<file name="index.html">
|
2660 |
|
|
<div ng-controller="TabsDemoCtrl">
|
2661 |
|
|
<button class="btn btn-small" ng-click="items[0].active = true">
|
2662 |
|
|
Select item 1, using active binding
|
2663 |
|
|
</button>
|
2664 |
|
|
<button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
|
2665 |
|
|
Enable/disable item 2, using disabled binding
|
2666 |
|
|
</button>
|
2667 |
|
|
<br />
|
2668 |
|
|
<tabset>
|
2669 |
|
|
<tab heading="Tab 1">First Tab</tab>
|
2670 |
|
|
<tab select="alertMe()">
|
2671 |
|
|
<tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
|
2672 |
|
|
Second Tab, with alert callback and html heading!
|
2673 |
|
|
</tab>
|
2674 |
|
|
<tab ng-repeat="item in items"
|
2675 |
|
|
heading="{{item.title}}"
|
2676 |
|
|
disabled="item.disabled"
|
2677 |
|
|
active="item.active">
|
2678 |
|
|
{{item.content}}
|
2679 |
|
|
</tab>
|
2680 |
|
|
</tabset>
|
2681 |
|
|
</div>
|
2682 |
|
|
</file>
|
2683 |
|
|
<file name="script.js">
|
2684 |
|
|
function TabsDemoCtrl($scope) {
|
2685 |
|
|
$scope.items = [
|
2686 |
|
|
{ title:"Dynamic Title 1", content:"Dynamic Item 0" },
|
2687 |
|
|
{ title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
|
2688 |
|
|
];
|
2689 |
|
|
|
2690 |
|
|
$scope.alertMe = function() {
|
2691 |
|
|
setTimeout(function() {
|
2692 |
|
|
alert("You've selected the alert tab!");
|
2693 |
|
|
});
|
2694 |
|
|
};
|
2695 |
|
|
};
|
2696 |
|
|
</file>
|
2697 |
|
|
</example>
|
2698 |
|
|
*/
|
2699 |
|
|
|
2700 |
|
|
/**
|
2701 |
|
|
* @ngdoc directive
|
2702 |
|
|
* @name ui.bootstrap.tabs.directive:tabHeading
|
2703 |
|
|
* @restrict EA
|
2704 |
|
|
*
|
2705 |
|
|
* @description
|
2706 |
|
|
* Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
|
2707 |
|
|
*
|
2708 |
|
|
* @example
|
2709 |
|
|
<example module="ui.bootstrap">
|
2710 |
|
|
<file name="index.html">
|
2711 |
|
|
<tabset>
|
2712 |
|
|
<tab>
|
2713 |
|
|
<tab-heading><b>HTML</b> in my titles?!</tab-heading>
|
2714 |
|
|
And some content, too!
|
2715 |
|
|
</tab>
|
2716 |
|
|
<tab>
|
2717 |
|
|
<tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
|
2718 |
|
|
That's right.
|
2719 |
|
|
</tab>
|
2720 |
|
|
</tabset>
|
2721 |
|
|
</file>
|
2722 |
|
|
</example>
|
2723 |
|
|
*/
|
2724 |
|
|
.directive('tab', ['$parse', function($parse) {
|
2725 |
|
|
return {
|
2726 |
|
|
require: '^tabset',
|
2727 |
|
|
restrict: 'EA',
|
2728 |
|
|
replace: true,
|
2729 |
|
|
templateUrl: 'template/tabs/tab.html',
|
2730 |
|
|
transclude: true,
|
2731 |
|
|
scope: {
|
2732 |
|
|
heading: '@',
|
2733 |
|
|
onSelect: '&select', //This callback is called in contentHeadingTransclude
|
2734 |
|
|
//once it inserts the tab's content into the dom
|
2735 |
|
|
onDeselect: '&deselect'
|
2736 |
|
|
},
|
2737 |
|
|
controller: function() {
|
2738 |
|
|
//Empty controller so other directives can require being 'under' a tab
|
2739 |
|
|
},
|
2740 |
|
|
compile: function(elm, attrs, transclude) {
|
2741 |
|
|
return function postLink(scope, elm, attrs, tabsetCtrl) {
|
2742 |
|
|
var getActive, setActive;
|
2743 |
|
|
if (attrs.active) {
|
2744 |
|
|
getActive = $parse(attrs.active);
|
2745 |
|
|
setActive = getActive.assign;
|
2746 |
|
|
scope.$parent.$watch(getActive, function updateActive(value, oldVal) {
|
2747 |
|
|
// Avoid re-initializing scope.active as it is already initialized
|
2748 |
|
|
// below. (watcher is called async during init with value ===
|
2749 |
|
|
// oldVal)
|
2750 |
|
|
if (value !== oldVal) {
|
2751 |
|
|
scope.active = !!value;
|
2752 |
|
|
}
|
2753 |
|
|
});
|
2754 |
|
|
scope.active = getActive(scope.$parent);
|
2755 |
|
|
} else {
|
2756 |
|
|
setActive = getActive = angular.noop;
|
2757 |
|
|
}
|
2758 |
|
|
|
2759 |
|
|
scope.$watch('active', function(active) {
|
2760 |
|
|
// Note this watcher also initializes and assigns scope.active to the
|
2761 |
|
|
// attrs.active expression.
|
2762 |
|
|
setActive(scope.$parent, active);
|
2763 |
|
|
if (active) {
|
2764 |
|
|
tabsetCtrl.select(scope);
|
2765 |
|
|
scope.onSelect();
|
2766 |
|
|
} else {
|
2767 |
|
|
scope.onDeselect();
|
2768 |
|
|
}
|
2769 |
|
|
});
|
2770 |
|
|
|
2771 |
|
|
scope.disabled = false;
|
2772 |
|
|
if ( attrs.disabled ) {
|
2773 |
|
|
scope.$parent.$watch($parse(attrs.disabled), function(value) {
|
2774 |
|
|
scope.disabled = !! value;
|
2775 |
|
|
});
|
2776 |
|
|
}
|
2777 |
|
|
|
2778 |
|
|
scope.select = function() {
|
2779 |
|
|
if ( ! scope.disabled ) {
|
2780 |
|
|
scope.active = true;
|
2781 |
|
|
}
|
2782 |
|
|
};
|
2783 |
|
|
|
2784 |
|
|
tabsetCtrl.addTab(scope);
|
2785 |
|
|
scope.$on('$destroy', function() {
|
2786 |
|
|
tabsetCtrl.removeTab(scope);
|
2787 |
|
|
});
|
2788 |
|
|
|
2789 |
|
|
|
2790 |
|
|
//We need to transclude later, once the content container is ready.
|
2791 |
|
|
//when this link happens, we're inside a tab heading.
|
2792 |
|
|
scope.$transcludeFn = transclude;
|
2793 |
|
|
};
|
2794 |
|
|
}
|
2795 |
|
|
};
|
2796 |
|
|
}])
|
2797 |
|
|
|
2798 |
|
|
.directive('tabHeadingTransclude', [function() {
|
2799 |
|
|
return {
|
2800 |
|
|
restrict: 'A',
|
2801 |
|
|
require: '^tab',
|
2802 |
|
|
link: function(scope, elm, attrs, tabCtrl) {
|
2803 |
|
|
scope.$watch('headingElement', function updateHeadingElement(heading) {
|
2804 |
|
|
if (heading) {
|
2805 |
|
|
elm.html('');
|
2806 |
|
|
elm.append(heading);
|
2807 |
|
|
}
|
2808 |
|
|
});
|
2809 |
|
|
}
|
2810 |
|
|
};
|
2811 |
|
|
}])
|
2812 |
|
|
|
2813 |
|
|
.directive('tabContentTransclude', function() {
|
2814 |
|
|
return {
|
2815 |
|
|
restrict: 'A',
|
2816 |
|
|
require: '^tabset',
|
2817 |
|
|
link: function(scope, elm, attrs) {
|
2818 |
|
|
var tab = scope.$eval(attrs.tabContentTransclude);
|
2819 |
|
|
|
2820 |
|
|
//Now our tab is ready to be transcluded: both the tab heading area
|
2821 |
|
|
//and the tab content area are loaded. Transclude 'em both.
|
2822 |
|
|
tab.$transcludeFn(tab.$parent, function(contents) {
|
2823 |
|
|
angular.forEach(contents, function(node) {
|
2824 |
|
|
if (isTabHeading(node)) {
|
2825 |
|
|
//Let tabHeadingTransclude know.
|
2826 |
|
|
tab.headingElement = node;
|
2827 |
|
|
} else {
|
2828 |
|
|
elm.append(node);
|
2829 |
|
|
}
|
2830 |
|
|
});
|
2831 |
|
|
});
|
2832 |
|
|
}
|
2833 |
|
|
};
|
2834 |
|
|
function isTabHeading(node) {
|
2835 |
|
|
return node.tagName && (
|
2836 |
|
|
node.hasAttribute('tab-heading') ||
|
2837 |
|
|
node.hasAttribute('data-tab-heading') ||
|
2838 |
|
|
node.tagName.toLowerCase() === 'tab-heading' ||
|
2839 |
|
|
node.tagName.toLowerCase() === 'data-tab-heading'
|
2840 |
|
|
);
|
2841 |
|
|
}
|
2842 |
|
|
})
|
2843 |
|
|
|
2844 |
|
|
;
|
2845 |
|
|
|
2846 |
|
|
angular.module('ui.bootstrap.timepicker', [])
|
2847 |
|
|
|
2848 |
|
|
.constant('timepickerConfig', {
|
2849 |
|
|
hourStep: 1,
|
2850 |
|
|
minuteStep: 1,
|
2851 |
|
|
showMeridian: true,
|
2852 |
|
|
meridians: null,
|
2853 |
|
|
readonlyInput: false,
|
2854 |
|
|
mousewheel: true
|
2855 |
|
|
})
|
2856 |
|
|
|
2857 |
|
|
.directive('timepicker', ['$parse', '$log', 'timepickerConfig', '$locale', function ($parse, $log, timepickerConfig, $locale) {
|
2858 |
|
|
return {
|
2859 |
|
|
restrict: 'EA',
|
2860 |
|
|
require:'?^ngModel',
|
2861 |
|
|
replace: true,
|
2862 |
|
|
scope: {},
|
2863 |
|
|
templateUrl: 'template/timepicker/timepicker.html',
|
2864 |
|
|
link: function(scope, element, attrs, ngModel) {
|
2865 |
|
|
if ( !ngModel ) {
|
2866 |
|
|
return; // do nothing if no ng-model
|
2867 |
|
|
}
|
2868 |
|
|
|
2869 |
|
|
var selected = new Date(),
|
2870 |
|
|
meridians = angular.isDefined(attrs.meridians) ? scope.$parent.$eval(attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
|
2871 |
|
|
|
2872 |
|
|
var hourStep = timepickerConfig.hourStep;
|
2873 |
|
|
if (attrs.hourStep) {
|
2874 |
|
|
scope.$parent.$watch($parse(attrs.hourStep), function(value) {
|
2875 |
|
|
hourStep = parseInt(value, 10);
|
2876 |
|
|
});
|
2877 |
|
|
}
|
2878 |
|
|
|
2879 |
|
|
var minuteStep = timepickerConfig.minuteStep;
|
2880 |
|
|
if (attrs.minuteStep) {
|
2881 |
|
|
scope.$parent.$watch($parse(attrs.minuteStep), function(value) {
|
2882 |
|
|
minuteStep = parseInt(value, 10);
|
2883 |
|
|
});
|
2884 |
|
|
}
|
2885 |
|
|
|
2886 |
|
|
// 12H / 24H mode
|
2887 |
|
|
scope.showMeridian = timepickerConfig.showMeridian;
|
2888 |
|
|
if (attrs.showMeridian) {
|
2889 |
|
|
scope.$parent.$watch($parse(attrs.showMeridian), function(value) {
|
2890 |
|
|
scope.showMeridian = !!value;
|
2891 |
|
|
|
2892 |
|
|
if ( ngModel.$error.time ) {
|
2893 |
|
|
// Evaluate from template
|
2894 |
|
|
var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
|
2895 |
|
|
if (angular.isDefined( hours ) && angular.isDefined( minutes )) {
|
2896 |
|
|
selected.setHours( hours );
|
2897 |
|
|
refresh();
|
2898 |
|
|
}
|
2899 |
|
|
} else {
|
2900 |
|
|
updateTemplate();
|
2901 |
|
|
}
|
2902 |
|
|
});
|
2903 |
|
|
}
|
2904 |
|
|
|
2905 |
|
|
// Get scope.hours in 24H mode if valid
|
2906 |
|
|
function getHoursFromTemplate ( ) {
|
2907 |
|
|
var hours = parseInt( scope.hours, 10 );
|
2908 |
|
|
var valid = ( scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24);
|
2909 |
|
|
if ( !valid ) {
|
2910 |
|
|
return undefined;
|
2911 |
|
|
}
|
2912 |
|
|
|
2913 |
|
|
if ( scope.showMeridian ) {
|
2914 |
|
|
if ( hours === 12 ) {
|
2915 |
|
|
hours = 0;
|
2916 |
|
|
}
|
2917 |
|
|
if ( scope.meridian === meridians[1] ) {
|
2918 |
|
|
hours = hours + 12;
|
2919 |
|
|
}
|
2920 |
|
|
}
|
2921 |
|
|
return hours;
|
2922 |
|
|
}
|
2923 |
|
|
|
2924 |
|
|
function getMinutesFromTemplate() {
|
2925 |
|
|
var minutes = parseInt(scope.minutes, 10);
|
2926 |
|
|
return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined;
|
2927 |
|
|
}
|
2928 |
|
|
|
2929 |
|
|
function pad( value ) {
|
2930 |
|
|
return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value;
|
2931 |
|
|
}
|
2932 |
|
|
|
2933 |
|
|
// Input elements
|
2934 |
|
|
var inputs = element.find('input'), hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1);
|
2935 |
|
|
|
2936 |
|
|
// Respond on mousewheel spin
|
2937 |
|
|
var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel;
|
2938 |
|
|
if ( mousewheel ) {
|
2939 |
|
|
|
2940 |
|
|
var isScrollingUp = function(e) {
|
2941 |
|
|
if (e.originalEvent) {
|
2942 |
|
|
e = e.originalEvent;
|
2943 |
|
|
}
|
2944 |
|
|
//pick correct delta variable depending on event
|
2945 |
|
|
var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY;
|
2946 |
|
|
return (e.detail || delta > 0);
|
2947 |
|
|
};
|
2948 |
|
|
|
2949 |
|
|
hoursInputEl.bind('mousewheel wheel', function(e) {
|
2950 |
|
|
scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() );
|
2951 |
|
|
e.preventDefault();
|
2952 |
|
|
});
|
2953 |
|
|
|
2954 |
|
|
minutesInputEl.bind('mousewheel wheel', function(e) {
|
2955 |
|
|
scope.$apply( (isScrollingUp(e)) ? scope.incrementMinutes() : scope.decrementMinutes() );
|
2956 |
|
|
e.preventDefault();
|
2957 |
|
|
});
|
2958 |
|
|
}
|
2959 |
|
|
|
2960 |
|
|
scope.readonlyInput = (angular.isDefined(attrs.readonlyInput)) ? scope.$eval(attrs.readonlyInput) : timepickerConfig.readonlyInput;
|
2961 |
|
|
if ( ! scope.readonlyInput ) {
|
2962 |
|
|
|
2963 |
|
|
var invalidate = function(invalidHours, invalidMinutes) {
|
2964 |
|
|
ngModel.$setViewValue( null );
|
2965 |
|
|
ngModel.$setValidity('time', false);
|
2966 |
|
|
if (angular.isDefined(invalidHours)) {
|
2967 |
|
|
scope.invalidHours = invalidHours;
|
2968 |
|
|
}
|
2969 |
|
|
if (angular.isDefined(invalidMinutes)) {
|
2970 |
|
|
scope.invalidMinutes = invalidMinutes;
|
2971 |
|
|
}
|
2972 |
|
|
};
|
2973 |
|
|
|
2974 |
|
|
scope.updateHours = function() {
|
2975 |
|
|
var hours = getHoursFromTemplate();
|
2976 |
|
|
|
2977 |
|
|
if ( angular.isDefined(hours) ) {
|
2978 |
|
|
selected.setHours( hours );
|
2979 |
|
|
refresh( 'h' );
|
2980 |
|
|
} else {
|
2981 |
|
|
invalidate(true);
|
2982 |
|
|
}
|
2983 |
|
|
};
|
2984 |
|
|
|
2985 |
|
|
hoursInputEl.bind('blur', function(e) {
|
2986 |
|
|
if ( !scope.validHours && scope.hours < 10) {
|
2987 |
|
|
scope.$apply( function() {
|
2988 |
|
|
scope.hours = pad( scope.hours );
|
2989 |
|
|
});
|
2990 |
|
|
}
|
2991 |
|
|
});
|
2992 |
|
|
|
2993 |
|
|
scope.updateMinutes = function() {
|
2994 |
|
|
var minutes = getMinutesFromTemplate();
|
2995 |
|
|
|
2996 |
|
|
if ( angular.isDefined(minutes) ) {
|
2997 |
|
|
selected.setMinutes( minutes );
|
2998 |
|
|
refresh( 'm' );
|
2999 |
|
|
} else {
|
3000 |
|
|
invalidate(undefined, true);
|
3001 |
|
|
}
|
3002 |
|
|
};
|
3003 |
|
|
|
3004 |
|
|
minutesInputEl.bind('blur', function(e) {
|
3005 |
|
|
if ( !scope.invalidMinutes && scope.minutes < 10 ) {
|
3006 |
|
|
scope.$apply( function() {
|
3007 |
|
|
scope.minutes = pad( scope.minutes );
|
3008 |
|
|
});
|
3009 |
|
|
}
|
3010 |
|
|
});
|
3011 |
|
|
} else {
|
3012 |
|
|
scope.updateHours = angular.noop;
|
3013 |
|
|
scope.updateMinutes = angular.noop;
|
3014 |
|
|
}
|
3015 |
|
|
|
3016 |
|
|
ngModel.$render = function() {
|
3017 |
|
|
var date = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : null;
|
3018 |
|
|
|
3019 |
|
|
if ( isNaN(date) ) {
|
3020 |
|
|
ngModel.$setValidity('time', false);
|
3021 |
|
|
$log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
|
3022 |
|
|
} else {
|
3023 |
|
|
if ( date ) {
|
3024 |
|
|
selected = date;
|
3025 |
|
|
}
|
3026 |
|
|
makeValid();
|
3027 |
|
|
updateTemplate();
|
3028 |
|
|
}
|
3029 |
|
|
};
|
3030 |
|
|
|
3031 |
|
|
// Call internally when we know that model is valid.
|
3032 |
|
|
function refresh( keyboardChange ) {
|
3033 |
|
|
makeValid();
|
3034 |
|
|
ngModel.$setViewValue( new Date(selected) );
|
3035 |
|
|
updateTemplate( keyboardChange );
|
3036 |
|
|
}
|
3037 |
|
|
|
3038 |
|
|
function makeValid() {
|
3039 |
|
|
ngModel.$setValidity('time', true);
|
3040 |
|
|
scope.invalidHours = false;
|
3041 |
|
|
scope.invalidMinutes = false;
|
3042 |
|
|
}
|
3043 |
|
|
|
3044 |
|
|
function updateTemplate( keyboardChange ) {
|
3045 |
|
|
var hours = selected.getHours(), minutes = selected.getMinutes();
|
3046 |
|
|
|
3047 |
|
|
if ( scope.showMeridian ) {
|
3048 |
|
|
hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system
|
3049 |
|
|
}
|
3050 |
|
|
scope.hours = keyboardChange === 'h' ? hours : pad(hours);
|
3051 |
|
|
scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes);
|
3052 |
|
|
scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
|
3053 |
|
|
}
|
3054 |
|
|
|
3055 |
|
|
function addMinutes( minutes ) {
|
3056 |
|
|
var dt = new Date( selected.getTime() + minutes * 60000 );
|
3057 |
|
|
selected.setHours( dt.getHours(), dt.getMinutes() );
|
3058 |
|
|
refresh();
|
3059 |
|
|
}
|
3060 |
|
|
|
3061 |
|
|
scope.incrementHours = function() {
|
3062 |
|
|
addMinutes( hourStep * 60 );
|
3063 |
|
|
};
|
3064 |
|
|
scope.decrementHours = function() {
|
3065 |
|
|
addMinutes( - hourStep * 60 );
|
3066 |
|
|
};
|
3067 |
|
|
scope.incrementMinutes = function() {
|
3068 |
|
|
addMinutes( minuteStep );
|
3069 |
|
|
};
|
3070 |
|
|
scope.decrementMinutes = function() {
|
3071 |
|
|
addMinutes( - minuteStep );
|
3072 |
|
|
};
|
3073 |
|
|
scope.toggleMeridian = function() {
|
3074 |
|
|
addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) );
|
3075 |
|
|
};
|
3076 |
|
|
}
|
3077 |
|
|
};
|
3078 |
|
|
}]);
|
3079 |
|
|
|
3080 |
|
|
angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])
|
3081 |
|
|
|
3082 |
|
|
/**
|
3083 |
|
|
* A helper service that can parse typeahead's syntax (string provided by users)
|
3084 |
|
|
* Extracted to a separate service for ease of unit testing
|
3085 |
|
|
*/
|
3086 |
|
|
.factory('typeaheadParser', ['$parse', function ($parse) {
|
3087 |
|
|
|
3088 |
|
|
// 00000111000000000000022200000000000000003333333333333330000000000044000
|
3089 |
|
|
var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
|
3090 |
|
|
|
3091 |
|
|
return {
|
3092 |
|
|
parse:function (input) {
|
3093 |
|
|
|
3094 |
|
|
var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source;
|
3095 |
|
|
if (!match) {
|
3096 |
|
|
throw new Error(
|
3097 |
|
|
"Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
|
3098 |
|
|
" but got '" + input + "'.");
|
3099 |
|
|
}
|
3100 |
|
|
|
3101 |
|
|
return {
|
3102 |
|
|
itemName:match[3],
|
3103 |
|
|
source:$parse(match[4]),
|
3104 |
|
|
viewMapper:$parse(match[2] || match[1]),
|
3105 |
|
|
modelMapper:$parse(match[1])
|
3106 |
|
|
};
|
3107 |
|
|
}
|
3108 |
|
|
};
|
3109 |
|
|
}])
|
3110 |
|
|
|
3111 |
|
|
.directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
|
3112 |
|
|
function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) {
|
3113 |
|
|
|
3114 |
|
|
var HOT_KEYS = [9, 13, 27, 38, 40];
|
3115 |
|
|
|
3116 |
|
|
return {
|
3117 |
|
|
require:'ngModel',
|
3118 |
|
|
link:function (originalScope, element, attrs, modelCtrl) {
|
3119 |
|
|
|
3120 |
|
|
//SUPPORTED ATTRIBUTES (OPTIONS)
|
3121 |
|
|
|
3122 |
|
|
//minimal no of characters that needs to be entered before typeahead kicks-in
|
3123 |
|
|
var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
|
3124 |
|
|
|
3125 |
|
|
//minimal wait time after last character typed before typehead kicks-in
|
3126 |
|
|
var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
|
3127 |
|
|
|
3128 |
|
|
//should it restrict model values to the ones selected from the popup only?
|
3129 |
|
|
var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
|
3130 |
|
|
|
3131 |
|
|
//binding to a variable that indicates if matches are being retrieved asynchronously
|
3132 |
|
|
var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
|
3133 |
|
|
|
3134 |
|
|
//a callback executed when a match is selected
|
3135 |
|
|
var onSelectCallback = $parse(attrs.typeaheadOnSelect);
|
3136 |
|
|
|
3137 |
|
|
var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
|
3138 |
|
|
|
3139 |
|
|
var appendToBody = attrs.typeaheadAppendToBody ? $parse(attrs.typeaheadAppendToBody) : false;
|
3140 |
|
|
|
3141 |
|
|
//INTERNAL VARIABLES
|
3142 |
|
|
|
3143 |
|
|
//model setter executed upon match selection
|
3144 |
|
|
var $setModelValue = $parse(attrs.ngModel).assign;
|
3145 |
|
|
|
3146 |
|
|
//expressions used by typeahead
|
3147 |
|
|
var parserResult = typeaheadParser.parse(attrs.typeahead);
|
3148 |
|
|
|
3149 |
|
|
var hasFocus;
|
3150 |
|
|
|
3151 |
|
|
//pop-up element used to display matches
|
3152 |
|
|
var popUpEl = angular.element('<div typeahead-popup></div>');
|
3153 |
|
|
popUpEl.attr({
|
3154 |
|
|
matches: 'matches',
|
3155 |
|
|
active: 'activeIdx',
|
3156 |
|
|
select: 'select(activeIdx)',
|
3157 |
|
|
query: 'query',
|
3158 |
|
|
position: 'position'
|
3159 |
|
|
});
|
3160 |
|
|
//custom item template
|
3161 |
|
|
if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
|
3162 |
|
|
popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
|
3163 |
|
|
}
|
3164 |
|
|
|
3165 |
|
|
//create a child scope for the typeahead directive so we are not polluting original scope
|
3166 |
|
|
//with typeahead-specific data (matches, query etc.)
|
3167 |
|
|
var scope = originalScope.$new();
|
3168 |
|
|
originalScope.$on('$destroy', function(){
|
3169 |
|
|
scope.$destroy();
|
3170 |
|
|
});
|
3171 |
|
|
|
3172 |
|
|
var resetMatches = function() {
|
3173 |
|
|
scope.matches = [];
|
3174 |
|
|
scope.activeIdx = -1;
|
3175 |
|
|
};
|
3176 |
|
|
|
3177 |
|
|
var getMatchesAsync = function(inputValue) {
|
3178 |
|
|
|
3179 |
|
|
var locals = {$viewValue: inputValue};
|
3180 |
|
|
isLoadingSetter(originalScope, true);
|
3181 |
|
|
$q.when(parserResult.source(originalScope, locals)).then(function(matches) {
|
3182 |
|
|
|
3183 |
|
|
//it might happen that several async queries were in progress if a user were typing fast
|
3184 |
|
|
//but we are interested only in responses that correspond to the current view value
|
3185 |
|
|
if (inputValue === modelCtrl.$viewValue && hasFocus) {
|
3186 |
|
|
if (matches.length > 0) {
|
3187 |
|
|
|
3188 |
|
|
scope.activeIdx = 0;
|
3189 |
|
|
scope.matches.length = 0;
|
3190 |
|
|
|
3191 |
|
|
//transform labels
|
3192 |
|
|
for(var i=0; i<matches.length; i++) {
|
3193 |
|
|
locals[parserResult.itemName] = matches[i];
|
3194 |
|
|
scope.matches.push({
|
3195 |
|
|
label: parserResult.viewMapper(scope, locals),
|
3196 |
|
|
model: matches[i]
|
3197 |
|
|
});
|
3198 |
|
|
}
|
3199 |
|
|
|
3200 |
|
|
scope.query = inputValue;
|
3201 |
|
|
//position pop-up with matches - we need to re-calculate its position each time we are opening a window
|
3202 |
|
|
//with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
|
3203 |
|
|
//due to other elements being rendered
|
3204 |
|
|
scope.position = appendToBody ? $position.offset(element) : $position.position(element);
|
3205 |
|
|
scope.position.top = scope.position.top + element.prop('offsetHeight');
|
3206 |
|
|
|
3207 |
|
|
} else {
|
3208 |
|
|
resetMatches();
|
3209 |
|
|
}
|
3210 |
|
|
isLoadingSetter(originalScope, false);
|
3211 |
|
|
}
|
3212 |
|
|
}, function(){
|
3213 |
|
|
resetMatches();
|
3214 |
|
|
isLoadingSetter(originalScope, false);
|
3215 |
|
|
});
|
3216 |
|
|
};
|
3217 |
|
|
|
3218 |
|
|
resetMatches();
|
3219 |
|
|
|
3220 |
|
|
//we need to propagate user's query so we can higlight matches
|
3221 |
|
|
scope.query = undefined;
|
3222 |
|
|
|
3223 |
|
|
//Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
|
3224 |
|
|
var timeoutPromise;
|
3225 |
|
|
|
3226 |
|
|
//plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
|
3227 |
|
|
//$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
|
3228 |
|
|
modelCtrl.$parsers.unshift(function (inputValue) {
|
3229 |
|
|
|
3230 |
|
|
hasFocus = true;
|
3231 |
|
|
|
3232 |
|
|
if (inputValue && inputValue.length >= minSearch) {
|
3233 |
|
|
if (waitTime > 0) {
|
3234 |
|
|
if (timeoutPromise) {
|
3235 |
|
|
$timeout.cancel(timeoutPromise);//cancel previous timeout
|
3236 |
|
|
}
|
3237 |
|
|
timeoutPromise = $timeout(function () {
|
3238 |
|
|
getMatchesAsync(inputValue);
|
3239 |
|
|
}, waitTime);
|
3240 |
|
|
} else {
|
3241 |
|
|
getMatchesAsync(inputValue);
|
3242 |
|
|
}
|
3243 |
|
|
} else {
|
3244 |
|
|
isLoadingSetter(originalScope, false);
|
3245 |
|
|
resetMatches();
|
3246 |
|
|
}
|
3247 |
|
|
|
3248 |
|
|
if (isEditable) {
|
3249 |
|
|
return inputValue;
|
3250 |
|
|
} else {
|
3251 |
|
|
if (!inputValue) {
|
3252 |
|
|
// Reset in case user had typed something previously.
|
3253 |
|
|
modelCtrl.$setValidity('editable', true);
|
3254 |
|
|
return inputValue;
|
3255 |
|
|
} else {
|
3256 |
|
|
modelCtrl.$setValidity('editable', false);
|
3257 |
|
|
return undefined;
|
3258 |
|
|
}
|
3259 |
|
|
}
|
3260 |
|
|
});
|
3261 |
|
|
|
3262 |
|
|
modelCtrl.$formatters.push(function (modelValue) {
|
3263 |
|
|
|
3264 |
|
|
var candidateViewValue, emptyViewValue;
|
3265 |
|
|
var locals = {};
|
3266 |
|
|
|
3267 |
|
|
if (inputFormatter) {
|
3268 |
|
|
|
3269 |
|
|
locals['$model'] = modelValue;
|
3270 |
|
|
return inputFormatter(originalScope, locals);
|
3271 |
|
|
|
3272 |
|
|
} else {
|
3273 |
|
|
|
3274 |
|
|
//it might happen that we don't have enough info to properly render input value
|
3275 |
|
|
//we need to check for this situation and simply return model value if we can't apply custom formatting
|
3276 |
|
|
locals[parserResult.itemName] = modelValue;
|
3277 |
|
|
candidateViewValue = parserResult.viewMapper(originalScope, locals);
|
3278 |
|
|
locals[parserResult.itemName] = undefined;
|
3279 |
|
|
emptyViewValue = parserResult.viewMapper(originalScope, locals);
|
3280 |
|
|
|
3281 |
|
|
return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
|
3282 |
|
|
}
|
3283 |
|
|
});
|
3284 |
|
|
|
3285 |
|
|
scope.select = function (activeIdx) {
|
3286 |
|
|
//called from within the $digest() cycle
|
3287 |
|
|
var locals = {};
|
3288 |
|
|
var model, item;
|
3289 |
|
|
|
3290 |
|
|
locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
|
3291 |
|
|
model = parserResult.modelMapper(originalScope, locals);
|
3292 |
|
|
$setModelValue(originalScope, model);
|
3293 |
|
|
modelCtrl.$setValidity('editable', true);
|
3294 |
|
|
|
3295 |
|
|
onSelectCallback(originalScope, {
|
3296 |
|
|
$item: item,
|
3297 |
|
|
$model: model,
|
3298 |
|
|
$label: parserResult.viewMapper(originalScope, locals)
|
3299 |
|
|
});
|
3300 |
|
|
|
3301 |
|
|
resetMatches();
|
3302 |
|
|
|
3303 |
|
|
//return focus to the input element if a mach was selected via a mouse click event
|
3304 |
|
|
element[0].focus();
|
3305 |
|
|
};
|
3306 |
|
|
|
3307 |
|
|
//bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
|
3308 |
|
|
element.bind('keydown', function (evt) {
|
3309 |
|
|
|
3310 |
|
|
//typeahead is open and an "interesting" key was pressed
|
3311 |
|
|
if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
|
3312 |
|
|
return;
|
3313 |
|
|
}
|
3314 |
|
|
|
3315 |
|
|
evt.preventDefault();
|
3316 |
|
|
|
3317 |
|
|
if (evt.which === 40) {
|
3318 |
|
|
scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
|
3319 |
|
|
scope.$digest();
|
3320 |
|
|
|
3321 |
|
|
} else if (evt.which === 38) {
|
3322 |
|
|
scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
|
3323 |
|
|
scope.$digest();
|
3324 |
|
|
|
3325 |
|
|
} else if (evt.which === 13 || evt.which === 9) {
|
3326 |
|
|
scope.$apply(function () {
|
3327 |
|
|
scope.select(scope.activeIdx);
|
3328 |
|
|
});
|
3329 |
|
|
|
3330 |
|
|
} else if (evt.which === 27) {
|
3331 |
|
|
evt.stopPropagation();
|
3332 |
|
|
|
3333 |
|
|
resetMatches();
|
3334 |
|
|
scope.$digest();
|
3335 |
|
|
}
|
3336 |
|
|
});
|
3337 |
|
|
|
3338 |
|
|
element.bind('blur', function (evt) {
|
3339 |
|
|
hasFocus = false;
|
3340 |
|
|
});
|
3341 |
|
|
|
3342 |
|
|
// Keep reference to click handler to unbind it.
|
3343 |
|
|
var dismissClickHandler = function (evt) {
|
3344 |
|
|
if (element[0] !== evt.target) {
|
3345 |
|
|
resetMatches();
|
3346 |
|
|
scope.$digest();
|
3347 |
|
|
}
|
3348 |
|
|
};
|
3349 |
|
|
|
3350 |
|
|
$document.bind('click', dismissClickHandler);
|
3351 |
|
|
|
3352 |
|
|
originalScope.$on('$destroy', function(){
|
3353 |
|
|
$document.unbind('click', dismissClickHandler);
|
3354 |
|
|
});
|
3355 |
|
|
|
3356 |
|
|
var $popup = $compile(popUpEl)(scope);
|
3357 |
|
|
if ( appendToBody ) {
|
3358 |
|
|
$document.find('body').append($popup);
|
3359 |
|
|
} else {
|
3360 |
|
|
element.after($popup);
|
3361 |
|
|
}
|
3362 |
|
|
}
|
3363 |
|
|
};
|
3364 |
|
|
|
3365 |
|
|
}])
|
3366 |
|
|
|
3367 |
|
|
.directive('typeaheadPopup', function () {
|
3368 |
|
|
return {
|
3369 |
|
|
restrict:'EA',
|
3370 |
|
|
scope:{
|
3371 |
|
|
matches:'=',
|
3372 |
|
|
query:'=',
|
3373 |
|
|
active:'=',
|
3374 |
|
|
position:'=',
|
3375 |
|
|
select:'&'
|
3376 |
|
|
},
|
3377 |
|
|
replace:true,
|
3378 |
|
|
templateUrl:'template/typeahead/typeahead-popup.html',
|
3379 |
|
|
link:function (scope, element, attrs) {
|
3380 |
|
|
|
3381 |
|
|
scope.templateUrl = attrs.templateUrl;
|
3382 |
|
|
|
3383 |
|
|
scope.isOpen = function () {
|
3384 |
|
|
return scope.matches.length > 0;
|
3385 |
|
|
};
|
3386 |
|
|
|
3387 |
|
|
scope.isActive = function (matchIdx) {
|
3388 |
|
|
return scope.active == matchIdx;
|
3389 |
|
|
};
|
3390 |
|
|
|
3391 |
|
|
scope.selectActive = function (matchIdx) {
|
3392 |
|
|
scope.active = matchIdx;
|
3393 |
|
|
};
|
3394 |
|
|
|
3395 |
|
|
scope.selectMatch = function (activeIdx) {
|
3396 |
|
|
scope.select({activeIdx:activeIdx});
|
3397 |
|
|
};
|
3398 |
|
|
}
|
3399 |
|
|
};
|
3400 |
|
|
})
|
3401 |
|
|
|
3402 |
|
|
.directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) {
|
3403 |
|
|
return {
|
3404 |
|
|
restrict:'EA',
|
3405 |
|
|
scope:{
|
3406 |
|
|
index:'=',
|
3407 |
|
|
match:'=',
|
3408 |
|
|
query:'='
|
3409 |
|
|
},
|
3410 |
|
|
link:function (scope, element, attrs) {
|
3411 |
|
|
var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
|
3412 |
|
|
$http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){
|
3413 |
|
|
element.replaceWith($compile(tplContent.trim())(scope));
|
3414 |
|
|
});
|
3415 |
|
|
}
|
3416 |
|
|
};
|
3417 |
|
|
}])
|
3418 |
|
|
|
3419 |
|
|
.filter('typeaheadHighlight', function() {
|
3420 |
|
|
|
3421 |
|
|
function escapeRegexp(queryToEscape) {
|
3422 |
|
|
return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
|
3423 |
|
|
}
|
3424 |
|
|
|
3425 |
|
|
return function(matchItem, query) {
|
3426 |
|
|
return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
|
3427 |
|
|
};
|
3428 |
|
|
});
|
3429 |
|
|
angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
|
3430 |
|
|
$templateCache.put("template/accordion/accordion-group.html",
|
3431 |
|
|
"<div class=\"panel panel-default\">\n" +
|
3432 |
|
|
" <div class=\"panel-heading\">\n" +
|
3433 |
|
|
" <h4 class=\"panel-title\">\n" +
|
3434 |
|
|
" <a class=\"accordion-toggle\" ng-click=\"isOpen = !isOpen\" accordion-transclude=\"heading\">{{heading}}</a>\n" +
|
3435 |
|
|
" </h4>\n" +
|
3436 |
|
|
" </div>\n" +
|
3437 |
|
|
" <div class=\"panel-collapse\" collapse=\"!isOpen\">\n" +
|
3438 |
|
|
" <div class=\"panel-body\" ng-transclude></div>\n" +
|
3439 |
|
|
" </div>\n" +
|
3440 |
|
|
"</div>");
|
3441 |
|
|
}]);
|
3442 |
|
|
|
3443 |
|
|
angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) {
|
3444 |
|
|
$templateCache.put("template/accordion/accordion.html",
|
3445 |
|
|
"<div class=\"panel-group\" ng-transclude></div>");
|
3446 |
|
|
}]);
|
3447 |
|
|
|
3448 |
|
|
angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) {
|
3449 |
|
|
$templateCache.put("template/alert/alert.html",
|
3450 |
|
|
"<div class='alert' ng-class='\"alert-\" + (type || \"warning\")'>\n" +
|
3451 |
|
|
" <button ng-show='closeable' type='button' class='close' ng-click='close()'>×</button>\n" +
|
3452 |
|
|
" <div ng-transclude></div>\n" +
|
3453 |
|
|
"</div>\n" +
|
3454 |
|
|
"");
|
3455 |
|
|
}]);
|
3456 |
|
|
|
3457 |
|
|
angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) {
|
3458 |
|
|
$templateCache.put("template/carousel/carousel.html",
|
3459 |
|
|
"<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\">\n" +
|
3460 |
|
|
" <ol class=\"carousel-indicators\" ng-show=\"slides().length > 1\">\n" +
|
3461 |
|
|
" <li ng-repeat=\"slide in slides()\" ng-class=\"{active: isActive(slide)}\" ng-click=\"select(slide)\"></li>\n" +
|
3462 |
|
|
" </ol>\n" +
|
3463 |
|
|
" <div class=\"carousel-inner\" ng-transclude></div>\n" +
|
3464 |
|
|
" <a class=\"left carousel-control\" ng-click=\"prev()\" ng-show=\"slides().length > 1\"><span class=\"icon-prev\"></span></a>\n" +
|
3465 |
|
|
" <a class=\"right carousel-control\" ng-click=\"next()\" ng-show=\"slides().length > 1\"><span class=\"icon-next\"></span></a>\n" +
|
3466 |
|
|
"</div>\n" +
|
3467 |
|
|
"");
|
3468 |
|
|
}]);
|
3469 |
|
|
|
3470 |
|
|
angular.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache) {
|
3471 |
|
|
$templateCache.put("template/carousel/slide.html",
|
3472 |
|
|
"<div ng-class=\"{\n" +
|
3473 |
|
|
" 'active': leaving || (active && !entering),\n" +
|
3474 |
|
|
" 'prev': (next || active) && direction=='prev',\n" +
|
3475 |
|
|
" 'next': (next || active) && direction=='next',\n" +
|
3476 |
|
|
" 'right': direction=='prev',\n" +
|
3477 |
|
|
" 'left': direction=='next'\n" +
|
3478 |
|
|
" }\" class=\"item text-center\" ng-transclude></div>\n" +
|
3479 |
|
|
"");
|
3480 |
|
|
}]);
|
3481 |
|
|
|
3482 |
|
|
angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
|
3483 |
|
|
$templateCache.put("template/datepicker/datepicker.html",
|
3484 |
|
|
"<table>\n" +
|
3485 |
|
|
" <thead>\n" +
|
3486 |
|
|
" <tr>\n" +
|
3487 |
|
|
" <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
|
3488 |
|
|
" <th colspan=\"{{rows[0].length - 2 + showWeekNumbers}}\"><button type=\"button\" class=\"btn btn-default btn-sm btn-block\" ng-click=\"toggleMode()\"><strong>{{title}}</strong></button></th>\n" +
|
3489 |
|
|
" <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
|
3490 |
|
|
" </tr>\n" +
|
3491 |
|
|
" <tr ng-show=\"labels.length > 0\" class=\"h6\">\n" +
|
3492 |
|
|
" <th ng-show=\"showWeekNumbers\" class=\"text-center\">#</th>\n" +
|
3493 |
|
|
" <th ng-repeat=\"label in labels\" class=\"text-center\">{{label}}</th>\n" +
|
3494 |
|
|
" </tr>\n" +
|
3495 |
|
|
" </thead>\n" +
|
3496 |
|
|
" <tbody>\n" +
|
3497 |
|
|
" <tr ng-repeat=\"row in rows\">\n" +
|
3498 |
|
|
" <td ng-show=\"showWeekNumbers\" class=\"text-center\"><em>{{ getWeekNumber(row) }}</em></td>\n" +
|
3499 |
|
|
" <td ng-repeat=\"dt in row\" class=\"text-center\">\n" +
|
3500 |
|
|
" <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default btn-sm\" ng-class=\"{'btn-info': dt.selected}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\"><span ng-class=\"{'text-muted': dt.secondary}\">{{dt.label}}</span></button>\n" +
|
3501 |
|
|
" </td>\n" +
|
3502 |
|
|
" </tr>\n" +
|
3503 |
|
|
" </tbody>\n" +
|
3504 |
|
|
"</table>\n" +
|
3505 |
|
|
"");
|
3506 |
|
|
}]);
|
3507 |
|
|
|
3508 |
|
|
angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) {
|
3509 |
|
|
$templateCache.put("template/datepicker/popup.html",
|
3510 |
|
|
"<ul class=\"dropdown-menu\" ng-style=\"{display: (isOpen && 'block') || 'none', top: position.top+'px', left: position.left+'px'}\">\n" +
|
3511 |
|
|
" <li ng-transclude></li>\n" +
|
3512 |
|
|
" <li ng-show=\"showButtonBar\" style=\"padding:10px 9px 2px\">\n" +
|
3513 |
|
|
" <span class=\"btn-group\">\n" +
|
3514 |
|
|
" <button type=\"button\" class=\"btn btn-sm btn-info\" ng-click=\"today()\">{{currentText}}</button>\n" +
|
3515 |
|
|
" <button type=\"button\" class=\"btn btn-sm btn-default\" ng-click=\"showWeeks = ! showWeeks\" ng-class=\"{active: showWeeks}\">{{toggleWeeksText}}</button>\n" +
|
3516 |
|
|
" <button type=\"button\" class=\"btn btn-sm btn-danger\" ng-click=\"clear()\">{{clearText}}</button>\n" +
|
3517 |
|
|
" </span>\n" +
|
3518 |
|
|
" <button type=\"button\" class=\"btn btn-sm btn-success pull-right\" ng-click=\"isOpen = false\">{{closeText}}</button>\n" +
|
3519 |
|
|
" </li>\n" +
|
3520 |
|
|
"</ul>\n" +
|
3521 |
|
|
"");
|
3522 |
|
|
}]);
|
3523 |
|
|
|
3524 |
|
|
angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
|
3525 |
|
|
$templateCache.put("template/modal/backdrop.html",
|
3526 |
|
|
"<div class=\"modal-backdrop fade\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1040 + index*10}\"></div>");
|
3527 |
|
|
}]);
|
3528 |
|
|
|
3529 |
|
|
angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) {
|
3530 |
|
|
$templateCache.put("template/modal/window.html",
|
3531 |
|
|
"<div tabindex=\"-1\" class=\"modal fade {{ windowClass }}\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\" ng-click=\"close($event)\">\n" +
|
3532 |
|
|
" <div class=\"modal-dialog\"><div class=\"modal-content\" ng-transclude></div></div>\n" +
|
3533 |
|
|
"</div>");
|
3534 |
|
|
}]);
|
3535 |
|
|
|
3536 |
|
|
angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) {
|
3537 |
|
|
$templateCache.put("template/pagination/pager.html",
|
3538 |
|
|
"<ul class=\"pager\">\n" +
|
3539 |
|
|
" <li ng-repeat=\"page in pages\" ng-class=\"{disabled: page.disabled, previous: page.previous, next: page.next}\"><a ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" +
|
3540 |
|
|
"</ul>");
|
3541 |
|
|
}]);
|
3542 |
|
|
|
3543 |
|
|
angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
|
3544 |
|
|
$templateCache.put("template/pagination/pagination.html",
|
3545 |
|
|
"<ul class=\"pagination\">\n" +
|
3546 |
|
|
" <li ng-repeat=\"page in pages\" ng-class=\"{active: page.active, disabled: page.disabled}\"><a ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" +
|
3547 |
|
|
"</ul>");
|
3548 |
|
|
}]);
|
3549 |
|
|
|
3550 |
|
|
angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) {
|
3551 |
|
|
$templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html",
|
3552 |
|
|
"<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
|
3553 |
|
|
" <div class=\"tooltip-arrow\"></div>\n" +
|
3554 |
|
|
" <div class=\"tooltip-inner\" bind-html-unsafe=\"content\"></div>\n" +
|
3555 |
|
|
"</div>\n" +
|
3556 |
|
|
"");
|
3557 |
|
|
}]);
|
3558 |
|
|
|
3559 |
|
|
angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
|
3560 |
|
|
$templateCache.put("template/tooltip/tooltip-popup.html",
|
3561 |
|
|
"<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
|
3562 |
|
|
" <div class=\"tooltip-arrow\"></div>\n" +
|
3563 |
|
|
" <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
|
3564 |
|
|
"</div>\n" +
|
3565 |
|
|
"");
|
3566 |
|
|
}]);
|
3567 |
|
|
|
3568 |
|
|
angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) {
|
3569 |
|
|
$templateCache.put("template/popover/popover.html",
|
3570 |
|
|
"<div class=\"popover {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
|
3571 |
|
|
" <div class=\"arrow\"></div>\n" +
|
3572 |
|
|
"\n" +
|
3573 |
|
|
" <div class=\"popover-inner\">\n" +
|
3574 |
|
|
" <h3 class=\"popover-title\" ng-bind=\"title\" ng-show=\"title\"></h3>\n" +
|
3575 |
|
|
" <div class=\"popover-content\" ng-bind=\"content\"></div>\n" +
|
3576 |
|
|
" </div>\n" +
|
3577 |
|
|
"</div>\n" +
|
3578 |
|
|
"");
|
3579 |
|
|
}]);
|
3580 |
|
|
|
3581 |
|
|
angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) {
|
3582 |
|
|
$templateCache.put("template/progressbar/bar.html",
|
3583 |
|
|
"<div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" ng-transclude></div>");
|
3584 |
|
|
}]);
|
3585 |
|
|
|
3586 |
|
|
angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) {
|
3587 |
|
|
$templateCache.put("template/progressbar/progress.html",
|
3588 |
|
|
"<div class=\"progress\" ng-transclude></div>");
|
3589 |
|
|
}]);
|
3590 |
|
|
|
3591 |
|
|
angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) {
|
3592 |
|
|
$templateCache.put("template/progressbar/progressbar.html",
|
3593 |
|
|
"<div class=\"progress\"><div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" ng-transclude></div></div>");
|
3594 |
|
|
}]);
|
3595 |
|
|
|
3596 |
|
|
angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
|
3597 |
|
|
$templateCache.put("template/rating/rating.html",
|
3598 |
|
|
"<span ng-mouseleave=\"reset()\">\n" +
|
3599 |
|
|
" <i ng-repeat=\"r in range\" ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"glyphicon\" ng-class=\"$index < val && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')\"></i>\n" +
|
3600 |
|
|
"</span>");
|
3601 |
|
|
}]);
|
3602 |
|
|
|
3603 |
|
|
angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
|
3604 |
|
|
$templateCache.put("template/tabs/tab.html",
|
3605 |
|
|
"<li ng-class=\"{active: active, disabled: disabled}\">\n" +
|
3606 |
|
|
" <a ng-click=\"select()\" tab-heading-transclude>{{heading}}</a>\n" +
|
3607 |
|
|
"</li>\n" +
|
3608 |
|
|
"");
|
3609 |
|
|
}]);
|
3610 |
|
|
|
3611 |
|
|
angular.module("template/tabs/tabset-titles.html", []).run(["$templateCache", function($templateCache) {
|
3612 |
|
|
$templateCache.put("template/tabs/tabset-titles.html",
|
3613 |
|
|
"<ul class=\"nav {{type && 'nav-' + type}}\" ng-class=\"{'nav-stacked': vertical}\">\n" +
|
3614 |
|
|
"</ul>\n" +
|
3615 |
|
|
"");
|
3616 |
|
|
}]);
|
3617 |
|
|
|
3618 |
|
|
angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
|
3619 |
|
|
$templateCache.put("template/tabs/tabset.html",
|
3620 |
|
|
"\n" +
|
3621 |
|
|
"<div class=\"tabbable\">\n" +
|
3622 |
|
|
" <ul class=\"nav {{type && 'nav-' + type}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
|
3623 |
|
|
" <div class=\"tab-content\">\n" +
|
3624 |
|
|
" <div class=\"tab-pane\" \n" +
|
3625 |
|
|
" ng-repeat=\"tab in tabs\" \n" +
|
3626 |
|
|
" ng-class=\"{active: tab.active}\"\n" +
|
3627 |
|
|
" tab-content-transclude=\"tab\">\n" +
|
3628 |
|
|
" </div>\n" +
|
3629 |
|
|
" </div>\n" +
|
3630 |
|
|
"</div>\n" +
|
3631 |
|
|
"");
|
3632 |
|
|
}]);
|
3633 |
|
|
|
3634 |
|
|
angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) {
|
3635 |
|
|
$templateCache.put("template/timepicker/timepicker.html",
|
3636 |
|
|
"<table>\n" +
|
3637 |
|
|
" <tbody>\n" +
|
3638 |
|
|
" <tr class=\"text-center\">\n" +
|
3639 |
|
|
" <td><a ng-click=\"incrementHours()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
|
3640 |
|
|
" <td> </td>\n" +
|
3641 |
|
|
" <td><a ng-click=\"incrementMinutes()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
|
3642 |
|
|
" <td ng-show=\"showMeridian\"></td>\n" +
|
3643 |
|
|
" </tr>\n" +
|
3644 |
|
|
" <tr>\n" +
|
3645 |
|
|
" <td style=\"width:50px;\" class=\"form-group\" ng-class=\"{'has-error': invalidHours}\">\n" +
|
3646 |
|
|
" <input type=\"text\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-mousewheel=\"incrementHours()\" ng-readonly=\"readonlyInput\" maxlength=\"2\">\n" +
|
3647 |
|
|
" </td>\n" +
|
3648 |
|
|
" <td>:</td>\n" +
|
3649 |
|
|
" <td style=\"width:50px;\" class=\"form-group\" ng-class=\"{'has-error': invalidMinutes}\">\n" +
|
3650 |
|
|
" <input type=\"text\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\">\n" +
|
3651 |
|
|
" </td>\n" +
|
3652 |
|
|
" <td ng-show=\"showMeridian\"><button type=\"button\" class=\"btn btn-default text-center\" ng-click=\"toggleMeridian()\">{{meridian}}</button></td>\n" +
|
3653 |
|
|
" </tr>\n" +
|
3654 |
|
|
" <tr class=\"text-center\">\n" +
|
3655 |
|
|
" <td><a ng-click=\"decrementHours()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
|
3656 |
|
|
" <td> </td>\n" +
|
3657 |
|
|
" <td><a ng-click=\"decrementMinutes()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
|
3658 |
|
|
" <td ng-show=\"showMeridian\"></td>\n" +
|
3659 |
|
|
" </tr>\n" +
|
3660 |
|
|
" </tbody>\n" +
|
3661 |
|
|
"</table>\n" +
|
3662 |
|
|
"");
|
3663 |
|
|
}]);
|
3664 |
|
|
|
3665 |
|
|
angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) {
|
3666 |
|
|
$templateCache.put("template/typeahead/typeahead-match.html",
|
3667 |
|
|
"<a tabindex=\"-1\" bind-html-unsafe=\"match.label | typeaheadHighlight:query\"></a>");
|
3668 |
|
|
}]);
|
3669 |
|
|
|
3670 |
|
|
angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
|
3671 |
|
|
$templateCache.put("template/typeahead/typeahead-popup.html",
|
3672 |
|
|
"<ul class=\"dropdown-menu\" ng-style=\"{display: isOpen()&&'block' || 'none', top: position.top+'px', left: position.left+'px'}\">\n" +
|
3673 |
|
|
" <li ng-repeat=\"match in matches\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index)\">\n" +
|
3674 |
|
|
" <div typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
|
3675 |
|
|
" </li>\n" +
|
3676 |
|
|
"</ul>");
|
3677 |
|
|
}]);
|