My goal: Show a default image if one does not exist.
My approach: I've created a helper that makes a server-side Meteor.call to check if the image url exists. The helper's intent is to either return a default image path (does not exist) or the dynamic path (image exists).
Where I'm stuck
Helper: On the client, I can successfully console.log the output
from the server-side method (result.statusCode). However, the helper does not return my
desired string in the template (/images/db/...etc).
Method: I'm getting a 200 results
status for ALL file paths, even ones that don't exist. I suspect
this has to do with iron-router's global NotFound template, but not
sure how to get around it. I tried using fs.exists but could never
get it to find a file (all responses were false).
Any and all suggestions most appreciated. If there's a simpler way to accomplish this, I'm all ears.
HTML:
<img src="{{imagePath key}}avatar.jpg">
My helper:
UI.registerHelper('imagePath', function(key){
//Build the Meteor.call url
var $host = document.location.host;
var $imgBaseUrl = '/images/db/'
var $assetPath = $imgBaseUrl + key + '/';
var url = 'http://' + $host + $assetPath + 'bg.jpg';
//Define the default image location
var $assetPathDefault = $imgBaseUrl + 'default' + '/';
//Call the server-side method
Meteor.call('checkIfImageExists', url, function(error, result) {
if (false) {
console.log('Error');
return $assetPathDefault;
} else {
console.log('Result: ' + result.statusCode);
console.log($assetPath);
return $assetPath;
};
});
});
Server-side method
Future = Npm.require('fibers/future');
Meteor.methods({
checkIfImageExists: function(url) {
check(url, String);
var fut = new Future();
this.unblock();
HTTP.get(url, function (error, result) {
if (!error) {
console.log('Found a file!: ' + url);
console.log('Result: ' + result.statusCode);
fut.return (result);
} else {
console.log(error);
console.log('Error: ' + error);
fut.return (false);
};
});
return fut.wait();
}
});
FWIW - I'm adding the "url check" to an old helper that simply inserted a string w/out checking if the image existed. Worked great.
UI.registerHelper('imagePath', function(key){
var baseUrl = '/images/db/';
return baseUrl + key + '/';
});
Your client–side helper doesn't return anything! Check out the structure you've used:
function a() {
...
Meteor.call(..., function b() {
return something;
});
}
The return something is a return statement of function b, while function a has no return statement – thus it returns undefined.
Meteor server-side methods are (and have to be) asynchrounous, while the nature of client-side Javascript implies that the helper methods are synchronous (there is no "wait" in the browser). Thus, to use a server-side method inside a client-side helper you have to take advantage of reactivity. Fortunately, it's pretty easy with a ReactiveDict:
var imagePathDict = new ReactiveDict();
UI.registerHelper('imagePath', function(key) {
...
if(!imagePathDict.get(key)) {
// the path was not initialized, fetch it from the server
Meteor.call(..., function(error, result) {
...
imagePathDict.set(key, result.assetPath);
});
}
// return the reactive path
return imagePathDict.get(key);
});
By the way, don't start your variable names with $ (unless you refer to a jQuery object), it's against conventions in Javascript.
Related
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
Javascript noob here. Apologies if there are a ton of duplicate questions out there because it seems like this must be a fundamental js function thing, but I honestly can't find an answer to this. I'm trying to wrap an API GET call in a function, and I'm running into behavior that I don't understand. The code in question:
I'm using the node-rest-client package to call the mapquest geocoding API. I'm interested in the lat/long data only.
var Client = require('node-rest-client').Client;
If I make the GET call like this, I can access parsed as an object, which is what I want.
var address = 'New York'
var client = new Client();
var parsed;
client.get("http://www.mapquestapi.com/geocoding/v1/address?" +
'key=' + mapquestKeys.consumer_key +
'&location=' + address,
function(data, response) {
parsed = data.results[0].locations[0].latLng
}
);
// parsed == {lat, long}
But if I wrap this in a function:
function geocode(address){
var client = new Client();
var parsed;
client.get("http://www.mapquestapi.com/geocoding/v1/address?" +
'key=' + mapquestKeys.consumer_key +
'&location=' + address,
function(data, response) {
parsed = data.results[0].locations[0].latLng
}
);
return parsed
}
var address = 'New York'
parsed = geocode(address);
// parsed === undefined
parsed doesn't seem to be affected by the inner function; it's undefined. How can I return parsed as an object containing the data I want as in the first example? What the heck is going on here?
In:
function geocode(address){
var client = new Client();
var parsed;
client.get("http://www.mapquestapi.com/geocoding/v1/address?" +
'key=' + mapquestKeys.consumer_key +
'&location=' + address,
function(data, response) {
parsed = data.results[0].locations[0].latLng
}
);
return parsed
}
var address = 'New York'
parsed = geocode(address);
// parsed === undefined
You never defined parsed outside of the scope of your function. Also you're returning parsed inside of the function before it's had a chance to retrieve from the GET request. If you wanted to do it this way, you'd need to put return(prased) inside the callback function of client.get. A better way to do it is to wrap it inside a Promise like so:
function geocode(address){
return new Promise((resolve, reject) => {
var client = new Client();
client.get("http://www.mapquestapi.com/geocoding/v1/address?" +
'key=' + mapquestKeys.consumer_key +
'&location=' + address,
function(data, response) {
if(data){
resolve(data.results[0].locations[0].latLng)
}
else{
reject(response)
}
});
})
};
var address = 'New York';
var parsed;
geocode(address).then(function(latlong){
parsed = latlong
}).catch(err => {
console.log(err)});
Here, parsed will only evaluate to latlong once the Promise has been resolved (the GET request has returned successful). It will also reject the Promise if data of the GET request is NULL and return an error.
If you wanted to then do something with parsed you could include that in the .then() statement.
Learning how to code in Javascript means learning how to write code asynchronously. Promises help you treat things which are by default asynchronous as synchronous.
You never defined parsed in scope (externally to the function):
function geocode(address){
var client = new Client();
var parsed;
client.get("http://www.mapquestapi.com/geocoding/v1/address?" +
'key=' + mapquestKeys.consumer_key +
'&location=' + address,
function(data, response) {
parsed = data.results[0].locations[0].latLng
}
);
return parsed
}
var address = 'New York'
var parsed = geocode(address);
Notice the var parsed = geocode(address);
you need to wrap this into a promise, or return the result with callback. Callback would be like this:
function call(arg, callback) {
client.get("http:////" + arg, function (data, response) {
callback(data.results[0].locations[0].latLng)
});
}
call("yolo", function (parsed) { console.log(parsed) })
promise is well described here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
I'm running the following within node.js:
function makeGetFunction(url) {
url = url + '?' + authstring;
console.log("get " + url);
request.get(url, function(e, r, data) {
var json = JSON.parse(data);
var resp = json.entities;
});
}
I would like to have makeGetFunction() return resp. I think the the answer lies somewhere with using a callback, but as I'm fairly new to asynchronous calls in javascript, I'm stumbling with how to properly pull it off. I haven't been able to find any examples or posts related to my question yet. Please excuse the newbie question.
Since the procedure involves asynchronous calls, the best choice is to follow the same pattern in makeGetFunction, by adding a callback argument:
function makeGetFunction(url, callback) {
url = url + '?' + authstring;
console.log("get " + url);
request.get(url, function(e, r, data) {
var json = JSON.parse(data);
var resp = json.entities;
callback(resp);
});
}
When using makeGetFunction, pass it a handler function instead of retrieving the return value:
makeGetFunction('www.myurl.net/what', function(resp) {
// do stuff with response
});
We are trying to modify an existing script which uses backbone.js to fetch JSON from a URL and render it in a defined way on screen.
Earlier the script was pointing to an external PHP file to fetch the JSON from it.
url: function () {
var ajaxValue = document.getElementById('ajax').value;
if(ajaxValue==0){
return this.options.apiBase + '/liveEvents.json';
} else {
var eventDate = document.getElementById('timestamp').value;
return this.options.apiBase + '/ajax.php?eventDate='+eventDate;
}
},
But now we are trying to omit the requirement of PHP and get JSON purely using Javascript. For this, we created a JS function fetch_data_set(), that returns proper JSON
var ArrayMerge = array1.concat(array2,array3,array4);
return JSON.stringify(ArrayMerge);
So our question is, how can we feed this JSON to backbone instead of using an external URL. Because if we do this (which is obviously wrong):
url: function () {
var ajaxValue = document.getElementById('ajax').value;
if(ajaxValue==0){
var data_set = fetch_data_set();
return data_set;
}
},
It throws error: Error: A "url" property or function must be specified
The main key is to extend Backbone.sync instead of url() method, so you could use this way to fetch your models in any kind of model, and you could do something similar like this link:
https://github.com/huffingtonpost/backbone-fixtures/blob/master/backbone-fixtures.js
Backbone.Model contains a sync() function able to load JSON data from an url. sync() uses the url() function to determine from where it should fetch data. (Note : sync() is called under-the-hood by save(), fetch() and destroy())
The trick here is that you should stop overriding url() and reimplement sync() directly instead, cf. http://backbonejs.org/#Model-sync
Here is an example :
// specialized version to be used with a store.js - like object
sync: function(method, model, options) {
console.log("sync_to_store begin('"+method+"',...) called with ", arguments);
var when_deferred = when.defer();
var id = this.url();
if(method === "read") {
if(typeof id === 'undefined')
throw new Error("can't fetch without id !");
var data = model.store_.get(id);
// apply fetched data
model.set(data);
when_deferred.resolve( [model, undefined, options] );
}
else if(method === "create") {
// use Backbone id as server id
model.id = model.cid;
model.store_.set(id, model.attributes);
when_deferred.resolve( [model, undefined, options] );
}
else if(method === "update") {
if(typeof id === 'undefined')
throw new Error("can't update without id !");
model.store_.set(id, model.attributes);
when_deferred.resolve( [model, undefined, options] );
}
else if(method === "delete") {
if(typeof id === 'undefined')
throw new Error("can't delete without id !");
model.store_.set(id, undefined);
model.id = undefined;
when_deferred.resolve( [model, undefined, options] );
}
else {
// WAT ?
}
console.log("sync_to_store end - Current changes = ", model.changed_attributes());
return when_deferred.promise;
}
Note 1 : API is slightly different from vanilla Backbone since I return
a when promise
Note 2 : url() is still used, as an id
I am using Parse (http://parse.com) inside a Meteor Application (http://meteor.com)
I am trying to query my Parse Database from the server side, and everything is fine until I get to the query.
I get the following error:
[TypeError: Cannot call method 'getItem' of undefined]
This is what my code looks like: [I have even tried query.find()]
var VITxUser = Parse.Object.extend("VITxMaster");
var query = new Parse.Query(VITxUser);
query.equalTo("fbid", "1231212");
//no errors till here
query.first({
success: function(object) {
if (!object){
//insert the user
var GameScore = Parse.Object.extend("VITxMaster");
var gameScore = new GameScore();
gameScore.set("fbid", profile.id);
gameScore.set("registrationNumber", "12DEV0000");
gameScore.set("VITevents", "true");
gameScore.save(null, {
success: function(gameScore) {
// Execute any logic that should take place after the object is saved.
alert('New object created with objectId: ' + gameScore.id + 'and fbid: ' + profile.id);
},
error: function(gameScore, error) {
// Execute any logic that should take place if the save fails.
// error is a Parse.Error with an error code and description.
alert('Failed to create new object, with error code: ' + error.description);
}
});
}
else{
console.log("found object");
console.log(object.get("registrationNumber"));
}
}
});
I can't see a reference to getItem in your code. I suspect however that the issue is due to meteor's variable scoping. Basically in Meteor each file is variable scoped. So you if you had two files file1.js and file2.js they would be wrapped around in a function(){..}.
You would need to remove the variable scoping by not using var to define your variables. Particularly the one's you want to be accessible globally (in other files)
I am writing a Javascript SDK to interact with a web service. I am using jQuery to do my AJAX calls.
When an AJAX call fails, I have registered an event handler for the ajaxError that gets called at the top of my .js file. My problem, and I don't understand why, is that when it gets called I have no way of accessing class member variables for my Akamanda.Client.
I tried adding another method for Akamanda.Client as .prototype.logError, which got called by the jQuery Ajax handler, but even then a test for (this.logging) failed as well.
How can I access class member variables from jQuery callbacks? What am I failing to understand here? Akamanda.Client.logging is undefined from the ajaxError callback.
My code for the SDK:
$(document).ajaxError(function(event, jqxhr, settings, exception) {
// more robust error handling for different conditions
if (Akamanda.Client.logging) {
console.log('FAILED: ' + settings.type + ' ' + settings.url + ' => ' + exception);
}
});
Akamanda.Client = function(options) {
this.URL = options.URL || 'http://m-test.akamanda.com';
this.baseURL = this.URL + '/api/' + Akamanda.API_VERSION;
this.feedsURI = '/websyndication/feed/';
// who is the client? (iphone/android/web)
this.clientName = options.clientName;
// For development: Logging and buildcurl IS ON, for production: OFF
//this.logging = options.logging || true;
this.logging = true;
// called when a user is not authorised (Disabled)
// this.logoutCallback = options.logoutCallback || null;
}
Akamanda.Client.prototype.getFeeds = function(callback){
var feeds = [];
$.getJSON(this.baseURL + this.feedsURI, function(data) {
$.each(data, function(index, feed) {
feeds[index] = {
name: feed.name,
title: feed.title,
link: feed.link
};
})
callback(feeds);
});//.error(function(err) { (disabled at the moment in favour of ajaxError event)
// console.log('Error: ' + err.error);
// });
}
My code for the client (in another JS source file):
var options = { logging: true };
myAPI = new Akamanda.Client(options);
var feeds = [];
var articles = [];
function getFeeds()
{
myAPI.getFeeds(function(AkamandaFeeds) {
feeds = AkamandaFeeds;
showFeeds();
});
}
As far as I can see from the code you posted, you are never instantiating an object of type Akamanda.Client.
var Client = new Akamanda.Client();
or
var Akamanda.Client = {};
Akamanda.Client.logging = ....
JSBin Example: http://jsbin.com/ajidig/1/edit
Ok, here a little example(real code but very simplified):
//we wrap our code in a self invoking function so that we don't pollute the global namespace, see http://stackoverflow.com/questions/6715805/self-invoking-functions-javascript for further details
(function(){
//create your object that holds all your function, that are different ways to do this
var Akamanda = {};
//a private function
function ErrorHandler(clientObj) {
this.clientObj = clientObj;
//do whatever with clientObj
this.log = function(){..}
}
//private constructor for clientobj
function Client(options){
..
}
Akamanda.Client = function(){
var newClient = new Client({..});
//setup
Akamanda.ErrorLogging = new ErrorHandler(newClient);
return newClient;
}
//bind our service to the window object to make it accesible
window.Akamanda = Akamanda;
})()
//client
var myAPI = Akamanda.Client();
Akamanda.ErrorLogging.log();
I hope this basic examples helps. If you need to know more about Javascript Patterns, I can recommend this book http://jsninja.com/ by John Resig, the creator of jQuery.
Depending on what you want to do, there's also a lot of frameworks like http://backbonejs.org/ that help with this kind of stuff.