Vue on:click Seems to Call All Methods - javascript

I'm currently in the process of learning Vue, and I ran into an issue that I'm hoping someone can help me with.
While using the v-on:click directive to call a method, all other instance methods are being called if the method is also used elsewhere.
HTML:
<div id="exercise">
<div><button class="test" v-on:click="newCheck()">New Challenge</button>
<p >Success check is: {{ passCheck }}</p>
</div>
<div>
<button class="roll" v-on:click="roll20()">Roll!</button>
<p>You Rolled a {{ roll20() }}</p>
</div>
</div>
JS:
new Vue({
el: '#exercise',
data: {
yourRoll: '10',
passCheck: '10',
},
methods: {
roll20: function(){
this.yourRoll = Math.round(Math.random()*19)+1;
return this.yourRoll;
},
newCheck: function(){
this.passCheck = Math.round(Math.random()*19)+1;
}
}
});
When {{ roll20() }} is used in the second paragraph, clicking the 'New Challenge' button runs both roll20() and newCheck(). However, if {{ yourRoll }} is used in the second paragraph instead, this doesn't happen.
In both instances, clicking 'Roll!' only runs roll20().
Can someone help explain what is happening here?
Here is a codepen of the issue:
Codepen of code
Note: I ended up bypassing the issue by using a different approach, but I would still like to know why this was happening: Working Approach

Whenever the DOM is updated, it will run roll20 again, because of the line:
<p>You Rolled a {{ roll20() }}</p>
So anything that triggers an update will trigger roll20 by consequence.
Now, because of the template:
<div><button class="test" v-on:click="newCheck()">New Challenge</button>
We know that when you hit the New Challenge, it calls the newCheck method.
And because the newCheck method changes a variable (passCheck) that is used in the template:
newCheck: function(){
this.passCheck = Math.round(Math.random()*19)+1;
}
That is used here:
<p>Success check is: {{ passCheck }}</p>
Changing passCheck will trigger a DOM update. And DOM updates will call roll20 automatically (because of the reason stated in the first paragraph of this answer).
Working around it:
The simplest way is just not to call roll20 in the template. And, as roll20 actually updates a yourRoll property:
roll20: function(){
this.yourRoll = Math.round(Math.random()*19)+1;
return this.yourRoll;
},
You could just use yourRoll in the template, instead of roll20():
<p>You Rolled a {{ yourRoll }}</p>
CodePen: https://codepen.io/acdcjunior/pen/PePBeo

<div id="exercise">
<div><button class="test" v-on:click="newCheck()">New Challenge</button>
<p >Success check is: {{ passCheck }}</p>
<!-- 3) Call a function to output a random float between 0 and 1 (Math.random()) -->
</div>
<div>
<button class="roll" v-on:click="roll20()">Roll!</button>
<p>You Rolled a {{ yourRoll }}</p> <!-- this changed -->
</div>
</div>
new Vue({
el: '#exercise',
data: {
yourRoll: '10',
passCheck: '10',
},
methods: {
roll20: function(){
this.yourRoll = Math.round(Math.random()*19)+1;
},
newCheck: function(){
this.passCheck = Math.round(Math.random()*19)+1;
}
}
});
Try this it workes fine!Just output your data property because in one your was calling the method! See it in action

Related

x-for does not react on data change with alpinejs and IE11

I am building a web application which must support IE11. For dynamic content i want to use AlpineJS in its ie11 version.
But it ignores data change of my init function. The function itself is async, but even in opera and the version 2 of alpinejs it does not work.
My code looks like this:
<div class="m-3" id="datapanel" x-data="{
title: window.DdsWord.getSearchParam('title'),
results: [],
init: async function (){
console.log('init called!');
window.results = [];
await window.DdsWord.initialize().then(function(data){
console.log(data.data);
results = data.data;
});
console.log('init finished!');
}
}" x-init="init()">
<h1 x-text="title"></h1>
<template x-for="result in results">
<div>
<template x-if="result.isHtmlContent">
<button x-text="result.displayValue" x-on:click="window.DdsWord.addHtml(result.htmlContent);">
</button>
</template>
<template x-if="result.isPlainTextContent">
<button x-text="result.displayValue" x-on:click="window.DdsWord.addPlainText(result.plainText);">
</button>
</template>
</div>
</template>
</div>
The title is changing, but only on first load, if i change its later the value is never applied. Does anybody know what the problem is?

Adding a Vue Element to Hyperlink

I have my Vue element props.item[hdr.value] which is changable. I print this to my webpage using {{ props.item[hdr.value] }}, but I am unsure how to use this value to create a dynamic title tag to mu link:
<a #click="testFunc()" title="More on %%%">{{ props.item[hdr.value] }}</a>
I have tried ' ', + +, { }, {{ }} and various combinations around the call (and numerous Google searches) but I just cannot seem to find he rights syntax to get this to display.
Can anyone help out here please?
You can try this,
new Vue({
el: '#app',
data: {
dynamicVariable: 'Hello Vue.js!'
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<a #click="testFunc()" :title="`More on ${dynamicVariable}`">{{ dynamicVariable }}</a>
</div>
After some work I got the solution, it is a derivative of the answer posted by #ricristian
<a #click="testFunc()" :title="'More on ' + props.item[hdr.value]"></a>

Vue.JS 2.0 slow when modifying unrelated data

Suppose I have an input field in Vue.JS that v-model bind to a String data property, and a long list of random numbers that are completely unrelated to that first String.
data: {
input: "",
randoms: []
}
<input type="text" v-model="input">
<p v-for="random in randoms" v-text="random"></p>
When I put both in the same Vue, I see a huge slowdown when typing in the input field, as it appears Vue is reevaluating the DOM for each list entry after every input event, although they really have nothing to do with each other.
https://jsfiddle.net/5jf3fmb8/2/
When I however move the v-for to a child component where I bind randoms to a prop, I experience no such slowdown
https://jsfiddle.net/j601cja8/1/
Is there a way I can achieve the performance of the second fiddle without using a child-component?
Is there a way I can achieve the performance of the second fiddle without using a child-component?
Short answer
No.
Long answer
Whenever any dependency of the template changes, Vue has to re-run the render function for the entire component and diff the new virtualDOM against the new one. It can't do this for this or that part of the template only, and skip the rest. Therefore, each time the input value changes, the entire virutalDOM is re-rendered.
Since your v-for is producing quite a bit of elements, this can take a few 100ms, enough to be noticable when you type.
Extracting the heavy part of the template into its own component is in fact the "official" way to optimize that.
As Alex explained, v-model.lazy might improve the situation a bit, but does not fix the core of the issue.
Shortest, simplest answer: change v-model to v-model.lazy.
When I put both in the same Vue, I see a huge slowdown when typing in the input field, as it appears Vue is reevaluating the DOM for each list entry after every input event, although they really have nothing to do with each other.
Note that the OnceFor sample still chugs like mad despite not actually being reactive any more. I don't understand Vue well enough to say if that's intentional or not.
const Example = {
data() { return { input: "", randoms: [] } },
created() { this.newRandoms() },
methods: {
newRandoms() { this.randoms = Array(50000).fill().map(() => Math.random()) }
}
}
new Vue({
el: "#vue-root",
data(){ return {example: 'lazy-model'}},
components: {
LazyModel: {...Example, template: "#lazy-model"
},
OnceFor: {...Example, template: "#once-for"
},
InlineTemplate: {...Example, template: "#inline-template",
components: {
Welp: {
props: ['randoms']
}
}
}
}
})
button,
input,
div {
margin: 2px;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="vue-root">
<span><button v-for="(component, name) in $options.components" #click="$set($data, 'example', name)">{{name}}</button></span>
<component :is="example"></component>
</div>
<template id="lazy-model">
<div>
<input type="text" v-model.lazy="input"><br>
<input type="submit" value="Regenerate" #click="newRandoms">
<p v-for="random of randoms" v-text="random"></p>
</div>
</template>
<template id="once-for">
<div>
<input type="text" v-model="input"><br>
<input type="submit" value="Regenerate" #click="newRandoms">
<p v-for="random of randoms" v-text="random" v-once></p>
</div>
</template>
<template id="inline-template">
<div>
<input type="text" v-model="input"><br>
<input type="submit" value="Regenerate" #click="newRandoms">
<welp :randoms="randoms" inline-template>
<div>
<p v-for="(random, index) of randoms" :key="index"> {{index}}: {{random}} </p>
</div>
</welp>
</div>
</template>

Why oldIndex and newIndex always return 0 for Sortable JS (rubaxa-sortable)?

I am using sortable js for drag and drop action. (Link to github page) As I am using vue.js as well, I am using this plugin to bridge them. I am new to both of the library and so I am trying to duplicate the example given in the plugin. (i.e. this example)
HTML section:
<div class="drag">
<h2>List 1 v-dragable-for</h2>
<div class="dragArea" >
<template v-dragable-for="element in list1" options='{"group":{ "name":"people", "pull":"clone", "put":false }}' track-by="$index">
<p>{{element.name}}</p>
</template>
</div>
<h2>List 2 v-dragable-for</h2>
<div class="dragArea">
<div v-dragable-for="element in list2" options='{"group":"people"}' track-by="$index">{{element.name}}</div>
</div>
</div>
Javascript part:
var vm = new Vue({
el: "#main",
data: {
list1:[{name:"John" , id:"1"},
{name:"Joao", id:"2"},
{name:"Jean", id:"3"} ],
list2:[{name:"Juan", id:"4"},
{name:"Edgard", id:"5"},
{name:"Johnson", id:"6"} ]
},
methods:{
}
});
It works fine on jsfiddle, but when I try to duplicate the case on my local server, it always return 0 for both oldIndex and newIndex in the onUpdate event. This makes the element always insert at the beginning of the second list. Any clue on what can I miss to cause this problem?

Meteor: Render a template to the DOM on click

I am having a seemingly simple problem that I cannot really find a solution to. I have 2 columns: One with an overview of different tasks, and one with an area where detailed information about a task should be displayed when the "More information" button attached to each task is clicked. My logic is:
Have 2 templates: task_short and task_long
When the button in task_short is clicked use Blaze.render to render task_long to a div in the second column.
when "More information" is clicked on another task_short, use Blaze.remove to remove the view.
My main problem is: How do I tell Meteor which task should be render in task_long? task_short gets its {{content}},{{name}} etc parameters through the each tasks loop. But how do I do it with a single task? Also, I don't really understand Blaze.remove. Where do I get the ViewId from that needs to be passed in?
I am insanely grateful for any help!
This can be solved with a session variable and some conditionals in your template. You shouldn't need to use Blaze.render/Blaze.remove unless you are doing something really fancy. I don't know exactly how your template is structured, but this example should give you and idea of what to do:
app.html
<body>
{{#each tasks}}
{{> task}}
{{/each}}
</body>
<template name="task">
<div class='short'>
<p>Here are the short instructions</p>
<button>More information</button>
</div>
{{#if isShowingLong}}
<div class='long'>
<p>Here are the long instructions</p>
</div>
{{/if}}
<hr>
</template>
app.js
if (Meteor.isClient) {
Template.body.helpers({
tasks: function () {
// return some fake data
return [{_id: '1'}, {_id: '2'}, {_id: '3'}];
}
});
Template.task.helpers({
isShowingLong: function () {
return (this._id === Session.get('currentTask'));
}
});
Template.task.events({
'click button': function () {
Session.set('currentTask', this._id);
}
});
}

Categories

Resources