|
@@ -0,0 +1,1592 @@
|
|
|
|
|
+/*
|
|
|
|
|
+ * angular-ui-bootstrap
|
|
|
|
|
+ * http://angular-ui.github.io/bootstrap/
|
|
|
|
|
+
|
|
|
|
|
+ * Version: 0.13.4 - 2015-09-03
|
|
|
|
|
+ * License: MIT
|
|
|
|
|
+ */
|
|
|
|
|
+angular.module("ui.bootstrap", ["ui.bootstrap.tpls","ui.bootstrap.position","ui.bootstrap.dateparser","ui.bootstrap.datepicker","ui.bootstrap.tabs"]);
|
|
|
|
|
+angular.module("ui.bootstrap.tpls", ["template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/tabs/tab.html","template/tabs/tabset.html"]);
|
|
|
|
|
+angular.module('ui.bootstrap.position', [])
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * A set of utility methods that can be use to retrieve position of DOM elements.
|
|
|
|
|
+ * It is meant to be used where we need to absolute-position DOM elements in
|
|
|
|
|
+ * relation to other, existing elements (this is the case for tooltips, popovers,
|
|
|
|
|
+ * typeahead suggestions etc.).
|
|
|
|
|
+ */
|
|
|
|
|
+ .factory('$position', ['$document', '$window', function($document, $window) {
|
|
|
|
|
+ function getStyle(el, cssprop) {
|
|
|
|
|
+ if (el.currentStyle) { //IE
|
|
|
|
|
+ return el.currentStyle[cssprop];
|
|
|
|
|
+ } else if ($window.getComputedStyle) {
|
|
|
|
|
+ return $window.getComputedStyle(el)[cssprop];
|
|
|
|
|
+ }
|
|
|
|
|
+ // finally try and get inline style
|
|
|
|
|
+ return el.style[cssprop];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Checks if a given element is statically positioned
|
|
|
|
|
+ * @param element - raw DOM element
|
|
|
|
|
+ */
|
|
|
|
|
+ function isStaticPositioned(element) {
|
|
|
|
|
+ return (getStyle(element, 'position') || 'static' ) === 'static';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * returns the closest, non-statically positioned parentOffset of a given element
|
|
|
|
|
+ * @param element
|
|
|
|
|
+ */
|
|
|
|
|
+ var parentOffsetEl = function(element) {
|
|
|
|
|
+ var docDomEl = $document[0];
|
|
|
|
|
+ var offsetParent = element.offsetParent || docDomEl;
|
|
|
|
|
+ while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
|
|
|
|
|
+ offsetParent = offsetParent.offsetParent;
|
|
|
|
|
+ }
|
|
|
|
|
+ return offsetParent || docDomEl;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Provides read-only equivalent of jQuery's position function:
|
|
|
|
|
+ * http://api.jquery.com/position/
|
|
|
|
|
+ */
|
|
|
|
|
+ position: function(element) {
|
|
|
|
|
+ var elBCR = this.offset(element);
|
|
|
|
|
+ var offsetParentBCR = { top: 0, left: 0 };
|
|
|
|
|
+ var offsetParentEl = parentOffsetEl(element[0]);
|
|
|
|
|
+ if (offsetParentEl != $document[0]) {
|
|
|
|
|
+ offsetParentBCR = this.offset(angular.element(offsetParentEl));
|
|
|
|
|
+ offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
|
|
|
|
|
+ offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var boundingClientRect = element[0].getBoundingClientRect();
|
|
|
|
|
+ return {
|
|
|
|
|
+ width: boundingClientRect.width || element.prop('offsetWidth'),
|
|
|
|
|
+ height: boundingClientRect.height || element.prop('offsetHeight'),
|
|
|
|
|
+ top: elBCR.top - offsetParentBCR.top,
|
|
|
|
|
+ left: elBCR.left - offsetParentBCR.left
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Provides read-only equivalent of jQuery's offset function:
|
|
|
|
|
+ * http://api.jquery.com/offset/
|
|
|
|
|
+ */
|
|
|
|
|
+ offset: function(element) {
|
|
|
|
|
+ var boundingClientRect = element[0].getBoundingClientRect();
|
|
|
|
|
+ return {
|
|
|
|
|
+ width: boundingClientRect.width || element.prop('offsetWidth'),
|
|
|
|
|
+ height: boundingClientRect.height || element.prop('offsetHeight'),
|
|
|
|
|
+ top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
|
|
|
|
|
+ left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Provides coordinates for the targetEl in relation to hostEl
|
|
|
|
|
+ */
|
|
|
|
|
+ positionElements: function(hostEl, targetEl, positionStr, appendToBody) {
|
|
|
|
|
+ var positionStrParts = positionStr.split('-');
|
|
|
|
|
+ var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center';
|
|
|
|
|
+
|
|
|
|
|
+ var hostElPos,
|
|
|
|
|
+ targetElWidth,
|
|
|
|
|
+ targetElHeight,
|
|
|
|
|
+ targetElPos;
|
|
|
|
|
+
|
|
|
|
|
+ hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl);
|
|
|
|
|
+
|
|
|
|
|
+ targetElWidth = targetEl.prop('offsetWidth');
|
|
|
|
|
+ targetElHeight = targetEl.prop('offsetHeight');
|
|
|
|
|
+
|
|
|
|
|
+ var shiftWidth = {
|
|
|
|
|
+ center: function() {
|
|
|
|
|
+ return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
|
|
|
|
|
+ },
|
|
|
|
|
+ left: function() {
|
|
|
|
|
+ return hostElPos.left;
|
|
|
|
|
+ },
|
|
|
|
|
+ right: function() {
|
|
|
|
|
+ return hostElPos.left + hostElPos.width;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ var shiftHeight = {
|
|
|
|
|
+ center: function() {
|
|
|
|
|
+ return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
|
|
|
|
|
+ },
|
|
|
|
|
+ top: function() {
|
|
|
|
|
+ return hostElPos.top;
|
|
|
|
|
+ },
|
|
|
|
|
+ bottom: function() {
|
|
|
|
|
+ return hostElPos.top + hostElPos.height;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ switch (pos0) {
|
|
|
|
|
+ case 'right':
|
|
|
|
|
+ targetElPos = {
|
|
|
|
|
+ top: shiftHeight[pos1](),
|
|
|
|
|
+ left: shiftWidth[pos0]()
|
|
|
|
|
+ };
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 'left':
|
|
|
|
|
+ targetElPos = {
|
|
|
|
|
+ top: shiftHeight[pos1](),
|
|
|
|
|
+ left: hostElPos.left - targetElWidth
|
|
|
|
|
+ };
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 'bottom':
|
|
|
|
|
+ targetElPos = {
|
|
|
|
|
+ top: shiftHeight[pos0](),
|
|
|
|
|
+ left: shiftWidth[pos1]()
|
|
|
|
|
+ };
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ targetElPos = {
|
|
|
|
|
+ top: hostElPos.top - targetElHeight,
|
|
|
|
|
+ left: shiftWidth[pos1]()
|
|
|
|
|
+ };
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return targetElPos;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ }]);
|
|
|
|
|
+
|
|
|
|
|
+angular.module('ui.bootstrap.dateparser', [])
|
|
|
|
|
+
|
|
|
|
|
+.service('dateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, orderByFilter) {
|
|
|
|
|
+ // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
|
|
|
|
|
+ var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
|
|
|
|
|
+
|
|
|
|
|
+ this.parsers = {};
|
|
|
|
|
+
|
|
|
|
|
+ var formatCodeToRegex = {
|
|
|
|
|
+ 'yyyy': {
|
|
|
|
|
+ regex: '\\d{4}',
|
|
|
|
|
+ apply: function(value) { this.year = +value; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'yy': {
|
|
|
|
|
+ regex: '\\d{2}',
|
|
|
|
|
+ apply: function(value) { this.year = +value + 2000; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'y': {
|
|
|
|
|
+ regex: '\\d{1,4}',
|
|
|
|
|
+ apply: function(value) { this.year = +value; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'MMMM': {
|
|
|
|
|
+ regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
|
|
|
|
|
+ apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'MMM': {
|
|
|
|
|
+ regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
|
|
|
|
|
+ apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'MM': {
|
|
|
|
|
+ regex: '0[1-9]|1[0-2]',
|
|
|
|
|
+ apply: function(value) { this.month = value - 1; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'M': {
|
|
|
|
|
+ regex: '[1-9]|1[0-2]',
|
|
|
|
|
+ apply: function(value) { this.month = value - 1; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'dd': {
|
|
|
|
|
+ regex: '[0-2][0-9]{1}|3[0-1]{1}',
|
|
|
|
|
+ apply: function(value) { this.date = +value; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'd': {
|
|
|
|
|
+ regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
|
|
|
|
|
+ apply: function(value) { this.date = +value; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'EEEE': {
|
|
|
|
|
+ regex: $locale.DATETIME_FORMATS.DAY.join('|')
|
|
|
|
|
+ },
|
|
|
|
|
+ 'EEE': {
|
|
|
|
|
+ regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
|
|
|
|
|
+ },
|
|
|
|
|
+ 'HH': {
|
|
|
|
|
+ regex: '(?:0|1)[0-9]|2[0-3]',
|
|
|
|
|
+ apply: function(value) { this.hours = +value; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'hh': {
|
|
|
|
|
+ regex: '0[0-9]|1[0-2]',
|
|
|
|
|
+ apply: function(value) { this.hours = +value; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'H': {
|
|
|
|
|
+ regex: '1?[0-9]|2[0-3]',
|
|
|
|
|
+ apply: function(value) { this.hours = +value; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'h': {
|
|
|
|
|
+ regex: '[0-9]|1[0-2]',
|
|
|
|
|
+ apply: function(value) { this.hours = +value; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'mm': {
|
|
|
|
|
+ regex: '[0-5][0-9]',
|
|
|
|
|
+ apply: function(value) { this.minutes = +value; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'm': {
|
|
|
|
|
+ regex: '[0-9]|[1-5][0-9]',
|
|
|
|
|
+ apply: function(value) { this.minutes = +value; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'sss': {
|
|
|
|
|
+ regex: '[0-9][0-9][0-9]',
|
|
|
|
|
+ apply: function(value) { this.milliseconds = +value; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'ss': {
|
|
|
|
|
+ regex: '[0-5][0-9]',
|
|
|
|
|
+ apply: function(value) { this.seconds = +value; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 's': {
|
|
|
|
|
+ regex: '[0-9]|[1-5][0-9]',
|
|
|
|
|
+ apply: function(value) { this.seconds = +value; }
|
|
|
|
|
+ },
|
|
|
|
|
+ 'a': {
|
|
|
|
|
+ regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
|
|
|
|
|
+ apply: function(value) {
|
|
|
|
|
+ if (this.hours === 12) {
|
|
|
|
|
+ this.hours = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (value === 'PM') {
|
|
|
|
|
+ this.hours += 12;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ function createParser(format) {
|
|
|
|
|
+ var map = [], regex = format.split('');
|
|
|
|
|
+
|
|
|
|
|
+ angular.forEach(formatCodeToRegex, function(data, code) {
|
|
|
|
|
+ var index = format.indexOf(code);
|
|
|
|
|
+
|
|
|
|
|
+ if (index > -1) {
|
|
|
|
|
+ format = format.split('');
|
|
|
|
|
+
|
|
|
|
|
+ regex[index] = '(' + data.regex + ')';
|
|
|
|
|
+ format[index] = '$'; // Custom symbol to define consumed part of format
|
|
|
|
|
+ for (var i = index + 1, n = index + code.length; i < n; i++) {
|
|
|
|
|
+ regex[i] = '';
|
|
|
|
|
+ format[i] = '$';
|
|
|
|
|
+ }
|
|
|
|
|
+ format = format.join('');
|
|
|
|
|
+
|
|
|
|
|
+ map.push({ index: index, apply: data.apply });
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ regex: new RegExp('^' + regex.join('') + '$'),
|
|
|
|
|
+ map: orderByFilter(map, 'index')
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.parse = function(input, format, baseDate) {
|
|
|
|
|
+ if (!angular.isString(input) || !format) {
|
|
|
|
|
+ return input;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ format = $locale.DATETIME_FORMATS[format] || format;
|
|
|
|
|
+ format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
|
|
|
|
|
+
|
|
|
|
|
+ if (!this.parsers[format]) {
|
|
|
|
|
+ this.parsers[format] = createParser(format);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var parser = this.parsers[format],
|
|
|
|
|
+ regex = parser.regex,
|
|
|
|
|
+ map = parser.map,
|
|
|
|
|
+ results = input.match(regex);
|
|
|
|
|
+
|
|
|
|
|
+ if (results && results.length) {
|
|
|
|
|
+ var fields, dt;
|
|
|
|
|
+ if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
|
|
|
|
|
+ fields = {
|
|
|
|
|
+ year: baseDate.getFullYear(),
|
|
|
|
|
+ month: baseDate.getMonth(),
|
|
|
|
|
+ date: baseDate.getDate(),
|
|
|
|
|
+ hours: baseDate.getHours(),
|
|
|
|
|
+ minutes: baseDate.getMinutes(),
|
|
|
|
|
+ seconds: baseDate.getSeconds(),
|
|
|
|
|
+ milliseconds: baseDate.getMilliseconds()
|
|
|
|
|
+ };
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (baseDate) {
|
|
|
|
|
+ $log.warn('dateparser:', 'baseDate is not a valid date');
|
|
|
|
|
+ }
|
|
|
|
|
+ fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (var i = 1, n = results.length; i < n; i++) {
|
|
|
|
|
+ var mapper = map[i-1];
|
|
|
|
|
+ if (mapper.apply) {
|
|
|
|
|
+ mapper.apply.call(fields, results[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (isValid(fields.year, fields.month, fields.date)) {
|
|
|
|
|
+ dt = new Date(fields.year, fields.month, fields.date,
|
|
|
|
|
+ fields.hours, fields.minutes, fields.seconds,
|
|
|
|
|
+ fields.milliseconds || 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return dt;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Check if date is valid for specific month (and year for February).
|
|
|
|
|
+ // Month: 0 = Jan, 1 = Feb, etc
|
|
|
|
|
+ function isValid(year, month, date) {
|
|
|
|
|
+ if (date < 1) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (month === 1 && date > 28) {
|
|
|
|
|
+ return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (month === 3 || month === 5 || month === 8 || month === 10) {
|
|
|
|
|
+ return date < 31;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+}]);
|
|
|
|
|
+
|
|
|
|
|
+angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position'])
|
|
|
|
|
+
|
|
|
|
|
+.value('$datepickerSuppressError', false)
|
|
|
|
|
+
|
|
|
|
|
+.constant('datepickerConfig', {
|
|
|
|
|
+ formatDay: 'dd',
|
|
|
|
|
+ formatMonth: 'MMMM',
|
|
|
|
|
+ formatYear: 'yyyy',
|
|
|
|
|
+ formatDayHeader: 'EEE',
|
|
|
|
|
+ formatDayTitle: 'MMMM yyyy',
|
|
|
|
|
+ formatMonthTitle: 'yyyy',
|
|
|
|
|
+ datepickerMode: 'day',
|
|
|
|
|
+ minMode: 'day',
|
|
|
|
|
+ maxMode: 'year',
|
|
|
|
|
+ showWeeks: true,
|
|
|
|
|
+ startingDay: 0,
|
|
|
|
|
+ yearRange: 20,
|
|
|
|
|
+ minDate: null,
|
|
|
|
|
+ maxDate: null,
|
|
|
|
|
+ shortcutPropagation: false
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'datepickerConfig', '$datepickerSuppressError', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError) {
|
|
|
|
|
+ var self = this,
|
|
|
|
|
+ ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl;
|
|
|
|
|
+
|
|
|
|
|
+ // Modes chain
|
|
|
|
|
+ this.modes = ['day', 'month', 'year'];
|
|
|
|
|
+
|
|
|
|
|
+ // Configuration attributes
|
|
|
|
|
+ angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle',
|
|
|
|
|
+ 'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) {
|
|
|
|
|
+ self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key];
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Watchable date attributes
|
|
|
|
|
+ angular.forEach(['minDate', 'maxDate'], function(key) {
|
|
|
|
|
+ if ($attrs[key]) {
|
|
|
|
|
+ $scope.$parent.$watch($parse($attrs[key]), function(value) {
|
|
|
|
|
+ self[key] = value ? new Date(value) : null;
|
|
|
|
|
+ self.refreshView();
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ angular.forEach(['minMode', 'maxMode'], function(key) {
|
|
|
|
|
+ if ($attrs[key]) {
|
|
|
|
|
+ $scope.$parent.$watch($parse($attrs[key]), function(value) {
|
|
|
|
|
+ self[key] = angular.isDefined(value) ? value : $attrs[key];
|
|
|
|
|
+ $scope[key] = self[key];
|
|
|
|
|
+ if ((key == 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key])) || (key == 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key]))) {
|
|
|
|
|
+ $scope.datepickerMode = self[key];
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self[key] = datepickerConfig[key] || null;
|
|
|
|
|
+ $scope[key] = self[key];
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
|
|
|
|
|
+ $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
|
|
|
|
|
+
|
|
|
|
|
+ if (angular.isDefined($attrs.initDate)) {
|
|
|
|
|
+ this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date();
|
|
|
|
|
+ $scope.$parent.$watch($attrs.initDate, function(initDate) {
|
|
|
|
|
+ if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
|
|
|
|
|
+ self.activeDate = initDate;
|
|
|
|
|
+ self.refreshView();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.activeDate = new Date();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $scope.isActive = function(dateObject) {
|
|
|
|
|
+ if (self.compare(dateObject.date, self.activeDate) === 0) {
|
|
|
|
|
+ $scope.activeDateId = dateObject.uid;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ this.init = function(ngModelCtrl_) {
|
|
|
|
|
+ ngModelCtrl = ngModelCtrl_;
|
|
|
|
|
+
|
|
|
|
|
+ ngModelCtrl.$render = function() {
|
|
|
|
|
+ self.render();
|
|
|
|
|
+ };
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ this.render = function() {
|
|
|
|
|
+ if (ngModelCtrl.$viewValue) {
|
|
|
|
|
+ var date = new Date(ngModelCtrl.$viewValue),
|
|
|
|
|
+ isValid = !isNaN(date);
|
|
|
|
|
+
|
|
|
|
|
+ if (isValid) {
|
|
|
|
|
+ this.activeDate = date;
|
|
|
|
|
+ } else if (!$datepickerSuppressError) {
|
|
|
|
|
+ $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ this.refreshView();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ this.refreshView = function() {
|
|
|
|
|
+ if (this.element) {
|
|
|
|
|
+ this._refreshView();
|
|
|
|
|
+
|
|
|
|
|
+ var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
|
|
|
|
|
+ ngModelCtrl.$setValidity('dateDisabled', !date || (this.element && !this.isDisabled(date)));
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ this.createDateObject = function(date, format) {
|
|
|
|
|
+ var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
|
|
|
|
|
+ return {
|
|
|
|
|
+ date: date,
|
|
|
|
|
+ label: dateFilter(date, format),
|
|
|
|
|
+ selected: model && this.compare(date, model) === 0,
|
|
|
|
|
+ disabled: this.isDisabled(date),
|
|
|
|
|
+ current: this.compare(date, new Date()) === 0,
|
|
|
|
|
+ customClass: this.customClass(date)
|
|
|
|
|
+ };
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ this.isDisabled = function(date) {
|
|
|
|
|
+ return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode})));
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ this.customClass = function(date) {
|
|
|
|
|
+ return $scope.customClass({date: date, mode: $scope.datepickerMode});
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Split array into smaller arrays
|
|
|
|
|
+ this.split = function(arr, size) {
|
|
|
|
|
+ var arrays = [];
|
|
|
|
|
+ while (arr.length > 0) {
|
|
|
|
|
+ arrays.push(arr.splice(0, size));
|
|
|
|
|
+ }
|
|
|
|
|
+ return arrays;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Fix a hard-reprodusible bug with timezones
|
|
|
|
|
+ // The bug depends on OS, browser, current timezone and current date
|
|
|
|
|
+ // i.e.
|
|
|
|
|
+ // var date = new Date(2014, 0, 1);
|
|
|
|
|
+ // console.log(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours());
|
|
|
|
|
+ // can result in "2013 11 31 23" because of the bug.
|
|
|
|
|
+ this.fixTimeZone = function(date) {
|
|
|
|
|
+ var hours = date.getHours();
|
|
|
|
|
+ date.setHours(hours === 23 ? hours + 2 : 0);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ $scope.select = function(date) {
|
|
|
|
|
+ if ($scope.datepickerMode === self.minMode) {
|
|
|
|
|
+ var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0);
|
|
|
|
|
+ dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
|
|
|
|
|
+ ngModelCtrl.$setViewValue(dt);
|
|
|
|
|
+ ngModelCtrl.$render();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self.activeDate = date;
|
|
|
|
|
+ $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ $scope.move = function(direction) {
|
|
|
|
|
+ var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
|
|
|
|
|
+ month = self.activeDate.getMonth() + direction * (self.step.months || 0);
|
|
|
|
|
+ self.activeDate.setFullYear(year, month, 1);
|
|
|
|
|
+ self.refreshView();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ $scope.toggleMode = function(direction) {
|
|
|
|
|
+ direction = direction || 1;
|
|
|
|
|
+
|
|
|
|
|
+ if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Key event mapper
|
|
|
|
|
+ $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
|
|
|
|
|
+
|
|
|
|
|
+ var focusElement = function() {
|
|
|
|
|
+ self.element[0].focus();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Listen for focus requests from popup directive
|
|
|
|
|
+ $scope.$on('datepicker.focus', focusElement);
|
|
|
|
|
+
|
|
|
|
|
+ $scope.keydown = function(evt) {
|
|
|
|
|
+ var key = $scope.keys[evt.which];
|
|
|
|
|
+
|
|
|
|
|
+ if (!key || evt.shiftKey || evt.altKey) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ evt.preventDefault();
|
|
|
|
|
+ if (!self.shortcutPropagation) {
|
|
|
|
|
+ evt.stopPropagation();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (key === 'enter' || key === 'space') {
|
|
|
|
|
+ if (self.isDisabled(self.activeDate)) {
|
|
|
|
|
+ return; // do nothing
|
|
|
|
|
+ }
|
|
|
|
|
+ $scope.select(self.activeDate);
|
|
|
|
|
+ focusElement();
|
|
|
|
|
+ } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
|
|
|
|
|
+ $scope.toggleMode(key === 'up' ? 1 : -1);
|
|
|
|
|
+ focusElement();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self.handleKeyDown(key, evt);
|
|
|
|
|
+ self.refreshView();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+}])
|
|
|
|
|
+
|
|
|
|
|
+.directive('datepicker', function() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ restrict: 'EA',
|
|
|
|
|
+ replace: true,
|
|
|
|
|
+ templateUrl: function(element, attrs) {
|
|
|
|
|
+ return attrs.templateUrl || 'template/datepicker/datepicker.html';
|
|
|
|
|
+ },
|
|
|
|
|
+ scope: {
|
|
|
|
|
+ datepickerMode: '=?',
|
|
|
|
|
+ dateDisabled: '&',
|
|
|
|
|
+ customClass: '&',
|
|
|
|
|
+ shortcutPropagation: '&?'
|
|
|
|
|
+ },
|
|
|
|
|
+ require: ['datepicker', '^ngModel'],
|
|
|
|
|
+ controller: 'DatepickerController',
|
|
|
|
|
+ controllerAs: 'datepicker',
|
|
|
|
|
+ link: function(scope, element, attrs, ctrls) {
|
|
|
|
|
+ var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
|
|
|
|
|
+
|
|
|
|
|
+ datepickerCtrl.init(ngModelCtrl);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+.directive('daypicker', ['dateFilter', function(dateFilter) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ restrict: 'EA',
|
|
|
|
|
+ replace: true,
|
|
|
|
|
+ templateUrl: 'template/datepicker/day.html',
|
|
|
|
|
+ require: '^datepicker',
|
|
|
|
|
+ link: function(scope, element, attrs, ctrl) {
|
|
|
|
|
+ scope.showWeeks = ctrl.showWeeks;
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.step = { months: 1 };
|
|
|
|
|
+ ctrl.element = element;
|
|
|
|
|
+
|
|
|
|
|
+ var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
|
|
|
+ function getDaysInMonth(year, month) {
|
|
|
|
|
+ return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function getDates(startDate, n) {
|
|
|
|
|
+ var dates = new Array(n), current = new Date(startDate), i = 0, date;
|
|
|
|
|
+ while (i < n) {
|
|
|
|
|
+ date = new Date(current);
|
|
|
|
|
+ ctrl.fixTimeZone(date);
|
|
|
|
|
+ dates[i++] = date;
|
|
|
|
|
+ current.setDate(current.getDate() + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ return dates;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ctrl._refreshView = function() {
|
|
|
|
|
+ var year = ctrl.activeDate.getFullYear(),
|
|
|
|
|
+ month = ctrl.activeDate.getMonth(),
|
|
|
|
|
+ firstDayOfMonth = new Date(year, month, 1),
|
|
|
|
|
+ difference = ctrl.startingDay - firstDayOfMonth.getDay(),
|
|
|
|
|
+ numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
|
|
|
|
|
+ firstDate = new Date(firstDayOfMonth);
|
|
|
|
|
+
|
|
|
|
|
+ if (numDisplayedFromPreviousMonth > 0) {
|
|
|
|
|
+ firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 42 is the number of days on a six-month calendar
|
|
|
|
|
+ var days = getDates(firstDate, 42);
|
|
|
|
|
+ for (var i = 0; i < 42; i ++) {
|
|
|
|
|
+ days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), {
|
|
|
|
|
+ secondary: days[i].getMonth() !== month,
|
|
|
|
|
+ uid: scope.uniqueId + '-' + i
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ scope.labels = new Array(7);
|
|
|
|
|
+ for (var j = 0; j < 7; j++) {
|
|
|
|
|
+ scope.labels[j] = {
|
|
|
|
|
+ abbr: dateFilter(days[j].date, ctrl.formatDayHeader),
|
|
|
|
|
+ full: dateFilter(days[j].date, 'EEEE')
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle);
|
|
|
|
|
+ scope.rows = ctrl.split(days, 7);
|
|
|
|
|
+
|
|
|
|
|
+ if (scope.showWeeks) {
|
|
|
|
|
+ scope.weekNumbers = [];
|
|
|
|
|
+ var thursdayIndex = (4 + 7 - ctrl.startingDay) % 7,
|
|
|
|
|
+ numWeeks = scope.rows.length;
|
|
|
|
|
+ for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
|
|
|
|
|
+ scope.weekNumbers.push(
|
|
|
|
|
+ getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.compare = function(date1, date2) {
|
|
|
|
|
+ return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ function getISO8601WeekNumber(date) {
|
|
|
|
|
+ var checkDate = new Date(date);
|
|
|
|
|
+ checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
|
|
|
|
|
+ var time = checkDate.getTime();
|
|
|
|
|
+ checkDate.setMonth(0); // Compare with Jan 1
|
|
|
|
|
+ checkDate.setDate(1);
|
|
|
|
|
+ return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.handleKeyDown = function(key, evt) {
|
|
|
|
|
+ var date = ctrl.activeDate.getDate();
|
|
|
|
|
+
|
|
|
|
|
+ if (key === 'left') {
|
|
|
|
|
+ date = date - 1; // up
|
|
|
|
|
+ } else if (key === 'up') {
|
|
|
|
|
+ date = date - 7; // down
|
|
|
|
|
+ } else if (key === 'right') {
|
|
|
|
|
+ date = date + 1; // down
|
|
|
|
|
+ } else if (key === 'down') {
|
|
|
|
|
+ date = date + 7;
|
|
|
|
|
+ } else if (key === 'pageup' || key === 'pagedown') {
|
|
|
|
|
+ var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
|
|
|
|
|
+ ctrl.activeDate.setMonth(month, 1);
|
|
|
|
|
+ date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date);
|
|
|
|
|
+ } else if (key === 'home') {
|
|
|
|
|
+ date = 1;
|
|
|
|
|
+ } else if (key === 'end') {
|
|
|
|
|
+ date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth());
|
|
|
|
|
+ }
|
|
|
|
|
+ ctrl.activeDate.setDate(date);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.refreshView();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+}])
|
|
|
|
|
+
|
|
|
|
|
+.directive('monthpicker', ['dateFilter', function(dateFilter) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ restrict: 'EA',
|
|
|
|
|
+ replace: true,
|
|
|
|
|
+ templateUrl: 'template/datepicker/month.html',
|
|
|
|
|
+ require: '^datepicker',
|
|
|
|
|
+ link: function(scope, element, attrs, ctrl) {
|
|
|
|
|
+ ctrl.step = { years: 1 };
|
|
|
|
|
+ ctrl.element = element;
|
|
|
|
|
+
|
|
|
|
|
+ ctrl._refreshView = function() {
|
|
|
|
|
+ var months = new Array(12),
|
|
|
|
|
+ year = ctrl.activeDate.getFullYear(),
|
|
|
|
|
+ date;
|
|
|
|
|
+
|
|
|
|
|
+ for (var i = 0; i < 12; i++) {
|
|
|
|
|
+ date = new Date(year, i, 1);
|
|
|
|
|
+ ctrl.fixTimeZone(date);
|
|
|
|
|
+ months[i] = angular.extend(ctrl.createDateObject(date, ctrl.formatMonth), {
|
|
|
|
|
+ uid: scope.uniqueId + '-' + i
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle);
|
|
|
|
|
+ scope.rows = ctrl.split(months, 3);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.compare = function(date1, date2) {
|
|
|
|
|
+ return new Date(date1.getFullYear(), date1.getMonth()) - new Date(date2.getFullYear(), date2.getMonth());
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.handleKeyDown = function(key, evt) {
|
|
|
|
|
+ var date = ctrl.activeDate.getMonth();
|
|
|
|
|
+
|
|
|
|
|
+ if (key === 'left') {
|
|
|
|
|
+ date = date - 1; // up
|
|
|
|
|
+ } else if (key === 'up') {
|
|
|
|
|
+ date = date - 3; // down
|
|
|
|
|
+ } else if (key === 'right') {
|
|
|
|
|
+ date = date + 1; // down
|
|
|
|
|
+ } else if (key === 'down') {
|
|
|
|
|
+ date = date + 3;
|
|
|
|
|
+ } else if (key === 'pageup' || key === 'pagedown') {
|
|
|
|
|
+ var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
|
|
|
|
|
+ ctrl.activeDate.setFullYear(year);
|
|
|
|
|
+ } else if (key === 'home') {
|
|
|
|
|
+ date = 0;
|
|
|
|
|
+ } else if (key === 'end') {
|
|
|
|
|
+ date = 11;
|
|
|
|
|
+ }
|
|
|
|
|
+ ctrl.activeDate.setMonth(date);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.refreshView();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+}])
|
|
|
|
|
+
|
|
|
|
|
+.directive('yearpicker', ['dateFilter', function(dateFilter) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ restrict: 'EA',
|
|
|
|
|
+ replace: true,
|
|
|
|
|
+ templateUrl: 'template/datepicker/year.html',
|
|
|
|
|
+ require: '^datepicker',
|
|
|
|
|
+ link: function(scope, element, attrs, ctrl) {
|
|
|
|
|
+ var range = ctrl.yearRange;
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.step = { years: range };
|
|
|
|
|
+ ctrl.element = element;
|
|
|
|
|
+
|
|
|
|
|
+ function getStartingYear( year ) {
|
|
|
|
|
+ return parseInt((year - 1) / range, 10) * range + 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ctrl._refreshView = function() {
|
|
|
|
|
+ var years = new Array(range), date;
|
|
|
|
|
+
|
|
|
|
|
+ for (var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++) {
|
|
|
|
|
+ date = new Date(start + i, 0, 1);
|
|
|
|
|
+ ctrl.fixTimeZone(date);
|
|
|
|
|
+ years[i] = angular.extend(ctrl.createDateObject(date, ctrl.formatYear), {
|
|
|
|
|
+ uid: scope.uniqueId + '-' + i
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ scope.title = [years[0].label, years[range - 1].label].join(' - ');
|
|
|
|
|
+ scope.rows = ctrl.split(years, 5);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.compare = function(date1, date2) {
|
|
|
|
|
+ return date1.getFullYear() - date2.getFullYear();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.handleKeyDown = function(key, evt) {
|
|
|
|
|
+ var date = ctrl.activeDate.getFullYear();
|
|
|
|
|
+
|
|
|
|
|
+ if (key === 'left') {
|
|
|
|
|
+ date = date - 1; // up
|
|
|
|
|
+ } else if (key === 'up') {
|
|
|
|
|
+ date = date - 5; // down
|
|
|
|
|
+ } else if (key === 'right') {
|
|
|
|
|
+ date = date + 1; // down
|
|
|
|
|
+ } else if (key === 'down') {
|
|
|
|
|
+ date = date + 5;
|
|
|
|
|
+ } else if (key === 'pageup' || key === 'pagedown') {
|
|
|
|
|
+ date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years;
|
|
|
|
|
+ } else if (key === 'home') {
|
|
|
|
|
+ date = getStartingYear(ctrl.activeDate.getFullYear());
|
|
|
|
|
+ } else if (key === 'end') {
|
|
|
|
|
+ date = getStartingYear(ctrl.activeDate.getFullYear()) + range - 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ ctrl.activeDate.setFullYear(date);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.refreshView();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+}])
|
|
|
|
|
+
|
|
|
|
|
+.constant('datepickerPopupConfig', {
|
|
|
|
|
+ datepickerPopup: 'yyyy-MM-dd',
|
|
|
|
|
+ datepickerPopupTemplateUrl: 'template/datepicker/popup.html',
|
|
|
|
|
+ datepickerTemplateUrl: 'template/datepicker/datepicker.html',
|
|
|
|
|
+ html5Types: {
|
|
|
|
|
+ date: 'yyyy-MM-dd',
|
|
|
|
|
+ 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
|
|
|
|
|
+ 'month': 'yyyy-MM'
|
|
|
|
|
+ },
|
|
|
|
|
+ currentText: 'Today',
|
|
|
|
|
+ clearText: 'Clear',
|
|
|
|
|
+ closeText: 'Done',
|
|
|
|
|
+ closeOnDateSelection: true,
|
|
|
|
|
+ appendToBody: false,
|
|
|
|
|
+ showButtonBar: true,
|
|
|
|
|
+ onOpenFocus: true
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+.directive('datepickerPopup', ['$compile', '$parse', '$document', '$rootScope', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', '$timeout',
|
|
|
|
|
+function($compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ restrict: 'EA',
|
|
|
|
|
+ require: 'ngModel',
|
|
|
|
|
+ scope: {
|
|
|
|
|
+ isOpen: '=?',
|
|
|
|
|
+ currentText: '@',
|
|
|
|
|
+ clearText: '@',
|
|
|
|
|
+ closeText: '@',
|
|
|
|
|
+ dateDisabled: '&',
|
|
|
|
|
+ customClass: '&'
|
|
|
|
|
+ },
|
|
|
|
|
+ link: function(scope, element, attrs, ngModel) {
|
|
|
|
|
+ var dateFormat,
|
|
|
|
|
+ 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,
|
|
|
|
|
+ cache = {};
|
|
|
|
|
+
|
|
|
|
|
+ scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
|
|
|
|
|
+
|
|
|
|
|
+ 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()));
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ var isHtml5DateInput = false;
|
|
|
|
|
+ if (datepickerPopupConfig.html5Types[attrs.type]) {
|
|
|
|
|
+ dateFormat = datepickerPopupConfig.html5Types[attrs.type];
|
|
|
|
|
+ isHtml5DateInput = true;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ dateFormat = attrs.datepickerPopup || datepickerPopupConfig.datepickerPopup;
|
|
|
|
|
+ attrs.$observe('datepickerPopup', 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('datepickerPopup must have a date format specified.');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!dateFormat) {
|
|
|
|
|
+ throw new Error('datepickerPopup must have a date format specified.');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (isHtml5DateInput && attrs.datepickerPopup) {
|
|
|
|
|
+ throw new Error('HTML5 date input types do not support custom formats.');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // popup element used to display calendar
|
|
|
|
|
+ var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>');
|
|
|
|
|
+ popupEl.attr({
|
|
|
|
|
+ 'ng-model': 'date',
|
|
|
|
|
+ 'ng-change': 'dateSelection(date)',
|
|
|
|
|
+ 'template-url': datepickerPopupTemplateUrl
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ function cameltoDash(string) {
|
|
|
|
|
+ return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // datepicker element
|
|
|
|
|
+ var 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 (attrs.datepickerOptions) {
|
|
|
|
|
+ var options = scope.$parent.$eval(attrs.datepickerOptions);
|
|
|
|
|
+ if (options && options.initDate) {
|
|
|
|
|
+ scope.initDate = options.initDate;
|
|
|
|
|
+ datepickerEl.attr('init-date', 'initDate');
|
|
|
|
|
+ delete options.initDate;
|
|
|
|
|
+ }
|
|
|
|
|
+ angular.forEach(options, function(value, option) {
|
|
|
|
|
+ datepickerEl.attr( cameltoDash(option), value );
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ scope.watchData = {};
|
|
|
|
|
+ angular.forEach(['minMode', 'maxMode', 'minDate', 'maxDate', 'datepickerMode', 'initDate', 'shortcutPropagation'], function(key) {
|
|
|
|
|
+ if (attrs[key]) {
|
|
|
|
|
+ var getAttribute = $parse(attrs[key]);
|
|
|
|
|
+ scope.$parent.$watch(getAttribute, function(value) {
|
|
|
|
|
+ scope.watchData[key] = value;
|
|
|
|
|
+ if (key === 'minDate' || key === 'maxDate') {
|
|
|
|
|
+ cache[key] = new Date(value);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
|
|
|
|
|
+
|
|
|
|
|
+ // Propagate changes from datepicker to outside
|
|
|
|
|
+ if (key === 'datepickerMode') {
|
|
|
|
|
+ var setAttribute = getAttribute.assign;
|
|
|
|
|
+ scope.$watch('watchData.' + key, function(value, oldvalue) {
|
|
|
|
|
+ if (angular.isFunction(setAttribute) && value !== oldvalue) {
|
|
|
|
|
+ setAttribute(scope.$parent, value);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (attrs.dateDisabled) {
|
|
|
|
|
+ datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (attrs.showWeeks) {
|
|
|
|
|
+ datepickerEl.attr('show-weeks', attrs.showWeeks);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (attrs.customClass) {
|
|
|
|
|
+ datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function parseDate(viewValue) {
|
|
|
|
|
+ if (angular.isNumber(viewValue)) {
|
|
|
|
|
+ // presumably timestamp to date object
|
|
|
|
|
+ viewValue = new Date(viewValue);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!viewValue) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ } else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
|
|
|
|
|
+ return viewValue;
|
|
|
|
|
+ } else if (angular.isString(viewValue)) {
|
|
|
|
|
+ var date = dateParser.parse(viewValue, dateFormat, scope.date);
|
|
|
|
|
+ if (isNaN(date)) {
|
|
|
|
|
+ return undefined;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return date;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return 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;
|
|
|
|
|
+ } else if (angular.isDate(value) && !isNaN(value)) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ } else if (angular.isString(value)) {
|
|
|
|
|
+ var date = dateParser.parse(value, dateFormat);
|
|
|
|
|
+ return !isNaN(date);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 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) {
|
|
|
|
|
+ scope.date = value;
|
|
|
|
|
+ return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ngModel.$formatters.push(function(value) {
|
|
|
|
|
+ scope.date = value;
|
|
|
|
|
+ return value;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Inner change
|
|
|
|
|
+ scope.dateSelection = function(dt) {
|
|
|
|
|
+ if (angular.isDefined(dt)) {
|
|
|
|
|
+ scope.date = dt;
|
|
|
|
|
+ }
|
|
|
|
|
+ var date = scope.date ? dateFilter(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();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Detect changes in the view from the text box
|
|
|
|
|
+ ngModel.$viewChangeListeners.push(function() {
|
|
|
|
|
+ scope.date = dateParser.parse(ngModel.$viewValue, dateFormat, scope.date);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ var documentClickBind = function(event) {
|
|
|
|
|
+ if (scope.isOpen && !(element[0].contains(event.target) || popupEl[0].contains(event.target))) {
|
|
|
|
|
+ scope.$apply(function() {
|
|
|
|
|
+ scope.isOpen = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ var inputKeydownBind = function(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;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ element.bind('keydown', inputKeydownBind);
|
|
|
|
|
+
|
|
|
|
|
+ scope.keydown = function(evt) {
|
|
|
|
|
+ if (evt.which === 27) {
|
|
|
|
|
+ scope.isOpen = false;
|
|
|
|
|
+ element[0].focus();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ scope.$watch('isOpen', function(value) {
|
|
|
|
|
+ if (value) {
|
|
|
|
|
+ scope.position = appendToBody ? $position.offset(element) : $position.position(element);
|
|
|
|
|
+ scope.position.top = scope.position.top + element.prop('offsetHeight');
|
|
|
|
|
+
|
|
|
|
|
+ $timeout(function() {
|
|
|
|
|
+ if (onOpenFocus) {
|
|
|
|
|
+ scope.$broadcast('datepicker.focus');
|
|
|
|
|
+ }
|
|
|
|
|
+ $document.bind('click', documentClickBind);
|
|
|
|
|
+ }, 0, false);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $document.unbind('click', documentClickBind);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ scope.select = function(date) {
|
|
|
|
|
+ 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() {
|
|
|
|
|
+ scope.isOpen = false;
|
|
|
|
|
+ element[0].focus();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ var $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.unbind('keydown', inputKeydownBind);
|
|
|
|
|
+ $document.unbind('click', documentClickBind);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+}])
|
|
|
|
|
+
|
|
|
|
|
+.directive('datepickerPopupWrap', function() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ restrict:'EA',
|
|
|
|
|
+ replace: true,
|
|
|
|
|
+ transclude: true,
|
|
|
|
|
+ templateUrl: function(element, attrs) {
|
|
|
|
|
+ return attrs.templateUrl || 'template/datepicker/popup.html';
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @ngdoc overview
|
|
|
|
|
+ * @name ui.bootstrap.tabs
|
|
|
|
|
+ *
|
|
|
|
|
+ * @description
|
|
|
|
|
+ * AngularJS version of the tabs directive.
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+angular.module('ui.bootstrap.tabs', [])
|
|
|
|
|
+
|
|
|
|
|
+.controller('TabsetController', ['$scope', function TabsetCtrl($scope) {
|
|
|
|
|
+ var ctrl = this,
|
|
|
|
|
+ tabs = ctrl.tabs = $scope.tabs = [];
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.select = function(selectedTab) {
|
|
|
|
|
+ angular.forEach(tabs, function(tab) {
|
|
|
|
|
+ if (tab.active && tab !== selectedTab) {
|
|
|
|
|
+ tab.active = false;
|
|
|
|
|
+ tab.onDeselect();
|
|
|
|
|
+ selectedTab.selectCalled = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ selectedTab.active = true;
|
|
|
|
|
+ // only call select if it has not already been called
|
|
|
|
|
+ if (!selectedTab.selectCalled) {
|
|
|
|
|
+ selectedTab.onSelect();
|
|
|
|
|
+ selectedTab.selectCalled = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.addTab = function addTab(tab) {
|
|
|
|
|
+ tabs.push(tab);
|
|
|
|
|
+ // we can't run the select function on the first tab
|
|
|
|
|
+ // since that would select it twice
|
|
|
|
|
+ if (tabs.length === 1 && tab.active !== false) {
|
|
|
|
|
+ tab.active = true;
|
|
|
|
|
+ } else if (tab.active) {
|
|
|
|
|
+ ctrl.select(tab);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ tab.active = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ ctrl.removeTab = function removeTab(tab) {
|
|
|
|
|
+ var index = tabs.indexOf(tab);
|
|
|
|
|
+ //Select a new tab if the tab to be removed is selected and not destroyed
|
|
|
|
|
+ if (tab.active && tabs.length > 1 && !destroyed) {
|
|
|
|
|
+ //If this is the last tab, select the previous tab. else, the next tab.
|
|
|
|
|
+ var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
|
|
|
|
|
+ ctrl.select(tabs[newActiveIndex]);
|
|
|
|
|
+ }
|
|
|
|
|
+ tabs.splice(index, 1);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ var destroyed;
|
|
|
|
|
+ $scope.$on('$destroy', function() {
|
|
|
|
|
+ destroyed = true;
|
|
|
|
|
+ });
|
|
|
|
|
+}])
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @ngdoc directive
|
|
|
|
|
+ * @name ui.bootstrap.tabs.directive:tabset
|
|
|
|
|
+ * @restrict EA
|
|
|
|
|
+ *
|
|
|
|
|
+ * @description
|
|
|
|
|
+ * Tabset is the outer container for the tabs directive
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
|
|
|
|
|
+ * @param {boolean=} justified Whether or not to use justified styling for the tabs.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @example
|
|
|
|
|
+<example module="ui.bootstrap">
|
|
|
|
|
+ <file name="index.html">
|
|
|
|
|
+ <tabset>
|
|
|
|
|
+ <tab heading="Tab 1"><b>First</b> Content!</tab>
|
|
|
|
|
+ <tab heading="Tab 2"><i>Second</i> Content!</tab>
|
|
|
|
|
+ </tabset>
|
|
|
|
|
+ <hr />
|
|
|
|
|
+ <tabset vertical="true">
|
|
|
|
|
+ <tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab>
|
|
|
|
|
+ <tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab>
|
|
|
|
|
+ </tabset>
|
|
|
|
|
+ <tabset justified="true">
|
|
|
|
|
+ <tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab>
|
|
|
|
|
+ <tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab>
|
|
|
|
|
+ </tabset>
|
|
|
|
|
+ </file>
|
|
|
|
|
+</example>
|
|
|
|
|
+ */
|
|
|
|
|
+.directive('tabset', function() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ restrict: 'EA',
|
|
|
|
|
+ transclude: true,
|
|
|
|
|
+ replace: true,
|
|
|
|
|
+ scope: {
|
|
|
|
|
+ type: '@'
|
|
|
|
|
+ },
|
|
|
|
|
+ controller: 'TabsetController',
|
|
|
|
|
+ templateUrl: '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;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @ngdoc directive
|
|
|
|
|
+ * @name ui.bootstrap.tabs.directive:tab
|
|
|
|
|
+ * @restrict EA
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}.
|
|
|
|
|
+ * @param {string=} select An expression to evaluate when the tab is selected.
|
|
|
|
|
+ * @param {boolean=} active A binding, telling whether or not this tab is selected.
|
|
|
|
|
+ * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @description
|
|
|
|
|
+ * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @example
|
|
|
|
|
+<example module="ui.bootstrap">
|
|
|
|
|
+ <file name="index.html">
|
|
|
|
|
+ <div ng-controller="TabsDemoCtrl">
|
|
|
|
|
+ <button class="btn btn-small" ng-click="items[0].active = true">
|
|
|
|
|
+ Select item 1, using active binding
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
|
|
|
|
|
+ Enable/disable item 2, using disabled binding
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <br />
|
|
|
|
|
+ <tabset>
|
|
|
|
|
+ <tab heading="Tab 1">First Tab</tab>
|
|
|
|
|
+ <tab select="alertMe()">
|
|
|
|
|
+ <tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
|
|
|
|
|
+ Second Tab, with alert callback and html heading!
|
|
|
|
|
+ </tab>
|
|
|
|
|
+ <tab ng-repeat="item in items"
|
|
|
|
|
+ heading="{{item.title}}"
|
|
|
|
|
+ disabled="item.disabled"
|
|
|
|
|
+ active="item.active">
|
|
|
|
|
+ {{item.content}}
|
|
|
|
|
+ </tab>
|
|
|
|
|
+ </tabset>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </file>
|
|
|
|
|
+ <file name="script.js">
|
|
|
|
|
+ function TabsDemoCtrl($scope) {
|
|
|
|
|
+ $scope.items = [
|
|
|
|
|
+ { title:"Dynamic Title 1", content:"Dynamic Item 0" },
|
|
|
|
|
+ { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $scope.alertMe = function() {
|
|
|
|
|
+ setTimeout(function() {
|
|
|
|
|
+ alert("You've selected the alert tab!");
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+ };
|
|
|
|
|
+ </file>
|
|
|
|
|
+</example>
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @ngdoc directive
|
|
|
|
|
+ * @name ui.bootstrap.tabs.directive:tabHeading
|
|
|
|
|
+ * @restrict EA
|
|
|
|
|
+ *
|
|
|
|
|
+ * @description
|
|
|
|
|
+ * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @example
|
|
|
|
|
+<example module="ui.bootstrap">
|
|
|
|
|
+ <file name="index.html">
|
|
|
|
|
+ <tabset>
|
|
|
|
|
+ <tab>
|
|
|
|
|
+ <tab-heading><b>HTML</b> in my titles?!</tab-heading>
|
|
|
|
|
+ And some content, too!
|
|
|
|
|
+ </tab>
|
|
|
|
|
+ <tab>
|
|
|
|
|
+ <tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
|
|
|
|
|
+ That's right.
|
|
|
|
|
+ </tab>
|
|
|
|
|
+ </tabset>
|
|
|
|
|
+ </file>
|
|
|
|
|
+</example>
|
|
|
|
|
+ */
|
|
|
|
|
+.directive('tab', ['$parse', '$log', function($parse, $log) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ require: '^tabset',
|
|
|
|
|
+ restrict: 'EA',
|
|
|
|
|
+ replace: true,
|
|
|
|
|
+ templateUrl: 'template/tabs/tab.html',
|
|
|
|
|
+ transclude: true,
|
|
|
|
|
+ scope: {
|
|
|
|
|
+ active: '=?',
|
|
|
|
|
+ heading: '@',
|
|
|
|
|
+ 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
|
|
|
|
|
+ },
|
|
|
|
|
+ link: function(scope, elm, attrs, tabsetCtrl, transclude) {
|
|
|
|
|
+ scope.$watch('active', function(active) {
|
|
|
|
|
+ if (active) {
|
|
|
|
|
+ tabsetCtrl.select(scope);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ scope.disabled = false;
|
|
|
|
|
+ if (attrs.disable) {
|
|
|
|
|
+ scope.$parent.$watch($parse(attrs.disable), function(value) {
|
|
|
|
|
+ scope.disabled = !! value;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Deprecation support of "disabled" parameter
|
|
|
|
|
+ // fix(tab): IE9 disabled attr renders grey text on enabled tab #2677
|
|
|
|
|
+ // This code is duplicated from the lines above to make it easy to remove once
|
|
|
|
|
+ // the feature has been completely deprecated
|
|
|
|
|
+ if (attrs.disabled) {
|
|
|
|
|
+ $log.warn('Use of "disabled" attribute has been deprecated, please use "disable"');
|
|
|
|
|
+ scope.$parent.$watch($parse(attrs.disabled), function(value) {
|
|
|
|
|
+ scope.disabled = !! value;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ scope.select = function() {
|
|
|
|
|
+ if (!scope.disabled) {
|
|
|
|
|
+ scope.active = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ tabsetCtrl.addTab(scope);
|
|
|
|
|
+ scope.$on('$destroy', function() {
|
|
|
|
|
+ tabsetCtrl.removeTab(scope);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ //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('tabHeadingTransclude', function() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ restrict: 'A',
|
|
|
|
|
+ require: '^tab',
|
|
|
|
|
+ link: function(scope, elm, attrs, tabCtrl) {
|
|
|
|
|
+ scope.$watch('headingElement', function updateHeadingElement(heading) {
|
|
|
|
|
+ if (heading) {
|
|
|
|
|
+ elm.html('');
|
|
|
|
|
+ elm.append(heading);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+.directive('tabContentTransclude', function() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ restrict: 'A',
|
|
|
|
|
+ require: '^tabset',
|
|
|
|
|
+ link: function(scope, elm, attrs) {
|
|
|
|
|
+ var tab = scope.$eval(attrs.tabContentTransclude);
|
|
|
|
|
+
|
|
|
|
|
+ //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('tab-heading') ||
|
|
|
|
|
+ node.hasAttribute('data-tab-heading') ||
|
|
|
|
|
+ node.hasAttribute('x-tab-heading') ||
|
|
|
|
|
+ node.tagName.toLowerCase() === 'tab-heading' ||
|
|
|
|
|
+ node.tagName.toLowerCase() === 'data-tab-heading' ||
|
|
|
|
|
+ node.tagName.toLowerCase() === 'x-tab-heading'
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
|
|
|
|
|
+ $templateCache.put("template/datepicker/datepicker.html",
|
|
|
|
|
+ "<div ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" +
|
|
|
|
|
+ " <daypicker ng-switch-when=\"day\" tabindex=\"0\"></daypicker>\n" +
|
|
|
|
|
+ " <monthpicker ng-switch-when=\"month\" tabindex=\"0\"></monthpicker>\n" +
|
|
|
|
|
+ " <yearpicker ng-switch-when=\"year\" tabindex=\"0\"></yearpicker>\n" +
|
|
|
|
|
+ "</div>");
|
|
|
|
|
+}]);
|
|
|
|
|
+
|
|
|
|
|
+angular.module("template/datepicker/day.html", []).run(["$templateCache", function($templateCache) {
|
|
|
|
|
+ $templateCache.put("template/datepicker/day.html",
|
|
|
|
|
+ "<table role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
|
|
|
|
|
+ " <thead>\n" +
|
|
|
|
|
+ " <tr>\n" +
|
|
|
|
|
+ " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
|
|
|
|
|
+ " <th colspan=\"{{::5 + showWeeks}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
|
|
|
|
|
+ " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
|
|
|
|
|
+ " </tr>\n" +
|
|
|
|
|
+ " <tr>\n" +
|
|
|
|
|
+ " <th ng-if=\"showWeeks\" class=\"text-center\"></th>\n" +
|
|
|
|
|
+ " <th ng-repeat=\"label in ::labels track by $index\" class=\"text-center\"><small aria-label=\"{{::label.full}}\">{{::label.abbr}}</small></th>\n" +
|
|
|
|
|
+ " </tr>\n" +
|
|
|
|
|
+ " </thead>\n" +
|
|
|
|
|
+ " <tbody>\n" +
|
|
|
|
|
+ " <tr ng-repeat=\"row in rows track by $index\">\n" +
|
|
|
|
|
+ " <td ng-if=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" +
|
|
|
|
|
+ " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{::dt.uid}}\" ng-class=\"::dt.customClass\">\n" +
|
|
|
|
|
+ " <button type=\"button\" style=\"min-width:100%;\" 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>\n" +
|
|
|
|
|
+ " </td>\n" +
|
|
|
|
|
+ " </tr>\n" +
|
|
|
|
|
+ " </tbody>\n" +
|
|
|
|
|
+ "</table>\n" +
|
|
|
|
|
+ "");
|
|
|
|
|
+}]);
|
|
|
|
|
+
|
|
|
|
|
+angular.module("template/datepicker/month.html", []).run(["$templateCache", function($templateCache) {
|
|
|
|
|
+ $templateCache.put("template/datepicker/month.html",
|
|
|
|
|
+ "<table role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
|
|
|
|
|
+ " <thead>\n" +
|
|
|
|
|
+ " <tr>\n" +
|
|
|
|
|
+ " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
|
|
|
|
|
+ " <th><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
|
|
|
|
|
+ " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
|
|
|
|
|
+ " </tr>\n" +
|
|
|
|
|
+ " </thead>\n" +
|
|
|
|
|
+ " <tbody>\n" +
|
|
|
|
|
+ " <tr ng-repeat=\"row in rows track by $index\">\n" +
|
|
|
|
|
+ " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{::dt.uid}}\" ng-class=\"::dt.customClass\">\n" +
|
|
|
|
|
+ " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
|
|
|
|
|
+ " </td>\n" +
|
|
|
|
|
+ " </tr>\n" +
|
|
|
|
|
+ " </tbody>\n" +
|
|
|
|
|
+ "</table>\n" +
|
|
|
|
|
+ "");
|
|
|
|
|
+}]);
|
|
|
|
|
+
|
|
|
|
|
+angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) {
|
|
|
|
|
+ $templateCache.put("template/datepicker/popup.html",
|
|
|
|
|
+ "<ul class=\"dropdown-menu\" ng-if=\"isOpen\" style=\"display: block\" ng-style=\"{top: position.top+'px', left: position.left+'px'}\" ng-keydown=\"keydown($event)\" ng-click=\"$event.stopPropagation()\">\n" +
|
|
|
|
|
+ " <li ng-transclude></li>\n" +
|
|
|
|
|
+ " <li ng-if=\"showButtonBar\" style=\"padding:10px 9px 2px\">\n" +
|
|
|
|
|
+ " <span class=\"btn-group pull-left\">\n" +
|
|
|
|
|
+ " <button type=\"button\" class=\"btn btn-sm btn-info\" ng-click=\"select('today')\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" +
|
|
|
|
|
+ " <button type=\"button\" class=\"btn btn-sm btn-danger\" ng-click=\"select(null)\">{{ getText('clear') }}</button>\n" +
|
|
|
|
|
+ " </span>\n" +
|
|
|
|
|
+ " <button type=\"button\" class=\"btn btn-sm btn-success pull-right\" ng-click=\"close()\">{{ getText('close') }}</button>\n" +
|
|
|
|
|
+ " </li>\n" +
|
|
|
|
|
+ "</ul>\n" +
|
|
|
|
|
+ "");
|
|
|
|
|
+}]);
|
|
|
|
|
+
|
|
|
|
|
+angular.module("template/datepicker/year.html", []).run(["$templateCache", function($templateCache) {
|
|
|
|
|
+ $templateCache.put("template/datepicker/year.html",
|
|
|
|
|
+ "<table role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
|
|
|
|
|
+ " <thead>\n" +
|
|
|
|
|
+ " <tr>\n" +
|
|
|
|
|
+ " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
|
|
|
|
|
+ " <th colspan=\"3\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
|
|
|
|
|
+ " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
|
|
|
|
|
+ " </tr>\n" +
|
|
|
|
|
+ " </thead>\n" +
|
|
|
|
|
+ " <tbody>\n" +
|
|
|
|
|
+ " <tr ng-repeat=\"row in rows track by $index\">\n" +
|
|
|
|
|
+ " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{::dt.uid}}\">\n" +
|
|
|
|
|
+ " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
|
|
|
|
|
+ " </td>\n" +
|
|
|
|
|
+ " </tr>\n" +
|
|
|
|
|
+ " </tbody>\n" +
|
|
|
|
|
+ "</table>\n" +
|
|
|
|
|
+ "");
|
|
|
|
|
+}]);
|
|
|
|
|
+
|
|
|
|
|
+angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
|
|
|
|
|
+ $templateCache.put("template/tabs/tab.html",
|
|
|
|
|
+ "<li ng-class=\"{active: active, disabled: disabled}\">\n" +
|
|
|
|
|
+ " <a href ng-click=\"select()\" tab-heading-transclude>{{heading}}</a>\n" +
|
|
|
|
|
+ "</li>\n" +
|
|
|
|
|
+ "");
|
|
|
|
|
+}]);
|
|
|
|
|
+
|
|
|
|
|
+angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
|
|
|
|
|
+ $templateCache.put("template/tabs/tabset.html",
|
|
|
|
|
+ "<div>\n" +
|
|
|
|
|
+ " <ul class=\"nav nav-{{type || 'tabs'}} nav-tabs-alt\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
|
|
|
|
|
+ " <div class=\"tab-content\">\n" +
|
|
|
|
|
+ " <div class=\"tab-pane\" \n" +
|
|
|
|
|
+ " ng-repeat=\"tab in tabs\" \n" +
|
|
|
|
|
+ " ng-class=\"{active: tab.active}\"\n" +
|
|
|
|
|
+ " tab-content-transclude=\"tab\">\n" +
|
|
|
|
|
+ " </div>\n" +
|
|
|
|
|
+ " </div>\n" +
|
|
|
|
|
+ "</div>\n" +
|
|
|
|
|
+ "");
|
|
|
|
|
+}]);
|