angular.module('plunker', [])
.controller('MainCtrl', function($scope) {
$scope.user = {
//dob: '2011-04-02T22:00:00.000Z'
};
})
.directive('customdatepicker', [
'$window', '$templateCache',
function ($window, $templateCache) {
if(!$window.moment){
console.log('moment.js is required for this datepicker, http://momentjs.com/');
return {
link: function(){}
};
}
var fileName = 'datepicker.directive.html';
var template = $templateCache.get(fileName);
if(!template){
template = [
'<form name="datepicker">',
'<div class="btn-group btn-group-justified" role="group">',
'<div ng-repeat="i in localeOrder track by $index" class="btn-group" role="group">',
'<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">',
'<span ng-bind="date[options[i].name] || options[i].name"> </span>',
'<span class="caret"></span>',
'</button>',
'<ul class="dropdown-menu" role="menu">',
'<li ng-repeat="(j, option) in options[i].options track by $index" ng-class="{\'selectedval\': option.selected === true, \'disabled\': option.disabled === true}">',
'<a ng-click="select(options[i].name, option)" ng-bind="options[i].labels[j] || option.value"></a>',
'</li>',
'</ul>',
'</div>',
'</div>',
'</form>'
].join('');
$templateCache.put(fileName, template);
}
return {
restrict: 'A',
//replace: true,
require: 'ngModel',
templateUrl: fileName,
scope: {
model: '=ngModel',
minDate: '=minDate',
maxDate: '=maxDate',
locale: '=?locale'
},
link: function(scope, elem, attrs, ngModelCtrl){
var days = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
scope.localeOrder = ['days', 'months', 'years'];
// params object is the main object to be modified. It is iterated by ng-repeat in the dom.
var params = {
days: {
name: 'days',
initLabel: 'Day',
options: []
},
months: {
name: 'months',
initLabel: 'Month',
options: [],
},
years: {
name: 'years',
initLabel: 'Year',
options: []
}
};
// setting up exceptions because of minDate, maxDate;
var minDate;
var maxDate;
// setting minDate
if(scope.minDate && scope.minDate !== 'now'){
var tempMinDate;
if(typeof scope.minDate === 'number'){
// assume this is something like -100 or +100;
if(scope.minDate > 0){
tempMinDate = moment().add(scope.minDate, 'years');
} else if (scope.minDate < 0) {
tempMinDate = moment().subtract(Math.abs(scope.minDate), 'years');
} else {
tempMinDate = moment();
}
} else {
tempMinDate = moment(scope.minDate);
}
// setting it to minDate
minDate = tempMinDate;
} else {
minDate = moment();
}
// setting maxDate. THIS IS NOT DRY, BUT A PASTE FROM SETTING minDate !
if(scope.maxDate && scope.maxDate !== 'now'){
var tempMaxDate;
if(typeof scope.maxDate === 'number'){
// assume this is something like -100 or +100;
if(scope.maxDate > 0){
tempMaxDate = moment().add(scope.maxDate, 'years');
} else if (scope.maxDate < 0) {
tempMaxDate = moment().subtract(Math.abs(scope.maxDate), 'years');
} else {
tempMaxDate = moment();
}
} else {
tempMaxDate = moment(scope.maxDate);
}
// setting it to maxDate
maxDate = tempMaxDate;
} else {
maxDate = moment();
}
// copying scope.options, which is used in view.
scope.options = angular.copy(params);
// holder for date object, modified by scope.select(). Every change the method setupModel() is called, a new Date is created from this.
scope.date = {};
// Select the value;
scope.select = function(typeString, option){
if(option.disabled === undefined || option.disabled !== true){
scope.date[typeString] = option.value;
calcAvailableDates(scope.date);
setupModel();
}
};
// recreate scope.options based on params and date input;
function calcAvailableDates(dateObj){
var maxYear = maxDate.get('years')
var minYear = minDate.get('years');
var maxMonth = maxDate.get('month');
var minMonth = minDate.get('month');
var maxDay = maxDate.get('date');
var minDay = minDate.get('date');
// CALCULATE YEARS
if(minDate !== maxDate){
scope.options.years.options = [];
for (var i = minYear; i <= maxYear ; i++) {
var yearObj = {
value: i,
available: true
};
if(dateObj && dateObj.years === i){
yearObj.selected = true;
}
scope.options.years.options.push(yearObj);
}
} else {
// do something like assumptions, e.g. datepicker?
}
// CALCULATE MONTHS
if(minDate !== maxDate){
scope.options.months.options = [];
for (var i = 0; i < months.length; i++) {
var monthObj = {
value: months[i]
};
if(dateObj && dateObj.months === months[i]){
monthObj.selected = true;
}
if(dateObj && dateObj.years){
if(dateObj.years === minYear){
if(minMonth > i ){
monthObj.disabled = true;
}
} else if (dateObj.years === maxYear){
if(maxMonth < i ){
monthObj.disabled = true;
}
}
}
scope.options.months.options.push(monthObj);
}
} else {
// do something like assumptions, e.g. datepicker?
}
// CALCULATE DAYS
if(minDate !== maxDate){
scope.options.days.options = [];
if((dateObj && dateObj.years && dateObj.months) || dateObj.months){
var useYear;
if(dateObj.years){
useYear = dateObj.years
} else {
useYear = moment().get('year');
}
var totalDaysMonth = moment({
years: useYear,
months: months.indexOf(dateObj.months)
}).endOf('month').get('date');
for (var i = 0; i < totalDaysMonth; i++) {
var dayObj = {}
dayObj.value = i + 1;
if(dateObj && dateObj.days && dateObj.days === i + 1){
dayObj.selected = true;
}
// setting values disabled when they're mindate < or > maxDate
if(dateObj.years && dateObj.months){
if(dateObj.years === minYear && months.indexOf(dateObj.months) === minMonth){
if(minDay > i){
dayObj.disabled = true;
}
} else if (dateObj.years === maxYear && months.indexOf(dateObj.months) === maxMonth){
if(maxDay < i){
dayObj.disabled = true;
}
}
}
scope.options.days.options.push(dayObj);
}
} else {
// only days available, just use 31 days.
scope.options.days.options = [];
for (var i = 0; i < days.length; i++) {
var dayObj = {}
dayObj.value = i + 1;
if(dateObj && dateObj.days && dateObj.days === i + 1){
dayObj.selected = true;
}
scope.options.days.options.push(dayObj);
}
}
}
}
function setupModel(){
// Not setting before all values are available.
if(scope.date.years !== undefined && scope.date.months !== undefined && scope.date.days !== undefined){
var createDate = {
years: scope.date.years,
months: months.indexOf(scope.date.months),
date: scope.date.days,
};
createDate = moment.utc(createDate);
ngModelCtrl.$setViewValue(createDate)
}
}
// toView - what is transformed from model to view.
var toView = function(value){
if(value !== undefined){
var tempDate = moment(value);
calcAvailableDates({
years: tempDate.get('year'),
months: months[tempDate.get('months')],
days: tempDate.get('date')
});
} else {
calcAvailableDates();
}
if(value !== undefined){
var createDate = moment(value);
if(createDate.isValid()){
var obj = {};
obj.days = createDate.date();
obj.months = months[createDate.month()];
obj.years = createDate.year();
scope.date = obj;
}
} else {
scope.date = {};
}
setupModel();
return value;
};
// toModel - what is transformed from view to model (called by ngModelCtrl.$setViewValue(createDate))
// in setupModel()
var toModel = function(value){
console.log('toModel ', value)
if(value && value.isValid()){
if((value.isAfter(minDate) || minDate.isSame(value)) && (maxDate.isAfter(value) || maxDate.isSame(value)) ){
ngModelCtrl.$setValidity('not-allowed', true);
return value.format();
} else {
ngModelCtrl.$setValidity('not-allowed', false);
return undefined;
}
} else {
return undefined;
}
};
// Pushing functions to $parsers and $formatters.
ngModelCtrl.$parsers.push(toModel);
ngModelCtrl.$formatters.push(toView);
}
}
}
]);
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script data-require="angular.js@*" data-semver="1.3.7" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.7/angular.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.4/moment.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet">
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<div class="container">
<div customdatepicker class="datepicker" ng-model="user.dob" min-date="-20" max-date="'2018-05-02T22:00:00.000Z'"></div>
<hr>
min-date === -20 <br>
maximum === '2018-05-02T22:00:00.000Z'
<hr>
<pre ng-bind="user | json"></pre>
</div>
</body>
</html>
body {
padding-top: 50px;
}
.btn .caret {
margin-left: 0;
float: right;
margin-top: 8px;
}
a {
cursor: pointer;
}
.datepicker.ng-invalid > form > .btn-group {
border: 1px solid red;
border-radius: 5px;
}
.datepicker .selectedval a {
font-weight: 600;
font-size: 1.1em;
}