From d67bc0654c296d744e026de269a0508549b3f057 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Wed, 2 Nov 2016 15:39:07 -0400 Subject: [PATCH] Fixes: inconsistent form validity & failing redirect after saving a project --- awx/ui/client/src/controllers/Projects.js | 2 +- awx/ui/client/src/shared/directives.js | 45 +++++++++++++++++----- awx/ui/client/src/shared/form-generator.js | 2 + 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/awx/ui/client/src/controllers/Projects.js b/awx/ui/client/src/controllers/Projects.js index b2eecdf301..0016a3dbe1 100644 --- a/awx/ui/client/src/controllers/Projects.js +++ b/awx/ui/client/src/controllers/Projects.js @@ -357,7 +357,7 @@ export function ProjectsAdd($scope, $rootScope, $compile, $location, $log, Rest.post(data) .success(function(data) { $scope.addedItem = data.id; - $state.go('projects.edit', { id: data.id }, { reload: true }); + $state.go('projects.edit', { project_id: data.id }, { reload: true }); }) .error(function(data, status) { Wait('stop'); diff --git a/awx/ui/client/src/shared/directives.js b/awx/ui/client/src/shared/directives.js index 54a5eed72b..cd14cdb17a 100644 --- a/awx/ui/client/src/shared/directives.js +++ b/awx/ui/client/src/shared/directives.js @@ -391,7 +391,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper']) .directive('awlookup', ['Rest', 'GetBasePath', '$q', function(Rest, GetBasePath, $q) { return { require: 'ngModel', - link: function(scope, elm, attrs, ctrl) { + link: function(scope, elm, attrs, fieldCtrl) { let query, basePath, @@ -399,8 +399,18 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper']) // query the API to see if field value corresponds to a valid resource // .ng-pending will be applied to the directive element while the request is outstanding - ctrl.$asyncValidators.validResource = function(modelValue, viewValue) { - if (viewValue) { + // form.$pending will contain object reference to any ngModelControllers with outstanding requests + fieldCtrl.$asyncValidators.validResource = function(modelValue, viewValue) { + + applyValidationStrategy(viewValue, fieldCtrl); + + return defer.promise; + }; + + function applyValidationStrategy(viewValue, ctrl) { + + // use supplied data attributes to build an endpoint, query, resolve outstanding promise + function applyValidation(viewValue) { basePath = GetBasePath(elm.attr('data-basePath')) || elm.attr('data-basePath'); query = elm.attr('data-query'); query = query.replace(/\:value/, encodeURI(viewValue)); @@ -412,17 +422,34 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'JobsHelper']) .then((res) => { if (res.data.results.length > 0) { scope[elm.attr('data-source')] = res.data.results[0].id; - ctrl.$setValidity('awlookup', true); - defer.resolve(true); + return setValidity(ctrl, true); } else { scope[elm.attr('data-source')] = null; - ctrl.$setValidity('awlookup', false); - defer.resolve(false); + return setValidity(ctrl, false); } }); } - return defer.promise; - }; + + function setValidity(ctrl, validity){ + ctrl.$setValidity('awlookup', validity); + return defer.resolve(validity); + } + + // Three common cases for clarity: + + // 1) Field is not required & pristine. Pass validation & skip async $pending state + // 2) Field is required. Always validate & use async $pending state + // 3) Field is not required, but is not $pristine. Always validate & use async $pending state + + // case 1 + if (!ctrl.$validators.required && ctrl.$pristine) { + return setValidity(ctrl, true); + } + // case 2 & 3 + else { + return applyValidation(viewValue); + } + } } }; }]) diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index af110494b4..f3ea41ea07 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -1140,6 +1140,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += "ng-model=\"" + field.sourceModel + '_' + field.sourceField + "\" "; html += "name=\"" + field.sourceModel + '_' + field.sourceField + "\" "; html += "class=\"form-control\" "; + html += (field.required) ? 'required ' : ''; html += (field.ngChange) ? this.attr(field, 'ngChange') : ""; html += (field.ngDisabled) ? this.attr(field, 'ngDisabled') : "" ; html += (field.ngRequired) ? this.attr(field, 'ngRequired') : ""; @@ -1156,6 +1157,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += `data-basePath="${field.basePath}"`; html += `data-source="${field.sourceModel}"`; html += `data-query="?${field.sourceField}__iexact=:value"`; + html += `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 300, 'blur': 0 } }"`; html += " awlookup >\n"; html += "\n";