Commit 5eefa361 by Torkel Ödegaard

feat(timepickerv2): big progress on new design of new timepicker, #2761

parent a30f73fe
......@@ -56,7 +56,8 @@ function (angular, $, _, appLevelRequire) {
'ang-drag-drop',
'grafana',
'pasvaz.bindonce',
'ui.bootstrap.tabs',
'ui.bootstrap',
'ui.bootstrap.tpls',
];
var module_types = ['controllers', 'directives', 'factories', 'services', 'filters', 'routes'];
......
......@@ -19,7 +19,7 @@ require.config({
'angular-sanitize': 'vendor/angular-sanitize/angular-sanitize',
'angular-dragdrop': 'vendor/angular-native-dragdrop/draganddrop',
'angular-strap': 'vendor/angular-other/angular-strap',
'angular-ui': 'vendor/angular-ui/tabs',
'angular-ui': 'vendor/angular-ui/ui-bootstrap-tpls',
timepicker: 'vendor/angular-other/timepicker',
datepicker: 'vendor/angular-other/datepicker',
bindonce: 'vendor/angular-bindonce/bindonce',
......
......@@ -72,7 +72,8 @@ function ($, coreModule) {
};
var src = "'" + payload.src + "'";
var view = $('<div class="gf-box" ng-include="' + src + '"></div>');
var cssClass = payload.cssClass || 'gf-box';
var view = $('<div class="' + cssClass + '" ng-include="' + src + '"></div>');
if (payload.cssClass) {
view.addClass(payload.cssClass);
......
......@@ -15,12 +15,12 @@ var spans = {
};
var rangeOptions = [
{ from: 'now/d', to: 'now/d', display: 'Today', section: 0 },
{ from: 'now/w', to: 'now/w', display: 'This week', section: 0 },
{ from: 'now/d', to: 'now', display: 'The day so far', section: 0 },
{ from: 'now/w', to: 'now', display: 'Week to date', section: 0 },
{ from: 'now/M', to: 'now/M', display: 'This month', section: 0 },
{ from: 'now/y', to: 'now/y', display: 'This year', section: 0 },
{ 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', 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 },
{ from: 'now-1d/d', to: 'now-1d/d', display: 'Yesterday', section: 1 },
{ from: 'now-2d/d', to: 'now-2d/d', display: 'Day before yesterday', section: 1 },
......@@ -29,22 +29,22 @@ var rangeOptions = [
{ from: 'now-1M/M', to: 'now-1M/M', display: 'Previous month', section: 1 },
{ from: 'now-1y/y', to: 'now-1y/y', display: 'Previous year', section: 1 },
{ from: 'now-5m', to: 'now', display: 'Last 5 minutes', section: 2 },
{ from: 'now-15m', to: 'now', display: 'Last 15 minutes', section: 2 },
{ from: 'now-30m', to: 'now', display: 'Last 30 minutes', section: 2 },
{ from: 'now-1h', to: 'now', display: 'Last 1 hour', section: 2 },
{ from: 'now-4h', to: 'now', display: 'Last 4 hours', section: 2 },
{ from: 'now-12h', to: 'now', display: 'Last 12 hours', section: 2 },
{ from: 'now-24h', to: 'now', display: 'Last 24 hours', section: 2 },
{ from: 'now-7d', to: 'now', display: 'Last 7 days', section: 2 },
{ from: 'now-30d', to: 'now', display: 'Last 30 days', section: 3 },
{ from: 'now-60d', to: 'now', display: 'Last 60 days', section: 3 },
{ from: 'now-90d', to: 'now', display: 'Last 90 days', section: 3 },
{ from: 'now-6M', to: 'now', display: 'Last 6 months', section: 3 },
{ from: 'now-1y', to: 'now', display: 'Last 1 year', section: 3 },
{ from: 'now-2y', to: 'now', display: 'Last 2 years', section: 3 },
{ from: 'now-5y', to: 'now', display: 'Last 5 years', section: 3 },
{ from: 'now-5m', to: 'now', display: 'Last 5 minutes', section: 3 },
{ from: 'now-15m', to: 'now', display: 'Last 15 minutes', section: 3 },
{ from: 'now-30m', to: 'now', display: 'Last 30 minutes', section: 3 },
{ from: 'now-1h', to: 'now', display: 'Last 1 hour', section: 3 },
{ from: 'now-6h', to: 'now', display: 'Last 6 hours', section: 3 },
{ from: 'now-12h', to: 'now', display: 'Last 12 hours', section: 3 },
{ from: 'now-24h', to: 'now', display: 'Last 24 hours', section: 3 },
{ from: 'now-7d', to: 'now', display: 'Last 7 days', section: 3 },
{ from: 'now-30d', to: 'now', display: 'Last 30 days', section: 0 },
{ from: 'now-60d', to: 'now', display: 'Last 60 days', section: 0 },
{ from: 'now-90d', to: 'now', display: 'Last 90 days', section: 0 },
{ from: 'now-6M', to: 'now', display: 'Last 6 months', section: 0 },
{ from: 'now-1y', to: 'now', display: 'Last 1 year', section: 0 },
{ from: 'now-2y', to: 'now', display: 'Last 2 years', section: 0 },
{ from: 'now-5y', to: 'now', display: 'Last 5 years', section: 0 },
];
var rangeIndex = {};
......@@ -52,10 +52,14 @@ _.each(rangeOptions, function (frame) {
rangeIndex[frame.from + ' to ' + frame.to] = frame;
});
function getRelativeTimesList(timepickerSettings) {
return _.map(timepickerSettings.time_options, function(duration: string) {
return describeTextRange(duration);
function getRelativeTimesList(timepickerSettings, currentDisplay) {
return _.groupBy(rangeOptions, (option: any) => {
option.active = option.display === currentDisplay;
return option.section;
});
// return _.map(timepickerSettings.time_options, function(duration: string) {
// return describeTextRange(duration);
// });
}
// handles expressions like
......
......@@ -216,6 +216,10 @@ function (angular, $, kbn, _, moment) {
};
p.formatDate = function(date, format) {
if (!moment.isMoment(date)) {
date = moment(date)
}
format = format || 'YYYY-MM-DD HH:mm:ss';
return this.timezone === 'browser' ?
......
......@@ -9,40 +9,6 @@
</div>
<div class="gf-box-body">
<style>
.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;
}
</style>
<div class="timepicker form-horizontal">
<form name="timeForm" style="margin-bottom: 0">
......
<div class="row pull-right">
<div class="gf-timepicker-relative-section">
<h3>Quick ranges</h3>
<ul ng-repeat="group in timeOptions">
<li bindonce ng-repeat='option in group' ng-class="{active: option.active}">
<a ng-click="ctrl.setRelativeFilter(option)" bo-text="option.display"></a>
</li>
</ul>
</div>
<div class="gf-timepicker-absolute-section">
<h3>Time range</h3>
<label class="small">From:</label>
<div class="input-prepend">
<input type="text" class="input-large" ng-model="timeRaw.from" input-datetime>
<button class="btn btn-primary" type="button" ng-click="openFromPicker=!openFromPicker">
<i class="fa fa-calendar"></i>
</button>
</div>
<div ng-if="openFromPicker">
<datepicker ng-model="absolute.to" class="gf-timepicker-component" show-weeks="false"></datepicker>
</div>
<label class="small">To:</label>
<div class="input-prepend">
<input type="text" class="input-large" ng-model="timeRaw.to" input-datetime>
<button class="btn btn-primary" type="button" ng-click="openToPicker=!openToPicker">
<i class="fa fa-calendar"></i>
</button>
</div>
<label class="small">Refreshing every:</label>
<select ng-model="dashboard.refresh" class='input-large' ng-options="f for f in ['5m','10m']"></select>
</div>
</div>
<div class="clearfix"></div>
<!-- <tabset> -->
<!-- <tab heading="Relative"> -->
<!-- -->
<!-- <div style="float:right; width: 200px" ng&#45;repeat="group in timeOptions"> -->
<!-- <ul> -->
<!-- <li bindonce ng&#45;repeat='option in group'> -->
<!-- <a ng&#45;click="ctrl.setRelativeFilter(option)" bo&#45;text="option.display"></a> -->
<!-- </li> -->
<!-- </ul> -->
<!-- </div> -->
<!-- -->
<!-- </tab> -->
<!-- <tab heading="Absolute"> -->
<!-- -->
<!-- <form name="timeForm" style="margin: 0 20px 20px 20px"> -->
<!-- <div class="gf&#45;timepicker&#45;section"> -->
<!-- <div> -->
<!-- <div class="tight&#45;form last"> -->
<!-- </div> -->
<!-- <label class="small">From:</label> -->
<!-- <input type="text" required class="input&#45;large" ng&#45;model="absolute.from" input&#45;datetime="MMMM Do YYYY, HH:mm:ss.SSS"> -->
<!-- <br> -->
<!-- </div> -->
<!-- <datepicker ng&#45;model="absolute.from" class="gf&#45;timepicker&#45;component" show&#45;weeks="false"></datepicker> -->
<!-- </div> -->
<!-- <div class="gf&#45;timepicker&#45;section"> -->
<!-- <div> -->
<!-- <label class="small">To:</label> -->
<!-- <input type="text" required class="input&#45;large" ng&#45;model="absolute.to" input&#45;datetime="MMMM Do YYYY, HH:mm:ss.SSS"> -->
<!-- <br> -->
<!-- </div> -->
<!-- <datepicker ng&#45;model="absolute.to" class="gf&#45;timepicker&#45;component" show&#45;weeks="false"></datepicker> -->
<!-- </div> -->
<!-- <div class="clearfix"></div> -->
<!-- </form> -->
<!-- </tab> -->
<!-- </tabset> -->
<!-- -->
<!-- <!&#45;&#45; <!&#38;#45;&#38;#45; Auto refresh submenu &#38;#45;&#38;#45;> &#45;&#45;> -->
<!-- <li class="dropdown&#45;submenu"> -->
<!-- <a href="#">Auto&#45;Refresh</a> -->
<!-- <ul class="dropdown&#45;menu" ng&#45;class="{'dropdown&#45;submenu&#45;left': refreshMenuLeftSide}"> -->
<!-- <li> -->
<!-- <a ng&#45;click="timeSrv.set_interval(false)">Off</a> -->
<!-- </li> -->
<!-- <li bindonce ng&#45;repeat="interval in panel.refresh_intervals track by $index"> -->
<!-- <a ng&#45;click="timeSrv.set_interval(interval)" bo&#45;text="'Every ' + interval"></a> -->
<!-- </li> -->
<!-- </ul> -->
<!-- </li> -->
<!-- <li><a ng&#45;click="ctrl.customTime()">Custom</a></li> -->
<!-- </div> -->
define([
"angular",
"moment",
],function (angular, moment) {
'use strict';
angular.
module("grafana.directives").
directive('inputDatetime', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function ($scope, $elem, attrs, ngModel) {
// var format = 'YYYY-MM-DD HH:mm:ss.SSS';
// // $elem.after('<div class="input-datetime-format">' + format + '</div>');
//
// // What should I make with the input from the user?
// var fromUser = function (text) {
// var parsed = moment(text, format);
// return parsed.isValid() ? parsed : undefined;
// };
//
// // How should I present the data back to the user in the input field?
// var toUser = function (datetime) {
// return moment(datetime).format(format);
// };
//
// ngModel.$parsers.push(fromUser);
// ngModel.$formatters.push(toUser);
}
};
});
});
<style>
.timepicker-timestring {
font-weight: normal;
}
<ul class="nav timepicker-dropdown">
.timepicker-dropdown {
margin: 0px !important;
border: 0px !important;
}
</style>
<form name="input" style="margin:0">
<ul class="nav timepicker-dropdown">
<li class="grafana-menu-zoom-out">
<a class='small' ng-click='zoom(2)'>
Zoom Out
</a>
</li>
<li class="grafana-menu-zoom-out">
<a class='small' ng-click='zoom(2)'>
Zoom Out
</a>
</li>
<li>
<a class="timepicker-dropdown" bs-tooltip="tooltip" data-placement="bottom" ng-click="ctrl.loadTimeOptions()">
<i class="fa fa-clock-o"></i>
<span ng-bind="rangeString"></span>
<i class="fa fa-caret-down"></i>
</a>
</li>
<li class="dropdown">
<li class="grafana-menu-refresh">
<a ng-click="timeSrv.refreshDashboard()">
<i class="fa fa-refresh"></i>
<span ng-show="dashboard.refresh" class="text-warning">refreshed every {{dashboard.refresh}} </span>
</a>
</li>
<a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" bs-tooltip="time.tooltip" data-placement="bottom" ng-click="ctrl.loadTimeOptions();">
<i class="fa fa-clock-o"></i>
<span ng-bind="time.rangeString"></span>
<span ng-show="dashboard.refresh" class="text-warning">refreshed every {{dashboard.refresh}} </span>
<i class="fa fa-caret-down"></i>
</a>
<!-- lacy load this -->
<ul class="dropdown-menu" ng-if="timeOptions" >
<li bindonce ng-repeat='option in timeOptions'>
<a ng-click="ctrl.setRelativeFilter(option)" bo-text="option.display"></a>
</li>
<!-- Auto refresh submenu -->
<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>
</ul>
</li>
<li ng-show="!dashboard.refresh" class="grafana-menu-refresh">
<a ng-click="timeSrv.refreshDashboard()"><i class="fa fa-refresh"></i></a>
</li>
</ul>
</form>
</div>
</ul>
///<reference path="../../../headers/common.d.ts" />
///<amd-dependency path="./input_date" name="inputDate" />
import angular = require('angular');
import _ = require('lodash');
......@@ -7,6 +8,8 @@ import kbn = require('kbn');
import dateMath = require('app/core/utils/datemath');
import rangeUtil = require('app/core/utils/rangeutil');
declare var inputDate: any;
export class TimePickerCtrl {
static defaults = {
......@@ -44,57 +47,30 @@ export class TimePickerCtrl {
if (_.isString(timeRaw.to) && timeRaw.to.indexOf('now') === 0) {
this.$scope.panel.now = true;
this.$scope.rangeString = rangeUtil.describeTimeRange(timeRaw);
} else {
let format = 'MMM D, YYYY HH:mm:ss';
this.$scope.rangeString = this.$scope.dashboard.formatDate(time.from, format) + ' to ' +
this.$scope.dashboard.formatDate(time.to, format);
}
this.$scope.time = this.getScopeTimeObj(time.from, time.to);
this.$scope.timeRaw = timeRaw;
this.$scope.absolute = {form: time.from.toDate(), to: time.to.toDate()};
this.$scope.onAppEvent('zoom-out', function() {
this.$scope.zoom(2);
});
}
pad(n: number, width: number, z = 0): string {
var str = n.toString();
return str.length >= width ? str : new Array(width - str.length + 1).join(z.toString()) + str;
}
getTimeObj(date): any {
return {
date: date,
hour: this.pad(date.hours(), 2),
minute: this.pad(date.minutes(), 2),
second: this.pad(date.seconds(), 2),
millisecond: this.pad(date.milliseconds(), 3)
};
};
getScopeTimeObj(from, to) {
var model : any = {from: this.getTimeObj(from), to: this.getTimeObj(to)};
if (model.from.date) {
model.tooltip = this.$scope.dashboard.formatDate(model.from.date) + ' <br>to<br>';
model.tooltip += this.$scope.dashboard.formatDate(model.to.date);
}
else {
model.tooltip = 'Click to set time filter';
}
if (this.timeSrv.time) {
if (this.$scope.panel.now) {
model.rangeString = rangeUtil.describeTimeRange(this.timeSrv.time);
}
else {
model.rangeString = this.$scope.dashboard.formatDate(model.from.date, 'MMM D, YYYY HH:mm:ss') + ' to ' +
this.$scope.dashboard.formatDate(model.to.date, 'MMM D, YYYY HH:mm:ss');
}
}
return model;
this.$scope.tooltip = this.$scope.dashboard.formatDate(time.from.date) + ' <br>to<br>';
this.$scope.tooltip += this.$scope.dashboard.formatDate(time.to.date);
}
loadTimeOptions() {
this.$scope.timeOptions = rangeUtil.getRelativeTimesList(this.$scope.panel);
this.$scope.refreshMenuLeftSide = this.$scope.time.rangeString.length < 10;
this.$scope.timeOptions = rangeUtil.getRelativeTimesList(this.$scope.panel, this.$scope.rangeString);
this.$scope.appEvent('show-dash-editor', {
src: 'app/features/dashboard/timepicker/dropdown.html',
scope: this.$scope,
cssClass: 'gf-timepicker-dropdown',
});
}
customTime() {
......@@ -118,10 +94,6 @@ export class TimePickerCtrl {
});
}
setNow() {
this.$scope.time.to = this.getTimeObj(new Date());
}
setAbsoluteTimeFilter(time) {
// Create filter object
var _filter = _.clone(time);
......@@ -130,8 +102,6 @@ export class TimePickerCtrl {
_filter.to = "now";
}
// Update our representation
this.$scope.time = this.getScopeTimeObj(time.from, time.to);
this.timeSrv.setTime(_filter);
}
......@@ -145,8 +115,7 @@ export class TimePickerCtrl {
}
this.timeSrv.setTime(range);
this.$scope.time = this.getScopeTimeObj(dateMath.parse(range.from), moment());
this.$scope.appEvent('hide-dash-editor');
}
validate(time): any {
......
......@@ -542,5 +542,3 @@ a.thumbnail {
}
// MEDIA QUERIES
// -----------------------------------------------------
......@@ -17,6 +17,7 @@
@import "validation.less";
@import "fonts.less";
@import "tabs.less";
@import "timepicker.less";
.row-control-inner {
padding:0px;
......
......@@ -2,6 +2,7 @@
.nav-tabs-alt {
border-bottom: @grafanaTriggerBorder;
padding-left: 10px;
margin: 0;
& > li > a {
color: darken(@linkColor, 20%);
......@@ -31,3 +32,8 @@
}
}
.tab-content {
padding: 10px;
background-color: @grafanaPanelBackground;
}
.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;
}
.timepicker-timestring {
font-weight: normal;
}
.gf-timepicker-dropdown {
margin: 15px;
padding: 10px 20px;
float: right;
background-color: @grafanaPanelBackground;
border: 1px solid @grafanaTargetFuncBackground;
}
.gf-timepicker-absolute-section {
width: 300px;
float: left;
padding: 0 20px 0 20px;
}
.gf-timepicker-relative-section {
border-right: @grafanaTriggerBorder;
padding: 0 20px 0 20px;
min-height: 258px;
float: left;
ul {
float: left;
margin: 0 20px 10px 25px;
li.active {
border-left: 1px solid @blue;
padding: 2px 0;
}
}
}
.gf-timepicker-component {
button.btn-sm {
.buttonBackground(@btnInverseBackground, @btnInverseBackgroundHighlight);
background-image: none;
border: none;
padding: 6px 9px;
color: @textColor;
&.active .text-info {
color: @orange;
font-weight: bold;
}
}
}
.faMixin {
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.glyphicon-chevron-right:before {
.faMixin;
content: "\f054";
}
.glyphicon-chevron-left:before {
.faMixin;
content: "\f053";
}
.input-datetime-format {
color: @linkColorDisabled
}
......@@ -14,142 +14,6 @@ angular.module('$strap', [
'$strap.config'
]);
'use strict';
angular.module('$strap.directives').directive('bsDatepicker', [
'$timeout',
'$strapConfig',
function ($timeout, $strapConfig) {
var isAppleTouch = /(iP(a|o)d|iPhone)/g.test(navigator.userAgent);
var regexpMap = function regexpMap(language) {
language = language || 'en';
return {
'/': '[\\/]',
'-': '[-]',
'.': '[.]',
' ': '[\\s]',
'dd': '(?:(?:[0-2]?[0-9]{1})|(?:[3][01]{1}))',
'd': '(?:(?:[0-2]?[0-9]{1})|(?:[3][01]{1}))',
'mm': '(?:[0]?[1-9]|[1][012])',
'm': '(?:[0]?[1-9]|[1][012])',
'DD': '(?:' + $.fn.datepicker.dates[language].days.join('|') + ')',
'D': '(?:' + $.fn.datepicker.dates[language].daysShort.join('|') + ')',
'MM': '(?:' + $.fn.datepicker.dates[language].months.join('|') + ')',
'M': '(?:' + $.fn.datepicker.dates[language].monthsShort.join('|') + ')',
'yyyy': '(?:(?:[1]{1}[0-9]{1}[0-9]{1}[0-9]{1})|(?:[2]{1}[0-9]{3}))(?![[0-9]])',
'yy': '(?:(?:[0-9]{1}[0-9]{1}))(?![[0-9]])'
};
};
var regexpForDateFormat = function regexpForDateFormat(format, language) {
var re = format, map = regexpMap(language), i;
i = 0;
angular.forEach(map, function (v, k) {
re = re.split(k).join('${' + i + '}');
i++;
});
i = 0;
angular.forEach(map, function (v, k) {
re = re.split('${' + i + '}').join(v);
i++;
});
return new RegExp('^' + re + '$', ['i']);
};
return {
restrict: 'A',
require: '?ngModel',
link: function postLink(scope, element, attrs, controller) {
var options = angular.extend({ autoclose: true }, $strapConfig.datepicker || {}), type = attrs.dateType || options.type || 'date';
angular.forEach([
'format',
'weekStart',
'calendarWeeks',
'startDate',
'endDate',
'daysOfWeekDisabled',
'autoclose',
'startView',
'minViewMode',
'todayBtn',
'todayHighlight',
'keyboardNavigation',
'language',
'forceParse'
], function (key) {
if (angular.isDefined(attrs[key]))
options[key] = attrs[key];
});
var language = options.language || 'en', readFormat = attrs.dateFormat || options.format || $.fn.datepicker.dates[language] && $.fn.datepicker.dates[language].format || 'mm/dd/yyyy', format = isAppleTouch ? 'yyyy-mm-dd' : readFormat, dateFormatRegexp = regexpForDateFormat(format, language);
if (controller) {
controller.$formatters.unshift(function (modelValue) {
return type === 'date' && angular.isString(modelValue) && modelValue ? $.fn.datepicker.DPGlobal.parseDate(new Date(modelValue), $.fn.datepicker.DPGlobal.parseFormat(readFormat), language) : modelValue;
});
controller.$parsers.unshift(function (viewValue) {
if (!viewValue) {
controller.$setValidity('date', true);
return null;
} else if (type === 'date' && angular.isDate(viewValue)) {
controller.$setValidity('date', true);
return viewValue;
} else if (angular.isString(viewValue) && dateFormatRegexp.test(viewValue)) {
controller.$setValidity('date', true);
if (isAppleTouch)
return new Date(viewValue);
return type === 'string' ? viewValue : $.fn.datepicker.DPGlobal.parseDate(viewValue, $.fn.datepicker.DPGlobal.parseFormat(format), language);
} else {
controller.$setValidity('date', false);
return undefined;
}
});
controller.$render = function ngModelRender() {
if (isAppleTouch) {
var date = controller.$viewValue ? $.fn.datepicker.DPGlobal.formatDate(controller.$viewValue, $.fn.datepicker.DPGlobal.parseFormat(format), language) : '';
element.val(date);
return date;
}
if (!controller.$viewValue)
element.val('');
return element.datepicker('update', controller.$viewValue);
};
}
if (isAppleTouch) {
element.prop('type', 'date').css('-webkit-appearance', 'textfield');
} else {
if (controller) {
element.on('changeDate', function (ev) {
scope.$apply(function () {
controller.$setViewValue(type === 'string' ? element.val() : ev.date);
});
});
}
element.datepicker(angular.extend(options, {
format: format,
language: language
}));
scope.$on('$destroy', function () {
var datepicker = element.data('datepicker');
if (datepicker) {
datepicker.picker.remove();
element.data('datepicker', null);
}
});
attrs.$observe('startDate', function (value) {
element.datepicker('setStartDate', value);
});
attrs.$observe('endDate', function (value) {
element.datepicker('setEndDate', value);
});
}
var component = element.siblings('[data-toggle="datepicker"]');
if (component.length) {
component.on('click', function () {
if (!element.prop('disabled')) {
element.trigger('focus');
}
});
}
}
};
}
]);
'use strict';
angular.module('$strap.directives').factory('$modal', [
'$rootScope',
'$compile',
......
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;
}
}]);
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;
}
};
}]);
......@@ -20,6 +20,7 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* makes the font 33% larger relative to the icon container */
.fa-lg {
font-size: 1.33333333em;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment