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: [],
}
}
Related
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>
Hello all and thank you very much.
This is my first try with vue.js and I cant get the example working.
this is the error i am getting
vue.runtime.esm.js:620 [Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
What does compile mean? It's seems like a valid ES5. I am using the rollup-plugin-vuehere. What option should I use to get the compiled components. or am I trying to connect to the DOM incorrectly?
Thank you very much :)
var ListItem = Vue.component('ListItem', {
data: function () {
return {
title: ''
}
},
addNewItem: function() {
if(this.newItem.trim().length > 0) {
this.items.push(this.newItem);
this.newItem = "";
}
},
template: `
<div class="list-item">
{{ title }}
</div>
`
});
var DynamicList = Vue.component('DynamicList', {
data: function () {
return {
title: '',
newItem: '',
items: []
}
},
addNewItem: function() {
if(this.newItem.trim().length > 0) {
this.items.push(this.newItem);
this.newItem = "";
}
},
template: `
<div class="dynamic-list">
<h3>{{ title }}</h3>
<div>
<input type="text" v-model="newItem">
<input type="button" >
</div>
<ListItem
v-for="item in items"
v-bind:key="item"
v-bind:title="title"
></ListItem>
</div>
`
});
new Vue({
el: "#vue-container",
data: {},
render: h => h(DynamicList),
});
I have seen a few questions similar to this but all revolve around webpack, which I am not currently using.
I have a Vue template:
var question = Vue.component('question', {
props: {
scenario: { type: Object }
},
beforeMount: function () {
this.retrieveScenario();
},
methods: {
postChoice: function () {
Post("Study", "PostScenarioChoice")
},
retrieveScenario: function () {
Get("ScenariosVue", "GetScenario", 1, this,
(c, data) => { this.scenario = data; },
(c, errors) => { console.log(errors); }
);
}
},
template:
`<div class="visible">
<div class="row justify-content-center">
<div class="col-lg-6">
<a v-on:click="this.postChoice">
<img class="img-fluid" v-bind:src="scenario.scenarioLeftImg" />
</a>
</div>
<div class="col-lg-6">
<a v-on:click="this.postChoice">
<img class="img-fluid" v-bind:src="scenario.scenarioLeftImg" />
</a>
</div>
</div>
</div >`
});
The Ajax retrieval returns an object with the following:
returned
Object {
scenarioId: 1,
description: "Dog vs Bolard",
scenarioLeftImg: "images\\Scenarios\\bolard_dog_Left.png",
scenarioRightImg: "images\\Scenarios\\bolard_dog_Right.png",
participantScenarios: [],
scenarioTypesScenarios: []
}
However, the Html, doesn't add the src tag to the tag and I'm really not sure why because the data's clearly available.
Much Help would be greatly appreciated.
This is happening because Vue's template system doesn't "watch" properties (or nested properties) of an object for changes. If you need to do this, then you can either use the computed property with a watch on the computed property, or you can just create two props instead of the single prop. Here is what I would do to change your code:
var question = Vue.component('question', {
props: {
// Replace this prop with the two below.
// scenario: { type: Object }
scenarioLeftImg: { type: String },
scenarioRightImg: { type: String }
},
beforeMount: function () {
this.retrieveScenario();
},
methods: {
postChoice: function () {
Post("Study", "PostScenarioChoice")
},
retrieveScenario: function () {
Get("ScenariosVue", "GetScenario", 1, this,
(c, data) => { this.scenario = data; },
(c, errors) => { console.log(errors); }
);
}
},
template:
`<div class="visible">
<div class="row justify-content-center">
<div class="col-lg-6">
<a v-on:click="this.postChoice">
<img class="img-fluid" v-bind:src="scenarioLeftImg" />
</a>
</div>
<div class="col-lg-6">
<a v-on:click="this.postChoice">
<img class="img-fluid" v-bind:src="scenarioLeftImg" />
</a>
</div>
</div>
</div >`
});
Your main issue is that due to the limitations of Javascript, Vue cannot detect property additions in Objects.
Change to
retrieveScenario: function () {
Get("ScenariosVue", "GetScenario", 1, this,
(c, data) => { Vue.set(this.data, 'scenarios', data); },
(c, errors) => { console.log(errors); }
);
}
Please be aware that it is considered really bad practice to have components modify their props. Rather mirror those props into your vm's data() function in the created() hook of your component, then modify that. If you need the "modified" props outside of your component, emit an event telling those components props has changed.
Also replace the backslashes in your path by slashes:
scenarioLeftImg: "images\\Scenarios\\bolard_dog_Left.png",
scenarioRightImg: "images\\Scenarios\\bolard_dog_Right.png",
has to be
scenarioLeftImg: "images/Scenarios/bolard_dog_Left.png",
scenarioRightImg: "images/Scenarios/bolard_dog_Right.png",
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>
UPDATE: Almost there. Added the template to HTML. Added v-if and v-else to the button (couldn't get it to work on the template). Hard coded the example below. Trying to sus out final object path to array - for some reason v-if="this.id == favourites.listings_id" isn't working.
I'm building a favourites feature using vue.js. The create and delete requests both work fine. Finally what's required is to either show class favourited or notFavourited with appropriate action template create or delete depending on if the user has favourited the object or not. The users favourites array passes to the favourites component - and I've been trying to handled it that way.
I've commented out what I'm trying to achieve in the js code below.
HTML
<div id="app">
<listings>
</listings>
</div>
<template id="listing-template">
<div class="container">
<div v-for="listing in listings" class="panel panel-default">
<div class="panel-body">
<div>#{{ listing.id }}</div>
<favourite :id="listing.id"></favourite>
</div>
</div>
</div>
</template>
<template id="favourite-template">
<form><input class="hidden" type="input" name="_method" value="#{{ id }}" v-model="form.listings_id"></input><button v-if="this.id == 2" class="not-favourited" #click.prevent="create(favourite)" :disabled="form.busy"><i class="fa fa-heart" aria-hidden="true"></i><button v-else class="favourited" #click.prevent="delete(favourite)" :disabled="form.busy"><i class="fa fa-heart" aria-hidden="true"></i></button></form>
</template>
VUE.JS
new Vue({
el: '#app'
});
Vue.component('favourite', {
props: ['id'],
data: function() {
return {
form: new SparkForm({
listings_id: ''
}),
favourited: {
color: 'red',
fontSize: '12px'
},
notFavourited: {
color: 'black',
fontSize: '12px'
},
favourites: [], listings: [],
};
},
created() {
this.getFavourites();
},
methods: {
getFavourites() {
this.$http.get('/api/favourites')
.then(response => {
this.favourites = response.data;
});
},
// if favourite exists in favourites array
// then give class favourited and call template delete
// else give class notFavourited and call template create
create() {
Spark.post('/api/favourite', this.form)
.then(favourite => {
this.favourite.push(favourite);
this.form.id = '';
});
},
delete(favourite) {
this.$http.delete('/api/favourite/' + this.id);
this.form.id = '';
}
}
});
Vue.component('listings', {
template: '#listing-template',
data: function() {
return {
listings: [],
};
},
created() {
this.getListings();
},
methods: {
getListings() {
this.$http.get('/api/listings')
.then(response => {
this.listings = response.data;
});
}
}
});
EXAMPLE OBJECT IN FAVOURITES ARRAY
4: Object
created_at: "2016-07-22 21:14:40"
id: 81
listings_id: 1
updated_at: "2016-07-22 21:14:40"
user_id: 3