How can I preserve data between switching between tabs in Vue? - vue.js

I'm trying to learn some Vue, I have tab component it has 2 tabs with some dropdowns and text fields inside, the problem is when I switch between tabs it loss data like selected items from dropdowns or text values, I've tried with v-model but nothing, any help?
Here is the tab view
<script setup>
import { ref } from 'vue';
const nestedRouteItems = ref([
{
label: 'Personal',
to: '/uikit/ficha'
},
{
label: 'Seat',
to: '/uikit/ficha/seat'
},
]);
</script>
<template>
<div class="grid">
<div class="col-12 md:col-12">
<div class="card p-fluid">
<h5>Ficha</h5>
<div class="col-12 md:col-12">
<div class="card card-w-title">
<h5>Tab container</h5>
<p>Lorem ipsum dolor sit amet, consectetur.</p>
<TabMenu :model="nestedRouteItems" />
<router-view />
</div>
</div>
</div>
</div>
</div>
</template>
Here is the content of first tab:
<template>
<div class="flex align-items-center py-5 px-3">
<div class="card p-fluid" style="width:800px">
<h5>Datos personales</h5>
<div class="field">
<label for="name1">Nombre</label>
<InputText id="name1" type="text" />
</div>
</div>
</div>
</template>
Here is the content of second tab:
<script setup>
import { ref } from 'vue';
const dropdownItems = ref([
{ name: 'Principal', code: 'Principal' },
{ name: 'Laboral', code: 'Laboral' },
{ name: 'Familiar', code: 'Familiar' }
]);
const dropdownItem = ref(null);
</script>
<template>
<div class="flex align-items-center py-5 px-3">
<div class="card p-fluid" style="width:800px">
<h5>Domicilio</h5>
<div class="field">
<label for="tipoDomicilio">Tipo de domicilio</label>
<Dropdown id="tipoDomicilio" v-model="dropdownItem" :options="dropdownItems" optionLabel="name" placeholder="Elegir opción..."></Dropdown>
</div>
</div>
</div>
</template>

You can use KeepAlive to do this.
<KeepAlive> is a built-in component that allows us to conditionally
cache component instances when dynamically switching between multiple
components.
If your tab components are dynamic then use KeepAlive like this-
<KeepAlive>
<component :is="tabComponent" />
</KeepAlive>
If you are using tab components individually then do like this-
<KeepAlive>
<tab-a></tab-a>
<tab-b></tab-b>
</KeepAlive>
You can read more about KeepAlive in the documentation.

Related

Nuxt Props pass to data and set images

how do i pass props into data ? i have a object that pass into prop and i want prop to set the image url with data "mainImage", must keep the mainImage.
<template>
<!-- start Product Header -->
<div class="ps-product__header">
<div class="ps-product__thumbnail" data-vertical="false">
<div class="ps-wrapper">
<div class="ps-product__gallery">
<div class="col-12 px-md-2 d-none d-md-block">
<div class="" style="cursor: pointer">
<b-img :src="mainImage" alt="" style="width: 100%" class="image" #click="showMainImage()"></b-img>
</div>
</div>
</div>
</div>
<div class="ps-product__variants">
<div class="col-12 d-none d-md-block my-4">
<div class="row">
<div class="col-3" v-for="(image, index) in product.images" :key="index">
<div class="thumbnail" style="cursor: pointer" #click="changeMainImage(image.src)">
<b-img :src="`${image.src}`" style="width: 100%" alt="" class="image" :class="mainImage === image ? 'activess' : ''"></b-img>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
here is the script
<script>
export default {
name: 'ShopHeader',
props: {
product: {
type: Object,
required: true
}
},
data() {
return {
mainImage: String,
}
},
methods: {
changeMainImage(image) {
this.mainImage = image
}
},
mounted() {
this.mainImage = this.product.image
console.log(this.mainImage)
}
}
</script>
sample : https://stackblitz.com/edit/nuxt-starter-pake3o?file=components%2FShopHeader.vue
when you type on the stackblitz url with /product, the image will show up but when you go through the link the image wont show up, so anyone know how to fix this problem ? i assume it was mounted only load once and component wont render after it.
add v-if to tag
<img v-if="mainImage" :src="mainImage" class="image">

How to send & receive object as a props in Vue.js

I have a Product object and I am taking it from API call. So in parent component I want to send this object into the child object which is SoldTickets component.
So here is my parent component:
<template>
<section id="cart-page" class="row">
<div v-for="item in cart.attributes.items" :key="item.id">
<div class="col-lg-8 col-md-12 col-sm-12 col-xs-12">
<div class="title">WARENKORB</div>
<SoldTickets :item = item />
</div>
</div>
</section>
</template>
And my child:
<template>
<div id="sold-tickets">
<div class="card">
<div class="sold-tickets-actions properties">
<div class="sold-tickets-inner">
<div class="ticket-details">
<div class="ticket-prop">
<div class="ticket-name">{{ item.product_name }}</div>
<div class="ticket-type">{{ item.variation_name }}</div>
</div>
</div>
<DeleteButton #click.prevent="removeProductFromCart(item.id)" />
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
data: {
item: Object,
}
},
}
</script>
So I am quite new in Vue so I am not really confident with props. COuld you please help me with this.
You mostly have it right, but there are a couple of errors:
Your props aren't declared correctly in the child component, you shouldn't have the "data" in there. All props you want to declare go directly under the "props" key of the component declaration:
export default {
props: {
item: Object,
},
}
You're also missing quotes around the attribute value in the parent component, so that's invalid HTML:
<SoldTickets :item = item />
should be
<SoldTickets :item = "item" />
Just wrap the bound value with "" or '' like :
<SoldTickets :item="item" />

How to toggle between components when onclick function happens on a icon in vue.js?

I have four components Dashboard.vue(parent component) it contains Three child components(DisplayBooks.vue,sortBooksHightoLow,sortBooksLowtoHigh).
Now i want to import one more component called Cart.vue inside the Dashboard.vue .By default only DisplayBooks.vue component is only visible,if i click on cart-icon inside the Dashboard component it displays the Cart.vue component and hides the DisplayBooks.vue component .
How to acheive this thing please help me to fix this thing..
Dashboard.vue
<template>
<div class="main">
<div class="navbar navbar-default navbar-fixed-top">
<div class="navbar-header">
<img src="../assets/education.png" alt="notFound" class="education-image" />
</div>
<ul class="nav navbar-nav">
<li>
<p class="brand">Bookstore</p>
</li>
</ul>
<div class="input-group">
<i #click="handlesubmit();" class="fas fa-search"></i>
<div class="form-outline">
<input type="search" v-model="name" class="form-control" placeholder='search...' />
</div>
</div>
<ul class="nav navbar-nav navbar-right" id="right-bar">
<li><a> <i class="far fa-user"></i></a></li>
<p class="profile-content">profile</p>
<li><a><i class="fas fa-cart-plus"></i></a></li>
<p class="cart-content" >cart <span class="length" v-if="booksCount">{{booksCount}}</span></p>
</ul>
</div>
<div class="mid-body">
<h6>Books<span class="items">(128items)</span></h6>
<select class="options" #change="applyOption">
<option disabled value="">Sort by relevance</option>
<option value="HighToLow">price:High to Low</option>
<option value="LowToHigh">price:Low to High</option>
</select>
</div>
<div v-if="flam==false">
<h2>Hello</h2>
</div>
<DisplayBooks v-show="flag==='noOrder'" #update-books-count="(n)=>booksCount=n"/>
<sortBooksLowtoHigh v-show="flag==='lowToHigh'" />
<sortBooksHightoLow v-show="flag==='highToLow'" />
<Cart />
</div>
</template>
<script>
import sortBooksLowtoHigh from './sortBooksLowtoHigh.vue'
import sortBooksHightoLow from './sortBooksHightoLow.vue'
import DisplayBooks from './DisplayBooks.vue'
import Cart from './Cart.vue'
export default {
components: {
DisplayBooks,
sortBooksLowtoHigh,
sortBooksHightoLow,
Cart
},
data() {
return {
booksCount:0,
flag: 'noOrder',
brand: 'Bookstore',
name: '',
flam: true,
visible: true,
}
},
methods: {
flip() {
this.flam = !this.flam;
},
applyOption(evt) {
if (evt.target.value === "HighToLow") {
this.flag = 'highToLow';
} else this.flag = 'lowToHigh';
},
}
}
</script>
Cart.vue
<template>
<div class="main">
<div v-for="book in books" :key="book.id" class="container">
<div class="content">
<h5>My Cart({{booksCount}})</h5>
</div>
<div class="mid-section">
<img v-bind:src="book.file" alt="not found">
<p class="title-section">{{book.name}}</p>
</div>
<div class="author-section">
<p>by {{book.author}}</p>
</div>
<div class="price-section">
<h6>Rs.{{book.price}}</h6>
</div>
<button class="close-btn" #click="handlesubmit();" type="submit">close</button>
<div class="btn-grps">
<button class="btn" type="submit" >Place Order</button>
</div>
</div>
</div>
</template>
<script>
import service from '../service/User'
export default{
data(){
return {
booksCount:0,
books: [{
id: 0,
file: 'https://images-na.ssl-images-amazon.com/images/I/41MdP5Tn0wL._SX258_BO1,204,203,200_.jpg',
name: 'Dont Make me think',
author: 'Sai',
price: '1500'
},]
}
},
methods:{
}
}
</script>
It's recommended to use vur-router, but for a simple situation you could define a property called shownComp then update when you click on cart icon :
data() {
return {
shownComp:'DisplayBooks',
booksCount:0,
flag: 'noOrder',
then <li #click="shownComp='Cart'"><a><i class="fas fa-cart-plus"></i></a></li>
and :
<DisplayBooks v-show="flag==='noOrder' && shownComp==='DisplayBooks'" #update-books-count="(n)=>booksCount=n"/>
<Cart v-show=" shownComp==='Cart'" />
...

VeeValidate with Yup: input type="number" value is converted to string on submit

I use VeeValidate and Yup for form validation and don't know why my input field with type="number" is converted to string on submit.
When I input 78 and submit the form the output of the console.log in the onSubmit(values) function is the following:
values: {
prioritaet: "78"
}
Am I doing something wrong here or is this the normal behavior of VeeValidate and Yup? I would like to have a number instead of a string after submit.
My code looks like this:
<template>
<div class="container m-3">
<div class="row bg-primary align-items-center py-2">
<div class="col">
<h3 class="text-light mb-0">Format {{ this.action }}</h3>
</div>
<div class="col-auto">
<h5 class="text-light mb-0">{{ this.formatTitle }}</h5>
</div>
</div>
<Form #submit="onSubmit" :validation-schema="formatSchema" v-slot="{ errors }" ref="formatForm">
<div class="row mt-4">
<div class="col">
<h5>Formatdaten</h5>
</div>
</div>
<div class="row mb-2">
<div class="col-4">
<label for="prioritaet-input">Priorität: </label>
</div>
<div class="col">
<Field type="number" name="prioritaet" id="prioritaet-input" class="w-100" />
</div>
</div>
<div class="row justify-content-end">
<div class="col-auto me-auto">
<button class="btn btn-outline-primary">Änderungen übernehmen</button>
</div>
<div class="col-auto">
<button class="btn btn-outline-primary">Abbrechen</button>
</div>
<div class="col-auto">
<button type="sumbit" class="btn btn-outline-primary">Speichern</button>
</div>
</div>
<div class="row mt-4">
<template v-if="Object.keys(errors).length">
<span class="text-danger" v-for="(message, field) in errors" :key="field">{{ message }}</span>
</template>
</div>
</Form>
</div>
</template>
<script>
import { Form, Field } from "vee-validate";
import deLocale from "../assets/yup-localization.js";
import * as Yup from "yup";
import { markRaw } from "vue";
import { mapActions, mapState } from "vuex";
Yup.setLocale(deLocale);
export default {
name: "FormatBearbeitungsSchirm",
props: ["material_id"],
data() {
let action = "neu";
let formatTitle = "Format neu";
let formatSchema = markRaw(Yup.object().shape({
prioritaet: Yup.number().min(1).max(100).integer().label("Priorität"),
}));
return { formatSchema, action, formatTitle };
},
created() {
},
components: {
Form,
Field,
},
methods: {
onSubmit(values) {
console.log("values", values);
},
},
};
</script>
It looks like there is currently no support for specifying the .number modifier on the internal field model value of <Field>, so the emitted form values would always contain a string for number-type fields.
One workaround is to convert the value in the template, updating <Form>'s values slot prop in <Field>'s update:modelValue event:
<Form #submit="onSubmit" v-slot="{ values }">
<Field 👆
type="number"
name="prioritaet" 👇
#update:modelValue="values.prioritaet = Number(values.prioritaet)"
/>
<button>Submit</button>
</Form>
demo
Another simple workaround is to convert the property inside onSubmit before using it:
export default {
onSubmit(values) {
values.prioritaet = Number(values.prioritaet)
// use values here...
}
}
You must use the .number modifier.
You can read about it here
If you want user input to be automatically typecast as a Number, you can add the number modifier to your v-model managed inputs:
const app = new Vue({
el: "#app",
data: () => ({
mynumber1: undefined,
mynumber2: undefined
}),
methods: {
submit() {
console.log(typeof this.mynumber1, this.mynumber1)
console.log(typeof this.mynumber2, this.mynumber2)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14/dist/vue.js"></script>
<div id="app">
<form>
<!-- number modifier -->
<input type="number" v-model.number="mynumber1" placeholder="Type here" />
<!-- no modifier -->
<input type="number" v-model="mynumber2" placeholder="Type here" />
<input type="button" #click="submit" value="submit" />
</form>
</div>

(Vue.js) modal close event

When I click on the outer area of ​​the modal, I want the same event as the close button of the modal. (Event that closes modal when clicking outside area of ​​modal)
The current progress is that the modal is closed when the close modal button is clicked.
Carousel.vue
<template>
<div>
<div v-for="(item, index) in photos" :key="index">
<div #click="imgClick(item)" style="cursor:pointer;">
<img :src="item.thumbnail" />
</div>
<Modal v-if='item.show' #close="item.show = false">
<div slot='body'>
<img :src="item.thumbnail" :class="`img-index--${index}`"/>
</div>
</Modal>
</div>
</div>
</template>
<script>
import Modal from './Modal.vue'
export default {
props: {
items: { type: Array, default: () => [] }
},
data() {
return {
photos: {}
}
},
created() {
this.photos = this.items.map(item => {
return { ...item, show: false }
})
},
methods: {
imgClick(item) {
item.show = true
}
},
components: {
Modal: Modal
}
}
</script>
Modal.vue
<template>
<transition name="modal">
<div class="modal-mask" #click="$emit('close')">
<div class="modal-wrapper">
<div class="app__phone">
<div class="feed">
<div class="post">
<div class="header headroom">
<div class="level-left">
<img src="../assets/imgs/user.gif" class="modal-header-img"/>
<div class="user">
<span class="username">username</span>
<button class="modal-default-button" #click="$emit('close')">Close</button>
</div>
</div>
</div>
<slot name="modal-img"></slot>
<div class="content">
<div class="content-title">
<slot name="modal-tit"></slot>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</transition>
</template>
When I add a click event to the bottom <div>, it closes when I click outside the modal, but it closes when I click anywhere in the modal.
<div class="modal-mask" #click="$emit('close')">
And this link has a Fiddle example in the accepted answer to the question.
https://stackoverflow.com/a/58023701/12066654
You need to add a handler to the outer modal div like so:
<template id="modal">
<div class="modal" #click.self="close">
<div class="close" #click="close">×</div>
<div class="body">
<slot name="body" />
</div>
</div>
</template>