How does paging in facebook javascript API work? - javascript

I'm trying to recover the last week posts in my facebook news feed with the javascript sdk.
I'm able to get the first page but then, I don't know how to continue iterating through the other pages. I've tried it with the following code:
$('#loadPosts').bind('click', function() {
FB.api('/me/home',{since:'last week'}, getPosts);
});
getPosts = function(response){
for (element in response.data){
post = response.data[element]
console.log(post);
}
previousPage = response.paging.previous;
console.log(previousPage);
// can i call FB.api(previousPage, getPosts); ??
}
But I'm getting a URL as previous page and I don't know how to make a javascript FB.api call from that URL. Any ideas?

Alright, it seems a lot of whining about a simple issue that I still believe my old answer clarifies. Anyway, let me babysit you. :)
First: I find out that you cannot really go to the "previous" page from the first page. Ideally, I should. So, here is a bug that I have filed you may want to follow: https://developers.facebook.com/bugs/391562790938294?browse=search_50fcac3ce094e7068176315
Second: If this is by design, you cannot go back to "previous" from the first page (because there is no previous), but you can surely go to "Next". However, since the API behaves as a cursor, and you have moved forward, now your "previous" page would work.
The answer to the question:
I'm getting a URL as previous page and I don't know how to make a javascript FB.api call from that URL. Any ideas?
yes, you can make FB.api call. But I suggest you to make a HTTP GET call instead, because it's easier. Also, note that previous may return and empty array like {"data":[]}
How to get previous/next page?
Here, I am writing a small code that uses jQuery. In case you do not want to read the code, there are two ways:
Use previous/next URL and make HTTP GET request. Which, if not empty, will come with next set of "previous", "next" link.
Parse the URL, and get the query-string as JSON ans pass it to FB.api. I used jQuery BBQ pluging for parsing.
Important Note: In the example, I am using "next" URL because on the first request if I use "previous" it gives empty JSON instead of giving posts from the past. However, I can use use "previous" URL once I have moved ahead a few pages. Like Google results, you can not go previous on page 1, but you can from any page > 1 (see Example 3 below). This is called pagination.
Example 1: Code using HTTP GET (preferred): (I will load 3 posts/page and look three next-pages)
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script type="text/javascript" src="http://connect.facebook.net/en_US/all.js"></script>
<script type="text/javascript" src="https://raw.github.com/cowboy/jquery-bbq/master/jquery.ba-bbq.min.js"></script>
<script>
var i =0;
var getPosts = function (response){
for (element in response.data){
post = response.data[element]
console.log(post.id + ": " +post.message);
}
// can i call FB.api(nextPage, getPosts); ??
if(i < 2){
nextPage = response.paging.next;
console.log(nextPage);
i++;
//Method 1: I use it.
$.get(nextPage, getPosts, "json"); //optional: $.getJSON can be use instead
}
}
$(document).ready(function(){
$('#loadPosts').bind('click', function() {
FB.api('/me/home',{since:'yesterday','limit': '3'}, getPosts);
});
})
</script>
</head>
<body>
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function() {
// init the FB JS SDK
FB.init({
appId : 'XXXXXXXXXXXX', // FILL YOUR APP ID HERE!
status : true, // check the login status upon init?
cookie : true, // set sessions cookies to allow your server to access the session?
});
// Additional initialization code such as adding Event Listeners goes here
};
</script>
<button id="loadPosts">Load Posts</button>
<p>Please open developer console to see what's happening. In Firefox, you can use ctrl+shift+k, and in Chrome/Chromium use ctrl+shift+i</p>
</body>
</html>
Response:
100004192352945_156620584487686: undefined
137723270230_10152423499430231: On this day, please spare a thought for those fellow citizens, for whom I just spare a thought and do nothing else.
642965867_10151211036740868: Thanks everyone for their wishes! The wishes made my day!
https://graph.facebook.com/677811901/home?limit=3&access_token=AAACYjXGS5FQBAIR3brc2LibjBcZCi2kRJUybG8VMaaJSZARQ8SzNE7BE4PBrDIFVZB0AaVEa1dZCpX1fhCvoD2rnq8uc8OGaIFhO9uvVXAZDZD&until=1359184568
367116489976035_536776529676696: Rage. Quit. Life.
899605553_10152450871820554: undefined
367116489976035_417820828298092: undefined
https://graph.facebook.com/677811901/home?limit=3&access_token=AAACYjXGS5FQBAIR3brc2LibjBcZCi2kRJUybG8VMaaJSZARQ8SzNE7BE4PBrDIFVZB0AaVEa1dZCpX1fhCvoD2rnq8uc8OGaIFhO9uvVXAZDZD&until=1359179890
137723270230_10152423148745231: Pratibha Patil used to love the Republic Day Parade, especially the part where the visiting Chief Guest extended her an invitation to visit his/her own country.
137723270230_10152423131700231: The Kingfisher tableau at Republic Day Parade was so simple. Vijay Mallya riding a bicycle.
367116489976035_484460034950769: undefined
Example 2: Code using FB.api: (I will load 3 posts/page and look three next-pages)
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script type="text/javascript" src="http://connect.facebook.net/en_US/all.js"></script>
<script type="text/javascript" src="https://raw.github.com/cowboy/jquery-bbq/master/jquery.ba-bbq.min.js"></script>
<script>
var i =0;
var getPosts = function (response){
for (element in response.data){
post = response.data[element]
console.log(post.id + ": " +post.message);
}
// can i call FB.api(nextPage, getPosts); ??
if(i < 2){
nextPage = response.paging.next;
console.log(nextPage);
i++;
//Method 2: If you have to call FB.api
var params = jQuery.deparam.querystring(nextPage);
console.log(JSON.stringify(params, null, 2));
FB.api('/me/home', params, getPosts)
}
}
$(document).ready(function(){
$('#loadPosts').bind('click', function() {
FB.api('/me/home',{since:'yesterday','limit': '3'}, getPosts);
});
})
</script>
</head>
<body>
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function() {
// init the FB JS SDK
FB.init({
appId : 'XXXXXXXXXXXX', // FILL YOUR APP ID HERE!
status : true, // check the login status upon init?
cookie : true, // set sessions cookies to allow your server to access the session?
});
// Additional initialization code such as adding Event Listeners goes here
};
</script>
<button id="loadPosts">Load Posts</button>
<p>Please open developer console to see what's happening. In Firefox, you can use ctrl+shift+k, and in Chrome/Chromium use ctrl+shift+i</p>
</body>
</html>
Response:
367116489976035_536776529676696: Rage. Quit. Life.
899605553_10152450871820554: undefined
367116489976035_417820828298092: undefined
{
"limit": "3",
"access_token": "AAACYjXGS5FQBAIR3brc2LibjBcZCi2kRJUybG8VMaaJSZARQ8SzNE7BE4PBrDIFVZB0AaVEa1dZCpX1fhCvoD2rnq8uc8OGaIFhO9uvVXAZDZD",
"until": "1359179890"
}
137723270230_10152423148745231: Pratibha Patil used to love the Republic Day Parade, especially the part where the visiting Chief Guest extended her an invitation to visit his/her own country.
137723270230_10152423131700231: The Kingfisher tableau at Republic Day Parade was so simple. Vijay Mallya riding a bicycle.
367116489976035_484460034950769: undefined
https://graph.facebook.com/677811901/home?limit=3&access_token=AAACYjXGS5FQBAIR3brc2LibjBcZCi2kRJUybG8VMaaJSZARQ8SzNE7BE4PBrDIFVZB0AaVEa1dZCpX1fhCvoD2rnq8uc8OGaIFhO9uvVXAZDZD&until=1359178140
{
"limit": "3",
"access_token": "AAACYjXGS5FQBAIR3brc2LibjBcZCi2kRJUybG8VMaaJSZARQ8SzNE7BE4PBrDIFVZB0AaVEa1dZCpX1fhCvoD2rnq8uc8OGaIFhO9uvVXAZDZD",
"until": "1359178140"
}
655515199_403590309726450: a good resolution to take on Republic Day
505588854_496901583686790: Love the secret world that slow motion reveals.
693811975_10151217837201976: undefined
Example 3: Performing: page1 -> page2 -> page1 or page -> next -> previous The following code will load page1, then go to "next" page (page2), then come back to page1, using "previous"
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script type="text/javascript" src="http://connect.facebook.net/en_US/all.js"></script>
<script type="text/javascript" src="https://raw.github.com/cowboy/jquery-bbq/master/jquery.ba-bbq.min.js"></script>
<script>
var i =0;
var getPosts = function (response){
for (element in response.data){
post = response.data[element]
console.log(post.id + ": " +post.message);
}
// can i call FB.api(nextPage, getPosts); ??
if(i < 2){
nextPage = response.paging.next;
if(i==1)
nextPage = response.paging.previous;
console.log(nextPage);
i++;
$.get(nextPage, getPosts, "json"); //optional: $.getJSON can be use instead
}
}
$(document).ready(function(){
$('#loadPosts').bind('click', function() {
FB.api('/me/home',{since:'yesterday','limit': '3'}, getPosts);
});
})
</script>
</head>
<body>
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function() {
// init the FB JS SDK
FB.init({
appId : 'XXXXXXXXXXXX', // FILL YOUR APP ID HERE!
status : true, // check the login status upon init?
cookie : true, // set sessions cookies to allow your server to access the session?
});
// Additional initialization code such as adding Event Listeners goes here
};
</script>
<button id="loadPosts">Load Posts</button>
<p>Please open developer console to see what's happening. In Firefox, you can use ctrl+shift+k, and in Chrome/Chromium use ctrl+shift+i</p>
</body>
</html>
Response:
PAGE1:
367116489976035_536806916340324: How big is the Solar System?
Full infographic here: http://bit.ly/WmzfVn
137723270230_10152423534790231: "Sociologist" Ashis Nandy has claimed that most of the corrupt came from OBC/SC/ST castes.
Following this, Corrupt people have strongly condemned Nandy's attempts to divide them on caste lines. They'll be united in loot, forever.
100004192352945_156620584487686: undefined
PAGE2:
https://graph.facebook.com/677811901/home?limit=3&access_token=AAACYjXGS5FQBAKqIMyCVYjH9upK4e2bjUwLoVbbFDL0ffc0SZBTVR9MUFGV4ZCq6HBdFIadFMpLDC3ATMZCJ4GPsXWpG4qTGODavuvzLAZDZD&until=1359185659
137723270230_10152423499430231: On this day, please spare a thought for those fellow citizens, for whom I just spare a thought and do nothing else.
642965867_10151211036740868: Thanks everyone for their wishes! The wishes made my day!
367116489976035_536776529676696: Rage. Quit. Life.
PAGE1:
https://graph.facebook.com/677811901/home?limit=3&access_token=AAACYjXGS5FQBAKqIMyCVYjH9upK4e2bjUwLoVbbFDL0ffc0SZBTVR9MUFGV4ZCq6HBdFIadFMpLDC3ATMZCJ4GPsXWpG4qTGODavuvzLAZDZD&since=1359185123&__previous=1
367116489976035_536806916340324: How big is the Solar System?
Full infographic here: http://bit.ly/WmzfVn
137723270230_10152423534790231: "Sociologist" Ashis Nandy has claimed that most of the corrupt came from OBC/SC/ST castes.
Following this, Corrupt people have strongly condemned Nandy's attempts to divide them on caste lines. They'll be united in loot, forever.
100004192352945_156620584487686: undefined
OLD ANSWER
Use limit, offset, since and until parameters to achieve your goal.
Refer: http://developers.facebook.com/docs/reference/api/
Paging
When querying connections, there are several useful parameters that enable you to filter and page through connection data:
limit, offset: https://graph.facebook.com/me/likes?limit=3
until, since (a unix timestamp or any date accepted by strtotime): https://graph.facebook.com/search?until=yesterday&q=orange
The following should get all the posts since last week until yesterday from 21st - 30th message (basically, third page of 10 message per page pagination).
FB.api(
'/me/home',
{
'since':'last week',
'limit': '10',
'offset': '20',
'until': 'yesterday'
},
getPosts
);
I've just tested, it works. I have used limit=4, which is page-size kind of thing. So, when I fetch data since Feb 02, 2011 (Unix Time Stamp: 1296626400) till today using this
https://graph.facebook.com/me/home?access_token=[AUTH_TOKEN]&since=1296626400&limit=4
It returns the data, plus it also return URL to go to next page
{
"data": [
<ARRAY OF POSTS HERE>
],
"paging": {
"previous": "https://graph.facebook.com/me/home?access_token=[NEW_AUTH_TOKEN]&since=1298026753&limit=4",
"next": "https://graph.facebook.com/me/home?access_token=[NEW_AUTH_TOKEN]&limit=4&until=1298023222"
}
}
You can safely use previous and next attributes of the JSON object to jump to next page (or previous page). This seems to be the easiest way to do.
By the way, \u00257C needed to be converted to | to get this to work.

If you simply wanted to get the next page (using the paging.next object) you could do a jQuery.getJSON request. Something like the following:
function loadAlbums(){
FB.api('/me/albums', function(response){
handleAlbumsResponse(response);
});
}
function handleAlbumsResponse(response){
var albums = response.data;
for( var i in albums){
var album = albums[i];
$('#albums ul').append('<li>' + album.name + '</li>');
}
if( response.paging.next){
console.log('fetching next page...');
$.getJSON(response.paging.next, function(response){
handleAlbumsResponse(response);
});
}
}

The key constraint in your question is we can't use the 'next' url provided in the response.
I'll try to answer your question by first asking a more general question:
How can we create a user experience for our Facebook app where every call for more items returns the same amount of items.
If the user requests 'more' and gets 10 items, presses 'more' and gets then 4, then 7 etc, she might think our app is buggy.
On the Open Graph intro page, different parameters for paging are introduced. These are:
limit
offset
until
since
as mentioned under the 'paging' heading. However if we implement a solution with limit and offset where we increment offset ,e.g.:
https://graph.facebook.com/me/home?limit=10&offset=OFFSET
where OFFSET will be increased by the limit each request, we find that the number of results returned will sometimes not be equal to the “limit” parameter we specified. This is because parameters are applied on Facebook's side before checking if the queried results are visible to the viewer. We ask for 10, but we might get 8 items in return.
This means we can't use a solution where we increment limit and offset if we want our app's 'more' request to always return the same amount of items.
A solution proposed in this blog by Jeff Bowen (who works on the Facebook plaform team) is this logic:
request item with limit = YOUR_LIMIT.
retrieve the created_time field of the last item in the response.
request next 10 items with since = RETRIEVED_CREATED_TIME and limit=YOUR_LIMIT
Here's a code sample, based on an example in the blog post mentioned above:
var graphURL = "https://graph.facebook.com/me/home?" +
"callback=processResult&" +
"date_format=U&" +
"limit=10";
function loadPosts() {
var script = document.createElement("script");
script.src = graphURL;
document.body.appendChild(script);
}
function processResult(posts) {
if (posts.data.length == 0) {
document.getElementById("loadMore").innerHTML =
"No more results";
}
else {
graphURL = graphURL + "&until=" +
posts.data[posts.data.length-1].created_time;
for (var post in posts.data) {
var message = document.createElement("div");
message.innerHTML = posts.data[post].message;
document.getElementById("content").appendChild(message);
}
}
}
This solution retrieves the next 10 items from the user's newsfeed in chronological order without using the url in the JSON response.

It's working
function friends_list()
{
for (var x = 0; x<500; x++)
{
FB.api(
'/me/friendlists/',
'GET',
{"fields":"id","offset":x},
function(response) {
for (i = 0; i < response.data.length; i++)
{
document.getElementById("friends_list").innerHTML =
document.getElementById("friends_list").innerHTML + "<br>" + response.data[i].id;
}
document.getElementById("friends_list").innerHTML =
document.getElementById("friends_list").innerHTML + "<br>" ;
}
);
}
}

I noticed the question is very old. My answer is true for the these days FB jsSDK (2017) :)
Actually it's simpler than what predecessors described and somewhat intuitive. FB jsSDK it is an API itself and it is expected to be able to navigate through pages of the response by itself and using same means, no?
function getPosts(){
FB.api('/me/posts', 'GET', {limit:250}, cbGetPosts);
}
function cbGetPosts(response){
// do your stuff with response.data
if(response && response.paging){
FB.api(response.paging.next, cbGetPosts); // yep, is this simple
}
}
Obviously this will request for next pages as long as there is a next key defined but s proving the concept.

Related

ReportViewer Web Form causes page to hang

I was asked to take a look at what should be a simple problem with one of our web pages for a small dashboard web app. This app just shows some basic state info for underlying backend apps which I work heavily on. The issues is as follows:
On a page where a user can input parameters and request to view a report with the given user input, a button invokes a JS function which opens a new page in the browser to show the rendered report. The code looks like this:
$('#btnShowReport').click(function () {
document.getElementById("Error").innerHTML = "";
var exists = CheckSession();
if (exists) {
window.open('<%=Url.Content("~/Reports/Launch.aspx?Report=Short&Area=1") %>');
}
});
The page that is then opened has the following code which is called from Page_Load:
rptViewer.ProcessingMode = ProcessingMode.Remote
rptViewer.AsyncRendering = True
rptViewer.ServerReport.Timeout = CInt(WebConfigurationManager.AppSettings("ReportTimeout")) * 60000
rptViewer.ServerReport.ReportServerUrl = New Uri(My.Settings.ReportURL)
rptViewer.ServerReport.ReportPath = "/" & My.Settings.ReportPath & "/" & Request("Report")
'Set the report to use the credentials from web.config
rptViewer.ServerReport.ReportServerCredentials = New SQLReportCredentials(My.Settings.ReportServerUser, My.Settings.ReportServerPassword, My.Settings.ReportServerDomain)
Dim myCredentials As New Microsoft.Reporting.WebForms.DataSourceCredentials
myCredentials.Name = My.Settings.ReportDataSource
myCredentials.UserId = My.Settings.DatabaseUser
myCredentials.Password = My.Settings.DatabasePassword
rptViewer.ServerReport.SetDataSourceCredentials(New Microsoft.Reporting.WebForms.DataSourceCredentials(0) {myCredentials})
rptViewer.ServerReport.SetParameters(parameters)
rptViewer.ServerReport.Refresh()
I have omitted some code which builds up the parameters for the report, but I doubt any of that is relevant.
The problem is that, when the user clicks the show report button, and this new page opens up, depending on the types of parameters they use the report could take quite some time to render, and in the mean time, the original page becomes completely unresponsive. The moment the report page actually renders, the main page begins functioning again. Where should I start (google keywords, ReportViewer properties, etc) if I want to fix this behavior such that the other page can load asynchronously without affecting the main page?
Edit -
I tried doing the follow, which was in a linked answer in a comment here:
$.ajax({
context: document.body,
async: true, //NOTE THIS
success: function () {
window.open(Address);
}
});
this replaced the window.open call. This seems to work, but when I check out the documentation, trying to understand what this is doing I found this:
The .context property was deprecated in jQuery 1.10 and is only maintained to the extent needed for supporting .live() in the jQuery Migrate plugin. It may be removed without notice in a future version.
I removed the context property entirely and it didnt seem to affect the code at all... Is it ok to use this ajax call in this way to open up the other window, or is there a better approach?
Using a timeout should open the window without blocking your main page
$('#btnShowReport').click(function () {
document.getElementById("Error").innerHTML = "";
var exists = CheckSession();
if (exists) {
setTimeout(function() {
window.open('<%=Url.Content("~/Reports/Launch.aspx?Report=Short&Area=1") %>');
}, 0);
}
});
This is a long shot, but have you tried opening the window with a blank URL first, and subsequently changing the location?
$("#btnShowReport").click(function(){
If (CheckSession()) {
var pop = window.open ('', 'showReport');
pop = window.open ('<%=Url.Content("~/Reports/Launch.aspx?Report=Short&Area=1") %>', 'showReport');
}
})
use
`$('#btnShowReport').click(function () {
document.getElementById("Error").innerHTML = "";
var exists = CheckSession();
if (exists) {
window.location.href='<%=Url.Content("~/Reports/Launch.aspx?Report=Short&Area=1") %>';
}
});`
it will work.

Detect when an iframe is loaded

I'm using an <iframe> (I know, I know, ...) in my app (single-page application with ExtJS 4.2) to do file downloads because they contain lots of data and can take a while to generate the Excel file (we're talking anything from 20 seconds to 20 minutes depending on the parameters).
The current state of things is : when the user clicks the download button, he is "redirected" by Javascript (window.location.href = xxx) to the page doing the export, but since it's done in PHP, and no headers are sent, the browser continuously loads the page, until the file is downloaded. But it's not very user-friendly, because nothing shows him whether it's still loading, done (except the file download), or failed (which causes the page to actually redirect, potentially making him lose the work he was doing).
So I created a small non-modal window docked in the bottom right corner that contains the iframe as well as a small message to reassure the user. What I need is to be able to detect when it's loaded and be able to differenciate 2 cases :
No data : OK => Close window
Text data : Error message => Display message to user + Close window
But I tried all 4 events (W3Schools doc) and none is ever fired. I could at least understand that if it's not HTML data returned, it may not be able to fire the event, but even if I force an error to return text data, it's not fired.
If anyone know of a solution for this, or an alternative system that may fit here, I'm all ears ! Thanks !
EDIT : Added iframe code. The idea is to get a better way to close it than a setTimeout.
var url = 'http://mywebsite.com/my_export_route';
var ifr = $('<iframe class="dl-frame" src="'+url+'" width="0" height="0" frameborder="0"></iframe>');
ifr.appendTo($('body'));
setTimeout(function() {
$('.dl-frame').remove();
}, 3000);
I wonder if it would require some significant changes in both frontend and backend code, but have you considered using AJAX? The workflow would be something like this: user sends AJAX request to start file generating and frontend constantly polls it's status from the server, when it's done - show a download link to the user. I believe that workflow would be more straightforward.
Well, you could also try this trick. In parent window create a callback function for the iframe's complete loading myOnLoadCallback, then call it from the iframe with parent.myOnLoadCallback(). But you would still have to use setTimeout to handle server errors/connection timeouts.
And one last thing - how did you tried to catch iframe's events? Maybe it something browser-related. Have you tried setting event callbacks in HTML attributes directly? Like
<iframe onload="done()" onerror="fail()"></iframe>
That's a bad practice, I know, but sometimes job need to be done fast, eh?
UPDATE
Well, I'm afraid you have to spend a long and painful day with a JS debugger. load event should work. I still have some suggestions, though:
1) Try to set event listener before setting element's src. Maybe onload event fires so fast that it slips between creating element and setting event's callback
2) At the same time try to check if your server code plays nicely with iframes. I have made a simple test which attempts to download a PDF from Dropbox, try to replace my URL with your backed route's.
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<iframe id="book"></iframe>
<button id="go">Request downloads!</button>
<script>
var bookUrl = 'https://www.dropbox.com/s/j4o7tw09lwncqa6/thinkpython.pdf';
$('#book').on('load', function(){
console.log('WOOT!', arguments);
});
$('#go').on('click', function(){
$('#book').attr('src', bookUrl);
});
</script>
UPDATE 2
3) Also, look at the Network tab of your browser's debugger, what happens when you set src to the iframe, it should show request and server's response with headers.
I've tried with jQuery and it worked just fine as you can see in this post.
I made a working example here.
It's basically this:
<iframe src="http://www.example.com" id="myFrame"></iframe>
And the code:
function test() {
alert('iframe loaded');
}
$('#myFrame').load(test);
Tested on IE11.
I guess I'll give a more hacky alternative to the more proper ways of doing it that the others have posted. If you have control over the PHP download script, perhaps you can just simply output javascript when the download is complete. Or perhaps redirect to a html page that runs javascript. The javascript run, can then try to call something in the parent frame. What will work depends if your app runs in the same domain or not
Same domain
Same domain frame can just use frame javascript objects to reference each other. so it could be something like, in your single page application you can have something like
window.downloadHasFinished=function(str){ //Global pollution. More unique name?
//code to be run when download has finished
}
And for your download php script, you can have it output this html+javascript when it's done
<script>
if(parent && parent.downloadHasFinished)
parent.downloadHasFinished("if you want to pass a data. maybe export url?")
</script>
Demo jsfiddle (Must run in fullscreen as the frames have different domain)
Parent jsfiddle
Child jsfiddle
Different Domains
For different domains, We can use postMessage. So in your single page application it will be something like
$(window).on("message",function(e){
var e=e.originalEvent
if(e.origin=="http://downloadphp.anotherdomain.com"){ //for security
var message=e.data //data passed if any
//code to be run when download has finished
}
});
and in your php download script you can have it output this html+javascript
<script>
parent.postMessage("if you want to pass data",
"http://downloadphp.anotherdomain.com");
</script>
Parent Demo
Child jsfiddle
Conclusion
Honestly, if the other answers work, you should probably use those. I just thought this was an interesting alternative so I posted it up.
You can use the following script. It comes from a project of mine.
$("#reportContent").html("<iframe id='reportFrame' sandbox='allow-same-origin allow-scripts' width='100%' height='300' scrolling='yes' onload='onReportFrameLoad();'\></iframe>");
Maybe you should use
$($('.dl-frame')[0].contentWindow.document).ready(function () {...})
Try this (pattern)
$(function () {
var session = function (url, filename) {
// `url` : URL of resource
// `filename` : `filename` for resource (optional)
var iframe = $("<iframe>", {
"class": "dl-frame",
"width": "150px",
"height": "150px",
"target": "_top"
})
// `iframe` `load` `event`
.one("load", function (e) {
$(e.target)
.contents()
.find("html")
.html("<html><body><div>"
+ $(e.target)[0].nodeName
+ " loaded" + "</div><br /></body></html>");
alert($(e.target)[0].nodeName
+ " loaded" + "\nClick link to download file");
return false
});
var _session = $.when($(iframe).appendTo("body"));
_session.then(function (data) {
var link = $("<a>", {
"id": "file",
"target": "_top",
"tabindex": "1",
"href": url,
"download": url,
"html": "Click to start {filename} download"
});
$(data)
.contents()
.find("body")
.append($(link))
.addBack()
.find("#file")
.attr("download", function (_, o) {
return (filename || o)
})
.html(function (_, o) {
return o.replace(/{filename}/,
(filename || $(this).attr("download")))
})
});
_session.always(function (data) {
$(data)
.contents()
.find("a#file")
.focus()
// start 6 second `download` `session`,
// on `link` `click`
.one("click", function (e) {
var timer = 6;
var t = setInterval(function () {
$(data)
.contents()
.find("div")
// `session` notifications
.html("Download session started at "
+ new Date() + "\n" + --timer);
}, 1000);
setTimeout(function () {
clearInterval(t);
$(data).replaceWith("<span class=session-notification>"
+ "Download session complete at\n"
+ new Date()
+ "</span><br class=session-notification />"
+ "<a class=session-restart href=#>"
+ "Restart download session</a>");
if ($("body *").is(".session-restart")) {
// start new `session`,
// on `.session-restart` `click`
$(".session-restart")
.on("click", function () {
$(".session-restart, .session-notification")
.remove()
// restart `session` (optional),
// or, other `session` `complete` `callback`
&& session(url, filename ? filename : null)
})
};
}, 6000);
});
});
};
// usage
session("http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf", "ECMA_JS.pdf")
});
jsfiddle http://jsfiddle.net/guest271314/frc82/
In regards to your comment about to get a better way to close it instead of setTimeout. You could use jQuery fadeOut option or any of the transitions and in the 'complete' callback remove the element. Below is an example you can dump right into a fiddle and only need to reference jQuery.
I also wrapped inside listener for 'load' event to not do the fade until the iFrame has been loaded as question originally was asking.
// plugin your URL here
var url = 'http://jquery.com';
// create the iFrame, set attrs, and append to body
var ifr = $("<iframe>")
.attr({
"src": url,
"width": 300,
"height": 100,
"frameborder": 0
})
.addClass("dl-frame")
.appendTo($('body'))
;
// log to show its part of DOM
console.log($(".dl-frame").length + " items found");
// create listener for load
ifr.one('load', function() {
console.log('iframe is loaded');
// call $ fadeOut to fade the iframe
ifr.fadeOut(3000, function() {
// remove iframe when fadeout is complete
ifr.remove();
// log after, should no longer exist in DOM
console.log($(".dl-frame").length + " items found");
});
});
If you are doing a file download from a iframe the load event wont fire :) I was doing this a week ago. The only solution to this problem is to call a download proxy script with a tag and then return that tag trough a cookie then the file is loaded. min while yo need to have a setInterval on the page witch will watch for that specific cookie.
// Jst to clearyfy
var token = new Date().getTime(); // ticks
$('<iframe>',{src:"yourproxy?file=somefile.file&token="+token}).appendTo('body');
var timers = [];
timers[timers.length+1] = setInterval(function(){
var _index = timers.length+1;
var cookie = $.cooke(token);
if(typeof cookie != "undefined"){
// File has been downloaded
$.removeCookie(token);
clearInterval(_index);
}
},400);
in your proxy script add the cookie with the name set to the string sent bay the token url parameter.
If you control the script in server that generates excel or whatever you are sending to iframe why don't you put a UID flag and store it in session with value 0, so... when iframe is created and server script is called just set UID flag to 1 and when script is finished (the iframe will be loaded) just put it to 2.
Then you only need a timer and a periodic AJAX call to the server to check the UID flag... if it's set to 0 the process doesn't started, if it's 1 the file is creating, and finally if it's 2 the process has been ended.
What do you think? If you need more information about this approach just ask.
What you are saying could be done for images and other media formats using $(iframe).load(function() {...});
For PDF files or other rich media, you can use the following Library:
http://johnculviner.com/jquery-file-download-plugin-for-ajax-like-feature-rich-file-downloads/
Note: You will need JQuery UI
You can use this library. The code snippet for you purpose would be something like:
window.onload = function () {
rajax_obj = new Rajax('',
{
action : 'http://mywebsite.com/my_export_route',
onComplete : function(response) {
//This will only called if you have returned any response
// instead of file from your export script
// In your case 2
// Text data : Error message => Display message to user
}
});
}
Then you can call rajax_obj.post() on your download link click.
Download
NB: You should add some header to your PHP script so it force file download
header('Content-Disposition: attachment; filename="'.$file.'"');
header('Content-Transfer-Encoding: binary');
There is two solutions that i can think of. Either you have PHP post it's progress to a MySQL table where from frontend will be pulling information from using AJAX calls to check up on the progress of the generation. Using somekind of unique key that is being generated when accessing the page would be ideal for multiple people generating excel files at the same time.
Another solution would be to use nodejs & then in PHP post the progress of the excel file using cURL or a socket to a nodejs service. Then when receiving updates from PHP in nodejs you simply write the progress of the excel file for the right socket. This will cut off some browser support though. Unless you go through with it using external libraries to bring websocket support for pretty much all browsers & versions.
Hope this answer helped. I was having the same issue previous year. Ended up doing AJAX polling having PHP post progress on the fly.
Try this:
Note: You should be on the same domain.
var url = 'http://mywebsite.com/my_export_route',
iFrameElem = $('body')
.append('<iframe class="dl-frame" src="' + url + '" width="0" height="0" frameborder="0"></iframe>')
.find('.dl-frame').get(0),
iDoc = iFrameElem.contentDocument || iFrameElem.contentWindow.document;
$(iDoc).ready(function (event) {
console.log('iframe ready!');
// do stuff here
});

Is it possible to know how long a user has spent on a page?

Say I've a browser extension which runs JS pages the user visits.
Is there an "outLoad" event or something of the like to start counting and see how long the user has spent on a page?
I am assuming that your user opens a tab, browses some webpage, then goes to another webpage, comes back to the first tab etc. You want to calculate exact time spent by the user. Also note that a user might open a webpage and keep it running but just go away. Come back an hour later and then once again access the page. You would not want to count the time that he is away from computer as time spent on the webpage. For this, following code does a docus check every 5 minutes. Thus, your actual time might be off by 5 minutes granularity but you can adjust the interval to check focus as per your needs. Also note that a user might just stare at a video for more than 5 minutes in which case the following code will not count that. You would have to run intelligent code that checks if there is a flash running or something.
Here is what I do in the content script (using jQuery):
$(window).on('unload', window_unfocused);
$(window).on("focus", window_focused);
$(window).on("blur", window_unfocused);
setInterval(focus_check, 300 * 1000);
var start_focus_time = undefined;
var last_user_interaction = undefined;
function focus_check() {
if (start_focus_time != undefined) {
var curr_time = new Date();
//Lets just put it for 4.5 minutes
if((curr_time.getTime() - last_user_interaction.getTime()) > (270 * 1000)) {
//No interaction in this tab for last 5 minutes. Probably idle.
window_unfocused();
}
}
}
function window_focused(eo) {
last_user_interaction = new Date();
if (start_focus_time == undefined) {
start_focus_time = new Date();
}
}
function window_unfocused(eo) {
if (start_focus_time != undefined) {
var stop_focus_time = new Date();
var total_focus_time = stop_focus_time.getTime() - start_focus_time.getTime();
start_focus_time = undefined;
var message = {};
message.type = "time_spent";
message.domain = document.domain;
message.time_spent = total_focus_time;
chrome.extension.sendMessage("", message);
}
}
onbeforeunload should fit your request. It fires right before page resources are being unloaded (page closed).
<script type="text/javascript">
function send_data(){
$.ajax({
url:'something.php',
type:'POST',
data:{data to send},
success:function(data){
//get your time in response here
}
});
}
//insert this data in your data base and notice your timestamp
window.onload=function(){ send_data(); }
window.onbeforeunload=function(){ send_data(); }
</script>
Now calculate the difference in your time.you will get the time spent by user on a page.
For those interested, I've put some work into a small JavaScript library that times how long a user interacts with a web page. It has the added benefit of more accurately (not perfectly, though) tracking how long a user is actually interacting with the page. It ignore times that a user switches to different tabs, goes idle, minimizes the browser, etc.
Edit: I have updated the example to include the current API usage.
http://timemejs.com
An example of its usage:
Include in your page:
<script src="http://timemejs.com/timeme.min.js"></script>
<script type="text/javascript">
TimeMe.initialize({
currentPageName: "home-page", // page name
idleTimeoutInSeconds: 15 // time before user considered idle
});
</script>
If you want to report the times yourself to your backend:
xmlhttp=new XMLHttpRequest();
xmlhttp.open("POST","ENTER_URL_HERE",true);
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
var timeSpentOnPage = TimeMe.getTimeOnCurrentPageInSeconds();
xmlhttp.send(timeSpentOnPage);
TimeMe.js also supports sending timing data via websockets, so you don't have to try to force a full http request into the document.onbeforeunload event.
The start_time is when the user first request the page and you get the end_time by firing an ajax notification to the server just before the user quits the page :
window.onbeforeunload = function () {
// Ajax request to record the page leaving event.
$.ajax({
url: "im_leaving.aspx", cache: false
});
};
also you have to keep the user session alive for users who stays long time on the same page (keep_alive.aspxcan be an empty page) :
var iconn = self.setInterval(
function () {
$.ajax({
url: "keep_alive.aspx", cache: false });
}
,300000
);
then, you can additionally get the time spent on the site, by checking (each time the user leaves a page) if he's navigating to an external page/domain.
Revisiting this question, I know this wouldn't be much help in a Chrome Ext env, but you could just open a websock that does nothing but ping every 1 second and then when the user quits, you know to a precision of 1 second how long they've spent on the site as the connection will die which you can escape however you want.
Try out active-timeout.js. It uses the Visibility API to check when the user has switched to another tab or has minimized the browser window.
With it, you can set up a counter that runs until a predicate function returns a falsy value:
ActiveTimeout.count(function (time) {
// `time` holds the active time passed up to this point.
return true; // runs indefinitely
});

How would I make a post on the users wall extending this example?

I am making an app with phonegap, adobe build and using facebook for authentication with this snippet of code.
I am looking to handle the 'success' login with some form of callback I suppose, and make a post on the user's wall.
<script type="text/javascript">
var my_client_id = "133914256793487", // YOUR APP ID
my_secret = "862f10f883f8d91617b77b4b143abc8d", // YOUR APP SECRET
my_redirect_uri = "https://www.facebook.com/connect/login_success.html", // LEAVE THIS
my_type ="user_agent", my_display = "touch"; // LEAVE THIS
var facebook_token = "fbToken"; // OUR TOKEN KEEPER
var ref; //IN APP BROWSER REFERENCE
// FACEBOOK
var Facebook = {
init:function(){
// Begin Authorization
alert("we have begun");
var authorize_url = "https://www.facebook.com/dialog/oauth?";
authorize_url += "client_id=" + my_client_id;
authorize_url += "&redirect_uri=" + my_redirect_uri;
authorize_url += "&display=" + my_display;
authorize_url += "&scope=publish_stream";
//CALL IN APP BROWSER WITH THE LINK
ref = window.open(authorize_url, '_blank', 'location=no');
ref.addEventListener('loadstart', function(event){
Facebook.facebookLocChanged(event.url);
});
} ,
facebookLocChanged:function(loc){
if (loc.indexOf("https://www.facebook.com/connect/login_success.html") >= 0 ) {
//CLOSE INAPPBROWSER AND NAVIGATE TO INDEX
ref.close();
//THIS IS MEANT TO BE DONE ON SERVER SIDE TO PROTECT CLIENT SECRET
var codeUrl = 'https://graph.facebook.com/oauth/access_token?client_id='+my_client_id+'&client_secret='+my_secret+'&redirect_uri='+my_redirect_uri+'&code='+loc.split("=")[1];
console.log('CODE_URL::' + codeUrl);
$.ajax({
url: codeUrl,
data: {},
type: 'POST',
async: false,
cache: false,
success: function(data, status){
//WE STORE THE TOKEN HERE
localStorage.setItem(facebook_token, data.split('=')[1].split('&')[0]);
},
error: function(){
alert("Unknown error Occured");
}
});
}
}
}
</script>
<script type="text/javascript">
Facebook.init();
</script>
Can anybody advise how to appropriately extend this example - and where I can find the API to be able to help myself? Currently it successfully asks the user to login - facebook pops up - I successfully accept and then it returns with Success - and a red message saying the user should retain this URL securely.
Cheers,
Andy
I know that changing the whole structure of your code is a bit of a daunting task, but I highly recommend using PhoneGap Facebook Plugin. And since you're using build, you can also easily integreate the Facebook Connect plugin from Build.
I highly recommend you check out the following two files found within the phonegap-facebook-plugin / example / HackBook folder of the PhoneGap Facebook plugin repository: index.html and js/auth.js .
When using the javascript FB Api, you can specify a callback function as the first parameter, as in FB.login(function(response) { ... }); You can find an example in the docs.
Line 25 of auth.js shows an example of making a call to the FB API, using the following code FB.api('/me' .... While this code requests information about the logged in user, you can make any javascript API call with FB.api(), which you can learn about here (look at the fourth example).
Check out the code and see how it works/how it's implemented, and let me know if you have any questions :)

How do I close the popup after I post to facebook?

On our blog we have a link where users can post our articles to their timeline. A popup opens up and the user posts to facebook, then the popup stays there and redirects to "www.oursite.com". How do we instead close the popup when the user either finishes posting or clicks on the cancel button? According to this so question it can't be done but Huffington post has figured it out but looking at their code we can't figure it out.
As an example, the facebook share button here will open up a popup and then close when you either post the article or cancel.
Here's what we have:
FB.init({appId: "90210", status: true, cookie: true});
function postToFeed() {
// calling the API ...
var obj = {
method: 'feed',
redirect_uri: 'http://www.oursite.com/',
link: 'http://www.oursite.com/',
picture: 'http://www.oursite.com/png.png',
name: 'Some title',
caption: '',
description: ''
};
function callback(response){
window.close(); // doesn't do anything
//document.getElementById('msg').innerHTML = "Post ID: " + response['post_id'];
}
FB.ui(obj, callback);
}
We've tried adding window.close(); in the callback (and also self.close();), tried leaving redirect_uri blank (and tried leaving redirect_uri out altogether, but it's required).
It seems that the accepted answer is no longer working due to the fact that facebook now strips anything after a hash and replaces it with post_id=xxxxx.
Solution #1 (if you trust FB not to change this in the near future):
if(window.location.search.indexOf('post_id')==1) window.close();
Solution #2 (if you want a little more insurance against change and don't mind a second file):
Create a new html file closewindow.html:
<html><body><script>window.close()</script></body></html>
and link to it in the redirect.
Redirect to http://oursite.com/#close_window. Then on your site's homepage, include something like this:
if (window.location.hash == '#close_window') window.close();.
After spending a whole day working on this problem, I have a very good solution that I'd like to share. Instead of using the SDK with FB.ui(), I have discovered that I can avoid it entirely by manually opening my own popup to https://www.facebook.com/dialog/feed. When doing it this way, redirect_uri works as expected. As long as you require the user to click a button to make the dialog pop up, no popup blocker will be triggered.
I don't believe there are any compromises with this code, and if anything, it is much easier to use than the actual SDK.
My Javascript code (which you can save as FacebookFeedDialog.js) looks like this:
/* by Steven Yang, Feb 2015, originally for www.mathscore.com. This code is free for anybody to use as long as you include this comment. */
function FacebookFeedDialog(appID, linkTarget, redirectTarget) {
this.mParams = {
app_id: appID,
link: linkTarget,
redirect_uri: redirectTarget,
display: "popup"
}
};
/* Common params include:
name - the title that appears in bold font
description - the text that appears below the title
picture - complete URL path to the image on the left of the dialog
caption - replaces the link text
*/
FacebookFeedDialog.prototype.addParam = function(key, value) {
this.mParams[key] = value;
};
FacebookFeedDialog.prototype.open = function() {
var url = 'https://www.facebook.com/dialog/feed?' + encodeCGIArgs(this.mParams);
popup(url, 'feedDialog', 700, 400);
};
/* Takes a param object like this:
{ arg1: "value1", arg2: "value2" }
and converts into CGI args like this:
arg1=value1&arg2=value2
The values and args will be properly URI encoded
*/
function encodeCGIArgs(paramObject) {
var result = '';
for (var key in paramObject) {
if (result)
result += '&';
result += encodeURIComponent(key) + '=' + encodeURIComponent(paramObject[key]);
}
return result;
}
function popup(mylink,windowname,width,height) {
if (!window.focus) return;
var href;
if (typeof(mylink) == 'string')
href=mylink;
else
href=mylink.href;
if (!windowname)
windowname='mywindow';
if (!width)
width=600;
if (!height)
height=350;
window.open(href, windowname, 'resizable=yes,width='+width+',height='+height+',scrollbars=yes');
}
Here's a sample HTML file that uses the Javascript code above:
<HTML>
<BODY>
<SCRIPT type="text/javascript" src="FacebookFeedDialog.js"></SCRIPT>
<SCRIPT>
var dialog = new FacebookFeedDialog(yourAppIDGoesHere,yourDestinationURLGoesHere,yourCloseWindowURLGoesHere);
dialog.addParam('name','This is my title');
dialog.addParam('description','This is the description');
dialog.addParam('picture',yourImageURLGoesHere);
dialog.addParam('caption','This is the caption');
</SCRIPT>
Open facebook dialog
</BODY>
</HTML>
Your closeWindow html file can look like this:
<SCRIPT>
window.close();
</SCRIPT>
As of 10/2017 removing redirect_uri seems to work.
It will default to https://www.facebook.com/dialog/return/close#_=_
whose source is just
<script type="text/javascript"> window.close() </script>
UPDATE:
Add this script to your redirect page:
if (document.referrer == "https://www.facebook.com/" && (window.location.href.indexOf('post_id=') != -1 || window.location.hash == '#_=_')) {
window.close();
}
Just remove the redirect_uri parameter from the url.
Like here.
This is not a direct answer to the original question, but it might help others arriving here.
There is a more robust way to do this that allows you take action on the originating page when the share has completed successfully:
In the originating page:
var shareWindow;
function openShareWindow() {
// See https://developers.facebook.com/docs/sharing/reference/share-dialog
shareWindow = window.open('https://www.facebook.com/dialog/share?app_id=...&display=popup&redirect_uri=...');
}
function shareCompleted() {
if (shareWindow) {
shareWindow.close();
shareWindow = null;
// The share was successful, so do something interesting here...
}
}
In the page you have set as your redirect_uri, which I usually map to something like http://myserver.com/share/close:
window.opener.shareCompleted();
Now you're assured the share window will close and you'll be able to take action on the originating page if needed.
Note: shareCompleted must be available in the root window namespace for this work. If you're wrapping all of your JavaScript as you should be, then be sure to:
window.shareCompleted = shareCompleted;
<script>if (window.location.hash.indexOf("#close_window") != -1) window.close();</script>

Categories

Resources