Calling a Facebook batch call using Appcelerator Module.Facebook - javascript

I spend about 2 hrs looking to do a batch call using Appcelerator Module.Facebook. I needed to get the Profile name and Picture. And I thought I wanted to do this in one HTTP request instead of two.
After a deep dive i finally found a way to do. I will post my answer in the answer section below.
Incase anyone else comes up against this..

var fb = require('facebook');
var log = require("log");
...
// After logging in
// gets user profile image and name uses a batch method example
function getUserInfo(userId) {
var batch = 'batch=[{"method":"GET", "relative_url":"me"},{"method":"GET", "relative_url":"me/picture"}]&access_token=' + fb.getAccessToken();
var url = "https://graph.facebook.com?" + batch;
var client = Ti.Network.createHTTPClient({
// function called when the response data is available
onload : function(e) {
log.args(this.responseText);
},
// function called when an error occurs, including a timeout
onerror : function(e) {
log.args(e.error);
},
timeout : 5000 // in milliseconds
});
// Prepare the connection.
client.open("POST", url);
// Send the request.
client.send();
}
This is how you would do a batch call using AppC, however there is a better way to get the info that i needed. In my case I only needed the Facebook name and picture
function getGraphPath(userId) {
fb.requestWithGraphPath('me?fields=id,name,picture', {}, 'GET', function(e) {
if (e.success) {
log.args('Modules.Facebook.requestWithGraphPath', JSON.parse(e.result));
} else if (e.error) {
log.args(e.error);
} else {
log.args('Unknown response');
}
});
}

Related

ag-grid + templateUrl crashes in IE9

I just implemented ag-grid, but found that IE9 crashes when using cellTemplates with angular compiled templates inside.
Did any of you encounter this and maybe found a workaround?
How to reproduce:
Head here (http://www.ag-grid.com/angular-grid-cell-template/index.php) with IE, and from DevTools, select IE9.
It will crash because of the angular compiled templates. Not sure what I can do about it.
(I also opened an issue on GitHub on this: https://github.com/ceolter/ag-grid/issues/521 )
EDIT:
Debugged it, there's an infinite loop because an update to an array from one method, is not visible to another method somehow...
The infinite loop is:
getTemplate, (wait in line until the call ends), call ends, template added to cache, run callback, callback doesn't see the template in templateCache, creates another callback, adds it to the queue, and so on.
(code from ag-grid below).
// returns the template if it is loaded, or null if it is not loaded
// but will call the callback when it is loaded
TemplateService.prototype.getTemplate = function (url, callback) {
var templateFromCache = this.templateCache[url];
if (templateFromCache) {
return templateFromCache;
}
var callbackList = this.waitingCallbacks[url];
var that = this;
if (!callbackList) {
// first time this was called, so need a new list for callbacks
callbackList = [];
this.waitingCallbacks[url] = callbackList;
// and also need to do the http request
var client = new XMLHttpRequest();
client.onload = function () {
that.handleHttpResult(this, url);
};
client.open("GET", url);
client.send();
}
// add this callback
if (callback) {
callbackList.push(callback);
}
// caller needs to wait for template to load, so return null
return null;
};
TemplateService.prototype.handleHttpResult = function (httpResult, url) {
if (httpResult.status !== 200 || httpResult.response === null) {
console.warn('Unable to get template error ' + httpResult.status + ' - ' + url);
return;
}
// response success, so process it
this.templateCache[url] = httpResult.response;
// inform all listeners that this is now in the cache
var callbacks = this.waitingCallbacks[url];
for (var i = 0; i < callbacks.length; i++) {
var callback = callbacks[i];
// we could pass the callback the response, however we know the client of this code
// is the cell renderer, and it passes the 'cellRefresh' method in as the callback
// which doesn't take any parameters.
callback();
}
if (this.$scope) {
var that = this;
setTimeout(function () {
that.$scope.$apply();
}, 0);
}
};
return TemplateService;
})();
I eventually found the issue.
In IE9, the template is on responseText inside the response.
In IE10+ and all other browsers it's on response.
So in order to fix it, in the above code, instead of:
// response success, so process it
this.templateCache[url] = httpResult.response;
I added:
// response success, so process it
//in IE9 the response is in - responseText
this.templateCache[url] = httpResult.response || httpResult.responseText;
For future reference, adding the answer here.
Had nothing to do with Angular. :)
UPDATE:
https://github.com/ceolter/ag-grid/issues/521
Code got into the repo :)
Thanks Niall Crosby (ceolter).

How to keep "response" out of FB.api

I am just trying FB JS api and want to know whether or how I can still use "response" out of FB.api. For example:
var picture;
FB.api('/me/picture?width=180&height=180', function (response) {
picture = response.data.url;
console.log(picture);
});
alert(picture);
The above code will show "undefined" in alert window.
Is there a way to use "response.data.url" out of FB.api?
Thanks
Update:
Here is the big picture: I need retrieve some information from FB user account, such as /me/name, /me/address/city, /me/picture.data.url and group them together and then send the information to server through AJAX.
var name;
var city;
var picture;
FB.api('/me', function (response) {
name = response.name;
FB.api('/me/address', function (adrresponse) {
city = adrresponse.city;
}
FB.api('/me/picture', function (imgresponse) {
picture = imgresponse.data.url;
}
//since FB API is async, the following is not correct!!!
var params = "name="+name+"&city="+city+"&picture="+picture;
//send out through AJAX.
var http = new XMLHttpRequest();
http.open("POST", url, true);
}
Is there a better way to finish the above job?
Update 2:
The best way is to use fields expansion
https://developers.facebook.com/docs/graph-api/using-graph-api/v2.3#fieldexpansion, as shown by the answer of this question.
Thanks
Derek
The problem is the picture variable is not populated at the time that the alert fires. It will only be populated after the FB.api callback completes.
var picture;
FB.api('/me/picture?width=180&height=180', function (response) {
picture = response.data.url;
// this would work correctly
alert(picture);
});
What are you attempting to do with the picture variable? Perhaps you should call a function do something with the picture inside your callback:
var picture;
FB.api('/me/picture?width=180&height=180', function (response) {
picture = response.data.url;
doSomethingWithPicture(picture);
});
Update
The simple way to achieve what you are after is this:
FB.api('/me', function (response) {
var name = response.name;
FB.api('/me/address', function (adrresponse) {
var city = adrresponse.city;
FB.api('/me/picture', function (imgresponse) {
var picture = imgresponse.data.url;
doAjax(name, city, picture);
}
}
}
function doAjax(name, city, picture) {
//since FB API is async, the following is not correct!!!
var params = "name="+name+"&city="+city+"&picture="+picture;
//send out through AJAX.
var http = new XMLHttpRequest();
http.open("POST", url, true);
}
However, this is not ideal as you have to wait for /me/address before you can call /me/picture.
Here are some other options
you need to call /me first.
you fire off both api calls and execute code when the both complete
Ways to accomplish #2
You could then use a promise library to chain the /me/address and /me/picture/. See: https://github.com/kriskowal/q or https://api.jquery.com/category/deferred-object/ to get started
Call a callback after each that conditionally fires the ajax if both address and picture are set
I am sure there are a number of other ways:
How to chain ajax requests?
How to chain ajax calls using jquery
Update #2
This is the best way to accomplish what you are after (no additional callbacks required)
FB.api('/me', {fields: ['first_name', 'last_name', 'picture', 'address']}, function(response) {
// response will now have everything you need
console.log(response);
});
I did not give this answer originally as it was not the topic of the question which seemed to be scoping.

Live title with dynamic update (AJAX+jQuery)

I have a website, where users can get inbox messages and notifications while they are on the website. (Like on facebook, you see (1) at the begining of the tile as you have notification)
Currently I have an ajax request which grabs the data the title has to show. It works liek charm but the issue is that this file is called every 10 seconds. If user has 10 page tabs though, this file is called 10x10=100 times.. if my site has thousand users, you understand how much load it would generate.
I though of running the javascript on active tab only but how can I update the title of all opened tabs of my website? Any other suggestion?
Here is my code
var oldtitle=$(document).attr("title");
var checker=function(){
$.ajax({
url : 'live_title.php',
type : 'POST',
success : function(data) {
... code ....
... code ....
... code ....
if (sum>0) {
$(document).attr("title", "("+sum+") "+oldtitle);
}
}
});
}
setInterval(checker,20000);
checker();
A cache mechanism seems the right way to go.
First idea: use HTTP caching
Be sure to add a parameter as a query string with the current timestamp rounded to the previous 10th of second.
Be sure your web server sends the correct header for the HTTP cache to work. It's best with a GET request.
Example:
$.ajax({
url : 'live_title.php',
type : 'GET',
success : function(data) {
// code
},
data: {t: Math.floor((+new Date())/10000)}
}
// we send a request similar to live_title.php?t=142608488
Second idea: use window.localStorage as a secondary local cache.
Additionnaly to the first idea:
var getCache = function(t) {
if (window.localStorage) {
var liveTitle = localStorage.getItem('liveTitle') || {};
return liveTitle[t] || null;
}
};
var setCache = function(t, data) {
if (window.localStorage) {
window.localStorage.setItem('liveTitle', {t:data});
}
}
var run = function() {
var t = Math.floor((+new Date())/10000);
var cache = getCache(t);
var success = function(data) {
/*code*/
};
if (cache) {
success(cache);
}
else {
$.ajax({
url : 'live_title.php',
type : 'GET',
success : function(data) {
setCache(t, data);
success(data);
},
data: {t: t}
}
}
}
I don't think you can do what you want easily.
Moreover to optimize that, I would recommend to use cache :
One time a tab calls the method which count the messages, do the query and cache the result to a simple file or in memory
during the next 5 minutes, each time a tab calls the method, use the cache and do not query the database
when the 5 minutes are passed, do again a query, cache it and so on.
Like this, on 100 calls, you have only 1 big request, others are like requesting a js or img files

Chrome extension: Not downloading after clicking button

I'm trying to download a file to my downloads file. I'm creating a button on the web-page dynamically and id like to download something when that button is pressed. For what ever reason when I click the button nothing happens and I don't know why. Please help
background.js code
function SendRequest(url, callback){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
callback(xhr.responseText);
}
};
xhr.open("GET", url, true);
xhr.send();
}
var objurl = localStorage.getItem("OBJURL");
function EditContent(objurl){
chrome.downloads.download({url:objurl,filename:"Object Edit - Chrome Extension.rbxl",conflictAction:"overwrite"})
}
item.js
contentInput.onclick = function(){
var assetid = $('.thumbnail-span').attr("data-3d-url")
var baseurl = 'http://www.roblox.com'
SendRequest(baseurl + assetid, function (response) {
var response = response; //Easy Peasy
var jsonObject = JSON.parse(response); //Parse the response
localStorage.setItem("URL1", jsonObject.Url); //It's saved!
var test = localStorage.getItem("URL1"); //Let's grab it and save it to a variable
console.log(test); //Logs "Hello World!"
});
var url1 = localStorage.getItem("URL1");
SendRequest(url1, function (response1) {
var response = response1; //Easy Peasy
var jsonObject = JSON.parse(response); //Parse the response
localStorage.setItem("OBJ", jsonObject.obj); //It's saved!
});
var hashdecode = "http://roblox.com/thumbnail/resolve-hash/"
var objhash = localStorage.getItem("OBJ");
SendRequest(hashdecode + objhash, function (objresponse) {
var response = objresponse; //Easy Peasy
var jsonObject = JSON.parse(response); //Parse the response
localStorage.setItem("OBJURL", jsonObject.Url); //It's saved!
});
chrome.extension.sendRequest({
action: "EditContent",
})
}
Oh boy. There's a lot wrong here. And I mean a lot.
chrome.extension.sendRequest is deprecated. Use chrome.runtime.sendMessage instead.
That said, you're sending the message, but nothing actually listens to it. action: "something" doesn't mean anything a priori, you need to react to that yourself:
// background.js
chrome.runtime.onMessage.addListener(
function(message, sender, sendResponse){
if(message.action == "EditContent") {
/* do stuff */
}
}
);
localStorage is NOT shared between the content script and the background script. In fact, background script has its own copy of localStorage (bound to chrome-extension://whateverisyourid/ domain) and the content script shares it with the page.
You should either use chrome.storage, which is shared but works differently, or pass what you need in the message, i.e. a message like:
{action: "EditContent", objurl: jsonObject.Url}
And use it:
chrome.runtime.onMessage.addListener(
function(message, sender, sendResponse){
if(message.action == "EditContent") {
EditContent(message.objurl)
}
}
);
A function defined in the background script (SendRequest) cannot be called in the content script. You need to move it to the content script, or call it from the background script.
Your SendRequest is asynchronous. If you write something like:
function f(){
action1();
SendRequest(url1, function(response){
action2();
});
action3();
SendRequest(url2, function(response){
action4();
});
action5();
}
f();
then this is what will happen:
action1()
SendRequest will queue a request with url1, but not wait for it
action3()
SendRequest will queue a request with url2, but not wait for it
action5()
Your function f() terminates, next in queue is the first request.
When the request finishes, action2() is run.
Next in queue is the second request.
When the request finishes, action4() is run.
This might even get swapped, I suppose, depending on which request finishes first.
You see the problem? You need to chain asynchronous calls instead:
function f(){
action1();
SendRequest(url1, function(response){
action2();
action3();
SendRequest(url2, function(response){
action4();
action5();
});
});
}
This might not be the full list of problems, and certainly is not a full working code.
Please, please debug your extensions next time. You can access errors from the content script in the page's console (Ctrl+Shift+J), and background page's console from chrome://extensions in Developer Mode.

Dynamically append content to an element with jQuery

I would like to be able to have an ajax get update the text in a span tag each time it is fired.
$.ajax({
type: 'GET',
url: "JSON URL",
cache: false,
contentType: 'application/json',
dataType: 'json',
success: function(html){
$('#status_frame_span').prepend(html.status)
alert(html.status)
},
error: function(jq,stats,errmes) {
alert("Error" + errmes);
}
});
the first time it fires, the content of the json returned from the URL is properly prepended to the span. however for subsequent firings it is not updated.
How do I ensure that with each firing the content gets updated?
What triggers the call to the server? Is it a button or link inside of the HTML being updated? if it is, the event handler may be lost when the UI is updated. Or, something else is losing the event handler, which doesn't call the method to fire the get request, etc.
HTH.
Of course your view is updated only once: you are calling the server only once!
If, as your tags suggest, you are using long polling (please make sure that's the case, I'm not sure you have a very clear idea of what is an event, a poll and a distant call), then you need to make a new request each time you've received one!
In both your success and error handlers, you have to recursively make an AJAX call to the server. You also have to set a timeout for the calls, which could cancel them and start a new one after, for example, 30 seconds.
You should also implement some kind of throttling for recursive calls, unless you're 99.99% sure the server page will never send errors. Otherwise, you'll kill your client.
For the sake of completeness, I have to add this would be a great use-case for HTML5 SSE or WebSocket. But they're not ready for production usage yet.
it does not work that way - if the success callback is called - the connection has been closed so your long polling will be dead once the request is completed.
The idea behind long polling is that you keep the connection alive. Configure your server properly so that it will hold the connection open as long as possible (set timeout as high as possible).
Here's an approach from my coffee break (not tested):
Server
Every message has to end with the delimiter ::PART::
The server must be properly configured this means set the timeout as high as possible!
Client (Browser)
// setup longpoll, check all 250ms for new data in the stream
var myPoller = new LongPoll('some-url', 250);
// bind connection lost
myPoller.bind('longpoll:end', function(evt) {
alert('connection lost - trying reconnect');
});
// bind error event
myPoller.bind('longpoll:error', function(evt, errmsg) {
alert('error: ' + errmsg);
});
// bind data event
myPoller.bind('longpoll:data', function(evt, data) {
try {
// try to parse json
data = $.parseJSON(data);
// prepend
$('#status_frame_span').prepend(data.status);
} catch(e) {
// invalid json
alert('invalid json: ' + data);
}
});
longpoll.js
var LongPoll = function(url, timeout) {
// url we connect to
this.url = url;
// running?
this.isRunning = false;
// timer for checking the stream
this.timer = null;
// the length of the received data
this.dataLength = 0;
/*
The messages has to be delimited by the delimiter like:
first data::PART::second data::PART::third data::PART::
*/
this.delimiter = RegExp.new("::PART::", 'gm');
// residue from previous transmission
this.residue = ''
};
// connect to server
LongPoll.prototype.connect = function() {
var self = this;
// reset data length
this.dataLength = 0;
// reset residue
this.residue = '';
// start ajax request
this.xhr = $.ajax({
type: 'GET',
url: this.url,
cache: false,
contentType: 'application/json',
dataType: 'text',
success: function(){
// the connection is dead!
self.xhr = null;
// trigger event
$(self).trigger('longpoll:end');
// reconnect if still running
if(self.isRunning) {
self.connect();
}
},
error: function(jq,stats,errmes) {
// stop timer and connection
self.stop();
$(self).trigger('longpoll:error', errmes);
}
});
};
// process data
LongPoll.prototype.process = function(buffer) {
var self = this;
// check if there is anything new
if(buffer.length > this.dataLength) {
var newData = this.residue + buffer.substring(this.dataLength, buffer.length);
// reset residue
this.residue = '';
// store the new position
this.dataLength = buffer.length;
// split data
var dataParts = newData.split(this.delimiter);
// how many full parts?
var fullParts = newData.match(this.delimiter).length;
if(dataParts.length > fullParts) {
// pop residue (incomplete message)
this.residue += dataParts.pop();
}
$.each(dataParts, function(index, part) {
// broadcast data parts
$(self).trigger('longpoll:data', $.trim(data));
});
}
};
// check for data
LongPoll.prototype.receive = function() {
var self = this;
// connection still there?
if(this.xhr) {
// process buffer
this.process(this.xhr.responseText);
}
};
// start long poll
LongPoll.prototype.start = function() {
var self = this;
// set flag
this.isRunning = true;
this.timer = setInterval(function() { self.receive(); }, this.timeout);
this.connect();
};
// stop long poll
LongPoll.prototype.stop = function() {
// set flag
this.isRunning = false;
// clear timer
clearInterval(this.timer);
if(this.xhr) {
// abort request
this.xhr.abort();
}
};

Categories