VueJS: render new component after click - javascript

I'd like to render new component in vue.js as if it's new page.
I'm trying to do it with something called "dynamic component"
parent: Post.vue
child: Detail.vue
so, if one of the posts is clicked, Post is off and Detail is on.
The thing is I have to send clicked post's id to the Detail.
Here's some of my code.
Post.vue
<template>
<div>
<div v-if="loading">
loading...
</div>
<div v-else class="container">
<ul>
<li v-for="(post, index) in filteredPosts" v-bind:key="post.no">
<section class="post__main">
<div #click..?? class="main__title">{{post.title}}</div>
</section>
</li>
</ul>
</div>
</div>
</template>
<script>
created() {
axios.get(this.url, {
params: {
page: this.page,
ord: this.ord,
category: []
}
}).then(res => {
this.posts = res.data.list;
this.loading = false;
this.page++;
});
Detail.vue
<template>
<div>
{{contents}}
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Datail',
data() {
return {
req_no: null,
contents: {}
}
},
created() {
axios.get(url, {
params: {
req_no: this.req_no
}
}).then(res => {
this.contents = this.res.data
});
}
}
</script>
I feel like I can do it with props and v-if.
Can someone help me? Thanks!

Once a post is clicked, pass post id to the click handler. In the click handler, route to detail.vue passing post id as route param.
like below:
<li v-for="(post, index) in filteredPosts" v-bind:key="post.no">
<section class="post__main">
<div #click="onPostClick(post.id)" class="main__title">{{post.title}}</div>
</section>
</li>
And in your click handler:
onPostClick(id: number) {
this.$router.push({
name: 'details',
params: {
id
}
});
}
This will work provided you set up vue router correctly in your app and have a valid route for details.
You can access the post id in details component as follows:
created() {
this.postId = this.$route.params.id;
}

I would take a look at the <component> which takes a prop :to and renders a component, this is good for something like tabs where you are rendering different component from a the same general location on the page without reloading the whole page. See here:
https://v2.vuejs.org/v2/guide/components-dynamic-async.html
This seems to be a very good use case for you, just pass into the component the props you need.

Related

When moving between Dynamic Pages with NuxtLink, the data of store cannot be retrieved

A demo is provided below.
stackblitz
When I move from the top page to a post page, the correct content is displayed, but when I move from a post page to another post page, the correct content is not displayed.
However, reloading will display the correct content.
Could you please show us how to display the correct content after transitioning from one posting page to another?
The code for the submission page is as follows.
// pages/post/_id.vue
<template>
<div></div>
</template>
<script>
import { fetchPosts } from '../../lib/post';
export default {
name: 'Post',
layout: 'post/index',
async asyncData({ route, store }) {
const posts = await fetchPosts();
const post = posts.find(({ id }) => id === route.params.id);
store.dispatch('setPost', post);
store.dispatch('setPosts', posts);
},
};
</script>
// layouts/post/index.vue
<template>
<div>
<h1 v-if="post">{{ post.title }}</h1>
<p v-if="post">{{ post.title }} page</p>
<ul>
<li v-for="post in posts" :key="post.id">
<NuxtLink :to="'/post/' + post.id">
{{ post.title }}
</NuxtLink>
</li>
</ul>
<NuxtLink to="/">Top</NuxtLink>
</div>
</template>
<script>
export default {
data() {
return {
post: null,
posts: [],
};
},
created() {
this.post = this.$store.getters['post'].post;
this.posts = this.$store.getters['posts'].posts;
},
};
</script>
The process flow is as follows
pages retrieves data from the server and dispatches it to the store
laytous retrieves data from the store and displays the data in laytous
I know that the use of pages and layouts is not common, but the project I am currently working on specifies this usage and I cannot change this usage.
That's because the layout was already rendered when the route changes (when the route changes, the layout/component used is already created, so the hook created is not called again), a simple fix would be to add a watch for the route:
...
watch: {
'$route': function (value) {
this.post = this.$store.getters['post'].post;
this.posts = this.$store.getters['posts'].posts;
}
},
...

Delete function in parent component in vue.js

I have a ticket as you can see in the picture below:
I have a delete button as a component and I am trying to add delete functionality to it. I am using this component in my ticket component. So this is my delete component:
<template>
<div id="delete-button" #click.prevent="removeProductFromCart(item.id)">
<input type="checkbox" id="checkbox">
<div id="bin-icon">
<div id="lid"></div>
<div id="box">
<div id="box-inner">
<div id="bin-lines"></div>
</div>
</div>
</div>
</div>
</template>
<script>
import cartHelper from "../helpers/cartHelper";
export default {
props: {
item: Object,
},
data() {
return {
loading: false,
};
},
methods: {
removeProductFromCart(id) {
this.loading = true;
setTimeout(() => {
cartHelper.removeFromCart(id, (response) => {
this.$store.dispatch('removeProductFromCart', {
cart: response.data,
})
this.loading = false
});
}, 1000)
}
}
};
</script>
So the parent component is ticket component:
[![<template>
<div id="sold-tickets">
<div class="card">
<div class="sold-tickets-actions properties">
<div class="sold-tickets-inner">
<DeleteButton :item = "item" />
</div>
</div>
</div>
</div>
</template>][1]][1]
<script>
import image from "../../../../img/Hallenbad.jpg";
import DeleteButton from "./DeleteButton";
import cartHelper from "../helpers/cartHelper";
export default {
props: {
item: Object,
},
components: {DeleteButton},
data() {
return {
image: image,
};
},
};
</script>
My problem is, the ticket is being deleted even when I click outside of the child component (Delete component). But I want to use the delete component like a button and I only want to delete the ticket when it is clicked, not outside of the button.
Try to stop propagation. You probably do not even need the prevent modifier.
#click.prevent.stop='...'
or
#click.stop='...'
If the display type is block, you might also want to inspect the div to see if it is actually filling the entire width of the page. If so, use an inline type, a flex layout, or similiar.

vue.js post list not updating after form submission

In my vue app I have two components one which is a form that posts the form data to my api. And the other gets and displays these posts in a section on the page. My issue is that when I submit a new post the posts lists aren't updated. The data stays the same unless I refresh the page. How can I get my posts list to update when I submit the form?
My Code:
client/src/App.vue
<template>
<div id="app">
<MainHeader :modalVisability="modal" v-on:showModal="toggleModal" />
<div id="content_wrap">
<Summary />
</div>
<OppForm :modalVisability="modal" />
</div>
</template>
<script>
import MainHeader from './components/MainHeader.vue';
import OppForm from './components/oppForm.vue';
import Summary from './components/Summary.vue';
export default {
name: 'App',
components: {
MainHeader,
Summary,
OppForm
},
data () {
return {
modal: false
}
},
methods: {
toggleModal (modalBool) {
this.modal = modalBool;
}
}
}
</script>
client/src/components/oppForm.vue
<template>
<div id="opp_form_modal" >
<form #submit.prevent="SubmitOpp" v-if="modalVisability">
<input type="text" name="company_name" v-model="company_name">
<button type="submit">Submit</button>
</form>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'oppForm',
props: {
modalVisability: Boolean,
},
data () {
return {
company_name: ''
}
},
methods: {
SubmitOpp () {
axios.post('http://localhost:5000/', {
company_name: this.company_name,
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
}
}
</script>
client/src/components/Summary.vue
<template>
<div id="summary_section">
<h2>Summary</h2>
<div id="summary_board">
<div class="column">
<div class="head">
<h3>Opportunities</h3>
</div>
<div class="body">
<div class="post"
v-for="(post, index) in posts"
:key="index"
>
<p class="company">{{ post.company_name }}</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return{
posts: []
};
},
created() {
axios.get('http://localhost:5000/')
.then(res => {
// console.log(res);
const data = res.data;
this.posts = data;
})
.catch(error => console.log(error));
}
}
</script>
The problem is that you're actually fetching your posts only on the app creation (i.e. inside the created() method).
You should wrap your axios call inside a function updatePosts() and then call it whenever you add a new post successfully, or you could create a custom event that is triggered whenever a new post is added.
created() is called only once (see vue lifecycle) so you fetch API before submitting form.
Try to add some console.log to understand what is called when.
You could use an global event bus and send form value as event data to summary. I could imagine also a solution where event is used to "tell" summary that form was submitted (just boolean, not data itself). In summary you then call API each time you receive event.
Or simple add an "update" button to summary to manually call API.
See Communication between sibling components in VueJs 2.0
or global vue instance for events for detailed examples.

VueJS How to pass data to a modal component using eventbus

I'm building a small vue application where among other things it is possible to delete an entry of a music collection. So at this point I have a list of music albums and next to the entry I have a "delete" button. When I do the following:
<li v-for="cd in cds">
<span>{{cd.artist}} - {{cd.album}}</span> <button v-on:click="deleteAlbum(cd.ID)">Delete</button>
</li>
and then in my methods do:
deleteAlbum(id){
this.$http.delete('/api/cds/delete/'+id)
.then(function(response){
this.fetchAll()
// });
},
this works fine so far, but to make it more nice, I want the delete functionality to appear in a modal/popup, so I made the following changes:
<li v-for="cd in cds">
<div class="cd-wrap">
<span>{{cd.artist}} - {{cd.album}}</span>
<button #click="showDeleteModal({id: cd.ID, artist: cd.artist, album: cd.album})" class="btn">Delete</button>
</div>
<delete-modal v-if="showDelete" #close="showDelete = false" #showDeleteModal="cd.ID = $event"></delete-modal>
</li>
so, as seen above I created a <delete-modal>-component. When I click on the delete button I want to pass the data from the entry to <delete-modal> component with the help of an eventbus. For that, inside my methods I did this:
showDeleteModal(item) {
this.showDelete = true
eventBus.$emit('showDeleteModal', {item: item})
}
Then, in the <delete-modal>, inside the created()-lifecycle I did this:
created(){
eventBus.$on('showDeleteModal', (item) => {
console.log('bus data: ', item)
})
}
this gives me plenty of empty opened popups/modals!!??
Can someone tell me what I'm doing wrong here?
** EDIT **
After a good suggestion I dumped the eventBus method and pass the data as props to the <delete-modal> so now it looks like this:
<delete-modal :id="cd.ID" :artist="cd.artist" :album="cd.album"></delete-modal>
and the delete-modal component:
export default {
props: ['id', 'artist', 'album'],
data() {
return {
isOpen: false
}
},
created(){
this.isOpen = true
}
}
Only issue I have now, is that it tries to open a modal for each entry, how can I detect the correct ID/entry?
I am going to show you how to do it with props since it is a parent-child relation.I will show you a simple way of doing it.You need to modify or add some code of course in order to work in your app.
Parent component
<template>
<div>
<li v-for="cd in cds" :key="cd.ID">
<div class="cd-wrap">
<span>{{cd.artist}} - {{cd.album}}</span>
<button
#click="showDeleteModal({id: cd.ID, artist: cd.artist, album: cd.album})"
class="btn"
>
Delete
</button>
</div>
<delete-modal v-if="showDelete" :modal.sync="showDelte" :passedObject="objectToPass"></delete-modal>
</li>
</div>
</template>
<script>
import Child from 'Child'
export default {
components: {
'delete-modal': Child
},
data() {
return {
showDelete: false,
objectToPass: null,
//here put your other properties
}
},
methods: {
showDeleteModal(item) {
this.showDelete = true
this.objectToPass = item
}
}
}
</script>
Child Component
<template>
/* Here put your logic component */
</template>
<script>
export default {
props: {
modal:{
default:false
},
passedObject: {
type: Object
}
},
methods: {
closeModal() { //the method to close the modal
this.$emit('update:modal')
}
}
//here put your other vue.js code
}
</script>
When you use the .sync modifier to pass a prop in child component then,there (in child cmp) you have to emit an event like:
this.$emit('update:modal')
And with that the modal will close and open.Also using props we have passed to child component the object that contains the id and other stuff.
If you want to learn more about props, click here

Referencing router-link params data in vuejs

I have this component
<div class="card col-4" style="width: 22rem;">
<img class="card-img-top" src="../assets/images/olu.jpg" alt="Card image cap">
<div class="card-block">
<h4 class="card-title">Card title</h4>
<p class="card-text">{{ stories.articles[0].summary }}</p>
<router-link :to="{path: '/viewArticle', params:{id:123}}"><a class="btn btn-primary">Continue Reading</a></router-link>
</div>
</div>
Notice the router-link tag:
<router-link :to="{path: '/viewArticle', params:{id:123}}"><a class="btn btn-primary">Continue Reading</a></router-link>
It's being routed to display an article.vue component which is shown below:
<template>
<div>
<div class="container row">
<h1 class="display-3 col">Article in view</h1>
</div>
<div class="container">
<img src="../assets/images/olu.jpg"/>
<article>
some text
</article>
</div>
</div>
</template>
<script>
// /console.log(params.id);
export default {
name: 'article',
data() {
return {
}
}
}
</script>
This works absolutely fine. My question is very simple, how do I reference the id value passed into the router-link params property, inside this article.vue component, which is being returned whenever the /viewArticle path is hit, as shown in the 1st component above.
I've tried looking through the documentation and a few articles, but so far I haven't been able to find a suitable solution.
Kind regards
You can set the props property to true on the article route as described in Passing Props to Router Component section.
{
name: 'article'
path: '/viewArticle/:id',
component: ArticleView // whatever you named the component
props: true
}
Then you your ArticleView component you can add an id prop:
<script>
export default {
name: 'article',
props: ['id'],
data() {
return {
}
}
}
</script>
The id is now available to you directly on the component and you can fetch the article.
If you want to you can also preload the article so that the actual article is passed instead of the id.
You do that by adding beforeRouteEnter to the component:
<script>
export default {
name: 'article',
props: ['id'],
data() {
return {
article: null,
}
},
beforeRouteEnter (to, from, next) {
// your ajax stuff goes here
// I use axios in this example
axios.get(`/api/article/${to.params.id}`)
.then((article) => {
next(vm => {
vm.article = article
})
})
.catch(() => {
next('/404')
})
}
}
</script>
So before the router is entered it will fetch the article. This has the added advantage that all your component code can assume that you have the article loaded already. You don't have to deal with the case where it is loaded or not loaded.
In addition you can also access the matched route like this: this.$route and the router for navigation like this: this.$router (with r at the end).

Categories