You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by bm...@apache.org on 2013/07/13 00:16:14 UTC
[4/7] Replaced bootstrap.js with angular-ui.js
http://git-wip-us.apache.org/repos/asf/mesos/blob/9929d2b8/src/webui/master/static/ui-bootstrap-tpls-0.4.0.js
----------------------------------------------------------------------
diff --git a/src/webui/master/static/ui-bootstrap-tpls-0.4.0.js b/src/webui/master/static/ui-bootstrap-tpls-0.4.0.js
new file mode 100644
index 0000000..2e9eeb9
--- /dev/null
+++ b/src/webui/master/static/ui-bootstrap-tpls-0.4.0.js
@@ -0,0 +1,3165 @@
+angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.datepicker","ui.bootstrap.dialog","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.position","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
+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/dialog/message.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/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead.html"]);
+angular.module('ui.bootstrap.transition', [])
+
+/**
+ * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
+ * @param {DOMElement} element The DOMElement that will be animated.
+ * @param {string|object|function} trigger The thing that will cause the transition to start:
+ * - As a string, it represents the css class to be added to the element.
+ * - As an object, it represents a hash of style attributes to be applied to the element.
+ * - As a function, it represents a function to be called that will cause the transition to occur.
+ * @return {Promise} A promise that is resolved when the transition finishes.
+ */
+.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
+
+ var $transition = function(element, trigger, options) {
+ options = options || {};
+ var deferred = $q.defer();
+ var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
+
+ var transitionEndHandler = function(event) {
+ $rootScope.$apply(function() {
+ element.unbind(endEventName, transitionEndHandler);
+ deferred.resolve(element);
+ });
+ };
+
+ if (endEventName) {
+ element.bind(endEventName, transitionEndHandler);
+ }
+
+ // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
+ $timeout(function() {
+ if ( angular.isString(trigger) ) {
+ element.addClass(trigger);
+ } else if ( angular.isFunction(trigger) ) {
+ trigger(element);
+ } else if ( angular.isObject(trigger) ) {
+ element.css(trigger);
+ }
+ //If browser does not support transitions, instantly resolve
+ if ( !endEventName ) {
+ deferred.resolve(element);
+ }
+ });
+
+ // Add our custom cancel function to the promise that is returned
+ // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
+ // i.e. it will therefore never raise a transitionEnd event for that transition
+ deferred.promise.cancel = function() {
+ if ( endEventName ) {
+ element.unbind(endEventName, transitionEndHandler);
+ }
+ deferred.reject('Transition cancelled');
+ };
+
+ return deferred.promise;
+ };
+
+ // Work out the name of the transitionEnd event
+ var transElement = document.createElement('trans');
+ var transitionEndEventNames = {
+ 'WebkitTransition': 'webkitTransitionEnd',
+ 'MozTransition': 'transitionend',
+ 'OTransition': 'oTransitionEnd',
+ 'transition': 'transitionend'
+ };
+ var animationEndEventNames = {
+ 'WebkitTransition': 'webkitAnimationEnd',
+ 'MozTransition': 'animationend',
+ 'OTransition': 'oAnimationEnd',
+ 'transition': 'animationend'
+ };
+ function findEndEventName(endEventNames) {
+ for (var name in endEventNames){
+ if (transElement.style[name] !== undefined) {
+ return endEventNames[name];
+ }
+ }
+ }
+ $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
+ $transition.animationEndEventName = findEndEventName(animationEndEventNames);
+ return $transition;
+}]);
+
+angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition'])
+
+// The collapsible directive indicates a block of html that will expand and collapse
+.directive('collapse', ['$transition', function($transition) {
+ // CSS transitions don't work with height: auto, so we have to manually change the height to a
+ // specific value and then once the animation completes, we can reset the height to auto.
+ // Unfortunately if you do this while the CSS transitions are specified (i.e. in the CSS class
+ // "collapse") then you trigger a change to height 0 in between.
+ // The fix is to remove the "collapse" CSS class while changing the height back to auto - phew!
+ var fixUpHeight = function(scope, element, height) {
+ // We remove the collapse CSS class to prevent a transition when we change to height: auto
+ element.removeClass('collapse');
+ element.css({ height: height });
+ // It appears that reading offsetWidth makes the browser realise that we have changed the
+ // height already :-/
+ var x = element[0].offsetWidth;
+ element.addClass('collapse');
+ };
+
+ return {
+ link: function(scope, element, attrs) {
+
+ var isCollapsed;
+ var initialAnimSkip = true;
+ scope.$watch(function (){ return element[0].scrollHeight; }, function (value) {
+ //The listener is called when scollHeight changes
+ //It actually does on 2 scenarios:
+ // 1. Parent is set to display none
+ // 2. angular bindings inside are resolved
+ //When we have a change of scrollHeight we are setting again the correct height if the group is opened
+ if (element[0].scrollHeight !== 0) {
+ if (!isCollapsed) {
+ if (initialAnimSkip) {
+ fixUpHeight(scope, element, element[0].scrollHeight + 'px');
+ } else {
+ fixUpHeight(scope, element, 'auto');
+ }
+ }
+ }
+ });
+
+ scope.$watch(attrs.collapse, function(value) {
+ if (value) {
+ collapse();
+ } else {
+ expand();
+ }
+ });
+
+
+ var currentTransition;
+ var doTransition = function(change) {
+ if ( currentTransition ) {
+ currentTransition.cancel();
+ }
+ currentTransition = $transition(element,change);
+ currentTransition.then(
+ function() { currentTransition = undefined; },
+ function() { currentTransition = undefined; }
+ );
+ return currentTransition;
+ };
+
+ var expand = function() {
+ if (initialAnimSkip) {
+ initialAnimSkip = false;
+ if ( !isCollapsed ) {
+ fixUpHeight(scope, element, 'auto');
+ }
+ } else {
+ doTransition({ height : element[0].scrollHeight + 'px' })
+ .then(function() {
+ // This check ensures that we don't accidentally update the height if the user has closed
+ // the group while the animation was still running
+ if ( !isCollapsed ) {
+ fixUpHeight(scope, element, 'auto');
+ }
+ });
+ }
+ isCollapsed = false;
+ };
+
+ var collapse = function() {
+ isCollapsed = true;
+ if (initialAnimSkip) {
+ initialAnimSkip = false;
+ fixUpHeight(scope, element, 0);
+ } else {
+ fixUpHeight(scope, element, element[0].scrollHeight + 'px');
+ doTransition({'height':'0'});
+ }
+ };
+ }
+ };
+}]);
+
+angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
+
+.constant('accordionConfig', {
+ closeOthers: true
+})
+
+.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {
+
+ // This array keeps track of the accordion groups
+ this.groups = [];
+
+ // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
+ this.closeOthers = function(openGroup) {
+ var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
+ if ( closeOthers ) {
+ angular.forEach(this.groups, function (group) {
+ if ( group !== openGroup ) {
+ group.isOpen = false;
+ }
+ });
+ }
+ };
+
+ // This is called from the accordion-group directive to add itself to the accordion
+ this.addGroup = function(groupScope) {
+ var that = this;
+ this.groups.push(groupScope);
+
+ groupScope.$on('$destroy', function (event) {
+ that.removeGroup(groupScope);
+ });
+ };
+
+ // This is called from the accordion-group directive when to remove itself
+ this.removeGroup = function(group) {
+ var index = this.groups.indexOf(group);
+ if ( index !== -1 ) {
+ this.groups.splice(this.groups.indexOf(group), 1);
+ }
+ };
+
+}])
+
+// The accordion directive simply sets up the directive controller
+// and adds an accordion CSS class to itself element.
+.directive('accordion', function () {
+ return {
+ restrict:'EA',
+ controller:'AccordionController',
+ transclude: true,
+ replace: false,
+ templateUrl: 'template/accordion/accordion.html'
+ };
+})
+
+// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
+.directive('accordionGroup', ['$parse', '$transition', '$timeout', function($parse, $transition, $timeout) {
+ return {
+ require:'^accordion', // We need this directive to be inside an accordion
+ restrict:'EA',
+ transclude:true, // It transcludes the contents of the directive into the template
+ replace: true, // The element containing the directive will be replaced with the template
+ templateUrl:'template/accordion/accordion-group.html',
+ scope:{ heading:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope
+ controller: ['$scope', function($scope) {
+ this.setHeading = function(element) {
+ this.heading = element;
+ };
+ }],
+ link: function(scope, element, attrs, accordionCtrl) {
+ var getIsOpen, setIsOpen;
+
+ accordionCtrl.addGroup(scope);
+
+ scope.isOpen = false;
+
+ if ( attrs.isOpen ) {
+ getIsOpen = $parse(attrs.isOpen);
+ setIsOpen = getIsOpen.assign;
+
+ scope.$watch(
+ function watchIsOpen() { return getIsOpen(scope.$parent); },
+ function updateOpen(value) { scope.isOpen = value; }
+ );
+
+ scope.isOpen = getIsOpen ? getIsOpen(scope.$parent) : false;
+ }
+
+ scope.$watch('isOpen', function(value) {
+ if ( value ) {
+ accordionCtrl.closeOthers(scope);
+ }
+ if ( setIsOpen ) {
+ setIsOpen(scope.$parent, value);
+ }
+ });
+ }
+ };
+}])
+
+// Use accordion-heading below an accordion-group to provide a heading containing HTML
+// <accordion-group>
+// <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading>
+// </accordion-group>
+.directive('accordionHeading', function() {
+ return {
+ restrict: 'EA',
+ transclude: true, // Grab the contents to be used as the heading
+ template: '', // In effect remove this element!
+ replace: true,
+ require: '^accordionGroup',
+ compile: function(element, attr, transclude) {
+ return function link(scope, element, attr, accordionGroupCtrl) {
+ // Pass the heading to the accordion-group controller
+ // so that it can be transcluded into the right place in the template
+ // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
+ accordionGroupCtrl.setHeading(transclude(scope, function() {}));
+ };
+ }
+ };
+})
+
+// Use in the accordion-group template to indicate where you want the heading to be transcluded
+// You must provide the property on the accordion-group controller that will hold the transcluded element
+// <div class="accordion-group">
+// <div class="accordion-heading" ><a ... accordion-transclude="heading">...</a></div>
+// ...
+// </div>
+.directive('accordionTransclude', function() {
+ return {
+ require: '^accordionGroup',
+ link: function(scope, element, attr, controller) {
+ scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
+ if ( heading ) {
+ element.html('');
+ element.append(heading);
+ }
+ });
+ }
+ };
+});
+
+angular.module("ui.bootstrap.alert", []).directive('alert', function () {
+ return {
+ restrict:'EA',
+ templateUrl:'template/alert/alert.html',
+ transclude:true,
+ replace:true,
+ scope: {
+ type: '=',
+ close: '&'
+ },
+ link: function(scope, iElement, iAttrs, controller) {
+ scope.closeable = "close" in iAttrs;
+ }
+ };
+});
+
+angular.module('ui.bootstrap.buttons', [])
+
+ .constant('buttonConfig', {
+ activeClass:'active',
+ toggleEvent:'click'
+ })
+
+ .directive('btnRadio', ['buttonConfig', function (buttonConfig) {
+ var activeClass = buttonConfig.activeClass || 'active';
+ var toggleEvent = buttonConfig.toggleEvent || 'click';
+
+ return {
+
+ require:'ngModel',
+ link:function (scope, element, attrs, ngModelCtrl) {
+
+ //model -> UI
+ ngModelCtrl.$render = function () {
+ element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
+ };
+
+ //ui->model
+ element.bind(toggleEvent, function () {
+ if (!element.hasClass(activeClass)) {
+ scope.$apply(function () {
+ ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio));
+ ngModelCtrl.$render();
+ });
+ }
+ });
+ }
+ };
+}])
+
+ .directive('btnCheckbox', ['buttonConfig', function (buttonConfig) {
+
+ var activeClass = buttonConfig.activeClass || 'active';
+ var toggleEvent = buttonConfig.toggleEvent || 'click';
+
+ return {
+ require:'ngModel',
+ link:function (scope, element, attrs, ngModelCtrl) {
+
+ var trueValue = scope.$eval(attrs.btnCheckboxTrue);
+ var falseValue = scope.$eval(attrs.btnCheckboxFalse);
+
+ trueValue = angular.isDefined(trueValue) ? trueValue : true;
+ falseValue = angular.isDefined(falseValue) ? falseValue : false;
+
+ //model -> UI
+ ngModelCtrl.$render = function () {
+ element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, trueValue));
+ };
+
+ //ui->model
+ element.bind(toggleEvent, function () {
+ scope.$apply(function () {
+ ngModelCtrl.$setViewValue(element.hasClass(activeClass) ? falseValue : trueValue);
+ ngModelCtrl.$render();
+ });
+ });
+ }
+ };
+}]);
+/**
+* @ngdoc overview
+* @name ui.bootstrap.carousel
+*
+* @description
+* AngularJS version of an image carousel.
+*
+*/
+angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
+.controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope, $timeout, $transition, $q) {
+ var self = this,
+ slides = self.slides = [],
+ currentIndex = -1,
+ currentTimeout, isPlaying;
+ self.currentSlide = null;
+
+ /* direction: "prev" or "next" */
+ self.select = function(nextSlide, direction) {
+ var nextIndex = slides.indexOf(nextSlide);
+ //Decide direction if it's not given
+ if (direction === undefined) {
+ direction = nextIndex > currentIndex ? "next" : "prev";
+ }
+ if (nextSlide && nextSlide !== self.currentSlide) {
+ if ($scope.$currentTransition) {
+ $scope.$currentTransition.cancel();
+ //Timeout so ng-class in template has time to fix classes for finished slide
+ $timeout(goNext);
+ } else {
+ goNext();
+ }
+ }
+ function goNext() {
+ //If we have a slide to transition from and we have a transition type and we're allowed, go
+ if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) {
+ //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime
+ nextSlide.$element.addClass(direction);
+ nextSlide.$element[0].offsetWidth = nextSlide.$element[0].offsetWidth; //force reflow
+
+ //Set all other slides to stop doing their stuff for the new transition
+ angular.forEach(slides, function(slide) {
+ angular.extend(slide, {direction: '', entering: false, leaving: false, active: false});
+ });
+ angular.extend(nextSlide, {direction: direction, active: true, entering: true});
+ angular.extend(self.currentSlide||{}, {direction: direction, leaving: true});
+
+ $scope.$currentTransition = $transition(nextSlide.$element, {});
+ //We have to create new pointers inside a closure since next & current will change
+ (function(next,current) {
+ $scope.$currentTransition.then(
+ function(){ transitionDone(next, current); },
+ function(){ transitionDone(next, current); }
+ );
+ }(nextSlide, self.currentSlide));
+ } else {
+ transitionDone(nextSlide, self.currentSlide);
+ }
+ self.currentSlide = nextSlide;
+ currentIndex = nextIndex;
+ //every time you change slides, reset the timer
+ restartTimer();
+ }
+ function transitionDone(next, current) {
+ angular.extend(next, {direction: '', active: true, leaving: false, entering: false});
+ angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false});
+ $scope.$currentTransition = null;
+ }
+ };
+
+ /* Allow outside people to call indexOf on slides array */
+ self.indexOfSlide = function(slide) {
+ return slides.indexOf(slide);
+ };
+
+ $scope.next = function() {
+ var newIndex = (currentIndex + 1) % slides.length;
+
+ //Prevent this user-triggered transition from occurring if there is already one in progress
+ if (!$scope.$currentTransition) {
+ return self.select(slides[newIndex], 'next');
+ }
+ };
+
+ $scope.prev = function() {
+ var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1;
+
+ //Prevent this user-triggered transition from occurring if there is already one in progress
+ if (!$scope.$currentTransition) {
+ return self.select(slides[newIndex], 'prev');
+ }
+ };
+
+ $scope.select = function(slide) {
+ self.select(slide);
+ };
+
+ $scope.isActive = function(slide) {
+ return self.currentSlide === slide;
+ };
+
+ $scope.slides = function() {
+ return slides;
+ };
+
+ $scope.$watch('interval', restartTimer);
+ function restartTimer() {
+ if (currentTimeout) {
+ $timeout.cancel(currentTimeout);
+ }
+ function go() {
+ if (isPlaying) {
+ $scope.next();
+ restartTimer();
+ } else {
+ $scope.pause();
+ }
+ }
+ var interval = +$scope.interval;
+ if (!isNaN(interval) && interval>=0) {
+ currentTimeout = $timeout(go, interval);
+ }
+ }
+ $scope.play = function() {
+ if (!isPlaying) {
+ isPlaying = true;
+ restartTimer();
+ }
+ };
+ $scope.pause = function() {
+ if (!$scope.noPause) {
+ isPlaying = false;
+ if (currentTimeout) {
+ $timeout.cancel(currentTimeout);
+ }
+ }
+ };
+
+ self.addSlide = function(slide, element) {
+ slide.$element = element;
+ slides.push(slide);
+ //if this is the first slide or the slide is set to active, select it
+ if(slides.length === 1 || slide.active) {
+ self.select(slides[slides.length-1]);
+ if (slides.length == 1) {
+ $scope.play();
+ }
+ } else {
+ slide.active = false;
+ }
+ };
+
+ self.removeSlide = function(slide) {
+ //get the index of the slide inside the carousel
+ var index = slides.indexOf(slide);
+ slides.splice(index, 1);
+ if (slides.length > 0 && slide.active) {
+ if (index >= slides.length) {
+ self.select(slides[index-1]);
+ } else {
+ self.select(slides[index]);
+ }
+ } else if (currentIndex > index) {
+ currentIndex--;
+ }
+ };
+}])
+
+/**
+ * @ngdoc directive
+ * @name ui.bootstrap.carousel.directive:carousel
+ * @restrict EA
+ *
+ * @description
+ * Carousel is the outer container for a set of image 'slides' to showcase.
+ *
+ * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide.
+ * @param {boolean=} noTransition Whether to disable transitions on the carousel.
+ * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover).
+ *
+ * @example
+<example module="ui.bootstrap">
+ <file name="index.html">
+ <carousel>
+ <slide>
+ <img src="http://placekitten.com/150/150" style="margin:auto;">
+ <div class="carousel-caption">
+ <p>Beautiful!</p>
+ </div>
+ </slide>
+ <slide>
+ <img src="http://placekitten.com/100/150" style="margin:auto;">
+ <div class="carousel-caption">
+ <p>D'aww!</p>
+ </div>
+ </slide>
+ </carousel>
+ </file>
+ <file name="demo.css">
+ .carousel-indicators {
+ top: auto;
+ bottom: 15px;
+ }
+ </file>
+</example>
+ */
+.directive('carousel', [function() {
+ return {
+ restrict: 'EA',
+ transclude: true,
+ replace: true,
+ controller: 'CarouselController',
+ require: 'carousel',
+ templateUrl: 'template/carousel/carousel.html',
+ scope: {
+ interval: '=',
+ noTransition: '=',
+ noPause: '='
+ }
+ };
+}])
+
+/**
+ * @ngdoc directive
+ * @name ui.bootstrap.carousel.directive:slide
+ * @restrict EA
+ *
+ * @description
+ * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element.
+ *
+ * @param {boolean=} active Model binding, whether or not this slide is currently active.
+ *
+ * @example
+<example module="ui.bootstrap">
+ <file name="index.html">
+<div ng-controller="CarouselDemoCtrl">
+ <carousel>
+ <slide ng-repeat="slide in slides" active="slide.active">
+ <img ng-src="{{slide.image}}" style="margin:auto;">
+ <div class="carousel-caption">
+ <h4>Slide {{$index}}</h4>
+ <p>{{slide.text}}</p>
+ </div>
+ </slide>
+ </carousel>
+ <div class="row-fluid">
+ <div class="span6">
+ <ul>
+ <li ng-repeat="slide in slides">
+ <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>
+ {{$index}}: {{slide.text}}
+ </li>
+ </ul>
+ <a class="btn" ng-click="addSlide()">Add Slide</a>
+ </div>
+ <div class="span6">
+ Interval, in milliseconds: <input type="number" ng-model="myInterval">
+ <br />Enter a negative number to stop the interval.
+ </div>
+ </div>
+</div>
+ </file>
+ <file name="script.js">
+function CarouselDemoCtrl($scope) {
+ $scope.myInterval = 5000;
+ var slides = $scope.slides = [];
+ $scope.addSlide = function() {
+ var newWidth = 200 + ((slides.length + (25 * slides.length)) % 150);
+ slides.push({
+ image: 'http://placekitten.com/' + newWidth + '/200',
+ text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' '
+ ['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4]
+ });
+ };
+ for (var i=0; i<4; i++) $scope.addSlide();
+}
+ </file>
+ <file name="demo.css">
+ .carousel-indicators {
+ top: auto;
+ bottom: 15px;
+ }
+ </file>
+</example>
+*/
+
+.directive('slide', ['$parse', function($parse) {
+ return {
+ require: '^carousel',
+ restrict: 'EA',
+ transclude: true,
+ replace: true,
+ templateUrl: 'template/carousel/slide.html',
+ scope: {
+ },
+ link: function (scope, element, attrs, carouselCtrl) {
+ //Set up optional 'active' = binding
+ if (attrs.active) {
+ var getActive = $parse(attrs.active);
+ var setActive = getActive.assign;
+ var lastValue = scope.active = getActive(scope.$parent);
+ scope.$watch(function parentActiveWatch() {
+ var parentActive = getActive(scope.$parent);
+
+ if (parentActive !== scope.active) {
+ // we are out of sync and need to copy
+ if (parentActive !== lastValue) {
+ // parent changed and it has precedence
+ lastValue = scope.active = parentActive;
+ } else {
+ // if the parent can be assigned then do so
+ setActive(scope.$parent, parentActive = lastValue = scope.active);
+ }
+ }
+ return parentActive;
+ });
+ }
+
+ carouselCtrl.addSlide(scope, element);
+ //when the scope is destroyed then remove the slide from the current slides array
+ scope.$on('$destroy', function() {
+ carouselCtrl.removeSlide(scope);
+ });
+
+ scope.$watch('active', function(active) {
+ if (active) {
+ carouselCtrl.select(scope);
+ }
+ });
+ }
+ };
+}]);
+
+angular.module('ui.bootstrap.datepicker', [])
+
+.constant('datepickerConfig', {
+ dayFormat: 'dd',
+ monthFormat: 'MMMM',
+ yearFormat: 'yyyy',
+ dayHeaderFormat: 'EEE',
+ dayTitleFormat: 'MMMM yyyy',
+ monthTitleFormat: 'yyyy',
+ showWeeks: true,
+ startingDay: 0,
+ yearRange: 20
+})
+
+.directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', function (dateFilter, $parse, datepickerConfig) {
+ return {
+ restrict: 'EA',
+ replace: true,
+ scope: {
+ model: '=ngModel',
+ dateDisabled: '&'
+ },
+ templateUrl: 'template/datepicker/datepicker.html',
+ link: function(scope, element, attrs) {
+ scope.mode = 'day'; // Initial mode
+
+ // Configuration parameters
+ var selected = new Date(), showWeeks, minDate, maxDate, format = {};
+ format.day = angular.isDefined(attrs.dayFormat) ? scope.$eval(attrs.dayFormat) : datepickerConfig.dayFormat;
+ format.month = angular.isDefined(attrs.monthFormat) ? scope.$eval(attrs.monthFormat) : datepickerConfig.monthFormat;
+ format.year = angular.isDefined(attrs.yearFormat) ? scope.$eval(attrs.yearFormat) : datepickerConfig.yearFormat;
+ format.dayHeader = angular.isDefined(attrs.dayHeaderFormat) ? scope.$eval(attrs.dayHeaderFormat) : datepickerConfig.dayHeaderFormat;
+ format.dayTitle = angular.isDefined(attrs.dayTitleFormat) ? scope.$eval(attrs.dayTitleFormat) : datepickerConfig.dayTitleFormat;
+ format.monthTitle = angular.isDefined(attrs.monthTitleFormat) ? scope.$eval(attrs.monthTitleFormat) : datepickerConfig.monthTitleFormat;
+ var startingDay = angular.isDefined(attrs.startingDay) ? scope.$eval(attrs.startingDay) : datepickerConfig.startingDay;
+ var yearRange = angular.isDefined(attrs.yearRange) ? scope.$eval(attrs.yearRange) : datepickerConfig.yearRange;
+
+ if (attrs.showWeeks) {
+ scope.$parent.$watch($parse(attrs.showWeeks), function(value) {
+ showWeeks = !! value;
+ updateShowWeekNumbers();
+ });
+ } else {
+ showWeeks = datepickerConfig.showWeeks;
+ updateShowWeekNumbers();
+ }
+
+ if (attrs.min) {
+ scope.$parent.$watch($parse(attrs.min), function(value) {
+ minDate = new Date(value);
+ refill();
+ });
+ }
+ if (attrs.max) {
+ scope.$parent.$watch($parse(attrs.max), function(value) {
+ maxDate = new Date(value);
+ refill();
+ });
+ }
+
+ function updateCalendar (rows, labels, title) {
+ scope.rows = rows;
+ scope.labels = labels;
+ scope.title = title;
+ }
+
+ // Define whether the week number are visible
+ function updateShowWeekNumbers() {
+ scope.showWeekNumbers = ( scope.mode === 'day' && showWeeks );
+ }
+
+ function compare( date1, date2 ) {
+ if ( scope.mode === 'year') {
+ return date2.getFullYear() - date1.getFullYear();
+ } else if ( scope.mode === 'month' ) {
+ return new Date( date2.getFullYear(), date2.getMonth() ) - new Date( date1.getFullYear(), date1.getMonth() );
+ } else if ( scope.mode === 'day' ) {
+ return (new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) - new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) );
+ }
+ }
+
+ function isDisabled(date) {
+ return ((minDate && compare(date, minDate) > 0) || (maxDate && compare(date, maxDate) < 0) || (scope.dateDisabled && scope.dateDisabled({ date: date, mode: scope.mode })));
+ }
+
+ // Split array into smaller arrays
+ var split = function(a, size) {
+ var arrays = [];
+ while (a.length > 0) {
+ arrays.push(a.splice(0, size));
+ }
+ return arrays;
+ };
+ var getDaysInMonth = function( year, month ) {
+ return new Date(year, month + 1, 0).getDate();
+ };
+
+ var fill = {
+ day: function() {
+ var days = [], labels = [], lastDate = null;
+
+ function addDays( dt, n, isCurrentMonth ) {
+ for (var i =0; i < n; i ++) {
+ days.push( {date: new Date(dt), isCurrent: isCurrentMonth, isSelected: isSelected(dt), label: dateFilter(dt, format.day), disabled: isDisabled(dt) } );
+ dt.setDate( dt.getDate() + 1 );
+ }
+ lastDate = dt;
+ }
+
+ var d = new Date(selected);
+ d.setDate(1);
+
+ var difference = startingDay - d.getDay();
+ var numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference;
+
+ if ( numDisplayedFromPreviousMonth > 0 ) {
+ d.setDate( - numDisplayedFromPreviousMonth + 1 );
+ addDays(d, numDisplayedFromPreviousMonth, false);
+ }
+ addDays(lastDate || d, getDaysInMonth(selected.getFullYear(), selected.getMonth()), true);
+ addDays(lastDate, (7 - days.length % 7) % 7, false);
+
+ // Day labels
+ for (i = 0; i < 7; i++) {
+ labels.push( dateFilter(days[i].date, format.dayHeader) );
+ }
+ updateCalendar( split( days, 7 ), labels, dateFilter(selected, format.dayTitle) );
+ },
+ month: function() {
+ var months = [], i = 0, year = selected.getFullYear();
+ while ( i < 12 ) {
+ var dt = new Date(year, i++, 1);
+ months.push( {date: dt, isCurrent: true, isSelected: isSelected(dt), label: dateFilter(dt, format.month), disabled: isDisabled(dt)} );
+ }
+ updateCalendar( split( months, 3 ), [], dateFilter(selected, format.monthTitle) );
+ },
+ year: function() {
+ var years = [], year = parseInt((selected.getFullYear() - 1) / yearRange, 10) * yearRange + 1;
+ for ( var i = 0; i < yearRange; i++ ) {
+ var dt = new Date(year + i, 0, 1);
+ years.push( {date: dt, isCurrent: true, isSelected: isSelected(dt), label: dateFilter(dt, format.year), disabled: isDisabled(dt)} );
+ }
+ var title = years[0].label + ' - ' + years[years.length - 1].label;
+ updateCalendar( split( years, 5 ), [], title );
+ }
+ };
+ var refill = function() {
+ fill[scope.mode]();
+ };
+ var isSelected = function( dt ) {
+ if ( scope.model && scope.model.getFullYear() === dt.getFullYear() ) {
+ if ( scope.mode === 'year' ) {
+ return true;
+ }
+ if ( scope.model.getMonth() === dt.getMonth() ) {
+ return ( scope.mode === 'month' || (scope.mode === 'day' && scope.model.getDate() === dt.getDate()) );
+ }
+ }
+ return false;
+ };
+
+ scope.$watch('model', function ( dt, olddt ) {
+ if ( angular.isDate(dt) ) {
+ selected = angular.copy(dt);
+ }
+
+ if ( ! angular.equals(dt, olddt) ) {
+ refill();
+ }
+ });
+ scope.$watch('mode', function() {
+ updateShowWeekNumbers();
+ refill();
+ });
+
+ scope.select = function( dt ) {
+ selected = new Date(dt);
+
+ if ( scope.mode === 'year' ) {
+ scope.mode = 'month';
+ selected.setFullYear( dt.getFullYear() );
+ } else if ( scope.mode === 'month' ) {
+ scope.mode = 'day';
+ selected.setMonth( dt.getMonth() );
+ } else if ( scope.mode === 'day' ) {
+ scope.model = new Date(selected);
+ }
+ };
+ scope.move = function(step) {
+ if (scope.mode === 'day') {
+ selected.setMonth( selected.getMonth() + step );
+ } else if (scope.mode === 'month') {
+ selected.setFullYear( selected.getFullYear() + step );
+ } else if (scope.mode === 'year') {
+ selected.setFullYear( selected.getFullYear() + step * yearRange );
+ }
+ refill();
+ };
+ scope.toggleMode = function() {
+ scope.mode = ( scope.mode === 'day' ) ? 'month' : ( scope.mode === 'month' ) ? 'year' : 'day';
+ };
+ scope.getWeekNumber = function(row) {
+ if ( scope.mode !== 'day' || ! scope.showWeekNumbers || row.length !== 7 ) {
+ return;
+ }
+
+ var index = ( startingDay > 4 ) ? 11 - startingDay : 4 - startingDay; // Thursday
+ var d = new Date( row[ index ].date );
+ d.setHours(0, 0, 0);
+ return Math.ceil((((d - new Date(d.getFullYear(), 0, 1)) / 86400000) + 1) / 7); // 86400000 = 1000*60*60*24;
+ };
+ }
+ };
+}]);
+// The `$dialogProvider` can be used to configure global defaults for your
+// `$dialog` service.
+var dialogModule = angular.module('ui.bootstrap.dialog', ['ui.bootstrap.transition']);
+
+dialogModule.controller('MessageBoxController', ['$scope', 'dialog', 'model', function($scope, dialog, model){
+ $scope.title = model.title;
+ $scope.message = model.message;
+ $scope.buttons = model.buttons;
+ $scope.close = function(res){
+ dialog.close(res);
+ };
+}]);
+
+dialogModule.provider("$dialog", function(){
+
+ // The default options for all dialogs.
+ var defaults = {
+ backdrop: true,
+ dialogClass: 'modal',
+ backdropClass: 'modal-backdrop',
+ transitionClass: 'fade',
+ triggerClass: 'in',
+ resolve:{},
+ backdropFade: false,
+ dialogFade:false,
+ keyboard: true, // close with esc key
+ backdropClick: true // only in conjunction with backdrop=true
+ /* other options: template, templateUrl, controller */
+ };
+
+ var globalOptions = {};
+
+ var activeBackdrops = {value : 0};
+
+ // The `options({})` allows global configuration of all dialogs in the application.
+ //
+ // var app = angular.module('App', ['ui.bootstrap.dialog'], function($dialogProvider){
+ // // don't close dialog when backdrop is clicked by default
+ // $dialogProvider.options({backdropClick: false});
+ // });
+ this.options = function(value){
+ globalOptions = value;
+ };
+
+ // Returns the actual `$dialog` service that is injected in controllers
+ this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition", "$injector",
+ function ($http, $document, $compile, $rootScope, $controller, $templateCache, $q, $transition, $injector) {
+
+ var body = $document.find('body');
+
+ function createElement(clazz) {
+ var el = angular.element("<div>");
+ el.addClass(clazz);
+ return el;
+ }
+
+ // The `Dialog` class represents a modal dialog. The dialog class can be invoked by providing an options object
+ // containing at lest template or templateUrl and controller:
+ //
+ // var d = new Dialog({templateUrl: 'foo.html', controller: 'BarController'});
+ //
+ // Dialogs can also be created using templateUrl and controller as distinct arguments:
+ //
+ // var d = new Dialog('path/to/dialog.html', MyDialogController);
+ function Dialog(opts) {
+
+ var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts);
+ this._open = false;
+
+ this.backdropEl = createElement(options.backdropClass);
+ if(options.backdropFade){
+ this.backdropEl.addClass(options.transitionClass);
+ this.backdropEl.removeClass(options.triggerClass);
+ }
+
+ this.modalEl = createElement(options.dialogClass);
+ if(options.dialogFade){
+ this.modalEl.addClass(options.transitionClass);
+ this.modalEl.removeClass(options.triggerClass);
+ }
+
+ this.handledEscapeKey = function(e) {
+ if (e.which === 27) {
+ self.close();
+ e.preventDefault();
+ self.$scope.$apply();
+ }
+ };
+
+ this.handleBackDropClick = function(e) {
+ self.close();
+ e.preventDefault();
+ self.$scope.$apply();
+ };
+
+ this.handleLocationChange = function() {
+ self.close();
+ };
+ }
+
+ // The `isOpen()` method returns wether the dialog is currently visible.
+ Dialog.prototype.isOpen = function(){
+ return this._open;
+ };
+
+ // The `open(templateUrl, controller)` method opens the dialog.
+ // Use the `templateUrl` and `controller` arguments if specifying them at dialog creation time is not desired.
+ Dialog.prototype.open = function(templateUrl, controller){
+ var self = this, options = this.options;
+
+ if(templateUrl){
+ options.templateUrl = templateUrl;
+ }
+ if(controller){
+ options.controller = controller;
+ }
+
+ if(!(options.template || options.templateUrl)) {
+ throw new Error('Dialog.open expected template or templateUrl, neither found. Use options or open method to specify them.');
+ }
+
+ this._loadResolves().then(function(locals) {
+ var $scope = locals.$scope = self.$scope = locals.$scope ? locals.$scope : $rootScope.$new();
+
+ self.modalEl.html(locals.$template);
+
+ if (self.options.controller) {
+ var ctrl = $controller(self.options.controller, locals);
+ self.modalEl.children().data('ngControllerController', ctrl);
+ }
+
+ $compile(self.modalEl)($scope);
+ self._addElementsToDom();
+
+ // trigger tranisitions
+ setTimeout(function(){
+ if(self.options.dialogFade){ self.modalEl.addClass(self.options.triggerClass); }
+ if(self.options.backdropFade){ self.backdropEl.addClass(self.options.triggerClass); }
+ });
+
+ self._bindEvents();
+ });
+
+ this.deferred = $q.defer();
+ return this.deferred.promise;
+ };
+
+ // closes the dialog and resolves the promise returned by the `open` method with the specified result.
+ Dialog.prototype.close = function(result){
+ var self = this;
+ var fadingElements = this._getFadingElements();
+
+ if(fadingElements.length > 0){
+ for (var i = fadingElements.length - 1; i >= 0; i--) {
+ $transition(fadingElements[i], removeTriggerClass).then(onCloseComplete);
+ }
+ return;
+ }
+
+ this._onCloseComplete(result);
+
+ function removeTriggerClass(el){
+ el.removeClass(self.options.triggerClass);
+ }
+
+ function onCloseComplete(){
+ if(self._open){
+ self._onCloseComplete(result);
+ }
+ }
+ };
+
+ Dialog.prototype._getFadingElements = function(){
+ var elements = [];
+ if(this.options.dialogFade){
+ elements.push(this.modalEl);
+ }
+ if(this.options.backdropFade){
+ elements.push(this.backdropEl);
+ }
+
+ return elements;
+ };
+
+ Dialog.prototype._bindEvents = function() {
+ if(this.options.keyboard){ body.bind('keydown', this.handledEscapeKey); }
+ if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.bind('click', this.handleBackDropClick); }
+ };
+
+ Dialog.prototype._unbindEvents = function() {
+ if(this.options.keyboard){ body.unbind('keydown', this.handledEscapeKey); }
+ if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.unbind('click', this.handleBackDropClick); }
+ };
+
+ Dialog.prototype._onCloseComplete = function(result) {
+ this._removeElementsFromDom();
+ this._unbindEvents();
+
+ this.deferred.resolve(result);
+ };
+
+ Dialog.prototype._addElementsToDom = function(){
+ body.append(this.modalEl);
+
+ if(this.options.backdrop) {
+ if (activeBackdrops.value === 0) {
+ body.append(this.backdropEl);
+ }
+ activeBackdrops.value++;
+ }
+
+ this._open = true;
+ };
+
+ Dialog.prototype._removeElementsFromDom = function(){
+ this.modalEl.remove();
+
+ if(this.options.backdrop) {
+ activeBackdrops.value--;
+ if (activeBackdrops.value === 0) {
+ this.backdropEl.remove();
+ }
+ }
+ this._open = false;
+ };
+
+ // Loads all `options.resolve` members to be used as locals for the controller associated with the dialog.
+ Dialog.prototype._loadResolves = function(){
+ var values = [], keys = [], templatePromise, self = this;
+
+ if (this.options.template) {
+ templatePromise = $q.when(this.options.template);
+ } else if (this.options.templateUrl) {
+ templatePromise = $http.get(this.options.templateUrl, {cache:$templateCache})
+ .then(function(response) { return response.data; });
+ }
+
+ angular.forEach(this.options.resolve || [], function(value, key) {
+ keys.push(key);
+ values.push(angular.isString(value) ? $injector.get(value) : $injector.invoke(value));
+ });
+
+ keys.push('$template');
+ values.push(templatePromise);
+
+ return $q.all(values).then(function(values) {
+ var locals = {};
+ angular.forEach(values, function(value, index) {
+ locals[keys[index]] = value;
+ });
+ locals.dialog = self;
+ return locals;
+ });
+ };
+
+ // The actual `$dialog` service that is injected in controllers.
+ return {
+ // Creates a new `Dialog` with the specified options.
+ dialog: function(opts){
+ return new Dialog(opts);
+ },
+ // creates a new `Dialog` tied to the default message box template and controller.
+ //
+ // Arguments `title` and `message` are rendered in the modal header and body sections respectively.
+ // The `buttons` array holds an object with the following members for each button to include in the
+ // modal footer section:
+ //
+ // * `result`: the result to pass to the `close` method of the dialog when the button is clicked
+ // * `label`: the label of the button
+ // * `cssClass`: additional css class(es) to apply to the button for styling
+ messageBox: function(title, message, buttons){
+ return new Dialog({templateUrl: 'template/dialog/message.html', controller: 'MessageBoxController', resolve:
+ {model: function() {
+ return {
+ title: title,
+ message: message,
+ buttons: buttons
+ };
+ }
+ }});
+ }
+ };
+ }];
+});
+
+/*
+ * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js
+ * @restrict class or attribute
+ * @example:
+ <li class="dropdown">
+ <a class="dropdown-toggle">My Dropdown Menu</a>
+ <ul class="dropdown-menu">
+ <li ng-repeat="choice in dropChoices">
+ <a ng-href="{{choice.href}}">{{choice.text}}</a>
+ </li>
+ </ul>
+ </li>
+ */
+
+angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document, $location) {
+ var openElement = null,
+ closeMenu = angular.noop;
+ return {
+ restrict: 'CA',
+ link: function(scope, element, attrs) {
+ scope.$watch('$location.path', function() { closeMenu(); });
+ element.parent().bind('click', function() { closeMenu(); });
+ element.bind('click', function (event) {
+
+ var elementWasOpen = (element === openElement);
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (!!openElement) {
+ closeMenu();
+ }
+
+ if (!elementWasOpen) {
+ element.parent().addClass('open');
+ openElement = element;
+ closeMenu = function (event) {
+ if (event) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ $document.unbind('click', closeMenu);
+ element.parent().removeClass('open');
+ closeMenu = angular.noop;
+ openElement = null;
+ };
+ $document.bind('click', closeMenu);
+ }
+ });
+ }
+ };
+}]);
+angular.module('ui.bootstrap.modal', ['ui.bootstrap.dialog'])
+.directive('modal', ['$parse', '$dialog', function($parse, $dialog) {
+ return {
+ restrict: 'EA',
+ terminal: true,
+ link: function(scope, elm, attrs) {
+ var opts = angular.extend({}, scope.$eval(attrs.uiOptions || attrs.bsOptions || attrs.options));
+ var shownExpr = attrs.modal || attrs.show;
+ var setClosed;
+
+ // Create a dialog with the template as the contents of the directive
+ // Add the current scope as the resolve in order to make the directive scope as a dialog controller scope
+ opts = angular.extend(opts, {
+ template: elm.html(),
+ resolve: { $scope: function() { return scope; } }
+ });
+ var dialog = $dialog.dialog(opts);
+
+ elm.remove();
+
+ if (attrs.close) {
+ setClosed = function() {
+ $parse(attrs.close)(scope);
+ };
+ } else {
+ setClosed = function() {
+ if (angular.isFunction($parse(shownExpr).assign)) {
+ $parse(shownExpr).assign(scope, false);
+ }
+ };
+ }
+
+ scope.$watch(shownExpr, function(isShown, oldShown) {
+ if (isShown) {
+ dialog.open().then(function(){
+ setClosed();
+ });
+ } else {
+ //Make sure it is not opened
+ if (dialog.isOpen()){
+ dialog.close();
+ }
+ }
+ });
+ }
+ };
+}]);
+angular.module('ui.bootstrap.pagination', [])
+
+.controller('PaginationController', ['$scope', function (scope) {
+
+ scope.noPrevious = function() {
+ return scope.currentPage === 1;
+ };
+ scope.noNext = function() {
+ return scope.currentPage === scope.numPages;
+ };
+
+ scope.isActive = function(page) {
+ return scope.currentPage === page;
+ };
+
+ scope.selectPage = function(page) {
+ if ( ! scope.isActive(page) && page > 0 && page <= scope.numPages) {
+ scope.currentPage = page;
+ scope.onSelectPage({ page: page });
+ }
+ };
+}])
+
+.constant('paginationConfig', {
+ boundaryLinks: false,
+ directionLinks: true,
+ firstText: 'First',
+ previousText: 'Previous',
+ nextText: 'Next',
+ lastText: 'Last',
+ rotate: true
+})
+
+.directive('pagination', ['paginationConfig', function(paginationConfig) {
+ return {
+ restrict: 'EA',
+ scope: {
+ numPages: '=',
+ currentPage: '=',
+ maxSize: '=',
+ onSelectPage: '&'
+ },
+ controller: 'PaginationController',
+ templateUrl: 'template/pagination/pagination.html',
+ replace: true,
+ link: function(scope, element, attrs) {
+
+ // Setup configuration parameters
+ var boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks;
+ var directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$eval(attrs.directionLinks) : paginationConfig.directionLinks;
+ var firstText = angular.isDefined(attrs.firstText) ? scope.$parent.$eval(attrs.firstText) : paginationConfig.firstText;
+ var previousText = angular.isDefined(attrs.previousText) ? scope.$parent.$eval(attrs.previousText) : paginationConfig.previousText;
+ var nextText = angular.isDefined(attrs.nextText) ? scope.$parent.$eval(attrs.nextText) : paginationConfig.nextText;
+ var lastText = angular.isDefined(attrs.lastText) ? scope.$parent.$eval(attrs.lastText) : paginationConfig.lastText;
+ var rotate = angular.isDefined(attrs.rotate) ? scope.$eval(attrs.rotate) : paginationConfig.rotate;
+
+ // Create page object used in template
+ function makePage(number, text, isActive, isDisabled) {
+ return {
+ number: number,
+ text: text,
+ active: isActive,
+ disabled: isDisabled
+ };
+ }
+
+ scope.$watch('numPages + currentPage + maxSize', function() {
+ scope.pages = [];
+
+ // Default page limits
+ var startPage = 1, endPage = scope.numPages;
+ var isMaxSized = ( angular.isDefined(scope.maxSize) && scope.maxSize < scope.numPages );
+
+ // recompute if maxSize
+ if ( isMaxSized ) {
+ if ( rotate ) {
+ // Current page is displayed in the middle of the visible ones
+ startPage = Math.max(scope.currentPage - Math.floor(scope.maxSize/2), 1);
+ endPage = startPage + scope.maxSize - 1;
+
+ // Adjust if limit is exceeded
+ if (endPage > scope.numPages) {
+ endPage = scope.numPages;
+ startPage = endPage - scope.maxSize + 1;
+ }
+ } else {
+ // Visible pages are paginated with maxSize
+ startPage = ((Math.ceil(scope.currentPage / scope.maxSize) - 1) * scope.maxSize) + 1;
+
+ // Adjust last page if limit is exceeded
+ endPage = Math.min(startPage + scope.maxSize - 1, scope.numPages);
+ }
+ }
+
+ // Add page number links
+ for (var number = startPage; number <= endPage; number++) {
+ var page = makePage(number, number, scope.isActive(number), false);
+ scope.pages.push(page);
+ }
+
+ // Add links to move between page sets
+ if ( isMaxSized && ! rotate ) {
+ if ( startPage > 1 ) {
+ var previousPageSet = makePage(startPage - 1, '...', false, false);
+ scope.pages.unshift(previousPageSet);
+ }
+
+ if ( endPage < scope.numPages ) {
+ var nextPageSet = makePage(endPage + 1, '...', false, false);
+ scope.pages.push(nextPageSet);
+ }
+ }
+
+ // Add previous & next links
+ if (directionLinks) {
+ var previousPage = makePage(scope.currentPage - 1, previousText, false, scope.noPrevious());
+ scope.pages.unshift(previousPage);
+
+ var nextPage = makePage(scope.currentPage + 1, nextText, false, scope.noNext());
+ scope.pages.push(nextPage);
+ }
+
+ // Add first & last links
+ if (boundaryLinks) {
+ var firstPage = makePage(1, firstText, false, scope.noPrevious());
+ scope.pages.unshift(firstPage);
+
+ var lastPage = makePage(scope.numPages, lastText, false, scope.noNext());
+ scope.pages.push(lastPage);
+ }
+
+ if ( scope.currentPage > scope.numPages ) {
+ scope.selectPage(scope.numPages);
+ }
+ });
+ }
+ };
+}])
+
+.constant('pagerConfig', {
+ previousText: '« Previous',
+ nextText: 'Next »',
+ align: true
+})
+
+.directive('pager', ['pagerConfig', function(config) {
+ return {
+ restrict: 'EA',
+ scope: {
+ numPages: '=',
+ currentPage: '=',
+ onSelectPage: '&'
+ },
+ controller: 'PaginationController',
+ templateUrl: 'template/pagination/pager.html',
+ replace: true,
+ link: function(scope, element, attrs, paginationCtrl) {
+
+ // Setup configuration parameters
+ var previousText = angular.isDefined(attrs.previousText) ? scope.$parent.$eval(attrs.previousText) : config.previousText;
+ var nextText = angular.isDefined(attrs.nextText) ? scope.$parent.$eval(attrs.nextText) : config.nextText;
+ var align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : config.align;
+
+ // Create page object used in template
+ function makePage(number, text, isDisabled, isPrevious, isNext) {
+ return {
+ number: number,
+ text: text,
+ disabled: isDisabled,
+ previous: ( align && isPrevious ),
+ next: ( align && isNext )
+ };
+ }
+
+ scope.$watch('numPages + currentPage', function() {
+ scope.pages = [];
+
+ // Add previous & next links
+ var previousPage = makePage(scope.currentPage - 1, previousText, scope.noPrevious(), true, false);
+ scope.pages.unshift(previousPage);
+
+ var nextPage = makePage(scope.currentPage + 1, nextText, scope.noNext(), false, true);
+ scope.pages.push(nextPage);
+
+ if ( scope.currentPage > scope.numPages ) {
+ scope.selectPage(scope.numPages);
+ }
+ });
+ }
+ };
+}]);
+
+angular.module('ui.bootstrap.position', [])
+
+/**
+ * A set of utility methods that can be use to retrieve position of DOM elements.
+ * It is meant to be used where we need to absolute-position DOM elements in
+ * relation to other, existing elements (this is the case for tooltips, popovers,
+ * typeahead suggestions etc.).
+ */
+ .factory('$position', ['$document', '$window', function ($document, $window) {
+
+ var mouseX, mouseY;
+
+ $document.bind('mousemove', function mouseMoved(event) {
+ mouseX = event.pageX;
+ mouseY = event.pageY;
+ });
+
+ function getStyle(el, cssprop) {
+ if (el.currentStyle) { //IE
+ return el.currentStyle[cssprop];
+ } else if ($window.getComputedStyle) {
+ return $window.getComputedStyle(el)[cssprop];
+ }
+ // finally try and get inline style
+ return el.style[cssprop];
+ }
+
+ /**
+ * Checks if a given element is statically positioned
+ * @param element - raw DOM element
+ */
+ function isStaticPositioned(element) {
+ return (getStyle(element, "position") || 'static' ) === 'static';
+ }
+
+ /**
+ * returns the closest, non-statically positioned parentOffset of a given element
+ * @param element
+ */
+ var parentOffsetEl = function (element) {
+ var docDomEl = $document[0];
+ var offsetParent = element.offsetParent || docDomEl;
+ while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent || docDomEl;
+ };
+
+ return {
+ /**
+ * Provides read-only equivalent of jQuery's position function:
+ * http://api.jquery.com/position/
+ */
+ position: function (element) {
+ var elBCR = this.offset(element);
+ var offsetParentBCR = { top: 0, left: 0 };
+ var offsetParentEl = parentOffsetEl(element[0]);
+ if (offsetParentEl != $document[0]) {
+ offsetParentBCR = this.offset(angular.element(offsetParentEl));
+ offsetParentBCR.top += offsetParentEl.clientTop;
+ offsetParentBCR.left += offsetParentEl.clientLeft;
+ }
+
+ return {
+ width: element.prop('offsetWidth'),
+ height: element.prop('offsetHeight'),
+ top: elBCR.top - offsetParentBCR.top,
+ left: elBCR.left - offsetParentBCR.left
+ };
+ },
+
+ /**
+ * Provides read-only equivalent of jQuery's offset function:
+ * http://api.jquery.com/offset/
+ */
+ offset: function (element) {
+ var boundingClientRect = element[0].getBoundingClientRect();
+ return {
+ width: element.prop('offsetWidth'),
+ height: element.prop('offsetHeight'),
+ top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop),
+ left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft)
+ };
+ },
+
+ /**
+ * Provides the coordinates of the mouse
+ */
+ mouse: function () {
+ return {x: mouseX, y: mouseY};
+ }
+ };
+ }]);
+
+/**
+ * The following features are still outstanding: animation as a
+ * function, placement as a function, inside, support for more triggers than
+ * just mouse enter/leave, html tooltips, and selector delegation.
+ */
+angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
+
+/**
+ * The $tooltip service creates tooltip- and popover-like directives as well as
+ * houses global options for them.
+ */
+.provider( '$tooltip', function () {
+ // The default options tooltip and popover.
+ var defaultOptions = {
+ placement: 'top',
+ animation: true,
+ popupDelay: 0
+ };
+
+ // Default hide triggers for each show trigger
+ var triggerMap = {
+ 'mouseenter': 'mouseleave',
+ 'click': 'click',
+ 'focus': 'blur'
+ };
+
+ // The options specified to the provider globally.
+ var globalOptions = {};
+
+ /**
+ * `options({})` allows global configuration of all tooltips in the
+ * application.
+ *
+ * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
+ * // place tooltips left instead of top by default
+ * $tooltipProvider.options( { placement: 'left' } );
+ * });
+ */
+ this.options = function( value ) {
+ angular.extend( globalOptions, value );
+ };
+
+ /**
+ * This allows you to extend the set of trigger mappings available. E.g.:
+ *
+ * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
+ */
+ this.setTriggers = function setTriggers ( triggers ) {
+ angular.extend( triggerMap, triggers );
+ };
+
+ /**
+ * This is a helper function for translating camel-case to snake-case.
+ */
+ function snake_case(name){
+ var regexp = /[A-Z]/g;
+ var separator = '-';
+ return name.replace(regexp, function(letter, pos) {
+ return (pos ? separator : '') + letter.toLowerCase();
+ });
+ }
+
+ /**
+ * Returns the actual instance of the $tooltip service.
+ * TODO support multiple triggers
+ */
+ this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) {
+ return function $tooltip ( type, prefix, defaultTriggerShow ) {
+ var options = angular.extend( {}, defaultOptions, globalOptions );
+
+ /**
+ * Returns an object of show and hide triggers.
+ *
+ * If a trigger is supplied,
+ * it is used to show the tooltip; otherwise, it will use the `trigger`
+ * option passed to the `$tooltipProvider.options` method; else it will
+ * default to the trigger supplied to this directive factory.
+ *
+ * The hide trigger is based on the show trigger. If the `trigger` option
+ * was passed to the `$tooltipProvider.options` method, it will use the
+ * mapped trigger from `triggerMap` or the passed trigger if the map is
+ * undefined; otherwise, it uses the `triggerMap` value of the show
+ * trigger; else it will just use the show trigger.
+ */
+ function setTriggers ( trigger ) {
+ var show, hide;
+
+ show = trigger || options.trigger || defaultTriggerShow;
+ if ( angular.isDefined ( options.trigger ) ) {
+ hide = triggerMap[options.trigger] || show;
+ } else {
+ hide = triggerMap[show] || show;
+ }
+
+ return {
+ show: show,
+ hide: hide
+ };
+ }
+
+ var directiveName = snake_case( type );
+ var triggers = setTriggers( undefined );
+
+ var startSym = $interpolate.startSymbol();
+ var endSym = $interpolate.endSymbol();
+ var template =
+ '<'+ directiveName +'-popup '+
+ 'title="'+startSym+'tt_title'+endSym+'" '+
+ 'content="'+startSym+'tt_content'+endSym+'" '+
+ 'placement="'+startSym+'tt_placement'+endSym+'" '+
+ 'animation="tt_animation()" '+
+ 'is-open="tt_isOpen"'+
+ '>'+
+ '</'+ directiveName +'-popup>';
+
+ return {
+ restrict: 'EA',
+ scope: true,
+ link: function link ( scope, element, attrs ) {
+ var tooltip = $compile( template )( scope );
+ var transitionTimeout;
+ var popupTimeout;
+ var $body;
+ var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
+
+ // By default, the tooltip is not open.
+ // TODO add ability to start tooltip opened
+ scope.tt_isOpen = false;
+
+ function toggleTooltipBind () {
+ if ( ! scope.tt_isOpen ) {
+ showTooltipBind();
+ } else {
+ hideTooltipBind();
+ }
+ }
+
+ // Show the tooltip with delay if specified, otherwise show it immediately
+ function showTooltipBind() {
+ if ( scope.tt_popupDelay ) {
+ popupTimeout = $timeout( show, scope.tt_popupDelay );
+ } else {
+ scope.$apply( show );
+ }
+ }
+
+ function hideTooltipBind () {
+ scope.$apply(function () {
+ hide();
+ });
+ }
+
+ // Show the tooltip popup element.
+ function show() {
+ var position,
+ ttWidth,
+ ttHeight,
+ ttPosition;
+
+ // Don't show empty tooltips.
+ if ( ! scope.tt_content ) {
+ return;
+ }
+
+ // If there is a pending remove transition, we must cancel it, lest the
+ // tooltip be mysteriously removed.
+ if ( transitionTimeout ) {
+ $timeout.cancel( transitionTimeout );
+ }
+
+ // Set the initial positioning.
+ tooltip.css({ top: 0, left: 0, display: 'block' });
+
+ // Now we add it to the DOM because need some info about it. But it's not
+ // visible yet anyway.
+ if ( appendToBody ) {
+ $body = $body || $document.find( 'body' );
+ $body.append( tooltip );
+ } else {
+ element.after( tooltip );
+ }
+
+ // Get the position of the directive element.
+ position = options.appendToBody ? $position.offset( element ) : $position.position( element );
+
+ // Get the height and width of the tooltip so we can center it.
+ ttWidth = tooltip.prop( 'offsetWidth' );
+ ttHeight = tooltip.prop( 'offsetHeight' );
+
+ // Calculate the tooltip's top and left coordinates to center it with
+ // this directive.
+ switch ( scope.tt_placement ) {
+ case 'mouse':
+ var mousePos = $position.mouse();
+ ttPosition = {
+ top: mousePos.y,
+ left: mousePos.x
+ };
+ break;
+ case 'right':
+ ttPosition = {
+ top: position.top + position.height / 2 - ttHeight / 2,
+ left: position.left + position.width
+ };
+ break;
+ case 'bottom':
+ ttPosition = {
+ top: position.top + position.height,
+ left: position.left + position.width / 2 - ttWidth / 2
+ };
+ break;
+ case 'left':
+ ttPosition = {
+ top: position.top + position.height / 2 - ttHeight / 2,
+ left: position.left - ttWidth
+ };
+ break;
+ default:
+ ttPosition = {
+ top: position.top - ttHeight,
+ left: position.left + position.width / 2 - ttWidth / 2
+ };
+ break;
+ }
+
+ ttPosition.top += 'px';
+ ttPosition.left += 'px';
+
+ // Now set the calculated positioning.
+ tooltip.css( ttPosition );
+
+ // And show the tooltip.
+ scope.tt_isOpen = true;
+ }
+
+ // Hide the tooltip popup element.
+ function hide() {
+ // First things first: we don't show it anymore.
+ scope.tt_isOpen = false;
+
+ //if tooltip is going to be shown after delay, we must cancel this
+ $timeout.cancel( popupTimeout );
+
+ // And now we remove it from the DOM. However, if we have animation, we
+ // need to wait for it to expire beforehand.
+ // FIXME: this is a placeholder for a port of the transitions library.
+ if ( angular.isDefined( scope.tt_animation ) && scope.tt_animation() ) {
+ transitionTimeout = $timeout( function () { tooltip.remove(); }, 500 );
+ } else {
+ tooltip.remove();
+ }
+ }
+
+ /**
+ * Observe the relevant attributes.
+ */
+ attrs.$observe( type, function ( val ) {
+ scope.tt_content = val;
+ });
+
+ attrs.$observe( prefix+'Title', function ( val ) {
+ scope.tt_title = val;
+ });
+
+ attrs.$observe( prefix+'Placement', function ( val ) {
+ scope.tt_placement = angular.isDefined( val ) ? val : options.placement;
+ });
+
+ attrs.$observe( prefix+'Animation', function ( val ) {
+ scope.tt_animation = angular.isDefined( val ) ? $parse( val ) : function(){ return options.animation; };
+ });
+
+ attrs.$observe( prefix+'PopupDelay', function ( val ) {
+ var delay = parseInt( val, 10 );
+ scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
+ });
+
+ attrs.$observe( prefix+'Trigger', function ( val ) {
+ element.unbind( triggers.show );
+ element.unbind( triggers.hide );
+
+ triggers = setTriggers( val );
+
+ if ( triggers.show === triggers.hide ) {
+ element.bind( triggers.show, toggleTooltipBind );
+ } else {
+ element.bind( triggers.show, showTooltipBind );
+ element.bind( triggers.hide, hideTooltipBind );
+ }
+ });
+
+ attrs.$observe( prefix+'AppendToBody', function ( val ) {
+ appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody;
+ });
+
+ // if a tooltip is attached to <body> we need to remove it on
+ // location change as its parent scope will probably not be destroyed
+ // by the change.
+ if ( appendToBody ) {
+ scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
+ if ( scope.tt_isOpen ) {
+ hide();
+ }
+ });
+ }
+
+ // Make sure tooltip is destroyed and removed.
+ scope.$on('$destroy', function onDestroyTooltip() {
+ if ( scope.tt_isOpen ) {
+ hide();
+ } else {
+ tooltip.remove();
+ }
+ });
+ }
+ };
+ };
+ }];
+})
+
+.directive( 'tooltipPopup', function () {
+ return {
+ restrict: 'E',
+ replace: true,
+ scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
+ templateUrl: 'template/tooltip/tooltip-popup.html'
+ };
+})
+
+.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
+ return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
+}])
+
+.directive( 'tooltipHtmlUnsafePopup', function () {
+ return {
+ restrict: 'E',
+ replace: true,
+ scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
+ templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
+ };
+})
+
+.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
+ return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
+}]);
+
+/**
+ * The following features are still outstanding: popup delay, animation as a
+ * function, placement as a function, inside, support for more triggers than
+ * just mouse enter/leave, html popovers, and selector delegatation.
+ */
+angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
+.directive( 'popoverPopup', function () {
+ return {
+ restrict: 'EA',
+ replace: true,
+ scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
+ templateUrl: 'template/popover/popover.html'
+ };
+})
+.directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) {
+ return $tooltip( 'popover', 'popover', 'click' );
+}]);
+
+
+angular.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition'])
+
+.constant('progressConfig', {
+ animate: true,
+ autoType: false,
+ stackedTypes: ['success', 'info', 'warning', 'danger']
+})
+
+.controller('ProgressBarController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) {
+
+ // Whether bar transitions should be animated
+ var animate = angular.isDefined($attrs.animate) ? $scope.$eval($attrs.animate) : progressConfig.animate;
+ var autoType = angular.isDefined($attrs.autoType) ? $scope.$eval($attrs.autoType) : progressConfig.autoType;
+ var stackedTypes = angular.isDefined($attrs.stackedTypes) ? $scope.$eval('[' + $attrs.stackedTypes + ']') : progressConfig.stackedTypes;
+
+ // Create bar object
+ this.makeBar = function(newBar, oldBar, index) {
+ var newValue = (angular.isObject(newBar)) ? newBar.value : (newBar || 0);
+ var oldValue = (angular.isObject(oldBar)) ? oldBar.value : (oldBar || 0);
+ var type = (angular.isObject(newBar) && angular.isDefined(newBar.type)) ? newBar.type : (autoType) ? getStackedType(index || 0) : null;
+
+ return {
+ from: oldValue,
+ to: newValue,
+ type: type,
+ animate: animate
+ };
+ };
+
+ function getStackedType(index) {
+ return stackedTypes[index];
+ }
+
+ this.addBar = function(bar) {
+ $scope.bars.push(bar);
+ $scope.totalPercent += bar.to;
+ };
+
+ this.clearBars = function() {
+ $scope.bars = [];
+ $scope.totalPercent = 0;
+ };
+ this.clearBars();
+}])
+
+.directive('progress', function() {
+ return {
+ restrict: 'EA',
+ replace: true,
+ controller: 'ProgressBarController',
+ scope: {
+ value: '=percent',
+ onFull: '&',
+ onEmpty: '&'
+ },
+ templateUrl: 'template/progressbar/progress.html',
+ link: function(scope, element, attrs, controller) {
+ scope.$watch('value', function(newValue, oldValue) {
+ controller.clearBars();
+
+ if (angular.isArray(newValue)) {
+ // Stacked progress bar
+ for (var i=0, n=newValue.length; i < n; i++) {
+ controller.addBar(controller.makeBar(newValue[i], oldValue[i], i));
+ }
+ } else {
+ // Simple bar
+ controller.addBar(controller.makeBar(newValue, oldValue));
+ }
+ }, true);
+
+ // Total percent listeners
+ scope.$watch('totalPercent', function(value) {
+ if (value >= 100) {
+ scope.onFull();
+ } else if (value <= 0) {
+ scope.onEmpty();
+ }
+ }, true);
+ }
+ };
+})
+
+.directive('progressbar', ['$transition', function($transition) {
+ return {
+ restrict: 'EA',
+ replace: true,
+ scope: {
+ width: '=',
+ old: '=',
+ type: '=',
+ animate: '='
+ },
+ templateUrl: 'template/progressbar/bar.html',
+ link: function(scope, element) {
+ scope.$watch('width', function(value) {
+ if (scope.animate) {
+ element.css('width', scope.old + '%');
+ $transition(element, {width: value + '%'});
+ } else {
+ element.css('width', value + '%');
+ }
+ });
+ }
+ };
+}]);
+angular.module('ui.bootstrap.rating', [])
+
+.constant('ratingConfig', {
+ max: 5
+})
+
+.directive('rating', ['ratingConfig', '$parse', function(ratingConfig, $parse) {
+ return {
+ restrict: 'EA',
+ scope: {
+ value: '='
+ },
+ templateUrl: 'template/rating/rating.html',
+ replace: true,
+ link: function(scope, element, attrs) {
+
+ var maxRange = angular.isDefined(attrs.max) ? scope.$eval(attrs.max) : ratingConfig.max;
+
+ scope.range = [];
+ for (var i = 1; i <= maxRange; i++) {
+ scope.range.push(i);
+ }
+
+ scope.rate = function(value) {
+ if ( ! scope.readonly ) {
+ scope.value = value;
+ }
+ };
+
+ scope.enter = function(value) {
+ if ( ! scope.readonly ) {
+ scope.val = value;
+ }
+ };
+
+ scope.reset = function() {
+ scope.val = angular.copy(scope.value);
+ };
+ scope.reset();
+
+ scope.$watch('value', function(value) {
+ scope.val = value;
+ });
+
+ scope.readonly = false;
+ if (attrs.readonly) {
+ scope.$parent.$watch($parse(attrs.readonly), function(value) {
+ scope.readonly = !!value;
+ });
+ }
+ }
+ };
+}]);
+
+/**
+ * @ngdoc overview
+ * @name ui.bootstrap.tabs
+ *
+ * @description
+ * AngularJS version of the tabs directive.
+ */
+
+angular.module('ui.bootstrap.tabs', [])
+
+.directive('tabs', function() {
+ return function() {
+ throw new Error("The `tabs` directive is deprecated, please migrate to `tabset`. Instructions can be found at http://github.com/angular-ui/bootstrap/tree/master/CHANGELOG.md");
+ };
+})
+
+.controller('TabsetController', ['$scope', '$element',
+function TabsetCtrl($scope, $element) {
+ var ctrl = this,
+ tabs = ctrl.tabs = $scope.tabs = [];
+
+ ctrl.select = function(tab) {
+ angular.forEach(tabs, function(tab) {
+ tab.active = false;
+ });
+ tab.active = true;
+ };
+
+ ctrl.addTab = function addTab(tab) {
+ tabs.push(tab);
+ if (tabs.length == 1) {
+ ctrl.select(tab);
+ }
+ };
+
+ ctrl.removeTab = function removeTab(tab) {
+ var index = tabs.indexOf(tab);
+ //Select a new tab if the tab to be removed is selected
+ if (tab.active && tabs.length > 1) {
+ //If this is the last tab, select the previous tab. else, the next tab.
+ var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
+ ctrl.select(tabs[newActiveIndex]);
+ }
+ tabs.splice(index, 1);
+ };
+}])
+
+/**
+ * @ngdoc directive
+ * @name ui.bootstrap.tabs.directive:tabset
+ * @restrict EA
+ *
+ * @description
+ * Tabset is the outer container for the tabs directive
+ *
+ * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
+ *
+ * @example
+<example module="ui.bootstrap">
+ <file name="index.html">
+ <tabset>
+ <tab heading="Vertical Tab 1"><b>First</b> Content!</tab>
+ <tab heading="Vertical Tab 2"><i>Second</i> Content!</tab>
+ </tabset>
+ <hr />
+ <tabset vertical="true">
+ <tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab>
+ <tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab>
+ </tabset>
+ </file>
+</example>
+ */
+.directive('tabset', function() {
+ return {
+ restrict: 'EA',
+ transclude: true,
+ scope: {},
+ controller: 'TabsetController',
+ templateUrl: 'template/tabs/tabset.html',
+ link: function(scope, element, attrs) {
+ scope.vertical = angular.isDefined(attrs.vertical) ? scope.$eval(attrs.vertical) : false;
+ scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
+ }
+ };
+})
+
+/**
+ * @ngdoc directive
+ * @name ui.bootstrap.tabs.directive:tab
+ * @restrict EA
+ *
+ * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}.
+ * @param {string=} select An expression to evaluate when the tab is selected.
+ * @param {boolean=} active A binding, telling whether or not this tab is selected.
+ * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
+ *
+ * @description
+ * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
+ *
+ * @example
+<example module="ui.bootstrap">
+ <file name="index.html">
+ <div ng-controller="TabsDemoCtrl">
+ <button class="btn btn-small" ng-click="items[0].active = true">
+ Select item 1, using active binding
+ </button>
+ <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
+ Enable/disable item 2, using disabled binding
+ </button>
+ <br />
+ <tabset>
+ <tab heading="Tab 1">First Tab</tab>
+ <tab select="alertMe()">
+ <tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
+ Second Tab, with alert callback and html heading!
+ </tab>
+ <tab ng-repeat="item in items"
+ heading="{{item.title}}"
+ disabled="item.disabled"
+ active="item.active">
+ {{item.content}}
+ </tab>
+ </tabset>
+ </div>
+ </file>
+ <file name="script.js">
+ function TabsDemoCtrl($scope) {
+ $scope.items = [
+ { title:"Dynamic Title 1", content:"Dynamic Item 0" },
+ { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
+ ];
+
+ $scope.alertMe = function() {
+ setTimeout(function() {
+ alert("You've selected the alert tab!");
+ });
+ };
+ };
+ </file>
+</example>
+ */
+
+/**
+ * @ngdoc directive
+ * @name ui.bootstrap.tabs.directive:tabHeading
+ * @restrict EA
+ *
+ * @description
+ * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
+ *
+ * @example
+<example module="ui.bootstrap">
+ <file name="index.html">
+ <tabset>
+ <tab>
+ <tab-heading><b>HTML</b> in my titles?!</tab-heading>
+ And some content, too!
+ </tab>
+ <tab>
+ <tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
+ That's right.
+ </tab>
+ </tabset>
+ </file>
+</example>
+ */
+.directive('tab', ['$parse', '$http', '$templateCache', '$compile',
+function($parse, $http, $templateCache, $compile) {
+ return {
+ require: '^tabset',
+ restrict: 'EA',
+ replace: true,
+ templateUrl: 'template/tabs/tab.html',
+ transclude: true,
+ scope: {
+ heading: '@',
+ onSelect: '&select' //This callback is called in contentHeadingTransclude
+ //once it inserts the tab's content into the dom
+ },
+ controller: function() {
+ //Empty controller so other directives can require being 'under' a tab
+ },
+ compile: function(elm, attrs, transclude) {
+ return function postLink(scope, elm, attrs, tabsetCtrl) {
+ var getActive, setActive;
+ scope.active = false; // default value
+ if (attrs.active) {
+ getActive = $parse(attrs.active);
+ setActive = getActive.assign;
+ scope.$parent.$watch(getActive, function updateActive(value) {
+ if ( !!value && scope.disabled ) {
+ setActive(scope.$parent, false); // Prevent active assignment
+ } else {
+ scope.active = !!value;
+ }
+ });
+ } else {
+ setActive = getActive = angular.noop;
+ }
+
+ scope.$watch('active', function(active) {
+ setActive(scope.$parent, active);
+ if (active) {
+ tabsetCtrl.select(scope);
+ scope.onSelect();
+ }
+ });
+
+ scope.disabled = false;
+ if ( attrs.disabled ) {
+ scope.$parent.$watch($parse(attrs.disabled), function(value) {
+ scope.disabled = !! value;
+ });
+ }
+
+ scope.select = function() {
+ if ( ! scope.disabled ) {
+ scope.active = true;
+ }
+ };
+
+ tabsetCtrl.addTab(scope);
+ scope.$on('$destroy', function() {
+ tabsetCtrl.removeTab(scope);
+ });
+ //If the tabset sets this tab to active, set the parent scope's active
+ //binding too. We do this so the watch for the parent's initial active
+ //value won't overwrite what is initially set by the tabset
+ if (scope.active) {
+ setActive(scope.$parent, true);
+ }
+
+ //Transclude the collection of sibling elements. Use forEach to find
+ //the heading if it exists. We don't use a directive for tab-heading
+ //because it is problematic. Discussion @ http://git.io/MSNPwQ
+ transclude(scope.$parent, function(clone) {
+ //Look at every element in the clone collection. If it's tab-heading,
+ //mark it as that. If it's not tab-heading, mark it as tab contents
+ var contents = [], heading;
+ angular.forEach(clone, function(el) {
+ //See if it's a tab-heading attr or element directive
+ //First make sure it's a normal element, one that has a tagName
+ if (el.tagName &&
+ (el.hasAttribute("tab-heading") ||
+ el.hasAttribute("data-tab-heading") ||
+ el.tagName.toLowerCase() == "tab-heading" ||
+ el.tagName.toLowerCase() == "data-tab-heading"
+ )) {
+ heading = el;
+ } else {
+ contents.push(el);
+ }
+ });
+ //Share what we found on the scope, so our tabHeadingTransclude and
+ //tabContentTransclude directives can find out what the heading and
+ //contents are.
+ if (heading) {
+ scope.headingElement = angular.element(heading);
+ }
+ scope.contentElement = angular.element(contents);
+ });
+ };
+ }
+ };
+}])
+
+.directive('tabHeadingTransclude', [function() {
+ return {
+ restrict: 'A',
+ require: '^tab',
+ link: function(scope, elm, attrs, tabCtrl) {
+ scope.$watch('headingElement', function updateHeadingElement(heading) {
+ if (heading) {
+ elm.html('');
+ elm.append(heading);
+ }
+ });
+ }
+ };
+}])
+
+.directive('tabContentTransclude', ['$parse', function($parse) {
+ return {
+ restrict: 'A',
+ require: '^tabset',
+ link: function(scope, elm, attrs, tabsetCtrl) {
+ scope.$watch($parse(attrs.tabContentTransclude), function(tab) {
+ elm.html('');
+ if (tab) {
+ elm.append(tab.contentElement);
+ }
+ });
+ }
+ };
+}])
+
+;
+
+
+angular.module('ui.bootstrap.timepicker', [])
+
+.filter('pad', function() {
+ return function(input) {
+ if ( angular.isDefined(input) && input.toString().length < 2 ) {
+ input = '0' + input;
+ }
+ return input;
+ };
+})
+
+.constant('timepickerConfig', {
+ hourStep: 1,
+ minuteStep: 1,
+ showMeridian: true,
+ meridians: ['AM', 'PM'],
+ readonlyInput: false,
+ mousewheel: true
+})
+
+.directive('timepicker', ['padFilter', '$parse', 'timepickerConfig', function (padFilter, $parse, timepickerConfig) {
+ return {
+ restrict: 'EA',
+ require:'ngModel',
+ replace: true,
+ templateUrl: 'template/timepicker/timepicker.html',
+ scope: {
+ model: '=ngModel'
+ },
+ link: function(scope, element, attrs, ngModelCtrl) {
+ var selected = new Date(), meridians = timepickerConfig.meridians;
+
+ var hourStep = timepickerConfig.hourStep;
+ if (attrs.hourStep) {
+ scope.$parent.$watch($parse(attrs.hourStep), function(value) {
+ hourStep = parseInt(value, 10);
+ });
+ }
+
+ var minuteStep = timepickerConfig.minuteStep;
+ if (attrs.minuteStep) {
+ scope.$parent.$watch($parse(attrs.minuteStep), function(value) {
+ minuteStep = parseInt(value, 10);
+ });
+ }
+
+ // 12H / 24H mode
+ scope.showMeridian = timepickerConfig.showMeridian;
+ if (attrs.showMeridian) {
+ scope.$parent.$watch($parse(attrs.showMeridian), function(value) {
+ scope.showMeridian = !! value;
+
+ if ( ! scope.model ) {
+ // Reset
+ var dt = new Date( selected );
+ var hours = getScopeHours();
+ if (angular.isDefined( hours )) {
+ dt.setHours( hours );
+ }
+ scope.model = new Date( dt );
+ } else {
+ refreshTemplate();
+ }
+ });
+ }
+
+ // Get scope.hours in 24H mode if valid
+ function getScopeHours ( ) {
+ var hours = parseInt( scope.hours, 10 );
+ var valid = ( scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24);
+ if ( !valid ) {
+ return;
+ }
+
+ if ( scope.showMeridian ) {
+ if ( hours === 12 ) {
+ hours = 0;
+ }
+ if ( scope.meridian === meridians[1] ) {
+ hours = hours + 12;
+ }
+ }
+ return hours;
+ }
+
+ // Input elements
+ var inputs = element.find('input');
+ var hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1);
+
+ // Respond on mousewheel spin
+ var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel;
+ if ( mousewheel ) {
+
+ var isScrollingUp = function(e) {
+ if (e.originalEvent) {
+ e = e.originalEvent;
+ }
+ return (e.detail || e.wheelDelta > 0);
+ };
+
+ hoursInputEl.bind('mousewheel', function(e) {
+ scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() );
+ e.preventDefault();
+ });
+
+ minutesInputEl.bind('mousewheel', function(e) {
+ scope.$apply( (isScrollingUp(e)) ? scope.incrementMinutes() : scope.decrementMinutes() );
+ e.preventDefault();
+ });
+ }
+
+ var keyboardChange = false;
+ scope.readonlyInput = (angular.isDefined(attrs.readonlyInput)) ? scope.$eval(attrs.readonlyInput) : timepickerConfig.readonlyInput;
+ if ( ! scope.readonlyInput ) {
+ scope.updateHours = function() {
+ var hours = getScopeHours();
+
+ if ( angular.isDefined(hours) ) {
+ keyboardChange = 'h';
+ if ( scope.model === null ) {
+ scope.model = new Date( selected );
+ }
+ scope.model.setHours( hours );
+ } else {
+ scope.model = null;
+ scope.validHours = false;
+ }
+ };
+
+ hoursInputEl.bind('blur', function(e) {
+ if ( scope.validHours && scope.hours < 10) {
+ scope.$apply( function() {
+ scope.hours = padFilter( scope.hours );
+ });
+ }
+ });
+
+ scope.updateMinutes = function() {
+ var minutes = parseInt(scope.minutes, 10);
+ if ( minutes >= 0 && minutes < 60 ) {
+ keyboardChange = 'm';
+ if ( scope.model === null ) {
+ scope.model = new Date( selected );
+ }
+ scope.model.setMinutes( minutes );
+ } else {
+ sco
<TRUNCATED>