From f93215f4ece9637199689e1614e697bf13c447ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= <torkel.odegaard@gmail.com> Date: Fri, 18 Sep 2015 10:36:47 +0200 Subject: [PATCH] feat(timepicker2): more progress --- public/app/core/utils/rangeutil.ts | 2 +- public/app/features/dashboard/timeSrv.js | 8 +- .../dashboard/timepicker/dropdown.html | 57 +- .../dashboard/timepicker/timepicker.html | 4 +- .../dashboard/timepicker/timepicker.ts | 76 +- public/less/timepicker.less | 49 +- public/vendor/angular-ui/dateparser.js | 212 --- public/vendor/angular-ui/datepicker.js | 823 --------- public/vendor/angular-ui/position.js | 150 -- public/vendor/angular-ui/tabs.js | 293 ---- public/vendor/angular-ui/ui-bootstrap.js | 1476 ----------------- 11 files changed, 38 insertions(+), 3112 deletions(-) delete mode 100644 public/vendor/angular-ui/dateparser.js delete mode 100644 public/vendor/angular-ui/datepicker.js delete mode 100644 public/vendor/angular-ui/position.js delete mode 100644 public/vendor/angular-ui/tabs.js delete mode 100644 public/vendor/angular-ui/ui-bootstrap.js diff --git a/public/app/core/utils/rangeutil.ts b/public/app/core/utils/rangeutil.ts index e4e14b7e606..db34e294628 100644 --- a/public/app/core/utils/rangeutil.ts +++ b/public/app/core/utils/rangeutil.ts @@ -16,8 +16,8 @@ var spans = { var rangeOptions = [ { from: 'now/d', to: 'now/d', display: 'Today', section: 2 }, - { from: 'now/w', to: 'now/w', display: 'This week', section: 2 }, { from: 'now/d', to: 'now', display: 'The day so far', section: 2 }, + { from: 'now/w', to: 'now/w', display: 'This week', section: 2 }, { from: 'now/w', to: 'now', display: 'Week to date', section: 2 }, { from: 'now/M', to: 'now/M', display: 'This month', section: 2 }, { from: 'now/y', to: 'now/y', display: 'This year', section: 2 }, diff --git a/public/app/features/dashboard/timeSrv.js b/public/app/features/dashboard/timeSrv.js index d88b8fd41a4..c956d73e2ab 100644 --- a/public/app/features/dashboard/timeSrv.js +++ b/public/app/features/dashboard/timeSrv.js @@ -23,7 +23,7 @@ define([ this._parseTime(); if(this.dashboard.refresh) { - this.set_interval(this.dashboard.refresh); + this.setAutoRefresh(this.dashboard.refresh); } }; @@ -64,7 +64,7 @@ define([ } }; - this.set_interval = function (interval) { + this.setAutoRefresh = function (interval) { this.dashboard.refresh = interval; if (interval) { var _i = kbn.interval_to_ms(interval); @@ -96,10 +96,10 @@ define([ // disable refresh if we have an absolute time if (_.isString(time.to) && time.to.indexOf('now') === -1) { this.old_refresh = this.dashboard.refresh || this.old_refresh; - this.set_interval(false); + this.setAutoRefresh(false); } else if (this.old_refresh && this.old_refresh !== this.dashboard.refresh) { - this.set_interval(this.old_refresh); + this.setAutoRefresh(this.old_refresh); this.old_refresh = null; } diff --git a/public/app/features/dashboard/timepicker/dropdown.html b/public/app/features/dashboard/timepicker/dropdown.html index 142f256c41c..95cbe3d4c9f 100644 --- a/public/app/features/dashboard/timepicker/dropdown.html +++ b/public/app/features/dashboard/timepicker/dropdown.html @@ -22,9 +22,10 @@ </div> <label class="small">Refreshing every:</label> - <select ng-model="currentRefresh" class='input-large' ng-options="f for f in refreshOptions"></select> + <select ng-model="refresh.value" class='input-medium' ng-options="f.value as f.text for f in refresh.options"> + </select> - <button class="btn btn-inverse pull-right" type="button" ng-click="applyCustomTimeRange()"> + <button class="btn btn-inverse gf-timepicker-btn-apply" type="button" ng-click="ctrl.applyCustom()"> Apply </button> </div> @@ -41,55 +42,3 @@ </div> <div class="clearfix"></div> -<!-- <tabset> --> -<!-- <tab heading="Relative"> --> -<!-- --> -<!-- <div style="float:right; width: 200px" ng-repeat="group in timeOptions"> --> - <!-- <ul> --> - <!-- <li bindonce ng-repeat='option in group'> --> - <!-- <a ng-click="ctrl.setRelativeFilter(option)" bo-text="option.display"></a> --> - <!-- </li> --> - <!-- </ul> --> - <!-- </div> --> -<!-- --> -<!-- </tab> --> -<!-- <tab heading="Absolute"> --> -<!-- --> -<!-- <form name="timeForm" style="margin: 0 20px 20px 20px"> --> - <!-- <div class="gf-timepicker-section"> --> - <!-- <div> --> - <!-- <div class="tight-form last"> --> - <!-- </div> --> - <!-- <label class="small">From:</label> --> - <!-- <input type="text" required class="input-large" ng-model="absolute.from" input-datetime="MMMM Do YYYY, HH:mm:ss.SSS"> --> - <!-- <br> --> - <!-- </div> --> - <!-- <datepicker ng-model="absolute.from" class="gf-timepicker-component" show-weeks="false"></datepicker> --> - <!-- </div> --> - <!-- <div class="gf-timepicker-section"> --> - <!-- <div> --> - <!-- <label class="small">To:</label> --> - <!-- <input type="text" required class="input-large" ng-model="absolute.to" input-datetime="MMMM Do YYYY, HH:mm:ss.SSS"> --> - <!-- <br> --> - <!-- </div> --> - <!-- <datepicker ng-model="absolute.to" class="gf-timepicker-component" show-weeks="false"></datepicker> --> - <!-- </div> --> - <!-- <div class="clearfix"></div> --> - <!-- </form> --> -<!-- </tab> --> -<!-- </tabset> --> -<!-- --> -<!-- <!-- <!&#45;&#45; Auto refresh submenu &#45;&#45;> --> --> -<!-- <li class="dropdown-submenu"> --> - <!-- <a href="#">Auto-Refresh</a> --> - <!-- <ul class="dropdown-menu" ng-class="{'dropdown-submenu-left': refreshMenuLeftSide}"> --> - <!-- <li> --> - <!-- <a ng-click="timeSrv.set_interval(false)">Off</a> --> - <!-- </li> --> - <!-- <li bindonce ng-repeat="interval in panel.refresh_intervals track by $index"> --> - <!-- <a ng-click="timeSrv.set_interval(interval)" bo-text="'Every ' + interval"></a> --> - <!-- </li> --> - <!-- </ul> --> - <!-- </li> --> -<!-- <li><a ng-click="ctrl.customTime()">Custom</a></li> --> -<!-- </div> --> diff --git a/public/app/features/dashboard/timepicker/timepicker.html b/public/app/features/dashboard/timepicker/timepicker.html index d80066848dc..5f10ab22835 100644 --- a/public/app/features/dashboard/timepicker/timepicker.html +++ b/public/app/features/dashboard/timepicker/timepicker.html @@ -1,4 +1,4 @@ -<ul class="nav timepicker-dropdown"> +<ul class="nav gf-timepicker-nav"> <li class="grafana-menu-zoom-out"> <a class='small' ng-click='zoom(2)'> @@ -7,7 +7,7 @@ </li> <li> - <a class="timepicker-dropdown" bs-tooltip="tooltip" data-placement="bottom" ng-click="ctrl.openDropdown()"> + <a bs-tooltip="tooltip" data-placement="bottom" ng-click="ctrl.openDropdown()"> <i class="fa fa-clock-o"></i> <span ng-bind="rangeString"></span> diff --git a/public/app/features/dashboard/timepicker/timepicker.ts b/public/app/features/dashboard/timepicker/timepicker.ts index c9cfc97bb5e..5a488e1f25a 100644 --- a/public/app/features/dashboard/timepicker/timepicker.ts +++ b/public/app/features/dashboard/timepicker/timepicker.ts @@ -54,7 +54,7 @@ export class TimePickerCtrl { this.$scope.dashboard.formatDate(time.to, format); } - this.$scope.absolute = {form: time.from.toDate(), to: time.to.toDate()}; + this.$scope.absoluteJs = {form: time.from.toDate(), to: time.to.toDate()}; this.$scope.timeRaw = timeRaw; this.$scope.tooltip = this.$scope.dashboard.formatDate(time.from) + ' <br>to<br>'; this.$scope.tooltip += this.$scope.dashboard.formatDate(time.to); @@ -66,9 +66,14 @@ export class TimePickerCtrl { openDropdown() { this.$scope.timeOptions = rangeUtil.getRelativeTimesList(this.$scope.panel, this.$scope.rangeString); - this.$scope.currentRefresh = this.$scope.dashboard.refresh || 'off'; - this.$scope.refreshOptions = this.$scope.panel.refresh_intervals; - this.$scope.refreshOptions.unshift('off'); + this.$scope.refresh = { + value: this.$scope.dashboard.refresh, + options: _.map(this.$scope.panel.refresh_intervals, (interval: any) => { + return {text: interval, value: interval}; + }) + }; + + this.$scope.refresh.options.unshift({text: 'off'}); this.$scope.appEvent('show-dash-editor', { src: 'app/features/dashboard/timepicker/dropdown.html', @@ -77,36 +82,14 @@ export class TimePickerCtrl { }); } - customTime() { - // Assume the form is valid since we're setting it to something valid - this.$scope.input.$setValidity("dummy", true); - this.$scope.temptime = angular.copy(this.$scope.time); - this.$scope.temptime.now = this.$scope.panel.now; - - // this.$scope.temptime.from.date.setHours(0, 0, 0, 0); - // this.$scope.temptime.to.date.setHours(0, 0, 0, 0); - - // Date picker needs the date to be at the start of the day - if (new Date().getTimezoneOffset() < 0) { - this.$scope.temptime.from.date = moment(this.$scope.temptime.from.date).add(1, 'days').toDate(); - this.$scope.temptime.to.date = moment(this.$scope.temptime.to.date).add(1, 'days').toDate(); + applyCustom() { + console.log(this.$scope.refresh.value); + if (this.$scope.refresh.value !== this.$scope.dashboard.refresh) { + this.timeSrv.setAutoRefresh(this.$scope.refresh.value); } - this.$scope.appEvent('show-dash-editor', { - src: 'app/features/dashboard/timepicker/custom.html', - scope: this.$scope - }); - } - - setAbsoluteTimeFilter(time) { - // Create filter object - var _filter = _.clone(time); - - if (time.now) { - _filter.to = "now"; - } - - this.timeSrv.setTime(_filter); + this.timeSrv.setTime(this.$scope.timeRaw); + this.$scope.appEvent('hide-dash-editor'); } setRelativeFilter(timespan) { @@ -122,35 +105,6 @@ export class TimePickerCtrl { this.$scope.appEvent('hide-dash-editor'); } - validate(time): any { - // Assume the form is valid. There is a hidden dummy input for invalidating it programatically. - this.$scope.input.$setValidity("dummy", true); - - var _from = this.datepickerToLocal(time.from.date); - var _to = this.datepickerToLocal(time.to.date); - var _t = time; - - if (this.$scope.input.$valid) { - _from.setHours(_t.from.hour, _t.from.minute, _t.from.second, _t.from.millisecond); - _to.setHours(_t.to.hour, _t.to.minute, _t.to.second, _t.to.millisecond); - - // Check that the objects are valid and to is after from - if (isNaN(_from.getTime()) || isNaN(_to.getTime()) || _from.getTime() >= _to.getTime()) { - this.$scope.input.$setValidity("dummy", false); - return false; - } - } else { - return false; - } - - return { from: _from, to: _to, now: time.now }; - } - - datepickerToLocal(date) { - date = moment(date).clone().toDate(); - return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000)).toDate(); - } - } export function settingsDirective() { diff --git a/public/less/timepicker.less b/public/less/timepicker.less index e17d009ca74..d35c90bd0fe 100644 --- a/public/less/timepicker.less +++ b/public/less/timepicker.less @@ -1,36 +1,5 @@ -.timepicker-to-column { - margin-top: 10px; -} - -.timepicker-input input { - outline: 0 !important; - border: 0px !important; - -webkit-box-shadow: 0; - -moz-box-shadow: 0; - box-shadow: 0; - position: relative; -} - -.timepicker-input input::-webkit-outer-spin-button, -.timepicker-input input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -input.timepicker-date { - width: 90px; -} - -input.timepicker-hms { - width: 20px; -} - -input.timepicker-ms { - width: 25px; -} - -div.timepicker-now { - float: right; +.nav.gf-timepicker-nav { + margin-right: 0; } .timepicker-timestring { @@ -38,7 +7,7 @@ div.timepicker-now { } .gf-timepicker-dropdown { - margin: 0px; + margin: 0px 0 15px 0; padding: 10px 20px; float: right; background-color: @grafanaPanelBackground; @@ -49,11 +18,19 @@ div.timepicker-now { width: 300px; float: left; border-right: @grafanaTriggerBorder; - padding: 0 20px 0 20px; + padding: 0 0 0 20px; + select { + width: 183px; + margin-bottom: 0; + } +} + +.gf-timepicker-btn-apply { + margin: 0 0 0 15px; } .gf-timepicker-relative-section { - padding: 0 20px 0 20px; + padding: 0 20px 0 25px; min-height: 258px; float: left; ul { diff --git a/public/vendor/angular-ui/dateparser.js b/public/vendor/angular-ui/dateparser.js deleted file mode 100644 index 783696ce083..00000000000 --- a/public/vendor/angular-ui/dateparser.js +++ /dev/null @@ -1,212 +0,0 @@ -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; - - var localeId; - var formatCodeToRegex; - - this.init = function() { - localeId = $locale.id; - - this.parsers = {}; - - 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; - } - } - } - }; - }; - - this.init(); - - 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 ($locale.id !== localeId) { - this.init(); - } - - 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; - } -}]); diff --git a/public/vendor/angular-ui/datepicker.js b/public/vendor/angular-ui/datepicker.js deleted file mode 100644 index f6c35cd1379..00000000000 --- a/public/vendor/angular-ui/datepicker.js +++ /dev/null @@ -1,823 +0,0 @@ -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); - } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { - $scope.toggleMode(key === 'up' ? 1 : -1); - } 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) { - var popup = $popup[0]; - var dpContainsTarget = element[0].contains(event.target); - // The popup node may not be an element node - // In some browsers (IE) only element nodes have the 'contains' function - var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target); - if (scope.isOpen && !(dpContainsTarget || popupContainsTarget)) { - scope.$apply(function() { - scope.isOpen = false; - }); - } - }; - - 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'; - } - }; -}); diff --git a/public/vendor/angular-ui/position.js b/public/vendor/angular-ui/position.js deleted file mode 100644 index 70b3640c553..00000000000 --- a/public/vendor/angular-ui/position.js +++ /dev/null @@ -1,150 +0,0 @@ -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; - } - }; - }]); diff --git a/public/vendor/angular-ui/tabs.js b/public/vendor/angular-ui/tabs.js deleted file mode 100644 index 4d87ac0bab1..00000000000 --- a/public/vendor/angular-ui/tabs.js +++ /dev/null @@ -1,293 +0,0 @@ - -/** - * @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.active = true; - selectedTab.onSelect(); - }; - - 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: 'app/partials/bootstrap/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: 'app/partials/bootstrap/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 - }, - compile: function(elm, attrs, transclude) { - return function postLink(scope, elm, attrs, tabsetCtrl) { - 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.tagName.toLowerCase() === 'tab-heading' || - node.tagName.toLowerCase() === 'data-tab-heading' - ); - } -}) - -; diff --git a/public/vendor/angular-ui/ui-bootstrap.js b/public/vendor/angular-ui/ui-bootstrap.js deleted file mode 100644 index 0f049e8461b..00000000000 --- a/public/vendor/angular-ui/ui-bootstrap.js +++ /dev/null @@ -1,1476 +0,0 @@ -/* - * angular-ui-bootstrap - * http://angular-ui.github.io/bootstrap/ - - * Version: 0.13.4 - 2015-09-03 - * License: MIT - */ -angular.module("ui.bootstrap", ["ui.bootstrap.position","ui.bootstrap.dateparser","ui.bootstrap.datepicker","ui.bootstrap.tabs"]); -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 || 'app/partials/bootstrap/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: 'app/partials/bootstrap/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: 'app/partials/bootstrap/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: 'app/partials/bootstrap/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: 'app/partials/bootstrap/popup.html', - datepickerTemplateUrl: 'app/partials/bootstrap/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: 'app/partials/bootstrap/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: 'app/partials/bootstrap/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' - ); - } -});