Vue.js 2.5 list rendering - javascript

I have an array in JS/Vue that I would like to display in <ul>/<li> tags, and keep updated as the array gets new elements.
HTML:
<ul id="ulist">
<li v-for="user in users">
#{{ user }} <!-- "#" needed since this is in a laravel project with blade templates -->
</li>
</ul>
JS:
<script>
var socket = io('localhost:3000');
new Vue({
el: "#ulist",
data: {
users: []
},
mounted: function() {
this.$nextTick(function() {
socket.on('test-action', function(data) {
this.users.push(data.username);
console.log(data.username);
}.bind(this));
});
}
});
</script>
The array is being properly populated (as I can see via the console.log statement), but the <li v-for="user in users">... part doesn't seem to be working as none of the <li>...</li> elements get created. What am I doing wrong here?
Just to clarify: if I add hard coded values to the users array, those values show up in <li> elements fine, but no additional values that are added to the array (in the mounted function) show up in <li>...</li> elements.
Edit: version is 2.5.13, if it matters

Can you try this ?
<script>
var socket = io('localhost:3000');
new Vue({
el: "#ulist",
data: {
users: []
},
mounted: function() {
var _self = this;
this.$nextTick(function() {
socket.on('test-action', function(data) {
self.users.push(data.username);
console.log(data.username);
}.bind(this));
});
}
});
</script>

The problem is with the scope of your this variable. In your code this line:
this.users.push(data.username);
is scoped to the function call within the ajax request, if you use () => it will keep the current scope in context within your method. Also, you shouldn't need nextTick within the mounted call so try this:
<script>
var socket = io('localhost:3000');
new Vue({
el: "#ulist",
data: {
users: []
},
mounted: function() {
socket.on('test-action', data => {
this.users.push(data.username);
console.log(data.username);
});
}
});
</script>
Although you were using bind(this) you were using this within nextTick which was causing scope issues.
Another thing worth noting, lists require a key in vue v?? (I can't remember which) so it's best to add a key when using v-for:
<ul id="ulist">
<li v-for="(user, index) in users" :key="index">
#{{ user }} <!-- "#" needed since this is in a laravel project with blade templates -->
</li>
</ul>

Related

Using $set to grab jquery variable and push it to an vue array object

I'm pulling data from a API and using jQuery's getJson method to extract the data I'm then trying to assign the data to a vue array object by utilizing app.$set.
So far I've been able to extract the data and assign it to the vue array but I can only access one thing at a time.
<div id="app">
<div v-once id="movies">
{{movieCall()}}
</div>
<div v-for="(movie,index) of movies" class="card" style="width: 18rem;">
<!-- <img src="..." class="card-img-top" alt="..."> -->
<div class="card-body">
<div class="card-title">{{movie}}</div>
</div>
</div>
</div>
var app = new Vue({
el: "#app",
movies: [
],
},
methods:
$.getJSON("https://api.themoviedb.org/3/movie/now_playing?api_key=9d9f46b8451885697e5cf7d1927da80f", function (movie) {
for (let i = 0; i < 3; i++) {
app.$set(app.movies, i, movie.results[i].title);
}
for (var x = 0; x < app.movies.length; x++) {
console.log(app.movies[x])
}
})
},
I'm extracting the movie and setting the title to the movie array but I'm wanting to assign it instead to a movie{title} object. This is so when I go through my v-for loop I can refer to the movie object array as movie.title, movie.overview, etc. to print them all. e.g.
In other words, is there a way to do:
app.$set(app.movies.title, i, movie.results[i].title);
app.$set(app.movies.overview, i, movie.results[i].description);
etc.
and have my movie array set up as:
movie[
{title:}
{description:}
]
and finally loop through like:
<div v-for(movie, index) of movies>
<div class="titles">
{{movie.title}}
</div>
<div class="descriptions">
{{movie.description}}
</div>
</div>
If you want to access movies like:
<div v-for="(movie, index) of movies">
...
{{movie.title}}
...
{{movie.description}}
Then populate it as:
app.$set(app.movies, i, {title: movie.results[i].title, description: movie.results[i].description});
Or, if i is incrementing one by one, the equivalent:
app.movies.push({title: movie.results[i].title, overview: movie.results[i].description});
Your code needs a bit of an upgrade / correction before it's OK, so I prepared a snippet for you that does the same thing (with a mockup JSON response), so you can see that you don't need app or $set for this.
var app = new Vue({
el: "#app",
data: {
movies: []
},
methods: {
movieCall() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(json => {
json.forEach(movie => this.movies.push(movie))
// or just simply:
// this.movies = json
})
}
},
created() {
this.movieCall()
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(movie,index) in movies">
<div>
<div>{{index + ". Title: " + movie.title}}</div>
<div>{{index + ". Body: " + movie.body}}</div>
</div>
</div>
</div>
I tried to keep this as close to your code as possible (but CSS classes were taken out).
Basically Vue is reactive (data property), so if you update the data property movies the template should immediately react to that and update the UI. That's the core idea of a JS frontend framework.
Accessing data in object syntax (like movies.title, with a dot), is another matter. In your project you set up a data property - movies, that's an array. An array can hold any type of elements - objects too.
The idea is that you download the objects and then read them (one by one) into the movies data property. Or, if you receive an array, then this.movies = response (make them equal, assigning the response to the movies array.
Then there's an other thing: Vue templates have their lifecycle, and created() is a hook, that can be used to execute functions when the template is created. If you want something to run once, you should utilize these lifecycle hooks. (Of course, if your app reloads this template, then it executes the hook many times. To avoid this (downloading something multiple times) you should look into state management, but that's a large topic in itself.)
Add data key to hold the data set
<script>
var app = new Vue({
el: "#app",
data: {
movies: [],
},
methods:
$.getJSON("https://api.themoviedb.org/3/movie/now_playing?api_key=9d9f46b8451885697e5cf7d1927da80f", function (movies) {
for (var x = 0; x < movies.results.length; x++) {
//console.log("\nTitle"+movies.results[x].title);
//app.movies.$set(x, movie.results[x].title);
app.movies.push(movies.results[x].title);
console.log(JSON.stringify(app.movies))
}
})
});
</script>
And try with this command
app.movies.push(movie.results[i].title);
Here is a working example or sample which i created : https://plnkr.co/edit/EnepqQqXzEquJlxqjzn6?p=preview
Ref1: https://v2.vuejs.org/v2/guide/list.html

VueJs Async loading templates

I Am building my first VueJs App and I want to asynchronous load my template. In our framework we have our templates stored in a database, that's why.
It is working until I have some nested dom-elements in my template without any data bound to it. So my my Vuejs is like:
var app = new Vue({
el: '#app',
data: {
finish: false,
template: null
},
render: function(createElement) {
if (!this.template) {
return createElement('div', 'loading...');
} else {
return this.template();
}
},
mounted() {
var self = this;
$.post('myUrl', {foo:'bar'}, function(response){
var tpl = response.data.template;
self.template = Vue.compile(tpl).render;
})
}
})
This is working when my template is like:
<div v-show="!finish">
<p>Test</p>
</div>
But when it's like this:
<div v-show="!finish">
<p>
<span>Test</span>
</p>
</div>
I get
[Vue warn]: Error in render: "TypeError: Cannot read property '0' of
undefined" (found in < Root >)
But when it's like this:
<div v-show="!finish">
<p v-show="!finish">
<span>Test</span>
</p>
</div>
It's working again.
Can anyone explain what is happening here? And is this the right way to do it or should I do it an other way?
My guess would be that you should try v-if instead of v-show. What v-show does is changing display property, vue is trying to render the element anyway.
docs

How to reassess a computed value upon key press?

I would like to display a different random word from a list upon pressing a key.
The "displaying a random word" part works fine:
var vm = new Vue({
el: "#root",
data: {
verbs: ['parier', 'coûter', 'couper', 'blesser']
},
computed: {
verb: function() {
return this.verbs[Math.floor(Math.random() * this.verbs.length)];
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<div id="root">
{{verb}}
</div>
I now would like to bind a keypress to the re-computation of verb. How should I do that?
The documentation on event handling suggests using v-on:keydown for this - I can add JavaScript (v-on:keydown="alert()" for instance) but do not know how to trigger a recalculation of a value (I tried v-on:keydown="eval(verb)" but it did not work).
Computed values by design are ideally run once.
One solution mentioned by Vue's creator, Evan, was to attach a global listener on component creation, and then call your method directly.
var vm = new Vue({
el: "#root",
data: {
verb: '',
verbs: ['parier', 'coûter', 'couper', 'blesser']
},
methods: {
getRandomVerb: function() {
this.verb = this.verbs[Math.floor(Math.random() * this.verbs.length)];
}
},
mounted() {
window.addEventListener('keydown', this.getRandomVerb)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<div id="root">
{{verb}}
</div>
To get the demo to respond correctly, Run the code snippet, then click in the snippet window and begin typing. Random verbs will be displayed.

bind data to vue model dynamically in component

I'm trying to make a simple form that will accept user's input for different types of currency.
Here's a (broken) fiddle that hopefully gets across what I want to do:
https://jsfiddle.net/4erk8yLj/7/
I'd like my component to bind data to my root vue instance, but I'm not sure if my v-model string is allowable. Check it out:
Vue.component('conversion-row', {
props: ['currency', 'values'],
template: '<div>{{currency}}:</div><div><input v-model="values[currency]></div><',
});
var vm = new Vue({
el: "#app",
data: {
currencies: ['USD', 'BTC'],
values: {
'BTC': '',
'USD': ''
}
}
});
template:
<div id="app">
<li>
<conversion-row is li v-for="currency in currencies" v-bind:currency="currency">
</conversion-row>
</li>
</div>
What's a good way to fix this?
Couple of things you might need to correct:
First, the data property must be a function rather than an object. This allows every instance to get data recomputed every time it is being created, see:
var vm = new Vue({
el: "#app",
data() {
return {
currencies: ['USD', 'BTC'],
values: {
'BTC': 'BTC Value',
'USD': 'USD Value',
},
};
}
});
Second, <conversion-row> doesn't have values property bound. Here's what you can do:
<div id="app">
<li v-for="currency in currencies">
<conversion-row :currency="currency" :values="values"></conversion-row>
</li>
</div>
Last, the component should always aim for one root element (wrapper) and then you can nest as many children inside as you want. What's more, instead of using v-model, you can bind value which is the proper way to pass a value to an input (one-way data binding), check the following:
Vue.component('conversion-row', {
props: ['currency', 'values'],
template: '<div>{{currency}}:<input type="text" :value="values[currency]"></div>'
});
There's more improvements you could possibly make here like re-thinking if you need to pass values as well as currency to the conversion-row but I'm pretty sure you'll figure it out later on.
All that above will make your code run and execute properly, here's the working example (fork of yours):
https://jsfiddle.net/maciejsmolinski/mp8m0ben/1/
Does this help?
Not sure what you're aiming for in terms of using v-model, but here's an example of working v-model (based on your example):
Vue.component('conversion-row', {
props: ['currency', 'values'],
template: '<div>{{currency}}:<input type="text" v-model="values[currency]"></div>'
});
And the corresponding template:
<div id="app">
<p><strong>USD Value:</strong> {{ values.USD }}</p>
<p><strong>BTC Value:</strong> {{ values.BTC }}</p>
<br>
<li v-for="currency in currencies">
<conversion-row :currency="currency" :values="values"></conversion-row>
</li>
</div>
You can find it under the following URL:
https://jsfiddle.net/maciejsmolinski/0xng8v86/2/

Show Action via iron:router, template isn't passed data from a collection.findOne method

I can't get a the 'show' page of an instance of a model to display its data.
Here's the template that won't show its data:
<template name="priority">
<h1>Priority: {{title}}</h1>
</template>
It's very simple in and of itself, yet I can't get title to display. Iron:router does the job of directing us to this page with the following code:
Router.route('/priority/:_id', function(){
var priority = this.params._id;
this.render('priority', {
data: function(priority){
Meteor.call('showPriority', priority, function(error){
if (error) {
console.log("An error has occured: " + error);
}
})
}
})
}, {
name: 'priority.show'
});
The Meteor.method is very simple, it just queries for the variable priority:
'showPriority': function(priority) {
return Priorities.findOne({_id: priority});
}
The view which carries the href is here:
<template name="priorityList">
<ul class="table-view">
{{#each this}}
<li class="table-view-cell">
{{title}}
<span class="pull-right icon icon-edit"></span>
</li>
{{/each}}
</ul>
</template>
Note that this view shows a list of all priorities. When I inspect the href, all the paths are being dynamically generated with an _id:
<a href="/priority/yYihyZmZ2xkAso7i5">...
Oh, and I should mention, that I also tried to use the waitOn method, since I thought I might be loading the template before the data, but that didn't help either...
Router.configure({
...
waitOn: function(){
return Meteor.subscribe('priorities');
}
});
So much code, just to show what's going on!
What's the deal here? Why won't my "show" template give me any data?
Change you route to this.
Router.map(function () {
this.route('priority', {
path: '/priority/:_id',
waitOn: function(){
return Meteor.subscribe('priorities',this.params._id);
},
data: function(){
if(this.ready()){
return Priorities.findOne({_id: this.params._id});
}else{
this.render('loading') //if data not ready we render some loading template
}
}
});
});
You don't need to make a Meteor.call, for the find(); instead to everything on the data:function()
The above is just an example so you can get the idea, but it should work since you are expecting _id:priority and _id:this.params._id its the same.
Just be sure you have the autopublish package removed.
and have the subscriptions/publish in order.
Meteor.publish('Menu', function(){
return Priorities.find();
});

Categories

Resources