I would like to create something similiar like tesla.com, content on left side will change when right side is scrolled.
I have tried create it using vuetify intersection, but it still buggy. Is there some way to improve it?
Here is demo for code i already created : https://hyundai-andalan.vercel.app/models/staria
This is code i using:
// page component
<template>
<div>
<ModelDesktop v-if="$vuetify.breakpoint.lgAndUp" />
<ModelMobile v-else />
</div>
</template>
<script>
import model from '~/mixins/model'
export default {
mixins: [model],
head () {
return {
title: this.model.name
}
}
}
</script>
// model desktop component
<template>
<div class="tw-grid tw-grid-cols-3">
<div class="tw-col-span-2">
<ModelCarousel :section="section" class="tw-sticky tw-top-0" />
</div>
<ModelDesktopContentWrapper #change:section="onSectionChange" />
</div>
</template>
<script>
export default {
data () {
return {
section: 'exterior'
}
},
methods: {
onSectionChange (value) {
this.section = value
}
}
}
</script>
// model carousel component
<template>
<v-carousel v-model="carousel" height="100vh">
<v-carousel-item
v-for="(item, i) in model[section].collection"
:key="i"
>
<v-img :src="item.image" aspect-ratio="1" eager />
</v-carousel-item>
</v-carousel>
</template>
<script>
import model from '~/mixins/model'
export default {
mixins: [model],
props: {
section: {
type: String,
default: 'exterior'
}
},
data: () => ({
carousel: 0,
colors: [
'primary',
'secondary',
'yellow darken-2',
'red',
'orange'
]
})
}
</script>
// model desktop component wrapper
<template>
<div class="tw-p-16">
<!-- Specification & Content -->
<div v-intersect="onExterior">
<ModelSpecification />
<ModelContent section="exterior" v-bind="model.exterior" />
</div>
<!-- Specification & Content -->
<!-- Interior -->
<ModelContent v-intersect="onInterior" section="interior" v-bind="model.interior" />
<!-- Interior -->
<!-- Interior -->
<ModelContent v-intersect="onPerformance" section="performance" v-bind="model.performance" />
<!-- Interior -->
<!-- Interior -->
<ModelContent v-intersect="onSafety" section="safety" v-bind="model.safety" />
<!-- Interior -->
</div>
</template>
<script>
import model from '~/mixins/model'
export default {
mixins: [model],
data () {
return {
exterior: false,
interior: false,
performance: false,
safety: false
}
},
watch: {
exterior (newValue) {
if (newValue) {
this.interior = false
this.performance = false
this.safety = false
this.$emit('change:section', 'exterior')
}
},
interior (newValue) {
if (newValue) {
this.exterior = false
this.performance = false
this.safety = false
this.$emit('change:section', 'interior')
}
},
performance (newValue) {
if (newValue) {
this.exterior = false
this.interior = false
this.safety = false
this.$emit('change:section', 'performance')
}
},
safety (newValue) {
if (newValue) {
this.exterior = false
this.interior = false
this.performance = false
this.$emit('change:section', 'safety')
}
}
},
methods: {
onExterior (entries) {
this.exterior = entries[0].isIntersecting
},
onInterior (entries) {
this.interior = entries[0].isIntersecting
},
onPerformance (entries) {
this.performance = entries[0].isIntersecting
},
onSafety (entries) {
this.safety = entries[0].isIntersecting
}
}
}
</script>
// model content component
<template>
<div>
<!-- Main Content -->
<div class="lg:tw-h-screen lg:tw-flex lg:tw-items-center tw-w-full">
<div class="lg:tw-flex-1">
<div class="tw-text-center">
<v-img v-if="$vuetify.breakpoint.mdAndDown" :src="banner" class="tw-mb-2" />
<h2 class="tw-capitalize">
{{ section }}
</h2>
<h3 class="tw-font-medium">
{{ title }}
</h3>
<div class="tw-mb-5" v-text="description" />
<v-btn rounded #click="dialogOpen = true">
feature detail
</v-btn>
</div>
</div>
</div>
<!-- Main Content -->
<ModelContentDialog v-model="dialogOpen" :section="section" :collection="collection" />
</div>
</template>
<script>
export default {
props: {
section: {
type: String,
required: true
},
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
banner: {
type: String,
default: ''
},
collection: {
type: Array,
required: true
}
},
data () {
return {
dialogOpen: false
}
}
}
</script>
Related
I'm sending an event from a child component to my parent component using $emit.
I know I should tell the parent component to listen to that event, however I don't know where to put the
#openModal="changeModalVisibility"
Should I do it as an attribute in the main div? Since this is the highest component I don't find the right place.
Child:
<template>
<div class="cocktail-result" v-on:click="openModal">
<img :src="cocktailImg" alt="cocktail-image" />
<p>{{ cocktailName }}</p>
<div class="explore-button">
<img
src="../assets/img/arrow-forward-icon.svg"
alt="arrow icon"
class="arrow-icon"
/>
</div>
</div>
</template>
<script>
export default {
name: "CocktailResult",
props: {
cocktailName: {
type: String,
},
cocktailImg: {
type: String,
},
modalClass: {
type: String,
},
},
methods: {
openModal() {
this.$emit("openModal");
},
},
};
</script>
Parent:
<template>
<div class="main-container" v-on:openModal="changeModalVisibility">
<div class="search-bar-container">
<img src="./assets/img/logo.png" alt="logo" class="logo" />
<div class="search-bar">
<input
type="text"
v-model="searchQuery"
class="search-bar-input"
placeholder="Enter Your Favorite Cocktail:"
#input="callCocktailApi"
/>
<img
src="./assets/img/close-icon.svg"
alt="close icon"
class="close-icon"
v-on:click="clearInput"
/>
</div>
</div>
<div class="result-container">
<ul v-for="(cocktail, index) in cocktailArray" :key="cocktail.key">
<CocktailResult
:cocktailName="this.cocktailArray[index].strDrink"
:cocktailImg="this.cocktailArray[index].strDrinkThumb"
:cocktailGlass="this.cocktailArray[index].strGlass"
:cocktailInstructions="this.cocktailArray[index].strInstructions"
:modalClass="this.modalClass"
:method="changeModalVisibility"
/>
</ul>
</div>
<CocktailModal :modalClass="this.modalClass" />
</div>
</template>
<script>
import CocktailResult from "./components/CocktailResult.vue";
import CocktailModal from "./components/CocktailModal.vue";
import axios from "axios";
export default {
name: "App",
data() {
return {
cocktailArray: "",
searchQuery: "",
cocktailName: "",
cocktailImg: "",
modalClass: "hidden",
};
},
components: {
CocktailResult,
CocktailModal,
},
methods: {
callCocktailApi() {
axios
.get(
`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${this.searchQuery}
` // API Call
)
.then((res) => {
this.cocktailArray = JSON.parse(
JSON.stringify(res.data.drinks || [])
);
this.cocktailName = this.cocktailArray[0].strDrink;
// Only render results if an array is fetched
console.log(this.cocktailArray);
})
.catch((error) => {
console.log(error);
});
},
clearInput() {
this.cocktailArray = "";
this.searchQuery = "";
},
changeModalVisibility() {
this.modalClass = "";
console.log("I'm working");
},
},
};
</script>
You should put #openModal in your parent component, there were you call CocktailResult.
<CocktailResult
#openModal=“YourCallback”
…
/>
I am trying to create a modal component that takes in user input, and upon saving that information, is displayed within another component. For example, a user is prompted to input their first and last name respectively in a modal component (Modal.vue). Once the user saves that data (a submit method on the modal), the data is displayed on another component (InputItem.vue).
Currently, I have a CreateEvent.vue component that houses the input elements, a modal.vue component that is the modal, an EventItem.vue component, that will display what is entered on once CreateEvent is executed, an EventsList.vue component that displays all the events that a user creates and finally, app.vue which houses the Modal and Events components.
I have been able to successfully get this CRUD functionality working without the modal component, but once I add the modal, I am getting confused.
If you could help lead me in the right direction, I would appreciate that!
Modal.vue
<template>
<transition name="modal-fade">
<div class="modal-backdrop">
<div
class="modal"
role="dialog"
aria-labelledby="modalTitle"
aria-describedby="modalDescription"
>
<header class="modal-header" id="modalTitle">
<slot name="header">
Create an Event
<button
type="button"
class="btn-close"
#click="close"
aria-label="Close modal"
>
x
</button>
</slot>
</header>
<section class="modal-body" id="modalDescription">
<slot name="body">
<div #keyup.enter="addTodo">
<input
type="text"
class="todo-input"
placeholder="What needs to be done"
v-model="newTodo"
/>
<input
type="text"
placeholder="add an emoji?"
v-model="newEmoji"
/>
</div>
<button #click="doneEdit">Create Event</button>
<button #click="cancelEdit">Cancel</button>
</slot>
</section>
<footer class="modal-footer">
<slot name="footer">
I'm the default footer!
<button
type="button"
class="btn-green"
#click="close"
aria-label="Close modal"
>
Close me!
</button>
</slot>
</footer>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'modal',
data() {
return {
newTodo: '',
newEmoji: '',
idForTodo: this.todos.length + 1
}
},
methods: {
close() {
this.$emit('close')
},
addTodo() {
if (this.newTodo.trim().length == 0) return
this.todos.push({
id: this.idForTodo,
title: this.newTodo,
emoji: this.newEmoji
})
this.newTodo = ''
this.newEmoji = ''
this.idForTodo++
}
}
}
</script>
CreateEvent.vue
<template>
<div #keyup.enter="addTodo">
<input
type="text"
class="todo-input"
placeholder="What needs to be done"
v-model="newTodo"
/>
<input type="text" placeholder="add an emoji?" v-model="newEmoji" />
</div>
</template>
<script>
export default {
props: {
todos: {
type: Array
}
},
data() {
return {
newTodo: '',
newEmoji: '',
idForTodo: this.todos.length + 1
}
},
methods: {
addTodo() {
if (this.newTodo.trim().length == 0) return
this.todos.push({
id: this.idForTodo,
title: this.newTodo,
emoji: this.newEmoji
})
this.newTodo = ''
this.newEmoji = ''
this.idForTodo++
}
}
}
</script>
EventItem.vue
<template>
<div class="todo-item">
<h3 class="todo-item--left">
<!-- <span v-if="!editing" #click="editTodo" class="todo-item--label">
{{ title }}
{{ emoji }}
</span> -->
<input
class="todo-item--edit"
type="text"
v-model="title"
#click="editTitle"
#blur="doneEdit"
/>
<input
class="todo-item--edit"
type="text"
v-model="emoji"
#click="editEmoji"
#blur="doneEdit"
/>
<!-- <button #click="doneEdit">Update</button>
<button #click="cancelEdit">Cancel</button> -->
</h3>
<button class="remove-item" #click="removeTodo(todo.id)">✘</button>
</div>
</template>
<script>
export default {
name: 'todo-item',
props: {
todo: {
type: Object,
required: true
}
},
data() {
return {
id: this.todo.id,
title: this.todo.title,
emoji: this.todo.emoji,
editing: this.todo.editing,
beforeEditCacheTitle: this.todo.title,
beforeEditCacheEmoji: this.todo.emoji
}
},
methods: {
editTitle() {
this.beforeEditCacheTitle = this.title
this.editing = true
},
editEmoji() {
this.beforeEditCacheEmoji = this.emoji
this.editing = true
},
doneEdit() {
if (this.title.trim() == '') {
this.title = this.beforeEditCacheTitle
}
if (this.emoji.trim() == '') {
this.emoji = this.beforeEditCacheEmoji
}
this.editing = false
this.$emit('finishedEdit', {
id: this.id,
title: this.title,
emoji: this.emoji,
editing: this.editing
})
},
cancelEdit() {
this.title = this.beforeEditCacheTitle
this.emoji = this.beforeEditCacheEmoji
this.editing = false
},
removeTodo(id) {
this.$emit('removedTodo', id)
}
}
}
</script>
Events.vue
<template>
<div>
<transition-group
name="fade"
enter-active-class="animated fadeInUp"
leave-active-class="animated fadeOutDown"
>
<EventItem
v-for="todo in todosFiltered"
:key="todo.id"
:todo="todo"
#removedTodo="removeTodo"
#finishedEdit="finishedEdit"
/>
</transition-group>
</div>
</template>
<script>
import EventItem from '#/components/EventItem'
export default {
components: {
EventItem
},
data() {
return {
filter: 'all',
todos: [
{
id: 1,
title: 'Eat sushi',
emoji: '💵',
editing: false
},
{
id: 2,
title: 'Take over world',
emoji: '👨🏽💻',
editing: false
}
]
}
},
computed: {
todosFiltered() {
if (this.filter == 'all') {
return this.todos
}
}
},
methods: {
removeTodo(id) {
const index = this.todos.findIndex(item => item.id == id)
this.todos.splice(index, 1)
},
finishedEdit(data) {
const index = this.todos.findIndex(item => item.id == data.id)
this.todos.splice(index, 1, data)
}
}
}
</script>
app.vue
<template>
<div id="app" class="container">
<button type="button" class="btn" #click="showModal">
Create Event
</button>
<Modal v-show="isModalVisible" #close="closeModal" />
<Events />
</div>
</template>
<script>
import Events from './components/Events'
import Modal from './components/Modal'
export default {
name: 'App',
components: {
Events,
Modal
},
data() {
return {
isModalVisible: false
}
},
methods: {
showModal() {
this.isModalVisible = true
},
closeModal() {
this.isModalVisible = false
}
}
}
</script>
The modal component should emit the values instead of pushing it into the todos array. When it emits it, the parent component (App.vue) listens for the emitted items.
I would do something like this
Modal.vue
<template>
...
// header
<section class="modal-body" id="modalDescription">
<slot name="body">
<div #keyup.enter="addTodo">
...
</div>
<button #click="handleModalSubmit">Create Event</button>
...
//footer
...
</template>
<script>
export default {
...
data() {
...
},
methods: {
...,
handleModalSubmit() {
this.$emit('todos-have-been-submitted', this.todos);
},
addTodo() {
...
this.todos.push({
id: this.idForTodo,
title: this.newTodo,
emoji: this.newEmoji
})
...
}
}
}
</script>
App.vue
<template>
...
<Modal
#todos-have-been-submitted="handleTodoSubmission" //watch the 'todos-have-been-submitted" emission and trigger handleTodoSubmission method when the emission is detected
/>
<Events
:todos="todos" // pass todos as a prop to the Events component
/>
...
</template>
<script>
import Events from './components/Events'
import Modal from './components/Modal'
export default {
name: 'App',
components: {
Events,
Modal
},
data() {
return {
...,
todos: []
}
},
methods: {
...,
handleTodoSubmission(todos) {
this.todos = [...todos];
}
}
}
</script>
./AppGallery.vue?vue&type=style&index=0&id=0cb54319&lang=scss&scoped=true& in ./components/sub-page/AppGallery.vue
AppGalery.vue doesn't import anything
<template>
<div class="gallery" v-if="pictures">
<a v-for="(picture, index) in pictures" :key="index" class="gallery__item" :href="picture.path" data-lightbox="gallery" :data-title="picture.title">
<div class="gallery__image">
<img :src="picture.path">
</div>
<div class="gallery__description" v-if="options.showDescription">{{ picture.title }}</div>
</a>
</div>
</template>
<script>
export default {
name: 'AppGallery',
props: {
pictures: {
type: Array,
required: true,
},
options: {
type: Object,
required: false,
default() {
return {
showDescription: true,
};
},
},
},
mounted() {
//lightbox.build();
},
};
</script>
On save error changes to other file.
I'm having a simple issue, that I just can't figure out why it isn't working.
I have a child component "app-buttons", where i have an input field, i want to listen to, so i can filter a list based on the input value.
If i put the input in the root component where i have the list, all works good. But i want to split it up, and $emit the search input value, to the parent en then use it.
// THIS IS THE COMPONENT WHERE I WAN'T TO LISTEN TO THE SEARCH INPUT
import Buttons from './app-buttons.js';
import Event from './vue-event.js';
export default Vue.component('post-item', {
template: `
<section class="posts flex" v-if="posts">
<app-buttons :posts="posts"></app-buttons>
<transition-group name="posts" tag="section" class="posts flex">
<article class="postitem" :class="{ 'danger': !post.published }" v-for="post in posts" :key="post.id">
<p v-if="post.published">Post is published</p>
<p v-else>Post is <em><strong>not</strong></em> published</p>
<h4>{{ post.title }}</h4>
<button type="submit" #click="post.published = !post.published">Change status</button>
</article>
</transition-group>
</section>
`,
data() {
return {
};
},
components: {
Buttons,
},
props: {
posts: Array,
filterdList: [],
},
created() {
console.log('%c Post items', 'font-weight: bold;color:blue;', 'created');
},
computed: {
//filteredList() {
// return this.posts.filter(post => {
// return post.title.toLowerCase().includes(this.search.toLowerCase());
// });
//},
},
methods: {
update() {
console.log('test');
}
}
});
// THIS IS THE COMPONENT IM TRIGGERING THE $EVENT FROM
import Event from './vue-event.js';
export default Vue.component('app-buttons', {
template: `
<div class="postswrapper flex flex--center flex--column">
<section :class="className" class="flex flex--center">
<button type="button" #click="showUnpublished">Unpublish</button>
<button type="button" #click="shufflePosts">Shuffle</button>
</section>
<section :class="className">
<input type="text" v-model="search" v-on:input="updateValue" placeholder="Search..." />
</section>
</div>
`,
data() {
return {
className: 'buttons',
search: '',
}
},
props: {
posts: Array,
},
created() {
//console.log(this);
console.log('%c Buttons', 'font-weight: bold;color:blue;', 'created');
},
methods: {
updateValue() {
//console.log(this.search);
this.$emit('searchquery', this.search);
},
showUnpublished() {
this.posts.forEach(item => {
item.published = true;
})
},
shufflePosts() {
this.$emit('postshuffled', 'Fisher-Yates to the rescue');
for (var i = this.posts.length - 1; i >= 0; i--) {
let random = Math.floor(Math.random() * i);
let temp = this.posts[i];
Vue.set(this.posts, i, this.posts[random]);
Vue.set(this.posts, random, temp);
}
},
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue JS</title>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.6/dist/vue.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="app">
<app-header :logo="logo" :name="name"></app-header>
<post-item :posts="posts" v-on:searchquery="update" v-on:sq="update" v-on:postshuffled="update"></post-item>
</div>
<script type="module">
import AppHeader from './components/app-header.js';
import PostItem from './components/post-item.js';
const app = new Vue({
el: '#app',
data: {
name: 'Vue App',
logo: {
class: 'vue-logo',
src: 'https://vuejs.org/images/logo.png',
},
components: {
AppHeader,
PostItem,
},
posts: [
{id: 1, title: 'Test', published: true},
{id: 2, title: 'New post', published: true},
{id: 3, title: 'Added', published: true},
{id: 4, title: 'Another post', published: true},
{id: 5, title: 'In the future', published: false},
{id: 6, title: 'Last post', published: true},
],
},
created() {
console.log('Created');
},
mounted() {
console.log('Mounted');
},
methods: {
update() {
console.log('updated');
}
},
});
</script>
</body>
</html>
You say:
I have a child component "app-buttons", where i have an input field, i
want to listen to, so i can filter a list based on the input value.
You have:
<div id="app">
<app-header :logo="logo" :name="name"></app-header>
<post-item :posts="posts" v-on:searchquery="update" v-on:sq="update" v-on:postshuffled="update"></post-item>
</div>
That says you expect post-item to emit a searchquery event. It does not.
Within post-item, you have:
<app-buttons :posts="posts"></app-buttons>
So you expect app-buttons to emit an event and post-item to implicitly bubble it up, but Vue events do not bubble. If you want that behavior, you will need to have post-item handle the event:
<app-buttons :posts="posts" v-on:searchquery="$emit('searchquery', $event)"></app-buttons>
Okay so I've changed the markup, and it works. But would this be the best way to do it? :)
And thanks to Roy J for helping out.
import Event from './vue-event.js';
import Buttons from './app-buttons.js';
export default Vue.component('post-item', {
template: `
<section class="posts flex" v-if="posts">
<app-buttons :posts="posts" v-on:searchquery="update($event)"></app-buttons>
<transition-group name="posts" tag="section" class="posts flex">
<article class="postitem" :class="{ 'danger': !post.published }" v-for="post in filteredItems" :key="post.id">
<p v-if="post.published">Post is published</p>
<p v-else>Post is <em><strong>not</strong></em> published</p>
<h4>{{ post.title }}</h4>
<button type="submit" #click="post.published = !post.published">Change status</button>
</article>
</transition-group>
</section>
`,
data() {
return {
search: '',
};
},
components: {
Buttons,
},
props: {
posts: Array,
},
created() {
console.log('%c Post items', 'font-weight: bold;color:blue;', 'created');
},
computed: {
filteredItems(search) {
return this.posts.filter(post => {
return post.title.toLowerCase().indexOf(this.search.toLowerCase()) > -1
});
}
},
methods: {
update(event) {
this.search = event;
}
}
});
// Child component
import Event from './vue-event.js';
export default Vue.component('app-buttons', {
template: `
<div class="postswrapper flex flex--center flex--column">
<section :class="className" class="flex flex--center">
<button type="button" #click="showUnpublished">Unpublish</button>
<button type="button" #click="shufflePosts">Shuffle</button>
</section>
<section :class="className">
<input type="text" v-model="search" v-on:input="updateValue" placeholder="Search..." />
</section>
</div>
`,
data() {
return {
className: 'buttons',
search: '',
}
},
props: {
posts: Array,
},
created() {
console.log('%c Buttons', 'font-weight: bold;color:blue;', 'created');
},
methods: {
updateValue() {
this.$emit('searchquery', this.search);
},
showUnpublished() {
this.posts.forEach(item => {
item.published = true;
})
},
shufflePosts() {
for (var i = this.posts.length - 1; i >= 0; i--) {
let random = Math.floor(Math.random() * i);
let temp = this.posts[i];
Vue.set(this.posts, i, this.posts[random]);
Vue.set(this.posts, random, temp);
}
},
}
});
Index same as before, just without alle the events on the components.
Hi guys Im trying to make my custom tabs, with Vue js but Im having a problem since like my data property is not getting updated :(...
Here is the case Im having trouble with:
When I open 3 tabs, If I open my Modal on the first tab and then close that first tab, I will be switched to second tab but my Modal that was from first tab stays open like it is modal from the first tab instead of second... I would like each tab to have its own modal instance.
Here I posted bellow gif of what is happening. Basically I dont want my modal to apear again on next tab, when previous is closed :)
Seems like my data values, are not destroyed with first tab, and they are just replicated onto the second tab, Im trying to figure out what is the issue for few days now but no succes...
Here is my App.vue
<template>
<div id="app">
<div class="event-tabs wrapper">
<div class="is-flex">
<div class="tabs is-boxed control">
<ul>
<li v-for="(newEvent, index) in newEventList" :key="index" :class="selectedEventClass(index)"
#click.left="selectEvent(index)" #click.middle="discardEvent(index)">
<span class="event-tab-title">
TAB
</span>
<span class="event-tab-close" #click.stop="closeEvent(index)">
<i class="fa fa-times"></i>
</span>
</li>
<li class="add-tab">
<a #click.prevent="createEvent" :title="'Create Tab'">
<span>+</span>
</a>
</li>
</ul>
</div>
</div>
<div class="tab-content">
<tab v-for="(event, index) in newEventList" :event="event" :index="index"
v-if="showEventTab" v-show="index === selectedEvent" :key="index"
ref="eventTab"></tab>
</div>
</div>
</div>
</template>
<script>
import tab from './components/EventTab.vue';
export default {
name: 'app',
components: {
tab,
},
computed: {
newEventList() {
return this.$store.getters['eventModule/getNewList'];
},
selectedEvent() {
return this.$store.getters['eventModule/getSelectedNew'];
},
eventToEdit() {
return this.$store.state.event.eventToEdit;
},
showEventTab() {
return this.newEventList.length > 0;
},
},
methods: {
selectedEventClass(eventIndex) {
return (eventIndex === this.selectedEvent) ? 'is-active' : '';
},
createEvent() {
this.$store.dispatch('eventModule/create');
},
selectEvent(eventIndex) {
this.$store.dispatch('eventModule/select', { eventIndex });
},
closeEvent(eventIndex) {
this.$store.dispatch('eventModule/close', { eventIndex });
},
},
}
</script>
<style lang="scss">
#import './assets/scss/main';
</style>
My Tab component:
<template>
<div class="event-form" v-if="event">
<div class="columns">
<div class="column is-half">
<div class="field">
<h1>This is the TAB number {{ index}} </h1>
</div>
<p class="control">
<button class="button is-danger" #click.prevent="openDialog">
Open Dialog
</button>
</p>
<modalDialog type="none" :show="modal.show"
:className="'eventTabModal'" :title="'Test modal'"
:text="'Test modal'"
#hide="closeDiscardModal">
<h3>Modal is active</h3>
</modalDialog>
</div>
</div>
</div>
</template>
<script>
import modalDialog from './ModalDialog.vue';
export default {
components: {
modalDialog,
},
props: ['event', 'index'],
data() {
return {
eventDefault: {},
/**
* Discard event modal
*/
modal: {
show: false,
},
};
},
computed: {
eventList() {
return this.$store.getters['event/getNewList'];
},
eventTypeList() {
return this.$store.getters['eventType/getList'];
},
},
methods: {
/**
* Opens discarded Modal
*/
closeDiscardModal() {
this.modal = {
show: false,
};
},
openDialog() {
this.modal = {
show: true,
};
},
},
}
</script>
My Modal component for displaying Dialog:
<template>
<transition name="fade">
<div class="modal is-active" v-show="shouldShowModal" :class="className">
<div class="modal-background" #click="hideModal"></div>
<div class="modal-card">
<header class="modal-card-head" v-if="title">
<p class="modal-card-title">{{ title }}</p>
</header>
<section class="modal-card-body">
<slot>
{{ text }}
</slot>
</section>
<footer class="modal-card-foot" v-if="type !== 'none'">
<template v-if="type === 'confirm'">
<a class="button is-success" #click.prevent="buttonClicked('yes')">Yes</a>
<a class="button is-danger" #click.prevent="buttonClicked('no')">No</a>
</template>
<template v-else-if="type === 'info'">
<a class="button" #click.prevent="buttonClicked('ok')">Ok</a>
</template>
</footer>
</div>
<button class="modal-close is-large" #click="hideModal"></button>
</div>
</transition>
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false,
},
title: {
type: String,
default: '',
},
text: {
type: String,
default: '',
},
type: {
type: String,
default: 'info',
},
className: {
type: String,
default: '',
},
},
data() {
return {
shouldShowModal: this.show,
};
},
watch: {
show(newValue) {
this.shouldShowModal = newValue;
},
},
methods: {
hideModal() {
this.shouldShowModal = false;
this.$emit('hide');
},
buttonClicked(type) {
this.hideModal();
this.$emit('buttonClicked', type);
},
},
};
</script>
And My store module for Tabs:
const eventModule = {
namespaced: true,
state: {
/**
* List of opened tabs
*/
newList: [],
selectedNew: 0,
savedList: [],
eventToEdit: null,
},
getters: {
getNewList(state) {
return state.newList;
},
getSelectedNew(state) {
return state.selectedNew;
},
getSavedList(state) {
return state.savedList;
},
},
mutations: {
addNew(state, { location } = {}) {
state.newList.push({
typeId: null,
active: true,
logs: [],
});
},
removeNew(state, index) {
state.newList.splice(index, 1);
},
setNew(state, { index = state.selectedNew, event }) {
state.newList.splice(index, 1, event);
},
selectNew(state, selectedNew) {
state.selectedNew = selectedNew;
},
},
actions: {
/**
* opens tab for creating new event
*
* #param context
* #param location
* #param stopProp
* #returns {*}
*/
create(context, { location, stopProp } = {}) {
const newList = context.getters.getNewList;
context.commit('addNew', { location });
context.commit('selectNew', newList.length - 1);
// if (!stopProp) {
// context.dispatch('stateChanged', null, { root: true });
// }
return Promise.resolve();
},
/**
* Saves event
* #param context
* #param event
* #return {Promise|Promise.<TResult>}
*/
save(context, { event, index, hideMessage }) {
const method = (event.id) ? 'patch' : 'post';
// const data = { event, userId: context.rootGetters['user/getData'].id };
const data = { event };
const payload = { method, url: 'event', data, hideMessage };
return context.dispatch('server/http', payload, { root: true })
.then((response) => {
context.commit('setNew', { event: response.data.object, index });
context.dispatch('loadList');
})
.catch(error => Promise.reject(error));
},
select(context, { eventIndex, stopProp }) {
context.commit('selectNew', eventIndex);
},
opened(context) {
const event = JSON.parse(JSON.stringify(context.state.eventToEdit));
context.state.eventToEdit = null;
context.dispatch('create', { stopProp: true });
context.commit('setNew', { event });
},
/**
* Closes for event
* #param context
* #param eventIndex
* #param stopProp
* #return {Promise|Promise.<TResult>}
*/
close(context, { eventIndex, stopProp }) {
const newList = context.getters.getNewList;
const selectedNew = context.getters.getSelectedNew;
context.commit('removeNew', eventIndex);
if (selectedNew >= newList.length && selectedNew > 0) {
context.commit('selectNew', selectedNew - 1);
}
},
},
};
export default eventModule;
Also Here is the link to my github page where full test code is located if someone wants to take a look:
Codesandbox link
Thanx in advance.
Solved it. The problem is with keys in v-for, :key prop, should be unique, so here is how I solved it, in mutations addNew, add new property tabId add like this:
state.newList.push({
tabId: new Date.now(),
typeId: null,
active: true,
briefing: false,
logs: [],
});
and App.vue change :key=“index” to :key=“event.tabId”