I have a VueJS data object in JS script. The JS script is linked at end of the page. So first HTML will is getting rendered. Then VueJS data object will be initialised. After initialising data object, DOM is getting updated perfectly. But after this, on updating the VueJS data object in success function of this.$http(), DOM is not getting updated. I have read documentation on reactivity and Common Beginner Gotchas. But I didn't get solution. If anyone knows the answer, it will be appreciated.
Here is my code.
index.html
<body>
....
{{ind_case.comments.length}}
<div class="comment_item" v-for="comment in ind_case.comments">
</div>
....
<script src="index.js"></script>
</body>
index.js
new Vue({
....
data: {
ind_case: {}
},
created: function () {
this.getCaseDetails();
},
methods: {
getCaseDetails: function () {
this.ind_case = {
"case_id": 16
}
this.getCaseComments(this.ind_case.case_id);
},
getCaseComments: function(case_id){
this.$http.get('/api/comments/' + case_id)
.then((response) => {
console.log(response.data); // Output: [{cmnt: "blah"}, {cmnt: "blah blah"}]
// Here i want add these comments in ind_case data object as ind_case.comments
// I have tried this
// this.$set(this.ind_case, 'comments', response.data);
// console.log(this.ind_case.comments); // Output: [Array[2], __ob__: Di]
}, (response) => {})
}
}
....
})
As seen in code, I can get comments in DOM. But I have to use .comments[0].length instead of .comments.length. And that's not the way to code.
EDIT
As suggested in below comment, I have tried this. But still same output (i.e, [Array[2], __ob__: Di]). And also one more thing. If I choose this option, I have to pre-define data object. But my requirement is run time creation/addition of data.
first thing, data should be a function and return {ind_case: {comments: []}}
i would recammend you make new object this way for ajax response
(response) => {
var data = response.data.map(o => getUwantProps(o));
this.ind_case.comments = data;
}
Related
This is kind of a long explanation of an issue that I'm having on a personal project. Basically, I want to set a data property before my page loads when I read in data from a CSV file using D3.JS. I almost have it done but running into a small issue. Please read on to get more detail.
Basically, when the user comes to a page in my application, I want to display weather graphs. Like I said, I'm using D3.js to read in the data and created an action to do that. It works perfectly fine-I can console.log the data and I know its been read. However, in my vue instance I have a data property, which would hold the data set like this:
data() {
return {
name: this.$store.state.name
weatherData: this.$store.state.yearData
}
}
I then want to ensure that the weatherData is filled, with data from the csv file so I display it on the page like this:
<p>{{ weatherData }}</p>
Nothing special here. When the page loads, weatherData is blank. But I have a beforeMount life cycle hook and if I comment out the only line in it then it will display the data. If I then refresh the page, fire the action to get the data and then uncomment out the line in the beforeMount hook then the data appears! So before I continue this is my full code for the store:
export const store = new Vuex.Store({
state: {
name: 'Weather Data'
yearData: []
},
getters: {
},
mutations: {
setYearData(state, data) {
state.yearData = data
}
},
actions: {
getYearData: ({commit}) => {
d3.csv("../src/components/data/alaska.csv")
.then(function(data){
let yearData = []
for (let i = 0; i < data.length; i++){
let day = data[i].AKST
yearData.push(day)
}
//console.log(yearData)
commit('setYearData', yearData)
})
}
})
Here are parts of the vue file: The template:
<p>{{ weatherData }}</p>
The Vue Intance:
export default {
name: 'Weather',
data() {
return {
name: this.$store.state.name,
weatherData: this.$store.state.yearData
}
},
methods: {
...mapActions([
'getYearData'
])
},
beforeMount(){
this.$store.dispatch('getYearData') //(un)Commenting out this line will make my data appear
}
}
Page when it loads: Notice empty array:
Then either comment out or comment the one line in the beforeMount hook and get this: THE DATA!!!
Again, my end goal is to have the action called and the data set before the page finishes loading. Finally, I know that I don't need VUEX but this project is further helping me understand it. Any guidance on why this is happening would be great.
use mapState instead of putting your data in the data object, which sometimes being late on updating the template.
just make your Vue instance to look like:
import {mapState} from 'vuex'
export default {
name: 'Weather',
data() {
return { }
},
computed:{
...mapState({
name: state=>state.name,
weatherData: state=>state.yearData
})
},
methods: {
...mapActions([
'getYearData'
])
},
beforeMount(){
this.$store.dispatch('getYearData') //(un)Commenting out this line will make my data appear
}
thats way, you work directly with one source of truth-the store, and your name and weatherData will be reactive as well.
more about mapState here: https://vuex.vuejs.org/guide/state.html#the-mapstate-helper
I have used AXIOS for fetching data from my API, it takes some extra time to get the data, have used CREATED hook point to call the API.
<template>
<div>
Period
{{This.GetPeriod()}}
</div>
</template>
data:function(){
APIData:{}
},
created :{
GetData:function(){
---- AXIOS get call
}
},
methods:{
GetPeriod:function(){
return this.ApiData.StartDate+":'+ApiData.EndDate
}
}
Now I am getting below exception :
StartDate and Endate is undefined.
The GetPeriod Method runs earlier then Created hook. that is the reason the startDate and EndDate is undefined. as that is the property in the data which which will be assigned in APIData after API call.
My whole code runs 2 times.
{
"Banners":[{ some properties }],
"Links":[{ some properties }],
"Widgets":[{ properties }],
"Layouts":{
"LayoutName":"Layout4",
"ContentDefinitionID":"9",
"PlaceHolderID":"",
"DisplayOrder":"",
"Type":"EmployeeLanding",
"StartDate":null,
"EndDate":null,
"VariantID":"EFDD2115",
"Status":null,
"Audience":{
"Groups":[],
"Segments":[ ],
"PopupMode":0,
"ErrorMessage":null
},
"PopupMode":0,
"ErrorMessage":null
}
}
The startDate and EndDate is in the Layout property so Do I need to create the schema on my Vue.js or can be managed dynamically so that it do not give exception of undefined .
Also the code is running 2 times.
The created hook you want to use is a function as you can see in the docs Lifecycle-Hooks
Also you could make GetPeriod a computed value as its more fitting here semantically.
For not getting the error you can either set default values for start and end date or before calculating the period or you can check if those are defined. If not return a dash or an empty string.
To make it more user friendly you can try having a loading state for your app, and after the xhr call is finished populate your templates and remove the loader.
Example usage
<template>
<div>
Period: {{period}}
</div>
</template>
<script>
data:function(){
APIData:{}
},
created(){
this.getData();
},
methods: {
getData:function(){
---- AXIOS get call
}
},
computed:{
period:function(){
let {StartDate, EndDate} = this.ApiData;
return (StartDate && EndDate) ? `${StartDate} : ${EndDate}` : '';
}
}
</script>
So I'm struggling to get this to work. Maybe my approach is not good. I'm quite a noob at using a Frontend framework like ember.js.
So, I've got a $.getJSON request obtaining a JSON data from a local file. The problem is that I need to pass this data, not to the template but to another object inside one ember controller.
I'm trying to use ember-CLI-charts to I need to return the object in the last lines of the property.
diffData: Ember.computed('model', function(){
let url = "log_hr24_diff.json";
let diff_data = Ember.$.getJSON(url);
return {
labels: ['Difficulty', 'Date'],
datasets: [
{
label: "Difficulty",
data: diff_data
},
{
label: "Date",
data: date_data
}
]
}
})
So that is not working. Either do this:
let diff_data = Ember.$.getJSON(url).then(function(data){
return Ember.Object.create(data);
});
So how do I get the JSON object from the diff_data JSON response to pass it to the return object?
Tried and search it a lot, but couldn't find an answer.
Thanks in advance!
It makes more sense to create a service, that way you can consume(in simpler words "use") your getJson call inside many controllers (or components and models), and change the url each time if you wish.
It makes sense when looking at making your code reusable.
To do this, you'll want to make a service through the CLI.
Ember g service someServiceName
Then your service might look a little like this:
import Ember from 'ember';
export default Ember.Service.extend({
getUrlData(url){
let data = Ember.$.getJSON(url);
return data.then((json) => {
let records = [];
json.forEach(function(item){
records.push(item);
});
return records;
});
}
});
For any advanced readers, i've avoided destructuring(ie - using a const) to avoid confusion for the OP.
And back in your controller, you may write something similar to:
import Ember from 'ember';
export default Ember.Controller.extend({
serviceToUse: Ember.inject.service('some-service-name'),
diffData: Ember.computed('model', function(){
let url = "log_hr24_diff.json";
let diff_data = this.get('serviceToUse').getUrlData(url);
//parse returnedData or put it in a new variable to use as you see fit.
return {
labels: ['Difficulty', 'Date'],
datasets: [
{
label: "Difficulty",
data: diff_data
},
{
label: "Date",
data: date_data
}
]
}
})
Now in the handlebar file for this controller, you can access the data from your controller like so:
{{diffData.labels}} //outputs "Difficulty,Date". You can loop through the datasets property yourself.
Beginner's hint - file names should match each other, that's how Ember knows how to link files. This does not always have to be true, but for now stick to that rule.
This should get you where you want to go. It works on my machine.
So finally I found the correct way its mean to be coded in ember (or a way to do it).
I realized that I need to return the diff and date data into the model. So i've done this.
routes/index.js
url = "log_hr24_diff.json";
var diff_data = Ember.$.getJSON(url, function(data){
return Ember.Object.create(data);
});
url = "log_hr24_dates.json"
var dates_data = Ember.$.getJSON(url, function(data){
return Ember.Object.create(data);
});
export default Ember.Route.extend({
model(){
return Ember.RSVP.hash({
price: price_data,
chart_diff: diff_data,
chart_dates: dates_data
});
},
});
Then in the main index controller file, just use this model data to pass the json data to the correct final object:
controllers/index.js
export default Ember.Controller.extend({
applicationController: Ember.inject.controller('application'),
diffData: Ember.computed('model', function(){
return {
labels: this.get('model.chart_dates'),
datasets: [
{
label: "Difficulty",
data: this.get('model.chart_diff')
}
]
}
})
});
So that's for me a correct way of doing things out on ember. Getting data in the model, passing data into the controller for logic workarround and finally pass the resulting object to the view.
I'm struggling to understand how to pass data between components in vue.js. I have read through the docs several times and looked at many vue related questions and tutorials, but I'm still not getting it.
To wrap my head around this, I am hoping for help completing a pretty simple example
display a list of users in one component (done)
send the user data to a new component when a link is clicked (done) - see update at bottom.
edit user data and send it back to original component (haven't gotten this far)
Here is a fiddle, which fails on step two: https://jsfiddle.net/retrogradeMT/d1a8hps0/
I understand that I need to use props to pass data to the new component, but I'm not sure how to functionally do it. How do I bind the data to the new component?
HTML:
<div id="page-content">
<router-view></router-view>
</div>
<template id="userBlock" >
<ul>
<li v-for="user in users">{{user.name}} - <a v-link="{ path: '/new' }"> Show new component</a>
</li>
</ul>
</template>
<template id="newtemp" :name ="{{user.name}}">
<form>
<label>Name: </label><input v-model="name">
<input type="submit" value="Submit">
</form>
</template>
js for main component:
Vue.component('app-page', {
template: '#userBlock',
data: function() {
return{
users: []
}
},
ready: function () {
this.fetchUsers();
},
methods: {
fetchUsers: function(){
var users = [
{
id: 1,
name: 'tom'
},
{
id: 2,
name: 'brian'
},
{
id: 3,
name: 'sam'
},
];
this.$set('users', users);
}
}
})
JS for second component:
Vue.component('newtemp', {
template: '#newtemp',
props: 'name',
data: function() {
return {
name: name,
}
},
})
UPDATE
Ok, I've got the second step figured out. Here is a new fiddle showing the progress: https://jsfiddle.net/retrogradeMT/9pffnmjp/
Because I'm using Vue-router, I don't use props to send the data to a new component. Instead, I need set params on the v-link and then use a transition hook to accept it.
V-link changes see named routes in vue-router docs:
<a v-link="{ name: 'new', params: { name: user.name }}"> Show new component</a>
Then on the component, add data to the route options see transition hooks:
Vue.component('newtemp', {
template: '#newtemp',
route: {
data: function(transition) {
transition.next({
// saving the id which is passed in url
name: transition.to.params.name
});
}
},
data: function() {
return {
name:name,
}
},
})
-------------Following is applicable only to Vue 1 --------------
Passing data can be done in multiple ways. The method depends on the type of use.
If you want to pass data from your html while you add a new component. That is done using props.
<my-component prop-name="value"></my-component>
This prop value will be available to your component only if you add the prop name prop-name to your props attribute.
When data is passed from a component to another component because of some dynamic or static event. That is done by using event dispatchers and broadcasters. So for example if you have a component structure like this:
<my-parent>
<my-child-A></my-child-A>
<my-child-B></my-child-B>
</my-parent>
And you want to send data from <my-child-A> to <my-child-B> then in <my-child-A> you will have to dispatch an event:
this.$dispatch('event_name', data);
This event will travel all the way up the parent chain. And from whichever parent you have a branch toward <my-child-B> you broadcast the event along with the data. So in the parent:
events:{
'event_name' : function(data){
this.$broadcast('event_name', data);
},
Now this broadcast will travel down the child chain. And at whichever child you want to grab the event, in our case <my-child-B> we will add another event:
events: {
'event_name' : function(data){
// Your code.
},
},
The third way to pass data is through parameters in v-links. This method is used when components chains are completely destroyed or in cases when the URI changes. And i can see you already understand them.
Decide what type of data communication you want, and choose appropriately.
The best way to send data from a parent component to a child is using props.
Passing data from parent to child via props
Declare props (array or object) in the child
Pass it to the child via <child :name="variableOnParent">
See demo below:
Vue.component('child-comp', {
props: ['message'], // declare the props
template: '<p>At child-comp, using props in the template: {{ message }}</p>',
mounted: function () {
console.log('The props are also available in JS:', this.message);
}
})
new Vue({
el: '#app',
data: {
variableAtParent: 'DATA FROM PARENT!'
}
})
<script src="https://unpkg.com/vue#2.5.13/dist/vue.min.js"></script>
<div id="app">
<p>At Parent: {{ variableAtParent }}<br>And is reactive (edit it) <input v-model="variableAtParent"></p>
<child-comp :message="variableAtParent"></child-comp>
</div>
I think the issue is here:
<template id="newtemp" :name ="{{user.name}}">
When you prefix the prop with : you are indicating to Vue that it is a variable, not a string. So you don't need the {{}} around user.name. Try:
<template id="newtemp" :name ="user.name">
EDIT-----
The above is true, but the bigger issue here is that when you change the URL and go to a new route, the original component disappears. In order to have the second component edit the parent data, the second component would need to be a child component of the first one, or just a part of the same component.
The above-mentioned responses work well but if you want to pass data between 2 sibling components, then the event bus can also be used.
Check out this blog which would help you understand better.
supppose for 2 components : CompA & CompB having same parent and main.js for setting up main vue app. For passing data from CompA to CompB without involving parent component you can do the following.
in main.js file, declare a separate global Vue instance, that will be event bus.
export const bus = new Vue();
In CompA, where the event is generated : you have to emit the event to bus.
methods: {
somethingHappened (){
bus.$emit('changedSomething', 'new data');
}
}
Now the task is to listen the emitted event, so, in CompB, you can listen like.
created (){
bus.$on('changedSomething', (newData) => {
console.log(newData);
})
}
Advantages:
Less & Clean code.
Parent should not involve in passing down data from 1 child comp to another ( as the number of children grows, it will become hard to maintain )
Follows pub-sub approach.
I've found a way to pass parent data to component scope in Vue, i think it's a little a bit of a hack but maybe this will help you.
1) Reference data in Vue Instance as an external object (data : dataObj)
2) Then in the data return function in the child component just return parentScope = dataObj and voila. Now you cann do things like {{ parentScope.prop }} and will work like a charm.
Good Luck!
I access main properties using $root.
Vue.component("example", {
template: `<div>$root.message</div>`
});
...
<example></example>
A global JS variable (object) can be used to pass data between components. Example: Passing data from Ammlogin.vue to Options.vue. In Ammlogin.vue rspData is set to the response from the server. In Options.vue the response from the server is made available via rspData.
index.html:
<script>
var rspData; // global - transfer data between components
</script>
Ammlogin.vue:
....
export default {
data: function() {return vueData},
methods: {
login: function(event){
event.preventDefault(); // otherwise the page is submitted...
vueData.errortxt = "";
axios.post('http://vueamm...../actions.php', { action: this.$data.action, user: this.$data.user, password: this.$data.password})
.then(function (response) {
vueData.user = '';
vueData.password = '';
// activate v-link via JS click...
// JSON.parse is not needed because it is already an object
if (response.data.result === "ok") {
rspData = response.data; // set global rspData
document.getElementById("loginid").click();
} else {
vueData.errortxt = "Felaktig avändare eller lösenord!"
}
})
.catch(function (error) {
// Wu oh! Something went wrong
vueData.errortxt = error.message;
});
},
....
Options.vue:
<template>
<main-layout>
<p>Alternativ</p>
<p>Resultat: {{rspData.result}}</p>
<p>Meddelande: {{rspData.data}}</p>
<v-link href='/'>Logga ut</v-link>
</main-layout>
</template>
<script>
import MainLayout from '../layouts/Main.vue'
import VLink from '../components/VLink.vue'
var optData = { rspData: rspData}; // rspData is global
export default {
data: function() {return optData},
components: {
MainLayout,
VLink
}
}
</script>
How do I pass data between two different routes and templates?
I have a javascript file on the front end (client folder) that simply calls Router.go() passing in the post ID as one of my parameters.
Below are the three main culprits (I believe). I've removed most of the code to make it easier to read. I can change to the PostDetail page with no problems. I can also retrieve the PostId on the PostDetail page from the Router. My problem is, the database entry (POLL) that is retrieved does not get rendered on the template. Hence {{Question}} is always blank even though the database entry is being returned.
Let me know if I should post more information.
FrontEnd.js
Template.PostTiles.events({
// When a choice is selected
'click .pin' : function(event, template) {
Router.go('Post', {_PostId: this.PostId});
}
});
post-detail.html
<template name="PostDetail">
<h3>{{Question}}</p>
</template>
Shared.js
Router.map( function() {
this.route('Home', {
path: '/',
template: 'PostTiles',
data: {
// Here we can return DB data instead of attaching
// a helper method to the Template object
QuestionsList: function() {
return POLL.find().fetch();
}
}
});
this.route('Post', {
template: 'PostDetail',
path: '/Post/:_PostId',
data: function() {
return POLL.findOne(this.params._PostId);
},
renderTemplates: {
'disqus': {to: 'comments'}
}
});
});
----- Update -----
I think I've narrowed down the issue to simply being able to render only one Database entry, instead of a list of them using the {{#each SomeList}} syntax.
Looks like you found the answer / resolved this, but just in case, I think it's in your findOne statement:
data: function() {
return POLL.findOne(this.params._PostId);
},
should read:
data: function() {
return POLL.findOne({_id:this.params._PostId});
},
(assuming that POLL has your posts listed by _id.
Hope that helps.
Could you pass the info in the Session? the docs for that are here http://docs.meteor.com/#session. That's what I'm planning on doing.