<!DOCTYPE html>
<html ng-app="drpc">
<head>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" data-semver="3.1.1" data-require="bootstrap-css@3.1.1" />
<link data-require="font-awesome@*" data-semver="4.0.3" rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" />
<script src="http://code.angularjs.org/1.2.13/angular.js" data-semver="1.2.13" data-require="angular.js@*"></script>
<script src="http://code.jquery.com/jquery-2.0.3.min.js" data-semver="2.0.3" data-require="jquery@*"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js" data-semver="3.1.1" data-require="bootstrap@*"></script>
<script data-require="moment.js@*" data-semver="2.5.1" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.js"></script>
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="checkbox.css" />
<script src="drpc.js"></script>
<meta charset="utf-8" />
<title>AngularJS DRPC app</title>
</head>
<body>
<div ng-controller="calendars">
<div ng-repeat="cal in calendars | reverse" class="calendar cal{{$index}}">
<div class="calendar-date">
<table class="table-condensed">
<thead>
<tr>
<th ng-class="{prev:$first}" ng-click="prev()">
<i ng-if="$first" class="fa fa-arrow-left icon-arrow-left glyphicon glyphicon-arrow-left"></i>
</th>
<th colspan="5" class="month">{{ cal.month | toFormat: "MMM, YYYY" }}</th>
<th ng-class="{next:$last}" ng-click="next()">
<i ng-if="$last" class="fa fa-arrow-right icon-arrow-right glyphicon glyphicon-arrow-right"></i>
</th>
</tr>
<tr>
<th>Su</th>
<th>Mo</th>
<th>Tu</th>
<th>We</th>
<th>Th</th>
<th>Fr</th>
<th>Sa</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="(rIndex, row) in cal.cal">
<td ng-class="{selDate: isSelected(col),
selCompDate: isCompSelected(col),
available: col.isValid(),
inRange: isinRange(col),
inCompRange: isinCompRange(col)}" ng-repeat="(cIndex, col) in row" ng-click="select(col)" ng-mouseover="hover(col)">
{{ cal.cal[rIndex][cIndex] | toFormat: 'DD' }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="container">
<form action="">
<!-- range: -->
<div class="row">
<label for="daterange">Date Range:</label>
<select ng-model="firstRange" class="form-control" ng-options="r.name for r in ranges" name="daterange" id="daterange">
</select>
</div>
<div class="row">
<label for="start">From:</label>
<label for="end">To:</label>
</div>
<div class="row">
<button type="text" class="form-control disabled" ng-click="restart(0)" id="start">{{ startDate | toFormat: 'DD MMM, YYYY' }}</button>
<button type="text" class="form-control disabled" ng-click="restart(1)" id="end">{{ endDate | toFormat: 'DD MMM, YYYY' }}</button>
</div>
<!-- compare -->
<div class="row">
<span class="checkbox-bright">
<input ng-model="compare" type="checkbox" id="brightCB">
<label for="brightCB">Compare to:</label>
</span>
<select ng-model="firstCompare" class="form-control" ng-options="cr.name for cr in compareRanges" name="comparerange" id="comparerange" ng-disabled="!compare">
</select>
</div>
<div class="row">
<label for="start-compare">From:</label>
<label for="end-compare">To:</label>
</div>
<div class="row">
<button type="text" class="form-control disabled" ng-click="restart(2)" id="start-compare">
{{ startCompare | toFormat: 'DD MMM, YYYY' }}
</button>
<button type="text" class="form-control disabled" ng-click="restart(3)" id="end-compare">
{{ endCompare | toFormat: 'DD MMM, YYYY' }}
</button>
</div>
</form>
</div>
</div>
<script src="drpc.js"></script>
</body>
</html>
/**
* drpc Module
*
* DRPC stands for date-range picker & compare
* module builds and controls html for a rangepicker
* and allows for comparison of two or more dates
*/
angular.module('drpc', [])
.filter('toFormat', function() {
return function(dt, tmp) {
if (dt.isValid()) {
return dt.format(tmp ? tmp : 'DD');
} else {
return "";
}
}
})
.filter('reverse', function() {
return function(items) {
return items.slice().reverse();
}
})
.controller('calendars', function($scope) {
$scope.months = 3;
$scope.calendars = [
{cal: [], month: 0},
{cal: [], month: 0},
{cal: [], month: 0}
];
$scope.startDate = moment().startOf('day').subtract('month', 1);
$scope.endDate = moment().startOf('day');
$scope.startCompare = moment(null);
$scope.endCompare = moment(null);
$scope.selectFirst = true;
$scope.compare = false;
$scope.ranges = [
{'name': 'Last Month', 'date': moment().startOf('day').subtract('month', 1) },
{'name': 'Last Week', 'date': moment().startOf('day').subtract('day', 7) },
{'name': 'Yesterday', 'date': moment().startOf('day').subtract('day', 1)},
{'name': 'Custom', 'date': null }
];
$scope.firstRange = $scope.ranges[1];
$scope.$watch('firstRange', function(newStart) {
//
if (newStart.date) {
$scope.endDate = moment().startOf('day');
$scope.startDate = newStart.date;
}
});
$scope.compareRanges = [
{'name': 'Previous Period', 'date': 'previous' },
{'name': 'Last Month', 'date': 'month' },
{'name': 'Last Year', 'date': 'year'},
{'name': 'Custom', 'date': null }
];
$scope.$watch('firstCompare', function(newValue) {
if (newValue && $scope.compare) {
$scope.endCompare = $scope.startDate.clone().subtract('day', 1);
if (newValue.date == 'previous') {
var range = $scope.endDate - $scope.startDate;
$scope.startCompare = moment( $scope.endCompare - range );
} else if (newValue.date == 'month') {
$scope.startCompare = $scope.startDate.clone().subtract('month', 1);
} else if (newValue.date == 'year') {
$scope.startCompare = $scope.startDate.clone().subtract('year', 1);
$scope.endCompare = $scope.endDate.clone().subtract('year', 1);
}
}
});
$scope.restart = function(pos) {
if (pos < 2) {
$scope.compare = false;
$scope.startCompare = moment(null);
$scope.endCompare = moment(null);
} else {
$scope.compare = true;
}
if (pos % 2 === 0) {
$scope.selectFirst = true;
} else {
$scope.selectFirst = false;
}
}
$scope.$watch('compare', function(newValue) {
if (newValue) {
$scope.firstCompare = $scope.compareRanges[0];
}
})
$scope.isSelected = function(m) {
return m.isSame($scope.startDate) || m.isSame($scope.endDate) ||
(m.isAfter($scope.startDate) && m.isBefore($scope.endDate));
}
$scope.isCompSelected = function(m) {
return m.isSame($scope.startCompare) || m.isSame($scope.endCompare) ||
(m.isAfter($scope.startCompare) && m.isBefore($scope.endCompare));
}
$scope.toggleSelect = function() {
$scope.selectFirst = ($scope.selectFirst ? false: true);
}
// hover functionality
$scope.hoverDate = moment(null);
$scope.hoverCompDate = moment(null);
$scope.isinRange = function(m) {
return ( (m.isAfter($scope.startDate) && m.isBefore($scope.hoverDate)) ||
(m.isAfter($scope.hoverDate) && m.isBefore($scope.startDate)) )
}
$scope.isinCompRange = function(m) {
return ( (m.isAfter($scope.startCompare) && m.isBefore($scope.hoverCompDate)) ||
(m.isAfter($scope.hoverCompDate) && m.isBefore($scope.startCompare)) )
}
$scope.hover = function(m) {
if (!$scope.selectFirst) {
if (!$scope.compare) {
$scope.hoverDate = m;
} else {
$scope.hoverCompDate = m;
}
}
}
$scope.select = function(m) {
if (m.isValid()) {
if (!$scope.compare) {
// mark custom
$scope.firstRange = $scope.ranges[3];
// set values
var start = $scope.startDate;
var end = $scope.endDate;
} else {
// mark custom
$scope.firstCompare = $scope.compareRanges[3];
// set values
var start = $scope.startCompare;
var end = $scope.endCompare;
}
// calculate
if ($scope.selectFirst) {
end = moment(null);
start = m;
} else {
if (m.isBefore(start)) {
end = start;
start = m;
} else {
end = m;
}
}
// assign them back
if (!$scope.compare) {
$scope.startDate = start;
$scope.endDate = end;
} else {
$scope.startCompare = start;
$scope.endCompare = end;
}
$scope.toggleSelect();
}
}
$scope.prev = function() {
for (var c = 0; c < $scope.calendars.length; c++) {
$scope.calendars[c].month.subtract('month', 1);
// re-build calendars
$scope.buildCalendar($scope.calendars[c]);
}
}
$scope.next = function() {
for (var c = 0; c < $scope.calendars.length; c++) {
$scope.calendars[c].month.add('month', 1);
// re-build calendars
$scope.buildCalendar($scope.calendars[c]);
}
}
// builds the calendars
$scope.buildCalendar = function(date) {
var firstDay = moment([date.month.year(), date.month.month(), 1]);
var lastMonth = moment(firstDay).subtract('month', 1).month();
var lastYear = moment(firstDay).subtract('month', 1).year();
var nextMonth = moment(firstDay).add('month', 1).month();
var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth();
var dayOfWeek = firstDay.day();
//initialize a 6 rows x 7 columns array for the calendar
for (var i = 0; i < 6; i++) {
date.cal[i] = [];
}
var locale = 0; // if we want to change the startday of the week...
//populate the calendar with date objects
var startDay = daysInLastMonth - dayOfWeek + locale + 1;
if (startDay > daysInLastMonth)
startDay -= 7;
if (dayOfWeek == locale)
startDay = daysInLastMonth - 6;
var curDate = moment([lastYear, lastMonth, startDay, 0, 0]);
for (i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add('hour', 24)) {
if (i > 0 && col % 7 === 0) {
col = 0;
row++;
}
if (curDate.month() == lastMonth ||
curDate.month() == nextMonth ) {
var value = moment(null);
} else {
var value = curDate.clone();
}
date.cal[row][col] = value;
}
};
// initialize for the first time
for (var c = 0; c < $scope.calendars.length; c++) {
// calculate current month
$scope.calendars[c].month = moment().subtract('month', c);
// build calendars
$scope.buildCalendar($scope.calendars[c]);
}
});
* { box-sizing: border-box; }
body { font: 14px/1.5 sans-serif; color: #222; margin: 5em; }
.calendar {
margin-right: 20px;
float: left;
}
.calendar th {
text-align: center;
}
.calendar .selDate {
background-color: darkblue;
color: white;
}
.calendar .selCompDate, .inCompRange {
background-color: orange;
color: white;
}
.calendar .selDate.selCompDate, .inRange {
background-color: lightblue;
}
.calendar td.available {
cursor: pointer;
}
.calendar td.available:hover {
background-color: blue;
}
.disabled {
cursor: not-allowed;
background-color: #eee;
opacity: 1;
}
/* ----- */
.container {
width: 40%;
float: left;
}
label {
width: 40%;
margin: 5px;
float: left;
}
.form-control {
width: 40%;
margin: 5px;
float: left;
}
DRPC stands for Date Range-Picker & Compare. It's build with angularJS and
moment.js, and is inspired by Google Analytic's similar Date Range-Picker.
.checkbox-dark input,
.checkbox-bright input,
.checkbox-star input {
display: none;
}
.checkbox-dark label,
.checkbox-bright label,
.checkbox-star label {
display: inline-block;
cursor: pointer;
position: relative;
padding-left: 25px;
}
.checkbox-dark label:before,
.checkbox-bright label:before,
.checkbox-star label:before {
font-family: FontAwesome;
content: "\f0c8";
position: absolute;
left: 0;
bottom: 0;
-webkit-text-stroke: 0.45px;
font-weight: normal;
}
.checkbox-dark input:checked + label:before,
.checkbox-bright input:checked + label:before,
.checkbox-star input:checked + label:before {
-webkit-text-stroke: 0.45px;
font-family: FontAwesome;
content: "\f14a";
font-weight: normal;
}
.checkbox-bright label:before {
color: #0957e4;
content: "\f096";
}
.checkbox-bright input:checked + label:before {
color: #0957e4;
content: "\f046";
}
.checkbox-star label:before {
color: #f17828;
content: "\f006";
}
.checkbox-star input:checked + label:before {
color: #f17828;
content: "\f005";
}