How to reassess a computed value upon key press? - javascript

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.

Related

Using Vue.js directives within component template

I'm new to Vue.js and trying to create a component that connects to one object within some global-scope data and displays differently based on the specifics of each object. I think I'm misunderstanding how the directives v-if and v-on work within component templates. (Apologies if this should actually be two different questions, but my guess is that the root of my misunderstanding is the same for both issues).
Below is a minimal working example. My goal is to have each member entry only display the Disable button if the associated member is active, and enable changing their status via the button. (I also want to keep the members data at the global scope, since in the actual tool there will be additional logic happening outside of the app itself).
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<member-display
v-for="member in members"
v-bind:member="member"
></member-display>
</div>
<script>
var members = [
{name: "Alex", status: "On"},
{name: "Bo", status: "On"},
{name: "Charley", status: "Off"}
]
Vue.component('member-display', {
props: ['member'],
computed: {
active: function() {
// Placeholder for something more complicated
return this.member.status == "On";}
},
methods: {
changeStatus: function() {
this.member.status = 'Off';
}
},
// WHERE MY BEST-GUESS FOR THE ISSUE IS:
template: `
<div>
{{member.name}} ({{member.status}})
<button v-if:active v-on:changeStatus>Disable</button>
</div>
`
});
var app = new Vue({
el: "#app",
data: {
members: members
}
})
</script>
</body>
</html>
Thanks for your help!
The code v-if and the v-on for the button just have the wrong syntax. The line should look like this:
<button v-if="active" v-on:click=changeStatus>Disable</button>

How to pass data into Vue instance so that subsequent updates are reflected?

I'm trying to dynamically update a Vue JS prop after the view has been loaded and the custom has been initialised. I'm building a custom Vue plugin and am using props to pass options, one of which is a object which I need to dynamically update the value passed after the component has loaded, e.g:
<div id="app">
<script>
var seedData = {
percent: 50,
name: 'Smith'
}
setInterval(() => {
seedData = {
percent: Math.random(),
name: 'Smith'
}
}, 1000)
</script>
<offers :parent-data="seedData"></offers>
</div>
Vue.component('offers', {
template: '<h1>Parent Data: {{ parentData.percent }}</h1>',
props: {
parentData: {
default: () => ({
percent: 0,
name: 'John'
}),
type: Object
},
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app'
});
This will load the initial name/values from offersData, however, the new values on the setInterval doesn't get passed through.
I've tried adding a watcher inside of my custom Vue plugin that gets loaded through <offers> but this doesn't seem to work either:
watch: {
parentData: function (newVal) {
this.parentData = newVal
}
}
UPDATE
The following is my implementation:
Code Pen -> https://codepen.io/sts-ryan-holton/pen/VwYNzdZ
There are multiple problems with your code
Everything inside <div id="app"> is treated by the Vue as a template and compiled - see the docs
If neither render function nor template option is present, the in-DOM HTML of the mounting DOM element will be extracted as the template. In this case, Runtime + Compiler build of Vue should be used.
Including <script> tag there is wrong. Just try to include vue.js (debug build) instead of vue.min.js (minified production build of Vue) and you will see bunch of errors (btw its always good idea to use debug build for development as it gives you lots of useful errors and warnings)
The fact that it "somehow works" in prod build (ie the initial values are shown on the page) doesn't mean it's supported...
So the inside of <div id="app"> is template for Vue. As I said before in the comments, all data referenced by template must be in the context of Vue instance. You cannot pass some global variable into props. So moving <script> outside of <div id="app"> won't help
[Vue warn]: Property or method "seedData" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initialising the property.
What you can do is to pass seedData object into root Vue instance like this:
var vm = new Vue({
el: '#app',
data: {
seedData: seedData
}
});
Now the errors are gone but data changes is not reflected still. Reason for that is not Vue specific. Its simple JavaScript. Object are passed by reference in JS. Look at this code:
var a = { name: 'John' }
var b = a
a = { name: 'Smith' }
// b is still pointing to "John" object
To workaround it, don't replace whole object. Just mutate it's properties (beware of Vue reactivity caveats)
setInterval(function() {
seedData.name = 'John';
seedData.percent = Math.random();
}, 1000)
Whole solution:
Vue.component('offers', {
template: '<h1>{{ parentData.name }}: {{ parentData.percent }}</h1>',
props: {
parentData: {
default: () => ({
percent: 0,
name: 'John'
}),
type: Object
},
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app',
data: {
seedData: seedData
}
});
<script>
var seedData = {
percent: 60,
name: 'Smith'
}
setInterval(function() {
seedData.name = 'John';
seedData.percent = Math.random();
}, 1000)
</script>
<div id="app">
<offers :parent-data="seedData"></offers>
</div>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>

Why Vue.js can't update the value once it has been set through $refs

Why doesn't the title change here once the callback function finishes executing nor when I click on the Change title button?
This is an example from a Udemy VueJS course and it runs fine on the video but I just can't seem to make it work on my side no matter what I try. I hope the question is understandable and well formulated.
var vm1 = new Vue({
el: '#app1',
data: {
title: 'The VueJS Instance',
},
methods: {
updateTitle: function(title) {
this.title = title;
}
}
});
vm1.$refs.heading.innerText = 'Something else';
setTimeout(function() {
vm1.title = 'Changed by Timer';
}, 3000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<div id="app1">
<h1 ref="heading">{{ title }}</h1>
<button v-on:click="updateTitle('a')" ref="myButton">Change title</button>
</div>
$refs is non-reactive,so you can't do data binding with it.
Check the documentation https://v2.vuejs.org/v2/api/#ref
If you want to use $refs to change value, you have to directly refer to it with property.
var vm1 = new Vue({
el: '#app1',
data: {
title: 'The VueJS Instance',
},
methods: {
updateTitle: function(title) {
this.$refs.heading.innerText = title;
}
}
});
vm1.$refs.heading.innerText = 'Something else';
setTimeout(function() {
vm1.$refs.heading.innerText = 'Changed by Timer';
}, 3000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<div id="app1">
<h1 ref="heading">{{ title }}</h1>
<button v-on:click="updateTitle('a')" ref="myButton">Change title</button>
</div>
I can't say for sure, but most likely this is happening because you manually messed with the DOM outside of Vue's knowledge.
Vue does a lot of bookkeeping in order to make patching the DOM as efficient as possible. By setting the innerText of the heading element, the existing text node (that Vue controls) is being replaced by a new text node that Vue doesn't control. When Vue re-renders the component, it detects that only the title data property changed, so it patches the old text node which no longer exists in the DOM because it got replaced.

Vue.js 2-way data bind only working one way

I have the following element:
<input type="text" v-model="selectedPost.title" v-bind:value="selectedPost.title">
and I have the following Vue.js app:
var vueapp = new Vue({
el: '#app',
data: {
selectedPost: {}
}
});
When I type something on the input, the Vue model is updated, I can see it unning this on my browser console:
vueapp.$data.selectedPost.title
Returns the value typed in the textbox. All good.
But.. when I do this:
vueapp.$data.selectedPost.title = "changed";
The textbox does not update with the assigned value.
Why? How to make it work?
This jsfiddle shows the issue happening: https://jsfiddle.net/raphadko/3fLvfea2/
The behavior you are seeing is because Vue cannot detect properties added to objects after the Vue has been initialized unless you add them using $set. In this case, just initialize the title property.
var vueapp = new Vue({
el: '#app',
data: {
selectedPost: {title:''}
}
});
Updated fiddle.

VueJS - Initializing a tagsinput form field that was loaded as part of template or $el attribute?

I'm following the pattern described in the official documentation of loading views using components. One of the components has a form field I need to have a method called .tagsinput() called on since I'm using TagsInput. So, something like $('#tags').tagsinput(). Here's a simplified version of what I'm doing:
CreateBoardForm = Vue.extend
template: "<input type='text' v-text='tags' id='tags'/>"
data:
tags: ''
ready: ->
// this is where I'm hoping to access
// tags and call $('#tags').tagsinput() on it
// However, this.$el and this.template are all undefined
// I was hoping to do something like this.$el.find('#tags').tagsinput()
Vue.component('CreateBoardForm', CreateBoardForm)
vue = new Vue(
el: '#main',
data:
currentView: 'createBoardForm'
components:
createBoardForm: CreateBoardForm
)
Any help on how I could possibly initialize that form field would be greatly appreciated.
Thank you
OK, I figured this out. Basically, you have to create a new component, listen to the attached event, use computed properties and then use the v-ref tag which becomes a reference to the tags input. I switched from this tagsinput library to another, but the idea is the same. Here's a working JSFiddle and below is the code:
<div id="tags-input-example">
<tags-input v-ref="twitterUsers"></tags-input>
<input type="button" v-on="click: onSubmit" value="Submit"/>
</div>
<script type="text/x-template" id="tags-input">
<input type="text" />
</script>
Vue.component('tags-input', {
template: "#tags-input",
attached: function() {
$(this.$el).find('input').tagsInput();
},
computed: {
tags: {
get: function () {
return $(this.$el).find('input').val();
}
}
}
});
vm = new Vue({
el: '#tags-input-example',
methods: {
onSubmit: function(e) {
console.log(this.$.twitterUsers.tags);
alert("The tags are: " + this.$.twitterUsers.tags);
}
}
});

Categories

Resources