I've been integrating nuxt-i18n into one of my projects to enable multiple languages. It all works fine, but when I switch language while filling in a form, all data from the page is lost.
So, I created a form and use v-model to bind the data to the page's data object.
<div class="form-group">
<label for="username">{{ $t('form.username') }}</label>
<input v-model="user.username" type="username" class="form-control" id="username" />
</div>
This is the page's data object
data: () => {
return {
user: {
username: ''
}
}
So when I type a username in the field, the model is updated, as I expect. But as soon as I switch the language from that page, the data is lost as soon as the chosen language has been set.
This is how I switch the language when clicking, for example, the dutch flag icon.
switchLocalePath(locale.code) // local.code is 'nl' in this case
When the user switches the language, the slug should also update. The code below shows the settings for the i18n package in my nuxt.config.js file.
modules: [
['nuxt-i18n', {
locales: [
{
code: 'en',
iso: 'en-US',
name: 'English'
},
{
code: 'nl',
iso: 'nl-NL',
name: 'Nederlands'
}
],
defaultLocale: 'nl',
parsePages: false,
pages: {
'account/register': {
en: '/account/register',
nl: '/account/registreren'
},
'account/index': {
en: '/account/login',
nl: '/account/inloggen'
}
},
vueI18n: {
fallbackLocale: 'nl',
messages: { nl, en }
}
}],
]
The actual question
So almost everything works just as I expect. But every time I change the language, the page's data object is cleared (It doesn't seem like the page actually reloads). So when filling in a form, then changing the language before submitting, all data is lost.
How can I make sure, if possible, that all data presists when toggling the language?
Thanks in advance!
I use this method to change the locale and the same behavior occurs.
<v-list>
<v-list-item
v-for="locale in availableLocales"
:key="locale.code"
#click="$router.push(switchLocalePath(locale.code))"
>
<v-list-item-title>{{ locale.name }}</v-list-item-title>
</v-list-item>
</v-list>
Also tried using $i18n.setLocale(locale.code) same thing happens.
Not sure if this should be a comment but since I can't comment yet (requires 50 rep), I post this as an extension of the description of the problem.
Related
We are currently migrating an externally developed Vue 2 application to Vue 3 to fix issues with deprecated libraries and to be more 'future-proof'. Due to lack of knowledge about the Vue framework and the differences between Vue 2 and 3 (we read the migration guide), we are stuck on the use of the Vuelidate Next library to validate form fields.
We make use of:
Vue 3.2.13
#Vuelidate/core 2.0.0
#vuelidate/validators 2.0.0
The below code describes the situation as created by the original programmers in Vue 2 with minor adjustments. First we import the Vuelidate libraries and the form compoments we want to pass data and validation rules to. Next in setup(), we initiate Vuelidate by calling the useVuelidate hook and binding it to v$. Then in data(), the form fields, based upon a JSON schema, are defined. In validations(), we define a single rule for the 'nameEng' form field. Finally created() calls upon the setValidation method from the methods section.
<template>
<div v-if="facility">
{{ /* When we echo v$ we do get the full object */ }}
{{ v$ }}
<form #submit.prevent>
<component v-for="field in fields" :is="field.type" :key="field.id" :value="facility.fieldValues[field.id]"
:path="[field.id]" :context="{ resource: 'facilities', institutionId: facility.institutionId }"
:label="field.label" :fields="field.fields" :editable="editable" v-bind="{ ...field.options }" />
</form>
</div>
</template>
<script>
import useVuelidate from '#vuelidate/core';
import { fetchInstitution } from '#/services/institutionsService';
import FieldRow from '#/modules/core/components/ui/formElements/FieldRow.vue';
/* The JSON schema fields aka:
{
"general": [
{
"type": "FieldRow",
"id": "nameEng",
"label": "Facility name (English)",
"options": {
"field": "StringField",
"fieldOptions": {
"errorMessage": "Please fill in a name for this facility"
}
}
},
}
*/
import fields from '../schemas/fields.json';
export default {
components: {
FieldRow,
},
props: {
facility: {
type: [Object, Array],
},
editable: {
type: Boolean,
default: false,
},
},
setup() {
const v$ = useVuelidate();
return { v$: v$ };
},
data() {
return {
fields: fields.general,
};
},
validations() {
return {
facility: {
fieldValues: {
nameEng: {
required,
},
},
},
};
},
created() {
this.setValidation();
},
methods: {
setValidation() {
this.fields = fields.general.map((field) => {
if (field.options.fieldOptions?.errorMessage) {
/* Returns empty object: {} */
console.log(this.v$);
field.options.fieldOptions.validation = this.v$.facility.fieldValues[field.id];
}
return field;
});
},
},
};
</script>
The setValidation method needs to add the validation rules to the data which gets passed on to a form field component as props. However, when trying to access the v$ in the method, it is a completely empty object. We expected to be able to call v$.facility to get the rule set, but this logically results in an undefined. Contrary, when we echo v$ in the HTML template, we do get the full object returned including the facility rule set.
Our questions would be: why can we not use the v$ object in the methods section like this? should this method still be the correct way achieve the desired result in Vue 3?
Thanks in advance!
~ Tom
I created a wizard form which uses Vuelidate to validate it's fields. The big majority of the fields have only the "required" function, so my validations are something close to this:
validations() {
if (this.currentStep === 0) {
return {
person: {
name: {
required,
},
age: {
required,
},
}
}
} else if (this.currentStep === 1) {
return {
person: {
street: {
required,
},
city: {
required,
},
state: {
required,
},
}
}
The thing is, I am receiving this data from an API, so the user can either fill the fields himself, or let the machine do it for him. When I receive the data, I make this attribution in a function in JS close to the following:
attributeData():
this.person.name = apiData.name;
this.person.age = apiData.age;
this.person.street = apiData.street;
this.person.city = apiData.city;
this.person.state = apiData.state;
If the user types the info, then everything works fine. If I receive the info from the API, then I get the error as if the input was empty.
This is how every field in my HTML is organized:
<b-form-group label="Name">
<b-form-input
v-model="person.name"
type="text"
size="sm"
:class="{
'is-invalid':
submitted && $v.person.name.$error,
}"
></b-form-input>
<div
v-if="submitted && $v.person.name.$error"
class="invalid-feedback"
>
<span v-if="!$v.person.name.required"
>Name is required.</span
>
</div>
</b-form-group>
Any idea of why Vuelidate can't recognize the values when I attribute them directly in JS? I've made this exact same way in another page and it worked. In DevTools even the $model of Vuelidate has the value that came from the API.
This error may occur if you have two elements using the same identifier.
Example:
data: {user_id: null} and setted v-model="user_id" in one input.
And another element input with: id:user_id
Beware if you are not manipulating the value and then it lost the reference, example:
data: {user: {name: null}}
And you filled it by API but latter in created or mounted put something like:
this.user = {}
The reference user.name was gone and validation can't work anymore.
I am using sails.js 1.0 with vue.js and want to create a dynamic form that contains a dynamic amount of inputs based on the user's preference. So the user should be able to add another input, type in the data and send the complete form with the dynamic amount of data.
My form looks like this:
<ajax-form action="addStuff" :syncing.sync="syncing" :cloud-error.sync="cloudError" #submitted="submittedForm()" :handle-parsing="handleParsingForm">
...
<input class="form-control" id="input1" name="input1" type="text" :class="[formErrors.password ? 'is-invalid' : '']"
v-model.trim="formData.input1" placeholder="Input #1" autofocus>
...
<ajax-button type="submit" :syncing="syncing" class="btn btn-dark">Save changes</ajax-button>
</ajax-form>
The action addStuff in sails looks like this:
module.exports = {
friendlyName: 'Do some stuff',
description: 'Do some stuff with the form data.',
inputs: {
input1: {
description: 'The first input.',
required: true
}
},
fn: async function (inputs, exits) {
// Do some stuff with the inputs
return exits.success();
}
};
I know that normally I would be able to create a dynamic form using vue.js by
setting the data of the Vue instance to an array
creating a two-way-binding
implementing a v-for loop in the form, that then creates an input for every element in the data object
modifying this array by inserting a new element in the array every time the user wants to add another input.
But with sails and this ajax-form, I do not know how to access the vue instance and the data element of it and how to make this also dynamic in the action. Obviously the input would need to contain an array.
How would it be possible to achieve such a dynamic form?
I figured out the missing part. Sails.js is using parasails which is built on top of vue.js.
When generating a new sails page using the sails generator sails new test-project, there is also a contact form generated which also contains the necessary code which can be adapted for this purpose.
That contact form basically consists of
The .ejs page (=the html code that renders the form) in views/pages
The contact.page.js client-side script in assets/js/pages
The server side controller deliver-contact-form-message.js in api/controllers
In the client-side script, the initial formData can be set:
parasails.registerPage('maindivid', {
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
data: {
// Main syncing/loading state for this page.
syncing: false,
// Form data
formData: { /* … */ },
// For tracking client-side validation errors in our form.
// > Has property set to `true` for each invalid property in `formData`.
formErrors: { /* … */ },
// Server error state for the form
cloudError: '',
// Success state when form has been submitted
cloudSuccess: false,
},
...
as well as methods etc.
It follows a similar structure than plain vue.js.
To achieve what I was trying to do I added a field as array to the formData
formData: {
myinputs: [
{
key: '',
value: ''
}
]
},
Then I bound that in the .ejs file:
<div class="form-row" v-for="(filter, index) in formData.mypinputs">
<input class="form-control form-control-sm" type="text" :class="[formErrors.password ? 'is-invalid' : '']"
v-model.trim="formData.myinputs[index].key" placeholder="My field">
<button type="button" class="btn btn-secondary btn-sm" #click="addFilterForm">add field</button>
</div>
And finally added a method to the client-side script in contact.page.js (or your name) that gets called when the user clicks the "add field" button.
methods: {
addFilterForm: function() {
this.formData.myinputs.push({
key: '',
value: ''
});
},
Because of the two way binding, as soon as an element is added to the array formData.myinputs, another input is created and added to the DOM.
I am trying to set up a basic server side vue-tables-2 with two filters - one is a dropdown and the other is a search field. I am having trouble detecting which of the two filters were applied within the requestFunction() so I can send a request over to the server. Currently, I am just trying to console log the input filter name and value as the filter is applied / input is changed.
JSFiddle:
https://jsfiddle.net/kbpq5vb3/39/
HTML
<h1 class="vue-title">Vue Tables 2 Demo</h1>
<div id="app">
<v-server-table url="https://jsonplaceholder.typicode.com/users" :columns="columns" :options="options"></v-server-table>
</div>
VueTable:
Vue.use(VueTables.ServerTable);
new Vue({
el: "#people",
data: {
columns: ['name', 'username'],
options: {
requestAdapter(data) {
console.log(data.query); // detect which filter was applied / which input changed
},
responseAdapter(resp) {
return {
data: resp,
count: resp.length
}
},
filterByColumn: true,
filterable: ['name', 'username'],
listColumns: {
name: [{
id: 'ervin',
text: 'Ervin'
}, {
id: 'chelsey',
text: 'Chelsey'
}]
}
}
}
});
According to the vue-tables-2 documentation:
vue-tables.filter/tableName/FILTER fires off when a filter is changed.
After looking a bit deeper, it really is as easy as listening for an event:
Vue developer tools really make this type of stuff easy to diagnose. Now, you'd want to listen to the custom event. You can learn how to do that over on the Vue documentation.
Finally, here is a working fiddle that shows how you listen to these events: https://jsfiddle.net/kbpq5vb3/55/
Hope this helps!
I have an order model that I’m collecting data for over a multi-page form. I’ve created some validations like this:
const Validations = buildValidations({
// these should only be used when we’re on checkout.info route
name: validator('presence', true),
email: [
validator('presence', true),
validator('format', { type: 'email' })
],
// these should only be used when we’re on checkout.shipping route
address1: validator('presence', true),
address2: validator('presence', true),
city: validator('presence', true),
});
My model is set up to use them like this:
export default Model.extend(Validations, {
// model set-up in here
})
What I’d like to happen is for it to only validate name and email when I’m on checkout.info and to validate address1, address2 and city when I’m on checkout.shipping.
One of the things I’ve tried already is running the validations inside of my checkout-form component:
let { m, validations } = order.validateSync({
on: ['name', 'email']
})
const isValid = validations.get('isValid')
order.set('didValidate', isValid)
The problem is that this doesn’t seem to unblock the disabled state on my form’s next button
{{next-button disabled=(v-get model.order 'isInvalid')}}
Another thing I tried was to build a custom routed-presence validator that disables presence when it’s not on the current route. The trouble with this is that other validators will still block this (e.g. type or length).
How might I go about achieving this?
Although it's not well documented, you can enable or disable validations based on a condition that your model computes:
import { validator, buildValidations } from 'ember-cp-validations';
export default Ember.Object.extend(buildValidations({
email: {
disabled: Ember.computed.alias('model.isCheckoutPage'),
validators: [
...
],
}
}), {
// could be a computed property too
isCheckoutPage: false,
});