<!DOCTYPE html>
<html ng-app="app">
<head>
</head>
<body ng-controller='AppCtrl as app'>
<div class='panel'><h3>Selection Type:</h3>
<div class='btn-group'>
<button class='btn btn-primary' ng-model='app.type' uib-btn-radio='"individual"'>Individual</button>
<button class='btn btn-primary' ng-model='app.type' uib-btn-radio='"range"'>Range</button>
</div>
</div>
<table>
<tr>
<td>
<uib-datepicker ng-model='app.activeDate' multi-select='app.selectedDates' select-range='{{app.type=="range"}}' template-url='multi-datepicker.tpl.html'></uib-datepicker>
</td>
<td style='width:50px'>
</td>
<td>
</td>
</tr>
<tr>
<td colspan='3' style='padding-top:20px'>
<button ng-if='!app.show2pickers' ng-click='app.show2pickers=true'>Show Second Datepicker</button>
</td>
</tr>
<tr ng-if='app.show2pickers'>
<td>
<uib-datepicker ng-model='app.activeDate2' multiple-views multi-select='app.selectedDates2' select-range='{{app.type=="range"}}' template-url='multi-datepicker.tpl.html'></uib-datepicker>
</td>
<td style='width:50px'>
</td>
<td>
<div ng-if='app.type=="individual"'>
Selected Dates:
<div class='well well-sm'>
<div ng-repeat='d in app.selectedDates2 | orderBy'>
{{d | date : 'fullDate'}}
<button class='btn btn-xs btn-warning' style='margin:5px' ng-click='app.removeFromSelected2(d)'>Remove</button>
</div>
</div>
</div>
<div ng-if='app.type=="range"' >
Selected Range:
<div class='well well-sm'>
{{(app.selectedDates | orderBy)[0] | date:"fullDate"}}
<span ng-if='app.selectedDates2.length > 1'>
- {{(app.selectedDates | orderBy : '-')[0] | date:"fullDate"}}
</span>
</div>
</div>
</td>
</tr>
</table>
<div ng-if='app.type=="individual"'>
Selected Dates:
<div class='well well-sm'>
<div ng-repeat='d in app.selectedDates | orderBy'>
{{d | date : 'fullDate'}}
<button class='btn btn-xs btn-warning' style='margin:5px' ng-click='app.removeFromSelected(d)'>Remove</button>
</div>
</div>
</div>
<div ng-if='app.type=="range"' >
Selected Range:
<div class='well well-sm'>
{{(app.selectedDates | orderBy)[0] | date:"fullDate"}}
<span ng-if='app.selectedDates.length > 1'>
- {{(app.selectedDates | orderBy : '-')[0] | date:"fullDate"}}
</span>
</div>
</div>
<script type="text/ng-template" id="uib/template/datepicker/day.html">
<table class="uib-daypicker" role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
<thead>
<tr>
<th>
<button type="button" class="btn btn-default btn-sm pull-left uib-left" ng-click="move(-1)" tabindex="-1">
<i class="glyphicon glyphicon-chevron-left"></i>
</button>
</th>
<th colspan="{{::5 + showWeeks}}">
<button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm uib-title" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1">
<strong>{{title}}</strong>
</button>
</th>
<th>
<button type="button" class="btn btn-default btn-sm pull-right uib-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button>
</th>
</tr>
<tr>
<th ng-if="showWeeks" class="text-center"></th>
<th ng-repeat="label in ::labels track by $index" class="text-center">
<small aria-label="{{::label.full}}">{{::label.abbr}}</small>
</th>
</tr>
</thead>
<tbody>
<tr class="uib-weeks" ng-repeat="row in rows track by $index">
<td ng-if="showWeeks" class="text-center h6">
<em>{{ weekNumbers[$index] }}</em>
</td>
<td ng-repeat="dt in row" class="uib-day text-center" role="gridcell" id="{{::dt.uid}}" ng-class="::dt.customClass">
<button type="button" class="btn btn-default btn-sm" ng-class="{'btn-info': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="::dt.disabled" tabindex="-1">
<span ng-class="::{'text-muted': dt.secondary, 'text-info': dt.current}">{{::dt.label}}</span>
</button>
</td>
</tr>
</tbody>
</table>
</script>
<script type="text/ng-template" id="uib/template/datepicker/year.html">
<table class="uib-daypicker" role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
<thead>
<tr>
<th>
<button type="button" class="btn btn-default btn-sm pull-left uib-left" ng-click="move(-1)" tabindex="-1">
<i class="glyphicon glyphicon-chevron-left"></i>
</button>
</th>
<th colspan="{{::5 + showWeeks}}">
<button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm uib-title" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1">
<strong>{{title}}</strong>
</button>
</th>
<th>
<button type="button" class="btn btn-default btn-sm pull-right uib-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button>
</th>
</tr>
<tr>
<th ng-if="showWeeks" class="text-center"></th>
<th ng-repeat="label in ::vm.labels track by $index" class="text-center">
<small aria-label="{{::label.full}}">{{::label.abbr}}</small>
</th>
</tr>
</thead>
<tbody>
<tr class="uib-weeks" ng-repeat="row in rows track by $index">
<td ng-if="showWeeks" class="text-center h6">
<em>{{ weekNumbers[$index] }}</em>
</td>
<td ng-repeat="dt in row" class="uib-day text-center" role="gridcell" id="{{::dt.uid}}" ng-class="::dt.customClass">
<button type="button" class="btn btn-default btn-sm" ng-class="{'btn-info': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="::dt.disabled" tabindex="-1">
<span ng-class="::{'text-muted': dt.secondary, 'text-info': dt.current}">{{::dt.label}}</span>
</button>
</td>
</tr>
</tbody>
</table>
</script>
<script type="text/ng-template" id="uib/template/datepicker/month.html">
<table class="uib-daypicker" role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
<thead>
<tr>
<th>
<button type="button" class="btn btn-default btn-sm pull-left uib-left" ng-click="move(-1)" tabindex="-1">
<i class="glyphicon glyphicon-chevron-left"></i>
</button>
</th>
<th colspan="{{::5 + showWeeks}}">
<button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm uib-title" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1">
<strong>{{title}}</strong>
</button>
</th>
<th>
<button type="button" class="btn btn-default btn-sm pull-right uib-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button>
</th>
</tr>
<tr>
<th ng-if="showWeeks" class="text-center"></th>
<th ng-repeat="label in ::labels track by $index" class="text-center">
<small aria-label="{{::label.full}}">{{::label.abbr}}</small>
</th>
</tr>
</thead>
<tbody>
<tr class="uib-weeks" ng-repeat="row in rows track by $index">
<td ng-if="showWeeks" class="text-center h6">
<em>{{ weekNumbers[$index] }}</em>
</td>
<td ng-repeat="dt in row" class="uib-day text-center" role="gridcell" id="{{::dt.uid}}" ng-class="::dt.customClass">
<button type="button" class="btn btn-default btn-sm" ng-class="{'btn-info': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="::dt.disabled" tabindex="-1">
<span ng-class="::{'text-muted': dt.secondary, 'text-info': dt.current}">{{::dt.label}}</span>
</button>
</td>
</tr>
</tbody>
</table>
</script>
<script data-require="angular.js@*" data-semver="1.5.6" src="//code.angularjs.org/1.5.6/angular.js"></script>
<script src="uibootstrap.js"></script>
<link data-require="bootstrap-css@*" data-semver="3.3.6" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
<script src="gm.datepickerMultiSelect.js"></script>
</body>
</html>
angular.module('app', ['gm.datepickerMultiSelect'])
.controller('AppCtrl', function() {
this.activeDate = null;
this.activeDate2 = null;
this.selectedDates = [new Date().setHours(0, 0, 0, 0)];
this.selectedDates2 = [new Date().setHours(0, 0, 0, 0)];
this.type = 'individual';
this.show2pickers = false;
this.removeFromSelected = function(dt) {
this.selectedDates.splice(this.selectedDates.indexOf(dt), 1);
}
this.removeFromSelected2 = function(dt) {
this.selectedDates2.splice(this.selectedDates2.indexOf(dt), 1);
}
})
angular.module('ui.bootstrap')
.controller('UibMultiDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
this.step = { months: 1 };
this.element = $element;
this.monthNames = [];
this.noOfViews= 3;
function getDaysInMonth(year, month) {
return month === 1 && year % 4 === 0 &&
(year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
}
this.init = function(ctrl) {
angular.extend(ctrl, this);
scope.showWeeks = ctrl.showWeeks;
ctrl.refreshView();
};
this.getDates = function(startDate, n) {
var dates = new Array(n), current = new Date(startDate), i = 0, date;
while (i < n) {
date = new Date(current);
dates[i++] = date;
current.setDate(current.getDate() + 1);
}
return dates;
};
this._refreshView = function() {
var activeDate = angular.copy(this.activeDate);
if(scope.views && scope.views.length){
angular.forEach(scope.views, function(value,index){
if(value.date.getMonth() == activeDate.getMonth() && value.date.getFullYear() == activeDate.getFullYear())
{
activeDate = scope.views[0].date;
return true;
}
});
}
scope.views = [];
// scope.views[0] = {};
var vw = {};
if(scope.noOfViews){}
scope.noOfViews = 3;
for(var it=0;it< scope.noOfViews;it++){
this.activeDate = new Date(activeDate.getFullYear(), activeDate.getMonth()+it, activeDate.getDate());
var year = this.activeDate.getFullYear(),
month = this.activeDate.getMonth(),
firstDayOfMonth = new Date(this.activeDate);
firstDayOfMonth.setFullYear(year, month, 1);
var difference = this.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-week calendar
var days = this.getDates(firstDate, 42);
for (var i = 0; i < 42; i ++) {
days[i] = angular.extend(this.createDateObject(days[i], this.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, this.formatDayHeader),
full: dateFilter(days[j].date, 'EEEE')
};
}
scope.title = dateFilter(this.activeDate, this.formatDayTitle);
scope.rows = this.split(days, 7);
if (scope.showWeeks) {
scope.weekNumbers = [];
var thursdayIndex = (4 + 7 - this.startingDay) % 7,
numWeeks = scope.rows.length;
for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
scope.weekNumbers.push(
getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
}
}
vw.date = this.activeDate;
vw.title= scope.title;
vw.labels = scope.labels;
vw.rows = scope.rows;
vw.showWeeks = scope.showWeeks;
vw.weekNumbers = scope.weekNumbers;
scope.views.push(angular.copy(vw));
}
this.activeDate = activeDate;
};
this.compare = function(date1, date2) {
var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
_date1.setFullYear(date1.getFullYear());
_date2.setFullYear(date2.getFullYear());
return _date1 - _date2;
};
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;
}
this.handleKeyDown = function(key, evt) {
var date = this.activeDate.getDate();
if (key === 'left') {
date = date - 1;
} else if (key === 'up') {
date = date - 7;
} else if (key === 'right') {
date = date + 1;
} else if (key === 'down') {
date = date + 7;
} else if (key === 'pageup' || key === 'pagedown') {
var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
this.activeDate.setMonth(month, 1);
date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
} else if (key === 'home') {
date = 1;
} else if (key === 'end') {
date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
}
this.activeDate.setDate(date);
};
}])
.directive('uibMultidaypicker', function() {
return {
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/datepicker/day.html';
},
require: ['^uibDatepicker', 'uibMultidaypicker'],
controller: 'UibMultiDaypickerController',
link: function(scope, element, attrs, ctrls) {
var datepickerCtrl = ctrls[0],
daypickerCtrl = ctrls[1];
daypickerCtrl.init(datepickerCtrl);
}
};
});
body { padding:10px; }
td { vertical-align: top }
[View on Github](https://github.com/spongessuck/gm.datepickerMultiSelect/)
/*
The MIT License (MIT)
Copyright (c) 2014 Gregory McGee
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
(function (angular) {
'use strict';
angular.module('gm.datepickerMultiSelect', ['ui.bootstrap'])
.config(['$provide', '$injector', function ($provide, $injector) {
// extending datepicker (access to attributes and app scope through $parent)
var datepickerDelegate = function ($delegate) {
var directive = $delegate[0];
// Override compile
var link = directive.link;
directive.compile = function () {
return function (scope, element, attrs, ctrls) {
link.apply(this, arguments);
scope.selectedDates = [];
scope.selectRange;
scope.$parent.$watchCollection(attrs.multiSelect, function (newVal) {
scope.selectedDates = newVal || [];
});
attrs.$observe('selectRange', function (newVal) {
scope.selectRange = !!newVal && newVal !== "false";
});
var ngModelCtrl = ctrls[1];
debugger;
ngModelCtrl.$viewChangeListeners.push(function() {
debugger;
var newVal = scope.$parent.$eval(attrs.ngModel);
if(!newVal)
return;
var dateVal = newVal.getTime(),
selectedDates = scope.selectedDates;
if (scope.selectRange) {
// reset range
if (!selectedDates.length || selectedDates.length > 1 || selectedDates[0] == dateVal)
return selectedDates.splice(0, selectedDates.length, dateVal);
selectedDates.push(dateVal);
var tempVal = Math.min.apply(null, selectedDates);
var maxVal = Math.max.apply(null, selectedDates);
// Start on the next day to prevent duplicating the first date
tempVal = new Date(tempVal).setHours(24);
while (tempVal < maxVal) {
selectedDates.push(tempVal);
// Set a day ahead after pushing to prevent duplicating last date
tempVal = new Date(tempVal).setHours(24);
}
} else {
if (selectedDates.indexOf(dateVal) < 0) {
selectedDates.push(dateVal);
} else {
selectedDates.splice(selectedDates.indexOf(dateVal), 1);
}
}
});
}
}
return $delegate;;
}
if ($injector.has('datepickerDirective'))
$provide.decorator('datepickerDirective', ['$delegate', datepickerDelegate]);
if ($injector.has('uibDatepickerDirective'))
$provide.decorator('uibDatepickerDirective', ['$delegate', datepickerDelegate]);
// extending daypicker (access to day and datepicker scope through $parent)
var daypickerDelegate = function ($delegate) {
var directive = $delegate[0];
// Override compile
var link = directive.link;
directive.compile = function () {
return function (scope, element, attrs, ctrls) {
link.apply(this, arguments);
scope.$parent.$watchCollection('selectedDates', update);
/*
Fires when date is selected or when month is changed.
UI bootstrap versions before 0.14.0 had just one controller DatepickerController,
now they have UibDatepickerController, UibDaypickerController and DatepickerController
see more on https://github.com/angular-ui/bootstrap/commit/44354f67e55c571df28b09e26a314a845a3b7397?diff=split#diff-6240fc17e068eaeef7095937a1d63eaeL251
and https://github.com/angular-ui/bootstrap/commit/44354f67e55c571df28b09e26a314a845a3b7397?diff=split#diff-6240fc17e068eaeef7095937a1d63eaeR462
*/
var ctrl = angular.isArray(ctrls) ? ctrls[0] : ctrls;
scope.$watch(function () {
return ctrl.activeDate.getTime();
}, update);
function update() {
debugger;
if(attrs.multipleViews){
angular.forEach(scope.views,function(value, index){
angular.forEach(value.rows, function (row) {
angular.forEach(row, function (day) {
day.selected = scope.selectedDates.indexOf(day.date.setHours(0, 0, 0, 0)) > -1
});
});
});
}else{
angular.forEach(scope.rows, function (row) {
angular.forEach(row, function (day) {
day.selected = scope.selectedDates.indexOf(day.date.setHours(0, 0, 0, 0)) > -1
});
});
}
}
}
}
return $delegate;
}
debugger;
if ($injector.has('daypickerDirective'))
$provide.decorator('daypickerDirective', ['$delegate', daypickerDelegate]);
if ($injector.has('uibDaypickerDirective'))
$provide.decorator('uibDaypickerDirective', ['$delegate', daypickerDelegate]);
if ($injector.has('uibMultidaypickerDirective'))
$provide.decorator('uibMultidaypickerDirective', ['$delegate', daypickerDelegate]);
}]);
})(window.angular);
<span><span data-ng-repeat="vw in views">
<table class="uib-daypicker" role="grid" aria-labelledby="{{::uniqueId}}-title" style="display:inline-block;"
aria-activedescendant="{{vw.activeDateId}}">
<thead>
<tr>
<th data-ng-show="$index == 0">
<button type="button" class="btn btn-default btn-sm pull-left uib-left"
ng-click="move(-3)" tabindex="-1">
<i class="glyphicon glyphicon-chevron-left"></i>
</button>
</th>
<th colspan="{{::5 + vw.showWeeks}}">
<button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm uib-title" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1">
<strong>{{vw.title}}</strong>
</button>
</th>
<th data-ng-show="$index == views.length-1">
<button type="button" class="btn btn-default btn-sm pull-right uib-right"
ng-click="move(3)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button>
</th>
</tr>
<tr>
<th ng-if="vw.showWeeks" class="text-center"></th>
<th ng-repeat="label in ::vw.labels track by $index" class="text-center">
<small aria-label="{{::label.full}}">{{::label.abbr}}</small>
</th>
</tr>
</thead>
<tbody>
<tr class="uib-weeks" ng-repeat="row in vw.rows track by $index">
<td ng-if="vw.showWeeks" class="text-center h6">
<em>{{ vw.weekNumbers[$index] }}</em>
</td>
<td ng-repeat="dt in row" class="uib-day text-center" role="gridcell" id="{{::dt.uid}}" ng-class="::dt.customClass">
<button type="button" class="btn btn-default btn-sm" ng-class="{'btn-info': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="::dt.disabled" tabindex="-1">
<span ng-class="::{'text-muted': dt.secondary, 'text-info': dt.current}">{{::dt.label}}</span>
</button>
</td>
</tr>
</tbody>
</table>
</span>
</span>
<div class="uib-datepicker" ng-switch="datepickerMode" role="application" ng-keydown="keydown($event)">
<uib-multidaypicker ng-switch-when="day" multiple-views="3" tabindex="0" template-url="multi-day.tpl.html"></uib-multidaypicker>
<uib-monthpicker ng-switch-when="month" tabindex="0"></uib-monthpicker>
<uib-yearpicker ng-switch-when="year" tabindex="0"></uib-yearpicker>
</div>
/*
* angular-ui-bootstrap
* http://angular-ui.github.io/bootstrap/
* Version: 1.2.1 - 2016-02-27
* License: MIT
*/angular.module("ui.bootstrap", ["ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.debounce","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.paging","ui.bootstrap.pager","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.collapse', [])
.directive('uibCollapse', ['$animate', '$q', '$parse', '$injector', function($animate, $q, $parse, $injector) {
var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
return {
link: function(scope, element, attrs) {
var expandingExpr = $parse(attrs.expanding),
expandedExpr = $parse(attrs.expanded),
collapsingExpr = $parse(attrs.collapsing),
collapsedExpr = $parse(attrs.collapsed);
if (!scope.$eval(attrs.uibCollapse)) {
element.addClass('in')
.addClass('collapse')
.attr('aria-expanded', true)
.attr('aria-hidden', false)
.css({height: 'auto'});
}
function expand() {
if (element.hasClass('collapse') && element.hasClass('in')) {
return;
}
$q.resolve(expandingExpr(scope))
.then(function() {
element.removeClass('collapse')
.addClass('collapsing')
.attr('aria-expanded', true)
.attr('aria-hidden', false);
if ($animateCss) {
$animateCss(element, {
addClass: 'in',
easing: 'ease',
to: { height: element[0].scrollHeight + 'px' }
}).start()['finally'](expandDone);
} else {
$animate.addClass(element, 'in', {
to: { height: element[0].scrollHeight + 'px' }
}).then(expandDone);
}
});
}
function expandDone() {
element.removeClass('collapsing')
.addClass('collapse')
.css({height: 'auto'});
expandedExpr(scope);
}
function collapse() {
if (!element.hasClass('collapse') && !element.hasClass('in')) {
return collapseDone();
}
$q.resolve(collapsingExpr(scope))
.then(function() {
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);
if ($animateCss) {
$animateCss(element, {
removeClass: 'in',
to: {height: '0'}
}).start()['finally'](collapseDone);
} else {
$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')
.addClass('collapse');
collapsedExpr(scope);
}
scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
if (shouldCollapse) {
collapse();
} else {
expand();
}
});
}
};
}]);
angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
.constant('uibAccordionConfig', {
closeOthers: true
})
.controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', 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('uibAccordion', function() {
return {
controller: 'UibAccordionController',
controllerAs: 'accordion',
transclude: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/accordion/accordion.html';
}
};
})
// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
.directive('uibAccordionGroup', function() {
return {
require: '^uibAccordion', // We need this directive to be inside an accordion
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 || 'uib/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 || 'panel-default';
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;
}
}
};
var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
scope.headingId = id + '-tab';
scope.panelId = id + '-panel';
}
};
})
// Use accordion-heading below an accordion-group to provide a heading containing HTML
.directive('uibAccordionHeading', function() {
return {
transclude: true, // Grab the contents to be used as the heading
template: '', // In effect remove this element!
replace: true,
require: '^uibAccordionGroup',
link: function(scope, element, attrs, 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
.directive('uibAccordionTransclude', function() {
return {
require: '^uibAccordionGroup',
link: function(scope, element, attrs, controller) {
scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
if (heading) {
var elem = angular.element(element[0].querySelector('[uib-accordion-header]'));
elem.html('');
elem.append(heading);
}
});
}
};
});
angular.module('ui.bootstrap.alert', [])
.controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) {
$scope.closeable = !!$attrs.close;
var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
$interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
if (dismissOnTimeout) {
$timeout(function() {
$scope.close();
}, parseInt(dismissOnTimeout, 10));
}
}])
.directive('uibAlert', function() {
return {
controller: 'UibAlertController',
controllerAs: 'alert',
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/alert/alert.html';
},
transclude: true,
replace: true,
scope: {
type: '@',
close: '&'
}
};
});
angular.module('ui.bootstrap.buttons', [])
.constant('uibButtonConfig', {
activeClass: 'active',
toggleEvent: 'click'
})
.controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
this.activeClass = buttonConfig.activeClass || 'active';
this.toggleEvent = buttonConfig.toggleEvent || 'click';
}])
.directive('uibBtnRadio', ['$parse', function($parse) {
return {
require: ['uibBtnRadio', 'ngModel'],
controller: 'UibButtonsController',
controllerAs: 'buttons',
link: function(scope, element, attrs, ctrls) {
var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
var uncheckableExpr = $parse(attrs.uibUncheckable);
element.find('input').css({display: 'none'});
//model -> UI
ngModelCtrl.$render = function() {
element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
};
//ui->model
element.on(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.uibBtnRadio));
ngModelCtrl.$render();
});
}
});
if (attrs.uibUncheckable) {
scope.$watch(uncheckableExpr, function(uncheckable) {
attrs.$set('uncheckable', uncheckable ? '' : undefined);
});
}
}
};
}])
.directive('uibBtnCheckbox', function() {
return {
require: ['uibBtnCheckbox', 'ngModel'],
controller: 'UibButtonsController',
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(attribute, defaultValue) {
return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
}
//model -> UI
ngModelCtrl.$render = function() {
element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
};
//ui->model
element.on(buttonsCtrl.toggleEvent, function() {
if (attrs.disabled) {
return;
}
scope.$apply(function() {
ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
ngModelCtrl.$render();
});
});
}
};
});
angular.module('ui.bootstrap.carousel', [])
.controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
var self = this,
slides = self.slides = $scope.slides = [],
SLIDE_DIRECTION = 'uib-slideDirection',
currentIndex = $scope.active,
currentInterval, isPlaying, bufferedTransitions = [];
var destroyed = false;
self.addSlide = function(slide, element) {
slides.push({
slide: slide,
element: element
});
slides.sort(function(a, b) {
return +a.slide.index > +b.slide.index;
});
//if this is the first slide or the slide is set to active, select it
if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) {
if ($scope.$currentTransition) {
$scope.$currentTransition = null;
}
currentIndex = slide.index;
$scope.active = slide.index;
setActive(currentIndex);
self.select(slides[findSlideIndex(slide)]);
if (slides.length === 1) {
$scope.play();
}
}
};
self.getCurrentIndex = function() {
for (var i = 0; i < slides.length; i++) {
if (slides[i].slide.index === currentIndex) {
return i;
}
}
};
self.next = $scope.next = function() {
var newIndex = (self.getCurrentIndex() + 1) % slides.length;
if (newIndex === 0 && $scope.noWrap()) {
$scope.pause();
return;
}
return self.select(slides[newIndex], 'next');
};
self.prev = $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(slides[newIndex], 'prev');
};
self.removeSlide = function(slide) {
var index = findSlideIndex(slide);
var bufferedIndex = bufferedTransitions.indexOf(slides[index]);
if (bufferedIndex !== -1) {
bufferedTransitions.splice(bufferedIndex, 1);
}
//get the index of the slide inside the carousel
slides.splice(index, 1);
if (slides.length > 0 && currentIndex === index) {
if (index >= slides.length) {
currentIndex = slides.length - 1;
$scope.active = currentIndex;
setActive(currentIndex);
self.select(slides[slides.length - 1]);
} else {
currentIndex = index;
$scope.active = currentIndex;
setActive(currentIndex);
self.select(slides[index]);
}
} else if (currentIndex > index) {
currentIndex--;
$scope.active = currentIndex;
}
//clean the active value when no more slide
if (slides.length === 0) {
currentIndex = null;
$scope.active = null;
clearBufferedTransitions();
}
};
/* direction: "prev" or "next" */
self.select = $scope.select = function(nextSlide, direction) {
var nextIndex = findSlideIndex(nextSlide.slide);
//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.slide.index !== currentIndex &&
!$scope.$currentTransition) {
goNext(nextSlide.slide, nextIndex, direction);
} else if (nextSlide && nextSlide.slide.index !== currentIndex && $scope.$currentTransition) {
bufferedTransitions.push(slides[nextIndex]);
}
};
/* Allow outside people to call indexOf on slides array */
$scope.indexOfSlide = function(slide) {
return +slide.slide.index;
};
$scope.isActive = function(slide) {
return $scope.active === slide.slide.index;
};
$scope.pause = function() {
if (!$scope.noPause) {
isPlaying = false;
resetTimer();
}
};
$scope.play = function() {
if (!isPlaying) {
isPlaying = true;
restartTimer();
}
};
$scope.$on('$destroy', function() {
destroyed = true;
resetTimer();
});
$scope.$watch('noTransition', function(noTransition) {
$animate.enabled($element, !noTransition);
});
$scope.$watch('interval', restartTimer);
$scope.$watchCollection('slides', resetTransition);
$scope.$watch('active', function(index) {
if (angular.isNumber(index) && currentIndex !== index) {
for (var i = 0; i < slides.length; i++) {
if (slides[i].slide.index === index) {
index = i;
break;
}
}
var slide = slides[index];
if (slide) {
currentIndex = index;
setActive(index);
self.select(slides[index]);
}
}
});
function clearBufferedTransitions() {
while (bufferedTransitions.length) {
bufferedTransitions.shift();
}
}
function getSlideByIndex(index) {
for (var i = 0, l = slides.length; i < l; ++i) {
if (slides[i].index === index) {
return slides[i];
}
}
}
function setActive(index) {
for (var i = 0; i < slides.length; i++) {
slides[i].slide.active = i === index;
}
}
function goNext(slide, index, direction) {
if (destroyed) {
return;
}
angular.extend(slide, {direction: direction});
angular.extend(slides[currentIndex].slide || {}, {direction: direction});
if ($animate.enabled($element) && !$scope.$currentTransition &&
slides[index].element && self.slides.length > 1) {
slides[index].element.data(SLIDE_DIRECTION, slide.direction);
var currentIdx = self.getCurrentIndex();
if (angular.isNumber(currentIdx) && slides[currentIdx].element) {
slides[currentIdx].element.data(SLIDE_DIRECTION, slide.direction);
}
$scope.$currentTransition = true;
$animate.on('addClass', slides[index].element, function(element, phase) {
if (phase === 'close') {
$scope.$currentTransition = null;
$animate.off('addClass', element);
if (bufferedTransitions.length) {
var nextSlide = bufferedTransitions.pop().slide;
var nextIndex = nextSlide.index;
var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
clearBufferedTransitions();
goNext(nextSlide, nextIndex, nextDirection);
}
}
});
}
$scope.active = slide.index;
currentIndex = slide.index;
setActive(index);
//every time you change slides, reset the timer
restartTimer();
}
function findSlideIndex(slide) {
for (var i = 0; i < slides.length; i++) {
if (slides[i].slide === slide) {
return i;
}
}
}
function resetTimer() {
if (currentInterval) {
$interval.cancel(currentInterval);
currentInterval = null;
}
}
function resetTransition(slides) {
if (!slides.length) {
$scope.$currentTransition = null;
clearBufferedTransitions();
}
}
function restartTimer() {
resetTimer();
var interval = +$scope.interval;
if (!isNaN(interval) && interval > 0) {
currentInterval = $interval(timerFn, interval);
}
}
function timerFn() {
var interval = +$scope.interval;
if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
$scope.next();
} else {
$scope.pause();
}
}
}])
.directive('uibCarousel', function() {
return {
transclude: true,
replace: true,
controller: 'UibCarouselController',
controllerAs: 'carousel',
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/carousel/carousel.html';
},
scope: {
active: '=',
interval: '=',
noTransition: '=',
noPause: '=',
noWrap: '&'
}
};
})
.directive('uibSlide', function() {
return {
require: '^uibCarousel',
transclude: true,
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/carousel/slide.html';
},
scope: {
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);
});
}
};
})
.animation('.item', ['$animateCss',
function($animateCss) {
var SLIDE_DIRECTION = 'uib-slideDirection';
function removeClass(element, className, callback) {
element.removeClass(className);
if (callback) {
callback();
}
}
return {
beforeAddClass: function(element, className, done) {
if (className === 'active') {
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);
$animateCss(element, {addClass: directionClass})
.start()
.done(removeClassFn);
return function() {
stopped = true;
};
}
done();
},
beforeRemoveClass: function (element, className, done) {
if (className === 'active') {
var stopped = false;
var direction = element.data(SLIDE_DIRECTION);
var directionClass = direction === 'next' ? 'left' : 'right';
var removeClassFn = removeClass.bind(this, element, directionClass, done);
$animateCss(element, {addClass: directionClass})
.start()
.done(removeClassFn);
return function() {
stopped = true;
};
}
done();
}
};
}]);
angular.module('ui.bootstrap.dateparser', [])
.service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', function($log, $locale, dateFilter, orderByFilter) {
// Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
var localeId;
var formatCodeToRegex;
this.init = function() {
localeId = $locale.id;
this.parsers = {};
this.formatters = {};
formatCodeToRegex = [
{
key: 'yyyy',
regex: '\\d{4}',
apply: function(value) { this.year = +value; },
formatter: function(date) {
var _date = new Date();
_date.setFullYear(Math.abs(date.getFullYear()));
return dateFilter(_date, 'yyyy');
}
},
{
key: 'yy',
regex: '\\d{2}',
apply: function(value) { this.year = +value + 2000; },
formatter: function(date) {
var _date = new Date();
_date.setFullYear(Math.abs(date.getFullYear()));
return dateFilter(_date, 'yy');
}
},
{
key: 'y',
regex: '\\d{1,4}',
apply: function(value) { this.year = +value; },
formatter: function(date) {
var _date = new Date();
_date.setFullYear(Math.abs(date.getFullYear()));
return dateFilter(_date, 'y');
}
},
{
key: 'M!',
regex: '0?[1-9]|1[0-2]',
apply: function(value) { this.month = value - 1; },
formatter: function(date) {
var value = date.getMonth();
if (/^[0-9]$/.test(value)) {
return dateFilter(date, 'MM');
}
return dateFilter(date, 'M');
}
},
{
key: 'MMMM',
regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); },
formatter: function(date) { return dateFilter(date, 'MMMM'); }
},
{
key: 'MMM',
regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); },
formatter: function(date) { return dateFilter(date, 'MMM'); }
},
{
key: 'MM',
regex: '0[1-9]|1[0-2]',
apply: function(value) { this.month = value - 1; },
formatter: function(date) { return dateFilter(date, 'MM'); }
},
{
key: 'M',
regex: '[1-9]|1[0-2]',
apply: function(value) { this.month = value - 1; },
formatter: function(date) { return dateFilter(date, 'M'); }
},
{
key: 'd!',
regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
apply: function(value) { this.date = +value; },
formatter: function(date) {
var value = date.getDate();
if (/^[1-9]$/.test(value)) {
return dateFilter(date, 'dd');
}
return dateFilter(date, 'd');
}
},
{
key: 'dd',
regex: '[0-2][0-9]{1}|3[0-1]{1}',
apply: function(value) { this.date = +value; },
formatter: function(date) { return dateFilter(date, 'dd'); }
},
{
key: 'd',
regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
apply: function(value) { this.date = +value; },
formatter: function(date) { return dateFilter(date, 'd'); }
},
{
key: 'EEEE',
regex: $locale.DATETIME_FORMATS.DAY.join('|'),
formatter: function(date) { return dateFilter(date, 'EEEE'); }
},
{
key: 'EEE',
regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'),
formatter: function(date) { return dateFilter(date, 'EEE'); }
},
{
key: 'HH',
regex: '(?:0|1)[0-9]|2[0-3]',
apply: function(value) { this.hours = +value; },
formatter: function(date) { return dateFilter(date, 'HH'); }
},
{
key: 'hh',
regex: '0[0-9]|1[0-2]',
apply: function(value) { this.hours = +value; },
formatter: function(date) { return dateFilter(date, 'hh'); }
},
{
key: 'H',
regex: '1?[0-9]|2[0-3]',
apply: function(value) { this.hours = +value; },
formatter: function(date) { return dateFilter(date, 'H'); }
},
{
key: 'h',
regex: '[0-9]|1[0-2]',
apply: function(value) { this.hours = +value; },
formatter: function(date) { return dateFilter(date, 'h'); }
},
{
key: 'mm',
regex: '[0-5][0-9]',
apply: function(value) { this.minutes = +value; },
formatter: function(date) { return dateFilter(date, 'mm'); }
},
{
key: 'm',
regex: '[0-9]|[1-5][0-9]',
apply: function(value) { this.minutes = +value; },
formatter: function(date) { return dateFilter(date, 'm'); }
},
{
key: 'sss',
regex: '[0-9][0-9][0-9]',
apply: function(value) { this.milliseconds = +value; },
formatter: function(date) { return dateFilter(date, 'sss'); }
},
{
key: 'ss',
regex: '[0-5][0-9]',
apply: function(value) { this.seconds = +value; },
formatter: function(date) { return dateFilter(date, 'ss'); }
},
{
key: 's',
regex: '[0-9]|[1-5][0-9]',
apply: function(value) { this.seconds = +value; },
formatter: function(date) { return dateFilter(date, 's'); }
},
{
key: 'a',
regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
apply: function(value) {
if (this.hours === 12) {
this.hours = 0;
}
if (value === 'PM') {
this.hours += 12;
}
},
formatter: function(date) { return dateFilter(date, 'a'); }
},
{
key: 'Z',
regex: '[+-]\\d{4}',
apply: function(value) {
var matches = value.match(/([+-])(\d{2})(\d{2})/),
sign = matches[1],
hours = matches[2],
minutes = matches[3];
this.hours += toInt(sign + hours);
this.minutes += toInt(sign + minutes);
},
formatter: function(date) {
return dateFilter(date, 'Z');
}
},
{
key: 'ww',
regex: '[0-4][0-9]|5[0-3]',
formatter: function(date) { return dateFilter(date, 'ww'); }
},
{
key: 'w',
regex: '[0-9]|[1-4][0-9]|5[0-3]',
formatter: function(date) { return dateFilter(date, 'w'); }
},
{
key: 'GGGG',
regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'),
formatter: function(date) { return dateFilter(date, 'GGGG'); }
},
{
key: 'GGG',
regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
formatter: function(date) { return dateFilter(date, 'GGG'); }
},
{
key: 'GG',
regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
formatter: function(date) { return dateFilter(date, 'GG'); }
},
{
key: 'G',
regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
formatter: function(date) { return dateFilter(date, 'G'); }
}
];
};
this.init();
function createParser(format, func) {
var map = [], regex = format.split('');
// check for literal values
var quoteIndex = format.indexOf('\'');
if (quoteIndex > -1) {
var inLiteral = false;
format = format.split('');
for (var i = quoteIndex; i < format.length; i++) {
if (inLiteral) {
if (format[i] === '\'') {
if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote
format[i+1] = '$';
regex[i+1] = '';
} else { // end of literal
regex[i] = '';
inLiteral = false;
}
}
format[i] = '$';
} else {
if (format[i] === '\'') { // start of literal
format[i] = '$';
regex[i] = '';
inLiteral = true;
}
}
}
format = format.join('');
}
angular.forEach(formatCodeToRegex, function(data) {
var index = format.indexOf(data.key);
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 + data.key.length; i < n; i++) {
regex[i] = '';
format[i] = '$';
}
format = format.join('');
map.push({
index: index,
key: data.key,
apply: data[func],
matcher: data.regex
});
}
});
return {
regex: new RegExp('^' + regex.join('') + '$'),
map: orderByFilter(map, 'index')
};
}
this.filter = function(date, format) {
if (!angular.isDate(date) || isNaN(date) || !format) {
return '';
}
format = $locale.DATETIME_FORMATS[format] || format;
if ($locale.id !== localeId) {
this.init();
}
if (!this.formatters[format]) {
this.formatters[format] = createParser(format, 'formatter');
}
var parser = this.formatters[format],
map = parser.map;
var _format = format;
return map.reduce(function(str, mapper, i) {
var match = _format.match(new RegExp('(.*)' + mapper.key));
if (match && angular.isString(match[1])) {
str += match[1];
_format = _format.replace(match[1] + mapper.key, '');
}
var endStr = i === map.length - 1 ? _format : '';
if (mapper.apply) {
return str + mapper.apply.call(null, date) + endStr;
}
return str + endStr;
}, '');
};
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 ($locale.id !== localeId) {
this.init();
}
if (!this.parsers[format]) {
this.parsers[format] = createParser(format, 'apply');
}
var parser = this.parsers[format],
regex = parser.regex,
map = parser.map,
results = input.match(regex),
tzOffset = false;
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.matcher === 'Z') {
tzOffset = true;
}
if (mapper.apply) {
mapper.apply.call(fields, results[i]);
}
}
var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
Date.prototype.setFullYear;
var timesetter = tzOffset ? Date.prototype.setUTCHours :
Date.prototype.setHours;
if (isValid(fields.year, fields.month, fields.date)) {
if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
dt = new Date(baseDate);
datesetter.call(dt, fields.year, fields.month, fields.date);
timesetter.call(dt, fields.hours, fields.minutes,
fields.seconds, fields.milliseconds);
} else {
dt = new Date(0);
datesetter.call(dt, fields.year, fields.month, fields.date);
timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
fields.seconds || 0, 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;
}
function toInt(str) {
return parseInt(str, 10);
}
this.toTimezone = toTimezone;
this.fromTimezone = fromTimezone;
this.timezoneToOffset = timezoneToOffset;
this.addDateMinutes = addDateMinutes;
this.convertTimezoneToLocal = convertTimezoneToLocal;
function toTimezone(date, timezone) {
return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
}
function fromTimezone(date, timezone) {
return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
}
//https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207
function timezoneToOffset(timezone, fallback) {
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
}
function addDateMinutes(date, minutes) {
date = new Date(date.getTime());
date.setMinutes(date.getMinutes() + minutes);
return date;
}
function convertTimezoneToLocal(date, timezone, reverse) {
reverse = reverse ? -1 : 1;
var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
}
}]);
// Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
// at most one element.
angular.module('ui.bootstrap.isClass', [])
.directive('uibIsClass', [
'$animate',
function ($animate) {
// 11111111 22222222
var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
// 11111111 22222222
var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;
var dataPerTracked = {};
return {
restrict: 'A',
compile: function (tElement, tAttrs) {
var linkedScopes = [];
var instances = [];
var expToData = {};
var lastActivated = null;
var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);
var onExp = onExpMatches[2];
var expsStr = onExpMatches[1];
var exps = expsStr.split(',');
return linkFn;
function linkFn(scope, element, attrs) {
linkedScopes.push(scope);
instances.push({
scope: scope,
element: element
});
exps.forEach(function (exp, k) {
addForExp(exp, scope);
});
scope.$on('$destroy', removeScope);
}
function addForExp(exp, scope) {
var matches = exp.match(IS_REGEXP);
var clazz = scope.$eval(matches[1]);
var compareWithExp = matches[2];
var data = expToData[exp];
if (!data) {
var watchFn = function (compareWithVal) {
var newActivated = null;
instances.some(function (instance) {
var thisVal = instance.scope.$eval(onExp);
if (thisVal === compareWithVal) {
newActivated = instance;
return true;
}
});
if (data.lastActivated !== newActivated) {
if (data.lastActivated) {
$animate.removeClass(data.lastActivated.element, clazz);
}
if (newActivated) {
$animate.addClass(newActivated.element, clazz);
}
data.lastActivated = newActivated;
}
};
expToData[exp] = data = {
lastActivated: null,
scope: scope,
watchFn: watchFn,
compareWithExp: compareWithExp,
watcher: scope.$watch(compareWithExp, watchFn)
};
}
data.watchFn(scope.$eval(compareWithExp));
}
function removeScope(e) {
var removedScope = e.targetScope;
var index = linkedScopes.indexOf(removedScope);
linkedScopes.splice(index, 1);
instances.splice(index, 1);
if (linkedScopes.length) {
var newWatchScope = linkedScopes[0];
angular.forEach(expToData, function (data) {
if (data.scope === removedScope) {
data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
data.scope = newWatchScope;
}
});
}
else {
expToData = {};
}
}
}
};
}]);
angular.module('ui.bootstrap.position', [])
/**
* A set of utility methods for working with the DOM.
* It is meant to be used where we need to absolute-position elements in
* relation to another element (this is the case for tooltips, popovers,
* typeahead suggestions etc.).
*/
.factory('$uibPosition', ['$document', '$window', function($document, $window) {
/**
* Used by scrollbarWidth() function to cache scrollbar's width.
* Do not access this variable directly, use scrollbarWidth() instead.
*/
var SCROLLBAR_WIDTH;
var OVERFLOW_REGEX = {
normal: /(auto|scroll)/,
hidden: /(auto|scroll|hidden)/
};
var PLACEMENT_REGEX = {
auto: /\s?auto?\s?/i,
primary: /^(top|bottom|left|right)$/,
secondary: /^(top|bottom|left|right|center)$/,
vertical: /^(top|bottom)$/
};
return {
/**
* Provides a raw DOM element from a jQuery/jQLite element.
*
* @param {element} elem - The element to convert.
*
* @returns {element} A HTML element.
*/
getRawNode: function(elem) {
return elem.nodeName ? elem : elem[0] || elem;
},
/**
* Provides a parsed number for a style property. Strips
* units and casts invalid numbers to 0.
*
* @param {string} value - The style value to parse.
*
* @returns {number} A valid number.
*/
parseStyle: function(value) {
value = parseFloat(value);
return isFinite(value) ? value : 0;
},
/**
* Provides the closest positioned ancestor.
*
* @param {element} element - The element to get the offest parent for.
*
* @returns {element} The closest positioned ancestor.
*/
offsetParent: function(elem) {
elem = this.getRawNode(elem);
var offsetParent = elem.offsetParent || $document[0].documentElement;
function isStaticPositioned(el) {
return ($window.getComputedStyle(el).position || 'static') === 'static';
}
while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {
offsetParent = offsetParent.offsetParent;
}
return offsetParent || $document[0].documentElement;
},
/**
* Provides the scrollbar width, concept from TWBS measureScrollbar()
* function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
*
* @returns {number} The width of the browser scollbar.
*/
scrollbarWidth: function() {
if (angular.isUndefined(SCROLLBAR_WIDTH)) {
var scrollElem = angular.element('<div class="uib-position-scrollbar-measure"></div>');
$document.find('body').append(scrollElem);
SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;
SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;
scrollElem.remove();
}
return SCROLLBAR_WIDTH;
},
/**
* Checks to see if the element is scrollable.
*
* @param {element} elem - The element to check.
* @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
* default is false.
*
* @returns {boolean} Whether the element is scrollable.
*/
isScrollable: function(elem, includeHidden) {
elem = this.getRawNode(elem);
var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
var elemStyle = $window.getComputedStyle(elem);
return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX);
},
/**
* Provides the closest scrollable ancestor.
* A port of the jQuery UI scrollParent method:
* https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
*
* @param {element} elem - The element to find the scroll parent of.
* @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
* default is false.
*
* @returns {element} A HTML element.
*/
scrollParent: function(elem, includeHidden) {
elem = this.getRawNode(elem);
var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
var documentEl = $document[0].documentElement;
var elemStyle = $window.getComputedStyle(elem);
var excludeStatic = elemStyle.position === 'absolute';
var scrollParent = elem.parentElement || documentEl;
if (scrollParent === documentEl || elemStyle.position === 'fixed') {
return documentEl;
}
while (scrollParent.parentElement && scrollParent !== documentEl) {
var spStyle = $window.getComputedStyle(scrollParent);
if (excludeStatic && spStyle.position !== 'static') {
excludeStatic = false;
}
if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {
break;
}
scrollParent = scrollParent.parentElement;
}
return scrollParent;
},
/**
* Provides read-only equivalent of jQuery's position function:
* http://api.jquery.com/position/ - distance to closest positioned
* ancestor. Does not account for margins by default like jQuery position.
*
* @param {element} elem - The element to caclulate the position on.
* @param {boolean=} [includeMargins=false] - Should margins be accounted
* for, default is false.
*
* @returns {object} An object with the following properties:
* <ul>
* <li>**width**: the width of the element</li>
* <li>**height**: the height of the element</li>
* <li>**top**: distance to top edge of offset parent</li>
* <li>**left**: distance to left edge of offset parent</li>
* </ul>
*/
position: function(elem, includeMagins) {
elem = this.getRawNode(elem);
var elemOffset = this.offset(elem);
if (includeMagins) {
var elemStyle = $window.getComputedStyle(elem);
elemOffset.top -= this.parseStyle(elemStyle.marginTop);
elemOffset.left -= this.parseStyle(elemStyle.marginLeft);
}
var parent = this.offsetParent(elem);
var parentOffset = {top: 0, left: 0};
if (parent !== $document[0].documentElement) {
parentOffset = this.offset(parent);
parentOffset.top += parent.clientTop - parent.scrollTop;
parentOffset.left += parent.clientLeft - parent.scrollLeft;
}
return {
width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),
height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),
top: Math.round(elemOffset.top - parentOffset.top),
left: Math.round(elemOffset.left - parentOffset.left)
};
},
/**
* Provides read-only equivalent of jQuery's offset function:
* http://api.jquery.com/offset/ - distance to viewport. Does
* not account for borders, margins, or padding on the body
* element.
*
* @param {element} elem - The element to calculate the offset on.
*
* @returns {object} An object with the following properties:
* <ul>
* <li>**width**: the width of the element</li>
* <li>**height**: the height of the element</li>
* <li>**top**: distance to top edge of viewport</li>
* <li>**right**: distance to bottom edge of viewport</li>
* </ul>
*/
offset: function(elem) {
elem = this.getRawNode(elem);
var elemBCR = elem.getBoundingClientRect();
return {
width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),
height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),
top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),
left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))
};
},
/**
* Provides offset distance to the closest scrollable ancestor
* or viewport. Accounts for border and scrollbar width.
*
* Right and bottom dimensions represent the distance to the
* respective edge of the viewport element. If the element
* edge extends beyond the viewport, a negative value will be
* reported.
*
* @param {element} elem - The element to get the viewport offset for.
* @param {boolean=} [useDocument=false] - Should the viewport be the document element instead
* of the first scrollable element, default is false.
* @param {boolean=} [includePadding=true] - Should the padding on the offset parent element
* be accounted for, default is true.
*
* @returns {object} An object with the following properties:
* <ul>
* <li>**top**: distance to the top content edge of viewport element</li>
* <li>**bottom**: distance to the bottom content edge of viewport element</li>
* <li>**left**: distance to the left content edge of viewport element</li>
* <li>**right**: distance to the right content edge of viewport element</li>
* </ul>
*/
viewportOffset: function(elem, useDocument, includePadding) {
elem = this.getRawNode(elem);
includePadding = includePadding !== false ? true : false;
var elemBCR = elem.getBoundingClientRect();
var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0};
var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);
var offsetParentBCR = offsetParent.getBoundingClientRect();
offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;
offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;
if (offsetParent === $document[0].documentElement) {
offsetBCR.top += $window.pageYOffset;
offsetBCR.left += $window.pageXOffset;
}
offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;
offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;
if (includePadding) {
var offsetParentStyle = $window.getComputedStyle(offsetParent);
offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);
offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);
offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);
offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);
}
return {
top: Math.round(elemBCR.top - offsetBCR.top),
bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),
left: Math.round(elemBCR.left - offsetBCR.left),
right: Math.round(offsetBCR.right - elemBCR.right)
};
},
/**
* Provides an array of placement values parsed from a placement string.
* Along with the 'auto' indicator, supported placement strings are:
* <ul>
* <li>top: element on top, horizontally centered on host element.</li>
* <li>top-left: element on top, left edge aligned with host element left edge.</li>
* <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>
* <li>bottom: element on bottom, horizontally centered on host element.</li>
* <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>
* <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>
* <li>left: element on left, vertically centered on host element.</li>
* <li>left-top: element on left, top edge aligned with host element top edge.</li>
* <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>
* <li>right: element on right, vertically centered on host element.</li>
* <li>right-top: element on right, top edge aligned with host element top edge.</li>
* <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>
* </ul>
* A placement string with an 'auto' indicator is expected to be
* space separated from the placement, i.e: 'auto bottom-left' If
* the primary and secondary placement values do not match 'top,
* bottom, left, right' then 'top' will be the primary placement and
* 'center' will be the secondary placement. If 'auto' is passed, true
* will be returned as the 3rd value of the array.
*
* @param {string} placement - The placement string to parse.
*
* @returns {array} An array with the following values
* <ul>
* <li>**[0]**: The primary placement.</li>
* <li>**[1]**: The secondary placement.</li>
* <li>**[2]**: If auto is passed: true, else undefined.</li>
* </ul>
*/
parsePlacement: function(placement) {
var autoPlace = PLACEMENT_REGEX.auto.test(placement);
if (autoPlace) {
placement = placement.replace(PLACEMENT_REGEX.auto, '');
}
placement = placement.split('-');
placement[0] = placement[0] || 'top';
if (!PLACEMENT_REGEX.primary.test(placement[0])) {
placement[0] = 'top';
}
placement[1] = placement[1] || 'center';
if (!PLACEMENT_REGEX.secondary.test(placement[1])) {
placement[1] = 'center';
}
if (autoPlace) {
placement[2] = true;
} else {
placement[2] = false;
}
return placement;
},
/**
* Provides coordinates for an element to be positioned relative to
* another element. Passing 'auto' as part of the placement parameter
* will enable smart placement - where the element fits. i.e:
* 'auto left-top' will check to see if there is enough space to the left
* of the hostElem to fit the targetElem, if not place right (same for secondary
* top placement). Available space is calculated using the viewportOffset
* function.
*
* @param {element} hostElem - The element to position against.
* @param {element} targetElem - The element to position.
* @param {string=} [placement=top] - The placement for the targetElem,
* default is 'top'. 'center' is assumed as secondary placement for
* 'top', 'left', 'right', and 'bottom' placements. Available placements are:
* <ul>
* <li>top</li>
* <li>top-right</li>
* <li>top-left</li>
* <li>bottom</li>
* <li>bottom-left</li>
* <li>bottom-right</li>
* <li>left</li>
* <li>left-top</li>
* <li>left-bottom</li>
* <li>right</li>
* <li>right-top</li>
* <li>right-bottom</li>
* </ul>
* @param {boolean=} [appendToBody=false] - Should the top and left values returned
* be calculated from the body element, default is false.
*
* @returns {object} An object with the following properties:
* <ul>
* <li>**top**: Value for targetElem top.</li>
* <li>**left**: Value for targetElem left.</li>
* <li>**placement**: The resolved placement.</li>
* </ul>
*/
positionElements: function(hostElem, targetElem, placement, appendToBody) {
hostElem = this.getRawNode(hostElem);
targetElem = this.getRawNode(targetElem);
// need to read from prop to support tests.
var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');
var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');
placement = this.parsePlacement(placement);
var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);
var targetElemPos = {top: 0, left: 0, placement: ''};
if (placement[2]) {
var viewportOffset = this.viewportOffset(hostElem);
var targetElemStyle = $window.getComputedStyle(targetElem);
var adjustedSize = {
width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),
height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))
};
placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :
placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :
placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :
placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :
placement[0];
placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :
placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :
placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :
placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :
placement[1];
if (placement[1] === 'center') {
if (PLACEMENT_REGEX.vertical.test(placement[0])) {
var xOverflow = hostElemPos.width / 2 - targetWidth / 2;
if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {
placement[1] = 'left';
} else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {
placement[1] = 'right';
}
} else {
var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;
if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {
placement[1] = 'top';
} else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {
placement[1] = 'bottom';
}
}
}
}
switch (placement[0]) {
case 'top':
targetElemPos.top = hostElemPos.top - targetHeight;
break;
case 'bottom':
targetElemPos.top = hostElemPos.top + hostElemPos.height;
break;
case 'left':
targetElemPos.left = hostElemPos.left - targetWidth;
break;
case 'right':
targetElemPos.left = hostElemPos.left + hostElemPos.width;
break;
}
switch (placement[1]) {
case 'top':
targetElemPos.top = hostElemPos.top;
break;
case 'bottom':
targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;
break;
case 'left':
targetElemPos.left = hostElemPos.left;
break;
case 'right':
targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;
break;
case 'center':
if (PLACEMENT_REGEX.vertical.test(placement[0])) {
targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;
} else {
targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;
}
break;
}
targetElemPos.top = Math.round(targetElemPos.top);
targetElemPos.left = Math.round(targetElemPos.left);
targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];
return targetElemPos;
},
/**
* Provides a way for positioning tooltip & dropdown
* arrows when using placement options beyond the standard
* left, right, top, or bottom.
*
* @param {element} elem - The tooltip/dropdown element.
* @param {string} placement - The placement for the elem.
*/
positionArrow: function(elem, placement) {
elem = this.getRawNode(elem);
var innerElem = elem.querySelector('.tooltip-inner, .popover-inner');
if (!innerElem) {
return;
}
var isTooltip = angular.element(innerElem).hasClass('tooltip-inner');
var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');
if (!arrowElem) {
return;
}
var arrowCss = {
top: '',
bottom: '',
left: '',
right: ''
};
placement = this.parsePlacement(placement);
if (placement[1] === 'center') {
// no adjustment necessary - just reset styles
angular.element(arrowElem).css(arrowCss);
return;
}
var borderProp = 'border-' + placement[0] + '-width';
var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];
var borderRadiusProp = 'border-';
if (PLACEMENT_REGEX.vertical.test(placement[0])) {
borderRadiusProp += placement[0] + '-' + placement[1];
} else {
borderRadiusProp += placement[1] + '-' + placement[0];
}
borderRadiusProp += '-radius';
var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];
switch (placement[0]) {
case 'top':
arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;
break;
case 'bottom':
arrowCss.top = isTooltip ? '0' : '-' + borderWidth;
break;
case 'left':
arrowCss.right = isTooltip ? '0' : '-' + borderWidth;
break;
case 'right':
arrowCss.left = isTooltip ? '0' : '-' + borderWidth;
break;
}
arrowCss[placement[1]] = borderRadius;
angular.element(arrowElem).css(arrowCss);
}
};
}]);
angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass', 'ui.bootstrap.position'])
.value('$datepickerSuppressError', false)
.value('uibDatepickerAttributeWarning', true)
.constant('uibDatepickerConfig', {
datepickerMode: 'day',
formatDay: 'dd',
formatMonth: 'MMMM',
formatYear: 'yyyy',
formatDayHeader: 'EEE',
formatDayTitle: 'MMMM yyyy',
formatMonthTitle: 'yyyy',
maxDate: null,
maxMode: 'year',
minDate: null,
minMode: 'day',
ngModelOptions: {},
shortcutPropagation: false,
showWeeks: true,
yearColumns: 5,
yearRows: 4
})
.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', 'uibDatepickerAttributeWarning', 'uibDateParser',
function($scope, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerSuppressError, datepickerAttributeWarning, dateParser) {
var self = this,
ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
ngModelOptions = {},
watchListeners = [],
optionsUsed = !!$attrs.datepickerOptions;
// Modes chain
this.modes = ['day', 'month', 'year'];
if (optionsUsed) {
[
'customClass',
'dateDisabled',
'datepickerMode',
'formatDay',
'formatDayHeader',
'formatDayTitle',
'formatMonth',
'formatMonthTitle',
'formatYear',
'initDate',
'maxDate',
'maxMode',
'minDate',
'minMode',
'showWeeks',
'shortcutPropagation',
'startingDay',
'yearColumns',
'yearRows'
].forEach(function(key) {
switch (key) {
case 'customClass':
case 'dateDisabled':
$scope[key] = $scope.datepickerOptions[key] || angular.noop;
break;
case 'datepickerMode':
$scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ?
$scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode;
break;
case 'formatDay':
case 'formatDayHeader':
case 'formatDayTitle':
case 'formatMonth':
case 'formatMonthTitle':
case 'formatYear':
self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
$interpolate($scope.datepickerOptions[key])($scope.$parent) :
datepickerConfig[key];
break;
case 'showWeeks':
case 'shortcutPropagation':
case 'yearColumns':
case 'yearRows':
self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
$scope.datepickerOptions[key] : datepickerConfig[key];
break;
case 'startingDay':
if (angular.isDefined($scope.datepickerOptions.startingDay)) {
self.startingDay = $scope.datepickerOptions.startingDay;
} else if (angular.isNumber(datepickerConfig.startingDay)) {
self.startingDay = datepickerConfig.startingDay;
} else {
self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7;
}
break;
case 'maxDate':
case 'minDate':
if ($scope.datepickerOptions[key]) {
$scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) {
if (value) {
if (angular.isDate(value)) {
self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
} else {
self[key] = new Date(dateFilter(value, 'medium'));
}
} else {
self[key] = null;
}
self.refreshView();
});
} else {
self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null;
}
break;
case 'maxMode':
case 'minMode':
if ($scope.datepickerOptions[key]) {
$scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) {
self[key] = $scope[key] = angular.isDefined(value) ? value : datepickerOptions[key];
if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) ||
key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) {
$scope.datepickerMode = self[key];
$scope.datepickerOptions.datepickerMode = self[key];
}
});
} else {
self[key] = $scope[key] = datepickerConfig[key] || null;
}
break;
case 'initDate':
if ($scope.datepickerOptions.initDate) {
self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date();
$scope.$watch(function() { return $scope.datepickerOptions.initDate; }, function(initDate) {
if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
self.refreshView();
}
});
} else {
self.activeDate = new Date();
}
}
});
} else {
// Interpolated configuration attributes
angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle'], function(key) {
self[key] = angular.isDefined($attrs[key]) ? $interpolate($attrs[key])($scope.$parent) : datepickerConfig[key];
if (angular.isDefined($attrs[key]) && datepickerAttributeWarning) {
$log.warn('uib-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead');
}
});
// Evaled configuration attributes
angular.forEach(['showWeeks', 'yearRows', 'yearColumns', 'shortcutPropagation'], function(key) {
self[key] = angular.isDefined($attrs[key]) ?
$scope.$parent.$eval($attrs[key]) : datepickerConfig[key];
if (angular.isDefined($attrs[key]) && datepickerAttributeWarning) {
$log.warn('uib-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead');
}
});
angular.forEach(['dateDisabled', 'customClass'], function(key) {
if (angular.isDefined($attrs[key]) && datepickerAttributeWarning) {
$log.warn('uib-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead');
}
});
if (angular.isDefined($attrs.startingDay)) {
if (datepickerAttributeWarning) {
$log.warn('uib-datepicker startingDay attribute usage is deprecated, use datepicker-options attribute instead');
}
self.startingDay = $scope.$parent.$eval($attrs.startingDay);
} else if (angular.isNumber(datepickerConfig.startingDay)) {
self.startingDay = datepickerConfig.startingDay;
} else {
self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7;
}
// Watchable date attributes
angular.forEach(['minDate', 'maxDate'], function(key) {
if ($attrs[key]) {
if (datepickerAttributeWarning) {
$log.warn('uib-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead');
}
watchListeners.push($scope.$parent.$watch($attrs[key], function(value) {
if (value) {
if (angular.isDate(value)) {
self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
} else {
self[key] = new Date(dateFilter(value, 'medium'));
}
} else {
self[key] = null;
}
self.refreshView();
}));
} else {
self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null;
}
});
angular.forEach(['minMode', 'maxMode'], function(key) {
if ($attrs[key]) {
if (datepickerAttributeWarning) {
$log.warn('uib-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead');
}
watchListeners.push($scope.$parent.$watch($attrs[key], function(value) {
self[key] = $scope[key] = angular.isDefined(value) ? value : $attrs[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] = $scope[key] = datepickerConfig[key] || null;
}
});
if (angular.isDefined($attrs.initDate)) {
if (datepickerAttributeWarning) {
$log.warn('uib-datepicker initDate attribute usage is deprecated, use datepicker-options attribute instead');
}
var initDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone);
this.activeDate = !isNaN(initDate) ? initDate : new Date();
watchListeners.push($scope.$parent.$watch($attrs.initDate, function(initDate) {
if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
initDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
self.activeDate = !isNaN(initDate) ? initDate : new Date();
self.refreshView();
}
}));
} else {
this.activeDate = new Date();
}
if ($attrs.datepickerMode && datepickerAttributeWarning) {
$log.warn('uib-datepicker datepickerMode attribute usage is deprecated, use datepicker-options attribute instead');
}
$scope.datepickerMode = $scope.datepickerMode ||
datepickerConfig.datepickerMode;
}
$scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
$scope.disabled = angular.isDefined($attrs.disabled) || false;
if (angular.isDefined($attrs.ngDisabled)) {
watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
$scope.disabled = disabled;
self.refreshView();
}));
}
$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_;
ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions;
this.activeDate = ngModelCtrl.$modelValue || new Date();
ngModelCtrl.$render = function() {
self.render();
};
};
this.render = function() {
if (ngModelCtrl.$viewValue) {
var date = new Date(ngModelCtrl.$viewValue),
isValid = !isNaN(date);
if (isValid) {
this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
} else if (!$datepickerSuppressError) {
$log.error('Datepicker directive: "ng-model" value must be a Date object');
}
}
this.refreshView();
};
this.refreshView = function() {
if (this.element) {
$scope.selectedDt = null;
this._refreshView();
if ($scope.activeDt) {
$scope.activeDateId = $scope.activeDt.uid;
}
var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
date = dateParser.fromTimezone(date, ngModelOptions.timezone);
ngModelCtrl.$setValidity('dateDisabled', !date ||
this.element && !this.isDisabled(date));
}
};
this.createDateObject = function(date, format) {
var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
model = dateParser.fromTimezone(model, ngModelOptions.timezone);
var dt = {
date: date,
label: dateParser.filter(date, format),
selected: model && this.compare(date, model) === 0,
disabled: this.isDisabled(date),
current: this.compare(date, new Date()) === 0,
customClass: this.customClass(date) || null
};
if (model && this.compare(date, model) === 0) {
$scope.selectedDt = dt;
}
if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
$scope.activeDt = dt;
}
return dt;
};
this.isDisabled = function(date) {
return $scope.disabled ||
this.minDate && this.compare(date, this.minDate) < 0 ||
this.maxDate && this.compare(date, this.maxDate) > 0 ||
$scope.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;
};
$scope.select = function(date) {
if ($scope.datepickerMode === self.minMode) {
var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
ngModelCtrl.$setViewValue(dt);
ngModelCtrl.$render();
} else {
self.activeDate = date;
setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]);
$scope.$emit('uib:datepicker.mode');
}
};
$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;
}
setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]);
$scope.$emit('uib:datepicker.mode');
};
// 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('uib:datepicker.focus', focusElement);
$scope.keydown = function(evt) {
var key = $scope.keys[evt.which];
if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
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);
} else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
$scope.toggleMode(key === 'up' ? 1 : -1);
} else {
self.handleKeyDown(key, evt);
self.refreshView();
}
};
$scope.$on("$destroy", function() {
//Clear all watch listeners on destroy
while (watchListeners.length) {
watchListeners.shift()();
}
});
function setMode(mode) {
$scope.datepickerMode = mode;
if (optionsUsed) {
$scope.datepickerOptions.datepickerMode = mode;
}
}
}])
.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
this.step = { months: 1 };
this.element = $element;
this.monthNames = [];
this.noOfViews= 3;
function getDaysInMonth(year, month) {
return month === 1 && year % 4 === 0 &&
(year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
}
this.init = function(ctrl) {
angular.extend(ctrl, this);
scope.showWeeks = ctrl.showWeeks;
ctrl.refreshView();
};
this.getDates = function(startDate, n) {
var dates = new Array(n), current = new Date(startDate), i = 0, date;
while (i < n) {
date = new Date(current);
dates[i++] = date;
current.setDate(current.getDate() + 1);
}
return dates;
};
this._refreshView = function() {
var year = this.activeDate.getFullYear(),
month = this.activeDate.getMonth(),
firstDayOfMonth = new Date(this.activeDate);
firstDayOfMonth.setFullYear(year, month, 1);
var difference = this.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-week calendar
var days = this.getDates(firstDate, 42);
for (var i = 0; i < 42; i ++) {
days[i] = angular.extend(this.createDateObject(days[i], this.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, this.formatDayHeader),
full: dateFilter(days[j].date, 'EEEE')
};
}
scope.title = dateFilter(this.activeDate, this.formatDayTitle);
scope.rows = this.split(days, 7);
if (scope.showWeeks) {
scope.weekNumbers = [];
var thursdayIndex = (4 + 7 - this.startingDay) % 7,
numWeeks = scope.rows.length;
for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
scope.weekNumbers.push(
getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
}
}
};
this.compare = function(date1, date2) {
var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
_date1.setFullYear(date1.getFullYear());
_date2.setFullYear(date2.getFullYear());
return _date1 - _date2;
};
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;
}
this.handleKeyDown = function(key, evt) {
var date = this.activeDate.getDate();
if (key === 'left') {
date = date - 1;
} else if (key === 'up') {
date = date - 7;
} else if (key === 'right') {
date = date + 1;
} else if (key === 'down') {
date = date + 7;
} else if (key === 'pageup' || key === 'pagedown') {
var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
this.activeDate.setMonth(month, 1);
date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
} else if (key === 'home') {
date = 1;
} else if (key === 'end') {
date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
}
this.activeDate.setDate(date);
};
}])
.controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
this.step = { years: 1 };
this.element = $element;
this.init = function(ctrl) {
angular.extend(ctrl, this);
ctrl.refreshView();
};
this._refreshView = function() {
var months = new Array(12),
year = this.activeDate.getFullYear(),
date;
for (var i = 0; i < 12; i++) {
date = new Date(this.activeDate);
date.setFullYear(year, i, 1);
months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
uid: scope.uniqueId + '-' + i
});
}
scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
scope.rows = this.split(months, 3);
};
this.compare = function(date1, date2) {
var _date1 = new Date(date1.getFullYear(), date1.getMonth());
var _date2 = new Date(date2.getFullYear(), date2.getMonth());
_date1.setFullYear(date1.getFullYear());
_date2.setFullYear(date2.getFullYear());
return _date1 - _date2;
};
this.handleKeyDown = function(key, evt) {
var date = this.activeDate.getMonth();
if (key === 'left') {
date = date - 1;
} else if (key === 'up') {
date = date - 3;
} else if (key === 'right') {
date = date + 1;
} else if (key === 'down') {
date = date + 3;
} else if (key === 'pageup' || key === 'pagedown') {
var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
this.activeDate.setFullYear(year);
} else if (key === 'home') {
date = 0;
} else if (key === 'end') {
date = 11;
}
this.activeDate.setMonth(date);
};
}])
.controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
var columns, range;
this.element = $element;
function getStartingYear(year) {
return parseInt((year - 1) / range, 10) * range + 1;
}
this.yearpickerInit = function() {
columns = this.yearColumns;
range = this.yearRows * columns;
this.step = { years: range };
};
this._refreshView = function() {
var years = new Array(range), date;
for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
date = new Date(this.activeDate);
date.setFullYear(start + i, 0, 1);
years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
uid: scope.uniqueId + '-' + i
});
}
scope.title = [years[0].label, years[range - 1].label].join(' - ');
scope.rows = this.split(years, columns);
scope.columns = columns;
};
this.compare = function(date1, date2) {
return date1.getFullYear() - date2.getFullYear();
};
this.handleKeyDown = function(key, evt) {
var date = this.activeDate.getFullYear();
if (key === 'left') {
date = date - 1;
} else if (key === 'up') {
date = date - columns;
} else if (key === 'right') {
date = date + 1;
} else if (key === 'down') {
date = date + columns;
} else if (key === 'pageup' || key === 'pagedown') {
date += (key === 'pageup' ? - 1 : 1) * range;
} else if (key === 'home') {
date = getStartingYear(this.activeDate.getFullYear());
} else if (key === 'end') {
date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
}
this.activeDate.setFullYear(date);
};
}])
.directive('uibDatepicker', function() {
return {
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';
},
scope: {
datepickerMode: '=?',
datepickerOptions: '=?',
dateDisabled: '&',
customClass: '&',
shortcutPropagation: '&?'
},
require: ['uibDatepicker', '^ngModel'],
controller: 'UibDatepickerController',
controllerAs: 'datepicker',
link: function(scope, element, attrs, ctrls) {
var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
datepickerCtrl.init(ngModelCtrl);
}
};
})
.directive('uibDaypicker', function() {
return {
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/datepicker/day.html';
},
require: ['^uibDatepicker', 'uibDaypicker'],
controller: 'UibDaypickerController',
link: function(scope, element, attrs, ctrls) {
var datepickerCtrl = ctrls[0],
daypickerCtrl = ctrls[1];
daypickerCtrl.init(datepickerCtrl);
}
};
})
.directive('uibMonthpicker', function() {
return {
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/datepicker/month.html';
},
require: ['^uibDatepicker', 'uibMonthpicker'],
controller: 'UibMonthpickerController',
link: function(scope, element, attrs, ctrls) {
var datepickerCtrl = ctrls[0],
monthpickerCtrl = ctrls[1];
monthpickerCtrl.init(datepickerCtrl);
}
};
})
.directive('uibYearpicker', function() {
return {
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/datepicker/year.html';
},
require: ['^uibDatepicker', 'uibYearpicker'],
controller: 'UibYearpickerController',
link: function(scope, element, attrs, ctrls) {
var ctrl = ctrls[0];
angular.extend(ctrl, ctrls[1]);
ctrl.yearpickerInit();
ctrl.refreshView();
}
};
})
.value('uibDatepickerPopupAttributeWarning', true)
.constant('uibDatepickerPopupConfig', {
altInputFormats: [],
appendToBody: false,
clearText: 'Clear',
closeOnDateSelection: true,
closeText: 'Done',
currentText: 'Today',
datepickerPopup: 'yyyy-MM-dd',
datepickerPopupTemplateUrl: 'uib/template/datepicker/popup.html',
datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
html5Types: {
date: 'yyyy-MM-dd',
'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
'month': 'yyyy-MM'
},
onOpenFocus: true,
showButtonBar: true,
placement: 'auto bottom-left'
})
.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', 'uibDatepickerPopupAttributeWarning',
function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, datepickerPopupAttributeWarning) {
var cache = {},
isHtml5DateInput = false;
var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl,
ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [];
$scope.watchData = {};
this.init = function(_ngModel_) {
ngModel = _ngModel_;
ngModelOptions = _ngModel_.$options || datepickerConfig.ngModelOptions;
closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?
$scope.$parent.$eval($attrs.closeOnDateSelection) :
datepickerPopupConfig.closeOnDateSelection;
appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ?
$scope.$parent.$eval($attrs.datepickerAppendToBody) :
datepickerPopupConfig.appendToBody;
onOpenFocus = angular.isDefined($attrs.onOpenFocus) ?
$scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ?
$attrs.datepickerPopupTemplateUrl :
datepickerPopupConfig.datepickerPopupTemplateUrl;
datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ?
$attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
altInputFormats = angular.isDefined($attrs.altInputFormats) ?
$scope.$parent.$eval($attrs.altInputFormats) :
datepickerPopupConfig.altInputFormats;
$scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ?
$scope.$parent.$eval($attrs.showButtonBar) :
datepickerPopupConfig.showButtonBar;
if (datepickerPopupConfig.html5Types[$attrs.type]) {
dateFormat = datepickerPopupConfig.html5Types[$attrs.type];
isHtml5DateInput = true;
} else {
dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
$attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
// Invalidate the $modelValue to ensure that formatters re-run
// FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
if (newDateFormat !== dateFormat) {
dateFormat = newDateFormat;
ngModel.$modelValue = null;
if (!dateFormat) {
throw new Error('uibDatepickerPopup must have a date format specified.');
}
}
});
}
if (!dateFormat) {
throw new Error('uibDatepickerPopup must have a date format specified.');
}
if (isHtml5DateInput && $attrs.uibDatepickerPopup) {
throw new Error('HTML5 date input types do not support custom formats.');
}
// popup element used to display calendar
popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
$scope.ngModelOptions = angular.copy(ngModelOptions);
$scope.ngModelOptions.timezone = null;
if ($scope.ngModelOptions.updateOnDefault === true) {
$scope.ngModelOptions.updateOn = $scope.ngModelOptions.updateOn ?
$scope.ngModelOptions.updateOn + ' default' : 'default';
}
popupEl.attr({
'ng-model': 'date',
'ng-model-options': 'ngModelOptions',
'ng-change': 'dateSelection(date)',
'template-url': datepickerPopupTemplateUrl
});
// datepicker element
datepickerEl = angular.element(popupEl.children()[0]);
datepickerEl.attr('template-url', datepickerTemplateUrl);
if (isHtml5DateInput) {
if ($attrs.type === 'month') {
datepickerEl.attr('datepicker-mode', '"month"');
datepickerEl.attr('min-mode', 'month');
}
}
if ($scope.datepickerOptions) {
datepickerEl.attr('datepicker-options', 'datepickerOptions');
}
angular.forEach(['minMode', 'maxMode', 'datepickerMode', 'shortcutPropagation'], function(key) {
if ($attrs[key]) {
if (datepickerPopupAttributeWarning) {
$log.warn('uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead');
}
var getAttribute = $parse($attrs[key]);
var propConfig = {
get: function() {
return getAttribute($scope.$parent);
}
};
datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
// Propagate changes from datepicker to outside
if (key === 'datepickerMode') {
var setAttribute = getAttribute.assign;
propConfig.set = function(v) {
setAttribute($scope.$parent, v);
};
}
Object.defineProperty($scope.watchData, key, propConfig);
}
});
angular.forEach(['minDate', 'maxDate', 'initDate'], function(key) {
if ($attrs[key]) {
if (datepickerPopupAttributeWarning) {
$log.warn('uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead');
}
var getAttribute = $parse($attrs[key]);
watchListeners.push($scope.$parent.$watch(getAttribute, function(value) {
if (key === 'minDate' || key === 'maxDate') {
if (value === null) {
cache[key] = null;
} else if (angular.isDate(value)) {
cache[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
} else {
cache[key] = new Date(dateFilter(value, 'medium'));
}
$scope.watchData[key] = value === null ? null : cache[key];
} else {
var date = value ? new Date(value) : new Date();
$scope.watchData[key] = dateParser.fromTimezone(date, ngModelOptions.timezone);
}
}));
datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
}
});
if ($attrs.dateDisabled) {
if (datepickerPopupAttributeWarning) {
$log.warn('uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead');
}
datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
}
angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', 'showWeeks', 'startingDay', 'yearRows', 'yearColumns'], function(key) {
if (angular.isDefined($attrs[key])) {
if (datepickerPopupAttributeWarning) {
$log.warn('uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead');
}
datepickerEl.attr(cameltoDash(key), $attrs[key]);
}
});
if ($attrs.customClass) {
if (datepickerPopupAttributeWarning) {
$log.warn('uib-datepicker settings via uib-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead');
}
datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
}
if (!isHtml5DateInput) {
// Internal API to maintain the correct ng-invalid-[key] class
ngModel.$$parserName = 'date';
ngModel.$validators.date = validator;
ngModel.$parsers.unshift(parseDate);
ngModel.$formatters.push(function(value) {
if (ngModel.$isEmpty(value)) {
$scope.date = value;
return value;
}
$scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
if (angular.isNumber($scope.date)) {
$scope.date = new Date($scope.date);
}
return dateParser.filter($scope.date, dateFormat);
});
} else {
ngModel.$formatters.push(function(value) {
$scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
return value;
});
}
// Detect changes in the view from the text box
ngModel.$viewChangeListeners.push(function() {
$scope.date = parseDateString(ngModel.$viewValue);
});
$element.on('keydown', inputKeydownBind);
$popup = $compile(popupEl)($scope);
// Prevent jQuery cache memory leak (template is now redundant after linking)
popupEl.remove();
if (appendToBody) {
$document.find('body').append($popup);
} else {
$element.after($popup);
}
$scope.$on('$destroy', function() {
if ($scope.isOpen === true) {
if (!$rootScope.$$phase) {
$scope.$apply(function() {
$scope.isOpen = false;
});
}
}
$popup.remove();
$element.off('keydown', inputKeydownBind);
$document.off('click', documentClickBind);
if (scrollParentEl) {
scrollParentEl.off('scroll', positionPopup);
}
angular.element($window).off('resize', positionPopup);
//Clear all watch listeners on destroy
while (watchListeners.length) {
watchListeners.shift()();
}
});
};
$scope.getText = function(key) {
return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
};
$scope.isDisabled = function(date) {
if (date === 'today') {
date = new Date();
}
return $scope.watchData.minDate && $scope.compare(date, cache.minDate) < 0 ||
$scope.watchData.maxDate && $scope.compare(date, cache.maxDate) > 0;
};
$scope.compare = function(date1, date2) {
return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
};
// Inner change
$scope.dateSelection = function(dt) {
if (angular.isDefined(dt)) {
$scope.date = dt;
}
var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
$element.val(date);
ngModel.$setViewValue(date);
if (closeOnDateSelection) {
$scope.isOpen = false;
$element[0].focus();
}
};
$scope.keydown = function(evt) {
if (evt.which === 27) {
evt.stopPropagation();
$scope.isOpen = false;
$element[0].focus();
}
};
$scope.select = function(date, evt) {
evt.stopPropagation();
if (date === 'today') {
var today = new Date();
if (angular.isDate($scope.date)) {
date = new Date($scope.date);
date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
} else {
date = new Date(today.setHours(0, 0, 0, 0));
}
}
$scope.dateSelection(date);
};
$scope.close = function(evt) {
evt.stopPropagation();
$scope.isOpen = false;
$element[0].focus();
};
$scope.disabled = angular.isDefined($attrs.disabled) || false;
if ($attrs.ngDisabled) {
watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) {
$scope.disabled = disabled;
}));
}
$scope.$watch('isOpen', function(value) {
if (value) {
if (!$scope.disabled) {
$timeout(function() {
positionPopup();
if (onOpenFocus) {
$scope.$broadcast('uib:datepicker.focus');
}
$document.on('click', documentClickBind);
var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
if (appendToBody || $position.parsePlacement(placement)[2]) {
scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element));
if (scrollParentEl) {
scrollParentEl.on('scroll', positionPopup);
}
} else {
scrollParentEl = null;
}
angular.element($window).on('resize', positionPopup);
}, 0, false);
} else {
$scope.isOpen = false;
}
} else {
$document.off('click', documentClickBind);
if (scrollParentEl) {
scrollParentEl.off('scroll', positionPopup);
}
angular.element($window).off('resize', positionPopup);
}
});
function cameltoDash(string) {
return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
}
function parseDateString(viewValue) {
var date = dateParser.parse(viewValue, dateFormat, $scope.date);
if (isNaN(date)) {
for (var i = 0; i < altInputFormats.length; i++) {
date = dateParser.parse(viewValue, altInputFormats[i], $scope.date);
if (!isNaN(date)) {
return date;
}
}
}
return date;
}
function parseDate(viewValue) {
if (angular.isNumber(viewValue)) {
// presumably timestamp to date object
viewValue = new Date(viewValue);
}
if (!viewValue) {
return null;
}
if (angular.isDate(viewValue) && !isNaN(viewValue)) {
return viewValue;
}
if (angular.isString(viewValue)) {
var date = parseDateString(viewValue);
if (!isNaN(date)) {
return dateParser.toTimezone(date, ngModelOptions.timezone);
}
}
return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
}
function validator(modelValue, viewValue) {
var value = modelValue || viewValue;
if (!$attrs.ngRequired && !value) {
return true;
}
if (angular.isNumber(value)) {
value = new Date(value);
}
if (!value) {
return true;
}
if (angular.isDate(value) && !isNaN(value)) {
return true;
}
if (angular.isString(value)) {
return !isNaN(parseDateString(viewValue));
}
return false;
}
function documentClickBind(event) {
if (!$scope.isOpen && $scope.disabled) {
return;
}
var popup = $popup[0];
var dpContainsTarget = $element[0].contains(event.target);
// The popup node may not be an element node
// In some browsers (IE) only element nodes have the 'contains' function
var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
$scope.$apply(function() {
$scope.isOpen = false;
});
}
}
function inputKeydownBind(evt) {
if (evt.which === 27 && $scope.isOpen) {
evt.preventDefault();
evt.stopPropagation();
$scope.$apply(function() {
$scope.isOpen = false;
});
$element[0].focus();
} else if (evt.which === 40 && !$scope.isOpen) {
evt.preventDefault();
evt.stopPropagation();
$scope.$apply(function() {
$scope.isOpen = true;
});
}
}
function positionPopup() {
if ($scope.isOpen) {
var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup'));
var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
var position = $position.positionElements($element, dpElement, placement, appendToBody);
dpElement.css({top: position.top + 'px', left: position.left + 'px'});
if (dpElement.hasClass('uib-position-measure')) {
dpElement.removeClass('uib-position-measure');
}
}
}
$scope.$on('uib:datepicker.mode', function() {
$timeout(positionPopup, 0, false);
});
}])
.directive('uibDatepickerPopup', function() {
return {
require: ['ngModel', 'uibDatepickerPopup'],
controller: 'UibDatepickerPopupController',
scope: {
datepickerOptions: '=?',
isOpen: '=?',
currentText: '@',
clearText: '@',
closeText: '@',
dateDisabled: '&',
customClass: '&'
},
link: function(scope, element, attrs, ctrls) {
var ngModel = ctrls[0],
ctrl = ctrls[1];
ctrl.init(ngModel);
}
};
})
.directive('uibDatepickerPopupWrap', function() {
return {
replace: true,
transclude: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/datepicker/popup.html';
}
};
});
angular.module('ui.bootstrap.debounce', [])
/**
* A helper, internal service that debounces a function
*/
.factory('$$debounce', ['$timeout', function($timeout) {
return function(callback, debounceTime) {
var timeoutPromise;
return function() {
var self = this;
var args = Array.prototype.slice.call(arguments);
if (timeoutPromise) {
$timeout.cancel(timeoutPromise);
}
timeoutPromise = $timeout(function() {
callback.apply(self, args);
}, debounceTime);
};
};
}]);
angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
.constant('uibDropdownConfig', {
appendToOpenClass: 'uib-dropdown-open',
openClass: 'open'
})
.service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
var openScope = null;
this.open = function(dropdownScope) {
if (!openScope) {
$document.on('click', closeDropdown);
$document.on('keydown', keybindFilter);
}
if (openScope && openScope !== dropdownScope) {
openScope.isOpen = false;
}
openScope = dropdownScope;
};
this.close = function(dropdownScope) {
if (openScope === dropdownScope) {
openScope = null;
$document.off('click', closeDropdown);
$document.off('keydown', keybindFilter);
}
};
var closeDropdown = function(evt) {
// This method may still be called during the same mouse event that
// unbound this event handler. So check openScope before proceeding.
if (!openScope) { return; }
if (evt && openScope.getAutoClose() === 'disabled') { return; }
if (evt && evt.which === 3) { return; }
var toggleElement = openScope.getToggleElement();
if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
return;
}
var dropdownElement = openScope.getDropdownElement();
if (evt && openScope.getAutoClose() === 'outsideClick' &&
dropdownElement && dropdownElement[0].contains(evt.target)) {
return;
}
openScope.isOpen = false;
if (!$rootScope.$$phase) {
openScope.$apply();
}
};
var keybindFilter = function(evt) {
if (evt.which === 27) {
openScope.focusToggleElement();
closeDropdown();
} else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) {
evt.preventDefault();
evt.stopPropagation();
openScope.focusDropdownEntry(evt.which);
}
};
}])
.controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) {
var self = this,
scope = $scope.$new(), // create a child scope so we are not polluting original one
templateScope,
appendToOpenClass = dropdownConfig.appendToOpenClass,
openClass = dropdownConfig.openClass,
getIsOpen,
setIsOpen = angular.noop,
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
appendToBody = false,
appendTo = null,
keynavEnabled = false,
selectedOption = null,
body = $document.find('body');
$element.addClass('dropdown');
this.init = function() {
if ($attrs.isOpen) {
getIsOpen = $parse($attrs.isOpen);
setIsOpen = getIsOpen.assign;
$scope.$watch(getIsOpen, function(value) {
scope.isOpen = !!value;
});
}
if (angular.isDefined($attrs.dropdownAppendTo)) {
var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
if (appendToEl) {
appendTo = angular.element(appendToEl);
}
}
appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
keynavEnabled = angular.isDefined($attrs.keyboardNav);
if (appendToBody && !appendTo) {
appendTo = body;
}
if (appendTo && self.dropdownMenu) {
appendTo.append(self.dropdownMenu);
$element.on('$destroy', function handleDestroyEvent() {
self.dropdownMenu.remove();
});
}
};
this.toggle = function(open) {
return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
};
// Allow other directives to watch status
this.isOpen = function() {
return scope.isOpen;
};
scope.getToggleElement = function() {
return self.toggleElement;
};
scope.getAutoClose = function() {
return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
};
scope.getElement = function() {
return $element;
};
scope.isKeynavEnabled = function() {
return keynavEnabled;
};
scope.focusDropdownEntry = function(keyCode) {
var elems = self.dropdownMenu ? //If append to body is used.
angular.element(self.dropdownMenu).find('a') :
$element.find('ul').eq(0).find('a');
switch (keyCode) {
case 40: {
if (!angular.isNumber(self.selectedOption)) {
self.selectedOption = 0;
} else {
self.selectedOption = self.selectedOption === elems.length - 1 ?
self.selectedOption :
self.selectedOption + 1;
}
break;
}
case 38: {
if (!angular.isNumber(self.selectedOption)) {
self.selectedOption = elems.length - 1;
} else {
self.selectedOption = self.selectedOption === 0 ?
0 : self.selectedOption - 1;
}
break;
}
}
elems[self.selectedOption].focus();
};
scope.getDropdownElement = function() {
return self.dropdownMenu;
};
scope.focusToggleElement = function() {
if (self.toggleElement) {
self.toggleElement[0].focus();
}
};
scope.$watch('isOpen', function(isOpen, wasOpen) {
if (appendTo && self.dropdownMenu) {
var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
css,
rightalign;
css = {
top: pos.top + 'px',
display: isOpen ? 'block' : 'none'
};
rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
if (!rightalign) {
css.left = pos.left + 'px';
css.right = 'auto';
} else {
css.left = 'auto';
css.right = window.innerWidth -
(pos.left + $element.prop('offsetWidth')) + 'px';
}
// Need to adjust our positioning to be relative to the appendTo container
// if it's not the body element
if (!appendToBody) {
var appendOffset = $position.offset(appendTo);
css.top = pos.top - appendOffset.top + 'px';
if (!rightalign) {
css.left = pos.left - appendOffset.left + 'px';
} else {
css.right = window.innerWidth -
(pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
}
}
self.dropdownMenu.css(css);
}
var openContainer = appendTo ? appendTo : $element;
var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass);
if (hasOpenClass === !isOpen) {
$animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
toggleInvoker($scope, { open: !!isOpen });
}
});
}
if (isOpen) {
if (self.dropdownMenuTemplateUrl) {
$templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
templateScope = scope.$new();
$compile(tplContent.trim())(templateScope, function(dropdownElement) {
var newEl = dropdownElement;
self.dropdownMenu.replaceWith(newEl);
self.dropdownMenu = newEl;
});
});
}
scope.focusToggleElement();
uibDropdownService.open(scope);
} else {
if (self.dropdownMenuTemplateUrl) {
if (templateScope) {
templateScope.$destroy();
}
var newEl = angular.element('<ul class="dropdown-menu"></ul>');
self.dropdownMenu.replaceWith(newEl);
self.dropdownMenu = newEl;
}
uibDropdownService.close(scope);
self.selectedOption = null;
}
if (angular.isFunction(setIsOpen)) {
setIsOpen($scope, isOpen);
}
});
$scope.$on('$locationChangeSuccess', function() {
if (scope.getAutoClose() !== 'disabled') {
scope.isOpen = false;
}
});
}])
.directive('uibDropdown', function() {
return {
controller: 'UibDropdownController',
link: function(scope, element, attrs, dropdownCtrl) {
dropdownCtrl.init();
}
};
})
.directive('uibDropdownMenu', function() {
return {
restrict: 'A',
require: '?^uibDropdown',
link: function(scope, element, attrs, dropdownCtrl) {
if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
return;
}
element.addClass('dropdown-menu');
var tplUrl = attrs.templateUrl;
if (tplUrl) {
dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
}
if (!dropdownCtrl.dropdownMenu) {
dropdownCtrl.dropdownMenu = element;
}
}
};
})
.directive('uibDropdownToggle', function() {
return {
require: '?^uibDropdown',
link: function(scope, element, attrs, dropdownCtrl) {
if (!dropdownCtrl) {
return;
}
element.addClass('dropdown-toggle');
dropdownCtrl.toggleElement = element;
var toggleDropdown = function(event) {
event.preventDefault();
if (!element.hasClass('disabled') && !attrs.disabled) {
scope.$apply(function() {
dropdownCtrl.toggle();
});
}
};
element.bind('click', toggleDropdown);
// WAI-ARIA
element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
element.attr('aria-expanded', !!isOpen);
});
scope.$on('$destroy', function() {
element.unbind('click', toggleDropdown);
});
}
};
});
angular.module('ui.bootstrap.stackedMap', [])
/**
* 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;
}
};
}
};
});
angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
/**
* A helper, internal data structure that stores all references attached to key
*/
.factory('$$multiMap', function() {
return {
createNew: function() {
var map = {};
return {
entries: function() {
return Object.keys(map).map(function(key) {
return {
key: key,
value: map[key]
};
});
},
get: function(key) {
return map[key];
},
hasKey: function(key) {
return !!map[key];
},
keys: function() {
return Object.keys(map);
},
put: function(key, value) {
if (!map[key]) {
map[key] = [];
}
map[key].push(value);
},
remove: function(key, value) {
var values = map[key];
if (!values) {
return;
}
var idx = values.indexOf(value);
if (idx !== -1) {
values.splice(idx, 1);
}
if (!values.length) {
delete map[key];
}
}
};
}
};
})
/**
* Pluggable resolve mechanism for the modal resolve resolution
* Supports UI Router's $resolve service
*/
.provider('$uibResolve', function() {
var resolve = this;
this.resolver = null;
this.setResolver = function(resolver) {
this.resolver = resolver;
};
this.$get = ['$injector', '$q', function($injector, $q) {
var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null;
return {
resolve: function(invocables, locals, parent, self) {
if (resolver) {
return resolver.resolve(invocables, locals, parent, self);
}
var promises = [];
angular.forEach(invocables, function(value) {
if (angular.isFunction(value) || angular.isArray(value)) {
promises.push($q.resolve($injector.invoke(value)));
} else if (angular.isString(value)) {
promises.push($q.resolve($injector.get(value)));
} else {
promises.push($q.resolve(value));
}
});
return $q.all(promises).then(function(resolves) {
var resolveObj = {};
var resolveIter = 0;
angular.forEach(invocables, function(value, key) {
resolveObj[key] = resolves[resolveIter++];
});
return resolveObj;
});
}
};
}];
})
/**
* A helper directive for the $modal service. It creates a backdrop element.
*/
.directive('uibModalBackdrop', ['$animateCss', '$injector', '$uibModalStack',
function($animateCss, $injector, $modalStack) {
return {
replace: true,
templateUrl: 'uib/template/modal/backdrop.html',
compile: function(tElement, tAttrs) {
tElement.addClass(tAttrs.backdropClass);
return linkFn;
}
};
function linkFn(scope, element, attrs) {
if (attrs.modalInClass) {
$animateCss(element, {
addClass: attrs.modalInClass
}).start();
scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
var done = setIsAsync();
if (scope.modalOptions.animation) {
$animateCss(element, {
removeClass: attrs.modalInClass
}).start().then(done);
} else {
done();
}
});
}
}
}])
.directive('uibModalWindow', ['$uibModalStack', '$q', '$animate', '$animateCss', '$document',
function($modalStack, $q, $animate, $animateCss, $document) {
return {
scope: {
index: '@'
},
replace: true,
transclude: true,
templateUrl: function(tElement, tAttrs) {
return tAttrs.templateUrl || 'uib/template/modal/window.html';
},
link: function(scope, element, attrs) {
element.addClass(attrs.windowClass || '');
element.addClass(attrs.windowTopClass || '');
scope.size = attrs.size;
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');
}
};
// moved from template to fix issue #2280
element.on('click', scope.close);
// This property is only added to the scope for the purpose of detecting when this directive is rendered.
// We can detect that by using this property in the template associated with this directive and then use
// {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
scope.$isRendered = true;
// Deferred object that will be resolved when this modal is render.
var modalRenderDeferObj = $q.defer();
// Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
// In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
attrs.$observe('modalRender', function(value) {
if (value === 'true') {
modalRenderDeferObj.resolve();
}
});
modalRenderDeferObj.promise.then(function() {
var animationPromise = null;
if (attrs.modalInClass) {
animationPromise = $animateCss(element, {
addClass: attrs.modalInClass
}).start();
scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
var done = setIsAsync();
if ($animateCss) {
$animateCss(element, {
removeClass: attrs.modalInClass
}).start().then(done);
} else {
$animate.removeClass(element, attrs.modalInClass).then(done);
}
});
}
$q.when(animationPromise).then(function() {
// Notify {@link $modalStack} that modal is rendered.
var modal = $modalStack.getTop();
if (modal) {
$modalStack.modalRendered(modal.key);
}
/**
* If something within the freshly-opened modal already has focus (perhaps via a
* directive that causes focus). then no need to try and focus anything.
*/
if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
var inputWithAutofocus = element[0].querySelector('[autofocus]');
/**
* Auto-focusing of a freshly-opened modal element causes any child elements
* with the autofocus attribute to lose focus. This is an issue on touch
* based devices which will show and then hide the onscreen keyboard.
* Attempts to refocus the autofocus element via JavaScript will not reopen
* the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
* the modal element if the modal does not contain an autofocus element.
*/
if (inputWithAutofocus) {
inputWithAutofocus.focus();
} else {
element[0].focus();
}
}
});
});
}
};
}])
.directive('uibModalAnimationClass', function() {
return {
compile: function(tElement, tAttrs) {
if (tAttrs.modalAnimation) {
tElement.addClass(tAttrs.uibModalAnimationClass);
}
}
};
})
.directive('uibModalTransclude', function() {
return {
link: function(scope, element, attrs, controller, transclude) {
transclude(scope.$parent, function(clone) {
element.empty();
element.append(clone);
});
}
};
})
.factory('$uibModalStack', ['$animate', '$animateCss', '$document',
'$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap',
function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap) {
var OPENED_MODAL_CLASS = 'modal-open';
var backdropDomEl, backdropScope;
var openedWindows = $$stackedMap.createNew();
var openedClasses = $$multiMap.createNew();
var $modalStack = {
NOW_CLOSING_EVENT: 'modal.stack.now-closing'
};
//Modal focus behavior
var focusableElementList;
var focusIndex = 0;
var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
'iframe, object, embed, *[tabindex], *[contenteditable=true]';
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, elementToReceiveFocus) {
var modalWindow = openedWindows.get(modalInstance).value;
var appendToElement = modalWindow.appendTo;
//clean up the stack
openedWindows.remove(modalInstance);
removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
openedClasses.remove(modalBodyClass, modalInstance);
appendToElement.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
toggleTopWindowClass(true);
}, modalWindow.closedDeferred);
checkRemoveBackdrop();
//move focus to specified element if available, or else to body
if (elementToReceiveFocus && elementToReceiveFocus.focus) {
elementToReceiveFocus.focus();
} else if (appendToElement.focus) {
appendToElement.focus();
}
}
// Add or remove "windowTopClass" from the top window in the stack
function toggleTopWindowClass(toggleSwitch) {
var modalWindow;
if (openedWindows.length() > 0) {
modalWindow = openedWindows.top().value;
modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
}
}
function checkRemoveBackdrop() {
//remove backdrop if no longer needed
if (backdropDomEl && backdropIndex() === -1) {
var backdropScopeRef = backdropScope;
removeAfterAnimate(backdropDomEl, backdropScope, function() {
backdropScopeRef = null;
});
backdropDomEl = undefined;
backdropScope = undefined;
}
}
function removeAfterAnimate(domEl, scope, done, closedDeferred) {
var asyncDeferred;
var asyncPromise = null;
var setIsAsync = function() {
if (!asyncDeferred) {
asyncDeferred = $q.defer();
asyncPromise = asyncDeferred.promise;
}
return function asyncDone() {
asyncDeferred.resolve();
};
};
scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
// Note that it's intentional that asyncPromise might be null.
// That's when setIsAsync has not been called during the
// NOW_CLOSING_EVENT broadcast.
return $q.when(asyncPromise).then(afterAnimating);
function afterAnimating() {
if (afterAnimating.done) {
return;
}
afterAnimating.done = true;
$animateCss(domEl, {
event: 'leave'
}).start().then(function() {
domEl.remove();
if (closedDeferred) {
closedDeferred.resolve();
}
});
scope.$destroy();
if (done) {
done();
}
}
}
$document.on('keydown', keydownListener);
$rootScope.$on('$destroy', function() {
$document.off('keydown', keydownListener);
});
function keydownListener(evt) {
if (evt.isDefaultPrevented()) {
return evt;
}
var modal = openedWindows.top();
if (modal) {
switch (evt.which) {
case 27: {
if (modal.value.keyboard) {
evt.preventDefault();
$rootScope.$apply(function() {
$modalStack.dismiss(modal.key, 'escape key press');
});
}
break;
}
case 9: {
$modalStack.loadFocusElementList(modal);
var focusChanged = false;
if (evt.shiftKey) {
if ($modalStack.isFocusInFirstItem(evt) || $modalStack.isModalFocused(evt, modal)) {
focusChanged = $modalStack.focusLastFocusableElement();
}
} else {
if ($modalStack.isFocusInLastItem(evt)) {
focusChanged = $modalStack.focusFirstFocusableElement();
}
}
if (focusChanged) {
evt.preventDefault();
evt.stopPropagation();
}
break;
}
}
}
}
$modalStack.open = function(modalInstance, modal) {
var modalOpener = $document[0].activeElement,
modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
toggleTopWindowClass(false);
openedWindows.add(modalInstance, {
deferred: modal.deferred,
renderDeferred: modal.renderDeferred,
closedDeferred: modal.closedDeferred,
modalScope: modal.scope,
backdrop: modal.backdrop,
keyboard: modal.keyboard,
openedClass: modal.openedClass,
windowTopClass: modal.windowTopClass,
animation: modal.animation,
appendTo: modal.appendTo
});
openedClasses.put(modalBodyClass, modalInstance);
var appendToElement = modal.appendTo,
currBackdropIndex = backdropIndex();
if (!appendToElement.length) {
throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
}
if (currBackdropIndex >= 0 && !backdropDomEl) {
backdropScope = $rootScope.$new(true);
backdropScope.modalOptions = modal;
backdropScope.index = currBackdropIndex;
backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
backdropDomEl.attr('backdrop-class', modal.backdropClass);
if (modal.animation) {
backdropDomEl.attr('modal-animation', 'true');
}
$compile(backdropDomEl)(backdropScope);
$animate.enter(backdropDomEl, appendToElement);
}
var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
angularDomEl.attr({
'template-url': modal.windowTemplateUrl,
'window-class': modal.windowClass,
'window-top-class': modal.windowTopClass,
'size': modal.size,
'index': openedWindows.length() - 1,
'animate': 'animate'
}).html(modal.content);
if (modal.animation) {
angularDomEl.attr('modal-animation', 'true');
}
$animate.enter($compile(angularDomEl)(modal.scope), appendToElement)
.then(function() {
if (!modal.scope.$$uibDestructionScheduled) {
$animate.addClass(appendToElement, modalBodyClass);
}
});
openedWindows.top().value.modalDomEl = angularDomEl;
openedWindows.top().value.modalOpener = modalOpener;
$modalStack.clearFocusListCache();
};
function broadcastClosing(modalWindow, resultOrReason, closing) {
return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
}
$modalStack.close = function(modalInstance, result) {
var modalWindow = openedWindows.get(modalInstance);
if (modalWindow && broadcastClosing(modalWindow, result, true)) {
modalWindow.value.modalScope.$$uibDestructionScheduled = true;
modalWindow.value.deferred.resolve(result);
removeModalWindow(modalInstance, modalWindow.value.modalOpener);
return true;
}
return !modalWindow;
};
$modalStack.dismiss = function(modalInstance, reason) {
var modalWindow = openedWindows.get(modalInstance);
if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
modalWindow.value.modalScope.$$uibDestructionScheduled = true;
modalWindow.value.deferred.reject(reason);
removeModalWindow(modalInstance, modalWindow.value.modalOpener);
return true;
}
return !modalWindow;
};
$modalStack.dismissAll = function(reason) {
var topModal = this.getTop();
while (topModal && this.dismiss(topModal.key, reason)) {
topModal = this.getTop();
}
};
$modalStack.getTop = function() {
return openedWindows.top();
};
$modalStack.modalRendered = function(modalInstance) {
var modalWindow = openedWindows.get(modalInstance);
if (modalWindow) {
modalWindow.value.renderDeferred.resolve();
}
};
$modalStack.focusFirstFocusableElement = function() {
if (focusableElementList.length > 0) {
focusableElementList[0].focus();
return true;
}
return false;
};
$modalStack.focusLastFocusableElement = function() {
if (focusableElementList.length > 0) {
focusableElementList[focusableElementList.length - 1].focus();
return true;
}
return false;
};
$modalStack.isModalFocused = function(evt, modalWindow) {
if (evt && modalWindow) {
var modalDomEl = modalWindow.value.modalDomEl;
if (modalDomEl && modalDomEl.length) {
return (evt.target || evt.srcElement) === modalDomEl[0];
}
}
return false;
};
$modalStack.isFocusInFirstItem = function(evt) {
if (focusableElementList.length > 0) {
return (evt.target || evt.srcElement) === focusableElementList[0];
}
return false;
};
$modalStack.isFocusInLastItem = function(evt) {
if (focusableElementList.length > 0) {
return (evt.target || evt.srcElement) === focusableElementList[focusableElementList.length - 1];
}
return false;
};
$modalStack.clearFocusListCache = function() {
focusableElementList = [];
focusIndex = 0;
};
$modalStack.loadFocusElementList = function(modalWindow) {
if (focusableElementList === undefined || !focusableElementList.length) {
if (modalWindow) {
var modalDomE1 = modalWindow.value.modalDomEl;
if (modalDomE1 && modalDomE1.length) {
focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
}
}
}
};
return $modalStack;
}])
.provider('$uibModal', function() {
var $modalProvider = {
options: {
animation: true,
backdrop: true, //can also be false or 'static'
keyboard: true
},
$get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack',
function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) {
var $modal = {};
function getTemplatePromise(options) {
return options.template ? $q.when(options.template) :
$templateRequest(angular.isFunction(options.templateUrl) ?
options.templateUrl() : options.templateUrl);
}
var promiseChain = null;
$modal.getPromiseChain = function() {
return promiseChain;
};
$modal.open = function(modalOptions) {
var modalResultDeferred = $q.defer();
var modalOpenedDeferred = $q.defer();
var modalClosedDeferred = $q.defer();
var modalRenderDeferred = $q.defer();
//prepare an instance of a modal to be injected into controllers and returned to a caller
var modalInstance = {
result: modalResultDeferred.promise,
opened: modalOpenedDeferred.promise,
closed: modalClosedDeferred.promise,
rendered: modalRenderDeferred.promise,
close: function (result) {
return $modalStack.close(modalInstance, result);
},
dismiss: function (reason) {
return $modalStack.dismiss(modalInstance, reason);
}
};
//merge and clean up options
modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
modalOptions.resolve = modalOptions.resolve || {};
modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
//verify options
if (!modalOptions.template && !modalOptions.templateUrl) {
throw new Error('One of template or templateUrl options is required.');
}
var templateAndResolvePromise =
$q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
function resolveWithTemplate() {
return templateAndResolvePromise;
}
// Wait for the resolution of the existing promise chain.
// Then switch to our own combined promise dependency (regardless of how the previous modal fared).
// Then add to $modalStack and resolve opened.
// Finally clean up the chain variable if no subsequent modal has overwritten it.
var samePromise;
samePromise = promiseChain = $q.all([promiseChain])
.then(resolveWithTemplate, resolveWithTemplate)
.then(function resolveSuccess(tplAndVars) {
var providedScope = modalOptions.scope || $rootScope;
var modalScope = providedScope.$new();
modalScope.$close = modalInstance.close;
modalScope.$dismiss = modalInstance.dismiss;
modalScope.$on('$destroy', function() {
if (!modalScope.$$uibDestructionScheduled) {
modalScope.$dismiss('$uibUnscheduledDestruction');
}
});
var ctrlInstance, ctrlLocals = {};
//controllers
if (modalOptions.controller) {
ctrlLocals.$scope = modalScope;
ctrlLocals.$uibModalInstance = modalInstance;
angular.forEach(tplAndVars[1], function(value, key) {
ctrlLocals[key] = value;
});
ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
if (modalOptions.controllerAs) {
if (modalOptions.bindToController) {
ctrlInstance.$close = modalScope.$close;
ctrlInstance.$dismiss = modalScope.$dismiss;
angular.extend(ctrlInstance, providedScope);
if (angular.isFunction(ctrlInstance.$onInit)) {
ctrlInstance.$onInit();
}
}
modalScope[modalOptions.controllerAs] = ctrlInstance;
}
}
$modalStack.open(modalInstance, {
scope: modalScope,
deferred: modalResultDeferred,
renderDeferred: modalRenderDeferred,
closedDeferred: modalClosedDeferred,
content: tplAndVars[0],
animation: modalOptions.animation,
backdrop: modalOptions.backdrop,
keyboard: modalOptions.keyboard,
backdropClass: modalOptions.backdropClass,
windowTopClass: modalOptions.windowTopClass,
windowClass: modalOptions.windowClass,
windowTemplateUrl: modalOptions.windowTemplateUrl,
size: modalOptions.size,
openedClass: modalOptions.openedClass,
appendTo: modalOptions.appendTo
});
modalOpenedDeferred.resolve(true);
}, function resolveError(reason) {
modalOpenedDeferred.reject(reason);
modalResultDeferred.reject(reason);
})['finally'](function() {
if (promiseChain === samePromise) {
promiseChain = null;
}
});
return modalInstance;
};
return $modal;
}
]
};
return $modalProvider;
});
angular.module('ui.bootstrap.paging', [])
/**
* Helper internal service for generating common controller code between the
* pager and pagination components
*/
.factory('uibPaging', ['$parse', function($parse) {
return {
create: function(ctrl, $scope, $attrs) {
ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl
ctrl._watchers = [];
ctrl.init = function(ngModelCtrl, config) {
ctrl.ngModelCtrl = ngModelCtrl;
ctrl.config = config;
ngModelCtrl.$render = function() {
ctrl.render();
};
if ($attrs.itemsPerPage) {
ctrl._watchers.push($scope.$parent.$watch($attrs.itemsPerPage, function(value) {
ctrl.itemsPerPage = parseInt(value, 10);
$scope.totalPages = ctrl.calculateTotalPages();
ctrl.updatePage();
}));
} else {
ctrl.itemsPerPage = config.itemsPerPage;
}
$scope.$watch('totalItems', function(newTotal, oldTotal) {
if (angular.isDefined(newTotal) || newTotal !== oldTotal) {
$scope.totalPages = ctrl.calculateTotalPages();
ctrl.updatePage();
}
});
};
ctrl.calculateTotalPages = function() {
var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage);
return Math.max(totalPages || 0, 1);
};
ctrl.render = function() {
$scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1;
};
$scope.selectPage = function(page, evt) {
if (evt) {
evt.preventDefault();
}
var clickAllowed = !$scope.ngDisabled || !evt;
if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
if (evt && evt.target) {
evt.target.blur();
}
ctrl.ngModelCtrl.$setViewValue(page);
ctrl.ngModelCtrl.$render();
}
};
$scope.getText = function(key) {
return $scope[key + 'Text'] || ctrl.config[key + 'Text'];
};
$scope.noPrevious = function() {
return $scope.page === 1;
};
$scope.noNext = function() {
return $scope.page === $scope.totalPages;
};
ctrl.updatePage = function() {
ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable
if ($scope.page > $scope.totalPages) {
$scope.selectPage($scope.totalPages);
} else {
ctrl.ngModelCtrl.$render();
}
};
$scope.$on('$destroy', function() {
while (ctrl._watchers.length) {
ctrl._watchers.shift()();
}
});
}
};
}]);
angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging'])
.controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {
$scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;
uibPaging.create(this, $scope, $attrs);
}])
.constant('uibPagerConfig', {
itemsPerPage: 10,
previousText: '« Previous',
nextText: 'Next »',
align: true
})
.directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) {
return {
scope: {
totalItems: '=',
previousText: '@',
nextText: '@',
ngDisabled: '='
},
require: ['uibPager', '?ngModel'],
controller: 'UibPagerController',
controllerAs: 'pager',
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/pager/pager.html';
},
replace: true,
link: function(scope, element, attrs, ctrls) {
var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
if (!ngModelCtrl) {
return; // do nothing if no ng-model
}
paginationCtrl.init(ngModelCtrl, uibPagerConfig);
}
};
}]);
angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
.controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
var ctrl = this;
// Setup configuration parameters
var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize,
rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate,
forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses,
boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers,
pageLabel = angular.isDefined($attrs.pageLabel) ? function(idx) { return $scope.$parent.$eval($attrs.pageLabel, {$page: idx}); } : angular.identity;
$scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
$scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
uibPaging.create(this, $scope, $attrs);
if ($attrs.maxSize) {
ctrl._watchers.push($scope.$parent.$watch($parse($attrs.maxSize), function(value) {
maxSize = parseInt(value, 10);
ctrl.render();
}));
}
// Create page object used in template
function makePage(number, text, isActive) {
return {
number: number,
text: text,
active: isActive
};
}
function getPages(currentPage, totalPages) {
var pages = [];
// Default page limits
var startPage = 1, endPage = totalPages;
var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
// recompute if maxSize
if (isMaxSized) {
if (rotate) {
// Current page is displayed in the middle of the visible ones
startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);
endPage = startPage + maxSize - 1;
// Adjust if limit is exceeded
if (endPage > totalPages) {
endPage = totalPages;
startPage = endPage - maxSize + 1;
}
} else {
// Visible pages are paginated with maxSize
startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1;
// Adjust last page if limit is exceeded
endPage = Math.min(startPage + maxSize - 1, totalPages);
}
}
// Add page number links
for (var number = startPage; number <= endPage; number++) {
var page = makePage(number, pageLabel(number), number === currentPage);
pages.push(page);
}
// Add links to move between page sets
if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) {
if (startPage > 1) {
if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning
var previousPageSet = makePage(startPage - 1, '...', false);
pages.unshift(previousPageSet);
}
if (boundaryLinkNumbers) {
if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential
var secondPageLink = makePage(2, '2', false);
pages.unshift(secondPageLink);
}
//add the first page
var firstPageLink = makePage(1, '1', false);
pages.unshift(firstPageLink);
}
}
if (endPage < totalPages) {
if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end
var nextPageSet = makePage(endPage + 1, '...', false);
pages.push(nextPageSet);
}
if (boundaryLinkNumbers) {
if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential
var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false);
pages.push(secondToLastPageLink);
}
//add the last page
var lastPageLink = makePage(totalPages, totalPages, false);
pages.push(lastPageLink);
}
}
}
return pages;
}
var originalRender = this.render;
this.render = function() {
originalRender();
if ($scope.page > 0 && $scope.page <= $scope.totalPages) {
$scope.pages = getPages($scope.page, $scope.totalPages);
}
};
}])
.constant('uibPaginationConfig', {
itemsPerPage: 10,
boundaryLinks: false,
boundaryLinkNumbers: false,
directionLinks: true,
firstText: 'First',
previousText: 'Previous',
nextText: 'Next',
lastText: 'Last',
rotate: true,
forceEllipses: false
})
.directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) {
return {
scope: {
totalItems: '=',
firstText: '@',
previousText: '@',
nextText: '@',
lastText: '@',
ngDisabled:'='
},
require: ['uibPagination', '?ngModel'],
controller: 'UibPaginationController',
controllerAs: 'pagination',
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/pagination/pagination.html';
},
replace: true,
link: function(scope, element, attrs, ctrls) {
var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
if (!ngModelCtrl) {
return; // do nothing if no ng-model
}
paginationCtrl.init(ngModelCtrl, uibPaginationConfig);
}
};
}]);
/**
* 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', 'ui.bootstrap.stackedMap'])
/**
* The $tooltip service creates tooltip- and popover-like directives as well as
* houses global options for them.
*/
.provider('$uibTooltip', function() {
// The default options tooltip and popover.
var defaultOptions = {
placement: 'top',
placementClassPrefix: '',
animation: true,
popupDelay: 0,
popupCloseDelay: 0,
useContentExp: false
};
// Default hide triggers for each show trigger
var triggerMap = {
'mouseenter': 'mouseleave',
'click': 'click',
'outsideClick': 'outsideClick',
'focus': 'blur',
'none': ''
};
// 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', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
var openedTooltips = $$stackedMap.createNew();
$document.on('keypress', keypressListener);
$rootScope.$on('$destroy', function() {
$document.off('keypress', keypressListener);
});
function keypressListener(e) {
if (e.which === 27) {
var last = openedTooltips.top();
if (last) {
last.value.close();
openedTooltips.removeTop();
last = null;
}
}
}
return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
options = angular.extend({}, defaultOptions, globalOptions, options);
/**
* 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 getTriggers(trigger) {
var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
var hide = show.map(function(trigger) {
return triggerMap[trigger] || trigger;
});
return {
show: show,
hide: hide
};
}
var directiveName = snake_case(ttType);
var startSym = $interpolate.startSymbol();
var endSym = $interpolate.endSymbol();
var template =
'<div '+ directiveName + '-popup '+
'title="' + startSym + 'title' + endSym + '" '+
(options.useContentExp ?
'content-exp="contentExp()" ' :
'content="' + startSym + 'content' + endSym + '" ') +
'placement="' + startSym + 'placement' + endSym + '" '+
'popup-class="' + startSym + 'popupClass' + endSym + '" '+
'animation="animation" ' +
'is-open="isOpen"' +
'origin-scope="origScope" ' +
'class="uib-position-measure"' +
'>' +
'</div>';
return {
compile: function(tElem, tAttrs) {
var tooltipLinker = $compile(template);
return function link(scope, element, attrs, tooltipCtrl) {
var tooltip;
var tooltipLinkedScope;
var transitionTimeout;
var showTimeout;
var hideTimeout;
var positionTimeout;
var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
var triggers = getTriggers(undefined);
var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
var ttScope = scope.$new(true);
var repositionScheduled = false;
var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
var observers = [];
var lastPlacement;
var positionTooltip = function() {
// check if tooltip exists and is not empty
if (!tooltip || !tooltip.html()) { return; }
if (!positionTimeout) {
positionTimeout = $timeout(function() {
var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' });
if (!tooltip.hasClass(ttPosition.placement.split('-')[0])) {
tooltip.removeClass(lastPlacement.split('-')[0]);
tooltip.addClass(ttPosition.placement.split('-')[0]);
}
if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) {
tooltip.removeClass(options.placementClassPrefix + lastPlacement);
tooltip.addClass(options.placementClassPrefix + ttPosition.placement);
}
// first time through tt element will have the
// uib-position-measure class or if the placement
// has changed we need to position the arrow.
if (tooltip.hasClass('uib-position-measure')) {
$position.positionArrow(tooltip, ttPosition.placement);
tooltip.removeClass('uib-position-measure');
} else if (lastPlacement !== ttPosition.placement) {
$position.positionArrow(tooltip, ttPosition.placement);
}
lastPlacement = ttPosition.placement;
positionTimeout = null;
}, 0, false);
}
};
// Set up the correct scope to allow transclusion later
ttScope.origScope = scope;
// By default, the tooltip is not open.
// TODO add ability to start tooltip opened
ttScope.isOpen = false;
openedTooltips.add(ttScope, {
close: hide
});
function toggleTooltipBind() {
if (!ttScope.isOpen) {
showTooltipBind();
} else {
hideTooltipBind();
}
}
// Show the tooltip with delay if specified, otherwise show it immediately
function showTooltipBind() {
if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
return;
}
cancelHide();
prepareTooltip();
if (ttScope.popupDelay) {
// Do nothing if the tooltip was already scheduled to pop-up.
// This happens if show is triggered multiple times before any hide is triggered.
if (!showTimeout) {
showTimeout = $timeout(show, ttScope.popupDelay, false);
}
} else {
show();
}
}
function hideTooltipBind() {
cancelShow();
if (ttScope.popupCloseDelay) {
if (!hideTimeout) {
hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
}
} else {
hide();
}
}
// Show the tooltip popup element.
function show() {
cancelShow();
cancelHide();
// Don't show empty tooltips.
if (!ttScope.content) {
return angular.noop;
}
createTooltip();
// And show the tooltip.
ttScope.$evalAsync(function() {
ttScope.isOpen = true;
assignIsOpen(true);
positionTooltip();
});
}
function cancelShow() {
if (showTimeout) {
$timeout.cancel(showTimeout);
showTimeout = null;
}
if (positionTimeout) {
$timeout.cancel(positionTimeout);
positionTimeout = null;
}
}
// Hide the tooltip popup element.
function hide() {
if (!ttScope) {
return;
}
// First things first: we don't show it anymore.
ttScope.$evalAsync(function() {
if (ttScope) {
ttScope.isOpen = false;
assignIsOpen(false);
// 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.
// The fade transition in TWBS is 150ms.
if (ttScope.animation) {
if (!transitionTimeout) {
transitionTimeout = $timeout(removeTooltip, 150, false);
}
} else {
removeTooltip();
}
}
});
}
function cancelHide() {
if (hideTimeout) {
$timeout.cancel(hideTimeout);
hideTimeout = null;
}
if (transitionTimeout) {
$timeout.cancel(transitionTimeout);
transitionTimeout = null;
}
}
function createTooltip() {
// There can only be one tooltip element per directive shown at once.
if (tooltip) {
return;
}
tooltipLinkedScope = ttScope.$new();
tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
if (appendToBody) {
$document.find('body').append(tooltip);
} else {
element.after(tooltip);
}
});
prepObservers();
}
function removeTooltip() {
cancelShow();
cancelHide();
unregisterObservers();
if (tooltip) {
tooltip.remove();
tooltip = null;
}
if (tooltipLinkedScope) {
tooltipLinkedScope.$destroy();
tooltipLinkedScope = null;
}
}
/**
* Set the initial scope values. Once
* the tooltip is created, the observers
* will be added to keep things in sync.
*/
function prepareTooltip() {
ttScope.title = attrs[prefix + 'Title'];
if (contentParse) {
ttScope.content = contentParse(scope);
} else {
ttScope.content = attrs[ttType];
}
ttScope.popupClass = attrs[prefix + 'Class'];
ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
var placement = $position.parsePlacement(ttScope.placement);
lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0];
var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
}
function assignIsOpen(isOpen) {
if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
isOpenParse.assign(scope, isOpen);
}
}
ttScope.contentExp = function() {
return ttScope.content;
};
/**
* Observe the relevant attributes.
*/
attrs.$observe('disabled', function(val) {
if (val) {
cancelShow();
}
if (val && ttScope.isOpen) {
hide();
}
});
if (isOpenParse) {
scope.$watch(isOpenParse, function(val) {
if (ttScope && !val === ttScope.isOpen) {
toggleTooltipBind();
}
});
}
function prepObservers() {
observers.length = 0;
if (contentParse) {
observers.push(
scope.$watch(contentParse, function(val) {
ttScope.content = val;
if (!val && ttScope.isOpen) {
hide();
}
})
);
observers.push(
tooltipLinkedScope.$watch(function() {
if (!repositionScheduled) {
repositionScheduled = true;
tooltipLinkedScope.$$postDigest(function() {
repositionScheduled = false;
if (ttScope && ttScope.isOpen) {
positionTooltip();
}
});
}
})
);
} else {
observers.push(
attrs.$observe(ttType, function(val) {
ttScope.content = val;
if (!val && ttScope.isOpen) {
hide();
} else {
positionTooltip();
}
})
);
}
observers.push(
attrs.$observe(prefix + 'Title', function(val) {
ttScope.title = val;
if (ttScope.isOpen) {
positionTooltip();
}
})
);
observers.push(
attrs.$observe(prefix + 'Placement', function(val) {
ttScope.placement = val ? val : options.placement;
var placement = $position.parsePlacement(ttScope.placement);
lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0];
if (ttScope.isOpen) {
positionTooltip();
}
})
);
}
function unregisterObservers() {
if (observers.length) {
angular.forEach(observers, function(observer) {
observer();
});
observers.length = 0;
}
}
// hide tooltips/popovers for outsideClick trigger
function bodyHideTooltipBind(e) {
if (!ttScope || !ttScope.isOpen || !tooltip) {
return;
}
// make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
hideTooltipBind();
}
}
var unregisterTriggers = function() {
triggers.show.forEach(function(trigger) {
if (trigger === 'outsideClick') {
element.off('click', toggleTooltipBind);
} else {
element.off(trigger, showTooltipBind);
element.off(trigger, toggleTooltipBind);
}
});
triggers.hide.forEach(function(trigger) {
if (trigger === 'outsideClick') {
$document.off('click', bodyHideTooltipBind);
} else {
element.off(trigger, hideTooltipBind);
}
});
};
function prepTriggers() {
var val = attrs[prefix + 'Trigger'];
unregisterTriggers();
triggers = getTriggers(val);
if (triggers.show !== 'none') {
triggers.show.forEach(function(trigger, idx) {
if (trigger === 'outsideClick') {
element.on('click', toggleTooltipBind);
$document.on('click', bodyHideTooltipBind);
} else if (trigger === triggers.hide[idx]) {
element.on(trigger, toggleTooltipBind);
} else if (trigger) {
element.on(trigger, showTooltipBind);
element.on(triggers.hide[idx], hideTooltipBind);
}
element.on('keypress', function(e) {
if (e.which === 27) {
hideTooltipBind();
}
});
});
}
}
prepTriggers();
var animation = scope.$eval(attrs[prefix + 'Animation']);
ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
var appendToBodyVal;
var appendKey = prefix + 'AppendToBody';
if (appendKey in attrs && attrs[appendKey] === undefined) {
appendToBodyVal = true;
} else {
appendToBodyVal = scope.$eval(attrs[appendKey]);
}
appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
// Make sure tooltip is destroyed and removed.
scope.$on('$destroy', function onDestroyTooltip() {
unregisterTriggers();
removeTooltip();
openedTooltips.remove(ttScope);
ttScope = null;
});
};
}
};
};
}];
})
// This is mostly ngInclude code but with a custom scope
.directive('uibTooltipTemplateTransclude', [
'$animate', '$sce', '$compile', '$templateRequest',
function ($animate, $sce, $compile, $templateRequest) {
return {
link: function(scope, elem, attrs) {
var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
var changeCounter = 0,
currentScope,
previousElement,
currentElement;
var cleanupLastIncludeContent = function() {
if (previousElement) {
previousElement.remove();
previousElement = null;
}
if (currentScope) {
currentScope.$destroy();
currentScope = null;
}
if (currentElement) {
$animate.leave(currentElement).then(function() {
previousElement = null;
});
previousElement = currentElement;
currentElement = null;
}
};
scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
var thisChangeId = ++changeCounter;
if (src) {
//set the 2nd param to true to ignore the template request error so that the inner
//contents and scope can be cleaned up.
$templateRequest(src, true).then(function(response) {
if (thisChangeId !== changeCounter) { return; }
var newScope = origScope.$new();
var template = response;
var clone = $compile(template)(newScope, function(clone) {
cleanupLastIncludeContent();
$animate.enter(clone, elem);
});
currentScope = newScope;
currentElement = clone;
currentScope.$emit('$includeContentLoaded', src);
}, function() {
if (thisChangeId === changeCounter) {
cleanupLastIncludeContent();
scope.$emit('$includeContentError', src);
}
});
scope.$emit('$includeContentRequested', src);
} else {
cleanupLastIncludeContent();
}
});
scope.$on('$destroy', cleanupLastIncludeContent);
}
};
}])
/**
* Note that it's intentional that these classes are *not* applied through $animate.
* They must not be animated as they're expected to be present on the tooltip on
* initialization.
*/
.directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
// need to set the primary position so the
// arrow has space during position measure.
// tooltip.positionTooltip()
if (scope.placement) {
// // There are no top-left etc... classes
// // in TWBS, so we need the primary position.
var position = $uibPosition.parsePlacement(scope.placement);
element.addClass(position[0]);
}
if (scope.popupClass) {
element.addClass(scope.popupClass);
}
if (scope.animation()) {
element.addClass(attrs.tooltipAnimationClass);
}
}
};
}])
.directive('uibTooltipPopup', function() {
return {
replace: true,
scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
templateUrl: 'uib/template/tooltip/tooltip-popup.html'
};
})
.directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
}])
.directive('uibTooltipTemplatePopup', function() {
return {
replace: true,
scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
originScope: '&' },
templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'
};
})
.directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
useContentExp: true
});
}])
.directive('uibTooltipHtmlPopup', function() {
return {
replace: true,
scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'
};
})
.directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
useContentExp: true
});
}]);
/**
* 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, and selector delegatation.
*/
angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
.directive('uibPopoverTemplatePopup', function() {
return {
replace: true,
scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
originScope: '&' },
templateUrl: 'uib/template/popover/popover-template.html'
};
})
.directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
useContentExp: true
});
}])
.directive('uibPopoverHtmlPopup', function() {
return {
replace: true,
scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
templateUrl: 'uib/template/popover/popover-html.html'
};
})
.directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
useContentExp: true
});
}])
.directive('uibPopoverPopup', function() {
return {
replace: true,
scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
templateUrl: 'uib/template/popover/popover.html'
};
})
.directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
return $uibTooltip('uibPopover', 'popover', 'click');
}]);
angular.module('ui.bootstrap.progressbar', [])
.constant('uibProgressConfig', {
animate: true,
max: 100
})
.controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {
var self = this,
animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
this.bars = [];
$scope.max = getMaxOrDefault();
this.addBar = function(bar, element, attrs) {
if (!animate) {
element.css({'transition': 'none'});
}
this.bars.push(bar);
bar.max = getMaxOrDefault();
bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
bar.$watch('value', function(value) {
bar.recalculatePercentage();
});
bar.recalculatePercentage = function() {
var totalPercentage = self.bars.reduce(function(total, bar) {
bar.percent = +(100 * bar.value / bar.max).toFixed(2);
return total + bar.percent;
}, 0);
if (totalPercentage > 100) {
bar.percent -= totalPercentage - 100;
}
};
bar.$on('$destroy', function() {
element = null;
self.removeBar(bar);
});
};
this.removeBar = function(bar) {
this.bars.splice(this.bars.indexOf(bar), 1);
this.bars.forEach(function (bar) {
bar.recalculatePercentage();
});
};
//$attrs.$observe('maxParam', function(maxParam) {
$scope.$watch('maxParam', function(maxParam) {
self.bars.forEach(function(bar) {
bar.max = getMaxOrDefault();
bar.recalculatePercentage();
});
});
function getMaxOrDefault () {
return angular.isDefined($scope.maxParam) ? $scope.maxParam : progressConfig.max;
}
}])
.directive('uibProgress', function() {
return {
replace: true,
transclude: true,
controller: 'UibProgressController',
require: 'uibProgress',
scope: {
maxParam: '=?max'
},
templateUrl: 'uib/template/progressbar/progress.html'
};
})
.directive('uibBar', function() {
return {
replace: true,
transclude: true,
require: '^uibProgress',
scope: {
value: '=',
type: '@'
},
templateUrl: 'uib/template/progressbar/bar.html',
link: function(scope, element, attrs, progressCtrl) {
progressCtrl.addBar(scope, element, attrs);
}
};
})
.directive('uibProgressbar', function() {
return {
replace: true,
transclude: true,
controller: 'UibProgressController',
scope: {
value: '=',
maxParam: '=?max',
type: '@'
},
templateUrl: 'uib/template/progressbar/progressbar.html',
link: function(scope, element, attrs, progressCtrl) {
progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
}
};
});
angular.module('ui.bootstrap.rating', [])
.constant('uibRatingConfig', {
max: 5,
stateOn: null,
stateOff: null,
titles : ['one', 'two', 'three', 'four', 'five']
})
.controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
var ngModelCtrl = { $setViewValue: angular.noop };
this.init = function(ngModelCtrl_) {
ngModelCtrl = ngModelCtrl_;
ngModelCtrl.$render = this.render;
ngModelCtrl.$formatters.push(function(value) {
if (angular.isNumber(value) && value << 0 !== value) {
value = Math.round(value);
}
return value;
});
this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ;
this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
tmpTitles : ratingConfig.titles;
var ratingStates = angular.isDefined($attrs.ratingStates) ?
$scope.$parent.$eval($attrs.ratingStates) :
new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
$scope.range = this.buildTemplateObjects(ratingStates);
};
this.buildTemplateObjects = function(states) {
for (var i = 0, n = states.length; i < n; i++) {
states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
}
return states;
};
this.getTitle = function(index) {
if (index >= this.titles.length) {
return index + 1;
}
return this.titles[index];
};
$scope.rate = function(value) {
if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue === value ? 0 : value);
ngModelCtrl.$render();
}
};
$scope.enter = function(value) {
if (!$scope.readonly) {
$scope.value = value;
}
$scope.onHover({value: value});
};
$scope.reset = function() {
$scope.value = ngModelCtrl.$viewValue;
$scope.onLeave();
};
$scope.onKeydown = function(evt) {
if (/(37|38|39|40)/.test(evt.which)) {
evt.preventDefault();
evt.stopPropagation();
$scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
}
};
this.render = function() {
$scope.value = ngModelCtrl.$viewValue;
};
}])
.directive('uibRating', function() {
return {
require: ['uibRating', 'ngModel'],
scope: {
readonly: '=?readOnly',
onHover: '&',
onLeave: '&'
},
controller: 'UibRatingController',
templateUrl: 'uib/template/rating/rating.html',
replace: true,
link: function(scope, element, attrs, ctrls) {
var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
ratingCtrl.init(ngModelCtrl);
}
};
});
angular.module('ui.bootstrap.tabs', [])
.controller('UibTabsetController', ['$scope', function ($scope) {
var ctrl = this,
oldIndex;
ctrl.tabs = [];
ctrl.select = function(index) {
if (!destroyed) {
var previousIndex = findTabIndex(oldIndex);
var previousSelected = ctrl.tabs[previousIndex];
if (previousSelected) {
previousSelected.tab.onDeselect();
previousSelected.tab.active = false;
}
var selected = ctrl.tabs[index];
if (selected) {
selected.tab.onSelect();
selected.tab.active = true;
ctrl.active = selected.index;
oldIndex = selected.index;
} else if (!selected && angular.isNumber(oldIndex)) {
ctrl.active = null;
oldIndex = null;
}
}
};
ctrl.addTab = function addTab(tab) {
ctrl.tabs.push({
tab: tab,
index: tab.index
});
ctrl.tabs.sort(function(t1, t2) {
if (t1.index > t2.index) {
return 1;
}
if (t1.index < t2.index) {
return -1;
}
return 0;
});
if (tab.index === ctrl.active || !angular.isNumber(ctrl.active) && ctrl.tabs.length === 1) {
var newActiveIndex = findTabIndex(tab.index);
ctrl.select(newActiveIndex);
}
};
ctrl.removeTab = function removeTab(tab) {
var index = findTabIndex(tab.index);
if (tab.index === ctrl.active) {
var newActiveTabIndex = index === ctrl.tabs.length - 1 ?
index - 1 : index + 1 % ctrl.tabs.length;
ctrl.select(newActiveTabIndex);
}
ctrl.tabs.splice(index, 1);
};
$scope.$watch('tabset.active', function(val) {
if (angular.isNumber(val) && val !== oldIndex) {
ctrl.select(findTabIndex(val));
}
});
var destroyed;
$scope.$on('$destroy', function() {
destroyed = true;
});
function findTabIndex(index) {
for (var i = 0; i < ctrl.tabs.length; i++) {
if (ctrl.tabs[i].index === index) {
return i;
}
}
}
}])
.directive('uibTabset', function() {
return {
transclude: true,
replace: true,
scope: {},
bindToController: {
active: '=?',
type: '@'
},
controller: 'UibTabsetController',
controllerAs: 'tabset',
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/tabs/tabset.html';
},
link: function(scope, element, attrs) {
scope.vertical = angular.isDefined(attrs.vertical) ?
scope.$parent.$eval(attrs.vertical) : false;
scope.justified = angular.isDefined(attrs.justified) ?
scope.$parent.$eval(attrs.justified) : false;
if (angular.isUndefined(attrs.active)) {
scope.active = 0;
}
}
};
})
.directive('uibTab', ['$parse', function($parse) {
return {
require: '^uibTabset',
replace: true,
templateUrl: function(element, attrs) {
return attrs.templateUrl || 'uib/template/tabs/tab.html';
},
transclude: true,
scope: {
heading: '@',
index: '=?',
classes: '@?',
onSelect: '&select', //This callback is called in contentHeadingTransclude
//once it inserts the tab's content into the dom
onDeselect: '&deselect'
},
controller: function() {
//Empty controller so other directives can require being 'under' a tab
},
controllerAs: 'tab',
link: function(scope, elm, attrs, tabsetCtrl, transclude) {
scope.disabled = false;
if (attrs.disable) {
scope.$parent.$watch($parse(attrs.disable), function(value) {
scope.disabled = !! value;
});
}
if (angular.isUndefined(attrs.index)) {
if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) {
scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1;
} else {
scope.index = 0;
}
}
if (angular.isUndefined(attrs.classes)) {
scope.classes = '';
}
scope.select = function() {
if (!scope.disabled) {
var index;
for (var i = 0; i < tabsetCtrl.tabs.length; i++) {
if (tabsetCtrl.tabs[i].tab === scope) {
index = i;
break;
}
}
tabsetCtrl.select(index);
}
};
tabsetCtrl.addTab(scope);
scope.$on('$destroy', function() {
tabsetCtrl.removeTab(scope);
});
//We need to transclude later, once the content container is ready.
//when this link happens, we're inside a tab heading.
scope.$transcludeFn = transclude;
}
};
}])
.directive('uibTabHeadingTransclude', function() {
return {
restrict: 'A',
require: '^uibTab',
link: function(scope, elm) {
scope.$watch('headingElement', function updateHeadingElement(heading) {
if (heading) {
elm.html('');
elm.append(heading);
}
});
}
};
})
.directive('uibTabContentTransclude', function() {
return {
restrict: 'A',
require: '^uibTabset',
link: function(scope, elm, attrs) {
var tab = scope.$eval(attrs.uibTabContentTransclude).tab;
//Now our tab is ready to be transcluded: both the tab heading area
//and the tab content area are loaded. Transclude 'em both.
tab.$transcludeFn(tab.$parent, function(contents) {
angular.forEach(contents, function(node) {
if (isTabHeading(node)) {
//Let tabHeadingTransclude know.
tab.headingElement = node;
} else {
elm.append(node);
}
});
});
}
};
function isTabHeading(node) {
return node.tagName && (
node.hasAttribute('uib-tab-heading') ||
node.hasAttribute('data-uib-tab-heading') ||
node.hasAttribute('x-uib-tab-heading') ||
node.tagName.toLowerCase() === 'uib-tab-heading' ||
node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
node.tagName.toLowerCase() === 'x-uib-tab-heading'
);
}
});
angular.module('ui.bootstrap.timepicker', [])
.constant('uibTimepickerConfig', {
hourStep: 1,
minuteStep: 1,
secondStep: 1,
showMeridian: true,
showSeconds: false,
meridians: null,
readonlyInput: false,
mousewheel: true,
arrowkeys: true,
showSpinners: true,
templateUrl: 'uib/template/timepicker/timepicker.html'
})
.controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
var selected = new Date(),
watchers = [],
ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
$scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
$element.removeAttr('tabindex');
this.init = function(ngModelCtrl_, inputs) {
ngModelCtrl = ngModelCtrl_;
ngModelCtrl.$render = this.render;
ngModelCtrl.$formatters.unshift(function(modelValue) {
return modelValue ? new Date(modelValue) : null;
});
var hoursInputEl = inputs.eq(0),
minutesInputEl = inputs.eq(1),
secondsInputEl = inputs.eq(2);
var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
if (mousewheel) {
this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl);
}
var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
if (arrowkeys) {
this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl);
}
$scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl);
};
var hourStep = timepickerConfig.hourStep;
if ($attrs.hourStep) {
watchers.push($scope.$parent.$watch($parse($attrs.hourStep), function(value) {
hourStep = +value;
}));
}
var minuteStep = timepickerConfig.minuteStep;
if ($attrs.minuteStep) {
watchers.push($scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
minuteStep = +value;
}));
}
var min;
watchers.push($scope.$parent.$watch($parse($attrs.min), function(value) {
var dt = new Date(value);
min = isNaN(dt) ? undefined : dt;
}));
var max;
watchers.push($scope.$parent.$watch($parse($attrs.max), function(value) {
var dt = new Date(value);
max = isNaN(dt) ? undefined : dt;
}));
var disabled = false;
if ($attrs.ngDisabled) {
watchers.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(value) {
disabled = value;
}));
}
$scope.noIncrementHours = function() {
var incrementedSelected = addMinutes(selected, hourStep * 60);
return disabled || incrementedSelected > max ||
incrementedSelected < selected && incrementedSelected < min;
};
$scope.noDecrementHours = function() {
var decrementedSelected = addMinutes(selected, -hourStep * 60);
return disabled || decrementedSelected < min ||
decrementedSelected > selected && decrementedSelected > max;
};
$scope.noIncrementMinutes = function() {
var incrementedSelected = addMinutes(selected, minuteStep);
return disabled || incrementedSelected > max ||
incrementedSelected < selected && incrementedSelected < min;
};
$scope.noDecrementMinutes = function() {
var decrementedSelected = addMinutes(selected, -minuteStep);
return disabled || decrementedSelected < min ||
decrementedSelected > selected && decrementedSelected > max;
};
$scope.noIncrementSeconds = function() {
var incrementedSelected = addSeconds(selected, secondStep);
return disabled || incrementedSelected > max ||
incrementedSelected < selected && incrementedSelected < min;
};
$scope.noDecrementSeconds = function() {
var decrementedSelected = addSeconds(selected, -secondStep);
return disabled || decrementedSelected < min ||
decrementedSelected > selected && decrementedSelected > max;
};
$scope.noToggleMeridian = function() {
if (selected.getHours() < 12) {
return disabled || addMinutes(selected, 12 * 60) > max;
}
return disabled || addMinutes(selected, -12 * 60) < min;
};
var secondStep = timepickerConfig.secondStep;
if ($attrs.secondStep) {
watchers.push($scope.$parent.$watch($parse($attrs.secondStep), function(value) {
secondStep = +value;
}));
}
$scope.showSeconds = timepickerConfig.showSeconds;
if ($attrs.showSeconds) {
watchers.push($scope.$parent.$watch($parse($attrs.showSeconds), function(value) {
$scope.showSeconds = !!value;
}));
}
// 12H / 24H mode
$scope.showMeridian = timepickerConfig.showMeridian;
if ($attrs.showMeridian) {
watchers.push($scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
$scope.showMeridian = !!value;
if (ngModelCtrl.$error.time) {
// Evaluate from template
var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
if (angular.isDefined(hours) && angular.isDefined(minutes)) {
selected.setHours(hours);
refresh();
}
} else {
updateTemplate();
}
}));
}
// Get $scope.hours in 24H mode if valid
function getHoursFromTemplate() {
var hours = +$scope.hours;
var valid = $scope.showMeridian ? hours > 0 && hours < 13 :
hours >= 0 && hours < 24;
if (!valid) {
return undefined;
}
if ($scope.showMeridian) {
if (hours === 12) {
hours = 0;
}
if ($scope.meridian === meridians[1]) {
hours = hours + 12;
}
}
return hours;
}
function getMinutesFromTemplate() {
var minutes = +$scope.minutes;
return minutes >= 0 && minutes < 60 ? minutes : undefined;
}
function getSecondsFromTemplate() {
var seconds = +$scope.seconds;
return seconds >= 0 && seconds < 60 ? seconds : undefined;
}
function pad(value) {
if (value === null) {
return '';
}
return angular.isDefined(value) && value.toString().length < 2 ?
'0' + value : value.toString();
}
// Respond on mousewheel spin
this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
var isScrollingUp = function(e) {
if (e.originalEvent) {
e = e.originalEvent;
}
//pick correct delta variable depending on event
var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY;
return e.detail || delta > 0;
};
hoursInputEl.bind('mousewheel wheel', function(e) {
if (!disabled) {
$scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
}
e.preventDefault();
});
minutesInputEl.bind('mousewheel wheel', function(e) {
if (!disabled) {
$scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
}
e.preventDefault();
});
secondsInputEl.bind('mousewheel wheel', function(e) {
if (!disabled) {
$scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds());
}
e.preventDefault();
});
};
// Respond on up/down arrowkeys
this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
hoursInputEl.bind('keydown', function(e) {
if (!disabled) {
if (e.which === 38) { // up
e.preventDefault();
$scope.incrementHours();
$scope.$apply();
} else if (e.which === 40) { // down
e.preventDefault();
$scope.decrementHours();
$scope.$apply();
}
}
});
minutesInputEl.bind('keydown', function(e) {
if (!disabled) {
if (e.which === 38) { // up
e.preventDefault();
$scope.incrementMinutes();
$scope.$apply();
} else if (e.which === 40) { // down
e.preventDefault();
$scope.decrementMinutes();
$scope.$apply();
}
}
});
secondsInputEl.bind('keydown', function(e) {
if (!disabled) {
if (e.which === 38) { // up
e.preventDefault();
$scope.incrementSeconds();
$scope.$apply();
} else if (e.which === 40) { // down
e.preventDefault();
$scope.decrementSeconds();
$scope.$apply();
}
}
});
};
this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
if ($scope.readonlyInput) {
$scope.updateHours = angular.noop;
$scope.updateMinutes = angular.noop;
$scope.updateSeconds = angular.noop;
return;
}
var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) {
ngModelCtrl.$setViewValue(null);
ngModelCtrl.$setValidity('time', false);
if (angular.isDefined(invalidHours)) {
$scope.invalidHours = invalidHours;
}
if (angular.isDefined(invalidMinutes)) {
$scope.invalidMinutes = invalidMinutes;
}
if (angular.isDefined(invalidSeconds)) {
$scope.invalidSeconds = invalidSeconds;
}
};
$scope.updateHours = function() {
var hours = getHoursFromTemplate(),
minutes = getMinutesFromTemplate();
ngModelCtrl.$setDirty();
if (angular.isDefined(hours) && angular.isDefined(minutes)) {
selected.setHours(hours);
selected.setMinutes(minutes);
if (selected < min || selected > max) {
invalidate(true);
} else {
refresh('h');
}
} else {
invalidate(true);
}
};
hoursInputEl.bind('blur', function(e) {
ngModelCtrl.$setTouched();
if ($scope.hours === null || $scope.hours === '') {
invalidate(true);
} else if (!$scope.invalidHours && $scope.hours < 10) {
$scope.$apply(function() {
$scope.hours = pad($scope.hours);
});
}
});
$scope.updateMinutes = function() {
var minutes = getMinutesFromTemplate(),
hours = getHoursFromTemplate();
ngModelCtrl.$setDirty();
if (angular.isDefined(minutes) && angular.isDefined(hours)) {
selected.setHours(hours);
selected.setMinutes(minutes);
if (selected < min || selected > max) {
invalidate(undefined, true);
} else {
refresh('m');
}
} else {
invalidate(undefined, true);
}
};
minutesInputEl.bind('blur', function(e) {
ngModelCtrl.$setTouched();
if ($scope.minutes === null) {
invalidate(undefined, true);
} else if (!$scope.invalidMinutes && $scope.minutes < 10) {
$scope.$apply(function() {
$scope.minutes = pad($scope.minutes);
});
}
});
$scope.updateSeconds = function() {
var seconds = getSecondsFromTemplate();
ngModelCtrl.$setDirty();
if (angular.isDefined(seconds)) {
selected.setSeconds(seconds);
refresh('s');
} else {
invalidate(undefined, undefined, true);
}
};
secondsInputEl.bind('blur', function(e) {
if (!$scope.invalidSeconds && $scope.seconds < 10) {
$scope.$apply( function() {
$scope.seconds = pad($scope.seconds);
});
}
});
};
this.render = function() {
var date = ngModelCtrl.$viewValue;
if (isNaN(date)) {
ngModelCtrl.$setValidity('time', false);
$log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
} else {
if (date) {
selected = date;
}
if (selected < min || selected > max) {
ngModelCtrl.$setValidity('time', false);
$scope.invalidHours = true;
$scope.invalidMinutes = true;
} else {
makeValid();
}
updateTemplate();
}
};
// Call internally when we know that model is valid.
function refresh(keyboardChange) {
makeValid();
ngModelCtrl.$setViewValue(new Date(selected));
updateTemplate(keyboardChange);
}
function makeValid() {
ngModelCtrl.$setValidity('time', true);
$scope.invalidHours = false;
$scope.invalidMinutes = false;
$scope.invalidSeconds = false;
}
function updateTemplate(keyboardChange) {
if (!ngModelCtrl.$modelValue) {
$scope.hours = null;
$scope.minutes = null;
$scope.seconds = null;
$scope.meridian = meridians[0];
} else {
var hours = selected.getHours(),
minutes = selected.getMinutes(),
seconds = selected.getSeconds();
if ($scope.showMeridian) {
hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system
}
$scope.hours = keyboardChange === 'h' ? hours : pad(hours);
if (keyboardChange !== 'm') {
$scope.minutes = pad(minutes);
}
$scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
if (keyboardChange !== 's') {
$scope.seconds = pad(seconds);
}
$scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
}
}
function addSecondsToSelected(seconds) {
selected = addSeconds(selected, seconds);
refresh();
}
function addMinutes(selected, minutes) {
return addSeconds(selected, minutes*60);
}
function addSeconds(date, seconds) {
var dt = new Date(date.getTime() + seconds * 1000);
var newDate = new Date(date);
newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds());
return newDate;
}
$scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
$scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;
$scope.incrementHours = function() {
if (!$scope.noIncrementHours()) {
addSecondsToSelected(hourStep * 60 * 60);
}
};
$scope.decrementHours = function() {
if (!$scope.noDecrementHours()) {
addSecondsToSelected(-hourStep * 60 * 60);
}
};
$scope.incrementMinutes = function() {
if (!$scope.noIncrementMinutes()) {
addSecondsToSelected(minuteStep * 60);
}
};
$scope.decrementMinutes = function() {
if (!$scope.noDecrementMinutes()) {
addSecondsToSelected(-minuteStep * 60);
}
};
$scope.incrementSeconds = function() {
if (!$scope.noIncrementSeconds()) {
addSecondsToSelected(secondStep);
}
};
$scope.decrementSeconds = function() {
if (!$scope.noDecrementSeconds()) {
addSecondsToSelected(-secondStep);
}
};
$scope.toggleMeridian = function() {
var minutes = getMinutesFromTemplate(),
hours = getHoursFromTemplate();
if (!$scope.noToggleMeridian()) {
if (angular.isDefined(minutes) && angular.isDefined(hours)) {
addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60));
} else {
$scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0];
}
}
};
$scope.blur = function() {
ngModelCtrl.$setTouched();
};
$scope.$on('$destroy', function() {
while (watchers.length) {
watchers.shift()();
}
});
}])
.directive('uibTimepicker', ['uibTimepickerConfig', function(uibTimepickerConfig) {
return {
require: ['uibTimepicker', '?^ngModel'],
controller: 'UibTimepickerController',
controllerAs: 'timepicker',
replace: true,
scope: {},
templateUrl: function(element, attrs) {
return attrs.templateUrl || uibTimepickerConfig.templateUrl;
},
link: function(scope, element, attrs, ctrls) {
var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
if (ngModelCtrl) {
timepickerCtrl.init(ngModelCtrl, element.find('input'));
}
}
};
}]);
angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])
/**
* A helper service that can parse typeahead's syntax (string provided by users)
* Extracted to a separate service for ease of unit testing
*/
.factory('uibTypeaheadParser', ['$parse', function($parse) {
// 00000111000000000000022200000000000000003333333333333330000000000044000
var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
return {
parse: function(input) {
var match = input.match(TYPEAHEAD_REGEXP);
if (!match) {
throw new Error(
'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
' but got "' + input + '".');
}
return {
itemName: match[3],
source: $parse(match[4]),
viewMapper: $parse(match[2] || match[1]),
modelMapper: $parse(match[1])
};
}
};
}])
.controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',
function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {
var HOT_KEYS = [9, 13, 27, 38, 40];
var eventDebounceTime = 200;
var modelCtrl, ngModelOptions;
//SUPPORTED ATTRIBUTES (OPTIONS)
//minimal no of characters that needs to be entered before typeahead kicks-in
var minLength = originalScope.$eval(attrs.typeaheadMinLength);
if (!minLength && minLength !== 0) {
minLength = 1;
}
originalScope.$watch(attrs.typeaheadMinLength, function (newVal) {
minLength = !newVal && newVal !== 0 ? 1 : newVal;
});
//minimal wait time after last character typed before typeahead kicks-in
var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
//should it restrict model values to the ones selected from the popup only?
var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
originalScope.$watch(attrs.typeaheadEditable, function (newVal) {
isEditable = newVal !== false;
});
//binding to a variable that indicates if matches are being retrieved asynchronously
var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
//a callback executed when a match is selected
var onSelectCallback = $parse(attrs.typeaheadOnSelect);
//should it select highlighted popup value when losing focus?
var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
//binding to a variable that indicates if there were no results after the query is completed
var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
var appendTo = attrs.typeaheadAppendTo ?
originalScope.$eval(attrs.typeaheadAppendTo) : null;
var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
//If input matches an item of the list exactly, select it automatically
var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
//binding to a variable that indicates if dropdown is open
var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;
var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;
//INTERNAL VARIABLES
//model setter executed upon match selection
var parsedModel = $parse(attrs.ngModel);
var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
var $setModelValue = function(scope, newValue) {
if (angular.isFunction(parsedModel(originalScope)) &&
ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
return invokeModelSetter(scope, {$$$p: newValue});
}
return parsedModel.assign(scope, newValue);
};
//expressions used by typeahead
var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
var hasFocus;
//Used to avoid bug in iOS webview where iOS keyboard does not fire
//mousedown & mouseup events
//Issue #3699
var selected;
//create a child scope for the typeahead directive so we are not polluting original scope
//with typeahead-specific data (matches, query etc.)
var scope = originalScope.$new();
var offDestroy = originalScope.$on('$destroy', function() {
scope.$destroy();
});
scope.$on('$destroy', offDestroy);
// WAI-ARIA
var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
element.attr({
'aria-autocomplete': 'list',
'aria-expanded': false,
'aria-owns': popupId
});
var inputsContainer, hintInputElem;
//add read-only input to show hint
if (showHint) {
inputsContainer = angular.element('<div></div>');
inputsContainer.css('position', 'relative');
element.after(inputsContainer);
hintInputElem = element.clone();
hintInputElem.attr('placeholder', '');
hintInputElem.attr('tabindex', '-1');
hintInputElem.val('');
hintInputElem.css({
'position': 'absolute',
'top': '0px',
'left': '0px',
'border-color': 'transparent',
'box-shadow': 'none',
'opacity': 1,
'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
'color': '#999'
});
element.css({
'position': 'relative',
'vertical-align': 'top',
'background-color': 'transparent'
});
inputsContainer.append(hintInputElem);
hintInputElem.after(element);
}
//pop-up element used to display matches
var popUpEl = angular.element('<div uib-typeahead-popup></div>');
popUpEl.attr({
id: popupId,
matches: 'matches',
active: 'activeIdx',
select: 'select(activeIdx, evt)',
'move-in-progress': 'moveInProgress',
query: 'query',
position: 'position',
'assign-is-open': 'assignIsOpen(isOpen)',
debounce: 'debounceUpdate'
});
//custom item template
if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
}
if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
}
var resetHint = function() {
if (showHint) {
hintInputElem.val('');
}
};
var resetMatches = function() {
scope.matches = [];
scope.activeIdx = -1;
element.attr('aria-expanded', false);
resetHint();
};
var getMatchId = function(index) {
return popupId + '-option-' + index;
};
// Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
// This attribute is added or removed automatically when the `activeIdx` changes.
scope.$watch('activeIdx', function(index) {
if (index < 0) {
element.removeAttr('aria-activedescendant');
} else {
element.attr('aria-activedescendant', getMatchId(index));
}
});
var inputIsExactMatch = function(inputValue, index) {
if (scope.matches.length > index && inputValue) {
return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
}
return false;
};
var getMatchesAsync = function(inputValue, evt) {
var locals = {$viewValue: inputValue};
isLoadingSetter(originalScope, true);
isNoResultsSetter(originalScope, false);
$q.when(parserResult.source(originalScope, locals)).then(function(matches) {
//it might happen that several async queries were in progress if a user were typing fast
//but we are interested only in responses that correspond to the current view value
var onCurrentRequest = inputValue === modelCtrl.$viewValue;
if (onCurrentRequest && hasFocus) {
if (matches && matches.length > 0) {
scope.activeIdx = focusFirst ? 0 : -1;
isNoResultsSetter(originalScope, false);
scope.matches.length = 0;
//transform labels
for (var i = 0; i < matches.length; i++) {
locals[parserResult.itemName] = matches[i];
scope.matches.push({
id: getMatchId(i),
label: parserResult.viewMapper(scope, locals),
model: matches[i]
});
}
scope.query = inputValue;
//position pop-up with matches - we need to re-calculate its position each time we are opening a window
//with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
//due to other elements being rendered
recalculatePosition();
element.attr('aria-expanded', true);
//Select the single remaining option if user input matches
if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
$$debounce(function() {
scope.select(0, evt);
}, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
} else {
scope.select(0, evt);
}
}
if (showHint) {
var firstLabel = scope.matches[0].label;
if (angular.isString(inputValue) &&
inputValue.length > 0 &&
firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
} else {
hintInputElem.val('');
}
}
} else {
resetMatches();
isNoResultsSetter(originalScope, true);
}
}
if (onCurrentRequest) {
isLoadingSetter(originalScope, false);
}
}, function() {
resetMatches();
isLoadingSetter(originalScope, false);
isNoResultsSetter(originalScope, true);
});
};
// bind events only if appendToBody params exist - performance feature
if (appendToBody) {
angular.element($window).on('resize', fireRecalculating);
$document.find('body').on('scroll', fireRecalculating);
}
// Declare the debounced function outside recalculating for
// proper debouncing
var debouncedRecalculate = $$debounce(function() {
// if popup is visible
if (scope.matches.length) {
recalculatePosition();
}
scope.moveInProgress = false;
}, eventDebounceTime);
// Default progress type
scope.moveInProgress = false;
function fireRecalculating() {
if (!scope.moveInProgress) {
scope.moveInProgress = true;
scope.$digest();
}
debouncedRecalculate();
}
// recalculate actual position and set new values to scope
// after digest loop is popup in right position
function recalculatePosition() {
scope.position = appendToBody ? $position.offset(element) : $position.position(element);
scope.position.top += element.prop('offsetHeight');
}
//we need to propagate user's query so we can higlight matches
scope.query = undefined;
//Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
var timeoutPromise;
var scheduleSearchWithTimeout = function(inputValue) {
timeoutPromise = $timeout(function() {
getMatchesAsync(inputValue);
}, waitTime);
};
var cancelPreviousTimeout = function() {
if (timeoutPromise) {
$timeout.cancel(timeoutPromise);
}
};
resetMatches();
scope.assignIsOpen = function (isOpen) {
isOpenSetter(originalScope, isOpen);
};
scope.select = function(activeIdx, evt) {
//called from within the $digest() cycle
var locals = {};
var model, item;
selected = true;
locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
model = parserResult.modelMapper(originalScope, locals);
$setModelValue(originalScope, model);
modelCtrl.$setValidity('editable', true);
modelCtrl.$setValidity('parse', true);
onSelectCallback(originalScope, {
$item: item,
$model: model,
$label: parserResult.viewMapper(originalScope, locals),
$event: evt
});
resetMatches();
//return focus to the input element if a match was selected via a mouse click event
// use timeout to avoid $rootScope:inprog error
if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
$timeout(function() { element[0].focus(); }, 0, false);
}
};
//bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
element.on('keydown', function(evt) {
//typeahead is open and an "interesting" key was pressed
if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
return;
}
/**
* if there's nothing selected (i.e. focusFirst) and enter or tab is hit
* or
* shift + tab is pressed to bring focus to the previous element
* then clear the results
*/
if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13) || evt.which === 9 && !!evt.shiftKey) {
resetMatches();
scope.$digest();
return;
}
evt.preventDefault();
var target;
switch (evt.which) {
case 9:
case 13:
scope.$apply(function () {
if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
$$debounce(function() {
scope.select(scope.activeIdx, evt);
}, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
} else {
scope.select(scope.activeIdx, evt);
}
});
break;
case 27:
evt.stopPropagation();
resetMatches();
scope.$digest();
break;
case 38:
scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
scope.$digest();
target = popUpEl.find('li')[scope.activeIdx];
target.parentNode.scrollTop = target.offsetTop;
break;
case 40:
scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
scope.$digest();
target = popUpEl.find('li')[scope.activeIdx];
target.parentNode.scrollTop = target.offsetTop;
break;
}
});
element.bind('focus', function (evt) {
hasFocus = true;
if (minLength === 0 && !modelCtrl.$viewValue) {
$timeout(function() {
getMatchesAsync(modelCtrl.$viewValue, evt);
}, 0);
}
});
element.bind('blur', function(evt) {
if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
selected = true;
scope.$apply(function() {
if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) {
$$debounce(function() {
scope.select(scope.activeIdx, evt);
}, scope.debounceUpdate.blur);
} else {
scope.select(scope.activeIdx, evt);
}
});
}
if (!isEditable && modelCtrl.$error.editable) {
modelCtrl.$viewValue = '';
element.val('');
}
hasFocus = false;
selected = false;
});
// Keep reference to click handler to unbind it.
var dismissClickHandler = function(evt) {
// Issue #3973
// Firefox treats right click as a click on document
if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
resetMatches();
if (!$rootScope.$$phase) {
scope.$digest();
}
}
};
$document.on('click', dismissClickHandler);
originalScope.$on('$destroy', function() {
$document.off('click', dismissClickHandler);
if (appendToBody || appendTo) {
$popup.remove();
}
if (appendToBody) {
angular.element($window).off('resize', fireRecalculating);
$document.find('body').off('scroll', fireRecalculating);
}
// Prevent jQuery cache memory leak
popUpEl.remove();
if (showHint) {
inputsContainer.remove();
}
});
var $popup = $compile(popUpEl)(scope);
if (appendToBody) {
$document.find('body').append($popup);
} else if (appendTo) {
angular.element(appendTo).eq(0).append($popup);
} else {
element.after($popup);
}
this.init = function(_modelCtrl, _ngModelOptions) {
modelCtrl = _modelCtrl;
ngModelOptions = _ngModelOptions;
scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope);
//plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
//$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
modelCtrl.$parsers.unshift(function(inputValue) {
hasFocus = true;
if (minLength === 0 || inputValue && inputValue.length >= minLength) {
if (waitTime > 0) {
cancelPreviousTimeout();
scheduleSearchWithTimeout(inputValue);
} else {
getMatchesAsync(inputValue);
}
} else {
isLoadingSetter(originalScope, false);
cancelPreviousTimeout();
resetMatches();
}
if (isEditable) {
return inputValue;
}
if (!inputValue) {
// Reset in case user had typed something previously.
modelCtrl.$setValidity('editable', true);
return null;
}
modelCtrl.$setValidity('editable', false);
return undefined;
});
modelCtrl.$formatters.push(function(modelValue) {
var candidateViewValue, emptyViewValue;
var locals = {};
// The validity may be set to false via $parsers (see above) if
// the model is restricted to selected values. If the model
// is set manually it is considered to be valid.
if (!isEditable) {
modelCtrl.$setValidity('editable', true);
}
if (inputFormatter) {
locals.$model = modelValue;
return inputFormatter(originalScope, locals);
}
//it might happen that we don't have enough info to properly render input value
//we need to check for this situation and simply return model value if we can't apply custom formatting
locals[parserResult.itemName] = modelValue;
candidateViewValue = parserResult.viewMapper(originalScope, locals);
locals[parserResult.itemName] = undefined;
emptyViewValue = parserResult.viewMapper(originalScope, locals);
return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
});
};
}])
.directive('uibTypeahead', function() {
return {
controller: 'UibTypeaheadController',
require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
link: function(originalScope, element, attrs, ctrls) {
ctrls[2].init(ctrls[0], ctrls[1]);
}
};
})
.directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) {
return {
scope: {
matches: '=',
query: '=',
active: '=',
position: '&',
moveInProgress: '=',
select: '&',
assignIsOpen: '&',
debounce: '&'
},
replace: true,
templateUrl: function(element, attrs) {
return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html';
},
link: function(scope, element, attrs) {
scope.templateUrl = attrs.templateUrl;
scope.isOpen = function() {
var isDropdownOpen = scope.matches.length > 0;
scope.assignIsOpen({ isOpen: isDropdownOpen });
return isDropdownOpen;
};
scope.isActive = function(matchIdx) {
return scope.active === matchIdx;
};
scope.selectActive = function(matchIdx) {
scope.active = matchIdx;
};
scope.selectMatch = function(activeIdx, evt) {
var debounce = scope.debounce();
if (angular.isNumber(debounce) || angular.isObject(debounce)) {
$$debounce(function() {
scope.select({activeIdx: activeIdx, evt: evt});
}, angular.isNumber(debounce) ? debounce : debounce['default']);
} else {
scope.select({activeIdx: activeIdx, evt: evt});
}
};
}
};
}])
.directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
return {
scope: {
index: '=',
match: '=',
query: '='
},
link: function(scope, element, attrs) {
var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html';
$templateRequest(tplUrl).then(function(tplContent) {
var tplEl = angular.element(tplContent.trim());
element.replaceWith(tplEl);
$compile(tplEl)(scope);
});
}
};
}])
.filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
var isSanitizePresent;
isSanitizePresent = $injector.has('$sanitize');
function escapeRegexp(queryToEscape) {
// Regex: capture the whole query string and replace it with the string that will be used to match
// the results, for example if the capture is "a" the result will be \a
return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
}
function containsHtml(matchItem) {
return /<.*>/g.test(matchItem);
}
return function(matchItem, query) {
if (!isSanitizePresent && containsHtml(matchItem)) {
$log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
}
matchItem = query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag
if (!isSanitizePresent) {
matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
}
return matchItem;
};
}]);
angular.module('ui.bootstrap.carousel').run(function() {!angular.$$csp().noInlineStyle && angular.element(document).find('head').prepend('<style type="text/css">.ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}</style>'); });
angular.module('ui.bootstrap.position').run(function() {!angular.$$csp().noInlineStyle && angular.element(document).find('head').prepend('<style type="text/css">.uib-position-measure{display:block !important;visibility:hidden !important;position:absolute !important;top:-9999px !important;left:-9999px !important;}.uib-position-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll;}</style>'); });
angular.module('ui.bootstrap.datepicker').run(function() {!angular.$$csp().noInlineStyle && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker .uib-title{width:100%;}.uib-day button,.uib-month button,.uib-year button{min-width:100%;}.uib-datepicker-popup.dropdown-menu{display:block;float:none;margin:0;}.uib-button-bar{padding:10px 9px 2px;}.uib-left,.uib-right{width:100%}</style>'); });
angular.module('ui.bootstrap.tooltip').run(function() {!angular.$$csp().noInlineStyle && angular.element(document).find('head').prepend('<style type="text/css">[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow,[uib-popover-popup].popover.top-left > .arrow,[uib-popover-popup].popover.top-right > .arrow,[uib-popover-popup].popover.bottom-left > .arrow,[uib-popover-popup].popover.bottom-right > .arrow,[uib-popover-popup].popover.left-top > .arrow,[uib-popover-popup].popover.left-bottom > .arrow,[uib-popover-popup].popover.right-top > .arrow,[uib-popover-popup].popover.right-bottom > .arrow{top:auto;bottom:auto;left:auto;right:auto;margin:0;}[uib-popover-popup].popover,[uib-popover-template-popup].popover{display:block !important;}</style>'); });
angular.module('ui.bootstrap.timepicker').run(function() {!angular.$$csp().noInlineStyle && angular.element(document).find('head').prepend('<style type="text/css">.uib-time input{width:50px;}</style>'); });
angular.module('ui.bootstrap.typeahead').run(function() {!angular.$$csp().noInlineStyle && angular.element(document).find('head').prepend('<style type="text/css">[uib-typeahead-popup].dropdown-menu{display:block;}</style>'); });