Vue JS data availability among components - javascript

I've just stared playing with vueJS inside a laravel app.
I am trying to create a steps wizard and I'm having trouble accessing data from the vue definition inside one of the components.
I have:
new Vue({
el: '#app',
data: {
currentstep : 1,
...
chosenName: "",
And I would like to be able to access chosenName inside
Vue.component('step-controls', {
so that
<button class="btn btn-primary" v-on:click="nextStep()" :disabled="secondstep" v-if="firststep != true && laststep != true">Next</button>
Would be disabled if the value of chosenName is empty string
I would have imagined that it should be something like :
secondstep: function() {
return (this.currentstep == 2 && this.chosenName =='')
},
but chosenName is not one of the props. If I am adding it to the props array, how do I keep it in sync?
Link to fiddle: https://jsfiddle.net/angelin8r/oxse2p3v/3/

Using your fiddle, made the following changes
<step-controls
v-for="step in steps"
:step="step"
:stepcount="steps.length"
:currentstep="currentstep"
#step-change="stepChanged"
:chosen-name="chosenName">
</step-controls>
and
props: ['step', 'stepcount', 'currentstep','chosenName'],
By binding chosenName to the parent property, it will automatically update when the parent's property updates.

Your first problem is that the data property of your Vue element needs to be a function. That function will return an object with your currentStep and currentName. See the docs for reference
So, your component would look like
new Vue({
el: '#app',
data() {
return {
currentStep : 1,
chosenName: "",
};
},
})
Then, you can access and update the data with this.currentStep or this.chosenName

Related

Cannot access individual elements with this.$refs in Vue.js

I have a strange problem. wanted to access some elements in the created() hook. Specifically:
this i have accesss to the refs object:
created() {
console.log(this.$refs)
}
// returns:
{
bar: div.red.bar
content: div.name-description
description: p.description.dark
redContainer: div.red.red-container
sections: div.sections
title: h1.name.dark
__proto__: Object
}
But when i try to target a specific element I end up with undefined:
created() {
console.log(this.$refs.content)
}
//returns
undefined
Does anybody know why I have this behavior?
Similar problems when trying to get the width / height from elements in computed properties...
(e.g. this.$refs.content.clientWidth)
You cannot access refs from the created hook because the child components/elements have not been instantiated yet; instead access from the mounted hook:
Vue.component('foo', {
template: '<div>foo</div>',
created() {
console.log('foo created')
}
})
new Vue({
el: '#app',
created() {
console.log('parent created: $refs.foo is null?', this.$refs.foo == null)
},
mounted() {
console.log('parent mounted: $refs.foo is null?', this.$refs.foo == null)
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<foo ref="foo"></foo>
</div>
The reason why you're getting a discrepancy between the console.log output showing that the components are there but when you access one of the components it's not is probably because the browser is evaluating the properties of this.$refs lazily only once you click the arrow to expand the properties of the object, and by the time it is evaluated the child components have been created.
this.$refs is an object type, when your output is created, it will get a value undefined, then it will get another value of object type in mounted, and both values have the same reference in memory, either changed, another will change.

VueJS: Replace/Update Array

I currently have an array of object that I am rendering to a table. I am trying to follow the examples provided by Vuejs to use a "single source of truth" shared between multiple vues on the same page.
Overall, I am trying to make it where when vue1.refresh() is triggered, all the vues update their data when the "single source of truth" is updated. However, self.surveys = surveys; only updates the data on vue1.
Note: I am following the guide from https://v2.vuejs.org/v2/guide/state-management.html
// The single source of truth
var cache = {
data: [{...}] // Array of objects
}
var vue1 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
refresh: function(){
var self = this;
// After getting data back from an ajax call
.done(function(surveys) {
self.surveys = surveys;
});
},
}
});
var vue2 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
// Methods
}
});
There are two principles of Vue that will help you here:
In Vue, every data item is a source of truth.
Only the owner of a data item should modify it.
In your example, you have three sources of truth: the one you want to be the single source, and two others that are initialized from it. Also, the one you want to be the source of truth isn't a data item, it is outside Vue.
So to start, you should have a single Vue that represents your entire application and defines any data that represents application-level state:
new Vue({
el: '#app',
data: {
cache: {
data: [...]
}
}
});
The two Vue objects that you created should be children of the application Vue, which is to say, components.
The parent tells the children what the truth is via props. The child can suggest changes to the truth by emitting events to the parent, but the child does not directly modify the truth. That keeps all management of the truth in one place.
You would need to mutate the array, not replace it.
Array.prototype.splice can do this for you, if you don't want to use something like Vuex, as suggested by Vanojx1.
Splice expects specific elements, not a complete array for insertions. Because you have an array you want to use and you need to clear the old one, the syntax is a little odd... You pass this, the start, the count to remove (the entire length), and then the elements to add (concatenated on from your new array).
Array.prototype.splice.apply([self.surveys, 0, self.surveys.length].concat(surveys));
Problem is, you are replacing shared Cache object previously assigned to surveys variable, with new, not shared object. And solution? Do not try to mutate cache object. Just use Vuex. Vuex is simple, real "Vue way" solution.
// The single source of truth
var cache = {
data: [{...}] // Array of objects
}
var vue1 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
refresh: function(){
var self = this;
// After getting data back from an ajax call
.done(function(surveys) {
self.surveys = surveys; // Problem is right here
});
},
}
});
var vue2 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
// Methods
}
});
Try this example, which works like you code - not correct way:
var cache = {
key1: 'Value1'
}
var vue1 = new Vue({
el: '#app1',
data: {
surveys: cache
},
methods: {
replace () {
this.surveys = {key1: 'Replaced'}
}
}
})
var vue2 = new Vue({
el: '#app2',
data: {
surveys: cache
},
methods: {
replace () {
this.surveys = {key1: 'Replaced'}
}
}
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app1">
Input for Vue1: <input type="text" v-model="surveys.key1">
<button #click="replace">Replace</button>
<p>{{ surveys.key1 }}</p>
</div>
<div id="app2">
Input for Vue1: <input type="text" v-model="surveys.key1">
<button #click="replace">Replace</button>
<p>{{ surveys.key1 }}</p>
</div>
Then try this example, with Vuex, where you can freely replace "cache object" and replacint will affect other instance:
const store = new Vuex.Store({
state: {
cache: {
key1: 'Value1'
}
},
mutations: {
replace (state) {
state.cache = {key1: 'Replaced'}
}
}
})
var vue1 = new Vue({
el: '#app1',
store,
computed: {
surveys () {
return this.$store.state.cache
}
},
methods: Vuex.mapMutations([
'replace'
])
})
var vue2 = new Vue({
el: '#app2',
store,
computed: {
surveys () {
return this.$store.state.cache
}
},
methods: Vuex.mapMutations([
'replace'
])
})
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex#2.4.0/dist/vuex.min.js"></script>
<div id="app1">
Input for Vue1: <input type="text" v-model="surveys.key1">
<button #click="replace">Replace</button>
<p>{{ surveys.key1 }}</p>
</div>
<div id="app2">
Input for Vue1: <input type="text" v-model="surveys.key1">
<button #click="replace">Replace</button>
<p>{{ surveys.key1 }}</p>
</div>
As said in the comment before, you can use vuex to accomplish what you need, everytime you need to pass data between diferent components you can do that with a eventBus or passing props up and down between the components.
When you have a aplication that needs to pass a lot of data and receive it you can use vuex, first you need to install it and then you can do it this way:
you should cut the methods out and place the mounted(), it fires when the component loads, i think it was you need
var vue1 = new Vue({
el: "#table",
data: {
surveys: cache.data // Points to the single source of truth
},
methods: {
}.
mounted() {
var self = this;
// After getting data back from an ajax call
.done(function(surveys) {
self.surveys = surveys;
});
}
});
when you get the response pass it to vuex store, you can do it with a mutation like this:
this.$store.mutation('handlerFunction', self.surveys)
in the vuex you need to have the handlerfunction inside the mutation
mutations: {
// appends a section to the tree
handlerFunction: (state, dataReceived) => {
//then you can do
state.surveys = dataReceived
},
then in your other component you can receive it via a getter, the logic is the same watch vuex for more deaills, you have the main logic of connection here.
Hope it helps!

Data object defined in created function but NOT reactive?

I have a Vue instance where data property is initialised as an object:
var app = new Vue({
el: '#app',
data: {
obj: { }
},
methods: {
},
created: function() {
this.obj["obj2"] = {}
this.obj["obj2"].count = 0
},
mounted: function() {
setInterval(function() {
this.obj.obj2.count++
console.log(this.obj.obj2.count)
}.bind(this), 1000)
}
})
<div id="app">
{{ obj['obj2'].count }}
</div>
And then when the instance is created I add a property to the obj.
However, when I want to display the object's object property count, it shows 0 and is not reactive. If I defined the whole object in the data, it is reactive but I can't define the object in the data because its data depends on an external source - API, that's why it is filled with data in created function.
The only way how I managed to make it show the current count is by forcing updates on the view but I don't think it's the correct solution.
Any suggestions?
The problem is that Vue can not track completely new properties on its reactive objects. (It's a limitation of JavaScript).
It's described in detail here: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
The short version is: You have to do
created: function() {
Vue.set(this.obj, 'obj2', {})
Vue.set(this.obj.obj2, 'count', 0)
}
or
created: function() {
Vue.set(this.obj, 'obj2', {
count: 0
})
}

Parent's data change does not update child component in vuejs

I have the following:
Vue.component('times-updated', {
template: '<span>Times Updated: {{ timesUpdated }}</span>',
data: function() {
return {
timesUpdated: this.$parent.myData.timesUpdated
}
}
});
var vm = new Vue({
el: '#test',
data: function() {
return {
myData: {}
}
}
})
setInterval(function(){
$.ajax({
url: `${window.location.href}/json`, // This just returns an array : array.timesUpdated: 2 etc
}).done(function (data) {
vm.myData = data; // changes this data
});
}, 1000)
and am using the following html:
<div class="test">
<times-updated></times-updated>
</div>
I poll a REST API that returns an array which includes a timesUpdated property:
{
timesUpdated: 5
}
My intention is that every second I use jQuery's $.ajax method to call the API, update the myData data object on vm, which would then update the times-updated component.
The code works on initial page load, the times-updated component can retrieve the value on its parent's myData property, but whilst I have confirms that vm.myData does reflect the new value from the API, the component doesn't update its display to show the new count.
What am i doing wrong?
The data function is only called once during the life cycle of the component; when it is initially created. So essentially your component is just displaying the value as it existed when the component was created.
Additionally, it's generally bad practice to reach out of a component to get a data value. Vue is props down, events up. You should convert your component to use a property.
Vue.component('times-updated', {
props:["times"],
template: '<span>Times Updated: {{ times }}</span>',
})
The fact that you are using a function to define the Vue in this particular case doesn't really matter, it's just not a typical practice. Components require a function because they need an isolated scope.
Here is an example.
That callback is required only in components
// vue instance
new Vue({
data: {
status: true
}
};
// vue components (callback)
Vue.component('custom-component', {
data: function() {
return {
status: false
}
}
});

How to pass data(json) to vue instance

I have a simple Vue instance and want to pass json from the backend to vue without HTTP request because it's always the same.
I've tried do this with props, but it doesn't work...
In DOM it's looks like <div id="my-component" prices="[object Object]">
Vue debug tool show me image as an empty string, and in console undefined
<div id="my-component" :prices="{{ $prices }}">
</div>
<script>
new Vue({
el: '#my-component',
props: ['prices'],
mounted: function() {
console.log(this.image);
},
});
</script>
where $prices json encoded array.
Your solution was nearly there but you don't need a prop, rather use a data attribute and assign the JSON via a method:
new Vue({
el: '#app',
data: {
json: {},
},
methods: {
setJson (payload) {
this.json = payload
},
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app" :json="setJson({ foo: 'bar' })">
<pre>{{ json }}</pre>
</div>
You would just assign your Laravel data to the setJson methods payload, i.e.
:json="setJson({{ $prices }})
I don't know if there is any Laravel helper for this but I will present a generic approach.
One option would be to store you JSON data in a global variable and the page loads and then use it in your js files.
Basically you need to generate some html similar to:
<script>
window.myApp = window.myApp || {};
window.myApp.userData = { "firstName": "John", "lastName": "Doe" };
</script>
Then from javascript you should be able to access the myApp.userData variable and use it when initializing the Vue component.
new Vue({
el: '#app',
data: {
userData: myApp.userData
}
});
Here is an example:
new Vue({
el: '#app',
data: {
userData: myApp.userData
}
});
<script>
window.myApp = window.myApp || {};
window.myApp.userData = { "firstName": "John", "lastName": "Doe" };
</script>
<div id="app">
Hello {{userData.firstName}}
</div>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
I have upvoted this answer first, but I have to change my vote (can't do it actually not enough reputation...).
Please do not set the data this way, because it will trigger an error like this:
[Vue warn]: You may have an infinite update loop in a component render function
If anything will use the data you set this way (watch, render components based on it) you will have an infinite loop.
When you use this method:
you set the data in the render function (in the template)
if something triggers a re-render, the data will be set again
anything using this data will have to re-render, which may cause a
re-render on the main vue instance
This will cause the infinite loop.
LinusBorg have an explanation here.
While this op is old, here is how I would do it (inspired by how I do it in Symfony 4 + VueJS):
<div id="my-component" prices-data="{{ json($prices) }}">
</div>
<script>
new Vue({
el: '#my-component',
props: ['pricesData'],
data: {
prices: null,
},
mounted: function() {
this.prices = JSON.parse(this.pricesData);
},
});
</script>
This is obviously assuming that $prices is a blade variable.
Note: I used #json() above when $prices is a simple object that can be encoded with json_encode() (underlying function being used when you call blade json function. If however the object is complex, consider using JMS Serializer with #MaxDepth annotations if objects become too complex.

Categories