UNPKG

strong-arc

Version:

A visual suite for the StrongLoop API Platform

1,614 lines (1,415 loc) 174 kB
/* * angular-ui-bootstrap * http://angular-ui.github.io/bootstrap/ * Version: 0.13.4 - 2015-09-03 * License: MIT */ angular.module("ui.bootstrap", ["ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","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.transition","ui.bootstrap.typeahead"]); angular.module('ui.bootstrap.collapse', []) .directive('collapse', ['$animate', function($animate) { return { link: function(scope, element, attrs) { function expand() { element.removeClass('collapse') .addClass('collapsing') .attr('aria-expanded', true) .attr('aria-hidden', false); $animate.addClass(element, 'in', { to: { height: element[0].scrollHeight + 'px' } }).then(expandDone); } function expandDone() { element.removeClass('collapsing'); element.css({height: 'auto'}); } function collapse() { if (!element.hasClass('collapse') && !element.hasClass('in')) { return collapseDone(); } element // IMPORTANT: The height must be set before adding "collapsing" class. // Otherwise, the browser attempts to animate from height 0 (in // collapsing class) to the given height here. .css({height: element[0].scrollHeight + 'px'}) // initially all panel collapse have the collapse class, this removal // prevents the animation from jumping to collapsed state .removeClass('collapse') .addClass('collapsing') .attr('aria-expanded', false) .attr('aria-hidden', true); $animate.removeClass(element, 'in', { to: {height: '0'} }).then(collapseDone); } function collapseDone() { element.css({height: '0'}); // Required so that collapse works when animation is disabled element.removeClass('collapsing'); element.addClass('collapse'); } scope.$watch(attrs.collapse, function(shouldCollapse) { if (shouldCollapse) { collapse(); } else { expand(); } }); } }; }]); 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(index, 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', controllerAs: 'accordion', transclude: true, replace: false, templateUrl: function(element, attrs) { return attrs.templateUrl || 'template/accordion/accordion.html'; } }; }) // The accordion-group directive indicates a block of html that will expand and collapse in an accordion .directive('accordionGroup', function() { 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: function(element, attrs) { return attrs.templateUrl || 'template/accordion/accordion-group.html'; }, scope: { heading: '@', // Interpolate the heading attribute onto this scope isOpen: '=?', isDisabled: '=?' }, controller: function() { this.setHeading = function(element) { this.heading = element; }; }, link: function(scope, element, attrs, accordionCtrl) { accordionCtrl.addGroup(scope); scope.openClass = attrs.openClass || 'panel-open'; scope.panelClass = attrs.panelClass; scope.$watch('isOpen', function(value) { element.toggleClass(scope.openClass, value); if (value) { accordionCtrl.closeOthers(scope); } }); scope.toggleOpen = function($event) { if (!scope.isDisabled) { if (!$event || $event.which === 32) { scope.isOpen = !scope.isOpen; } } }; } }; }) // 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', link: function(scope, element, attr, accordionGroupCtrl, transclude) { // 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, angular.noop)); } }; }) // 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.find('span').html(''); element.find('span').append(heading); } }); } }; }) ; angular.module('ui.bootstrap.alert', []) .controller('AlertController', ['$scope', '$attrs', function($scope, $attrs) { $scope.closeable = !!$attrs.close; this.close = $scope.close; }]) .directive('alert', function() { return { controller: 'AlertController', controllerAs: 'alert', templateUrl: function(element, attrs) { return attrs.templateUrl || 'template/alert/alert.html'; }, transclude: true, replace: true, scope: { type: '@', close: '&' } }; }) .directive('dismissOnTimeout', ['$timeout', function($timeout) { return { require: 'alert', link: function(scope, element, attrs, alertCtrl) { $timeout(function() { alertCtrl.close(); }, parseInt(attrs.dismissOnTimeout, 10)); } }; }]); angular.module('ui.bootstrap.bindHtml', []) .value('$bindHtmlUnsafeSuppressDeprecated', false) .directive('bindHtmlUnsafe', ['$log', '$bindHtmlUnsafeSuppressDeprecated', function ($log, $bindHtmlUnsafeSuppressDeprecated) { return function (scope, element, attr) { if (!$bindHtmlUnsafeSuppressDeprecated) { $log.warn('bindHtmlUnsafe is now deprecated. Use ngBindHtml instead'); } element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { element.html(value || ''); }); }; }]); angular.module('ui.bootstrap.buttons', []) .constant('buttonConfig', { activeClass: 'active', toggleEvent: 'click' }) .controller('ButtonsController', ['buttonConfig', function(buttonConfig) { this.activeClass = buttonConfig.activeClass || 'active'; this.toggleEvent = buttonConfig.toggleEvent || 'click'; }]) .directive('btnRadio', function() { return { require: ['btnRadio', 'ngModel'], controller: 'ButtonsController', controllerAs: 'buttons', link: function(scope, element, attrs, ctrls) { var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; element.find('input').css({display: 'none'}); //model -> UI ngModelCtrl.$render = function() { element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); }; //ui->model element.bind(buttonsCtrl.toggleEvent, function() { if (attrs.disabled) { return; } var isActive = element.hasClass(buttonsCtrl.activeClass); if (!isActive || angular.isDefined(attrs.uncheckable)) { scope.$apply(function() { ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio)); ngModelCtrl.$render(); }); } }); } }; }) .directive('btnCheckbox', ['$document', function($document) { return { require: ['btnCheckbox', 'ngModel'], controller: 'ButtonsController', controllerAs: 'button', link: function(scope, element, attrs, ctrls) { var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; element.find('input').css({display: 'none'}); function getTrueValue() { return getCheckboxValue(attrs.btnCheckboxTrue, true); } function getFalseValue() { return getCheckboxValue(attrs.btnCheckboxFalse, false); } function getCheckboxValue(attributeValue, defaultValue) { var val = scope.$eval(attributeValue); return angular.isDefined(val) ? val : defaultValue; } //model -> UI ngModelCtrl.$render = function() { element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); }; //ui->model element.bind(buttonsCtrl.toggleEvent, function() { if (attrs.disabled) { return; } scope.$apply(function() { ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); ngModelCtrl.$render(); }); }); //accessibility element.on('keypress', function(e) { if (attrs.disabled || e.which !== 32 || $document[0].activeElement !== element[0]) { return; } scope.$apply(function() { ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); ngModelCtrl.$render(); }); }); } }; }]); /** * @ngdoc overview * @name ui.bootstrap.carousel * * @description * AngularJS version of an image carousel. * */ angular.module('ui.bootstrap.carousel', []) .controller('CarouselController', ['$scope', '$element', '$interval', '$animate', function ($scope, $element, $interval, $animate) { var self = this, slides = self.slides = $scope.slides = [], NEW_ANIMATE = angular.version.minor >= 4, NO_TRANSITION = 'uib-noTransition', SLIDE_DIRECTION = 'uib-slideDirection', currentIndex = -1, currentInterval, isPlaying; self.currentSlide = null; var destroyed = false; /* direction: "prev" or "next" */ self.select = $scope.select = function(nextSlide, direction) { var nextIndex = $scope.indexOfSlide(nextSlide); //Decide direction if it's not given if (direction === undefined) { direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev'; } //Prevent this user-triggered transition from occurring if there is already one in progress if (nextSlide && nextSlide !== self.currentSlide && !$scope.$currentTransition) { goNext(nextSlide, nextIndex, direction); } }; function goNext(slide, index, direction) { // Scope has been destroyed, stop here. if (destroyed) { return; } angular.extend(slide, {direction: direction, active: true}); angular.extend(self.currentSlide || {}, {direction: direction, active: false}); if ($animate.enabled() && !$scope.noTransition && !$scope.$currentTransition && slide.$element && self.slides.length > 1) { slide.$element.data(SLIDE_DIRECTION, slide.direction); if (self.currentSlide && self.currentSlide.$element) { self.currentSlide.$element.data(SLIDE_DIRECTION, slide.direction); } $scope.$currentTransition = true; if (NEW_ANIMATE) { $animate.on('addClass', slide.$element, function (element, phase) { if (phase === 'close') { $scope.$currentTransition = null; $animate.off('addClass', element); } }); } else { slide.$element.one('$animate:close', function closeFn() { $scope.$currentTransition = null; }); } } self.currentSlide = slide; currentIndex = index; //every time you change slides, reset the timer restartTimer(); } $scope.$on('$destroy', function () { destroyed = true; }); function getSlideByIndex(index) { if (angular.isUndefined(slides[index].index)) { return slides[index]; } var i, len = slides.length; for (i = 0; i < slides.length; ++i) { if (slides[i].index == index) { return slides[i]; } } } self.getCurrentIndex = function() { if (self.currentSlide && angular.isDefined(self.currentSlide.index)) { return +self.currentSlide.index; } return currentIndex; }; /* Allow outside people to call indexOf on slides array */ $scope.indexOfSlide = function(slide) { return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide); }; $scope.next = function() { var newIndex = (self.getCurrentIndex() + 1) % slides.length; if (newIndex === 0 && $scope.noWrap()) { $scope.pause(); return; } return self.select(getSlideByIndex(newIndex), 'next'); }; $scope.prev = function() { var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1; if ($scope.noWrap() && newIndex === slides.length - 1){ $scope.pause(); return; } return self.select(getSlideByIndex(newIndex), 'prev'); }; $scope.isActive = function(slide) { return self.currentSlide === slide; }; $scope.$watch('interval', restartTimer); $scope.$on('$destroy', resetTimer); function restartTimer() { resetTimer(); var interval = +$scope.interval; if (!isNaN(interval) && interval > 0) { currentInterval = $interval(timerFn, interval); } } function resetTimer() { if (currentInterval) { $interval.cancel(currentInterval); currentInterval = null; } } function timerFn() { var interval = +$scope.interval; if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) { $scope.next(); } else { $scope.pause(); } } $scope.play = function() { if (!isPlaying) { isPlaying = true; restartTimer(); } }; $scope.pause = function() { if (!$scope.noPause) { isPlaying = false; resetTimer(); } }; 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) { if (angular.isDefined(slide.index)) { slides.sort(function(a, b) { return +a.index > +b.index; }); } //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--; } //clean the currentSlide when no more slide if (slides.length === 0) { self.currentSlide = null; } }; $scope.$watch('noTransition', function(noTransition) { $element.data(NO_TRANSITION, noTransition); }); }]) /** * @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', controllerAs: 'carousel', require: 'carousel', templateUrl: function(element, attrs) { return attrs.templateUrl || 'template/carousel/carousel.html'; }, scope: { interval: '=', noTransition: '=', noPause: '=', noWrap: '&' } }; }]) /** * @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. * @param {number=} index The index of the slide. The slides will be sorted by this parameter. * * @example <example module="ui.bootstrap"> <file name="index.html"> <div ng-controller="CarouselDemoCtrl"> <carousel> <slide ng-repeat="slide in slides" active="slide.active" index="$index"> <img ng-src="{{slide.image}}" style="margin:auto;"> <div class="carousel-caption"> <h4>Slide {{$index}}</h4> <p>{{slide.text}}</p> </div> </slide> </carousel> Interval, in milliseconds: <input type="number" ng-model="myInterval"> <br />Enter a negative number to stop the interval. </div> </file> <file name="script.js"> function CarouselDemoCtrl($scope) { $scope.myInterval = 5000; } </file> <file name="demo.css"> .carousel-indicators { top: auto; bottom: 15px; } </file> </example> */ .directive('slide', function() { return { require: '^carousel', restrict: 'EA', transclude: true, replace: true, templateUrl: function(element, attrs) { return attrs.templateUrl || 'template/carousel/slide.html'; }, scope: { active: '=?', actual: '=?', index: '=?' }, link: function (scope, element, attrs, carouselCtrl) { 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); } }); } }; }) .animation('.item', [ '$injector', '$animate', function ($injector, $animate) { var NO_TRANSITION = 'uib-noTransition', SLIDE_DIRECTION = 'uib-slideDirection', $animateCss = null; if ($injector.has('$animateCss')) { $animateCss = $injector.get('$animateCss'); } function removeClass(element, className, callback) { element.removeClass(className); if (callback) { callback(); } } return { beforeAddClass: function (element, className, done) { // Due to transclusion, noTransition property is on parent's scope if (className == 'active' && element.parent() && !element.parent().data(NO_TRANSITION)) { var stopped = false; var direction = element.data(SLIDE_DIRECTION); var directionClass = direction == 'next' ? 'left' : 'right'; var removeClassFn = removeClass.bind(this, element, directionClass + ' ' + direction, done); element.addClass(direction); if ($animateCss) { $animateCss(element, {addClass: directionClass}) .start() .done(removeClassFn); } else { $animate.addClass(element, directionClass).then(function () { if (!stopped) { removeClassFn(); } done(); }); } return function () { stopped = true; }; } done(); }, beforeRemoveClass: function (element, className, done) { // Due to transclusion, noTransition property is on parent's scope if (className === 'active' && element.parent() && !element.parent().data(NO_TRANSITION)) { var stopped = false; var direction = element.data(SLIDE_DIRECTION); var directionClass = direction == 'next' ? 'left' : 'right'; var removeClassFn = removeClass.bind(this, element, directionClass, done); if ($animateCss) { $animateCss(element, {addClass: directionClass}) .start() .done(removeClassFn); } else { $animate.addClass(element, directionClass).then(function () { if (!stopped) { removeClassFn(); } done(); }); } return function () { stopped = true; }; } done(); } }; }]) ; angular.module('ui.bootstrap.dateparser', []) .service('dateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, orderByFilter) { // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; this.parsers = {}; var formatCodeToRegex = { 'yyyy': { regex: '\\d{4}', apply: function(value) { this.year = +value; } }, 'yy': { regex: '\\d{2}', apply: function(value) { this.year = +value + 2000; } }, 'y': { regex: '\\d{1,4}', apply: function(value) { this.year = +value; } }, 'MMMM': { regex: $locale.DATETIME_FORMATS.MONTH.join('|'), apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } }, 'MMM': { regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } }, 'MM': { regex: '0[1-9]|1[0-2]', apply: function(value) { this.month = value - 1; } }, 'M': { regex: '[1-9]|1[0-2]', apply: function(value) { this.month = value - 1; } }, 'dd': { regex: '[0-2][0-9]{1}|3[0-1]{1}', apply: function(value) { this.date = +value; } }, 'd': { regex: '[1-2]?[0-9]{1}|3[0-1]{1}', apply: function(value) { this.date = +value; } }, 'EEEE': { regex: $locale.DATETIME_FORMATS.DAY.join('|') }, 'EEE': { regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') }, 'HH': { regex: '(?:0|1)[0-9]|2[0-3]', apply: function(value) { this.hours = +value; } }, 'hh': { regex: '0[0-9]|1[0-2]', apply: function(value) { this.hours = +value; } }, 'H': { regex: '1?[0-9]|2[0-3]', apply: function(value) { this.hours = +value; } }, 'h': { regex: '[0-9]|1[0-2]', apply: function(value) { this.hours = +value; } }, 'mm': { regex: '[0-5][0-9]', apply: function(value) { this.minutes = +value; } }, 'm': { regex: '[0-9]|[1-5][0-9]', apply: function(value) { this.minutes = +value; } }, 'sss': { regex: '[0-9][0-9][0-9]', apply: function(value) { this.milliseconds = +value; } }, 'ss': { regex: '[0-5][0-9]', apply: function(value) { this.seconds = +value; } }, 's': { regex: '[0-9]|[1-5][0-9]', apply: function(value) { this.seconds = +value; } }, 'a': { regex: $locale.DATETIME_FORMATS.AMPMS.join('|'), apply: function(value) { if (this.hours === 12) { this.hours = 0; } if (value === 'PM') { this.hours += 12; } } } }; function createParser(format) { var map = [], regex = format.split(''); angular.forEach(formatCodeToRegex, function(data, code) { var index = format.indexOf(code); if (index > -1) { format = format.split(''); regex[index] = '(' + data.regex + ')'; format[index] = '$'; // Custom symbol to define consumed part of format for (var i = index + 1, n = index + code.length; i < n; i++) { regex[i] = ''; format[i] = '$'; } format = format.join(''); map.push({ index: index, apply: data.apply }); } }); return { regex: new RegExp('^' + regex.join('') + '$'), map: orderByFilter(map, 'index') }; } this.parse = function(input, format, baseDate) { if (!angular.isString(input) || !format) { return input; } format = $locale.DATETIME_FORMATS[format] || format; format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&'); if (!this.parsers[format]) { this.parsers[format] = createParser(format); } var parser = this.parsers[format], regex = parser.regex, map = parser.map, results = input.match(regex); if (results && results.length) { var fields, dt; if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) { fields = { year: baseDate.getFullYear(), month: baseDate.getMonth(), date: baseDate.getDate(), hours: baseDate.getHours(), minutes: baseDate.getMinutes(), seconds: baseDate.getSeconds(), milliseconds: baseDate.getMilliseconds() }; } else { if (baseDate) { $log.warn('dateparser:', 'baseDate is not a valid date'); } fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }; } for (var i = 1, n = results.length; i < n; i++) { var mapper = map[i-1]; if (mapper.apply) { mapper.apply.call(fields, results[i]); } } if (isValid(fields.year, fields.month, fields.date)) { dt = new Date(fields.year, fields.month, fields.date, fields.hours, fields.minutes, fields.seconds, fields.milliseconds || 0); } return dt; } }; // Check if date is valid for specific month (and year for February). // Month: 0 = Jan, 1 = Feb, etc function isValid(year, month, date) { if (date < 1) { return false; } if (month === 1 && date > 28) { return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); } if (month === 3 || month === 5 || month === 8 || month === 10) { return date < 31; } return true; } }]); 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) { 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 - offsetParentEl.scrollTop; offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; } var boundingClientRect = element[0].getBoundingClientRect(); return { width: boundingClientRect.width || element.prop('offsetWidth'), height: boundingClientRect.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: boundingClientRect.width || element.prop('offsetWidth'), height: boundingClientRect.height || element.prop('offsetHeight'), top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop), left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft) }; }, /** * Provides coordinates for the targetEl in relation to hostEl */ positionElements: function(hostEl, targetEl, positionStr, appendToBody) { var positionStrParts = positionStr.split('-'); var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; var hostElPos, targetElWidth, targetElHeight, targetElPos; hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); targetElWidth = targetEl.prop('offsetWidth'); targetElHeight = targetEl.prop('offsetHeight'); var shiftWidth = { center: function() { return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; }, left: function() { return hostElPos.left; }, right: function() { return hostElPos.left + hostElPos.width; } }; var shiftHeight = { center: function() { return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; }, top: function() { return hostElPos.top; }, bottom: function() { return hostElPos.top + hostElPos.height; } }; switch (pos0) { case 'right': targetElPos = { top: shiftHeight[pos1](), left: shiftWidth[pos0]() }; break; case 'left': targetElPos = { top: shiftHeight[pos1](), left: hostElPos.left - targetElWidth }; break; case 'bottom': targetElPos = { top: shiftHeight[pos0](), left: shiftWidth[pos1]() }; break; default: targetElPos = { top: hostElPos.top - targetElHeight, left: shiftWidth[pos1]() }; break; } return targetElPos; } }; }]); angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position']) .value('$datepickerSuppressError', false) .constant('datepickerConfig', { formatDay: 'dd', formatMonth: 'MMMM', formatYear: 'yyyy', formatDayHeader: 'EEE', formatDayTitle: 'MMMM yyyy', formatMonthTitle: 'yyyy', datepickerMode: 'day', minMode: 'day', maxMode: 'year', showWeeks: true, startingDay: 0, yearRange: 20, minDate: null, maxDate: null, shortcutPropagation: false }) .controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'datepickerConfig', '$datepickerSuppressError', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError) { var self = this, ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; // Modes chain this.modes = ['day', 'month', 'year']; // Configuration attributes angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', 'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) { self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; }); // Watchable date attributes angular.forEach(['minDate', 'maxDate'], function(key) { if ($attrs[key]) { $scope.$parent.$watch($parse($attrs[key]), function(value) { self[key] = value ? new Date(value) : null; self.refreshView(); }); } else { self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null; } }); angular.forEach(['minMode', 'maxMode'], function(key) { if ($attrs[key]) { $scope.$parent.$watch($parse($attrs[key]), function(value) { self[key] = angular.isDefined(value) ? value : $attrs[key]; $scope[key] = self[key]; if ((key == 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key])) || (key == 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key]))) { $scope.datepickerMode = self[key]; } }); } else { self[key] = datepickerConfig[key] || null; $scope[key] = self[key]; } }); $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); if (angular.isDefined($attrs.initDate)) { this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date(); $scope.$parent.$watch($attrs.initDate, function(initDate) { if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { self.activeDate = initDate; self.refreshView(); } }); } else { this.activeDate = new Date(); } $scope.isActive = function(dateObject) { if (self.compare(dateObject.date, self.activeDate) === 0) { $scope.activeDateId = dateObject.uid; return true; } return false; }; this.init = function(ngModelCtrl_) { ngModelCtrl = ngModelCtrl_; ngModelCtrl.$render = function() { self.render(); }; }; this.render = function() { if (ngModelCtrl.$viewValue) { var date = new Date(ngModelCtrl.$viewValue), isValid = !isNaN(date); if (isValid) { this.activeDate = date; } else if (!$datepickerSuppressError) { $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.'); } } this.refreshView(); }; this.refreshView = function() { if (this.element) { this._refreshView(); var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; ngModelCtrl.$setValidity('dateDisabled', !date || (this.element && !this.isDisabled(date))); } }; this.createDateObject = function(date, format) { var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; return { date: date, label: dateFilter(date, format), selected: model && this.compare(date, model) === 0, disabled: this.isDisabled(date), current: this.compare(date, new Date()) === 0, customClass: this.customClass(date) }; }; this.isDisabled = function(date) { return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}))); }; this.customClass = function(date) { return $scope.customClass({date: date, mode: $scope.datepickerMode}); }; // Split array into smaller arrays this.split = function(arr, size) { var arrays = []; while (arr.length > 0) { arrays.push(arr.splice(0, size)); } return arrays; }; // Fix a hard-reprodusible bug with timezones // The bug depends on OS, browser, current timezone and current date // i.e. // var date = new Date(2014, 0, 1); // console.log(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours()); // can result in "2013 11 31 23" because of the bug. this.fixTimeZone = function(date) { var hours = date.getHours(); date.setHours(hours === 23 ? hours + 2 : 0); }; $scope.select = function(date) { if ($scope.datepickerMode === self.minMode) { var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0); dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); ngModelCtrl.$setViewValue(dt); ngModelCtrl.$render(); } else { self.activeDate = date; $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1]; } }; $scope.move = function(direction) { var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), month = self.activeDate.getMonth() + direction * (self.step.months || 0); self.activeDate.setFullYear(year, month, 1); self.refreshView(); }; $scope.toggleMode = function(direction) { direction = direction || 1; if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) { return; } $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction]; }; // Key event mapper $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' }; var focusElement = function() { self.element[0].focus(); }; // Listen for focus requests from popup directive $scope.$on('datepicker.focus', focusElement); $scope.keydown = function(evt) { var key = $scope.keys[evt.which]; if (!key || evt.shiftKey || evt.altKey) { return; } evt.preventDefault(); if (!self.shortcutPropagation) { evt.stopPropagation(); } if (key === 'enter' || key === 'space') { if (self.isDisabled(self.activeDate)) { return; // do nothing } $scope.select(self.activeDate); focusElement(); } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { $scope.toggleMode(key === 'up' ? 1 : -1); focusElement(); } else { self.handleKeyDown(key, evt); self.refreshView(); } }; }]) .directive('datepicker', function() { return { restrict: 'EA', replace: true, templateUrl: function(element, attrs) { return attrs.templateUrl || 'template/datepicker/datepicker.html'; }, scope: { datepickerMode: '=?', dateDisabled: '&', customClass: '&', shortcutPropagation: '&?' }, require: ['datepicker', '^ngModel'], controller: 'DatepickerController', controllerAs: 'datepicker', link: function(scope, element, attrs, ctrls) { var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; datepickerCtrl.init(ngModelCtrl); } }; }) .directive('daypicker', ['dateFilter', function(dateFilter) { return { restrict: 'EA', replace: true, templateUrl: 'template/datepicker/day.html', require: '^datepicker', link: function(scope, element, attrs, ctrl) { scope.showWeeks = ctrl.showWeeks; ctrl.step = { months: 1 }; ctrl.element = element; var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; function getDaysInMonth(year, month) { return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; } function getDates(startDate, n) { var dates = new Array(n), current = new Date(startDate), i = 0, date; while (i < n) { date = new Date(current); ctrl.fixTimeZone(date); dates[i++] = date; current.setDate(current.getDate() + 1); } return dates; } ctrl._refreshView = function() { var year = ctrl.activeDate.getFullYear(), month = ctrl.activeDate.getMonth(), firstDayOfMonth = new Date(year, month, 1), difference = ctrl.startingDay - firstDayOfMonth.getDay(), numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, firstDate = new Date(firstDayOfMonth); if (numDisplayedFromPreviousMonth > 0) { firstDate.setDate(-numDisplayedFromPreviousMonth + 1); } // 42 is the number of days on a six-month calendar var days = getDates(firstDate, 42); for (var i = 0; i < 42; i ++) { days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), { secondary: days[i].getMonth() !== month, uid: scope.uniqueId + '-' + i }); } scope.labels = new Array(7); for (var j = 0; j < 7; j++) { scope.labels[j] = { abbr: dateFilter(days[j].date, ctrl.formatDayHeader), full: dateFilter(days[j].date, 'EEEE') }; } scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle); scope.rows = ctrl.split(days, 7); if (scope.showWeeks) { scope.weekNumbers = []; var thursdayIndex = (4 + 7 - ctrl.startingDay) % 7, numWeeks = scope.rows.length; for (var curWeek = 0; curWeek < numWeeks; curWeek++) { scope.weekNumbers.push( getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date)); } } }; ctrl.compare = function(date1, date2) { return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate())); }; function getISO8601WeekNumber(date) { var checkDate = new Date(date); checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday var time = checkDate.getTime(); checkDate.setMonth(0); // Compare with Jan 1 checkDate.setDate(1); return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; } ctrl.handleKeyDown = function(key, evt) { var date = ctrl.activeDate.getDate(); if (key === 'left') { date = date - 1; // up } else if (key === 'up') { date = date - 7; // down } else if (key === 'right') { date = date + 1; // down } else if (key === 'down') { date = date + 7; } else if (key === 'pageup' || key === 'pagedown') { var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); ctrl.activeDate.setMonth(month, 1); date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date); } else if (key === 'home') { date = 1; } else if (key === 'end') { date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()); } ctrl.activeDate.setDate(date); }; ctrl.refreshView(); } }; }]) .directive('monthpicker', ['dateFilter', function(dateFilter) { return { restrict: 'EA', replace: true, templateUrl: 'template/datepicker/month.html', require: '^datepicker', link: function(scope, element, attrs, ctrl) { ctrl.step = { years: 1 }; ctrl.element = element; ctrl._refreshView = function() { var months = new Array(12), year = ctrl.activeDate.getFullYear(), date; for (var i = 0; i < 12; i++) { date = new Date(year, i, 1); ctrl.fixTimeZone(date); months[i] = angular.extend(ctrl.createDateObject(date, ctrl.formatMonth), { uid: scope.uniqueId + '-' + i }); } scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle); scope.rows = ctrl.split(months, 3); }; ctrl.compare = function(date1, date2) { return new Date(date1.getFullYear(), date1.getMonth()) - new Date(date2.getFullYear(), date2.getMonth()); }; ctrl.handleKeyDown = function(key, evt) { var date = ctrl.activeDate.getMonth(); if (key === 'left') { date = date - 1; // up } else if (key === 'up') { date = date - 3; // down } else if (key === 'right') { date = date + 1; // down } else if (key === 'down') { date = date + 3; } else if (key === 'pageup' || key === 'pagedown') { var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); ctrl.activeDate.setFullYear(year); } else if (key === 'home') { date = 0; } else if (key === 'end') { date = 11; } ctrl.activeDate.setMonth(date); }; ctrl.refreshView(); } }; }]) .directive('yearpicker', ['dateFilter', function(dateFilter) { return { restrict: 'EA', replace: true, templateUrl: 'template/datepicker/year.html', require: '^datepicker', link: function(scope, element, attrs, ctrl) { var range = ctrl.yearRange; ctrl.step = { years: range }; ctrl.element = element; function getStartingYear( year ) { return parseInt((year - 1) / range, 10) * range + 1; } ctrl._refreshView = function() { var years = new Array(range), date; for (var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++) { date = new Date(start + i, 0, 1); ctrl.fixTimeZone(date); years[i] = angular.extend(ctrl.createDateObject(date, ctrl.formatYear), { uid: scope.uniqueId + '-' + i }); } scope.title = [years[0].label, years[range - 1].label].join(' - '); scope.rows = ctrl.split(years, 5); }; ctrl.compare = function(date1, date2) { return date1.getFullYear() - date2.getFullYear(); }; ctrl.handleKeyDown = function(key, evt) { var date = ctrl.activeDate.getFullYear(); if (key === 'left') { date = date - 1; // up } else if (key === 'up') { date = date - 5; // down } else if (key === 'right') { date = date + 1; // down } else if (key === 'down') { date = date + 5; } else if (key === 'pageup' || key === 'pagedown') { date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years; } else if (key === 'home') { date = getStartingYear(ctrl.activeDate.getFullYear()); } else if (key === 'end') { date = getStartingYear(ctrl.activeDate.getFullYear()) + range - 1; } ctrl.activeDate.setFullYear(date); }; ctrl.refreshView(); } }; }]) .constant('datepickerPopupConfig', { datepickerPopup: 'yyyy-MM-dd', datepickerPopupTemplateUrl: 'template/datepicker/popup.html', d