I have a list of links that I have to simulate a click on using CasperJS. They all share the same class.
However using this.click('.click-me') only clicks on the first link.
What's the proper way of clicking on all the links? I'm thinking that maybe I should try to get the number of links via evaluate() and then use a for loop. But if I use evaluate() with the number of links I have to use messages to communicate back and that seems complicated.
Is there a better way?
I ended up using the nth-child() selector to accomplish this. Here's how...
Page:
<ul id="links">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
Script:
casper.then(function() {
var i = 1;
this.repeat(3, function() {
this.click('#links li:nth-child(' + i + ') a');
i++;
});
});
You obviously don't have to use repeat but any iteration technique should work.
As proposed on the CasperJS ML and for the records, here's a possible implementation of clickWhileSelector:
var casper = require('casper').create();
casper.clickWhileSelector = function(selector) {
return this.then(function() {
if (this.exists(selector)) {
this.echo('found link: ' + this.getElementInfo(selector).tag);
this.click(selector);
return this.clickWhileSelector(selector);
}
return this.echo('Done.').exit();
});
}
casper.start().then(function() {
this.page.content =
'<html><body>' +
'link 1' +
'link 2' +
'link 3' +
'</body></html>';
});
casper.clickWhileSelector('a').run();
That gives:
$ casperjs c.js
found link: link 1
found link: link 2
found link: link 3
Done.
Mixing the other responses, to avoid the infinite loop (this worked for me, as my items were consecutive inside a tag):
casper.clickWhileSelector = function(selector, i) {
return this.then(function() {
i = i || 1;
selectorNth = selector+':nth-child(' + i + ')';
if (this.exists(selectorNth)) {
this.echo('found link: '+this.getElementInfo(selectorNth).tag);
this.click(selectorNth);
return this.clickWhileSelector(selector, i+1);
}
return this.echo('Done.').exit();
});
}
Hope it helps!
Luis.
Related
I am using jqGRid with subgrid configuration to display my data. I would like to have global expand & collapse button to display or hide all subgrid information. Does jqGrid library provide this feature by any means?
jqGrid has no "Expand/Collapse all". I modified the demo from the old answer which demonstrates creating on grid with local subgrids. The resulting demo you can see here:
and it has additional "+" button in the column header of "subgrids" column. If one clicks on the button all subgrids will be expanded:
I used the following code in the demo:
var subGridOptions = $grid.jqGrid("getGridParam", "subGridOptions"),
plusIcon = subGridOptions.plusicon,
minusIcon = subGridOptions.minusicon,
expandAllTitle = "Expand all subgrids",
collapseAllTitle = "Collapse all subgrids";
$("#jqgh_" + $grid[0].id + "_subgrid")
.html('<a style="cursor: pointer;"><span class="ui-icon ' + plusIcon +
'" title="' + expandAllTitle + '"></span></a>')
.click(function () {
var $spanIcon = $(this).find(">a>span"),
$body = $(this).closest(".ui-jqgrid-view")
.find(">.ui-jqgrid-bdiv>div>.ui-jqgrid-btable>tbody");
if ($spanIcon.hasClass(plusIcon)) {
$spanIcon.removeClass(plusIcon)
.addClass(minusIcon)
.attr("title", collapseAllTitle);
$body.find(">tr.jqgrow>td.sgcollapsed")
.click();
} else {
$spanIcon.removeClass(minusIcon)
.addClass(plusIcon)
.attr("title", expandAllTitle);
$body.find(">tr.jqgrow>td.sgexpanded")
.click();
}
});
You can simply make it to behave like as toggle as follows.
Take a button.
onlick of it call the function, say toggleSubgrid();
function toggleSubgrid(){
if($('#YOURGRIDID td').hasClass('sgexpanded')){
$('.ui-icon-minus').trigger('click');
}
else if($('#YOURGRIDID td').hasClass('sgcollapsed')){
$('.ui-icon-plus').trigger('click');
}
}
This will work for all rows that are already loaded. You might need to scope the selector a bit, as fits your needs.
function expandAll () {
$( ".tree-plus" ).click();
};
function collapseAll () {
$( ".tree-minus" ).click();
};
Ok, I've looked at a lot of examples that don't really appear different from mine. I simply want to do something (right now, just an alert), when a checkbox changes (is clicked, or whatever). My code:
$(document).ready(
function () {
$('input:checkbox').bind('change', function() {
var id = $(this).attr("id").substring(5);
var skill = $("#skill" + id).val();
alert("you processed skill number " + skill);
})
}) ; // end doc ready
One thing that MAY be different from others is that I'm dynamically creating these checkboxes with another script included like this (without the "script" tags here):
<pre>src="jscript/skills_boxes.js" type="text/javascript" </pre> <br>
Currently that is ABOVE my 'problem' but I've had it below and my stuff still doesn't work. Is there some sort of timing issue here? I'd appreciate any help. Thanks.
Use jquery on function.
$(document).ready(
function () {
$('body').on('change', 'input:checkbox', function() {
var id = $(this).attr("id").substring(5);
var skill = $("#skill" + id).val();
alert("you processed skill number " + skill);
})
}) ; // end doc ready
I've built a dynamic menu which also have highlighting. Now i got a problem, number of path-change events are increasing along with menu elements.
Of course, that's the result of applying directive on each element of the menu.
Custom directives at the moment is my most weak place, and i don't have idea how to refactor all this.
I also made an attempt to put directive in root element of menu (ul) in order to register watch once, but stuck on accessing the deep children elements (ul->li->a.href).
Here's the directive:
app.directive("testdir", function($location)
{
return {
restrict: 'A',
link: function(scope, element, attrs, controller) {
scope.$watch(function() { return $location.path(); }, function(path)
{
scope.$parent.firedEvents++;
path=path.substring(1);
if(path === element.children().attr("href").substring(2))
{
element.addClass("activeLink");
}
else
{
element.removeClass("activeLink");
}
})
}
};
And HTML Part:
<ul ng-app="test" ng-controller="MenuCtrl">
<li ng-repeat="link in menuDef" testdir>{{link.linkName}}</li>
</ul>
Whole example on JsFiddle
How this can be refactored? I'm exhausted.
and, i'm moving in the right direction? I have a feeling that this thing could be done in bit easier way, but maybe i'm wrong.
First of all, your firedEvents means how many time the callback has been called, not how many times the location actually changed, it's not the "number of path-change events"!
You have 20 (as in your fiddle) scopes are watching the location change, when you click on a different link other than the current active one, the location changes, ALL of the 20 scopes will see the change and call their own $watch callback functions, and each call to the callback will increase your firedEvents, so the result is what you have seen: the count goes up by 20.
Therefore, if you want to make the firedEvents to count how many time location has changed, you should move scope.$parent.firedEvents++; into the if. But keep in mind that every click will still cause the callback function be called by 20 times!
There are many ways to achieve the same effect you're trying to do here, I have a solution for you without digging into directive at all. Here you go:
HTML
<ul ng-controller="MenuCtrl">
<li ng-repeat="link in menuDef" ng-class="{activeLink: link.isActive}" ng-click="onLinkClick(link)">{{link.linkName}}
</li>
</ul>
JS
app.controller("MenuCtrl", function ($scope, $location) {
var menugen = [];
for (var i = 1; i <= 20; i++) {
menugen.push({
linkName: "Link " + i,
url: "#/url" + i
});
}
$scope.menuDef = menugen;
var activeLink = null;
$scope.onLinkClick = function (link) {
if (activeLink && $scope.activeLink !== link) {
activeLink.isActive = false;
}
link.isActive = true;
activeLink = link;
};
});
jsFiddle
Update
My first attempt is targeting simplicity, but as #VirtualVoid pointed out, it has a huge drawback -- it can't easily handle location change from outside of the menu.
Here, I came up a better solution: adding a directive to the ul, watch location change in there, and update activeLink in the callback function of the watch. In this way, one $watch is called, and only one callback will be called for a click.
JS
app.directive('menu', function ($location) {
return {
restrict: 'A',
controller: function ($scope, $location) {
var links = [];
this.registerLink = function (elem, path) {
links.push({
elem: elem,
path: path
});
};
$scope.$watch(function () {
return $location.path();
}, function (path) {
for (var i = 0; i < links.length; i++) {
if (path === links[i].path) {
links[i].elem.addClass('activeLink');
} else {
links[i].elem.removeClass('activeLink');
}
}
});
}
};
}).
directive("testdir", function () {
return {
restrict: 'A',
require: '^menu',
link: function (scope, element, attrs, controller) {
controller.registerLink(element, scope.link.url.substring(1));
}
};
});
HTML
<ul ng-app="test" ng-controller="MenuCtrl" menu>
<li ng-repeat="link in menuDef" testdir>
{{link.linkName}}
</li>
</ul>
jsFiddle: http://jsfiddle.net/jaux/MFYCX/
I've successfully implemented a bit of code that strips all HTML from a pasted string using stripTags(). My next goal is to mark a few tags with white flags so they get ignored on 'paste' event using .wrap() to augment the function.
I'm using prototype.js as a framework and have slowly been working through the growing pains of learning both the framework and javascript, but this issue has presented a bit of a roadblock.
I've googled around a bit and found what looks like two great solutions, but I don't seem to be implementing them correctly.
Found solutions:
http://perfectionkills.com/wrap-it-up/ (function to indicate tags to remove)
and
http://pastebin.com/xbymCFi9 (function to allow tags to keep)
I pretty much copied and pasted from the latter.
If I pull the 'br' from the code, then the regex is ignored and all html is stripped. If I leave it, nothing gets pasted.
Here is what I've pieced together (and I feel silly for not being able to figure this out!).
String.prototype.stripTags = String.prototype.stripTags.wrap(
function(proceed, allowTags) {
if (allowTags) {
if (Object.isString(allowTags)) allowTags = $w(allowTags)
this.gsub(/(<\/?\s*)([^\s>]+)(\s[^>]*)?>/, function(match) {
if (allowTags.include(match[2].toLowerCase()))
return match[1] + match[2] + match[3] + '>'
})
} else {
// proceed using the original function
return proceed();
}
});
WysiHat.Commands.promptLinkSelection = function() {
if (this.linkSelected()) {
if (confirm("Remove link?"))
this.unlinkSelection();
} else {
var value = prompt("Enter a URL", "http://www.alltrips.com/");
if (value)
this.linkSelection(value);
}
}
document.on("dom:loaded", function() {
var editor = WysiHat.Editor.attach('event_desc');
var toolbar = new WysiHat.Toolbar(editor);
editor.observe("paste", function(event) {
var el = $(this);
setTimeout(function() {
var pText = el.innerHTML.stripTags('br');
//alert(pText);
$('event_desc_editor').update(pText);
$('event_desc').setValue(pText);
}, 0);
});
(You may recognize the WysiHat code from 37Signals text editor)
note: you can see the alert commented out. If I do alert the ptext, I get 'undefined' returned.
So I've given up on and moved to a regex solution:
el.innerHTML.replace(/<(?!\s*\/?\s*p\b)[^>]*>/gi,'')
I tried posting the problem I had earlier, but I did not fully understand the problem and I had other issues that I confused to be part of this, so I deleted the post. Now that I solved the other problems and pinpointed the problem, here it is...
I'm trying to ajax load some elements from another page and it works in FF3.5+, Chrome 8+, Safari 3+, Opera 9.5+, and IE9.
I was trying to fix it on IE7 and found that it will only load the whole page and not just certain elements using selectors.
The code:
navigation.children('a').click(function() {
if(pageNum <= max) {
$(this)
.css({display: 'none'})
.after(loading)
.blur();
$('<div />')
.load(nextLink + ' .post', function() {
pageNum++;
nextLink = nextLink.replace(/\/page\/[0-9]?/, '/page/'+ pageNum);
if(pageNum <= max) {
navigation.children('a').css({display: 'inline'});
loading.remove();
}
else {
navigation.html('');
}
navigation.before($(this).html());
})
.ajaxComplete(function() {
if (pageNum > max) {
navigation.remove();
}
});
}
return false;
});
If I change .load(nextLink + ' .post', function() to .load(nextLink, function(), it will work in IE7, but will display all the other stuff I didn't want.
Link to the problem: http://gavsiu.com/portfolio/
are yousure nextLink is a string. Can you just cast it to a string and then try concatenating something like
String(nextString) + ".class"