Validate multiple selects with a vue Form - forms

I am new to Vue and I am trying to validate a form made by multiple select rendered by a vfor. The date is coming from a Json, simulated mock-server-json.
I can use Vue Vanilla or vee-validate. I saw I could use useFieldArray with vee-validate but I could not make it work.
<template>
<ux-loader v-if="dataArray.length == 0" loading></ux-loader>
<transition name="onEnter">
<div v-if="dataArray.length != 0">
<form #submit.prevent="handleSubmit">
<div class="form">
<div v-for="(data, index) in dataArray" :key="index" class="select">
{{ index }}
<ux-input-a11y-select v-model="form.selected[index]">
<option data-placeholder value="">-- Choisir une valeur --</option>
<option v-for="option in data.option" :key="option" :value="option">{{ option }}</option>
</ux-input-a11y-select>
</div>
</div>
<button class="submit">Valider</button>
</form>
<Modal v-show="isModalVisible" #close="closeModal" />
</div>
</transition>
<div v-if="error != null">{{ error }}</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import Modal from '../components/Modal.vue'
import { UxBtn, UxInputA11ySelect, UxLoader } from '#libui/ux-default-lib'
import getForm from '../composables/getForm.js'
import postForm from '../composables/postForm.js'
UxInputA11ySelect.define()
UxBtn.define()
UxLoader.define()
export default defineComponent({
name: 'FormUser',
components: {
Modal,
},
setup() {
const dataArray = ref([])
const { error, load } = getForm()
const { sendData } = postForm()
load().then((result: any) => {
dataArray.value = result
})
return { sendData, error, dataArray }
},
data() {
return {
isModalVisible: false,
form: {
selected: [],
},
}
},
methods: {
handleSubmit() {
this.isModalVisible = true
},
async closeModal() {
this.isModalVisible = false
console.log(this.form.selected)
console.log(Object.values(this.form.selected))
this.sendData(this.form.selected)
this.$router.push('Display')
},
},
})
</script>
This is my current code. It is working as I can get an object containing the results of the selects but I am wondering if there is a better way of doing it, like in Angular where you get an object with all results without doing anything particular.
Any help appreciated.

Related

VueJs submit multiple rows array in form

I have a form, which has a vue componenet that allows users to create additional input lines in the form.
I am having an issue getting all of these input lines to submit for the axios request, currently it only outputs the last added rows input.
Typically, in a normal PHP form, I would just make the field an array (name="MultiRow[]"), however I am lost at doing this in Vue and struggling to find any doc that covers this.
Here is the component in my form:
<div class="mt-5 md:mt-0 md:col-span-2">
<fieldset class="mt-6">
<div class="mt-6">
<response-set-input v-model="fields.response"></response-set-input>
</div>
</fieldset>
</div>
Here is my Vue File for form submission:
<script>
import Switch from '../../components/StatusToggle';
export default {
data() {
return {
fields: {},
errors: {},
statusToggle: false,
}
},
methods: {
toggled(toggleOn){
statusToggle: toggleOn
},
submit() {
this.errors = {};
axios.post('/submit', this.fields).then(response => {
alert('Message sent!');
}).catch(error => {
if (error.response.status === 422) {
this.errors = error.response.data.errors || {};
}
});
},
},
components: {
statusToggle: Switch
}
}
</script>
Here is my component code:
<template>
<div>
<div class="m-2" v-for="(row, index) in rows" :key="index">
<div class="col-span-12 sm:col-span-3 mb-1">
<label for="responseTitle" class="block text-sm font-medium leading-5 text-gray-700">Response</label>
<input
id="responseTitle"
class="mt-1 form-input block w-full py-2 px-3 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition duration-150 ease-in-out sm:text-sm sm:leading-5"
type="text"
name="fields.response[]"
:value="responseInput"
#input="onInput($event.target.value)"/>
</div>
<div class="mt-2">
<button type="button" class="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs leading-4 font-medium rounded text-gray-700 bg-green-100 hover:bg-indigo-50 focus:outline-none focus:border-indigo-300 focus:shadow-outline-indigo active:bg-indigo-200 transition ease-in-out duration-150" #click="addRow">
Add new Response
</button>
<button type="button" class="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs leading-4 font-medium rounded text-gray-700 bg-red-100 hover:bg-indigo-50 focus:outline-none focus:border-indigo-300 focus:shadow-outline-indigo active:bg-indigo-200 transition ease-in-out duration-150" #click="removeRow(index)">
Remove
</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['responseInput'],
data () {
return {
rows: [],
stopRemoval: true,
}
},
watch: {
rows () {
this.stopRemoval = this.rows.length <= 1
}
},
methods: {
onInput(responseInput){
this.$emit('input', responseInput),
console.log(responseInput)
},
addRow () {
let checkEmptyRows = this.rows.filter(row => row.number === null)
if (checkEmptyRows.length >= 1 && this.rows.length > 0) {
return
}
this.rows.push({
responseTitle: null,
})
},
removeRow (rowId) {
if (!this.stopRemoval) {
this.rows.splice(rowId, 1)
}
}
},
mounted () {
this.addRow()
}
}
</script>
How do I submit the multiple rows to the form submission with Vue?
There's a decent amount wrong with your code, I suggest that you read the documentation.
Just to name a few things:
You shouldn't update a prop in a component as it will get overridden when the parent updates, props: ['responseInput'], and :value="responseInput"
You're not passing any prop called responseInput, v-model passes a prop called value.
Vue is only reactive on properties that processed during instance initialisation and that means it doesn't know about response on fields: {},
You're using rows (which is good), but then you're only emitting the prop you passed in responseInput. I think :value="responseInput" is supposed to be :value="row"

How to enable required validation in vuelidate based on onChange event of select field

I have to enable required validation for the input field based on the onChange event of select field. I'm using vuelidate package for form validation in my project. Kindly provide solution to accomplish it.
My Template Code Below:
<template>
<section class="page_blk">
<form #submit="submitForm($event)" class="cryp_form">
<div class="input_ctrl_wrp">
<label for="username">Template</label>
<div class="input_select">
<select #change="getTemplate" v-model="$v.adForm.tnc.$model" name="" id="">
<option value="">Select</option>
<option value="New">New</option>
<option :value="term.idTnCTemplate"
v-for="term in termsList"
:key="term.idTnCTemplate">{{term.title}}</option>
</select>
<i class="fal fa-angle-down"></i>
</div>
</div>
<div class="input_ctrl_wrp">
<label for="username">Title</label>
<div class="input_text">
<input v-model="$v.adForm.title.$model" placeholder="" type="text">
</div>
</div>
<div class="input_ctrl_wrp">
<label for="username">Terms Of Trade</label>
<div class="input_textarea">
<textarea v-model="$v.adForm.content.$model" name="" rows="10"></textarea>
</div>
</div>
</form>
</section>
</template>
My Script Below:
<script>
import { required,requiredIf, decimal, numeric } from "vuelidate/lib/validators";
export default {
data() {
return {
adForm: {
tnc: '',
title: '',
content: '',
}
}
},
validations: {
adForm: {
tnc: {
required
},
title: {
required
},
content: {
required: requiredIf( (abc) => {
console.log('abc',abc)
return true;
})
},
schedule: {
required
}
}
},
methods: {
submitForm(e) {
},
getTemplate(e) {
}
},
mounted() {
}
}
</script>
I want to toggle the validation to required for the title and content field, if the user select new and other option from dropdown. Please provide solution to accomplish it. Thanks in advance.

Vuejs toggle class in v-for

I'm making a list of items with v-for loop. I have some API data from server.
items: [
{
foo: 'something',
number: 1
},
{
foo: 'anything',
number: 2
}
]
and my template is:
<div v-for(item,index) in items #click=toggleActive>
{{ item.foo }}
{{ item.number }}
</div>
JS:
methods: {
toggleActive() {
//
}
}
How can i toggle active class with :class={active : something} ?
P.S I don't have boolean value in items
You can try to implement something like:
<div
v-for="(item, index) in items"
v-bind:key="item.id" // or alternativelly use `index`.
v-bind:class={'active': activeItem[item.id]}
#click="toggleActive(item)"
>
JS:
data: () => ({
activeItem: {},
}),
methods: {
toggleActive(item) {
if (this.activeItem[item.id]) {
this.removeActiveItem(item);
return;
}
this.addActiveItem(item);
},
addActiveItem(item) {
this.activeItem = Object.assign({},
this.activeItem,
[item.id]: item,
);
},
removeActiveItem(item) {
delete this.activeItem[item.id];
this.activeItem = Object.assign({}, this.activeItem);
},
}
I had the same issue and while it isn't easy to find a whole lot of useful information it is relatively simple to implement. I have a list of stores that map to a sort of tag cloud of clickable buttons. When one of them is clicked the "added" class is added to the link. The markup:
<div class="col-sm-10">
{{ store.name }}
</div>
And the associated script (TypeScript in this case). toggleAdd adds or removes the store id from selectedStoreIds and the class is updated automatically:
new Vue({
el: "#productPage",
data: {
stores: [] as StoreModel[],
selectedStoreIds: [] as string[],
},
methods: {
toggleAdd(store: StoreModel) {
let idx = this.selectedStoreIds.indexOf(store.id);
if (idx !== -1) {
this.selectedStoreIds.splice(idx, 1);
} else {
this.selectedStoreIds.push(store.id);
}
},
async mounted () {
this.stores = await this.getStores(); // ajax request to retrieve stores from server
}
});
Marlon Barcarol's answer helped a lot to resolve this for me.
It can be done in 2 steps.
1) Create v-for loop in parent component, like
<myComponent v-for="item in itemsList"/>
data() {
return {
itemsList: ['itemOne', 'itemTwo', 'itemThree']
}
}
2) Create child myComponent itself with all necessary logic
<div :class="someClass" #click="toggleClass"></div>
data(){
return {
someClass: "classOne"
}
},
methods: {
toggleClass() {
this.someClass = "classTwo";
}
}
This way all elements in v-for loop will have separate logic, not concerning sibling elements
I was working on a project and I had the same requirement, here is the code:
You can ignore CSS and pick the vue logic :)
new Vue({
el: '#app',
data: {
items: [{ title: 'Finance', isActive: false }, { title: 'Advertisement', isActive: false }, { title: 'Marketing', isActive: false }],
},
})
body{background:#161616}.p-wrap{color:#bdbdbd;width:320px;background:#161616;min-height:500px;border:1px solid #ccc;padding:15px}.angle-down svg{width:20px;height:20px}.p-card.is-open .angle-down svg{transform:rotate(180deg)}.c-card,.p-card{background:#2f2f2f;padding:10px;border-bottom:1px solid #666}.c-card{height:90px}.c-card:first-child,.p-card:first-child{border-radius:8px 8px 0 0}.c-card:first-child{margin-top:10px}.c-card:last-child,.p-card:last-child{border-radius:0 0 8px 8px;border-bottom:none}.p-title .avatar{background-color:#8d6e92;width:40px;height:40px;border-radius:50%}.p-card.is-open .p-title .avatar{width:20px;height:20px}.p-card.is-open{padding:20px 0;background-color:transparent}.p-card.is-open:first-child{padding:10px 0 20px}.p-card.is-open:last-child{padding:20px 0 0}.p-body{display:none}.p-card.is-open .p-body{display:block}.sec-title{font-size:12px;margin-bottom:10px}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<div id="app" class="p-5">
<div class="p-wrap mx-auto">
<div class="sec-title">NEED TO ADD SECTION TITLE HERE</div>
<div>
<div v-for="(item, index) in items" v-bind:key="index" class="p-card" v-bind:class="{'is-open': item.isActive}"
v-on:click="item.isActive = !item.isActive">
<div class="row p-title align-items-center">
<div class="col-auto">
<div class="avatar"></div>
</div>
<div class="col pl-0">
<div class="title">{{item.title}}</div>
</div>
<div class="col-auto">
<div class="angle-down">
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="angle-down" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"
class="svg-inline--fa fa-angle-down fa-w-10 fa-3x">
<path fill="currentColor"
d="M151.5 347.8L3.5 201c-4.7-4.7-4.7-12.3 0-17l19.8-19.8c4.7-4.7 12.3-4.7 17 0L160 282.7l119.7-118.5c4.7-4.7 12.3-4.7 17 0l19.8 19.8c4.7 4.7 4.7 12.3 0 17l-148 146.8c-4.7 4.7-12.3 4.7-17 0z"
class=""></path>
</svg>
</div>
</div>
</div>
<div class="p-body">
<div class="c-card"></div>
<div class="c-card"></div>
<div class="c-card"></div>
</div>
</div>
</div>
</div>
</div>

Redux-form get the value from other tab/component

I have to implement a form with a bit less than 30 different fields.
So I decided to split them in 2 differents container component with two tabs to navigate between them.
I use redux-form to handle the data binding.
For on component I can get the value from handleSubimit of one component. But the final validation must be in the last tab only. From here I only have access to the value of the second tab. Like the data from the store where wipe out.
How can I access the store where my previous data should be ?
TabNavigationBar.js
import React from 'react';
import TabNavigationItem from './TabNavigationItem';
const TabNavigationBar = ({ onTabChange, activeTab }) => {
const tabList = [
{ hasIcon: 'fas fa-user-circle', hastext: 'Information Utilisateur' },
{ hasIcon: 'fas fa-file-alt', hastext: 'Informations contrat' }
];
const clickOnTab = tabNumer => {
onTabChange(tabNumer);
};
return (
<div className="columns">
<div className="column is-offset-one-quarter-desktop is-offset-one-thirds-tablet is-half-desktop is-one-thirds-tablet">
<div className="tabs is-toggle is-fullwidth">
<ul>
{tabList.map((tab, i) => (
<TabNavigationItem
key={i}
tabSelected={() => clickOnTab(i)}
hasClass={activeTab === i ? 'is-active' : ''}
hasIcon={tab.hasIcon}
hasText={tab.hastext}
/>
))}
</ul>
</div>
</div>
</div>
);
};
export default TabNavigationBar;
UserForm.js
import React from 'react';
import { reduxForm } from 'redux-form';
import CiviliteRadioButton from './CiviliteRadioButton';
import NameInputs from './NameInputs';
import AddressInputs from './AddressInputs';
import MailAndDOB from './MailAndDOB';
import TelephoneInputs from './TelephoneInputs';
let UserForm = ({ handleSubmit }) => {
return (
<div>
<CiviliteRadioButton />
<NameInputs />
<AddressInputs />
<MailAndDOB />
<TelephoneInputs />
</div>
);
};
UserForm = reduxForm({
form: 'form1',
initialValues: {
user: {
adresse: {
country: 'France'
},
civilite: 'Madame'
}
}
})(UserForm);
export default UserForm;
ContractForm.js
import React from 'react';
import { reduxForm } from 'redux-form';
import InputItem from '../InputItem';
import ContratInputsList from './contratInputList';
let ContratForm = ({ handleSubmit }) => {
const submit = values => {
console.log(values);
};
return (
<div>
<div className="columns is-multiline ">
{ContratInputsList.map((item, i) => {
return (
<div className="column is-half" key={i}>
<InputItem spec={item.spec} />
</div>
);
})}
</div>
<div className="columns">
<div className="column">
<div className="field is-grouped is-grouped-right">
<input
className="button is-primary"
onClick={handleSubmit(submit)}
type="submit"
value="Envoyer"
/>
</div>
</div>
</div>
</div>
);
};
ContratForm = reduxForm({
form: 'form2'
})(ContratForm);
export default ContratForm;
EDIT
When I click on my tabs, redux-form/DESTROY is called and erase form1's data.
Try setting destroyOnUnmount flag to false in reduxForm(options).

Mutliple Modals in same page Vuejs 2

I'm a newbie in Vuejs (i'm learning).
I'm trying to do many modals in same page using Vuejs2
Like many people, i have this console warning :
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated:
I read the official doc. I understand that i'm trying to mutating a props and now with vuejs2 its no longer possible.
I tried by using data and computed methods but still doesn't work.
I know the props are now in one way and i think that i understood i need to $emit some props
Here my code:
app.js
Vue.component('modal', Modal);
Vue.component('modal-add-equipe', ModalContent);
Vue.component('modal-user-team', ModalContentTeamUser);
new Vue({
delimiters: ['${', '}'],
el: '#app',
data: {
showModal: false,
showModalAddEquipe: false,
showModalUserTeam: false
},
methods: {
openModal: function(name) {
console.log(this.$refs[name]);
this.$refs[name].show = true;
//this.showModal = true;
},
closeModal: function(name) {
this.$refs[name].show = false;
//this.showModal = false;
}
}
})
Modal.vue
<template>
<transition name="modal">
<div class="modal-mask" #click="close" v-show="show">
<div class="modal-wrapper">
<div class="modal-container with-border" #click.stop>
<slot></slot>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
props: ['show', 'onClose'],
methods: {
close: function () {
this.onClose();
}
},
ready: function () {
document.addEventListener("keydown", (e) => {
if (this.show && e.keyCode == 27) {
this.onClose();
}
});
}
}
</script>
ModalContent.vue
<template>
<modal :show.sync="show" :on-close="close">
<div class="modal-header">
<slot name="header">
default header
</slot>
</div>
<div class="modal-body">
<slot name="body">
default body
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
</slot>
</div>
</modal>
</template>
<script>
var Modal = require('./Modal.vue');
export default {
props: ['show'],
methods: {
close: function () {
this.$emit('close', false);
},
},
components:{
'modal': Modal
}
}
</script>
the call in twig :
{% for user in usersTeamed %}
<span #click="openModal('userTeam{{ user.getId() }}')"><strong>Equipes</strong></span>
<modal-user-team ref="userTeam{{ user['id'] }}" :show.sync="showModal" #close="closeModal('userTeam{{ user['id'] }}')">
<h3 slot="header">{{ 'equipe_modal_user_equipe'|trans }}</h3>
<div slot="body">
{{ form_start(formUsersTeamed[user['id']]) }}
{{ form_rest(formUsersTeamed[user['id']]) }}
{{ form_end(formUsersTeamed[user['id']]) }}
</div>
</modal-user-team>
{% endfor %}
My goal it's to open one modal (but many in same page and avoid 1k variables like do the example https://v2.vuejs.org/v2/examples/modal.html ) find by "rel"
This code is working but i have console worning !
If someone can help me please :)
PS: I use also gulp with vueify, babelify and aliasify
PS2 : Sorry for my english