Count every elements inside an iFrame with CasperJS - javascript

I am searching for a way to count every element inside a div. The problem is, the div is inside an iFrame.
casper.then(function() {
this.echo(this.evaluate(function() {
__utils__.getElementByXPath('//*[#id="main-frame"]', __utils__.findAll('#design-scrollbox-0')).length;
}));
});
What I trying to do above is:
Getting the iFrame with XPath
Collecting every element
and finally getting the length of the returned array.
I'd love if you could point my in the right direction. Sadly, I cannot use a jQuery version > 1.7, so jQuery.contents is not an option.

You could inject some other jQuery version, but you don't need it, since CasperJS provides a convenient way of changing into the iframe and doing stuff in its context. casper.withFrame is a shortcut for the PhantomJS functions page.switchToChildFrame and page.switchToParentFrame. It creates a new step from the callback where further steps can be nested.
There are certainly different types to count elements, but probably the easiest is using
casper.getElementsInfo(selector).length
This is the function for printing the number of links I use for the proofs-of-concept:
function printLinks(){
try{
this.echo("elements: " + this.getElementsInfo("a").length);
} catch(e) {
this.echo("CAUGHT: " + e);
}
}
Proof-of-concept for iframes:
casper.start("http://jsfiddle.net/anjw2gnr/1/")
.then(printLinks)
.withFrame(0, printLinks)
//.withFrame(1, printLinks)
.then(function() {
console.log('Done', this.getCurrentUrl());
})
.run();
prints
elements: 33
elements: 2
Proof-of-concept for frames:
casper.start("https://docs.oracle.com/javase/7/docs/api/index.html")
.then(printLinks)
.withFrame(0, printLinks)
.withFrame(1, printLinks)
.then(function() {
console.log('Done', this.getCurrentUrl());
})
.run();
prints
CAUGHT: CasperError: Cannot get information from a: no elements found.
elements: 210
elements: 4024
So, if you want to count elements, but don't want to use a try-catch block, this is better:
casper.exists(selector) ? casper.getElementsInfo(selector).length : 0

You can use Casper's switchToChildFrame (see for example this link) to get 'into' the iframe.
(untested):
casper.then(function() {
// switch the context to first child frame
this.page.switchToChildFrame(0);
// ... execute casper commands in iframe context
// and switch back to parent frame if you need to
this.page.switchToParentFrame();
// ... execute casper commands in parent page context
});
To count elements you could try (untested also):
casper.then(function() {
var amount_elements = this.evaluate(function() {
var elements = document.querySelectorAll("#design-scrollbox-0");
// you can store in the DOM:
window.amount_elements = elements.length;
// and return the amount
return elements.length;
});
});
// the object stored in the DOM can be used later on:
casper.then(function() {
var amount_elements = this.evaluate(function() {
return window.amount_elements;
});
});

You can always do this $('ul').children().length this will tell you all the children element in the selector. Hope it helps.

Related

Javascript, passing my string as an argument turns it to a 0

I'm using scrollmagic.io and am making an anchor navigation menu. I'm following this tutorial. The scroll works! Except it was scrolling back to the beginning and not to the page it should be at.
Here is my code:
// init controller
var controller = new ScrollMagic.Controller();
// animate scroll instead of a jump
controller.scrollTo(function(target) {
console.log('scroooooll');
console.log('target = '+target); // THIS IS PRINTING 0
console.log(typeof(target));
/* Commenting out what it should do for simplicity. */
});
// scroll action when you click the nav links
$(document).on('click', 'a[href^=#]', function(e) {
var id = $(this).attr('href'); // get the href of clicked link
if ($(id).length > 0) { // not empty links
e.preventDefault(); // prevent normal link action
// this is the function call
console.log('click');
console.log('id = '+id); // IT PRINTS CORRECT HERE
console.log(typeof(id));
controller.scrollTo(id); // scroll on click
// update the URL
if (window.history && window.history.pushState) {
history.pushState("", document.title, id);
}
}
});
And here is the output of my console log:
click
id = #{the-href-value}
string
scroooooll
target = 0
number
My Javascript is pretty rusty, but this doesn't seem right to me. Why is it changing my variable from a string to a 0 when I pass it as a parameter?
From the documents:
"This function will be used for future scroll position modifications.
This provides a way for you to change the behaviour of scrolling and
adding new behaviour like animation. The function receives the new
scroll position as a parameter and a reference to the container
element using this. It may also optionally receive an optional
additional parameter (see below)"
So, the first parameter is passed by controller.
You will get your parameter after that.
http://scrollmagic.io/docs/ScrollMagic.Controller.html#scrollTo
Try printing console.log(args);
controller.scrollTo(function(scrollPos, targetHrefISent) {
console.log('scroooooll');
console.log('target = '+targetHrefISent); // THIS IS PRINTING 0
console.log(typeof(targetHrefISent));
/* Commenting out what it should do for simplicity. */
});

Click each element then scrape with horseman

I'm using a node.js module called horseman to scrape some data from a site which contains JavaScript. I'm having trouble figuring out how to click on each span element IF it contains a certain element within it, table in this case. This will expand that element and produce data available to scrape, which right now is hidden.
What I have right now
horseman
.open(url)
.click("span.title")
.waitforSelector("span.title")
.then(scrape)
The scrape function:
function scrape() {
return new Promise(function (resolve, reject) {
return getLinks()
.then(function (newLinks) {
links = links.concat(newLinks);
if (links.length < 1)
return horseman
.then(scrape);
}
})
.then(resolve);
});
}
And the getlinks function()
var links = [];
function getLinks() {
return horseman.evaluate(function () {
var links = [];
$("span.title").each(function (item) {
var link = {
title: $(this).text()
};
links.push(link);
});
return links;
});
}
My initial thoughts were that in the getLinks() function I could check if item contains table then click and then scrape, but not sure how to implement it.
The idea is to expand all the span elements, that are not already expanded, which means the data is visible and able to be scraped. I've hit a brick wall on what to do, so any help would be great!
The following code :
horseman
.open(url)
.click("span.title")
.waitforSelector("span.title")
.then(scrape)
...will not work because .click() horseman action only address single elements. Instead, you can try the following code that will work on many elements :
horseman
.open(url)
.evaluate(clickItems)
.waitforSelector("span.title XXX")
.then(scrape)
Where :
XXX should be the selector of the content inside the span.title (so the waitForSelector will actually wait). For example, let's consider this markup :
<span class="title"><!-- this is the clickable item -->
<table>...</table>
<div class="show-on-click">Blah blah</div>
</span>
In the above example, you would use .waitForSelector('span.item .show-on-click'). You have to find which selector does not exist until the data appears. (or use .wait(1000) instead)
clickItem function is defined as following (I see that you use jQuery so I will as well)
function clickItems() {
var $items = $('span.title:has(table)');
$items.each(function(index, $item) {
$item.click();
});
}
Note: This will click on all the elements span.title. You can modify the click element to add a table presence test in each $item, but I guess you can omit that if the other clicks do not do anything.

Looping through generated HTML with jQuery

I know if I wanted to bind events to generated HTML, I'd need to use something like .on(), but I've only used it when binding events like .click().
I'm creating a web app that applys a list of colors. Colors are generated from a JSON file. Once fetched, I add it to the page, with certain information contained in attributes. I'd like to do something with the new generated HTML, which is list-elements. But what console.log() is showing me is there is nothing in the parent ul. Even though on the page I see the newly added content.
Here's the entire code based around it.
var setColors = function(){
getColors = function(){
$.getJSON('js/colors.json', function(colors) {
$.each(colors, function(i, colors) {
//console.log(colors);
$('<li>', {
text: colors['color'],
'name' : colors['color'],
'data-hex' : colors['hex'],
'data-var' : colors['var']
}).appendTo('#picker');
})
});
addColors();
}
addColors = function(){
var el = $('#picker').children;
$(el).each(function(){
console.log($(this));
});
}
return getColors();
}
$(function(){
setColors();
});
addColors() is where I'm having trouble with. The error says 'Uncaught TypeError: Cannot read property 'firstChild' of null. How can I work with the newly generated HTML?
You are missing parentheses on the children method:
var el = $('#picker').children();
Also, if you want the addColor method to be executed on the newly generated html, then you must add a call to it after the html is generated, from within the getJSON callback method.
addColors = function(){
var el = $('#picker').children;
$(el).each(function(){
console.log($(this));
});
}
A few issues:
missing end semi-color
missing parentheses on .children()
children() returns a jQuery object, no need for $(el)
Updated:
window.addColors = function(){
var $el = $('#picker').children();
$el.each(function(){
// do stuff here, but could attach each() to above, after children()
});
};

Appending hash/subpage to URL when loading content in expanding divs

I'm loading in separate .html documents inside divs with this code:
JS
$('.thumbnail').click(function() {
var idStr = ("project/"+$(this).attr('id')) + " #projectcontainer";
$('#projectcontainer').animate({opacity:0});
$('#projectcontainer').hide().load(idStr,function(){
$(this).slideDown(500).animate({opacity:1}, function() {
$.scrollTo('#gohere',800);
$('#close').fadeIn(500).css({'display': 'block', 'height': '25px'});
});
});
});
HTML
<div class="thumbnail" id="atmotype.html">
<img src="image.whatever">
</div>
It all works as intended but I also wanna append an ID when you open a project, and also be able to link directly to said content (already expanded in the div). I've been trying around and can't come up with a solution, and that being said I'm pretty awful with JS in general.
Would really appreciate if someone could enlighten me on how this works.
Right now when you click your .thumbnail element, it is firing your click() event and using $(this).attr('id') for the hash/scroll. To make this run when the page load, you should probably break it out to a separate function that takes the ID as a parameter, and then call this function from your click() event as well as a generic page load using a parameter in location.hash.
$(document).ready(function(){
if (location.hash.length>0){
/* this assumes the page to load is the only thing in the
hash, for example /page.php#project.html */
var hash = location.hash.substring(1); // get hash and remove #
addHashAndScroll(hash); // call function with page name
}
$('.thumbnail').click(function() {
addHashAndScroll($(this).attr('id')); // pass ID value to function
});
}
// this function contains most of your original script
function addHashAndScroll(id){
var idStr = "project/"+ id + "#projectcontainer";
// rest of your code
}
UPDATE:
This is the thing about js it all makes sense when explained but executing it is a bitch. Anyways thanks alot for helping out. Based on your explanation what I get is:
$(document).ready(function() {
if (location.hash.length > 0) {
/* this assumes the page to load is the only thing in the
hash, for example /page.php#project.html */
var hash = location.hash.substring(1); // get hash and remove #
addHashAndScroll(hash); // call function with page name
}
$('.thumbnail').click(function() {
addHashAndScroll($(this).attr('id')); // pass ID value to function
});
}
// this function contains most of your original script
function addHashAndScroll(id) {
var idStr = "project/" + id + "#projectcontainer";
$('#projectcontainer').animate({
opacity: 0
});
$('#projectcontainer').hide().load(idStr, function() {
$(this).slideDown(500).animate({
opacity: 1
}, function() {
$.scrollTo('#gohere', 800);
$('#close').fadeIn(500).css({
'display': 'block',
'height': '25px'
});
});
});
}
I've tried to fiddle around with the closures and whatever minimal experience i have in bug testing js but i keep getting errors originating from this line:
function addHashAndScroll(id) {

Access javascript object directly through DOM traversing with jQuery

I'm working on a simple client side interface where I have a jQuery object that I want to access directly when clicking on a hyperlink. Simplified code:
<div class="controls">
<div class="score">
<a class="button" href="/add">Add points!</a>
</div>
</div>
$(".controls").myControls();
$.fn.myControls = function() {
return $.extend(this, $.myControls).initialize();
}
$.myControls = {
initialize: function() {
this.scoreElement = $("div.score", this);
this.linkElement = $("a", this.scoreElement);
this.scoreElement.score = 0;
var _this = this;
this.linkElement.click(function() {
_this.clickHandler(this);
});
},
clickHandler: function(element) {
var scoreElement = $(element).parent();
scoreElement.score = 1;
}
}
Explanation: .controls element has .score element which doubles as a container for score information (this.scoreElement.score). When I click on a link within the .score element, I find the parent element, which is the same element in the DOM as this.scoreElement and try to set its score property to 1. Obviously, this won't work, as the local scoreElement.score property in the clickHandler method is "undefined".
So here's my question: is there a simple way to access my this.scoreElement object directly through traversing the DOM with jQuery?
Surely I can check if this.scoreElement == $(element).parent() in some way and then access the right property in my this.scoreElement object, but direct access would be more elegant and robust. Is this possible? Am I going at it the wrong way? Thanks!
PS: Ignore the fact I use parent() to find the scoreElement, I only use it to illustrate my problem. Unless it is part of the problem, in that case don't ignore :)
While it's certainly possible to use your own 'control-object' to store the related data, I usually prefer to rely on jQuery doing it - with .data() method, like this:
$(this.scoreElement).data('score', 0); // in initialize()
$(this).parent().data('score', 1); // in clickHandler()
This approach allows me to scale more easily, as I never have to fear 'overlapping' issues, using a single 'control' object rather than object-for-element.
I would think that if you used jQuery's proxy function for your click handler, you then could just go this.scoreElement inside of clickHandler and you wouldn't even need to traverse the DOM. Like this:
$.myControls = {
initialize: function() {
this.scoreElement = $("div.score", this);
this.linkElement = $("a", this.scoreElement);
this.scoreElement.score = 0;
this.linkElement.click($.proxy(this.clickHandler, this));
},
clickHandler: function(event) {
var element = event.target;
this.scoreElement.score = 1;
}
}
After progressive simplification (and storing the score slightly differently) I get the code below, in which scoreElement is discovered once per .controls div, then held in a closure to make it available to its corresponding click handler. You could alternatively use .closest() - see commented out line.
$.fn.myControls = function() {
return this.each(function() {
var scoreElement = $("div.score", $(this));
scoreElement.data('score', 0);
$("a", scoreElement).on('click', function() {
scoreElement.data('score', 1);//get scoreElement from closure formed by the outer "each" function.
//$(this).closest(".score").data('score', 1);//alternative to the line above, not requiring closure.
});
});
};
Call as in the question with:
$(".controls").myControls();
This is so trivial and unidimensional it doesn't really warrant, in its own right, a jQuery plugin. Unless there was some compelling reason for a plugin (eg. reuse or the need for closely related methods), then I would phrase it as follows :
$(".controls").each(function() {
var scoreElement = $("div.score", $(this));
scoreElement.data('score', 0);
$("a", scoreElement).on('click', function() {
scoreElement.data('score', 1);//get scoreElement from closure formed by the outer "each" function.
//$(this).closest(".score").data('score', 1);//alternative to line above, not requiring closure.
});
});
That's the same code with the plugin wrapper removed and attached directly to the same base jQuery object.
And if you really wanted, you could write the whole thing in three lines as follows:
$(".controls").find("div.score").data('score', 0).find("a.button").on('click', function() {
$(this).closest(".score").data('score', 1);
});

Categories