Set object in data from a method in VUE.js - javascript

I have been stuck with this issues for 2 hours now and I really can't seem to get it work.
const app = new Vue({
el: '#book-search',
data: {
searchInput: 'a',
books: {},
},
methods: {
foo: function () {
axios.get('https://www.googleapis.com/books/v1/volumes', {
params: {
q: this.searchInput
}
})
.then(function (response) {
var items = response.data.items
for (i = 0; i < items.length; i++) {
var item = items[i].volumeInfo;
Vue.set(this.books[i], 'title', item.title);
}
})
.catch(function (error) {
console.log(error);
});
}
}
});
When I initiate search and the API call I want the values to be passed to data so the final structure looks similar to the one below.
data: {
searchInput: '',
books: {
"0": {
title: "Book 1"
},
"1": {
title: "Book 2"
}
},
Currently I get Cannot read property '0' of undefined.

Problem lies here:
Vue.set(this.books[i], 'title', item.title);
You are inside the callback context and the value of this is not the Vue object as you might expect it to be. One way to solve this is to save the value of this beforehand and use it in the callback function.
Also instead of using Vue.set(), try updating the books object directly.
const app = new Vue({
el: '#book-search',
data: {
searchInput: 'a',
books: {},
},
methods: {
foo: function () {
var self = this;
//--^^^^^^^^^^^^ Save this
axios.get('https://www.googleapis.com/books/v1/volumes', {
params: {
q: self.searchInput
//-^^^^--- use self instead of this
}
})
.then(function (response) {
var items = response.data.items
var books = {};
for (i = 0; i < items.length; i++) {
var item = items[i].volumeInfo;
books[i] = { 'title' : item.title };
}
self.books = books;
})
.catch(function (error) {
console.log(error);
});
}
}
});
Or if you want to use Vue.set() then use this:
Vue.set(self.books, i, {
'title': item.title
});
Hope this helps.

yep, the problem is about context. "this" returns not what you expect it to return.
you can use
let self = this;
or you can use bind
function(){this.method}.bind(this);
the second method is better.
Also google something like "how to define context in js", "bind call apply js" - it will help you to understand what is going wrong.

// update component's data with some object's fields
// bad idea, use at your own risk
Object
.keys(patch)
.forEach(key => this.$data[key] = patch[key])

Related

Global loaded data in VueJs is occasionally null

I'm new to VueJs and currently trying to load some data only once and make it globally available to all vue components. What would be the best way to achieve this?
I'm a little bit stuck because the global variables occasionally seem to become null and I can't figure out why.
In my main.js I make three global Vue instance variables:
let globalData = new Vue({
data: {
$serviceDiscoveryUrl: 'http://localhost:40000/api/v1',
$serviceCollection: null,
$clientConfiguration: null
}
});
Vue.mixin({
computed: {
$serviceDiscoveryUrl: {
get: function () { return globalData.$data.$serviceDiscoveryUrl },
set: function (newUrl) { globalData.$data.$serviceDiscoveryUrl = newUrl; }
},
$serviceCollection: {
get: function () { return globalData.$data.$serviceCollection },
set: function (newCollection) { globalData.$data.$serviceCollection = newCollection; }
},
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) { globalData.$data.$clientConfiguration = newConfiguration; }
}
}
})
and in my App.vue component I load all the data:
<script>
export default {
name: 'app',
data: function () {
return {
isLoading: true,
isError: false
};
},
methods: {
loadAllData: function () {
this.$axios.get(this.$serviceDiscoveryUrl)
.then(
response => {
this.$serviceCollection = response.data;
let configurationService = this.$serviceCollection.services.find(obj => obj.key == "ProcessConfigurationService");
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
})
}
},
created: function m() {
this.loadAllData();
}
}
</script>
But when I try to access the $clientConfiguration it seems to be null from time to time and I can't figure out why. For example when I try to build the navigation sidebar:
beforeMount: function () {
let $ = JQuery;
let clients = [];
if (this.$clientConfiguration === null)
console.error("client config is <null>");
$.each(this.$clientConfiguration, function (key, clientValue) {
let processes = [];
$.each(clientValue.processConfigurations, function (k, processValue) {
processes.push(
{
name: processValue.name,
url: '/process/' + processValue.id,
icon: 'fal fa-project-diagram'
});
});
clients.push(
{
name: clientValue.name,
url: '/client/' + clientValue.id,
icon: 'fal fa-building',
children: processes
});
});
this.nav.find(obj => obj.name == 'Processes').children = clients;
The most likely cause is that the null is just the initial value. Loading the data is asynchronous so you'll need to wait for loading to finish before trying to create any components that rely on that data.
You have an isLoading flag, which I would guess is your attempt to wait for loading to complete before showing any components (maybe via a suitable v-if). However, it currently only waits for the first request and not the second. So this:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
would need to be:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
this.isLoading = false;
}
);
If it isn't that initial value that's the problem then you need to figure out what is setting it to null. That should be prety easy, just put a debugger statement in your setter:
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) {
if (!newConfiguration) {
debugger;
}
globalData.$data.$clientConfiguration = newConfiguration;
}
}
Beyond the problem with the null, if you're using Vue 2.6+ I would suggest taking a look at Vue.observable, which is a simpler way of creating a reactive object than creating a new Vue instance.
Personally I would probably implement all of this by putting a reactive object on Vue.prototype rather than using a global mixin. That assumes that you even need the object to be reactive, if you don't then this is all somewhat more complicated than it needs to be.

TypeError: Cannot read property 'push' of undefined [VueJs]

I am trying to add an object to an array but it is not working with me, the program can't read the property push
I defined an array in <script>:
Data: function() {
return {
Projects: [
{
name: '',
id: 0,
subscribers: 0,
products: {name:'',color:''},
}
],
}
And in the function:
GetAllWorkspaces: function(){
var app = this;
const instance = axios.create({
timeout: 1000,
headers: {
........
}
});
instance.get("XXXXXXX")
.then( function(response) {
console.log(response);
Object.keys(response.data.result).forEach( function (product) {
var subscribersCounter = 0;
let example = {
name: response.data.result[product].name,
id: response.data.result[product].id,
subscribers: response.data.result[product].subscribers,
products: response.data.result[product].products,
};
let uploadedExample = {
name: '',
id: '',
subscribers: '',
products: {name:'',color:''},
};
uploadedExample.name = example.name;
uploadedExample.id = example.id;
if ( example.subscribers ) {
Object.keys(example.subscribers).forEach(function (key) {
subscribersCounter++;
});
}
uploadedExample.subscribers = subscribersCounter;
if ( example.products ) {
Object.keys(example.products).forEach(function (Pkeys) {
uploadedExample.products.name = Pkeys;
Object.keys(example.products[Pkeys]).forEach(function (key) {
if (key == 'color') {
uploadedExample.products.color = example.products[Pkeys][key];
}
});
});
}
//add the new workspace to the list of workspaces.
app.Projects.push(uploadedExample);
});
})
.catch(function(error) {
console.log(error);
});
My problem is with this line
app.Projects.push(uploadedExample);
where when I try to push an object into the array, the error message is shown:
TypeError: Cannot read property 'push' of undefined
As the error says, the problem is that app.Projects is undefined. This happens because 'this' refers to the function scope inside GetAllWorkspaces and not to the component scope (you can try it by console.logging 'this' - anyway- it is a good practice under all circumstances because 'this' can change from context to context). If you want to keep the component scope inside the method, you should use an arrow function like this:
GetAllWorkspaces: () => {
// do all your stuff
}

Can't update value into model generated by Constructor

I am trying to understand the OOP with the following example below. Can you please explain what am I doing wrong and why?
var shoppingcartModel = function() {
var _Cart = function() {
return {
totalPrice: {},
products: []
};
}
return {
cart: _Cart,
addProducts: function(product) {
return _Cart().products.push(product);
}
};
};
var shoppingCart = shoppingcartModel()
console.log(shoppingCart.cart())
shoppingCart.addProducts('product1')
shoppingCart.addProducts('product2')
console.log(shoppingCart.cart())
_Cart is a function that returns an object, not an object itself. Whenever you call Cart_(), including in addProducts, you create a new object, so whatever you push to one of the old objects is disregarded because no reference to the old object remains.
Try something like this instead:
var shoppingcartModel = function() {
const cart = {
totalPrice: {},
products: []
};
return {
cart,
addProducts: function(product) {
return cart.products.push(product);
}
};
};
var shoppingCart = shoppingcartModel()
console.log(shoppingCart.cart)
shoppingCart.addProducts('product1')
shoppingCart.addProducts('product2')
console.log(shoppingCart.cart)

Export table values Meteor Blaze

I am running into some difficulty exporting a table to csv in meteor/blaze. I am following: [http://rafaelquintanilha.com/export-your-json-data-to-csv-format/][1].
I have a Template.event that is triggering the export button
Template.export.onCreated( () => {
Template.instance().subscribe('table');
});
Template.export.helpers({
exportContacts() {
return Contacts.find();
}
});
Template.export.events({
'click .export-data' () {
MyAppExporter.exportAllContacts();
}
});
it is calling exportAllContacts() in a global helper
MyAppExporter = {
exportAllContacts: function() {
var self = this;
Meteor.call("exportContacts", function(error, data) {
if ( error ) {
alert(error);
return false;
}
var csv = Papa.unparse(data);
self._downloadCSV(csv);
});
},
_downloadCSV: function(csv) {
var blob = new Blob([csv]);
var a = window.document.createElement("a");
a.href = window.URL.createObjectURL(blob, {type: "text/plain"});
a.download = "contacts.csv";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
}
and the helper is calling a Meteor.method exportContacts
Meteor.methods({
exportContacts: function() {
let fields = [
"Email",
“Some Contact",
"Created Date",
"Hard Bounce",
"Unsubscribed"
];
let data = [];
let contacts = Contacts.find().fetch();
for(let i = 0; i < contacts.length; i++) {
let contact = contacts[i];
let contactString = JSON.stringify(contact);
_.each(contactString, function(c) {
console.log("Inside Loop", contactString);
data.push([
c.contact.emailAddress,
c.contact.someContact,
c.contact.creationDate,
c.contact.hardBounceBack,
c.contact.unsubscribed
]);
console.log("DATA", data)
return {fields: fields, data: data};
});
}
}
});
I keep getting an error that “emailAddress is not defined exportContacts.js:20:17
20160426-22:00:47.957(-4)? Inside Loop {"_id":"dRnXRdZrbR9CYdmBx","contact":[{"emailAddress":"fred#weasly.com","someContact":"No","creationDate":"N/A","hardBounceBack":"N/A","unsubscribed":"N/A"}]}
I20160426-22:00:48.029(-4)? Exception while invoking method 'exportContacts' ReferenceError: emailAddress is not defined
I20160426-22:00:48.029(-4)? at server/methods/exportContacts.js:20:17
I20160426-22:00:48.029(-4)? at Function._.each._.forEach (packages/underscore.js:142:22)
I20160426-22:00:48.029(-4)? at _loop (server/methods/exportContacts.js:17:7)
but I cannot seem to figure out how to access the contacts. I am logging it out (see above in logs). Any help would be appreciated.
ADDED LOGS
let contacts = Contacts.find().fetch(); console.log(contacts)
I20160427-09:06:23.484(-4)? CONTACTS [ { _id: 'dRnXRdZrbR9CYdmBx', contact: [ [Object] ] },
I20160427-09:06:23.484(-4)? { _id: 'LHmW4R9PLM5D7cZxr', contact: [ [Object] ] },
I20160427-09:06:23.484(-4)? { _id: 'jBdqQXz2b8itXJowX', contact: [ [Object] ] },
I20160427-09:06:23.484(-4)? { _id: 'bnDvNGX3i879z4wr2', contact: [ [Object] ] } ]
c.contact[0].emailAddress logged out
I20160427-09:22:08.142(-4)? Inside Loop {"_id":"dRnXRdZrbR9CYdmBx","contact":[{"emailAddress":"fred#weasly.com","someContact":"No","creationDate":"N/A","hardBounceBack":"N/A","unsubscribed":"N/A"}]}
I20160427-09:22:08.217(-4)? Exception while invoking method 'exportContacts' TypeError: Cannot read property '0' of undefined
I20160427-09:22:08.217(-4)? at server/methods/exportContacts.js:21:7
I20160427-09:22:08.217(-4)? at Function._.each._.forEach (packages/underscore.js:142:22)
I20160427-09:22:08.217(-4)? at _loop (server/methods/exportContacts.js:18:7)
I20160427-09:22:08.218(-4)? at [object Object].exportContacts (server/methods/exportContacts.js:15:46)
Within the _.each loop you are accessing the wrong data items. You can also use a _.each loop instead of the outer for loop too. If you do :
let contacts = Contacts.find().fetch();
_.each(contacts, function(contact) {
_each(contact.contact, function(c) {
data.push(
{
"email": c.emailAddress,
"contact": c. someContact,
"creationDate" : c.creationDate,
"bounceBack": c.hardBounceBack,
"unsubscribed": c.unsubscribed
}
})
})
This should solve your problem. This way you are looping through the outer contacts that is coming back from the fetch and then looping through the contact array from each element. This should be the most direct way to get down to the data you are looking for.
Your problem is this line: _.each(contactString, function(c) {
It should read: _.each(contact, function(c) {
:)

Transform data before rendering it to template in meteor

I want to return a single document with the fields joined together. That is, a result like as follows
{
_id: "someid",
name: "Odin",
profile: {
game: {
_id: "gameid",
name: "World of Warcraft"
}
}
}
I have a route controller which is fairly simple.
UserController = RouteController.extend({
waitOn: function () {
return Meteor.subscribe('users');
},
showAllUsers: function () {
this.render('userList', {
data: Meteor.users.find()
})
}
});
I've tried changing my data like so:
this.render('userList', {
data: Meteor.users.find().map(function (doc) {
doc.profile.game = Games.findOne();
return doc;
})
});
However, this does not have the intended effect of adding "game" to the user. (and yes, Games.findOne() has a result)
How can you transform the results of a cursor in meteor and iron:router?
Try defining your data as a function so it can be dynamically re-executed when needed.
UserController = RouteController.extend({
waitOn: function () {
return Meteor.subscribe('users');
},
showAllUsers: function () {
this.render('userList', {
data: function(){
return Meteor.users.find().map(function (doc) {
doc.profile.game = Games.findOne();
return doc;
});
}
});
}
});
Given your use of easy search, what might be simpler is just to define a template helper for profile
Template.userList.helpers({
profile: function(){
var game = Games.findOne({_id: this.gameId});
return { game: { _id: game._id, name: game.name }};
}
});
This assumes a single game per user. If you have more than one then you can iterate over a cursor of Games instead.

Categories