clients/classic-ui/public/libs/ui-bootstrap.js
angular.module('ui.bootstrap', ['ui.bootstrap.buttons','ui.bootstrap.position','ui.bootstrap.datepicker','ui.bootstrap.pagination','ui.bootstrap.rating','ui.bootstrap.timepicker','ui.bootstrap.bindHtml','ui.bootstrap.typeahead']);
angular.module('ui.bootstrap.buttons', [])
.constant('buttonConfig', {
activeClass:'active',
toggleEvent:'click'
})
.directive('btnRadio', ['buttonConfig', function (buttonConfig) {
var activeClass = buttonConfig.activeClass || 'active';
var toggleEvent = buttonConfig.toggleEvent || 'click';
return {
require:'ngModel',
link:function (scope, element, attrs, ngModelCtrl) {
//model -> UI
ngModelCtrl.$render = function () {
element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
};
//ui->model
element.bind(toggleEvent, function () {
if (!element.hasClass(activeClass)) {
scope.$apply(function () {
ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio));
ngModelCtrl.$render();
});
}
});
}
};
}])
.directive('btnCheckbox', ['buttonConfig', function (buttonConfig) {
var activeClass = buttonConfig.activeClass || 'active';
var toggleEvent = buttonConfig.toggleEvent || 'click';
return {
require:'ngModel',
link:function (scope, element, attrs, ngModelCtrl) {
function getTrueValue() {
var trueValue = scope.$eval(attrs.btnCheckboxTrue);
return angular.isDefined(trueValue) ? trueValue : true;
}
function getFalseValue() {
var falseValue = scope.$eval(attrs.btnCheckboxFalse);
return angular.isDefined(falseValue) ? falseValue : false;
}
//model -> UI
ngModelCtrl.$render = function () {
element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
};
//ui->model
element.bind(toggleEvent, function () {
scope.$apply(function () {
ngModelCtrl.$setViewValue(element.hasClass(activeClass) ? getFalseValue() : getTrueValue());
ngModelCtrl.$render();
});
});
}
};
}]);
angular.module('ui.bootstrap.position', [])
/**
* A set of utility methods that can be use to retrieve position of DOM elements.
* It is meant to be used where we need to absolute-position DOM elements in
* relation to other, existing elements (this is the case for tooltips, popovers,
* typeahead suggestions etc.).
*/
.factory('$position', ['$document', '$window', function ($document, $window) {
function getStyle(el, cssprop) {
if (el.currentStyle) { //IE
return el.currentStyle[cssprop];
} else if ($window.getComputedStyle) {
return $window.getComputedStyle(el)[cssprop];
}
// finally try and get inline style
return el.style[cssprop];
}
/**
* Checks if a given element is statically positioned
* @param element - raw DOM element
*/
function isStaticPositioned(element) {
return (getStyle(element, 'position') || 'static' ) === 'static';
}
/**
* returns the closest, non-statically positioned parentOffset of a given element
* @param element
*/
var parentOffsetEl = function (element) {
var docDomEl = $document[0];
var offsetParent = element.offsetParent || docDomEl;
while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
offsetParent = offsetParent.offsetParent;
}
return offsetParent || docDomEl;
};
return {
/**
* Provides read-only equivalent of jQuery's position function:
* http://api.jquery.com/position/
*/
position: function (element) {
var elBCR = this.offset(element);
var offsetParentBCR = { top: 0, left: 0 };
var offsetParentEl = parentOffsetEl(element[0]);
if (offsetParentEl != $document[0]) {
offsetParentBCR = this.offset(angular.element(offsetParentEl));
offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
}
return {
width: element.prop('offsetWidth'),
height: element.prop('offsetHeight'),
top: elBCR.top - offsetParentBCR.top,
left: elBCR.left - offsetParentBCR.left
};
},
/**
* Provides read-only equivalent of jQuery's offset function:
* http://api.jquery.com/offset/
*/
offset: function (element) {
var boundingClientRect = element[0].getBoundingClientRect();
return {
width: element.prop('offsetWidth'),
height: element.prop('offsetHeight'),
top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)
};
}
};
}]);
angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position'])
.constant('datepickerConfig', {
dayFormat: 'dd',
monthFormat: 'MMMM',
yearFormat: 'yyyy',
dayHeaderFormat: 'EEE',
dayTitleFormat: 'MMMM yyyy',
monthTitleFormat: 'yyyy',
showWeeks: true,
startingDay: 0,
yearRange: 20,
minDate: null,
maxDate: null
})
.controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function ($scope, $attrs, dateFilter, dtConfig) {
var format = {
day: getValue($attrs.dayFormat, dtConfig.dayFormat),
month: getValue($attrs.monthFormat, dtConfig.monthFormat),
year: getValue($attrs.yearFormat, dtConfig.yearFormat),
dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat),
dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat),
monthTitle: getValue($attrs.monthTitleFormat, dtConfig.monthTitleFormat)
},
startingDay = getValue($attrs.startingDay, dtConfig.startingDay),
yearRange = getValue($attrs.yearRange, dtConfig.yearRange);
this.minDate = dtConfig.minDate ? new Date(dtConfig.minDate) : null;
this.maxDate = dtConfig.maxDate ? new Date(dtConfig.maxDate) : null;
function getValue(value, defaultValue) {
return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue;
}
function getDaysInMonth( year, month ) {
return new Date(year, month, 0).getDate();
}
function getDates(startDate, n) {
var dates = new Array(n);
var current = startDate, i = 0;
while (i < n) {
dates[i++] = new Date(current);
current.setDate( current.getDate() + 1 );
}
return dates;
}
function makeDate(date, format, isSelected, isSecondary) {
return { date: date, label: dateFilter(date, format), selected: !!isSelected, secondary: !!isSecondary };
}
this.modes = [
{
name: 'day',
getVisibleDates: function (date, selected) {
var year = date.getFullYear(), month = date.getMonth(), firstDayOfMonth = new Date(year, month, 1);
var difference = startingDay - firstDayOfMonth.getDay(),
numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
firstDate = new Date(firstDayOfMonth), numDates = 0;
if ( numDisplayedFromPreviousMonth > 0 ) {
firstDate.setDate( - numDisplayedFromPreviousMonth + 1 );
numDates += numDisplayedFromPreviousMonth; // Previous
}
numDates += getDaysInMonth(year, month + 1); // Current
numDates += (7 - numDates % 7) % 7; // Next
var days = getDates(firstDate, numDates), labels = new Array(7);
for (var i = 0; i < numDates; i ++) {
var dt = new Date(days[i]);
days[i] = makeDate(dt, format.day, (selected && selected.getDate() === dt.getDate() && selected.getMonth() === dt.getMonth() && selected.getFullYear() === dt.getFullYear()), dt.getMonth() !== month);
}
for (var j = 0; j < 7; j++) {
labels[j] = dateFilter(days[j].date, format.dayHeader);
}
return { objects: days, title: dateFilter(date, format.dayTitle), labels: labels };
},
compare: function (date1, date2) {
return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) );
},
split: 7,
step: { months: 1 }
},
{
name: 'month',
getVisibleDates: function (date, selected) {
var months = new Array(12), year = date.getFullYear();
for ( var i = 0; i < 12; i++ ) {
var dt = new Date(year, i, 1);
months[i] = makeDate(dt, format.month, (selected && selected.getMonth() === i && selected.getFullYear() === year));
}
return { objects: months, title: dateFilter(date, format.monthTitle) };
},
compare: function (date1, date2) {
return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() );
},
split: 3,
step: { years: 1 }
},
{
name: 'year',
getVisibleDates: function (date, selected) {
var years = new Array(yearRange), year = date.getFullYear(), startYear = parseInt((year - 1) / yearRange, 10) * yearRange + 1;
for ( var i = 0; i < yearRange; i++ ) {
var dt = new Date(startYear + i, 0, 1);
years[i] = makeDate(dt, format.year, (selected && selected.getFullYear() === dt.getFullYear()));
}
return { objects: years, title: [years[0].label, years[yearRange - 1].label].join(' - ') };
},
compare: function (date1, date2) {
return date1.getFullYear() - date2.getFullYear();
},
split: 5,
step: { years: yearRange }
}
];
this.isDisabled = function (date, mode) {
var currentMode = this.modes[mode || 0];
return ((this.minDate && currentMode.compare(date, this.minDate) < 0) || (this.maxDate && currentMode.compare(date, this.maxDate) > 0) || ($scope.dateDisabled && $scope.dateDisabled({date: date, mode: currentMode.name})));
};
}])
.directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter, $parse, datepickerConfig, $log) {
return {
restrict: 'EA',
replace: true,
templateUrl: 'template/datepicker/datepicker.html',
scope: {
dateDisabled: '&'
},
require: ['datepicker', '?^ngModel'],
controller: 'DatepickerController',
link: function (scope, element, attrs, ctrls) {
var datepickerCtrl = ctrls[0], ngModel = ctrls[1];
if (!ngModel) {
return; // do nothing if no ng-model
}
// Configuration parameters
var mode = 0, selected = new Date(), showWeeks = datepickerConfig.showWeeks;
if (attrs.showWeeks) {
scope.$parent.$watch($parse(attrs.showWeeks), function (value) {
showWeeks = !! value;
updateShowWeekNumbers();
});
} else {
updateShowWeekNumbers();
}
if (attrs.min) {
scope.$parent.$watch($parse(attrs.min), function (value) {
datepickerCtrl.minDate = value ? new Date(value) : null;
refill();
});
}
if (attrs.max) {
scope.$parent.$watch($parse(attrs.max), function (value) {
datepickerCtrl.maxDate = value ? new Date(value) : null;
refill();
});
}
function updateShowWeekNumbers() {
scope.showWeekNumbers = mode === 0 && showWeeks;
}
// Split array into smaller arrays
function split(arr, size) {
var arrays = [];
while (arr.length > 0) {
arrays.push(arr.splice(0, size));
}
return arrays;
}
function refill( updateSelected ) {
var date = null, valid = true;
if ( ngModel.$modelValue ) {
date = new Date( ngModel.$modelValue );
if ( isNaN(date) ) {
valid = false;
$log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
} else if ( updateSelected ) {
selected = date;
}
}
ngModel.$setValidity('date', valid);
var currentMode = datepickerCtrl.modes[mode], data = currentMode.getVisibleDates(selected, date);
angular.forEach(data.objects, function (obj) {
obj.disabled = datepickerCtrl.isDisabled(obj.date, mode);
});
ngModel.$setValidity('date-disabled', (!date || !datepickerCtrl.isDisabled(date)));
scope.rows = split(data.objects, currentMode.split);
scope.labels = data.labels || [];
scope.title = data.title;
}
function setMode(value) {
mode = value;
updateShowWeekNumbers();
refill();
}
ngModel.$render = function () {
refill( true );
};
scope.select = function ( date ) {
if ( mode === 0 ) {
var dt = new Date( ngModel.$modelValue );
dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() );
ngModel.$setViewValue( dt );
refill( true );
} else {
selected = date;
setMode( mode - 1 );
}
};
scope.move = function (direction) {
var step = datepickerCtrl.modes[mode].step;
selected.setMonth( selected.getMonth() + direction * (step.months || 0) );
selected.setFullYear( selected.getFullYear() + direction * (step.years || 0) );
refill();
};
scope.toggleMode = function () {
setMode( (mode + 1) % datepickerCtrl.modes.length );
};
scope.getWeekNumber = function (row) {
return ( mode === 0 && scope.showWeekNumbers && row.length === 7 ) ? getISO8601WeekNumber(row[0].date) : null;
};
function getISO8601WeekNumber(date) {
var checkDate = new Date(date);
checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
var time = checkDate.getTime();
checkDate.setMonth(0); // Compare with Jan 1
checkDate.setDate(1);
return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
}
}
};
}])
.constant('datepickerPopupConfig', {
dateFormat: 'yyyy-MM-dd',
closeOnDateSelection: true
})
.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig',
function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig) {
return {
restrict: 'EA',
require: 'ngModel',
link: function (originalScope, element, attrs, ngModel) {
var closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
var dateFormat = attrs.datepickerPopup || datepickerPopupConfig.dateFormat;
// create a child scope for the datepicker directive so we are not polluting original scope
var scope = originalScope.$new();
originalScope.$on('$destroy', function () {
scope.$destroy();
});
var getIsOpen, setIsOpen;
if ( attrs.isOpen ) {
getIsOpen = $parse(attrs.isOpen);
setIsOpen = getIsOpen.assign;
originalScope.$watch(getIsOpen, function updateOpen(value) {
scope.isOpen = !! value;
});
}
scope.isOpen = getIsOpen ? getIsOpen(originalScope) : false; // Initial state
function setOpen( value ) {
if (setIsOpen) {
setIsOpen(originalScope, !!value);
} else {
scope.isOpen = !!value;
}
}
var documentClickBind = function (event) {
if (scope.isOpen && event.target !== element[0]) {
scope.$apply(function () {
setOpen(false);
});
}
};
var elementFocusBind = function () {
scope.$apply(function () {
setOpen( true );
});
};
// popup element used to display calendar
var popupEl = angular.element('<datepicker-popup-wrap><datepicker></datepicker></datepicker-popup-wrap>');
popupEl.attr({
'ng-model': 'date',
'ng-change': 'dateSelection()'
});
var datepickerEl = popupEl.find('datepicker');
if (attrs.datepickerOptions) {
datepickerEl.attr(angular.extend({}, originalScope.$eval(attrs.datepickerOptions)));
}
// TODO: reverse from dateFilter string to Date object
function parseDate(viewValue) {
if (!viewValue) {
ngModel.$setValidity('date', true);
return null;
} else if (angular.isDate(viewValue)) {
ngModel.$setValidity('date', true);
return viewValue;
} else if (angular.isString(viewValue)) {
var date = new Date(viewValue);
if (isNaN(date)) {
ngModel.$setValidity('date', false);
return undefined;
} else {
ngModel.$setValidity('date', true);
return date;
}
} else {
ngModel.$setValidity('date', false);
return undefined;
}
}
ngModel.$parsers.unshift(parseDate);
// Inner change
scope.dateSelection = function () {
ngModel.$setViewValue(scope.date);
ngModel.$render();
if (closeOnDateSelection) {
setOpen( false );
}
};
element.bind('input change keyup', function () {
scope.$apply(function () {
updateCalendar();
});
});
// Outter change
ngModel.$render = function () {
var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : '';
element.val(date);
updateCalendar();
};
function updateCalendar() {
scope.date = ngModel.$modelValue;
updatePosition();
}
function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) {
if (attribute) {
originalScope.$watch($parse(attribute), function (value){
scope[scopeProperty] = value;
});
datepickerEl.attr(datepickerAttribute || scopeProperty, scopeProperty);
}
}
addWatchableAttribute(attrs.min, 'min');
addWatchableAttribute(attrs.max, 'max');
if (attrs.showWeeks) {
addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks');
} else {
scope.showWeeks = true;
datepickerEl.attr('show-weeks', 'showWeeks');
}
if (attrs.dateDisabled) {
datepickerEl.attr('date-disabled', attrs.dateDisabled);
}
function updatePosition() {
scope.position = $position.position(element);
scope.position.top = scope.position.top + element.prop('offsetHeight');
}
var documentBindingInitialized = false, elementFocusInitialized = false;
scope.$watch('isOpen', function (value) {
if (value) {
updatePosition();
$document.bind('click', documentClickBind);
if(elementFocusInitialized) {
element.unbind('focus', elementFocusBind);
}
element[0].focus();
documentBindingInitialized = true;
} else {
if(documentBindingInitialized) {
$document.unbind('click', documentClickBind);
}
element.bind('focus', elementFocusBind);
elementFocusInitialized = true;
}
if ( setIsOpen ) {
setIsOpen(originalScope, value);
}
});
var $setModelValue = $parse(attrs.ngModel).assign;
scope.today = function () {
$setModelValue(originalScope, new Date());
};
scope.clear = function () {
$setModelValue(originalScope, null);
};
element.after($compile(popupEl)(scope));
}
};
}])
.directive('datepickerPopupWrap', [function () {
return {
restrict:'E',
replace: true,
transclude: true,
templateUrl: 'template/datepicker/popup.html',
link:function (scope, element, attrs) {
element.bind('click', function (event) {
event.preventDefault();
event.stopPropagation();
});
}
};
}]);
angular.module('ui.bootstrap.pagination', [])
.controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) {
var self = this;
this.init = function (defaultItemsPerPage) {
if ($attrs.itemsPerPage) {
$scope.$parent.$watch($parse($attrs.itemsPerPage), function (value) {
self.itemsPerPage = parseInt(value, 10);
$scope.totalPages = self.calculateTotalPages();
});
} else {
this.itemsPerPage = defaultItemsPerPage;
}
};
this.noPrevious = function () {
return this.page === 1;
};
this.noNext = function () {
return this.page === $scope.totalPages;
};
this.isActive = function (page) {
return this.page === page;
};
this.calculateTotalPages = function () {
return this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
};
this.getAttributeValue = function (attribute, defaultValue, interpolate) {
return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
};
this.render = function () {
this.page = parseInt($scope.page, 10) || 1;
$scope.pages = this.getPages(this.page, $scope.totalPages);
};
$scope.selectPage = function (page) {
if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) {
$scope.page = page;
$scope.onSelectPage({ page: page });
}
};
$scope.$watch('totalItems', function () {
$scope.totalPages = self.calculateTotalPages();
});
$scope.$watch('totalPages', function (value) {
if ( $attrs.numPages ) {
$scope.numPages = value; // Readonly variable
}
if ( self.page > value ) {
$scope.selectPage(value);
} else {
self.render();
}
});
$scope.$watch('page', function () {
self.render();
});
}])
.constant('paginationConfig', {
itemsPerPage: 10,
boundaryLinks: false,
directionLinks: true,
firstText: 'First',
previousText: 'Previous',
nextText: 'Next',
lastText: 'Last',
rotate: true
})
.directive('pagination', ['$parse', 'paginationConfig', function ($parse, config) {
return {
restrict: 'EA',
scope: {
page: '=',
totalItems: '=',
onSelectPage:' &',
numPages: '='
},
controller: 'PaginationController',
templateUrl: 'template/pagination/pagination.html',
replace: true,
link: function (scope, element, attrs, paginationCtrl) {
// Setup configuration parameters
var maxSize,
boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ),
directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ),
firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true),
previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true),
rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate);
paginationCtrl.init(config.itemsPerPage);
if (attrs.maxSize) {
scope.$parent.$watch($parse(attrs.maxSize), function (value) {
maxSize = parseInt(value, 10);
paginationCtrl.render();
});
}
// Create page object used in template
function makePage(number, text, isActive, isDisabled) {
return {
number: number,
text: text,
active: isActive,
disabled: isDisabled
};
}
paginationCtrl.getPages = function (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, number, paginationCtrl.isActive(number), false);
pages.push(page);
}
// Add links to move between page sets
if ( isMaxSized && ! rotate ) {
if ( startPage > 1 ) {
var previousPageSet = makePage(startPage - 1, '...', false, false);
pages.unshift(previousPageSet);
}
if ( endPage < totalPages ) {
var nextPageSet = makePage(endPage + 1, '...', false, false);
pages.push(nextPageSet);
}
}
// Add previous & next links
if (directionLinks) {
var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious());
pages.unshift(previousPage);
var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext());
pages.push(nextPage);
}
// Add first & last links
if (boundaryLinks) {
var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious());
pages.unshift(firstPage);
var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext());
pages.push(lastPage);
}
return pages;
};
}
};
}])
.constant('pagerConfig', {
itemsPerPage: 10,
previousText: '« Previous',
nextText: 'Next »',
align: true
})
.directive('pager', ['pagerConfig', function (config) {
return {
restrict: 'EA',
scope: {
page: '=',
totalItems: '=',
onSelectPage:' &',
numPages: '='
},
controller: 'PaginationController',
templateUrl: 'template/pagination/pager.html',
replace: true,
link: function (scope, element, attrs, paginationCtrl) {
// Setup configuration parameters
var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
align = paginationCtrl.getAttributeValue(attrs.align, config.align);
paginationCtrl.init(config.itemsPerPage);
// Create page object used in template
function makePage(number, text, isDisabled, isPrevious, isNext) {
return {
number: number,
text: text,
disabled: isDisabled,
previous: ( align && isPrevious ),
next: ( align && isNext )
};
}
paginationCtrl.getPages = function (currentPage) {
return [
makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false),
makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true)
];
};
}
};
}]);
angular.module('ui.bootstrap.rating', [])
.constant('ratingConfig', {
max: 5,
stateOn: null,
stateOff: null
})
.controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function ($scope, $attrs, $parse, ratingConfig) {
this.maxRange = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max;
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;
this.createDefaultRange = function (len) {
var defaultStateObject = {
stateOn: this.stateOn,
stateOff: this.stateOff
};
var states = new Array(len);
for (var i = 0; i < len; i++) {
states[i] = defaultStateObject;
}
return states;
};
this.normalizeRange = function (states) {
for (var i = 0, n = states.length; i < n; i++) {
states[i].stateOn = states[i].stateOn || this.stateOn;
states[i].stateOff = states[i].stateOff || this.stateOff;
}
return states;
};
// Get objects used in template
$scope.range = angular.isDefined($attrs.ratingStates) ? this.normalizeRange(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createDefaultRange(this.maxRange);
$scope.rate = function (value) {
if ( $scope.readonly || $scope.value === value) {
return;
}
$scope.value = value;
};
$scope.enter = function (value) {
if ( ! $scope.readonly ) {
$scope.val = value;
}
$scope.onHover({value: value});
};
$scope.reset = function () {
$scope.val = angular.copy($scope.value);
$scope.onLeave();
};
$scope.$watch('value', function (value) {
$scope.val = value;
});
$scope.readonly = false;
if ($attrs.readonly) {
$scope.$parent.$watch($parse($attrs.readonly), function (value) {
$scope.readonly = !!value;
});
}
}])
.directive('rating', function () {
return {
restrict: 'EA',
scope: {
value: '=',
onHover: '&',
onLeave: '&'
},
controller: 'RatingController',
templateUrl: 'template/rating/rating.html',
replace: true
};
});
angular.module('ui.bootstrap.timepicker', [])
.constant('timepickerConfig', {
hourStep: 1,
minuteStep: 1,
showMeridian: true,
meridians: ['AM', 'PM'],
readonlyInput: false,
mousewheel: true
})
.directive('timepicker', ['$parse', '$log', 'timepickerConfig', function ($parse, $log, timepickerConfig) {
return {
restrict: 'EA',
require:'?^ngModel',
replace: true,
scope: {},
templateUrl: 'template/timepicker/timepicker.html',
link: function (scope, element, attrs, ngModel) {
if ( !ngModel ) {
return; // do nothing if no ng-model
}
var selected = new Date(), meridians = timepickerConfig.meridians;
var hourStep = timepickerConfig.hourStep;
if (attrs.hourStep) {
scope.$parent.$watch($parse(attrs.hourStep), function (value) {
hourStep = parseInt(value, 10);
});
}
var minuteStep = timepickerConfig.minuteStep;
if (attrs.minuteStep) {
scope.$parent.$watch($parse(attrs.minuteStep), function (value) {
minuteStep = parseInt(value, 10);
});
}
// 12H / 24H mode
scope.showMeridian = timepickerConfig.showMeridian;
if (attrs.showMeridian) {
scope.$parent.$watch($parse(attrs.showMeridian), function (value) {
scope.showMeridian = !!value;
if ( ngModel.$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 = parseInt( scope.hours, 10 );
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 = parseInt(scope.minutes, 10);
return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined;
}
function pad( value ) {
return ( angular.isDefined(value) && value.toString().length < 2 ) ? `0${ value}` : value;
}
// Input elements
var inputs = element.find('input'), hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1);
// Respond on mousewheel spin
var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel;
if ( mousewheel ) {
var isScrollingUp = function (e) {
if (e.originalEvent) {
e = e.originalEvent;
}
//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) {
scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() );
e.preventDefault();
});
minutesInputEl.bind('mousewheel wheel', function (e) {
scope.$apply( (isScrollingUp(e)) ? scope.incrementMinutes() : scope.decrementMinutes() );
e.preventDefault();
});
}
scope.readonlyInput = (angular.isDefined(attrs.readonlyInput)) ? scope.$eval(attrs.readonlyInput) : timepickerConfig.readonlyInput;
if ( ! scope.readonlyInput ) {
var invalidate = function (invalidHours, invalidMinutes) {
ngModel.$setViewValue( null );
ngModel.$setValidity('time', false);
if (angular.isDefined(invalidHours)) {
scope.invalidHours = invalidHours;
}
if (angular.isDefined(invalidMinutes)) {
scope.invalidMinutes = invalidMinutes;
}
};
scope.updateHours = function () {
var hours = getHoursFromTemplate();
if ( angular.isDefined(hours) ) {
selected.setHours( hours );
refresh( 'h' );
} else {
invalidate(true);
}
};
hoursInputEl.bind('blur', function (e) {
if ( !scope.validHours && scope.hours < 10) {
scope.$apply( function () {
scope.hours = pad( scope.hours );
});
}
});
scope.updateMinutes = function () {
var minutes = getMinutesFromTemplate();
if ( angular.isDefined(minutes) ) {
selected.setMinutes( minutes );
refresh( 'm' );
} else {
invalidate(undefined, true);
}
};
minutesInputEl.bind('blur', function (e) {
if ( !scope.invalidMinutes && scope.minutes < 10 ) {
scope.$apply( function () {
scope.minutes = pad( scope.minutes );
});
}
});
} else {
scope.updateHours = angular.noop;
scope.updateMinutes = angular.noop;
}
ngModel.$render = function () {
var date = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : null;
if ( isNaN(date) ) {
ngModel.$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;
}
makeValid();
updateTemplate();
}
};
// Call internally when we know that model is valid.
function refresh( keyboardChange ) {
makeValid();
ngModel.$setViewValue( new Date(selected) );
updateTemplate( keyboardChange );
}
function makeValid() {
ngModel.$setValidity('time', true);
scope.invalidHours = false;
scope.invalidMinutes = false;
}
function updateTemplate( keyboardChange ) {
var hours = selected.getHours(), minutes = selected.getMinutes();
if ( scope.showMeridian ) {
hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system
}
scope.hours = keyboardChange === 'h' ? hours : pad(hours);
scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes);
scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
}
function addMinutes( minutes ) {
var dt = new Date( selected.getTime() + minutes * 60000 );
selected.setHours( dt.getHours(), dt.getMinutes() );
refresh();
}
scope.incrementHours = function () {
addMinutes( hourStep * 60 );
};
scope.decrementHours = function () {
addMinutes( - hourStep * 60 );
};
scope.incrementMinutes = function () {
addMinutes( minuteStep );
};
scope.decrementMinutes = function () {
addMinutes( - minuteStep );
};
scope.toggleMeridian = function () {
addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) );
};
}
};
}]);
angular.module('ui.bootstrap.bindHtml', [])
.directive('bindHtmlUnsafe', function () {
return function (scope, element, attr) {
element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe);
scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) {
element.html(value || '');
});
};
});
angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])
/**
* A helper service that can parse typeahead's syntax (string provided by users)
* Extracted to a separate service for ease of unit testing
*/
.factory('typeaheadParser', ['$parse', function ($parse) {
// 00000111000000000000022200000000000000003333333333333330000000000044000
var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
return {
parse:function (input) {
var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source;
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])
};
}
};
}])
.directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) {
var HOT_KEYS = [9, 13, 27, 38, 40];
return {
require:'ngModel',
link:function (originalScope, element, attrs, modelCtrl) {
//SUPPORTED ATTRIBUTES (OPTIONS)
//minimal no of characters that needs to be entered before typeahead kicks-in
var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
//minimal wait time after last character typed before typehead 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;
//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);
var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
//INTERNAL VARIABLES
//model setter executed upon match selection
var $setModelValue = $parse(attrs.ngModel).assign;
//expressions used by typeahead
var parserResult = typeaheadParser.parse(attrs.typeahead);
//pop-up element used to display matches
var popUpEl = angular.element('<typeahead-popup></typeahead-popup>');
popUpEl.attr({
matches: 'matches',
active: 'activeIdx',
select: 'select(activeIdx)',
query: 'query',
position: 'position'
});
//custom item template
if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
}
//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();
originalScope.$on('$destroy', function (){
scope.$destroy();
});
var resetMatches = function () {
scope.matches = [];
scope.activeIdx = -1;
};
var getMatchesAsync = function (inputValue) {
var locals = {$viewValue: inputValue};
isLoadingSetter(originalScope, true);
$q.when(parserResult.source(scope, 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
if (inputValue === modelCtrl.$viewValue) {
if (matches.length > 0) {
scope.activeIdx = 0;
scope.matches.length = 0;
//transform labels
for(var i=0; i<matches.length; i++) {
locals[parserResult.itemName] = matches[i];
scope.matches.push({
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
scope.position = $position.position(element);
scope.position.top = scope.position.top + element.prop('offsetHeight');
} else {
resetMatches();
}
isLoadingSetter(originalScope, false);
}
}, function (){
resetMatches();
isLoadingSetter(originalScope, false);
});
};
resetMatches();
//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;
//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) {
resetMatches();
if (inputValue && inputValue.length >= minSearch) {
if (waitTime > 0) {
if (timeoutPromise) {
$timeout.cancel(timeoutPromise);//cancel previous timeout
}
timeoutPromise = $timeout(function () {
getMatchesAsync(inputValue);
}, waitTime);
} else {
getMatchesAsync(inputValue);
}
}
if (isEditable) {
return inputValue;
} else {
modelCtrl.$setValidity('editable', false);
return undefined;
}
});
modelCtrl.$formatters.push(function (modelValue) {
var candidateViewValue, emptyViewValue;
var locals = {};
if (inputFormatter) {
locals['$model'] = modelValue;
return inputFormatter(originalScope, locals);
} else {
//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;
}
});
scope.select = function (activeIdx) {
//called from within the $digest() cycle
var locals = {};
var model, item;
locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
model = parserResult.modelMapper(originalScope, locals);
$setModelValue(originalScope, model);
modelCtrl.$setValidity('editable', true);
onSelectCallback(originalScope, {
$item: item,
$model: model,
$label: parserResult.viewMapper(originalScope, locals)
});
resetMatches();
//return focus to the input element if a mach was selected via a mouse click event
element[0].focus();
};
//bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
element.bind('keydown', function (evt) {
//typeahead is open and an "interesting" key was pressed
if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
return;
}
evt.preventDefault();
if (evt.which === 40) {
scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
scope.$digest();
} else if (evt.which === 38) {
scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
scope.$digest();
} else if (evt.which === 13 || evt.which === 9) {
scope.$apply(function () {
scope.select(scope.activeIdx);
});
} else if (evt.which === 27) {
evt.stopPropagation();
resetMatches();
scope.$digest();
}
});
// Keep reference to click handler to unbind it.
var dismissClickHandler = function (evt) {
if (element[0] !== evt.target) {
resetMatches();
scope.$digest();
}
};
$document.bind('click', dismissClickHandler);
originalScope.$on('$destroy', function (){
$document.unbind('click', dismissClickHandler);
});
element.after($compile(popUpEl)(scope));
}
};
}])
.directive('typeaheadPopup', function () {
return {
restrict:'E',
scope:{
matches:'=',
query:'=',
active:'=',
position:'=',
select:'&'
},
replace:true,
// Hardcode this path until this is fixed: https://github.com/decipherinc/angular-tags/issues/16#issuecomment-26614629
templateUrl:'/ui-bootstrap/template/typeahead/typeahead-popup.html',
link:function (scope, element, attrs) {
scope.templateUrl = attrs.templateUrl;
scope.isOpen = function () {
return scope.matches.length > 0;
};
scope.isActive = function (matchIdx) {
return scope.active == matchIdx;
};
scope.selectActive = function (matchIdx) {
scope.active = matchIdx;
};
scope.selectMatch = function (activeIdx) {
scope.select({activeIdx:activeIdx});
};
}
};
})
.directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) {
return {
restrict:'E',
scope:{
index:'=',
match:'=',
query:'='
},
link:function (scope, element, attrs) {
var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
$http.get(tplUrl, {cache: $templateCache}).success(function (tplContent){
element.replaceWith($compile(tplContent.trim())(scope));
});
}
};
}])
.filter('typeaheadHighlight', function () {
function escapeRegexp(queryToEscape) {
return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
}
return function (matchItem, query) {
return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
};
});