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

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.

Related

Vue.delete warning to avoid using JavaScript unary operator as property name on click

I get the following warning when using Vue.delete:
[Vue warn]: Error compiling template: avoid using JavaScript unary operator as property name: "delete(testObj,)" in expression #click="Vue.delete(testObj,'label')"
I can use Vue.delete anywhere else and it seems fine. Am I using it wrong?
new Vue({
el: '#app',
data() {
return {
testObj: {
label: "The label"
}
};
}
});
<div id="app">
{{testObj.label}}
<button #click="Vue.delete(testObj,'label')">Delete</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.11/dist/vue.js"></script>
The global Vue will not be available inside your template. Everything in a template is scoped to the current Vue instance, so you're effectively trying to access this.Vue. Trying to access any of the properties of the global Vue won't work.
You can use $delete instead of Vue.delete inside a template.
https://v2.vuejs.org/v2/api/#vm-delete
new Vue({
el: '#app',
data() {
return {
testObj: {
label: "The label"
}
};
}
});
<div id="app">
{{testObj.label}}
<button #click="$delete(testObj, 'label')">Delete</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.11/dist/vue.js"></script>
I would add that the specific error message you're seeing relates more generally to trying to use any property called delete but that's something of a moot point as you should be using $delete anyway.

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>

Multiple `new Vue()` Vue global instances using the same sub-component names. Possible?

In a single HTML file I have two new Vue() global Vue instances:
new Vue({ el: "#normal-view" }) and,
new Vue({ el: "#xrays-view" }),
And I want to show a different template for each view using the same component name. For example:
Vue.component('head', { template: "I see a eyes and skin" }) and Vue.component('head', { template: "I see a skull" }),
Vue.component('wrist', { template: "I see fingers" }) and Vue.component('wrist', { template: "I see bones" })
and so on.
How can I make each new Vue() instance read the correct set of Vue.components while keeping them global (created with Vue.component()) and with exactly the same names on them? Is it possible?

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.

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