Vue.js Component doesn't show data from method of component - javascript

I am new to Vue.JS (2) and I am trying to learn components now. I try to use a component in another component with data from a data method (I know you can't use the property Data in a component). My code now:
HTML
<div id="root">
<h1>Tasks</h1>
<list></list>
</div>
JS
Vue.component('list', {
template: '<task v-for="task in tasks">{{ task.task }}</task>',
data: function() {
return {
tasks: [
{task: 'Go to the store', completed: false},
{task: 'Go to the shop', completed: true},
{task: 'Go to the mall', completed: true}
]
};
}
});
Vue.component('task', {
template: '<li><slot></slot></li>'
});
new Vue({
el: '#root'
});
this will return a white screen. If I remove the data, and just use a string in the task template it shows the string, so the component "task" is working in the component "list".
Vue.component('list', {
template: '<task>Test</task>',
});
Vue.component('task', {
template: '<li><slot></slot></li>'
});
new Vue({
el: '#root'
});
So it seems like there is something wrong with displaying the data to the view with the method/data. I've tried many things but I just can't get it right.
Any help would be great, thanks in advance

As documented here,
components must contain exactly one root node.
Putting a v-for on the top-level element makes it repeated. If you wrap that element in a div, it works.
It looks like you may get around that limitation if you want to write your own render function.
Vue.component('my-list', {
template: '<div><task v-for="task in tasks">{{ task.task }}</task></div>',
data() {
return {
tasks: [{
task: 'Go to the store',
completed: false
},
{
task: 'Go to the shop',
completed: true
},
{
task: 'Go to the mall',
completed: true
}
]
};
}
});
Vue.component('task', {
template: '<li><slot></slot></li>'
});
new Vue({
el: '#root'
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.5/vue.min.js"></script>
<div id="root">
<h1>Tasks</h1>
<my-list></my-list>
</div>

Related

V-if directive not dynamically updating on variable change

I'm trying to create a button in my program that toggles on a number of other things and removes itself once it's clicked. The relevant HTML is as follows:
<div id="app">
<button #click="reveal" v-if="!showlists">Start</button>
<ul v-if="showlists">
<list v-for="name in chosenNames" v-bind:name="name"></list>
</ul>
</div>
In this, the unordered list should be shown once the variable "showlists" is true and the button should be removed once "showlists" is true. My Vue app looks like this:
let app = new Vue({
el: "#app",
data: {
showlists: false,
chosenNames: [
{ text: "name1" },
{ text: "name2" },
{ text: "name3" },
]
},
methods: {
reveal: function() {
showlists = true;
}
}
})
Based on this, the "showlists" variable starts as false, and the program works as intended with the button showing and the list hidden. Once the button is clicked, the function runs and showlists is then set to true (I confirmed this in my troubleshooting efforts). However, once this occurs, the DOM does not dynamically update and instead just remains as it was at the start.
Sorry if this is something really basic, I'm very new to Vue and still trying to learn :)
Any and all help would be appreciated.
You have to use the "this" keyword in your "revel" method before showlists like this.showlists = true; variable in your "Vue" instance.
For example, you can write like as follows
<div id="app">
<button #click="reveal" v-if="!showlists">Start</button>
<ul v-if="showlists">
<list v-for="(name, index) in chosenNames" :name="name" :key="'list-'+index"></list>
</ul>
</div>
And for new "Vue" instance
let app = new Vue({
el: "#app",
data: {
showlists: false,
chosenNames: [
{ text: "name1" },
{ text: "name2" },
{ text: "name3" },
]
},
methods: {
reveal: function() {
this.showlists = true;
}
}
})
I hope that might solve the problem :)
your code has 4 bug:
v-bind is set element's attribute, not innerHTML
showlists need change to this.showlists
showlists = true; is always set to true
list isn't valid html tag, you need li
below is right code:
<div id="app">
<button #click="reveal" v-if="!showlists">Start</button>
<ul v-if="showlists">
<li v-for="name in chosenNames" v-html="name"></li>
</ul>
</div>
<script>
let app = new Vue({
el: "#app",
data: {
showlists: false,
chosenNames: [
{ text: "name1" },
{ text: "name2" },
{ text: "name3" },
]
},
methods: {
reveal: function() {
this.showlists = !this.showlists;
}
}
})
</script>

Displaying random text in Vue.js template

I have a simple Loader component in my Vue.js app that just displays one of messages, randomly. I did it like that:
Vue.component('Loader', {
data() {
const textEntries = [
'Just a second',
'Please wait',
'Almost there',
'And there we go',
];
return {
text: textEntries[Math.trunc(Math.random() * textEntries.length)]
};
},
template: '<p class="loading">{{ text }}...</p>'
});
I'm not sure if keeping this in data like that is fine. Won't my text ever get re-rendered with another text? Also, having the array in the data() method seems awkward. Would it be more suitable to use lifecycle hooks for that instead? Or computed property?
You should use the lifecycle hooks which Vue provides. More precisely, I believe that for your example, you should use the created hook. So your code will be like this:
Vue.component('Loader', {
created: function () {
this.$options.textEntries = [
'Just a second',
'Please wait',
'Almost there',
'And there we go',
];
this.$options.randomIndex = Math.trunc(Math.random() * textEntries.length);
},
data() {
return {
text: this.$options.textEntries[this.$options.randomIndex]
};
},
template: '<p class="loading">{{ text }}...</p>'
});
If you leave textEntries within the data Vue property, then you will not have access to it as it will be erased from the memory as soon as the data function is processed.
WARNING: The $options, which I am using, cannot be changed since it is read-only. For more information, I would redirect you to this.
As I know data() will called only once, so the text will be one of the textEntries
I think it's better to extract textEntries out of the data method(since it will not be changed, so it should not be generated each data() call.
The right way would be to define the array in data and get the random text as computed:
new Vue({
el:"#app",
data() {
return{
textEntries: [
'Just a second',
'Please wait',
'Almost there',
'And there we go',
]
}
},
computed:{
text: function(){
return this.textEntries[Math.trunc(Math.random() * this.textEntries.length)]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p class="loading">{{ text }}...</p>
</div>
If you want the text to be re-rendered every period of time, you can use setTimeout and keep track of the random index:
new Vue({
el:"#app",
data() {
return{
textEntries: [
'Just a second',
'Please wait',
'Almost there',
'And there we go',
],
index: 0
}
},
mounted(){
setInterval(function(){
this.index = Math.trunc(Math.random() * this.textEntries.length)
}.bind(this), 3000);
},
computed:{
text: function(){
return this.textEntries[this.index];
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p class="loading">{{ text }}...</p>
</div>

Is there any way to have multiple Vues have a computed listener working on the same value?

Setup:
I have multiple Vue components, and each component has multiple instances in different dialogs in my web app.
For each type of component I have a global state (handrailOptions in the example below) so that each type of component stays in sync across the dialogs.
I'd like for it so that when a component proceeds beyond step 1, I hide the other components in that dialog.
I have achieved this nicely using the computed / watch combo.
However, my problem is that it seems if I try to listen in with computed through more than 1 Vue instance, it hijacks the other listeners.
Problem
Below is a simplified version of what I'm working with, when the app starts up, the console logs 'computed 1' & 'computed 2'. But then when I change handrailOptions.step, only the second fires. ('computed 2' & 'watched 2')
Is there any way to have multiple Vues have a computed listener working on the same value?
handrailOptions = {
step: 1
};
Vue.component( 'handrail-options', {
template: '#module-handrail-options',
data: function() {
return handrailOptions;
},
});
var checkoutDialog = new Vue({
el: '#dialog-checkout',
computed: {
newHandrailStep() {
console.log('computed 1');
return handrailOptions.step;
}
},
watch: {
newHandrailStep( test ) {
console.log('watched 1');
}
}
});
new Vue({
el: '#dialog-estimate-questions',
computed: {
newHandrailStep() {
console.log('computed 2');
return handrailOptions.step;
}
},
watch: {
newHandrailStep( test ) {
console.log('watched 2');
}
}
});
This works as expected. I made handrailOptions responsive by making the data object of a new Vue. Making it the data object of a component, as you did, could also work, but the component would have to be instantiated at least once. It makes more sense to have a single object for your global, anyway.
handrailOptions = {
step: 1
};
// Make it responsive
new Vue({data: handrailOptions});
var checkoutDialog = new Vue({
el: '#dialog-checkout',
computed: {
newHandrailStep() {
console.log('computed 1', handrailOptions.step);
return handrailOptions.step;
}
},
watch: {
newHandrailStep(test) {
console.log('watched 1');
}
}
});
new Vue({
el: '#dialog-estimate-questions',
computed: {
newHandrailStep() {
console.log('computed 2', handrailOptions.step);
return handrailOptions.step;
}
},
watch: {
newHandrailStep(test) {
console.log('watched 2');
}
}
});
setInterval(() => ++handrailOptions.step, 1500);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="dialog-estimate-questions">
Main step {{newHandrailStep}}
</div>
<div id="dialog-checkout">
CD step {{newHandrailStep}}
</div>

Sending data to a non parent component in VueJs

I am trying to replicate the TODO MVC in VueJs.
(Please checkout this codepen : http://codepen.io/sankalpsingha/pen/gwymJg )
I have created a component called 'todo-list' with the following code :
Vue.component('todo-list',{
template: '#todo-list',
props: ['todo'],
data: function() {
return {
// Let us set up a isEditing Boolean so that we can know if the user
// will edit something and we need to change the state to reflect it.
isEditing: false,
}
},
methods: {
enableEditing: function() {
this.isEditing = true;
},
editTodo: function(todo) {
// todo.todo = todo.todo.trim();
this.isEditing = false;
},
removeTodo: function(todo) {
//this.todos.$remove(todo); // --> This part is not working?
}
}
});
However, I have the data defined in the app instance :
var app = new Vue({
el: '#todo-section',
data: {
newTodo: '',
todos: [
{
id: 1,
todo: 'Go to the grocery',
completed: false,
},
{
id: 2,
todo: 'See the movie',
completed: true,
},
{
id: 3,
todo: 'Jack Reacher : Tom Cruise',
completed: false,
}
]
},
methods: {
addTodo: function() {
// This will not allow any empty items to be added.
if(this.newTodo.trim() == '') {
return;
}
this.todos.push({
todo: this.newTodo.trim(),
completed: false,
});
this.newTodo = '';
}
}
});
I am not able to delete a single Todo from the list. My guess is that I have to send a emit message to the app instance and put up a listener there to delete the data from it? How do I delete the data?
When I tried to delete by clicking the x button in your codePen example, I see the error: this.$parent.todos.$remove is not a function.
I have not looked deeply into your code. But attempting to access parent component methods using this.$parent is not a good idea. Reason: a component can be used anywhere, and assuming that it will have a $parent with a particular property or method is risky.
As you suggested in your question, you need to use $emit from the child component to delete the data.
There was another similar question here few days ago, for which I created a jsFiddle: https://jsfiddle.net/mani04/4kyzkgLu/
The child component has some code like:
<button #click="$emit('delete-row')">Delete</button>
This sends out an event to parent component. Parent component can subscribe to that event using v-on as seen in that jsFiddle example.
Here is that other question for reference: Delete a Vue child component
It's preferable to use your methods (DeleteTodo, EditTodo...) in your parent.
var app = new Vue({
el: '#app',
data: {
newTodo: '',
todos: [{
id: 1,
title: 'Go to the grocery',
completed: false
}, {
id: 2,
title: 'See the movie',
completed: true
}, {
id: 3,
title: 'Jack Reacher : Tom Cruise',
completed: false
}]
},
methods: {
addTodo: function() {
this.todos.push({
todo: this.newTodo.trim(),
completed: false
});
this.newTodo = ''
},
deleteTodo: function(todo) {
this.todos = this.todos.filter(function(i) {
return i !== todo
})
}
}
});
<div id="app">
<ul>
<li v-for="todo in todos">{{ todo.title }}
<button #click.prevent="deleteTodo(todo)">
Delete
</button>
</li>
</ul>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>

durandal.js navigation with parameters

i am not able to navigate from one view to another view with pararameter
from :-
ViewModel : App/Foldername/page1.js
View : App/Foldername/page1.html
i want to go with id parameter:
ViewModel : App/Foldername/page2.js
View : App/Foldername/page2.html
in page1.js i wrote following things,
self.goTopage2 = function (id) {
router.mapRoute('Foldername/page2/:id', 'viewmodels/Foldername/page2', 'This is page2view');
};
in shell.js
function boot() {
router.mapNav('home');
router.mapNav('details');
router.mapNav('Foldername/page2');
log('Hot Towel SPA Loaded!', null, true);
return router.activate('home');
}
please guid me correct way!
A common approach is to have a list of routes somewhere and load up that list. When you define a list such as below, you need to use router.map() to map the routes, as mapNav creates a default route without parameters. Example of an object containing routes -
var routes = [{
url: 'home',
moduleId: 'viewmodels/home',
name: 'Home',
visible: true,
settings: {}
}, {
url: 'events',
moduleId: 'viewmodels/events/events',
name: 'Events',
visible: true,
settings: {}
}, {
url: 'eventdetails/:id',
moduleId: 'viewmodels/events/eventdetails',
name: 'Event Details',
visible: false,
settings: { event: true, show: false }
}];
And how to map those routes -
router.map(routes);
And finally how to visit those routes -
router.activate('home');
or
var url = '#/fighterdetails/' + selectedFighter.id();
router.navigateTo(url);
(DurandalJS 1.2.0) I'm not totally sure if this is the best way, since I'm quite new to Durandal, but at least managed to make it work with this:
In main.js:
router.mapRoute('details/:id', 'viewmodels/details', 'Details', false);
In list.js:
loadDetails: function (id) {
router.navigateTo('#/details/' + id);
},

Categories