backbone.js collection not responding to .each - collections

I have what should be very simple. I create a new collection, and I want to pass it to a render and add the collection models to the page.
get_results: function(){
$.getJson(this.url,function(response){
this.search_results = new Kitchon.Collections.searchList(response);
console.log(this.search_results);
this.search_results.each(this.render_match);
}
},
render_match: function(model){
console.log(model)
},
This returns an error
Uncaught TypeError: undefined is not a function
my collection has an ordinary structure
_byCid: Object
_byId: Object
_onModelEvent: function () { [native code] }
_removeReference: function () { [native code] }
length: 7
models: Array[7]
__proto__: o
I've tried LOTS of things, but one thing that stuck out was maybe I had to pass
this.search_results.models.each(this.render_match); as that is the actual array, but if I do that I get a Uncaught typeError: Object [object Object],[object Object],...

you lose the execution context when callback function for each method is called
use _.bind(this.render_match, this) when passing callback to make sure that render_match has the right context
and you were getting error because you didn't wrap the callback function for getJson method neither. You have to use underscore bind method there as well.
You should read a bit about javascript this and how to tame it - try here http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/
Correct code should look more or less like this...
get_results: function(){
$.getJSON(this.url, _.bind(function(response){
this.search_results = new Kitchon.Collections.searchList(response);
console.log(this.search_results);
this.search_results.each(_.bind(this.render_match, this));
}, this));
},
render_match: function(model){
console.log(model)
},
Though from what I seee - I assume the code you've shown here is either a model or collection - is handling rendering the view - you shouldn't do that! Models and Collections are only to store and parse data - all rendering and controlling application flow should be done in the View(Controllers) with help of the Router.

$.getJson changes the this reference. Many methods in jquery do that, so the value of this.render_match is null. You pass null to each and it fails.
To solve that, create a reference to this (like var _this = this;) before $.getJson and use it instead of this. Code should be like below:
get_results: function(){
var _this = this;
$.getJson(this.url,function(response){
_this.search_results = new Kitchon.Collections.searchList(response);
console.log(_this.search_results);
_this.search_results.each(_this.render_match);
}
},
render_match: function(model){
console.log(model)
},

Just taking a stab here (I don't know anything about Backbone.js), but isn't this what you are looking for:
$.each(this.search_results, function(index, value) {
alert(index + ': ' + value);
});
Good Luck!

Related

Returned value from Meteor Helper not showing up in template

I have a Meteor Helper that does a GET request and am supposed to get response back and pass it back to the Template, but its now showing up the front end. When I log it to console, it shows the value corerctly, for the life of mine I can't get this to output to the actual template.
Here is my helper:
UI.registerHelper('getDistance', function(formatted_address) {
HTTP.call( 'GET', 'https://maps.googleapis.com/maps/api/distancematrix/json? units=imperial&origins=Washington,DC&destinations='+formatted_address+'&key=MYKEY', {}, function( error, response ) {
if ( error ) {
console.log( error );
} else {
var distanceMiles = response.data.rows[0].elements[0].distance.text;
console.log(response.data.rows[0].elements[0].distance.text);
return distanceMiles;
}
});
});
In my template I pass have the following:
{{getDistance formatted_address}}
Again, this works fine and shows exactly what I need in the console, but not in the template.
Any ideas what I'm doing wrong?
I posted an article on TMC recently that you may find useful for such a pattern. In that article the problem involves executing an expensive function for each item in a list. As others have pointed out, doing asynchronous calls in a helper is not good practice.
In your case, make a local collection called Distances. If you wish, you can use your document _id to align it with your collection.
const Distances = new Mongo.collection(); // only declare this on the client
Then setup a function that either lazily computes the distance or returns it immediately if it's already been computed:
function lazyDistance(formatted_address){
let doc = Distances.findOne({ formatted_address: formatted_address });
if ( doc ){
return doc.distanceMiles;
} else {
let url = 'https://maps.googleapis.com/maps/api/distancematrix/json';
url += '?units=imperial&origins=Washington,DC&key=MYKEY&destinations=';
url += formatted_address;
HTTP.call('GET',url,{},(error,response )=>{
if ( error ) {
console.log( error );
} else {
Distances.insert({
formatted_address: formatted_address,
distanceMiles: response.data.rows[0].elements[0].distance.text
});
}
});
}
});
Now you can have a helper that just returns a cached value from that local collection:
UI.registerHelper('getDistance',formatted_address=>{
return lazyDistance(formatted_address);
});
You could also do this based on an _id instead of an address string of course. There's a tacit assumption above that formatted_address is unique.
It's Meteor's reactivity that really makes this work. The first time the helper is called the distance will be null but as it gets computed asynchronously the helper will automagically update the value.
best practice is not to do an async call in a helper. think of the #each and the helper as a way for the view to simply show the results of a prior calculation, not to get started on doing the calculation. remember that a helper might be called multiple times for a single item.
instead, in the onCreated() of your template, start the work of getting the data you need and doing your calculations. store those results in a reactive var, or reactive array. then your helper should do nothing more than look up the previously calculated results. further, should that helper be called more times than you expect, you don't have to worry about all those additional async calls being made.
The result does not show up because HTTP.call is an async function.
Use a reactiveVar in your case.
Depending on how is the formated_address param updated you can trigger the getDistance with a tracker autorun.
Regs
Yann

Cannot read property of undefined, but property exists

I am getting a curious error in a template helper and I was hoping someone could lay eyes on it with me. Basically the error I'm getting in the console of the client is that the getArena().height is undefined. However, console.log(getArena().height) returns the correct property value. It appears to be a timing issue causing me to get the error, but my application is actually working. What can I do to alleviate this console error?
//My template helper function
yGrids: function() {
console.log(getArena);
console.log(getArena().height);
var yArray = [];
for (var i=0;i<(getArena().height);i++){
yArray.push({});
}
return yArray;
},
// The console results
function getArena() { // 50
return Arenas.findOne(Session.get('arena_id')); …
Exception in template helper: TypeError: Cannot read property 'height' of undefined
at Object.yGrids (http://localhost:3000/app/app.js?hash=c17abf51d6af6541e868fa3fd0b26e34eea2df28:94:35)
at http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:2994:16
at http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:1653:16
at http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:3046:66
at Function.Template._withTemplateInstanceFunc (http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:3687:12)
at http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:3045:27
at Object.Spacebars.call (http://localhost:3000/packages/spacebars.js?hash=65db8b6a8e3fca189b416de702967b1cb83d57d5:172:18)
at http://localhost:3000/app/app.js?hash=c17abf51d6af6541e868fa3fd0b26e34eea2df28:24:22
at .<anonymous> (http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:2754:17)
at http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:1875:20
function getArena() { // 50
return Arenas.findOne(Session.get('arena_id')); …
2
This is a very common issue in Meteor helpers when referring to a collection which may not yet have been loaded via a subscription. In general you want to show a loading template instead of your actual layout until your subscription is ready. Or (less elegant) you can defend yourself with:
var arena = getArena();
var height = arena && arena.height;
Whatever getArena() returns you ought to store it in the reactive variable by setting the reactive variable and you can access the reactive var in helper by get() method

Randomly occurs with Meteor: "TypeError: Cannot read property 'invalidate' of undefined"

On what seems to be random occasions, when a user on my website tries to answer a question on the quiz page, the following error occurs:
TypeError: Cannot read property 'invalidate' of undefined
at Tracker.Dependency.changed (tracker.js:388)
at ReactiveVar.set (reactive-var.js:82)
at null. (builtins.js:22)
at view.js:191
at Function.Template._withTemplateInstanceFunc (template.js:437)
at view.js:190
at Object.Blaze._withCurrentView (view.js:523)
at viewAutorun (view.js:189)
at Tracker.Computation._compute (tracker.js:294)
at Tracker.Computation._recompute (tracker.js:313)
I am unsure as to why this occurs, and I am not entirely sure when this error even means so if someone could point me in the right direction, that would be great!
Note: this error seems to only occur when using Chrome.
I'll hazard a guess, here's the culprit code in Tracker.Dependency
Tracker.Dependency.prototype.changed = function () {
var self = this;
for (var id in self._dependentsById)
// My Comment - we're expecting this to be a `Tracker.Computation` which has an `.invalidate()` method!
self._dependentsById[id].invalidate();
};
So, the problem is that something is modifying Object.prototype.
eg.
Object.prototype.test = function(){}
var emptyObj = {};
for (i in emptyObj)
console.log('has key=', i, 'isOwnProperty?=', emptyObj.hasOwnProperty(i))
will print out:
has key= test isOwnProperty?= false
A possible solution to try, would be adding this snipped to the client, and seeing if it fixes the problem (At least temporarily):
Tracker.Dependency.prototype.changed = function () {
var self = this;
for (var id in self._dependentsById){
if (self._dependentsById.hasOwnProperty(id)){
self._dependentsById[id].invalidate();
}
}
};
Tracker.Dependency.prototype.hasDependents = function () {
var self = this;
for(var id in self._dependentsById)
if (self._dependentsById.hasOwnProperty(id))
return true;
return false;
};
However - It's bad practice to add to Object.prototype, and lots of code will fail in unexpected ways. You'll need to track this down.
Hard to say without more info, but I'd take a look here, specifically the part under "Subscriptions Don't Block":
https://dweldon.silvrback.com/common-mistakes
As the author states, "In meteor, the majority of "Cannot read property of undefined"errors are caused by an incorrect assumption about the existence of subscribed data."
In other words, you may be trying to access the invalidate property before your subscriptions has returned anything.

Cannot acces properties of resulting array of cursor.fetch();

I have a helper where I want to acces the properties of a different collection.
Template.notification.helpers({
username: function () {
game = Games.findOne({_id: this.gameId}, {fields: {players:1}});
console.log(game) // output is correct
}
})
If I log this, it wil produce the result I expected:
Object {players: Array[2], _id: "qF3skjX2755BYcr8p"}
However, if I in my helper function I try to use/reach this properties I get an undefined error.
Template.notification.helpers({
username: function () {
game = Games.findOne({_id: this.gameId}, {fields: {players:1}});
console.log(game._id) // error;
console.log(game.players) // error
}
})
Output:
Exception from Deps recompute function: TypeError: Cannot read property 'players' of undefined
Why is this happening?
This happens because when Meteor initiall loads on your web browser, all the html and js is ready, but the data is not yet ready.
If you tried to check console.log(game) it may be null. It does this only when the page has loaded. If you load the template after all the data has downloaded you wouldn't see this issue.
When the data arrives the username helper would re-run with the new data.
In the meanwhile you just need to take care of this exception:
var game = Games.findOne({_id: this.gameId}, {fields: {players:1}});
if(!game) return null;

Get JSON array from Ext.data.Store outside?

How can I get JSON array from Ext.data.Store outside the function?
The code:
var store = new Ext.data.Store({
model: 'nested' + type,
proxy: {
type: 'ajax',
url: '/Grid/GetDetailed?InvoiceId=' + $(row).attr('id'),
reader: {
type: 'json',
root: 'items',
totalProperty: 'totalCount'
}
}
});
store.load();
And I want to use something like this:
store.getAt(0);
but it's undefined.
someone said it beacaouse of the ajax which is asynchronic.
If you use store.getAt(0) immediately after the store.load() is called then yes, the problem is that the load is asynchronic so you should use the callback method of the load to fix this.
store.load({
scope : this,
callback: function(records, operation, success) {
//here the store has been loaded so you can use what functions you like
store.getAt(0);
}
});
Use Ext.create instead of new keyword when creating the store, and define a storeId for the store. Then you can use Ext.getStore() method to retrieve the store.
You can also make it work by doing the following:
//This function will be called only after the store has been loaded successfully.
store.on('load',function(this, records, successful, eOpts){
store.getAt(0);
}, this);

Resources