UNPKG

insight-ui

Version:

An open-source frontend for the Insight API. The Insight API provides you with a convenient, powerful and simple way to query and broadcast data on the bitcoin network and build your own services with it.

1,515 lines (1,346 loc) 111 kB
/* * angular-ui-bootstrap * http://angular-ui.github.io/bootstrap/ * Version: 0.10.0 - 2014-01-13 * License: MIT */ angular.module("ui.bootstrap", ["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); 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']) .directive('collapse', ['$transition', function ($transition, $timeout) { return { link: function (scope, element, attrs) { var initialAnimSkip = true; var currentTransition; function doTransition(change) { var newTransition = $transition(element, change); if (currentTransition) { currentTransition.cancel(); } currentTransition = newTransition; newTransition.then(newTransitionDone, newTransitionDone); return newTransition; function newTransitionDone() { // Make sure it's this transition, otherwise, leave it alone. if (currentTransition === newTransition) { currentTransition = undefined; } } } function expand() { if (initialAnimSkip) { initialAnimSkip = false; expandDone(); } else { element.removeClass('collapse').addClass('collapsing'); doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone); } } function expandDone() { element.removeClass('collapsing'); element.addClass('collapse in'); element.css({height: 'auto'}); } function collapse() { if (initialAnimSkip) { initialAnimSkip = false; collapseDone(); element.css({height: 0}); } else { // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value element.css({ height: element[0].scrollHeight + 'px' }); //trigger reflow so a browser realizes that height was updated from auto to a specific value var x = element[0].offsetWidth; element.removeClass('collapse in').addClass('collapsing'); doTransition({ height: 0 }).then(collapseDone); } } function collapseDone() { 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(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', function($parse) { 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: function() { 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.$parent.$watch(getIsOpen, function(value) { scope.isOpen = !!value; }); } 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", []) .controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { $scope.closeable = 'close' in $attrs; }]) .directive('alert', function () { return { restrict:'EA', controller:'AlertController', templateUrl:'template/alert/alert.html', transclude:true, replace:true, scope: { type: '=', close: '&' } }; }); angular.module('ui.bootstrap.bindHtml', []) .directive('bindHtmlUnsafe', function () { return function (scope, element, attr) { 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', link: function (scope, element, attrs, ctrls) { var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; //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 (!element.hasClass(buttonsCtrl.activeClass)) { scope.$apply(function () { ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio)); ngModelCtrl.$render(); }); } }); } }; }) .directive('btnCheckbox', function () { return { require: ['btnCheckbox', 'ngModel'], controller: 'ButtonsController', link: function (scope, element, attrs, ctrls) { var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 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 () { 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', ['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; var destroyed = false; /* 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() { // Scope has been destroyed, stop here. if (destroyed) { return; } //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); var reflow = 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; } }; $scope.$on('$destroy', function () { destroyed = true; }); /* 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); $scope.$on('$destroy', resetTimer); function restartTimer() { resetTimer(); var interval = +$scope.interval; if (!isNaN(interval) && interval>=0) { currentTimeout = $timeout(timerFn, interval); } } function resetTimer() { if (currentTimeout) { $timeout.cancel(currentTimeout); currentTimeout = null; } } function timerFn() { if (isPlaying) { $scope.next(); restartTimer(); } 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) { //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.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].body.scrollTop || $document[0].documentElement.scrollTop), left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft) }; } }; }]); angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position']) .constant('datepickerConfig', { dayFormat: 'dd', monthFormat: 'MMMM', yearFormat: 'yyyy', dayHeaderFormat: 'EEE', dayTitleFormat: 'MMMM yyyy', monthTitleFormat: 'yyyy', showWeeks: true, startingDay: 0, yearRange: 20, minDate: null, maxDate: null }) .controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function($scope, $attrs, dateFilter, dtConfig) { var format = { day: getValue($attrs.dayFormat, dtConfig.dayFormat), month: getValue($attrs.monthFormat, dtConfig.monthFormat), year: getValue($attrs.yearFormat, dtConfig.yearFormat), dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat), dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat), monthTitle: getValue($attrs.monthTitleFormat, dtConfig.monthTitleFormat) }, startingDay = getValue($attrs.startingDay, dtConfig.startingDay), yearRange = getValue($attrs.yearRange, dtConfig.yearRange); this.minDate = dtConfig.minDate ? new Date(dtConfig.minDate) : null; this.maxDate = dtConfig.maxDate ? new Date(dtConfig.maxDate) : null; function getValue(value, defaultValue) { return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue; } function getDaysInMonth( year, month ) { return new Date(year, month, 0).getDate(); } function getDates(startDate, n) { var dates = new Array(n); var current = startDate, i = 0; while (i < n) { dates[i++] = new Date(current); current.setDate( current.getDate() + 1 ); } return dates; } function makeDate(date, format, isSelected, isSecondary) { return { date: date, label: dateFilter(date, format), selected: !!isSelected, secondary: !!isSecondary }; } this.modes = [ { name: 'day', getVisibleDates: function(date, selected) { var year = date.getFullYear(), month = date.getMonth(), firstDayOfMonth = new Date(year, month, 1); var difference = startingDay - firstDayOfMonth.getDay(), numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, firstDate = new Date(firstDayOfMonth), numDates = 0; if ( numDisplayedFromPreviousMonth > 0 ) { firstDate.setDate( - numDisplayedFromPreviousMonth + 1 ); numDates += numDisplayedFromPreviousMonth; // Previous } numDates += getDaysInMonth(year, month + 1); // Current numDates += (7 - numDates % 7) % 7; // Next var days = getDates(firstDate, numDates), labels = new Array(7); for (var i = 0; i < numDates; i ++) { var dt = new Date(days[i]); days[i] = makeDate(dt, format.day, (selected && selected.getDate() === dt.getDate() && selected.getMonth() === dt.getMonth() && selected.getFullYear() === dt.getFullYear()), dt.getMonth() !== month); } for (var j = 0; j < 7; j++) { labels[j] = dateFilter(days[j].date, format.dayHeader); } return { objects: days, title: dateFilter(date, format.dayTitle), labels: labels }; }, compare: function(date1, date2) { return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); }, split: 7, step: { months: 1 } }, { name: 'month', getVisibleDates: function(date, selected) { var months = new Array(12), year = date.getFullYear(); for ( var i = 0; i < 12; i++ ) { var dt = new Date(year, i, 1); months[i] = makeDate(dt, format.month, (selected && selected.getMonth() === i && selected.getFullYear() === year)); } return { objects: months, title: dateFilter(date, format.monthTitle) }; }, compare: function(date1, date2) { return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); }, split: 3, step: { years: 1 } }, { name: 'year', getVisibleDates: function(date, selected) { var years = new Array(yearRange), year = date.getFullYear(), startYear = parseInt((year - 1) / yearRange, 10) * yearRange + 1; for ( var i = 0; i < yearRange; i++ ) { var dt = new Date(startYear + i, 0, 1); years[i] = makeDate(dt, format.year, (selected && selected.getFullYear() === dt.getFullYear())); } return { objects: years, title: [years[0].label, years[yearRange - 1].label].join(' - ') }; }, compare: function(date1, date2) { return date1.getFullYear() - date2.getFullYear(); }, split: 5, step: { years: yearRange } } ]; this.isDisabled = function(date, mode) { var currentMode = this.modes[mode || 0]; return ((this.minDate && currentMode.compare(date, this.minDate) < 0) || (this.maxDate && currentMode.compare(date, this.maxDate) > 0) || ($scope.dateDisabled && $scope.dateDisabled({date: date, mode: currentMode.name}))); }; }]) .directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter, $parse, datepickerConfig, $log) { return { restrict: 'EA', replace: true, templateUrl: 'template/datepicker/datepicker.html', scope: { dateDisabled: '&' }, require: ['datepicker', '?^ngModel'], controller: 'DatepickerController', link: function(scope, element, attrs, ctrls) { var datepickerCtrl = ctrls[0], ngModel = ctrls[1]; if (!ngModel) { return; // do nothing if no ng-model } // Configuration parameters var mode = 0, selected = new Date(), showWeeks = datepickerConfig.showWeeks; if (attrs.showWeeks) { scope.$parent.$watch($parse(attrs.showWeeks), function(value) { showWeeks = !! value; updateShowWeekNumbers(); }); } else { updateShowWeekNumbers(); } if (attrs.min) { scope.$parent.$watch($parse(attrs.min), function(value) { datepickerCtrl.minDate = value ? new Date(value) : null; refill(); }); } if (attrs.max) { scope.$parent.$watch($parse(attrs.max), function(value) { datepickerCtrl.maxDate = value ? new Date(value) : null; refill(); }); } function updateShowWeekNumbers() { scope.showWeekNumbers = mode === 0 && showWeeks; } // Split array into smaller arrays function split(arr, size) { var arrays = []; while (arr.length > 0) { arrays.push(arr.splice(0, size)); } return arrays; } function refill( updateSelected ) { var date = null, valid = true; if ( ngModel.$modelValue ) { date = new Date( ngModel.$modelValue ); if ( isNaN(date) ) { valid = false; $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.'); } else if ( updateSelected ) { selected = date; } } ngModel.$setValidity('date', valid); var currentMode = datepickerCtrl.modes[mode], data = currentMode.getVisibleDates(selected, date); angular.forEach(data.objects, function(obj) { obj.disabled = datepickerCtrl.isDisabled(obj.date, mode); }); ngModel.$setValidity('date-disabled', (!date || !datepickerCtrl.isDisabled(date))); scope.rows = split(data.objects, currentMode.split); scope.labels = data.labels || []; scope.title = data.title; } function setMode(value) { mode = value; updateShowWeekNumbers(); refill(); } ngModel.$render = function() { refill( true ); }; scope.select = function( date ) { if ( mode === 0 ) { var dt = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); ngModel.$setViewValue( dt ); refill( true ); } else { selected = date; setMode( mode - 1 ); } }; scope.move = function(direction) { var step = datepickerCtrl.modes[mode].step; selected.setMonth( selected.getMonth() + direction * (step.months || 0) ); selected.setFullYear( selected.getFullYear() + direction * (step.years || 0) ); refill(); }; scope.toggleMode = function() { setMode( (mode + 1) % datepickerCtrl.modes.length ); }; scope.getWeekNumber = function(row) { return ( mode === 0 && scope.showWeekNumbers && row.length === 7 ) ? getISO8601WeekNumber(row[0].date) : null; }; 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; } } }; }]) .constant('datepickerPopupConfig', { dateFormat: 'yyyy-MM-dd', currentText: 'Today', toggleWeeksText: 'Weeks', clearText: 'Clear', closeText: 'Done', closeOnDateSelection: true, appendToBody: false, showButtonBar: true }) .directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig', 'datepickerConfig', function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig, datepickerConfig) { return { restrict: 'EA', require: 'ngModel', link: function(originalScope, element, attrs, ngModel) { var scope = originalScope.$new(), // create a child scope so we are not polluting original one dateFormat, closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? originalScope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? originalScope.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; attrs.$observe('datepickerPopup', function(value) { dateFormat = value || datepickerPopupConfig.dateFormat; ngModel.$render(); }); scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? originalScope.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; originalScope.$on('$destroy', function() { $popup.remove(); scope.$destroy(); }); attrs.$observe('currentText', function(text) { scope.currentText = angular.isDefined(text) ? text : datepickerPopupConfig.currentText; }); attrs.$observe('toggleWeeksText', function(text) { scope.toggleWeeksText = angular.isDefined(text) ? text : datepickerPopupConfig.toggleWeeksText; }); attrs.$observe('clearText', function(text) { scope.clearText = angular.isDefined(text) ? text : datepickerPopupConfig.clearText; }); attrs.$observe('closeText', function(text) { scope.closeText = angular.isDefined(text) ? text : datepickerPopupConfig.closeText; }); var getIsOpen, setIsOpen; if ( attrs.isOpen ) { getIsOpen = $parse(attrs.isOpen); setIsOpen = getIsOpen.assign; originalScope.$watch(getIsOpen, function updateOpen(value) { scope.isOpen = !! value; }); } scope.isOpen = getIsOpen ? getIsOpen(originalScope) : false; // Initial state function setOpen( value ) { if (setIsOpen) { setIsOpen(originalScope, !!value); } else { scope.isOpen = !!value; } } var documentClickBind = function(event) { if (scope.isOpen && event.target !== element[0]) { scope.$apply(function() { setOpen(false); }); } }; var elementFocusBind = function() { scope.$apply(function() { setOpen( true ); }); }; // popup element used to display calendar var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>'); popupEl.attr({ 'ng-model': 'date', 'ng-change': 'dateSelection()' }); var datepickerEl = angular.element(popupEl.children()[0]), datepickerOptions = {}; if (attrs.datepickerOptions) { datepickerOptions = originalScope.$eval(attrs.datepickerOptions); datepickerEl.attr(angular.extend({}, datepickerOptions)); } // TODO: reverse from dateFilter string to Date object function parseDate(viewValue) { if (!viewValue) { ngModel.$setValidity('date', true); return null; } else if (angular.isDate(viewValue)) { ngModel.$setValidity('date', true); return viewValue; } else if (angular.isString(viewValue)) { var date = new Date(viewValue); if (isNaN(date)) { ngModel.$setValidity('date', false); return undefined; } else { ngModel.$setValidity('date', true); return date; } } else { ngModel.$setValidity('date', false); return undefined; } } ngModel.$parsers.unshift(parseDate); // Inner change scope.dateSelection = function(dt) { if (angular.isDefined(dt)) { scope.date = dt; } ngModel.$setViewValue(scope.date); ngModel.$render(); if (closeOnDateSelection) { setOpen( false ); } }; element.bind('input change keyup', function() { scope.$apply(function() { scope.date = ngModel.$modelValue; }); }); // Outter change ngModel.$render = function() { var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; element.val(date); scope.date = ngModel.$modelValue; }; function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) { if (attribute) { originalScope.$watch($parse(attribute), function(value){ scope[scopeProperty] = value; }); datepickerEl.attr(datepickerAttribute || scopeProperty, scopeProperty); } } addWatchableAttribute(attrs.min, 'min'); addWatchableAttribute(attrs.max, 'max'); if (attrs.showWeeks) { addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks'); } else { scope.showWeeks = 'show-weeks' in datepickerOptions ? datepickerOptions['show-weeks'] : datepickerConfig.showWeeks; datepickerEl.attr('show-weeks', 'showWeeks'); } if (attrs.dateDisabled) { datepickerEl.attr('date-disabled', attrs.dateDisabled); } function updatePosition() { scope.position = appendToBody ? $position.offset(element) : $position.position(element); scope.position.top = scope.position.top + element.prop('offsetHeight'); } var documentBindingInitialized = false, elementFocusInitialized = false; scope.$watch('isOpen', function(value) { if (value) { updatePosition(); $document.bind('click', documentClickBind); if(elementFocusInitialized) { element.unbind('focus', elementFocusBind); } element[0].focus(); documentBindingInitialized = true; } else { if(documentBindingInitialized) { $document.unbind('click', documentClickBind); } element.bind('focus', elementFocusBind); elementFocusInitialized = true; } if ( setIsOpen ) { setIsOpen(originalScope, value); } }); scope.today = function() { scope.dateSelection(new Date()); }; scope.clear = function() { scope.dateSelection(null); }; var $popup = $compile(popupEl)(scope); if ( appendToBody ) { $document.find('body').append($popup); } else { element.after($popup); } } }; }]) .directive('datepickerPopupWrap', function() { return { restrict:'EA', replace: true, transclude: true, templateUrl: 'template/datepicker/popup.html', link:function (scope, element, attrs) { element.bind('click', function(event) { event.preventDefault(); event.stopPropagation(); }); } }; }); /* * 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.hasClass('disabled') && !element.prop('disabled')) { 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.transition']) /** * A helper, internal data structure that acts as a map but also allows getting / removing * elements in the LIFO order */ .factory('$$stackedMap', function () { return { createNew: function () { var stack = []; return { add: function (key, value) { stack.push({ key: key, value: value }); }, get: function (key) { for (var i = 0; i < stack.length; i++) { if (key == stack[i].key) { return stack[i]; } } }, keys: function() { var keys = []; for (var i = 0; i < stack.length; i++) { keys.push(stack[i].key); } return keys; }, top: function () { return stack[stack.length - 1]; }, remove: function (key) { var idx = -1; for (var i = 0; i < stack.length; i++) { if (key == stack[i].key) { idx = i; break; } } return stack.splice(idx, 1)[0]; }, removeTop: function () { return stack.splice(stack.length - 1, 1)[0]; }, length: function () { return stack.length; } }; } }; }) /** * A helper directive for the $modal service. It creates a backdrop element. */ .directive('modalBackdrop', ['$timeout', function ($timeout) { return { restrict: 'EA', replace: true, templateUrl: 'template/modal/backdrop.html', link: function (scope) { scope.animate = false; //trigger CSS transitions $timeout(function () { scope.animate = true; }); } }; }]) .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { return { restrict: 'EA', scope: { index: '@', animate: '=' }, replace: true, transclude: true, templateUrl: 'template/modal/window.html', link: function (scope, element, attrs) { scope.windowClass = attrs.windowClass || ''; $timeout(function () { // trigger CSS transitions scope.animate = true; // focus a freshly-opened modal element[0].focus(); }); scope.close = function (evt) { var modal = $modalStack.getTop(); if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { evt.preventDefault(); evt.stopPropagation(); $modalStack.dismiss(modal.key, 'backdrop click'); } }; } }; }]) .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { var OPENED_MODAL_CLASS = 'modal-open'; var backdropDomEl, backdropScope; var openedWindows = $$stackedMap.createNew(); var $modalStack = {}; function backdropIndex() { var topBackdropIndex = -1; var opened = openedWindows.keys(); for (var i = 0; i < opened.length; i++) { if (openedWindows.get(opened[i]).value.backdrop) { topBackdropIndex = i; } } return topBackdropIndex; } $rootScope.$watch(backdropIndex, function(newBackdropIndex){ if (backdropScope) { backdropScope.index = newBackdropIndex; } }); function removeModalWindow(modalInstance) { var body = $document.find('body').eq(0); var modalWindow = openedWindows.get(modalInstance).value; //clean up the stack openedWindows.remove(modalInstance); //remove window DOM element removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, checkRemoveBackdrop); body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); } function checkRemoveBackdrop() { //remove backdrop if no longer needed