From 6c630277284b0033b5ca9a1b355399d0f8172dd8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 11 Aug 2015 12:31:31 -0700 Subject: [PATCH 1/4] GUAC-1213: Parse date values leniently. --- .../form/controllers/dateFieldController.js | 7 ++ .../app/form/directives/guacLenientDate.js | 79 +++++++++++++++++++ .../webapp/app/form/templates/dateField.html | 1 + 3 files changed, 87 insertions(+) create mode 100644 guacamole/src/main/webapp/app/form/directives/guacLenientDate.js diff --git a/guacamole/src/main/webapp/app/form/controllers/dateFieldController.js b/guacamole/src/main/webapp/app/form/controllers/dateFieldController.js index af325be93..34acabe62 100644 --- a/guacamole/src/main/webapp/app/form/controllers/dateFieldController.js +++ b/guacamole/src/main/webapp/app/form/controllers/dateFieldController.js @@ -38,6 +38,13 @@ angular.module('form').controller('dateFieldController', ['$scope', '$injector', */ $scope.modelOptions = { + /** + * Space-delimited list of events on which the model will be updated. + * + * @type String + */ + updateOn : 'blur', + /** * The time zone to use when reading/writing the Date object of the * model. diff --git a/guacamole/src/main/webapp/app/form/directives/guacLenientDate.js b/guacamole/src/main/webapp/app/form/directives/guacLenientDate.js new file mode 100644 index 000000000..fe21c8efc --- /dev/null +++ b/guacamole/src/main/webapp/app/form/directives/guacLenientDate.js @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * A directive which modifies the parsing and formatting of ngModel when used + * on an HTML5 date input field, relaxing the otherwise strict parsing and + * validation behavior. The behavior of this directive for other input elements + * is undefined. + */ +angular.module('form').directive('guacLenientDate', ['$injector', + function guacLenientDate($injector) { + + // Required services + var $filter = $injector.get('$filter'); + + /** + * Directive configuration object. + * + * @type Object. + */ + var config = { + restrict : 'A', + require : 'ngModel' + }; + + // Linking function + config.link = function linkGuacLenientDate($scope, $element, $attrs, ngModel) { + + // Parse date strings leniently + ngModel.$parsers = [function parse(viewValue) { + + // If blank, return null + if (!viewValue) + return null; + + // Match basic date pattern + var match = /([0-9]*)(?:-([0-9]*)(?:-([0-9]*))?)?/.exec(viewValue); + if (!match) + return null; + + // Determine year, month, and day based on pattern + var year = parseInt(match[1] || '0') || new Date().getFullYear(); + var month = parseInt(match[2] || '0') || 1; + var day = parseInt(match[3] || '0') || 1; + + // Convert to Date object + return new Date(Date.UTC(year, month - 1, day)); + + }]; + + // Format date strings as "yyyy-MM-dd" + ngModel.$formatters = [function format(modelValue) { + return modelValue ? $filter('date')(modelValue, 'yyyy-MM-dd', 'UTC') : ''; + }]; + + }; + + return config; + +}]); diff --git a/guacamole/src/main/webapp/app/form/templates/dateField.html b/guacamole/src/main/webapp/app/form/templates/dateField.html index 239f3f52a..a186e19a5 100644 --- a/guacamole/src/main/webapp/app/form/templates/dateField.html +++ b/guacamole/src/main/webapp/app/form/templates/dateField.html @@ -2,6 +2,7 @@ From 2f73c2032150eb79c9837e3fa0689d9c6eb0819f Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 11 Aug 2015 12:56:27 -0700 Subject: [PATCH 2/4] GUAC-1213: Parse time values leniently. --- .../form/controllers/timeFieldController.js | 7 ++ .../app/form/directives/guacLenientTime.js | 99 +++++++++++++++++++ .../webapp/app/form/templates/timeField.html | 1 + 3 files changed, 107 insertions(+) create mode 100644 guacamole/src/main/webapp/app/form/directives/guacLenientTime.js diff --git a/guacamole/src/main/webapp/app/form/controllers/timeFieldController.js b/guacamole/src/main/webapp/app/form/controllers/timeFieldController.js index 67547d23f..5cb831c76 100644 --- a/guacamole/src/main/webapp/app/form/controllers/timeFieldController.js +++ b/guacamole/src/main/webapp/app/form/controllers/timeFieldController.js @@ -38,6 +38,13 @@ angular.module('form').controller('timeFieldController', ['$scope', '$injector', */ $scope.modelOptions = { + /** + * Space-delimited list of events on which the model will be updated. + * + * @type String + */ + updateOn : 'blur', + /** * The time zone to use when reading/writing the Date object of the * model. diff --git a/guacamole/src/main/webapp/app/form/directives/guacLenientTime.js b/guacamole/src/main/webapp/app/form/directives/guacLenientTime.js new file mode 100644 index 000000000..c5a03b917 --- /dev/null +++ b/guacamole/src/main/webapp/app/form/directives/guacLenientTime.js @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * A directive which modifies the parsing and formatting of ngModel when used + * on an HTML5 time input field, relaxing the otherwise strict parsing and + * validation behavior. The behavior of this directive for other input elements + * is undefined. + */ +angular.module('form').directive('guacLenientTime', ['$injector', + function guacLenientTime($injector) { + + // Required services + var $filter = $injector.get('$filter'); + + /** + * Directive configuration object. + * + * @type Object. + */ + var config = { + restrict : 'A', + require : 'ngModel' + }; + + // Linking function + config.link = function linkGuacLenientTIme($scope, $element, $attrs, ngModel) { + + // Parse time strings leniently + ngModel.$parsers = [function parse(viewValue) { + + // If blank, return null + if (!viewValue) + return null; + + // Match basic time pattern + var match = /([0-9]*)(?::([0-9]*)(?::([0-9]*))?)?(?:\s*(a|p))?/.exec(viewValue.toLowerCase()); + if (!match) + return null; + + // Determine hour, minute, and second based on pattern + var hour = parseInt(match[1] || '0'); + var minute = parseInt(match[2] || '0'); + var second = parseInt(match[3] || '0'); + + // Handle AM/PM + if (match[4]) { + + // Interpret 12 AM as 00:00 and 12 PM as 12:00 + if (hour === 12) + hour = 0; + + // Increment hour to evening if PM + if (match[4] === 'p') + hour += 12; + + } + + // Wrap seconds and minutes into minutes and hours + minute += second / 60; second %= 60; + hour += minute / 60; minute %= 60; + + // Constrain hours to 0 - 23 + hour %= 24; + + // Convert to Date object + return new Date(Date.UTC(1970, 0, 1, hour, minute, second)); + + }]; + + // Format time strings as "yyyy-MM-dd" + ngModel.$formatters = [function format(modelValue) { + return modelValue ? $filter('date')(modelValue, 'HH:mm:ss', 'UTC') : ''; + }]; + + }; + + return config; + +}]); diff --git a/guacamole/src/main/webapp/app/form/templates/timeField.html b/guacamole/src/main/webapp/app/form/templates/timeField.html index 6b2651946..24ae968b0 100644 --- a/guacamole/src/main/webapp/app/form/templates/timeField.html +++ b/guacamole/src/main/webapp/app/form/templates/timeField.html @@ -2,6 +2,7 @@ From 79f672bfa97b3cbe3bee0dd7c3f5a4385fee0075 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 11 Aug 2015 13:09:29 -0700 Subject: [PATCH 3/4] GUAC-1213: Set blank value if date/time is completely invalid. --- .../webapp/app/form/controllers/dateFieldController.js | 9 ++++++++- .../webapp/app/form/controllers/timeFieldController.js | 9 ++++++++- .../main/webapp/app/form/directives/guacLenientDate.js | 6 +++++- .../main/webapp/app/form/directives/guacLenientTime.js | 6 +++++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/guacamole/src/main/webapp/app/form/controllers/dateFieldController.js b/guacamole/src/main/webapp/app/form/controllers/dateFieldController.js index 34acabe62..26b663f30 100644 --- a/guacamole/src/main/webapp/app/form/controllers/dateFieldController.js +++ b/guacamole/src/main/webapp/app/form/controllers/dateFieldController.js @@ -69,7 +69,14 @@ angular.module('form').controller('dateFieldController', ['$scope', '$injector', * set. */ var parseDate = function parseDate(str) { - return new Date(str + 'T00:00Z'); + + // Parse date, return blank if invalid + var parsedDate = new Date(str + 'T00:00Z'); + if (isNaN(parsedDate.getTime())) + return null; + + return parsedDate; + }; // Update typed value when model is changed diff --git a/guacamole/src/main/webapp/app/form/controllers/timeFieldController.js b/guacamole/src/main/webapp/app/form/controllers/timeFieldController.js index 5cb831c76..58f077524 100644 --- a/guacamole/src/main/webapp/app/form/controllers/timeFieldController.js +++ b/guacamole/src/main/webapp/app/form/controllers/timeFieldController.js @@ -69,7 +69,14 @@ angular.module('form').controller('timeFieldController', ['$scope', '$injector', * set. */ var parseTime = function parseTime(str) { - return new Date('1970-01-01T' + str + 'Z'); + + // Parse time, return blank if invalid + var parsedDate = new Date('1970-01-01T' + str + 'Z'); + if (isNaN(parsedDate.getTime())) + return null; + + return parsedDate; + }; // Update typed value when model is changed diff --git a/guacamole/src/main/webapp/app/form/directives/guacLenientDate.js b/guacamole/src/main/webapp/app/form/directives/guacLenientDate.js index fe21c8efc..4bd282f79 100644 --- a/guacamole/src/main/webapp/app/form/directives/guacLenientDate.js +++ b/guacamole/src/main/webapp/app/form/directives/guacLenientDate.js @@ -63,7 +63,11 @@ angular.module('form').directive('guacLenientDate', ['$injector', var day = parseInt(match[3] || '0') || 1; // Convert to Date object - return new Date(Date.UTC(year, month - 1, day)); + var parsedDate = new Date(Date.UTC(year, month - 1, day)); + if (isNaN(parsedDate.getTime())) + return null; + + return parsedDate; }]; diff --git a/guacamole/src/main/webapp/app/form/directives/guacLenientTime.js b/guacamole/src/main/webapp/app/form/directives/guacLenientTime.js index c5a03b917..98ecd590a 100644 --- a/guacamole/src/main/webapp/app/form/directives/guacLenientTime.js +++ b/guacamole/src/main/webapp/app/form/directives/guacLenientTime.js @@ -83,7 +83,11 @@ angular.module('form').directive('guacLenientTime', ['$injector', hour %= 24; // Convert to Date object - return new Date(Date.UTC(1970, 0, 1, hour, minute, second)); + var parsedDate = new Date(Date.UTC(1970, 0, 1, hour, minute, second)); + if (isNaN(parsedDate.getTime())) + return null; + + return parsedDate; }]; From 0cfcb62195d1b69bbebf9ec842d7665c94f051d1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 11 Aug 2015 13:39:12 -0700 Subject: [PATCH 4/4] GUAC-1213: Correct comment regarding time formatting. --- .../src/main/webapp/app/form/directives/guacLenientTime.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/form/directives/guacLenientTime.js b/guacamole/src/main/webapp/app/form/directives/guacLenientTime.js index 98ecd590a..03957b567 100644 --- a/guacamole/src/main/webapp/app/form/directives/guacLenientTime.js +++ b/guacamole/src/main/webapp/app/form/directives/guacLenientTime.js @@ -91,7 +91,7 @@ angular.module('form').directive('guacLenientTime', ['$injector', }]; - // Format time strings as "yyyy-MM-dd" + // Format time strings as "HH:mm:ss" ngModel.$formatters = [function format(modelValue) { return modelValue ? $filter('date')(modelValue, 'HH:mm:ss', 'UTC') : ''; }];