Skip to content

Commit

Permalink
Refine input validation
Browse files Browse the repository at this point in the history
- Remove usage of JS core checkValidity() in favour of custom backend compliant validation
- Rewrite and refactor with removal of form tag in favour of section
- Scope styles
- Remove many uses of $nextTick
- Refine disabled state logic
- Translate account property constants

Signed-off-by: Christopher Ng <chrng8@gmail.com>
  • Loading branch information
Pytal committed Aug 24, 2021
1 parent 5e67677 commit d738ca4
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { showError } from '@nextcloud/dialogs'
import debounce from 'debounce'
import { savePrimaryDisplayName } from '../../../service/PersonalInfo/DisplayNameService'
import { validateDisplayName } from '../../../utils/validate'
// TODO Global avatar updating on events (e.g. updating the displayname) is currently being handled by global js, investigate using https://github.com/nextcloud/nextcloud-event-bus for global avatar updating
Expand Down Expand Up @@ -81,7 +82,7 @@ export default {
},
debounceDisplayNameChange: debounce(async function(displayName) {
if (this.$refs.displayName?.checkValidity() && this.isValid(displayName)) {
if (validateDisplayName(displayName)) {
await this.updatePrimaryDisplayName(displayName)
}
}, 500),
Expand Down Expand Up @@ -115,10 +116,6 @@ export default {
}
},
isValid(displayName) {
return displayName !== ''
},
onScopeChange(scope) {
this.$emit('update:scope', scope)
},
Expand All @@ -131,8 +128,18 @@ export default {
display: grid;
align-items: center;
input[type=text] {
input {
grid-area: 1 / 1;
height: 34px;
width: 100%;
margin: 3px 3px 3px 0;
padding: 7px 6px;
cursor: text;
font-family: var(--font-face);
border: 1px solid var(--color-border-dark);
border-radius: var(--border-radius);
background-color: var(--color-main-background);
color: var(--color-main-text);
}
.displayname__actions-container {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,25 @@
-->

<template>
<form
ref="form"
class="section"
@submit.stop.prevent="() => {}">
<section>
<HeaderBar
:account-property="accountProperty"
label-for="displayname"
:is-editable="displayNameChangeSupported"
:is-valid-form="isValidForm"
:is-valid-section="isValidSection"
:handle-scope-change="savePrimaryDisplayNameScope"
:scope.sync="primaryDisplayName.scope" />

<template v-if="displayNameChangeSupported">
<DisplayName
:scope.sync="primaryDisplayName.scope"
:display-name.sync="primaryDisplayName.value"
@update:display-name="onUpdateDisplayName" />
:scope.sync="primaryDisplayName.scope" />
</template>

<span v-else>
{{ primaryDisplayName.value || t('settings', 'No full name set') }}
</span>
</form>
</section>
</template>

<script>
Expand All @@ -53,6 +49,7 @@ import HeaderBar from '../shared/HeaderBar'
import { ACCOUNT_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants'
import { savePrimaryDisplayNameScope } from '../../../service/PersonalInfo/DisplayNameService'
import { validateDisplayName } from '../../../utils/validate'
const { displayNames: { primaryDisplayName } } = loadState('settings', 'personalInfoParameters', {})
const { displayNameChangeSupported } = loadState('settings', 'accountParameters', {})
Expand All @@ -69,31 +66,24 @@ export default {
return {
accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.DISPLAYNAME,
displayNameChangeSupported,
isValidForm: true,
primaryDisplayName,
savePrimaryDisplayNameScope,
}
},
mounted() {
this.$nextTick(() => this.updateFormValidity())
},
methods: {
onUpdateDisplayName() {
this.$nextTick(() => this.updateFormValidity())
},
updateFormValidity() {
this.isValidForm = this.$refs.form?.checkValidity()
computed: {
isValidSection() {
return validateDisplayName(this.primaryDisplayName.value)
},
},
}
</script>

<style lang="scss" scoped>
form::v-deep button {
&:disabled {
section {
padding: 10px 10px;
&::v-deep button:disabled {
cursor: default;
}
}
Expand Down
29 changes: 21 additions & 8 deletions apps/settings/src/components/PersonalInfo/EmailSection/Email.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
<ActionButton
:aria-label="deleteEmailLabel"
:close-after-click="true"
:disabled="deleteDisabled"
icon="icon-delete"
@click.stop.prevent="deleteEmail">
{{ deleteEmailLabel }}
Expand All @@ -83,6 +84,7 @@ import FederationControl from '../shared/FederationControl'
import { ACCOUNT_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants'
import { savePrimaryEmail, saveAdditionalEmail, saveAdditionalEmailScope, updateAdditionalEmail, removeAdditionalEmail } from '../../../service/PersonalInfo/EmailService'
import { validateEmail } from '../../../utils/validate'
export default {
name: 'Email',
Expand Down Expand Up @@ -126,9 +128,13 @@ export default {
computed: {
deleteDisabled() {
if (this.primary) {
return this.email === ''
// Disable for empty primary email as there is nothing to delete
// OR when initialEmail (reflects server state) and email (current input) are not the same
return this.email === '' || this.initialEmail !== this.email
} else if (this.initialEmail !== '') {
return this.initialEmail !== this.email
}
return this.email !== '' && !this.isValid(this.email)
return false
},
deleteEmailLabel() {
Expand Down Expand Up @@ -159,6 +165,7 @@ export default {
mounted() {
if (!this.primary && this.initialEmail === '') {
// $nextTick is needed here, otherwise it may not always work https://stackoverflow.com/questions/51922767/autofocus-input-on-mount-vue-ios/63485725#63485725
this.$nextTick(() => this.$refs.email?.focus())
}
},
Expand All @@ -170,7 +177,7 @@ export default {
},
debounceEmailChange: debounce(async function(email) {
if (this.$refs.email?.checkValidity() || email === '') {
if (validateEmail(email) || email === '') {
if (this.primary) {
await this.updatePrimaryEmail(email)
} else {
Expand Down Expand Up @@ -282,10 +289,6 @@ export default {
}
},
isValid(email) {
return /^\S+$/.test(email)
},
onScopeChange(scope) {
this.$emit('update:scope', scope)
},
Expand All @@ -298,8 +301,18 @@ export default {
display: grid;
align-items: center;
input[type=email] {
input {
grid-area: 1 / 1;
height: 34px;
width: 100%;
margin: 3px 3px 3px 0;
padding: 7px 6px;
cursor: text;
font-family: var(--font-face);
border: 1px solid var(--color-border-dark);
border-radius: var(--border-radius);
background-color: var(--color-main-background);
color: var(--color-main-text);
}
.email__actions-container {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,14 @@
-->

<template>
<form
ref="form"
class="section"
@submit.stop.prevent="() => {}">
<section>
<HeaderBar
:account-property="accountProperty"
label-for="email"
:handle-scope-change="savePrimaryEmailScope"
:is-editable="displayNameChangeSupported"
:is-multi-value-supported="true"
:is-valid-form="isValidForm"
:is-valid-section="isValidSection"
:scope.sync="primaryEmail.scope"
@add-additional="onAddAdditionalEmail" />

Expand All @@ -52,7 +49,7 @@
<span v-else>
{{ primaryEmail.value || t('settings', 'No email address set') }}
</span>
</form>
</section>
</template>

<script>
Expand All @@ -64,6 +61,7 @@ import HeaderBar from '../shared/HeaderBar'
import { ACCOUNT_PROPERTY_READABLE_ENUM, DEFAULT_ADDITIONAL_EMAIL_SCOPE } from '../../../constants/AccountPropertyConstants'
import { savePrimaryEmail, savePrimaryEmailScope, removeAdditionalEmail } from '../../../service/PersonalInfo/EmailService'
import { validateEmail } from '../../../utils/validate'
const { emails: { additionalEmails, primaryEmail } } = loadState('settings', 'personalInfoParameters', {})
const { displayNameChangeSupported } = loadState('settings', 'accountParameters', {})
Expand All @@ -81,13 +79,24 @@ export default {
accountProperty: ACCOUNT_PROPERTY_READABLE_ENUM.EMAIL,
additionalEmails,
displayNameChangeSupported,
isValidForm: true,
primaryEmail,
savePrimaryEmailScope,
}
},
computed: {
firstAdditionalEmail() {
if (this.additionalEmails.length) {
return this.additionalEmails[0].value
}
return null
},
isValidSection() {
return validateEmail(this.primaryEmail.value)
&& this.additionalEmails.map(({ value }) => value).every(validateEmail)
},
primaryEmailValue: {
get() {
return this.primaryEmail.value
Expand All @@ -96,41 +105,25 @@ export default {
this.primaryEmail.value = value
},
},
firstAdditionalEmail() {
if (this.additionalEmails.length) {
return this.additionalEmails[0].value
}
return null
},
},
mounted() {
this.$nextTick(() => this.updateFormValidity())
},
methods: {
onAddAdditionalEmail() {
if (this.$refs.form?.checkValidity()) {
if (this.isValidSection) {
this.additionalEmails.push({ value: '', scope: DEFAULT_ADDITIONAL_EMAIL_SCOPE })
this.$nextTick(() => this.updateFormValidity())
}
},
onDeleteAdditionalEmail(index) {
this.$delete(this.additionalEmails, index)
this.$nextTick(() => this.updateFormValidity())
},
async onUpdateEmail() {
this.$nextTick(() => this.updateFormValidity())
if (this.primaryEmailValue === '' && this.firstAdditionalEmail) {
const deletedEmail = this.firstAdditionalEmail
await this.deleteFirstAdditionalEmail()
this.primaryEmailValue = deletedEmail
await this.updatePrimaryEmail()
this.$nextTick(() => this.updateFormValidity())
}
},
Expand Down Expand Up @@ -166,17 +159,15 @@ export default {
this.logger.error(errorMessage, error)
}
},
updateFormValidity() {
this.isValidForm = this.$refs.form?.checkValidity()
},
},
}
</script>
<style lang="scss" scoped>
form::v-deep button {
&:disabled {
section {
padding: 10px 10px;
&::v-deep button:disabled {
cursor: default;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export default {
data() {
return {
accountPropertyLowerCase: this.accountProperty.toLowerCase(),
accountPropertyLowerCase: this.accountProperty.toLocaleLowerCase(),
initialScope: this.scope,
}
},
Expand Down
19 changes: 16 additions & 3 deletions apps/settings/src/components/PersonalInfo/shared/HeaderBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
<template>
<h3>
<label :for="labelFor">
{{ t('settings', accountProperty) }}
<!-- Already translated as required by prop validator -->
{{ accountProperty }}
</label>

<FederationControl
Expand All @@ -35,7 +36,7 @@
<template v-if="isEditable && isMultiValueSupported">
<AddButton
class="add-button"
:disabled="!isValidForm"
:disabled="!isValidSection"
@click.stop.prevent="onAddAdditional" />
</template>
</h3>
Expand Down Expand Up @@ -73,7 +74,7 @@ export default {
type: Boolean,
default: false,
},
isValidForm: {
isValidSection: {
type: Boolean,
default: true,
},
Expand Down Expand Up @@ -106,6 +107,18 @@ export default {
</script>

<style lang="scss" scoped>
h3 {
display: inline-flex;
width: 100%;
margin: 12px 0 0 0;
font-size: 16px;
color: var(--color-text-light);
label {
cursor: pointer;
}
}
.federation-control {
margin: -12px 0 0 8px;
}
Expand Down
Loading

0 comments on commit d738ca4

Please sign in to comment.