Why does nothing display in my Meteor application? - javascript

I've got this in my client.js file
Template.data.champ = Meteor.call("checkLeague", function(error, results) {
console.log(results.data.data);
return results.data.data;
});
So it shows fine in the console.log but it doesn't actually show on the webpage.
This is my html file with handlebars template
<body>
{{> hello}}
{{> data}}
</body>
<template name="hello">
<h1>Hello World!</h1>
{{greeting}}
<input type="button" value="Click" />
</template>
<template name="data">
{{#each champ}}
{{name}}
{{/each}}
</template>
From my understanding (which is very limited in terms of Handlebars) but the {{#each champ}} iterates over objects? But for some reason, nothing is being displayed on the page.
This is the object structure (displayed in the console).
Object {Aatrox: Object, Ahri: Object, Akali: Object, Alistar: Object, Amumu: Object…}
Aatrox: Object
id: "Aatrox"
image: Object
key: "266"
name: "Aatrox"
title: "the Darkin Blade"
__proto__: Object
Ahri: Object
Akali: Object
Alistar: Object
Amumu: Object
Anivia: Object
Annie: Object
Ashe: Object
So basically I am passing an object that has properties which have values of objects. I assume the {{#each} iterates over the properties and gives access to the values (which is an object) and then I try to access the name property of that variable in the handlebars template but it doesn't work.

From the docs regarding Meteor.call:
If you include a callback function as the last argument (which can't
be an argument to the method, since functions aren't serializable),
the method will run asynchronously: it will return nothing in
particular and will not throw an exception.
So whatever value Template.data.champ is assigned, it's "nothing in particular" (note that what you return from the callback you have will never be used anywhere).
You could store it in a Session like this:
Session.setDefault('theData', [])
Meteor.call("checkLeague", function(error, results) {
Session.set('theData', results.data.data)
});
Template.data.champ = function(){
return Session.get('theData')
}
But I would try to go with a collection instead.

Peppe's answer is correct - here is an option for how to deal with this situation:
Template.data.created = function() {
Meteor.call('checkLeague', function(error, results) {
Session.set('champ', results.data.data);
});
};
Template.data.champ = function() {
return Session.get('champ');
};
The data is loaded when the template is created, and asynchronously stored into a session variable. Keep in mind that this isn't reactive, but that's hard to overcome since your data is coming from a method call.

I think the problem might just be that #each is expecting an array and you are passing an object. In your helper, try return _.toArray( result.data.data );.
I couldn't find any mention of this in the meteor docs but handlebars.js docs mentions Array. Also, I have passed Array before and it works.

In addition to what was said about using a session variable. I think you will also need to transform your list into a list of just objects instead of a list of key, value you pairs.
So instead you want to make your data just like this
var champ = [{ id: "Aatrox",
image: Object,
key: "266",
name: "Aatrox",
title: "the Darkin Blade"},
{ id: "Ahri",
image: Object,
key: "267",
name: "Ahri",
title: "Hitchhikers Guide"}, ... ];
return champ;
To get this structure from your current structure you'll need to do something like
var champ = [];
for (var a in results.data.data) {
for (var key in results.data.data[a]) {
champ.push(results.data.data[a][key]);
}
}

Related

Edit readonly javascript objects

I have a read-only object that is returned by GraphQL (vue-apollo) query, the result which is read-only looks something like this:
result: {
id: 'yh383hjjf',
regulations: [{ title: 'Test', approved: false}]
})
I want to bind this to a form and be able to edit/update the values in the regulations array and save it back to the database.
at the moment when I try to edit I get the error below:
Uncaught TypeError: "title" is read-only
I tried cloning the result returned by the database using object.assign
//target template
const regulatoryApprovals = {
id: null,
regulations: [{ title: null, approved: null}]
})
regulatoryApprovals = Object.assign(regulatoryApprovals, result, {
regulations: Object.assign(regulatoryApprovals.regulations, result.regulations)
})
but this didn't work.
Does anyone know how I can properly clone the result?
regulatoryApprovals= Object.assign(regulatoryApprovals, ... indicates the problem because regulatoryApprovals is modified with Object.assign, so it would need no assignment.
Read-only regulatoryApprovals object needs to be cloned. regulations is an array and won't be merged correctly with Object.assign, unless it's known that array elements need to be replaced. It should be:
regulatoryApprovals = {
...regulatoryApprovals,
...result,
regulations: [...regulatoryApprovals.regulations, result.regulations]
}
Where { ...regulatoryApprovals, ... } is a shortcut for Object.assign({}, regulatoryApprovals, ...).

Vue js issues recalling url from API

I’m trying to display the store URLs from an API.
In the API are available different URLs and in the output, they are displayed as one single link. How can I display URLs in different lines?
My vue code:
<a v-bind:href=“storeUrl”>
{{storeUrl}}
</a>
My script:
....
computed:{
storeUrl() {
return this.games.store.map(({{url}}) => url).join(‘ ’)
},
}
I’m using https://api.rawg.io/api/games API
This is the current output:
This answer solves some problems after #chillin's answer.
As I mentioned in the comments, the reason you're not seeing any store urls is that you are iterating over an object that doesn't exist:
The problem, (as #chillin saw) is that you are iterating through
game.store when you should be iterating through game.stores If you
inspect the game object, you'll notice that there is a stores
array within, but not a store one.
You should also note that having the urls in anchors on their own will cause them to be squashed into one line. Wrapping the anchors in <p> elements solves that problem:
Before:
<a v-for="store in game.store" :href="store.url" :key="store.ID">{{store.url}}</a>
After:
<p v-for="store in game.stores" :key="store.ID">
<a :href="store.url">{{store.url}}</a>
</p>
Also, I'm not sure if the stores array could ever have duplicate IDs, (maybe if multiple versions of the same game are in the same store), but if that ever does happen your code could crash. So it might be a better option to simply use the index of the object as it's key, like so,
<p v-for="(store, index) in game.stores" :key="index">
<a :href="store.url">{{store.url}}</a>
</p>
...so as to avoid this potential problem.
Here's a demo and here's the modified codepen (I also removed the computed property storeUrl, as it was unused.
Updated with actual example
First, don't join them in the computed, and then implement using a v-for, something like this should work.
Basically this is my own take of course, but based on the actual API data, something like this should work, with a loop in a loop, I map out the data just for ease of use, you will end up with something like:
[
{
key: 'gta-v',
storeUrls: [
{
key: 'steam',
url: 'http:// ...'
},
{
key: 'riot',
url: 'http:// ...'
}
]
},
{
key: 'fortnite',
storeUrls: [
{
key: 'steam',
url: 'http:// ...'
},
{
key: 'riot',
url: 'http:// ...'
}
]
}
]
Using this we can also double down on a v-for in the template, and sort your data by game, and for each game loop through it's storeUrl's for a nice clean list, this also utilises the use of actual keys, rather than index.
<template>
<div class="root">
<div class="game" v-for="game in games" :key="game.key">
<h1>{{ game.key }}</h1>
<a v-for="store in game.storeUrls" :href=“store.url” :key="store.key">
{{store.url}}
</a>
</div>
</div>
</template>
export default {
data() {
return {
myData: null
}
},
computed: {
games() {
if (!this.myData) return [];
return this.myData.results.map(game => {
key: game.slug,
storeUrls: game.stores.map(store => {
return {
key: store.store.slug,
url: store.url_en
}
});
});
}
},
methods: {
getData() {
// However you do it, but the base data, with no mapping.
this.myData = axios.get('...');
}
}
}
I'm new to Vue, so maybe someone else can provide a better answer. But seems right to use v-for to loop over your store URLs.
Assuming your gameId watcher is running and completing successfully, I don't think you need to change anything since this.game.stores should already contain an array of objects.
So you should be able to do something like:
<a v-for="store in game.stores" :href="store.url" :key="store.store.id">{{ store.url }}</a>
I don't know the difference between store.id and store.store.id, but I've assumed that store.store.id uniquely identifies a store and is okay to be used as the key. (You'd have to check the API documentation to see what the IDs represent.)

How do you process an array outside the vue instance and assign the processed data to a (currently empty) array inside the vue instance?

I have an array of blog post inside a script tag on the page that looks like this:
var posts = [
{article_name: "name", category: "test".....},
{article_name: "name2", category: "test2".....},
{...}
]
There are 30 objects in each array. I need to grab the categories in each object and assign it to the data prop in a vue instance. I have an empty array inside the vue instance like:
var blog_posts_nav = new Vue({
el: '#blog-posts-nav',
data: {
tags: []
}
})
I want to have each individual "category" in the 30 objects mapped to the "tags" array in the data prop. I tried doing it via the created() hook but it seems that the created hook can't access data? I tried:
created() {
posts.forEach( function (item) {
this.tags.push(item.category)
});
}
But I get an error in the console that says tags is undefined. Any help how I would deal with this? Basically I want to do work on a set of data and assign it to an array inside vue before outputting it to the page and in a manner that vue can interact with the data.
So first and foremost, is the created() hook (or any hook) the best way to go about this? Or should I be using something like methods or computed?
What is the "best practices" way and how would I go about achieving this?
It's not the created() method that can't access this, it's the inner context of forEach.
You can capture this to a local constant first, then access it inside the loop.
console.clear()
var posts = [
{article_name: "name", category: "test" },
{article_name: "name2", category: "test2" },
]
Vue.component("tags",{
template:`
<div>
{{tags}}
</div>
`,
data(){
return {
tags: [],
}
},
created() {
const vm = this;
console.log('created', vm.tags)
posts.forEach( function (item) {
vm.tags.push(item.category)
console.log('created, posts.forEach', vm.tags)
});
}
})
new Vue({
el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.js"></script>
<div id="app">
<tags></tags>
</div>

String Interpolation not getting updated after change to object in Viewmodel in Aurelia

I have an element in my View in Aurelia that is not getting updated when an object from its Viewmodel is getting updated. I've seen the documentation about Pub/Sub and Event Aggregators, however this seems a little heavy-handed for what I want to do, since I am not trying to communicate between two different resources, but rather just within a View and its Viewmodel.
When a change occurs to the object in the Viewmodel, I don't know how to correctly update (or trigger an update to) the string interpolation in the View.
My code is as follows
myview.html
<h1>My List</h1>
<ul>
<li repeat.for="group of modelObject.groups">
<span>${group.id}</span>
<span repeat.for="state of group.region">${state}</span>
</li>
<ul>
<button click.delegate(editModelObject())>Edit</button>
myviewmodel.js
constructor()
{
this.modelObject = {
<other stuff>,
"groups": [
{
"id": "default",
"regions" : ["NY", "CT", "NJ"]
},
{
"id": "west",
"regions" : ["CA", "OR"]
}
],
<more stuff>
}
}
editModelObject() {
<something that updates both the id and regions of a group in this.modelObject>
}
For some reason, the states are correctly changing in the view, but the id's are not. Do I need to use something like Pub/Sub to get the two-way binding to work correctly? Or is there a simple thing that I am missing or doing wrong?
This works if you change a property of one of the array's objects. But this doesn't work if you assign one of the array's index because this would require dirty-checking. See https://github.com/aurelia/binding/issues/64
To solve your problem you should use splice() instead of indexed assignment. For instance:
const newItem = { id: 77, name: 'Test 77', obj: { name: 'Sub Name 77' } };
//instead of this.model.items[0] = newItem; use below
this.model.items.splice(0, 1, newItem);
Running example https://gist.run/?id=087bc928de6532784eaf834eb918cffa

Javascript reset object inside array

I have the below JS code in my Ember app that gets called;
myPanels.accordionPanels = [];
myPanels.accordionPanels.push({
panel: {
name: "my-grid",
type: 'comp',
props: [{
key: 'elementId',
value: "myCustomId"
}]
}
});
So as you can see, I start by setting myPanels.accordionPanels = [] every time and then push the object.
However, I got the following error
Assertion Failed: Attempted to register a view with an id already in
use: myCustomId
So I am assuming that the object inside is not getting reset & it is able to find the earlier created "myCustomId".
Am I resetting the array (or rather the object inside it) correctly ?
Since I am able to push values using:
accordionPanels = [];
accordionPanels.push({
panel: {
name: "my-grid",
type: 'comp',
props: [{
key: 'elementId',
value: "myCustomId"
}]
}
});
make sure myPanels.accordionPanels doesn't have any prototype associated with it.
Try to inspect its value as:
myPanels.accordionPanels = [];
console.log(myPanels.accordionPanels); // see if it has values.
You can delete value using :
delete myPanels.accordionPanels PROTOTYPE

Categories