I have a few different checklists for my "projects" and using the same Vue instance to handle these checklists that I am getting from my database. I am running into a problem though in which I really want to use the project's id and a type of checklist in my mounted() method to help my Controller endpoint (I'm using laravel but that is irrelevant here) point to the right database rows.
So for example:
HTML
<ul class="vue-checklist" data-project="16" data-type="permits">
</ul>
<ul class="vue-checklist" data-project="16" data-type="walkthrough">
</ul>
JS
new Vue({
el: '.vue-checklist',
data: {
items: [],
// is there a way to trap those data attrs here?
},
mounted : function(){
// I need to a way to access the project and type data attrs.
this.fetchChecklist(this.project, this.type); // <- does not work
},
methods: {
fetchChecklist : function(project, type){
this.$http.get('{ api/path }', { project: project, type: type}).then(function(response){
this.items = response.data;
})
}
});
Again, is there a way to get data-project and data-type attached in the HTML use that in the mounted() method.
You can reference the root element of the Vue instance via this.$el.
From there you can reference the element's attribute's via the getAttribute() method.
In your case, you could do something like this:
new Vue({
el: '.vue-checklist',
data: {
items: [],
project: null,
type: null,
},
mounted : function(){
this.project = this.$el.getAttribute('data-project');
this.type = this.$el.getAttribute('data-type');
this.fetchChecklist(this.project, this.type);
},
...
}
That isn't the most straight-forward solution though. If you're able, it'd be a lot cleaner to create a Vue instance on a parent element and then define vue-checklist as a component. That way you could just pass the project and type values as props to the component from the template:
Vue.component('vue-checklist', {
template: `<ul class="vue-checklist"></ul>`,
props: ['project', 'type'],
data: {
items: [],
},
mounted : function(){
this.fetchChecklist(this.project, this.type);
},
methods: {
fetchChecklist : function(project, type){
this.$http.get('{ api/path }', { project: project, type: type}).then(function(response){
this.items = response.data;
})
}
}
})
new Vue({
el: '#app',
})
<div id="app">
<vue-checklist project="16" type="permits"></vue-checklist>
<vue-checklist project="16" type="walkthrough"></vue-checklist>
</div>
Related
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!
How do I send data to a component in Vue.js? I got a response from the server on the button click event, and now I want to send this response to the component and display on list using v-for.
Here is my code:
var store = new Vuex.Store({
state: {
Item: []
},
mutations: {
getItems: function (state) {
}
},
actions: {
fetchData:function (context) {
Vue.http.get('data.json').then(function(response){
alert('dd')
}, function(error){
console.log(error.statusText);
});
}
}
})
var httprequest = Vue.extend({
"template": '#http_template',
data: function () {
return {
items: store.state.Item
}
},
methods: {
fetchData: function () {
store.dispatch('fetchData')
},
}
})
Vue.component('httprequest', httprequest);
var app = new Vue({
el: '#App',
data: {},
});
You have almost done everything correct. Only thing you are missing is after getting data, you are not assigning it to state.Item. Please check the below code:
var store = new Vuex.Store({
state: {
Item: []
},
mutations: {
getItems: function(state, items) {
items.forEach(function(item) {
state.Item.push(item)
})
}
},
actions: {
fetchData: function(context) {
Vue.http.get('data.json').then(function(response) {
context.commit('getItems', response.data)
}, function(error) {
console.log(error.statusText);
});
}
}
})
working example can be found here.
You don't send data to components. You set up reactive pipes and the data moves around when it needs to. In your case, with vuex, you want to register store.state.items on the data of your component.
You can use a prop if you want, but you still need to do the registration in the parent's data. If your component is a singleton, intended for this page only, you're better registering what you need directly in the data of the component.
In general vue follows the principle that data goes the DOM tree down via properties and up via events. See for example https://v2.vuejs.org/v2/guide/index.html#Composing-with-Components.
Thus to get data into your component define a property myProp inside your component and when using your component bind it via v-bind:myProp="myData".
To get data back from your component use this.$emit('myUpdateEvent', myUpdatedData) and listen to the event by using v-on:myUpdateEvent="myUpdateHandler".
Vue.js version is: 2.x
Hi. I'm sending an ajax request in vue js to another page and getting it's source which contains vue.js syntax such as events. When this source is added to property and property added to a template, the ajax data source (that contains vue.js syntax) can not be rendered and does not work properly.
For example template is:
<div id="app">
{{{ foo }}}
</div>
and app.js is:
var app = new Vue({
el: '#app',
data: {
foo: 'bar'
},
mounted(){
this.$http.get('/media').then(function(response){
data = response.body;
Vue.set(app, 'foo', data);
});
},
methods: {
alertVideoLink: function(event){
alert(event.target.href);
}
}
});
In the above app.js code, ajax request returns this code (that is response.body):
Video Link
but this link can't be rendered and does not work properly! I'm testing the render method and some useful hints, but no way found. Please help... Thanks
Sounds like you want to use an Async Component.
Something like...
components: {
'async-media': () => Vue.http.get('/media').then(res => ({
template: res.body,
methods: {
alertVideoLink (event) {
this.$emit('click', event)
}
}
}))
}
Then in your template...
<async-media #click="handleClickEventFromChildComponent" />
Here's an example using a timeout to fake "load" a template
var app = new Vue({
el: '#app',
data: {},
components: {
'async-media': () => new Promise(resolve => {
setTimeout(() => {
resolve({
template: 'Video Link',
methods: {
alertVideoLink(event) {
this.$emit('click', event.target.href)
}
}
})
}, 2000)
})
},
methods: {
handleClickEventFromChildComponent (href) {
console.info('Clicked on', href)
}
}
});
<div id="app">
<p>Wait 2 seconds</p>
<async-media #click="handleClickEventFromChildComponent" />
</div>
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
#Phil's answer is correct but in my project need to be changed. in this case, the better way is: using global components vs local components because is simple for this work.
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.
According to the docs, the constructor of the Vue object is managed like this.
var vm = new Vue({
created: function () { console.log("I'm created!"); }
});
However, I can't figure out how to do the corresponding thing when a Vue component is created. I've tried the following but don't get any print to the console.
export default {
created: function() { console.log("Component created!"); }
}
Is it possible to subscribe/listen to a component being rendered? I'd like to react to that event by downloading some data and putting it in the store, so that the table that the component carries will get its information to display.
In my applications, I tend to use the mounted hook to load up some Ajax data once the component has mounted.
Example code from my app:
Vue.component('book-class', {
template: '#booking-template',
props: ['teacherid'],
data: function () {
return{
// few data items returned here..
message: ''
}
},
methods: {
// Few methods here..
},
computed: {
// few computed methods here...
},
mounted: function () {
var that = this;
$.ajax({
type: 'GET',
url: '/classinfo/' + this.teacherid,
success: function (data) {
console.log(JSON.stringify(data));
}
})
}
});
new Vue({
el: '#mainapp',
data: {
message: 'some message here..'
}
});
However, I can also use created() hook as well as it is in the lifecycle as well.
In Vue2 you have the following lifecycle hooks:
components doesn't have life cycle hooks like app. but they has something similar. that fixed my problem:
https://v2.vuejs.org/v2/api/#updated