<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js@*" data-semver="1.2.3" src="http://code.angularjs.org/1.2.3/angular.js"></script>
<link rel="stylesheet" type="text/css" href="angular-pickadate.css" />
<style type="text/css">
.container {
width: 300px;
margin: auto;
}
.selected-date {
padding: 10px;
text-align: center;
}
.fluid-control {
margin-top: 100px;
text-align: center;
}
#slider {
width: 450px;
text-align: center;
}
h3 {
font-weight: normal;
}
</style>
</head>
<body ng-app="testApp" id="ng-app" ng-controller="TestController">
<div class="container">
<div pickadate="" ng-model="date" min-date="minDate"></div>
<div class="selected-date">
Selected date is {{date || '(no date selected)'}}
</div>
</div>
<div class="fluid-control">
<h3>Move the slider to change the datepicker's width</h3>
<input id="slider" type="range" min="200" max="500" value="300"></input>
</div>
<script type="text/javascript" src="angular-pickadate.js"></script>
<script type="text/javascript">
angular.module('testApp', ['pickadate']);
function TestController($scope, dateFilter) {
$scope.date = dateFilter(new Date(), 'yyyy-MM-dd');
$scope.minDate = '2015-10-06';
}
var slider = document.querySelector('#slider'),
container = document.querySelector('.container');
slider.addEventListener("input", function() {
container.style.width = slider.value + 'px';
}, false);
</script>
</body>
</html>
/* Styles go here */
.pickadate-header {
position: relative;
}
.pickadate {
font-family: 'Helvetica Neue', Helvetica, Helvetica, Arial, sans-serif;
}
.pickadate-main {
margin: 0;
padding: 0;
width: 100%;
text-align: center;
font-size: 12px;
}
.pickadate a:visited, .pickadate a {
color: #666666;
}
.pickadate-cell {
overflow: hidden;
margin: 0;
padding: 0;
}
.pickadate-cell li {
display: block;
float: left;
border: 1px solid #DCDCDC;
border-width: 0 1px 1px 0;
width: 14.285%;
padding: 1.3% 0 1.3% 0;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
.pickadate-cell li:nth-child(7n+0) {
border-right: 1px solid #DCDCDC;
}
.pickadate-cell li:nth-child(1),
.pickadate-cell li:nth-child(8),
.pickadate-cell li:nth-child(15),
.pickadate-cell li:nth-child(22),
.pickadate-cell li:nth-child(29),
.pickadate-cell li:nth-child(36) {
border-left: 1px solid #DCDCDC;
}
.pickadate-cell .pickadate-disabled,
.pickadate-cell .pickadate-disabled a {
color: #DCDCDC;
}
.pickadate-cell .pickadate-enabled {
cursor: pointer;
font-size: 12px;
color: #666666;
}
.pickadate-cell .pickadate-today {
background-color: #eaeaea;
}
.pickadate-cell .pickadate-active {
background-color: #b52a00;
}
.pickadate-cell .pickadate-active {
color: white;
}
.pickadate-cell .pickadate-head {
border-top: 1px solid #DCDCDC;
background: #f3f3f3;
}
.pickadate-cell .pickadate-head:nth-child(1),
.pickadate-cell .pickadate-head:nth-child(7) {
background: #f3f3f3;
}
.pickadate-centered-heading {
font-weight: normal;
text-align: center;
font-size: 1em;
margin: 13px 0 13px 0;
}
.pickadate-controls {
position: absolute;
z-index: 10;
width: 100%;
}
.pickadate-controls .pickadate-next {
float: right;
}
.pickadate-controls a {
text-decoration: none;
font-size: 0.9em;
}
;(function(angular){
var indexOf = [].indexOf || function(item) {
for (var i = 0, l = this.length; i < l; i++) {
if (i in this && this[i] === item) return i;
}
return -1;
};
angular.module('pickadate.utils', [])
.factory('pickadateUtils', ['dateFilter', function(dateFilter) {
return {
isDate: function(obj) {
return Object.prototype.toString.call(obj) === '[object Date]';
},
stringToDate: function(dateString) {
if (this.isDate(dateString)) return new Date(dateString);
var dateParts = dateString.split('-'),
year = dateParts[0],
month = dateParts[1],
day = dateParts[2];
// set hour to 3am to easily avoid DST change
return new Date(year, month - 1, day, 3);
},
dateRange: function(first, last, initial, format) {
var date, i, _i, dates = [];
if (!format) format = 'yyyy-MM-dd';
for (i = _i = first; first <= last ? _i < last : _i > last; i = first <= last ? ++_i : --_i) {
date = this.stringToDate(initial);
date.setDate(date.getDate() + i);
dates.push(dateFilter(date, format));
}
return dates;
}
};
}]);
angular.module('pickadate', ['pickadate.utils'])
.directive('pickadate', ['$locale', 'pickadateUtils', 'dateFilter', function($locale, dateUtils, dateFilter) {
return {
require: 'ngModel',
scope: {
date: '=ngModel',
minDate: '=',
maxDate: '=',
disabledDates: '='
},
template:
'<div class="pickadate">' +
'<div class="pickadate-header">' +
'<div class="pickadate-controls">' +
'<a href="" class="pickadate-prev" ng-click="changeMonth(-1)" ng-show="allowPrevMonth">prev</a>' +
'<a href="" class="pickadate-next" ng-click="changeMonth(1)" ng-show="allowNextMonth">next</a>' +
'</div>'+
'<h3 class="pickadate-centered-heading">' +
'{{currentDate | date:"MMMM yyyy"}}' +
'</h3>' +
'</div>' +
'<div class="pickadate-body">' +
'<div class="pickadate-main">' +
'<ul class="pickadate-cell">' +
'<li class="pickadate-head" ng-repeat="dayName in dayNames">' +
'{{dayName}}' +
'</li>' +
'</ul>' +
'<ul class="pickadate-cell">' +
'<li ng-repeat="d in dates" ng-click="setDate(d)" class="{{d.className}}" ng-class="{\'pickadate-active\': date == d.date}">' +
'{{d.date | date:"d"}}' +
'</li>' +
'</ul>' +
'</div>' +
'</div>' +
'</div>',
link: function(scope, element, attrs, ngModel) {
var minDate = scope.minDate && dateUtils.stringToDate(scope.minDate),
maxDate = scope.maxDate && dateUtils.stringToDate(scope.maxDate),
disabledDates = scope.disabledDates || [],
currentDate = new Date();
scope.dayNames = $locale.DATETIME_FORMATS['SHORTDAY'];
scope.currentDate = currentDate;
scope.render = function(initialDate) {
initialDate = new Date(initialDate.getFullYear(), initialDate.getMonth(), 1, 3);
var currentMonth = initialDate.getMonth() + 1,
dayCount = new Date(initialDate.getFullYear(), initialDate.getMonth() + 1, 0, 3).getDate(),
prevDates = dateUtils.dateRange(-initialDate.getDay(), 0, initialDate),
currentMonthDates = dateUtils.dateRange(0, dayCount, initialDate),
lastDate = dateUtils.stringToDate(currentMonthDates[currentMonthDates.length - 1]),
nextMonthDates = dateUtils.dateRange(1, 7 - lastDate.getDay(), lastDate),
allDates = prevDates.concat(currentMonthDates, nextMonthDates),
dates = [],
today = dateFilter(new Date(), 'yyyy-MM-dd');
// Add an extra row if needed to make the calendar to have 6 rows
if (allDates.length / 7 < 6) {
allDates = allDates.concat(dateUtils.dateRange(1, 8, allDates[allDates.length - 1]));
}
var nextMonthInitialDate = new Date(initialDate);
nextMonthInitialDate.setMonth(currentMonth);
scope.allowPrevMonth = !minDate || initialDate > minDate;
scope.allowNextMonth = !maxDate || nextMonthInitialDate < maxDate;
for (var i = 0; i < allDates.length; i++) {
var className = "", date = allDates[i];
if (date < scope.minDate || date > scope.maxDate || dateFilter(date, 'M') !== currentMonth.toString()) {
className = 'pickadate-disabled';
} else if (indexOf.call(disabledDates, date) >= 0) {
className = 'pickadate-disabled pickadate-unavailable';
} else {
className = 'pickadate-enabled';
}
if (date === today) {
className += ' pickadate-today';
}
dates.push({date: date, className: className});
}
scope.dates = dates;
};
scope.setDate = function(dateObj) {
if (isDateDisabled(dateObj)) return;
ngModel.$setViewValue(dateObj.date);
};
ngModel.$render = function () {
if ((date = ngModel.$modelValue) && (indexOf.call(disabledDates, date) === -1)) {
scope.currentDate = currentDate = dateUtils.stringToDate(date);
} else if (date) {
// if the initial date set by the user is in the disabled dates list, unset it
scope.setDate(undefined);
}
scope.render(currentDate);
};
scope.changeMonth = function (offset) {
// If the current date is January 31th, setting the month to date.getMonth() + 1
// sets the date to March the 3rd, since the date object adds 30 days to the current
// date. Settings the date to the 2nd day of the month is a workaround to prevent this
// behaviour
currentDate.setDate(1);
currentDate.setMonth(currentDate.getMonth() + offset);
scope.render(currentDate);
};
function isDateDisabled(dateObj) {
return (/pickadate-disabled/.test(dateObj.className));
}
}
};
}]);
})(window.angular);