I have problems to treat things after 'click' inside a loop that is inside a evaluate function. I don't know another way to treat that.
'Le' Code...
links = this.evaluate(function(){
story_boxes = __utils__.getElementsByXPath('//div[#id="contentCol"]//div[#id="stream_pagelet"]/div[contains(#id,"topnews_main_stream")]/div/div//div[contains(#data-ft,"{")]');
for(x=0;x<=story_boxes.length;x++){
story_box = story_boxes[x];
boxID = story_box.getAttribute('id');
//Is this feed a sponsored?
sponsored = story_box.querySelector('a.uiStreamSponsoredLink');
if(sponsored){
console.log("SPONSORED? " + sponsored );
try{
elink = story_box.querySelector('div > div > div > div > div > div > a');
}
catch(e){
console.log("Ooops! An error occured, sorry! " + e);
}
if(elink){
console.log("FOUND IT!");
crap = setTimeout( function(){
elink.click(); //where can I treat this?
}, 1000);
break;
}
else {
console.log("NO DONUT FOR YOU!");
}
console.log("\n\n#########");
}
}
console.log("#####");
});
//... more irrelevant things
The question is how can I treat each click ? I need just the subtree of the result of the click. I don't know if I'm clear here. Probably not... :P
I suspect you're asking how to handle the result of the click.
If the click leads to a new page load, then you can simply add a waiting step after your code and let CasperJS handle the loading of the page:
casper.then(function(){
var success = this.evaluate(function(){
...
if(elink){
console.log("FOUND IT!");
crap = setTimeout( function(){
elink.click(); //where can I treat this?
}, 1000);
return true;
}
...
return false;
});
if (success) {
this.wait(1100, function(){
// TODO: do something with the loaded page
})
} else {
// TODO: do something on fail
}
})
If the click changes the existing page, then you need to wait for such a change. There are a lot of functions that CasperJS provides for such a case. All of them begin with wait. For example:
casper.then(function(){
this.evaluate(...);
}).wait(1100).waitForSelector('some new element selector', function _then(){
// TODO: do something with the loaded page
}, function _onTimeout(){
// TODO: do something on fail
})
Related
I am using this code to infinite load a page on squarespace. My problem is the reloading doesn't capture the filtering that I have set up in my url. It cannot seem to 'see' the variables or even the url or categoryFilter in my collection. I've tried to use a .var directive but the lazy loaded items cannot see the scope of things defined before it. I'm running out of ideas here please help!
edit: I've since found the answer but gained another question.
I was able to use window.location.href instead of window.location.pathname to eventually get the parameters that way. Except this doesn't work in IE11 so now I have to search for this.
<script>
function infiniteScroll(parent, post) {
// Set some variables. We'll use all these later.
var postIndex = 1,
execute = true,
stuffBottom = Y.one(parent).get('clientHeight') + Y.one(parent).getY(),
urlQuery = window.location.pathname,
postNumber = Static.SQUARESPACE_CONTEXT.collection.itemCount,
presentNumber = Y.all(post).size();
Y.on('scroll', function() {
if (presentNumber >= postNumber && execute === true) {
Y.one(parent).append('<h1>There are no more posts.</h1>')
execute = false;
} else {
// A few more variables.
var spaceHeight = document.documentElement.clientHeight + window.scrollY,
next = false;
/*
This if statement measures if the distance from
the top of the page to the bottom of the content
is less than the scrollY position. If it is,
it's sets next to true.
*/
if (stuffBottom < spaceHeight && execute === true) {
next = true;
}
if (next === true) {
/*
Immediately set execute back to false.
This prevents the scroll listener from
firing too often.
*/
execute = false;
// Increment the post index.
postIndex++;
// Make the Ajax request.
Y.io(urlQuery + '?page=' + postIndex, {
on: {
success: function (x, o) {
try {
d = Y.DOM.create(o.responseText);
} catch (e) {
console.log("JSON Parse failed!");
return;
}
// Append the contents of the next page to this page.
Y.one(parent).append(Y.Selector.query(parent, d, true).innerHTML);
// Reset some variables.
stuffBottom = Y.one(parent).get('clientHeight') + Y.one(parent).getY();
presentNumber = Y.all(post).size();
execute = true;
}
}
});
}
}
});
}
// Call the function on domready.
Y.use('node', function() {
Y.on('domready', function() {
infiniteScroll('#content','.lazy-post');
});
});
</script>
I was able to get this script working the way I wanted.
I thought I could use:
Static.SQUARESPACE_CONTEXT.collection.itemCount
to get {collection.categoryFilter} like with jsont, like this:
Static.SQUARESPACE_CONTEXT.collection.categoryFilter
or this:
Static.SQUARESPACE_CONTEXT.categoryFilter
It didn't work so I instead changed
urlQuery = window.location.pathname
to
urlQuery = window.location.href
which gave me the parameters I needed.
The IE11 problem I encountered was this script uses
window.scrollY
I changed it to the ie11 compatible
Window.pageYOffset
and we were good to go!
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.
Forgive my naivety, this probably is quite obvious, I just can't see it now.
Please tell me what is wrong with the following code:
$('#iframe1').load(function(){
$('#iframe2').load(function(){
alert('loaded!');
});
});
The idea is to wait until both iframes have fully loaded, then alert "loaded" - of course this is a simplified example for the sake of stack.
The script sits in script tags at the end of the body of the html doc.
#Quertiy answer is perfectly fine, but not very jQuery-ish. It is hard-coded for 2 iframes only.
The beauty of jQuery is that you can make it work for the most number of people, with as little friction as possible.
I've advised a very simplistic plugin that does nearly what is present on that answer, but in a more open way. It not only works on iframes, but also on images, audio, video and whatever has a onload event!
Without further due, here's the code:
(function($){
$.fn.extend({allLoaded: function(fn){
if(!(fn instanceof Function))
{
throw new TypeError('fn must be a function');
}
var $elems = this;
var waiting = this.length;
var handler = function(){
--waiting;
if(!waiting)
{
setTimeout(fn.bind(window), 4);
}
};
return $elems.one('load.allLoaded', handler);
}});
})(window.jQuery);
It works by adding a load handler to every element in that selection. Since it is a plugin, you can use in whatever way you decide to use it.
Here's an example, that loads 30 random images:
//plugin code
(function($){
$.fn.extend({allLoaded: function(fn){
if(!(fn instanceof Function))
{
throw new TypeError('fn must be a function');
}
var $elems = this;
var waiting = this.length;
var handler = function(){
--waiting;
if(!waiting)
{
setTimeout(fn.bind(window), 4);
}
};
return $elems.one('load.allLoaded', handler);
}});
})(window.jQuery);
$(function(){
//generates the code for the 30 images
for(var i = 0, html = ''; i < 30; i++)
html += '<img data-src="http://lorempixel.com/g/400/200/?_=' + Math.random() + '">';
//stuffs the code into the body
$('#imgs').html(html);
//we select all images now
$('img')
.allLoaded(function(){
//runs when done
alert('loaded all')
})
.each(function(){
//the image URL is on a `data` attribute, to delay the loading
this.src = this.getAttribute('data-src')
})
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"></script>
<div id="imgs"></div>
Your problem, as said before many times, is that you have a load event attached to your iframe. That event is fired everytime the content change.
After that, you set a new event on #iframe2. When it's content changes, it will fire events left and right, above and beyound what you wish!
The best aproach is to keep track of which ones you loaded or not. After all have been loaded, you simply run the function.
The problem is that you're waiting until #iframe1 loads before you attach a handler for #iframe2 loading. So if #iframe2 loads first, you'll never get your callback.
Instead, watch the load event on both of them and track which ones you've seen:
var seen1 = false,
seen2 = false;
$('#iframe1, #iframe2').load(function(){
if (this.id == "iframe1") {
seen1 = true;
} else {
seen2 = true;
}
if (seen1 && seen2) {
alert('loaded!');
}
});
Why do you expect 2nd iframe to load after the first one?
~function () {
var loaded = 0;
$('#iframe1, #iframe2').load(function (){
if (++loaded === 2) {
alert('loaded!');
}
});
}()
I am trying to determine when a user closes their connection. The problem is that, when I try to use this._session.socket.on("close",...) it also registers when the user refreshes.
Here is my code:
Meteor.publish("friends", function () {
var id = this._session.userId;
this._session.socket.on("close", Meteor.bindEnvironment(function()
{
// This logs when the user disconnects OR refreshes
console.log(id);
}, function(e){console.log(e)}))
return Meteor.users.find({...});
});
How do I differentiate between a refresh and a real disconnection?
EDIT: I would really like to avoid using a 'keep alive' function if possible.
Ok, this is a little bit hacky but I'm not sure if there is a better solution. This requires the mizzao:user-status package. The way I solved this problem is to call a meteor method inside the "on close" function that starts polling the database at 5 second intervals and checks if the user's status is online. After a set amount of time (I said 65 seconds), if the user has come online I know it was a refresh.
Anyway, the above is a little bit confusing, so here is the code:
//server
Meteor.publish("some_collection", function(){
var id = this._session.userId;
this._session.socket.on("close", Meteor.bindEnvironment(function(){
Meteor.call("connectionTest", id);
}, function(e){console.log(e)}));
return Meteor.users.find({..});
});
//Meteor method
Meteor.methods({
connectionTest: function(userId){
this.unblock();
var i = 0;
var stop = false;
var id = Meteor.setInterval(function(){
var online = Meteor.users.findOne(userId).status.online;
if(!online){
console.log("offline");
}
else{
stop = true;
console.log("still online");
}
i++;
if(stop || i > 12){
if(online){
//do something
}
else{
// do something else
}
Meteor.clearInterval(id);
}
}, 5000);
}
});
i have a simple pagination pagination code that had been writen with raw javascript codes
function ChangeForumPage(e){
if(busy == 0)
{
switch(e)
{
case "Next":
page = p+1;
p++;
break;
case "Prev":
if(p>1)
{
page = p-1;
p--;
}
break;
}
xmlHttp=GetXmlHttpObject();
if (xmlHttp==null)
{
alert ("Your browser does not support AJAX!");
return;
}
var url="MTForumsBlock.php?req=LastTopics&p="+page;
xmlHttp.onreadystatechange=ChangeForumPageProces;
xmlHttp.open("GET",url,true);
xmlHttp.send(null);
}
}
function ChangeForumPageProces(e){
if(xmlHttp.readyState==4)
{
document.getElementById("MTForumBlock").innerHTML=xmlHttp.responseText;
document.getElementById("MTFloader").innerHTML='';
busy = 0;
}else{
document.getElementById("MTFloader").innerHTML='<img src="images/loader.gif" />';
busy = 1;
}
}
and i need to have the same possiblity with jquery
but i don know how to do that !
it should only change the page if i click next or previous button
You might want to use the jQuery pagination plugins instead and they work out of the box, you just specify your settings:
Pagination (Demo)
Making a jQuery pagination system (Demo)
Sure, not a problem. The following uses the multiple selector to attach a click event handler to your previous and next buttons. It then uses the load method to get the HTML that you will use to populate your #MTForumBlock:
var busy = false;
var p = 1;
$('#next, #prev').click(function(e){
// stop the link's href from being followed
e.preventDefault();
// check if we're in the middle of a request
if(busy) return;
busy = true;
// Update the page variable
if( $(this).attr('id') == 'next' ){
p++;
} else if ( p > 1 ) {
p--;
}
// Add the loader image
var loader = $('<img src="images/loader.gif" /');
$('#MTFloader').append(loader);
$('#MTForumBlock').load('MTForumsBlock.php?req=LastTopics&p='+p, function() {
// Remove the loader image once the AJAX is finished and mark finished
loader.remove();
busy = false;
});
)};
The HTML would look something like this:
Next
Previous