Automating Facebook group post deletion using CasperJS - javascript

I'm working on writing a script to delete posts from a Facebook Group, since Facebook's Graph API won't allow a developer to do so unless the posts were made from the developer's account.
So far, I have been able to log into Facebook, then navigate to the desired group page. From there I can get the XPath for each post visible on the page (using the selector a[data-testid='post_chevron_button']). My script fails while trying to call this.click() on each XPath selector.
My current script is as follows:
phantom.casperTest = true;
var x = require('casper').selectXPath;
var casper = require('casper').create({
verbose: true,
pageSettings: {
loadImages: false,
loadPlugins: false,
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4'
}
});
// print out all the messages in the headless browser context
casper.on('remote.message', function(msg) {
this.echo('remote message caught: ' + msg);
});
// print out all the messages in the headless browser context
casper.on("page.error", function(msg, trace) {
this.echo("Page Error: " + msg, "ERROR");
});
var url = 'http://www.facebook.com/';
casper.start(url, function() {
console.log("page loaded");
this.test.assertExists('form#login_form', 'form is found');
this.fill('form#login_form', {
email: '{email}',
pass: '{password}'
}, true);
this.click('#u_0_q');
this.wait(1000, function() {
this.echo("Capturing image of page after login.");
this.capture('loggedin.png');
});
});
casper.thenOpen('https://www.facebook.com/groups/{group-id}/', function() {
this.echo(this.getTitle());
this.wait(1000, function() {
this.capture('group.png');
});
var elements = casper.getElementsInfo("a[data-testid='post_chevron_button']");
var index = 1;
elements.forEach(function(element){
var xpath = '//*[#id="' + element.attributes["id"] + '"]';
console.log(xpath);
this.click(x(xpath));
this.wait(100, function() {
this.capture('chevronlink' + index + '.png');
});
index++;
});
});
casper.run();
When the script gets to this.click(x(xpath)); I get the error message TypeError: undefined is not a constructor (evaluating 'this.click(x(xpath))'). If I simply replace the last bit of code that creates an array and iterates through it with this.click("a[data-testid='post_chevron_button']");, my script has no problem.
Does anyone know what CasperJS doesn't like about calling click() with the XPath selector? XPath appears to be a valid selector going off of CasperJS's docs.
UPDATE
I've updated the title of the question to more accurately describe the desired result.
Per dasmelch's advice, I've reworked the script a bit and incorporated this bit into the script instead (after the casper.thenOpen portion):
casper.then(function() {
var elements = casper.getElementsAttribute("a[data-
testid='post_chevron_button']", 'id');
while (elements.length > 0) {
// get always the last element with target id
element = elements.pop();
(function(element) {
var xpath = '//*[#id="' + element + '"]';
console.log(xpath);
// do it step by step
casper.then(function() {
this.click(x(xpath));
});
casper.then(function() {
this.capture('chevronlink' + element + '.png');
});
// go back to the page with the links (if necessary)
casper.then(function() {
casper.back();
});
})(element);
};
});
I now get this error: Cannot dispatch mousedown event on nonexistent selector: xpath selector: //*[#id="u_0_47"].
Last night, I decided to go about it a little differently. I got closer to the desired end result, but now CasperJS and/or PhantomJS is having trouble finding the elements that are present in the dropdown after clicking the post_chevron_button. Here is what I ended up with (everything prior to casper.thenOpen remains the same in the script shown originally):
casper.thenOpen('https://www.facebook.com/groups/{group-id}/', function() {
this.echo(this.getTitle());
this.wait(1000, function() {
this.capture('group.png');
});
var elements = casper.getElementsInfo("a[data-
testid='post_chevron_button']");
while (elements.length > 0) {
this.click("a[data-testid='post_chevron_button']");
this.wait(1000, function() {
this.capture('chevron_click.png');
console.log("chevron_click.png saved");
});
var chevronLinks = casper.getElementsInfo("a[ajaxify]")
console.log("Found " + chevronLinks.length + " elements with ajaxify attribute.");
var chevronLinksIndex = 1;
chevronLinks.forEach(function(element){
var ajaxifyValue = element.attributes["ajaxify"];
console.log(ajaxifyValue);
if (ajaxifyValue.indexOf("delete.php?group_id={group-id}") !== -1) {
this.click("a[ajaxify='"+ajaxifyValue+"']");
this.wait(100, function(){
this.capture('deletePost' + chevronLinksIndex);
});
chevronLinksIndex++;
}
});
if (chevronLinksIndex === 1) {
break;
}
elements = casper.getElementsInfo("a[data-testid='post_chevron_button']");
}
});
I know there should be an element that contains an ajaxify attribute with the value I'm searching for (because stepping through it in a browser myself shows the element after the click on a[data-testid='post_chevron_button']), but Casper cannot find it. Not only that, my chevron_click.png image file should be updating on each run of this script, but it's not.
Some of the code execution is not happening in order. For instance, the logging of ajaxify attribute values is happening in the console prior to seeing chevron_click.png saved. This may be expected, but unfortunately I don't have a lot of JS experience. This execution order problem may explain why my search for the necessary element is not returning what I expect.
Here is an example of the element needing clicked for deletion of a post:
<a class="_54nc" href="#" rel="async-post"
ajaxify="/ajax/groups/mall/delete.php?group_id={group-id}&message_id=806608486110204&story_dom_id=mall_post_806608486110204%3A6%3A0&entstory_context=%7B%22last_view_time%22%3A1495072771%2C%22fbfeed_context%22%3Atrue%2C%22location_type%22%3A2%2C%22outer_object_element_id%22%3A%22mall_post_806608486110204%3A6%3A0%22%2C%22object_element_id%22%3A%22mall_post_806608486110204%3A6%3A0%22%2C%22is_ad_preview%22%3Afalse%2C%22is_editable%22%3Afalse%2C%22mall_how_many_post_comments%22%3A2%2C%22bump_reason%22%3A0%2C%22story_width%22%3A502%2C%22shimparams%22%3A%7B%22page_type%22%3A16%2C%22actor_id%22%3A664025626%2C%22story_id%22%3A806608486110204%2C%22ad_id%22%3A0%2C%22_ft_%22%3A%22%22%2C%22location%22%3A%22group%22%7D%2C%22story_id%22%3A%22u_0_21%22%2C%22caret_id%22%3A%22u_0_22%22%7D&surface=group_post_chevron"
role="menuitem"><span><span class="_54nh"><div class="_41t5"><i
class="_41t7 img sp_gJvT8CoKHU- sx_0f12ae"></i><i class="_41t8 img
sp_s36yWP_7MD_ sx_7e9f7d"></i>Delete Post</div></span></span></a>

I was able to accomplish what I was trying to do with the Selenium 2 API for .NET.
The solution code is below:
class Program
{
static void Main(string[] args)
{
var options = new ChromeOptions();
options.AddUserProfilePreference("profile.default_content_setting_values.notifications", 2);
using (IWebDriver driver = new ChromeDriver(options))
{
// Maximize window
driver.Manage().Window.Maximize();
// Log into Facebook
driver.Navigate().GoToUrl("http://www.facebook.com/");
driver.FindElement(By.Id("email")).SendKeys("username");
driver.FindElement(By.Id("pass")).SendKeys("password");
driver.FindElement(By.Id("pass")).SendKeys(Keys.Enter);
driver.Navigate().GoToUrl("https://www.facebook.com/groups/{group-id}/");
var chevronPostLinks = driver.FindElements(By.XPath("//a[#data-testid='post_chevron_button']"));
chevronPostLinks.FirstOrDefault().Click();
Thread.Sleep(1000);
var deletePostElements = driver.FindElements(By.XPath("//a[contains(#ajaxify,'delete.php?group_id={group-id}')]"));
while (deletePostElements.Count > 0 && chevronPostLinks.Count > 0)
{
Thread.Sleep(1000);
deletePostElements.Where(x => x.Displayed == true).FirstOrDefault().Click();
Thread.Sleep(1000);
driver.FindElement(By.ClassName("layerConfirm")).Click();
Thread.Sleep(2000);
chevronPostLinks = driver.FindElements(By.XPath("//a[#data-testid='post_chevron_button']"));
if (chevronPostLinks.Count > 0)
{
chevronPostLinks.FirstOrDefault().Click();
}
else
{
driver.Navigate().GoToUrl("https://www.facebook.com/groups/{group-id}/");
chevronPostLinks = driver.FindElements(By.XPath("//a[#data-testid='post_chevron_button']"));
chevronPostLinks.FirstOrDefault().Click();
}
Thread.Sleep(1000);
deletePostElements = driver.FindElements(By.XPath("//a[contains(#ajaxify,'delete.php?group_id={group-id}')]"));
}
}
}
}
There are some improvements I'd like to make, like using Selenium to wait for elements to be visible instead of using Thread.Sleep(), but it's working just fine for my purpose.

You do the xpath stuff correct, but it seems the method forEach is not working for this.
You can grab directly the id of all these elements with casper.getElementsAttribute an iterate easy with a while loop throw that them more easily like that:
...
casper.thenOpen('https://www.facebook.com/groups/{group-id}/', function() {
this.echo(this.getTitle());
this.wait(1000, function() {
this.capture('group.png');
});
});
// do a while loop with where you can use every single element and jump back
casper.then(function() {
var elements = casper.getElementsAttribute("a[data-testid='post_chevron_button']", 'id');
while (elements.length > 0) {
// get always the last element with target id
element = elements.pop();
(function(element) {
var xpath = '//*[#id="' + element + '"]';
console.log(xpath);
// do it step by step
casper.then(function() {
this.click(x(xpath));
});
casper.then(function() {
this.capture('chevronlink' + element + '.png');
});
// go back to the page with the links (if necessary)
casper.then(function() {
casper.back();
});
})(element);
};
});
...
Without looking at FB, i guess you have to go back (casper.back) to the site where the links (elements) are.

Related

How to check broken links in a webpage using Casperjs?

I want to check all broken links present in a webpage using casperjs. I write following code but it not working:
casper.then(function() {
var urls=casper.getElementsAttribute('a[href]', 'href');
casper.eachThen(urls, function(response) {
var link=response.data;
this.thenOpen(demourl, function(response) {
if (response == undefined || response.status >= 400) {
this.echo("failed");
}
})
this.on('http.status.404', function(resource) {
this.echo('wait, this url is 404: ' + resource.url);
})
})
})
My Webpage consists of more than 400 links. My code doesn't complete its execution and remains idle after some links. It is not giving me any response. I am not getting why this happening?
Get link URLs
There is a difference between attributes and properties of DOM elements. If you have a website which is on the domain http://example.com and you want to get the href of the following link on that page
text
If you use aElement.getAttribute("href") you will get "/path/to/stuff", but if you use aElement.href, you will get the computed URL "http://example.com/path/to/stuff". Only the latter is a URL that CasperJS (actually PhantomJS) understands.
I'm telling you this, because casper.getElementsAttribute() internally uses the element.getAttribute() approach which produces URLs that cannot be opened with casper.thenOpen().
The fix is easy:
var urls = casper.evaluate(function(){
return [].map.call(document.querySelectorAll('a[href]'), function(a){
return a.href;
});
});
Also, you might want to move the casper.on() event registration above the casper.eachThen() call. You don't need to register the event in every iteration.
Registering link timeouts
Since you have problems with some URLs not loading (probably because they are broken), you can use casper.options.stepTimeout to set a timeout for steps, so that CasperJS won't freeze on some unretrievable URL. You also need to define the onStepTimeout() callback, otherwise CasperJS will exit.
casper.then(function() {
var currentURL;
casper.options.stepTimeout = 10000; // 10 seconds
casper.options.onStepTimeout = function(timeout, stepNum){
this.echo('wait, this url timed out: ' + currentURL);
};
var urls = this.evaluate(function(){
return [].map.call(document.querySelectorAll('a[href]'), function(a){
return a.href;
});
});
this.on('http.status.404', function(resource) {
this.echo('wait, this url is 404: ' + resource.url);
});
urls.forEach(function(link) {
this.then(function(){
currentURL = link;
});
this.thenOpen(link, function(response) {
if (response == undefined || response.status >= 400) {
this.echo("failed: " + link);
}
});
});
});

How to avoid using a global variable, when closing an EventSource in internal links?

I have a web application, where some internal pages use an EventSource to receive live updates from the server.
The client code looks like this:
var LiveClient = (function() {
return {
live: function(i) {
var source = new EventSource("/stream/tick");
source.addEventListener('messages.keepalive', function(e) {
console.log("Client "+ i + ' received a message.');
});
}
};
})();
You can see a live demo on heroku: http://eventsourcetest.herokuapp.com/test/test/1. If you open the developer console, you will see a message printed every time an event is received.
The problem is that when visiting internal links, the EventSource remains open, causing messages to be printed even after the visitor moves from one page to another - so if you visit the three links on the top, you will get messages from three sources.
How can I close the previous connection after the user moves from one internal page to another?
A hacky workaround that I tried was to use a global variable for the EventSource object, like this:
var LiveClient = (function() {
return {
live_global: function(i) {
// We set source as global, otherwise we were left
// with sources remaining open after visiting internal
// pages
if (typeof source != "undefined" && source != null) {
if (source.OPEN) {
source.close();
console.log("Closed source");
}
}
source = new EventSource("/stream/tick");
source.addEventListener('messages.keepalive', function(e) {
console.log("Client "+ i + ' received a message.');
});
}
};
})();
Demo here: http://eventsourcetest.herokuapp.com/test/test_global/1, but I am looking for a solution that would avoid the use of a global variable if possible.
The HTML code that is generated is:
Page 1 |
Page 2 |
Page 3 |
<p>This is page 3</p>
<script>
$(function() {
LiveClient.live_global(3);
});
</script>
or with LiveClient.live_global(1); for the case with the global variable.
Try this. I haven't tested it. If it works, you might be able to replace LiveClient.source with this.source which is a lot cleaner imo.
var LiveClient = (function() {
return {
source: null,
live_global: function(i) {
// We set source as global, otherwise we were left
// with sources remaining open after visiting internal
// pages
if (typeof LiveClient.source != "undefined" && LiveClient.source != null) {
if (source.OPEN) {
source.close();
console.log("Closed source");
}
}
LiveClient.source = new EventSource("/stream/tick");
LiveClient.source.addEventListener('messages.keepalive', function(e) {
console.log("Client "+ i + ' received a message.');
});
}
};
})();

Firefox, javascript and iFrame performance with Jquery

I'm having a bit of a jquery javascript performance issue, specifically related to Firefox.
We have a set of vimeo embeds, and the ids are pulled in via a json file. On each click, a new video is displayed. After the video is played, the container is removed and the title cloud is put back in. After a certain number of rounds, Firefox performance seriously degrades and you get the "unresponsive script" error. This isn't happening on any other browsers. Furthermore, the profiler in FF doesn't seem to point to a root cause of the slowdown.
I believe this is caused by poor iframe performance and how FF handles iframes, but I'm not entirely sure about this. Nothing else I'm doing is anything too, mostly just stock jquery functions like empty(), remove(), prepend(), etc.
I have implemented a click counter which will just refresh the page after a certain amount of click throughs. This resolved the problem, but it's a hacky solution which I seriously dislike. I would love some ideas on the root cause of this and any advice on how to solve it.
Here's the link to the site and the specific portion mentioned:
http://www.wongdoody.com/mangles
This isn't all the code, but this is the part that gets called every click.
Also, I have tried just swapping out the src="" in the iframe, but performance still degrades.
EDIT: I can confirm this is not a memory leak, I used about:memory and with addons disabled in safe mode I'm getting decent memory usage:
359.11 MB ── private
361.25 MB ── resident
725.54 MB ── vsize
Something in the vimeo embed is slowing down the javascript engine, but it's not a memory leak. Also, this is confirmed by the fact that I can resolve the issue by just refreshing the page. If it was a memory leak I would have to close FF altogether.
function getIframeContent(vid) {
mangle_vid_id = vid;
return '<div class="vimeoContainerflex"><div class="vimeoContainer"><iframe class="vimeo" style="z-index:1;" width="100%" height="100%" frameborder="0" allowfullscreen="" mozallowfullscreen="" webkitallowfullscreen="" src="//player.vimeo.com/video/' + mangle_vid_id + '?api=1&title=0&color=89ff18&byline=0&portrait=0&autoplay=1"></iframe></div></div>';
}
function show_titles() {
$('.mangle-btn').hide();
$('.vimeoContainerflex').remove();
$('span.mangle').hide();
if ($('#mangle-titles').length < 1) {
$('#wongdoody').prepend(wd_titles_content);
}
$('#arrow').show();
if (clicks > 12) {
location.reload();
}
$('#mangle-titles span').click(function() {
clicks = clicks + 1;
$('#mangle-wrapper').remove();
var vidID = $(this).attr('data-id');
if ($('.vimeoContainer').length < 1) {
if (vidID == "home") {
$('#wongdoody').prepend(getIframeContent(getRandom()));
} else {
$('#wongdoody').prepend(getIframeContent(vidID));
}
}
$('#arrow').hide();
vimeoAPI();
});
$('#mangle-titles span').not('noscale').each(function() {
var _this = $(this);
var classname = _this.attr('class');
var scaleNum = classname.substr(classname.length - 2);
var upscale = parseInt(scaleNum);
var addition = upscale + 5;
var string = addition.toString();
_this.hover(
function() {
_this.addClass('scale' + string);
},
function() {
_this.removeClass('scale' + string);
}
);
});
}
function vimeoAPI() {
var player = $('iframe');
var url = window.location.protocol + player.attr('src').split('?')[0];
var status = $('.status');
// Listen for messages from the player
if (window.addEventListener) {
window.addEventListener('message', onMessageReceived, false);
} else {
window.attachEvent('onmessage', onMessageReceived, false);
}
// Handle messages received from the player
function onMessageReceived(e) {
var data = JSON.parse(e.data);
switch (data.event) {
case 'ready':
onReady();
break;
case 'finish':
onFinish();
break;
}
}
// Helper function for sending a message to the player
function post(action, value) {
var data = {
method: action
};
if (value) {
data.value = value;
}
var message = JSON.stringify(data);
if (player[0].contentWindow != null) player[0].contentWindow.postMessage(data, url);
}
function onReady() {
post('addEventListener', 'finish');
}
function onFinish() {
setTimeout(show_titles, 500);
}
}
Part of you're problem may be the fact that you keep adding more and more click-handlers to the spans. After each movie ends the onFinish function calls show_titles again, which attaches a new (=additional) click-handler to the $('#mangle-titles span') spans. jQuery does not remove previously attached handlers.
Try splitting the show_titles function into two. init_titles should be called only once:
function init_titles() {
if ($('#mangle-titles').length < 1) {
$('#wongdoody').prepend(wd_titles_content);
}
$('#mangle-titles span').click(function() {
$('#mangle-wrapper').remove();
var vidID = $(this).attr('data-id');
if ($('.vimeoContainer').length < 1) {
if (vidID == "home") {
$('#wongdoody').prepend(getIframeContent(getRandom()));
} else {
$('#wongdoody').prepend(getIframeContent(vidID));
}
}
$('#arrow').hide();
vimeoAPI();
});
$('#mangle-titles span').not('noscale').each(function() {
var _this = $(this);
var classname = _this.attr('class');
var scaleNum = classname.substr(classname.length - 2);
var upscale = parseInt(scaleNum);
var addition = upscale + 5;
var string = addition.toString();
_this.hover(
function() {
_this.addClass('scale' + string);
},
function() {
_this.removeClass('scale' + string);
}
);
});
}
function show_titles() {
$('.mangle-btn').hide();
$('.vimeoContainerflex').remove();
$('span.mangle').hide();
$('#arrow').show();
}
I'd recommend trying to re-use the iframe instead of wiping and re-adding. Failing that, I think you may be out of luck. Your method of closing the iFrame is fine; your browser that it's running in is not.
You're overloading window with eventListeners. Each time a user clicks a video, you're attaching an event to window that fires every time you're receiving a message.
You can easily check this by adding console.log("Fire!"), for instance, at the beginning of onMessageReceived. You'll see that this function gets triggered an awful number of times after the user has performed some clicks on videos.
That surely has an impact on performance.
Hope this helps.

Not able to get result value from google translate using phantomjs

This is my code to get the google translate of the word am giving in the program.Am using phantomjs to achieve this.
Am passing the "Word:asdasdasd" to the text area with the id "source" and triggering the click event.After that trying to check whether the result value which is wrapped under the newly created span tag or not.For that am checking whether its parent with the id "result_box" has more than one childern. If its a case,then we can say span tag is created dynamically once the value is passed to textarea.
var page = require('webpage').create();
console.log("Entering the program");
page.onConsoleMessage = function (msg) {
console.log(msg);
};
page.open("http://translate.google.com", function(status) {
if(status == 'success'){
console.log("Page loaded "+ status);
page.includeJs("//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js", function() {
setTimeout(function() {
var data = page.evaluate(function() {
$('#source').val("asdasdasd");
var ev = document.createEvent("MouseEvents");
ev.initEvent("click", true, true);
document.querySelector("input[id='gt-submit']").dispatchEvent(ev);
console.log("Click on page ");
if( $('#result_box').children.length > 0 ){
console.log("New Span included");
console.log("value: " + $('#result_box').val());
}
else{
console.log("No span");
}
});
console.log("Exiting the program");
phantom.exit()
}, 10000);
});
}
});
Output:
Entering the program
Page loaded success
Click on page
New Span included
value:
Exiting the program
My question here is it says new span tag is created but am not able to get the value of it.Its strange.Whats wrong with the above code?.
Please anyone point me the solution.
Thanks in advance
Update:
Adding points to #Neha,
this worked for me.
setTimeout(function(){
console.log("After 5 sec value: " + document.getElementById('result_box').innerHTML);
},5000);

Override "Error Loading Page" for network failure in js file

I have JQuery Mobile-1.0.js file.
// Load a page into the DOM.
$.mobile.loadPage = function (url, options) {
// This function uses deferred notifications to let callers
// know when the page is done loading, or if an error has occurred.
var deferred = $.Deferred(),
// The default loadPage options with overrides specified by
// the caller.
settings = $.extend({}, $.mobile.loadPage.defaults, options),
// The DOM element for the page after it has been loaded.
page = null,
// If the reloadPage option is true, and the page is already
// in the DOM, dupCachedPage will be set to the page element
// so that it can be removed after the new version of the
// page is loaded off the network.
dupCachedPage = null,
// determine the current base url
findBaseWithDefault = function () {
var closestBase = ($.mobile.activePage && getClosestBaseUrl($.mobile.activePage));
return closestBase || documentBase.hrefNoHash;
},
// The absolute version of the URL passed into the function. This
// version of the URL may contain dialog/subpage params in it.
absUrl = path.makeUrlAbsolute(url, findBaseWithDefault());
// If the caller provided data, and we're using "get" request,
// append the data to the URL.
if (settings.data && settings.type === "get") {
absUrl = path.addSearchParams(absUrl, settings.data);
settings.data = undefined;
}
// If the caller is using a "post" request, reloadPage must be true
if (settings.data && settings.type === "post") {
settings.reloadPage = true;
}
// The absolute version of the URL minus any dialog/subpage params.
// In otherwords the real URL of the page to be loaded.
var fileUrl = path.getFilePath(absUrl),
// The version of the Url actually stored in the data-url attribute of
// the page. For embedded pages, it is just the id of the page. For pages
// within the same domain as the document base, it is the site relative
// path. For cross-domain pages (Phone Gap only) the entire absolute Url
// used to load the page.
dataUrl = path.convertUrlToDataUrl(absUrl);
// Make sure we have a pageContainer to work with.
settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;
// Check to see if the page already exists in the DOM.
page = settings.pageContainer.children(":jqmData(url='" + dataUrl + "')");
// If we failed to find the page, check to see if the url is a
// reference to an embedded page. If so, it may have been dynamically
// injected by a developer, in which case it would be lacking a data-url
// attribute and in need of enhancement.
if (page.length === 0 && dataUrl && !path.isPath(dataUrl)) {
page = settings.pageContainer.children("#" + dataUrl)
.attr("data-" + $.mobile.ns + "url", dataUrl);
}
// If we failed to find a page in the DOM, check the URL to see if it
// refers to the first page in the application. If it isn't a reference
// to the first page and refers to non-existent embedded page, error out.
if (page.length === 0) {
if ($.mobile.firstPage && path.isFirstPageUrl(fileUrl)) {
// Check to make sure our cached-first-page is actually
// in the DOM. Some user deployed apps are pruning the first
// page from the DOM for various reasons, we check for this
// case here because we don't want a first-page with an id
// falling through to the non-existent embedded page error
// case. If the first-page is not in the DOM, then we let
// things fall through to the ajax loading code below so
// that it gets reloaded.
if ($.mobile.firstPage.parent().length) {
page = $($.mobile.firstPage);
}
} else if (path.isEmbeddedPage(fileUrl)) {
deferred.reject(absUrl, options);
return deferred.promise();
}
}
// Reset base to the default document base.
if (base) {
base.reset();
}
// If the page we are interested in is already in the DOM,
// and the caller did not indicate that we should force a
// reload of the file, we are done. Otherwise, track the
// existing page as a duplicated.
if (page.length) {
if (!settings.reloadPage) {
enhancePage(page, settings.role);
deferred.resolve(absUrl, options, page);
return deferred.promise();
}
dupCachedPage = page;
}
var mpc = settings.pageContainer,
pblEvent = new $.Event("pagebeforeload"),
triggerData = { url: url, absUrl: absUrl, dataUrl: dataUrl, deferred: deferred, options: settings };
// Let listeners know we're about to load a page.
mpc.trigger(pblEvent, triggerData);
// If the default behavior is prevented, stop here!
if (pblEvent.isDefaultPrevented()) {
return deferred.promise();
}
if (settings.showLoadMsg) {
// This configurable timeout allows cached pages a brief delay to load without showing a message
var loadMsgDelay = setTimeout(function () {
$.mobile.showPageLoadingMsg();
}, settings.loadMsgDelay),
// Shared logic for clearing timeout and removing message.
hideMsg = function () {
// Stop message show timer
clearTimeout(loadMsgDelay);
// Hide loading message
$.mobile.hidePageLoadingMsg();
};
}
if (!($.mobile.allowCrossDomainPages || path.isSameDomain(documentUrl, absUrl))) {
deferred.reject(absUrl, options);
} else {
// Load the new page.
$.ajax({
url: fileUrl,
type: settings.type,
data: settings.data,
dataType: "html",
success: function (html, textStatus, xhr) {
//pre-parse html to check for a data-url,
//use it as the new fileUrl, base path, etc
var all = $("<div></div>"),
//page title regexp
newPageTitle = html.match(/<title[^>]*>([^<]*)/) && RegExp.$1,
// TODO handle dialogs again
pageElemRegex = new RegExp("(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)"),
dataUrlRegex = new RegExp("\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?");
// data-url must be provided for the base tag so resource requests can be directed to the
// correct url. loading into a temprorary element makes these requests immediately
if (pageElemRegex.test(html)
&& RegExp.$1
&& dataUrlRegex.test(RegExp.$1)
&& RegExp.$1) {
url = fileUrl = path.getFilePath(RegExp.$1);
}
if (base) {
base.set(fileUrl);
}
//workaround to allow scripts to execute when included in page divs
all.get(0).innerHTML = html;
page = all.find(":jqmData(role='page'), :jqmData(role='dialog')").first();
//if page elem couldn't be found, create one and insert the body element's contents
if (!page.length) {
page = $("<div data-" + $.mobile.ns + "role='page'>" + html.split(/<\/?body[^>]*>/gmi)[1] + "</div>");
}
if (newPageTitle && !page.jqmData("title")) {
if (~newPageTitle.indexOf("&")) {
newPageTitle = $("<div>" + newPageTitle + "</div>").text();
}
page.jqmData("title", newPageTitle);
}
//rewrite src and href attrs to use a base url
if (!$.support.dynamicBaseTag) {
var newPath = path.get(fileUrl);
page.find("[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]").each(function () {
var thisAttr = $(this).is('[href]') ? 'href' :
$(this).is('[src]') ? 'src' : 'action',
thisUrl = $(this).attr(thisAttr);
// XXX_jblas: We need to fix this so that it removes the document
// base URL, and then prepends with the new page URL.
//if full path exists and is same, chop it - helps IE out
thisUrl = thisUrl.replace(location.protocol + '//' + location.host + location.pathname, '');
if (!/^(\w+:|#|\/)/.test(thisUrl)) {
$(this).attr(thisAttr, newPath + thisUrl);
}
});
}
//append to page and enhance
// TODO taging a page with external to make sure that embedded pages aren't removed
// by the various page handling code is bad. Having page handling code in many
// places is bad. Solutions post 1.0
page
.attr("data-" + $.mobile.ns + "url", path.convertUrlToDataUrl(fileUrl))
.attr("data-" + $.mobile.ns + "external-page", true)
.appendTo(settings.pageContainer);
// wait for page creation to leverage options defined on widget
page.one('pagecreate', $.mobile._bindPageRemove);
enhancePage(page, settings.role);
// Enhancing the page may result in new dialogs/sub pages being inserted
// into the DOM. If the original absUrl refers to a sub-page, that is the
// real page we are interested in.
if (absUrl.indexOf("&" + $.mobile.subPageUrlKey) > -1) {
page = settings.pageContainer.children(":jqmData(url='" + dataUrl + "')");
}
//bind pageHide to removePage after it's hidden, if the page options specify to do so
// Remove loading message.
if (settings.showLoadMsg) {
hideMsg();
}
// Add the page reference and xhr to our triggerData.
triggerData.xhr = xhr;
triggerData.textStatus = textStatus;
triggerData.page = page;
// Let listeners know the page loaded successfully.
settings.pageContainer.trigger("pageload", triggerData);
deferred.resolve(absUrl, options, page, dupCachedPage);
},
error: function (xhr, textStatus, errorThrown) {
//set base back to current path
if (base) {
base.set(path.get());
}
// Add error info to our triggerData.
triggerData.xhr = xhr;
triggerData.textStatus = textStatus;
triggerData.errorThrown = errorThrown;
var plfEvent = new $.Event("pageloadfailed");
// Let listeners know the page load failed.
settings.pageContainer.trigger(plfEvent, triggerData);
// If the default behavior is prevented, stop here!
// Note that it is the responsibility of the listener/handler
// that called preventDefault(), to resolve/reject the
// deferred object within the triggerData.
if (plfEvent.isDefaultPrevented()) {
return;
}
// Remove loading message.
if (settings.showLoadMsg) {
// Remove loading message.
hideMsg();
//show error message
$("<div class='ui-loader ui-overlay-shadow ui-body-e ui-corner-all'><h1>" + $.mobile.pageLoadErrorMessage + "</h1></div>")
.css({ "display": "block", "opacity": 0.96, "top": $window.scrollTop() + 100 })
.appendTo(settings.pageContainer)
.delay(800)
.fadeOut(400, function () {
$(this).remove();
});
}
deferred.reject(absUrl, options);
}
});
}
return deferred.promise();
};
This is the code for showing an error message "Error Loading Page" for error in page. Here i want to show alert message for net connection failure as "Please check your net connection" instead of the below image.
Note: I dont want to change the pageloaderrormessage. want to stop to get the page error messages, instead of that i will enable my network error condition as in Show Network Error in android. If the user pressed "Ok" in alert dialog i'll navigate them into Reload.html.
Please tell me where i can check that condition and where i have to change the error message?
As both #shkschneider and #codemonkey have suggested you need to set this option on mobileinit
Example:
$(document).bind("mobileinit", function(){
$.mobile.pageLoadErrorMessage = "Please check your net connection";
});
Linking the jQM 1.0.1 docs:
http://jquerymobile.com/demos/1.0.1/docs/api/globalconfig.html
Here is a example:
http://jquerymobile.com/demos/1.0.1/docs/config/pageLoadErrorMessage.html ( click the "or Try this broken link" button )
Now if you have the ability to upgrade jQM to 1.1.1 you might try something like this:
//use theme swatch "b", a custom message, and no spinner
$.mobile.showPageLoadingMsg("b", "Please check your net connection", true);
// hide after delay
setTimeout( $.mobile.hidePageLoadingMsg, 1500 );
Docs:
http://jquerymobile.com/demos/1.1.1/docs/api/methods.html
UPDATE:
Another thought is to use a plugin to achieve something like you want, Does something like this work?
http://dev.jtsage.com/jQM-SimpleDialog/demos/bool.html
Simply use:
$(document).bind("mobileinit", function(){
$.mobile.pageLoadErrorMessage("Please check your netconnection");
});
http://jquerymobile.com/test/docs/api/globalconfig.html
Set the pageLoadErrorMessage as described here http://jquerymobile.com/demos/1.1.1/docs/api/globalconfig.html
EDIT
If you want to handle the behaviour in a custom way, set loadingMessage to false. This prevents the loading message from being displayed. You can bind to the pageloadfailed (described here http://jquerymobile.com/demos/1.1.1/docs/api/events.html) and add add your custom handling logic in the event handler.

Categories

Resources