Skip to content

Commit f7c430a

Browse files
committed
feat(datepicker): timeless-json-mode
datepickerConfig option timelessJsonMode for json models when we don't care about the time. * on init and parse: sets model to 'yyyy-MM-dd' format * on format (1st load): converts model to date before normal formatting * timelessJsonUtils constant with method jsonDateToTicks for use with min-date, max-date * alias jsonDateAddDays * set timelessJsonMode = 'withUtils' to add methods to parent scope * also: jsonToDate -- used internally [codepen](http://codepen.io/davious/pen/XmMbma?editors=101) Related: angular-ui#1891, angular-ui#2906, angular-ui#2952
1 parent 9e71da8 commit f7c430a

File tree

2 files changed

+238
-5
lines changed

2 files changed

+238
-5
lines changed

Diff for: src/datepicker/datepicker.js

+65-5
Original file line numberDiff line numberDiff line change
@@ -516,16 +516,18 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
516516
closeOnDateSelection: true,
517517
appendToBody: false,
518518
showButtonBar: true,
519-
onOpenFocus: true
519+
onOpenFocus: true,
520+
timelessJsonMode: false
520521
})
521522

522-
.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout',
523-
function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout) {
523+
.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'timelessJsonUtils',
524+
function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, timelessJsonUtils) {
524525
var self = this;
525526
var cache = {},
526527
isHtml5DateInput = false;
527528
var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
528529
datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl,
530+
timelessJsonMode, addTimelessJsonUtils,
529531
ngModel, $popup;
530532

531533
scope.watchData = {};
@@ -537,6 +539,8 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
537539
onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
538540
datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl;
539541
datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
542+
timelessJsonMode = angular.isDefined(attrs.timelessJsonMode) ? attrs.timelessJsonMode : datepickerPopupConfig.timelessJsonMode;
543+
addTimelessJsonUtils = timelessJsonMode && timelessJsonMode.toString().toLowerCase() == 'withutils';
540544

541545
scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
542546

@@ -599,6 +603,10 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
599603
});
600604
}
601605

606+
if(addTimelessJsonUtils) {
607+
angular.extend(scope.$parent, timelessJsonUtils);
608+
}
609+
602610
angular.forEach(['minMode', 'maxMode', 'minDate', 'maxDate', 'datepickerMode', 'initDate', 'shortcutPropagation'], function(key) {
603611
if (attrs[key]) {
604612
var getAttribute = $parse(attrs[key]);
@@ -639,11 +647,22 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
639647
ngModel.$validators.date = validator;
640648
ngModel.$parsers.unshift(parseDate);
641649
ngModel.$formatters.push(function(value) {
650+
if (timelessJsonMode) {
651+
value = timelessJsonUtils.jsonToDate(value, ngModel, dateFilter, dateFormat);
652+
}
642653
scope.date = value;
643654
return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
644655
});
645656
} else {
657+
if (timelessJsonMode) {
658+
ngModel.$parsers.push(function(viewValue) {
659+
return dateFilter(viewValue, 'yyyy-MM-dd');
660+
});
661+
}
646662
ngModel.$formatters.push(function(value) {
663+
if (timelessJsonMode) {
664+
value = timelessJsonUtils.jsonToDate(value, ngModel, dateFilter, dateFormat);
665+
}
647666
scope.date = value;
648667
return value;
649668
});
@@ -773,14 +792,17 @@ function(scope, element, attrs, $compile, $parse, $document, $rootScope, $positi
773792
if (isNaN(date)) {
774793
return undefined;
775794
} else {
776-
return date;
795+
return timelessJsonMode ? dateFilter(date, 'yyyy-MM-dd') : date;
777796
}
778797
} else {
779798
return undefined;
780799
}
781800
}
782801

783802
function validator(modelValue, viewValue) {
803+
if(timelessJsonMode) {
804+
modelValue = timelessJsonUtils.jsonToDate(modelValue, ngModel, dateFilter, dateFormat);
805+
}
784806
var value = modelValue || viewValue;
785807

786808
if (!attrs.ngRequired && !value) {
@@ -1004,4 +1026,42 @@ angular.module('ui.bootstrap.datepicker')
10041026
}
10051027
}
10061028
};
1007-
}]);
1029+
}])
1030+
1031+
.constant('timelessJsonUtils', (function() {
1032+
return {
1033+
jsonToDate: jsonToDate,
1034+
jsonDateToTicks: jsonDateToTicks,
1035+
jsonDateAddDays: jsonDateToTicks
1036+
};
1037+
function jsonToDate(date, ngModel, dateFilter, dateFormat) {
1038+
if (date && angular.isString(date) && date.indexOf('-') > -1) {
1039+
var update = date.indexOf('T') > -1;
1040+
date = date.substring(0, 10).split('-');
1041+
date = new Date(+date[0], +date[1] - 1, +date[2]);
1042+
if (update && ngModel) {
1043+
ngModel.$setViewValue(dateFilter(date, dateFormat));
1044+
ngModel.$render();
1045+
}
1046+
}
1047+
return date;
1048+
}
1049+
function jsonDateToTicks(date, additionalDays) {
1050+
if(!date) {
1051+
return null;
1052+
}
1053+
if (angular.isString(date) && date.indexOf('-') > -1) {
1054+
date = date.substring(0, 10).split('-');
1055+
date = new Date(+date[0], +date[1] - 1, +date[2]);
1056+
}
1057+
if(!date.getTime) {
1058+
return null;
1059+
}
1060+
var ticks = date.getTime();
1061+
if(additionalDays) {
1062+
ticks += (+additionalDays * 1000 * 60 * 60 * 24);
1063+
}
1064+
return ticks;
1065+
}
1066+
})()
1067+
);

Diff for: src/datepicker/test/datepicker.spec.js

+173
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,179 @@ describe('datepicker directive', function() {
12541254

12551255
});
12561256

1257+
describe('uibDatepickerPopupConfig.timelessJsonMode = true', function() {
1258+
var inputEl, dropdownEl, $document, $sniffer, $timeout;
1259+
1260+
function assignElements(wrapElement) {
1261+
inputEl = wrapElement.find('input');
1262+
dropdownEl = wrapElement.find('ul');
1263+
element = dropdownEl.find('table');
1264+
}
1265+
1266+
beforeEach(inject(function(uibDatepickerPopupConfig) {
1267+
uibDatepickerPopupConfig.timelessJsonMode = true;
1268+
$rootScope.date = '2010-09-30T00:00:00.000';
1269+
$rootScope.isopen = true;
1270+
var wrapper = $compile('<div><input ng-model="date" uib-datepicker-popup="MM/dd/yyyy" is-open="isopen"></div>')($rootScope);
1271+
$rootScope.$digest();
1272+
assignElements(wrapper);
1273+
}));
1274+
1275+
afterEach(inject(function (uibDatepickerPopupConfig) {
1276+
// return it to the original state
1277+
delete uibDatepickerPopupConfig.timelessJsonMode;
1278+
}));
1279+
1280+
it('formats model to yyyy-MM-dd on initialization', function() {
1281+
expect(inputEl.val()).toBe('09/30/2010');
1282+
expect($rootScope.date).toEqual('2010-09-30');
1283+
});
1284+
1285+
it('updates the input when a day is clicked', function() {
1286+
clickOption(17);
1287+
expect(inputEl.val()).toBe('09/15/2010');
1288+
expect($rootScope.date).toEqual('2010-09-15');
1289+
});
1290+
});
1291+
1292+
describe('timeless-json-mode="true"', function() {
1293+
var inputEl, dropdownEl, $document, $sniffer, $timeout;
1294+
1295+
function assignElements(wrapElement) {
1296+
inputEl = wrapElement.find('input');
1297+
dropdownEl = wrapElement.find('ul');
1298+
element = dropdownEl.find('table');
1299+
}
1300+
1301+
beforeEach(inject(function() {
1302+
$rootScope.date = '2010-09-30T00:00:00.000';
1303+
$rootScope.isopen = true;
1304+
var wrapper = $compile('<div><input ng-model="date" uib-datepicker-popup="MM/dd/yyyy" timeless-json-mode="true" is-open="isopen"></div>')($rootScope);
1305+
$rootScope.$digest();
1306+
assignElements(wrapper);
1307+
}));
1308+
1309+
it('formats model to yyyy-MM-dd on initialization', function() {
1310+
expect(inputEl.val()).toBe('09/30/2010');
1311+
expect($rootScope.date).toEqual('2010-09-30');
1312+
});
1313+
1314+
it('updates the input when a day is clicked', function() {
1315+
clickOption(17);
1316+
expect(inputEl.val()).toBe('09/15/2010');
1317+
expect($rootScope.date).toEqual('2010-09-15');
1318+
});
1319+
});
1320+
1321+
describe('uibDatepickerPopupConfig.timelessJsonMode = true with HTML5', function() {
1322+
var inputEl, dropdownEl, $document, $sniffer, $timeout;
1323+
1324+
function assignElements(wrapElement) {
1325+
inputEl = wrapElement.find('input');
1326+
dropdownEl = wrapElement.find('ul');
1327+
element = dropdownEl.find('table');
1328+
}
1329+
1330+
beforeEach(inject(function(uibDatepickerPopupConfig) {
1331+
uibDatepickerPopupConfig.timelessJsonMode = true;
1332+
$rootScope.date = '2010-09-30T00:00:00.000';
1333+
$rootScope.isopen = true;
1334+
var wrapper = $compile('<div><input type="date" ng-model="date" uib-datepicker-popup is-open="isopen"></div>')($rootScope);
1335+
$rootScope.$digest();
1336+
assignElements(wrapper);
1337+
}));
1338+
1339+
afterEach(inject(function (uibDatepickerPopupConfig) {
1340+
// return it to the original state
1341+
delete uibDatepickerPopupConfig.timelessJsonMode;
1342+
}));
1343+
1344+
it('formats model to yyyy-MM-dd on initialization', function() {
1345+
expect(inputEl.val()).toBe('2010-09-30');
1346+
expect($rootScope.date).toEqual('2010-09-30');
1347+
});
1348+
1349+
it('updates the input when a day is clicked', function() {
1350+
clickOption(17);
1351+
expect(inputEl.val()).toBe('2010-09-15');
1352+
expect($rootScope.date).toEqual('2010-09-15');
1353+
});
1354+
});
1355+
1356+
describe('jsonDateUtils service: timelessJsonUtils.jsonDateToTicks', function() {
1357+
var inputEl, dropdownEl, $document, $sniffer, $timeout;
1358+
1359+
function assignElements(wrapElement) {
1360+
inputEl = wrapElement.find('input');
1361+
dropdownEl = wrapElement.find('ul');
1362+
element = dropdownEl.find('table');
1363+
}
1364+
1365+
beforeEach(inject(function(timelessJsonUtils) {
1366+
$rootScope.toTicks = timelessJsonUtils.jsonDateToTicks;
1367+
$rootScope.date = '2010-09-15T00:00:00.000';
1368+
$rootScope.isopen = true;
1369+
var wrapper = $compile('<div><input ng-model="date" uib-datepicker-popup="MM/dd/yyyy" timeless-json-mode="true" max-date="toTicks(date, 1)" is-open="isopen"></div>')($rootScope);
1370+
$rootScope.$digest();
1371+
assignElements(wrapper);
1372+
}));
1373+
1374+
it('service method jsonDateUtils.jsonDateToTicks should work with max-date', function() {
1375+
expect(getAllOptionsEl().eq(19).prop('disabled')).toBe(true);
1376+
});
1377+
});
1378+
1379+
describe('uibDatepickerPopupConfig.timelessJsonMode = "withUtils"', function() {
1380+
var inputEl, dropdownEl, $document, $sniffer, $timeout;
1381+
1382+
function assignElements(wrapElement) {
1383+
inputEl = wrapElement.find('input');
1384+
dropdownEl = wrapElement.find('ul');
1385+
element = dropdownEl.find('table');
1386+
}
1387+
1388+
beforeEach(inject(function(uibDatepickerPopupConfig) {
1389+
uibDatepickerPopupConfig.timelessJsonMode = 'withUtils';
1390+
$rootScope.date = '2010-09-15T00:00:00.000';
1391+
$rootScope.isopen = true;
1392+
var wrapper = $compile('<div><input ng-model="date" uib-datepicker-popup="MM/dd/yyyy" max-date="jsonDateAddDays(date, 1)" is-open="isopen"></div>')($rootScope);
1393+
$rootScope.$digest();
1394+
assignElements(wrapper);
1395+
}));
1396+
1397+
afterEach(inject(function (uibDatepickerPopupConfig) {
1398+
// return it to the original state
1399+
delete uibDatepickerPopupConfig.timelessJsonMode;
1400+
}));
1401+
1402+
1403+
it('jsonDateToTicks/jsonDateAddDays should be on parent scope and work with max-date', function() {
1404+
expect(getAllOptionsEl().eq(19).prop('disabled')).toBe(true);
1405+
});
1406+
});
1407+
1408+
describe('jsonDateToTicks/jsonDateAddDays with timeless-json-mode="withUtils"', function() {
1409+
var inputEl, dropdownEl, $document, $sniffer, $timeout;
1410+
1411+
function assignElements(wrapElement) {
1412+
inputEl = wrapElement.find('input');
1413+
dropdownEl = wrapElement.find('ul');
1414+
element = dropdownEl.find('table');
1415+
}
1416+
1417+
beforeEach(inject(function() {
1418+
$rootScope.date = '2010-09-15T00:00:00.000';
1419+
$rootScope.isopen = true;
1420+
var wrapper = $compile('<div><input ng-model="date" uib-datepicker-popup="MM/dd/yyyy" timeless-json-mode="withUtils" timeless-json-utils="true" max-date="jsonDateToTicks(date, 1)" is-open="isopen"></div>')($rootScope);
1421+
$rootScope.$digest();
1422+
assignElements(wrapper);
1423+
}));
1424+
1425+
it('jsonDateToTicks/jsonDateAddDays should be on parent scope and work with max-date', function() {
1426+
expect(getAllOptionsEl().eq(19).prop('disabled')).toBe(true);
1427+
});
1428+
});
1429+
12571430
describe('setting datepickerPopupConfig inside ng-if', function() {
12581431
var originalConfig = {};
12591432
beforeEach(inject(function (uibDatepickerPopupConfig) {

0 commit comments

Comments
 (0)