I am attempting to do a SPA using Vue.js but unfortunately I know almost nothing about it, I followed a tutorial and got something up and running. This should hopefully be relatively simple!
I'm trying to create a simple page that:
Does a REST API call and pulls some JSON
A list with links of a particular field in the list of results is displayed on the left side of the screen
(I've managed until here)
Now I would like to be able to click on one of the links and see on the right side of the screen the value of another field for the same record.
For instance, suppose my JSON is:
{
"jokes":{
[
"setup":"setup1",
"punchline":"punchline1"
],
[
"setup":"setup2",
"punchline":"punchline2"
],
[
"setup":"setup3",
"punchline":"punchline3"
]
}
}
So in my screen I would see:
setup1
setup2
setup3
So if I click in setup1 I see punchline1, setup2 displays punchline2 and so on.
Here is my code - I'm basically trying to display the punchline in the moduleinfo div. I realise the current solution does not work. I've been searching but can't find any similar examples. Any pointers would be greatly appreciated.
<template>
<div class="home">
<div class="module-list">
<input type="text" v-model.trim="search" placeholder="Search"/>
<div>
<ul>
<li class="modules" v-for="value in modulesList" :key="value.id">
{{ value.setup }}
</li>
</ul>
</div>
</div>
<div class="moduleinfo">
<h2>Module info</h2>
<!-- <p>{{ value.punchline }}</p> -->
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Home',
data: function(){
return {
jokes: [],
search : ""
}
},
mounted() {
this.getModules();
},
methods: {
getModules() {
var self = this
const options = {
method: 'GET',
url: 'https://dad-jokes.p.rapidapi.com/joke/search',
params: {term: 'car'},
headers: {
'x-rapidapi-key': '...',
'x-rapidapi-host': 'dad-jokes.p.rapidapi.com'
}
};
axios.request(options)
.then(response => {
self.jokes = response.data;
console.log(response.data);
}).catch(function (error) {
console.error(error);
});
}
},
computed: {
modulesList: function () {
var jokes = this.jokes.body;
var search = this.search;
if (search){
jokes = jokes.filter(function(value){
if(value.setup.toLowerCase().includes(search.toLowerCase())) {
return jokes;
}
})
}
return jokes;
}
},
};
</script>
Thanks!
I was building a sample Single File Component in my Vue 2 CLI app, and when I came back to post it, Ryoko had already answered the question with the same approach that I recommend, adding a new property to track showing the punchline.
Since I already built it, I figured that I might as well post my component, which does change the layout, using a table instead of a list, but the functionality works.
<template>
<div class="joke-list">
<div class="row">
<div class="col-md-6">
<table class="table table-bordered">
<thead>
<tr>
<th>SETUP</th>
<th>PUNCHLINE</th>
</tr>
</thead>
<tbody>
<tr v-for="(joke, index) in jokes" :key="index">
<td>
{{ joke.setup }}
</td>
<td>
<span v-if="joke.showPunchline">{{ joke.punchline }}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
jokes: [
{
setup: "setup1",
punchline: "punchline1"
},
{
setup: "setup2",
punchline: "punchline2"
},
{
setup: "setup3",
punchline: "punchline3"
}
]
}
},
methods: {
getPunchline(index) {
this.jokes[index].showPunchline = true;
},
addPropertyToJokes() {
// New property must be reactive
this.jokes.forEach( joke => this.$set(joke, 'showPunchline', false) );
}
},
mounted() {
this.addPropertyToJokes();
}
}
</script>
You can add a new property inside the data object and then make a new method to set it accordingly when you click the <a> tag. Have a look at the code below, it was a copy of your current solution, edited & simplified to show the addition that I made to make it easier for you to find it.
The select method will insert the object of the clicked joke to the selectedJoke so you can render it below the Module Info.
Because it's defaults to null, and it might be null or undefined, you have to add v-if to the attribute to check wether there is a value or not so you don't get error on the console.
<template>
<div class="home">
<div class="module-list">
<input type="text" v-model.trim="search" placeholder="Search"/>
<div>
<ul>
<li class="modules" v-for="value in modulesList" :key="value.id">
{{ value.setup }}
</li>
</ul>
</div>
</div>
<div class="moduleinfo">
<h2>Module info</h2>
<p v-if="selectedJoke">{{ selectedJoke.punchline }}</p>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Home',
data: function(){
return {
jokes: [],
search : "",
selectedJoke: null,
}
},
methods: {
select(joke) {
this.selectedJoke = joke;
},
},
};
</script>
Related
The objective: Vue component input-address has to be inside Vue component mail-composer and display a list of addresses only when someone click Address Book button. When someone click one of displayed mails or fill the To field by hand, createdmail.to has to get the value and I have to hide the list of addresses.
Vue component mail-composer. This component receives a list of addresses. (Everything is working here, I think the only part that is not working properly is v-model inside input-address tag)
Vue.component('mail-composer', {
props: ['addressesbook'],
methods: {
send: function(createmail) {
this.$emit('send', createmail);
}
},
template:
`
<div>
<input-address :addresses="addressesbook" v-model="createmail.to"></input-address>
<p><b>Subject: </b><input type="text" v-model="createmail.subject"></input></p>
<p><b>Body: </b><textarea v-model="createmail.body"></textarea></p>
<button #click="send(createmail)">Send</button>
</div>
`,
data(){
return{
createmail:{
to: '',
subject: '',
body: ''
}
}
}
});
The other Vue component is this one, which is in the same file. (I think all problems are here).
I need to display the list of addresses only when someone click Address Book button, and I have to hide it when someone click again the button or one of the emails which are in the list. When someone clicks a mail from list, the createmail.to property from the mail-composer has to get the value of the mail , also if I decide to put the mail by hand it has to occurs the same.
Vue.component('input-address',{
props:["addresses"],
template:
`
<div>
<label><b>To: </b><input type="text"></input><button #click="!(displayAddressBook)">Address Book</button></label>
<ul v-if="displayAddressBook">
<li v-for="address in addresses">
{{address}}
</li>
</ul>
</div>
`,
data(){
return{
displayAddressBook: false
}
}
})
There're some errors in your code:
#click="!(displayAddressBook)" should be #click="displayAddressBook = !displayAddressBook" - the first really does nothing (interesting), the second (suggested) sets the value of displayAddressBook to the opposite it has currently.
the input-address component does not really do anything with the input field (missing v-model)
the changes in the child component (input-address) are not sent back to the parent (added a watcher to do that in the child component)
the parent component (mail-composer) has to handle the values emitted from the child (added the #address-change action handler)
the v-for in your input-address component does not have a key set. Added key by using the index for it (not the best solution, but easy to do).
just put createmail.to: {{ createmail.to }} at the end of MailComposer, so you can see how it changes
Suggestions
always use CamelCase for component names - if you get used to it, then you get less "why is it not working?!" moments
watch for typos: createmail doesn't look good - createEmail or just simply createemail would be better (ok, it doesn't look so nice - maybe you should choose a totally different name for that)
Vue.component('InputAddress', {
props: ["addresses"],
data() {
return {
displayAddressBook: false,
address: null
}
},
template: `
<div>
<label><b>To: </b>
<input
type="text"
v-model="address"
/>
<button
#click="displayAddressBook = !displayAddressBook"
>
Address Book
</button>
</label>
<ul v-if="displayAddressBook">
<li
v-for="(address, i) in addresses"
:key="i"
#click="clickAddressHandler(address)"
>
{{address}}
</li>
</ul>
</div>
`,
watch: {
address(newVal) {
// emitting value to parent on change of the address
// data attribute
this.$emit('address-change', newVal)
}
},
methods: {
clickAddressHandler(address) {
// handling click on an address in the address book
this.address = address
this.displayAddressBook = false
}
}
})
Vue.component('MailComposer', {
props: ['addressesbook'],
data() {
return {
createmail: {
to: '',
subject: '',
body: ''
}
}
},
methods: {
send: function(createmail) {
this.$emit('send', createmail);
},
addressChangeHandler(value) {
this.createmail.to = value
}
},
template: `
<div>
<input-address
:addresses="addressesbook"
v-model="createmail.to"
#address-change="addressChangeHandler"
/>
<p>
<b>Subject: </b>
<input
type="text"
v-model="createmail.subject"
/>
</p>
<p>
<b>Body: </b>
<textarea v-model="createmail.body"></textarea>
</p>
<button #click="send(createmail)">Send</button><br />
createmail.to: {{ createmail.to }}
</div>
`
});
new Vue({
el: "#app",
data: {
addressesbook: [
'abcd#abcd.com',
'fghi#fghi.com'
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<mail-composer :addressesbook="addressesbook" />
</div>
Good afternoon, I have two child components Header and Pagination. In Header, I have an input search engine and two inputs (title and body) in order to be able to add a post to Pagination. I managed to transfer the search value to the Pagination component, but I don’t know how to transfer the value from two inputs (title, body). I use to transfer the event bus. Help me please pass the value of the two inputs (title, body) into the Pagination component when you click the AddPost button.
My code on GitHub
Screenshot of app
My code of component Header:
<template>
<div class="header">
<input type="text" v-model="search" class="header_input_search" placeholder="Search" #input="saveMessage" />
<img src="src/assets/milk.png">
<div class="header_div_inputs">
<input type="text" v-model="createTitle" class="created"/>
<p><input type="text" v-model="createBody" class="createBody"/></p>
</div>
<button #click="addPost()" class="addPost">AddPost</button>
</div>
</template>
<script>
import axios from 'axios';
import {eventEmitter} from './main'
export default {
name: 'Header',
data () {
return {
search: '',
createTitle: '',
createBody: '',
}
},
methods:{
saveMessage(){
eventEmitter.$emit('messageSave', this.search)
},
}
}
</script>
My code of component Pagination:
<template>
<div class = "app">
<ul>
<li v-for="(post, index) in paginatedData" class="post" :key="index">
<router-link :to="{ name: 'detail', params: {id: post.id, title: post.title, body: post.body} }">
<img src="src/assets/nature.jpg">
<p class="boldText"> {{ post.title }}</p>
</router-link>
<p> {{ post.body }}</p>
</li>
</ul>
<div class="allpagination">
<button type="button" #click="page -=1" v-if="page > 0" class="prev"><<</button>
<div class="pagin">
<button class="item"
v-for="n in evenPosts"
:key="n.id"
v-bind:class="{'selected': current === n.id}"
#click="page=n-1">{{ n }} </button>
</div>
<button type="button" #click="page +=1" class="next" v-if="page < evenPosts-1">>></button>
</div>
</div>
</template>
<script>
import {mapState} from 'vuex'
import {eventEmitter} from './main'
export default {
name: 'app',
data () {
return {
current: null,
page: 0,
visiblePostID: '',
pSearch: ''
}
},
mounted(){
this.$store.dispatch('loadPosts')
},
computed: {
...mapState([
'posts'
]),
evenPosts: function(posts){
return Math.ceil(this.posts.length/6);
},
paginatedData() {
const start = this.page * 6;
const end = start + 6;
return this.filteredPosts.slice(start, end);
},
filteredPosts() {
return this.posts.filter((post) => {
return post.title.match(this.pSearch);
});
},
},
created(){
eventEmitter.$on('messageSave', (string) => {
this.pSearch = string
})
}
}
</script>
You can wrap title and body in an object
addPost() {
const post = {
title: this.createTitle,
body: this.createBody
}
eventEmitter.$emit('postAdd', post)
}
and then listen as normal
created(){
eventEmitter.$on('postAdd', (post) => {
console.log(post)
// do whatever you want
})
}
I have not worked on vue js but agreed with #ittus answer. You can make an object consisting of your required data which you want to share across the component and pass it as an event data.
My vue component like this :
<template>
...
<b-col v-for="item in items"
v-bind:key="item.id"
cols="2">
...
<strong slot="header" class="text-dark" :title="item.tittle" >{{item.tittle}}</strong><br/>
...
<strong class="bg-warning text-dark"><i class="fa fa-money"></i> <b>{{item.price}}</b></strong><br/>
...
</b-col>
...
<b-pagination size="sm" :total-rows="itemsPagination.total" :per-page="itemsPagination.per_page" v-model="itemsPagination.current_page" prev-text="Prev" next-text="Next" hide-goto-end-buttons/>
...
</template>
<script>
export default {
...
data () {
return{
items: '',
itemsPagination: ''
}
},
mounted: function () {
this.getItems()
},
methods: {
getItems() {
let params = []
...
let request = new Request(ApiUrls.items + '?' + params.join('&'), {
method: 'GET',
headers: new Headers({
'Authorization': 'bearer ' + this.$session.get(SessionKeys.ApiTokens),
'Content-Type': 'text/plain'
})
})
fetch(request).then(r=> r.json())
.then(r=> {
this.items = r.data
this.itemsPagination = r
})
.catch(e=>console.log(e))
}
}
}
</script>
If I console.log(this.itemsPagination), the result in console like this :
My view of pagination in my application like this :
If the script executed, it will display content of item in page 1. But if I click page 2 etc, the content of item is not change. I'm still confused to make it work well
How can I solve this problem?
Update
I using coreui
https://coreui.io/vue/demo/#/base/paginations
https://github.com/coreui/coreui-free-vue-admin-template/blob/master/src/views/base/Paginations.vue
https://bootstrap-vue.js.org/docs/components/pagination
You are trying to v-model="itemsPagination.current_page", but you initialize itemsPagination as an empty string:
data () {
return{
items: '',
itemsPagination: ''
}
}
Vue cannot detect property addition or deletion, so nothing reacts. You need to initialize itemsPagination as an object that contains (at least) current_page:
data () {
return{
items: '',
itemsPagination: {
current_page: 1
}
}
}
Update:
You can actually edit the example here. Double-click the upper right corner to edit it, and paste in this code:
<template>
<div>
<h6>Default</h6>
<b-pagination size="md" :total-rows="100" v-model="itemsPagination.current_page" :per-page="10">
</b-pagination>
<br>
<h6>Small</h6>
<b-pagination size="sm" :total-rows="100" v-model="itemsPagination.current_page" :per-page="10">
</b-pagination>
<br>
<h6>Large</h6>
<b-pagination size="lg" :total-rows="100" v-model="itemsPagination.current_page" :per-page="10">
</b-pagination>
<br>
<div>currentPage: {{itemsPagination.current_page}}</div>
</div>
</template>
<script>
export default {
data () {
return {
itemsPagination: {
current_page: 1
}
}
}
}
</script>
<!-- pagination-1.vue -->
Maybe this is not the best way to answer you (redirecting to a video), but i think this video will be clearer than me about what is happening, and how to solve it .
https://youtu.be/7lpemgMhi0k?t=17m42s
I believe, you don't update items reactivly.
Can you try follow:
data () {
return{
container:{
// for Vue.set I didnt found how to do it without nested fields for data object, so did like that on my own project
items: '',
itemsPagination: ''
}
}
},
And then in fetch:
Vue.set(this.container, 'items', r.data);
// or this.$set, if you dont use arrow function here, and not Vue accessable
see https://v2.vuejs.org/v2/guide/reactivity.html
If that not helps, check for console.log(this) inside of your fetch if it your component
I have a project with a front-end written in vue.js by someone else. I need to add a conditional to display links when more than one link is present when the component loads. I apologize if some of my terminology is not correct, I have no prior experience with vue.js or anything similar and my knowledgable of JavaScript is limited.
Component
This is what I have tried so far. Where I get lost is how to trigger it on page load.
var ConceptListItem = {
props: ['concept'],
template: `<div class="list-group-item concept-item clearfix" id="concept-{{ concept.uri }}">
<div id="conceptDiv">
<a v-if="ident" v-on:click="select" style="cursor: pointer;">{{ concept.label }} ({{ concept.authority.name }}) {{ concept.identities[0].concepts[0] }}</a>
<a v-else v-on:click="select" style="cursor: pointer;">{{ concept.label }} ({{ concept.authority.name }})</a>
</div>
<div class="text text-muted">{{ concept.description }}</div>
</div>`,
data: function() {
return {
ident: false,
}
},
methods: {
select: function() {
this.$emit('selectconcept', this.concept);
},
}
}
I have then tried adding a function to created in the vue template
created () {
window.addEventListener('scroll', this.handleScroll);
window.addEventListener('resize', this.handleScroll);
var self = this;
document.getElementById('graphContainer').onmouseup = function() {
self.updateSwimRef();
self.handleScroll();
},
document.getElementById('conceptDiv')il. = function() {
self.ident = true;
}
},
Lets keep all your links into an array say links[], add the following code in your html
<div v-if="links.length>1">
// your code with condition
</div>
<div v-else>
// else part if any
</div>
In vue part you need to add the array in the following way,
data: function () {
return {
links: [],
}
}
I know how to remove a list item from a Vue instance. However, when list items are passed to Vue components, how to remove a list item while keeping the components in sync with the list data?
Here is the use case. Consider an online forum with a Markdown editor. We have a Vue instance whose data are a list of saved comments fetched from a server. These comments are supposed to be written in Markdowns.
To facilitate edits and previews, we also have a list of components. Each component contains an editable input buffer as well as a preview section. The content of the saved comment in the Vue instance is used to initialise the input buffer and to reset it when a user cancels an edit. The preview is a transformation of the content of the input buffer.
Below is a test implementation:
<template id="comment">
<div>
Component:
<textarea v-model="input_buffer" v-if="editing"></textarea>
{{ preview }}
<button type="button" v-on:click="edit" v-if="!editing">edit</button>
<button type="button" v-on:click="remove" v-if="!editing">remove</button>
<button type="button" v-on:click="cancel" v-if="editing">cancel</button>
</div>
</template>
<div id="app">
<ol>
<li v-for="(comment, index) in comments">
<div>Instance: {{comment}}</div>
<comment
v-bind:comment="comment"
v-bind:index="index"
v-on:remove="remove">
</comment>
</li>
</ol>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.js"></script>
<script>
let comments = ['111', '222', '333']
Vue.component('comment', {
template: '#comment',
props: ['comment', 'index'],
data: function() {
return {
input_buffer: '',
editing: false,
}
},
mounted: function() { this.cancel() },
computed: {
preview: function() {
// This is supposed to be a transformation of the input buffer,
// but for now, let's simply output the input buffer
return this.input_buffer
},
},
methods: {
edit: function() { this.editing = true },
remove: function() { this.$emit('remove', this.index) },
cancel: function() { this.input_buffer = this.comment; this.editing = false },
//save: function() {}, // submit to server; not implemented yet
},
})
let app = new Vue({
el: '#app',
data: { comments: comments },
methods: {
remove: function(index) { this.comments.splice(index, 1); app.$forceUpdate() },
},
})
</script>
The problem is that, if we remove a comment, the components are not refreshed accordingly. For example, we have 3 comments in the above implementation. if you remove comment 2, the preview of item 3 will still show the content of item 2. It is updated only if we press edit followed by cancel.
I've tried app.$forceUpdate(), but that didn't help.
You just need to add key attribute in the v-for loop like following:
<li v-for="(comment, index) in comments" :key="comment">
See working fiddle: https://fiddle.jshell.net/mimani/zLrLvqke/
Vue tries to optimises rendering, by providing key attribute, it treats those as completely different elements and re-renders those properly.
See the key documentation for more information.
try with:
Vue.component('comment', {
template:
`<div>
{{ comment }}
<button v-on:click="remove"> X </button>
</div>`,
props: ['comment', 'index'],
methods: {
remove: function() {
this.$emit('remove', this.index);
}
},
});
vm = new Vue({
el: '#app',
data: {
comments: ['a','b','c','d','e']
},
methods: {
remove: function(index) {
this.comments.splice(index, 1);
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.min.js"></script>
<div id="app">
<ol>
<li v-for="(comment, index) in comments">
<comment
v-bind:comment="comment"
v-bind:index="index"
v-on:remove="remove">
</comment>
</li>
</ol>
</div>