AIRAVATA-3565 Better handling of extended user profile validation
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
index d1a6df0..0b0b08d 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
@@ -6,6 +6,8 @@
:key="extendedUserProfileField.id"
:is="getEditor(extendedUserProfileField)"
:extended-user-profile-field="extendedUserProfileField"
+ @valid="recordValidChildComponent(extendedUserProfileField.id)"
+ @invalid="recordInvalidChildComponent(extendedUserProfileField.id)"
/>
</template>
</div>
@@ -17,13 +19,13 @@
import ExtendedUserProfileSingleChoiceFieldEditor from "./ExtendedUserProfileSingleChoiceFieldEditor.vue";
import ExtendedUserProfileTextFieldEditor from "./ExtendedUserProfileTextFieldEditor.vue";
import ExtendedUserProfileUserAgreementFieldEditor from "./ExtendedUserProfileUserAgreementFieldEditor.vue";
+import { mixins } from "django-airavata-common-ui";
export default {
+ mixins: [mixins.ValidationParent],
computed: {
...mapGetters("extendedUserProfile", ["extendedUserProfileFields"]),
valid() {
- return this.$refs.extendedUserProfileFieldComponents.every(
- (c) => c.valid
- );
+ return this.childComponentsAreValid;
},
},
methods: {
@@ -45,6 +47,9 @@
);
}
},
+ touch() {
+ this.$refs.extendedUserProfileFieldComponents.forEach((c) => c.touch());
+ },
},
};
</script>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileFieldEditor.vue
index 1115ea7..13b7a66 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileFieldEditor.vue
@@ -3,6 +3,14 @@
:label="extendedUserProfileField.name"
:description="extendedUserProfileField.help_text"
>
+ <template #label>
+ {{ extendedUserProfileField.name }}
+ <small
+ v-if="!extendedUserProfileField.required"
+ class="text-muted text-small"
+ >(Optional)</small
+ >
+ </template>
<b-card
v-for="link in extendedUserProfileField.links"
:key="link.id"
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
index ff02eea..17faef6 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
@@ -136,6 +136,17 @@
},
validateState: errors.vuelidateHelpers.validateState,
validateStateErrorOnly: errors.vuelidateHelpers.validateStateErrorOnly,
+ touch() {
+ this.$v.$touch();
+ },
+ },
+ watch: {
+ valid: {
+ handler(valid) {
+ this.$emit(valid ? "valid" : "invalid");
+ },
+ immediate: true,
+ },
},
};
</script>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
index 4087208..61c672f 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
@@ -138,6 +138,17 @@
},
validateState: errors.vuelidateHelpers.validateState,
validateStateErrorOnly: errors.vuelidateHelpers.validateStateErrorOnly,
+ touch() {
+ this.$v.$touch();
+ },
+ },
+ watch: {
+ valid: {
+ handler(valid) {
+ this.$emit(valid ? "valid" : "invalid");
+ },
+ immediate: true,
+ },
},
};
</script>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue
index 37b451d..3e2a8e6 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue
@@ -45,6 +45,17 @@
methods: {
...mapMutations("extendedUserProfile", ["setTextValue"]),
validateState: errors.vuelidateHelpers.validateState,
+ touch() {
+ this.$v.$touch();
+ },
+ },
+ watch: {
+ valid: {
+ handler(valid) {
+ this.$emit(valid ? "valid" : "invalid");
+ },
+ immediate: true,
+ },
},
};
</script>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue
index 487d6b0..d0fe40a 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue
@@ -65,6 +65,17 @@
},
validateState: errors.vuelidateHelpers.validateState,
validateStateErrorOnly: errors.vuelidateHelpers.validateStateErrorOnly,
+ touch() {
+ this.$v.$touch();
+ },
+ },
+ watch: {
+ valid: {
+ handler(valid) {
+ this.$emit(valid ? "valid" : "invalid");
+ },
+ immediate: true,
+ },
},
};
</script>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
index 86fbb39..3e8d101 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
@@ -105,14 +105,7 @@
})
);
} else {
- // TODO: make sure to highlight which fields are invalid
- notifications.NotificationList.add(
- new notifications.Notification({
- type: "WARNING",
- message: "The form is invalid. Please fix and try again.",
- duration: 5,
- })
- );
+ this.$refs.extendedUserProfileEditor.touch();
}
},
async handleResendEmailVerification() {
diff --git a/django_airavata/static/common/js/index.js b/django_airavata/static/common/js/index.js
index b4bcc5e..68c9719 100644
--- a/django_airavata/static/common/js/index.js
+++ b/django_airavata/static/common/js/index.js
@@ -31,6 +31,7 @@
import ListLayout from "./layouts/ListLayout.vue";
+import ValidationParent from "./mixins/ValidationParent";
import VModelMixin from "./mixins/VModelMixin";
import Notification from "./notifications/Notification";
@@ -80,6 +81,7 @@
};
const mixins = {
+ ValidationParent,
VModelMixin,
};
diff --git a/django_airavata/static/common/js/mixins/ValidationParent.js b/django_airavata/static/common/js/mixins/ValidationParent.js
new file mode 100644
index 0000000..6e89563
--- /dev/null
+++ b/django_airavata/static/common/js/mixins/ValidationParent.js
@@ -0,0 +1,30 @@
+/**
+ * Aggregate validation state of child components. Child components should
+ * dispatch 'valid' and 'invalid' events and component using this mixin should
+ * call recordValidChildComponent or recordInvalidChildComponent, respectively.
+ */
+export default {
+ data: function () {
+ return {
+ invalidChildComponents: [],
+ };
+ },
+ computed: {
+ childComponentsAreValid() {
+ return this.invalidChildComponents.length === 0;
+ },
+ },
+ methods: {
+ recordInvalidChildComponent(childComponentId) {
+ if (!this.invalidChildComponents.includes(childComponentId)) {
+ this.invalidChildComponents.push(childComponentId);
+ }
+ },
+ recordValidChildComponent(childComponentId) {
+ if (this.invalidChildComponents.includes(childComponentId)) {
+ const index = this.invalidChildComponents.indexOf(childComponentId);
+ this.invalidChildComponents.splice(index, 1);
+ }
+ },
+ },
+};