jQuery ":contains()" analog for pure JS - javascript

I'm writing a script for CasperJS. I need to click on the link that contains a span with "1". In jQuery can be used :contains('1'), but what the solution is for selectors in pure Javascript?
HTML: <a class="swchItem"><span>1</span></a><a class="swchItem"><span>2</span></a>
jQuery variant: $('a .swchItem span:contains("1")')
UPD CasperJS code:
casper.then(function () {
this.click('a .swchItem *select span with 1*')
})

Since 0.6.8, CasperJS offers XPath support, so you can write something like this:
var x = require('casper').selectXPath;
casper.then(function() {
this.click(x('//span[text()="1"]'))
})
Hope this helps.

Try the following. The difference between mine and gillesc's answer is I'm only getting a tags with the classname you specified, so if you have more a tags on the page without that class, you could have unexpected results with his answer. Here's mine:
var aTags = document.getElementsByTagName("a");
var matchingTag;
for (var i = 0; i < aTags.length; i++) {
if (aTags[i].className == "swchItem") {
for (var j = 0; j < aTags[i].childNodes.length; j++) {
if (aTags[i].childNodes[j].innerHTML == "1") {
matchingTag = aTags[i].childNodes[j];
}
}
}
}

var spans = document.getElementsByTagName('span'),
len = spans.length,
i = 0,
res = [];
for (; i < len; i++) {
if (spans.innerHTML == 1) res.push(spans[i]);
}
Is what you have to do unless the browser support native css queries.

jQuery is javascript. There are also a number of selector engines available as alternatives.
If you want to do it from scratch, you can use querySelectorAll and then look for appropriate content (assuming the content selector isn't implemented) and if that's not available, implement your own.
That would mean getting elements by tag name, filtering on the class, then looking for internal spans with matching content, so:
// Some helper functions
function hasClass(el, className) {
var re = new RegExp('(^|\\s)' + className + '(\\s|$)');
return re.test(el.className);
}
function toArray(o) {
var a = [];
for (var i=0, iLen=o.length; i<iLen; i++) {
a[i] = o[i];
}
return a;
}
// Main function
function getEls() {
var result = [], node, nodes;
// Collect spans inside A elements with class swchItem
// Test for qsA support
if (document.querySelectorAll) {
nodes = document.querySelectorAll('a.swchItem span');
// Otherwise...
} else {
var as = document.getElementsByTagName('a');
nodes = [];
for (var i=0, iLen=as.length; i<iLen; i++) {
a = as[i];
if (hasClass(a, 'swchItem')) {
nodes = nodes.concat(toArray(a.getElementsByTagName('span')));
}
}
}
// Filter spans on content
for (var j=0, jLen=nodes.length; j<jLen; j++) {
node = nodes[j];
if ((node.textContent || node.innerHTML).match('1')) {
result.push(node);
}
}
return result;
}

Related

Any better way to hide multiple elements at once?

This works perfectly fine, but for future reference I would like to know if there is a better way to do this.
document.getElementsByTagName('h1')[0].style.display = 'none';
document.getElementsByTagName("p")[0].style.display = 'none';
document.getElementsByTagName("p")[1].style.display = 'none';
document.getElementsByTagName("ul")[0].style.display = 'none';
document.getElementsByTagName("hr")[0].style.display = 'none';
I'm open to using jQuery if it would help.
While jQuery wasn't specifically requested, it would be the shortest implementation to meet the OP's sample code exactly as written:
$('h1:eq(0), ul:eq(0), hr:eq(0), p:lt(2)').hide();
Keep your nodes in an Array, then you can write some functions which will hide or show a whole Array (or NodeList) at once, e.g.
function display_none(nodelist) {
var i = nodelist.length;
while (i-- > 0)
nodelist[i].style.display = 'none';
}
function display_default(nodelist) {
var i = nodelist.length;
while (i-- > 0)
nodelist[i].style.display = '';
}
So you have
var elms = [
document.getElementsByTagName('h1')[0],
document.getElementsByTagName("p")[0],
document.getElementsByTagName("p")[1],
document.getElementsByTagName("ul")[0],
document.getElementsByTagName("hr")[0]
];
Now can simply do
display_none(elms); // hide them all
display_default(elms); // return to default
Here's a pure Javascript option that implements a jQuery-like extension for the :lt(n) pseudo selector so you can specify however many of each tag you want.
As the question asked, this operates on the first two "p" tags, then the first "h1", "ul" and "hr" tags:
function hideItems(selector) {
var r = /:lt\((\d+)\)/;
selector.split(',').forEach(function(item) {
var len, elems, i, m = item.match(r);
if (m) {
item = item.replace(r, "");
elems = document.querySelectorAll(item);
len = Math.min(+m[1], elems.length);
} else {
elems = document.querySelectorAll(item);
len = elems.length;
}
for (i = 0; i < len; i++) {
elems[i].style.display = 'none';
}
});
}
hideItems('p:lt(2),h1:lt(1),ul:lt(1),hr:lt(1)');
Working demo: http://jsfiddle.net/jfriend00/m9vspymz/
Using Pure JavaScript
//gets all DOM elements with a tag of p or li
var elems = document.querySelectorAll('p,li');
//loops over all elements
for(var i = 0;i < elems.length; i++)
{
//hides each element in the array
elems[i].style.display = 'none';
}
Code Specifically for you
var elems = document.querySelectorAll('p,h1,ul,hr');
for(var i = 0;i < elems.length; i++)
{
elems[i].style.display = 'none';
}
Here's the code (JavaScript):
var n = [ // 'tagname',which in order
'h1',0,
'p',0,
'p',1,
'ul',0,
'hr',0
];
for(var i = 0;i < elems.length)
{
document.getElementsByTagName(n[i*2])[(n[i*2]+1)].style.display = 'none';
i++;
i++;
}

write this line of jQuery in vanilla javascript

Can someone help me write this line of jQuery in javascript. It applies a single rule of styling to a class.
$('.dataCard').css('visibilty', 'visible !important');
As !important doesn't apply when setting styles with javascript, it would be something like this
var elems = document.querySelectorAll('.dataCard');
for (var i=elems.length; i--;) {
elems[i].style.visibility = 'visible';
}
If you want to make a general purpose function that can replace what you had in jQuery, you could do this:
function setStyle(elemOrSelector, prop, val) {
var items;
if (typeof elemOrSelector === "string") {
// run selector query
items = document.querySelectorAll(elemOrSelector);
} else if (elemOrSelector.nodeName) {
// must be single DOM object
items = [elemOrSelector];
} else if (elemOrSelector.length)
// must be an array or nodeList
items = elemOrSelector;
} else {
// don't know what it is
return;
}
for (var i = 0; i < items.length; i++) {
items[i].style[prop] = val;
}
}
setStyle('.dataCard', "visibility", "visible");
This general purpose function allows you to pass either a DOM element, an array like list of DOM elements or a selector string.
If you don't want the general purposeness, then you can just use this:
function setStyle(selector, prop, val) {
var items = document.querySelectorAll(selector);
for (var i = 0; i < items.length; i++) {
items[i].style[prop] = val;
}
}
setStyle('.dataCard', "visibility", "visible");

Extracting a DOM element into an array of lines

I'm working on a page that includes a div with contenteditable="true" and I need to extract the text typed by the user as plain text to be later processed by some other javascript code. I then wrote this function:
function extractLines(elem) {
var nodes = elem.childNodes;
var lines = [];
for (i = 0; i < nodes.length; ++i) {
var node = nodes[i];
if (node.nodeType == 3) {
if (node.nodeValue.length > 0) {
lines.push(node.nodeValue);
}
}
if (node.nodeType == 1) {
if (node.nodeName == "BR") {
lines.push("");
}
else {
lines = lines.concat(extractLines(node));
}
}
}
return lines;
}
This takes an element and should return an array of lines. I don't expect it to work for any HTML, but it should be able to process what the browser generates on the div. Currently I'm testing on Chrome only (later I'll expand the idea to other browsers as they format of generated html is different on contenteditable divs).
Given this HTML:
<div id="target">aaa<div><br></div></div>
It correctly produces:
["aaa", ""]
But my problem is when the user insert two consecutive line breaks (EnterEnter). Chrome produces this:
<div id="target">aaa<div><br></div><div><br></div></div>
And my code gets stuck into an infinite loop. Why?
You can try with this:
console.log(extractLines(target));
Note: you might need to force-kill the tab (use Shift+Esc)
Live demo here (click).
var myElem = document.getElementById('myElem');
var myBtn = document.getElementById('myBtn');
myBtn.addEventListener('click', function() {
var results = [];
var children = myElem.childNodes;
for (var i=0; i<children.length; ++i) {
var child = children[i];
if (child.nodeName === '#text') {
results.push(child.textContent);
}
else {
var subChildren = child.childNodes;
for (var j=0; j<subChildren.length; ++j) {
var subChild = subChildren[j];
results.push(subChild.textContent);
}
}
}
console.log(results);
});
Old Answer
How about this? Live demo here (click).
var myElem = document.getElementById('myElem');
var myBtn = document.getElementById('myBtn');
myBtn.addEventListener('click', function() {
var results = [];
var children = myElem.childNodes;
for (var i=0; i<children.length; ++i) {
var text = children[i].textContent;
if (text) { //remove empty lines
results.push(text);
}
}
console.log(results);
});
You can remove that if (text) statement if you want to keep the empty lines.

How to hide a class using javascript?

I have text on a page, its in a <h3> tag, which has a class ms-standardheader, but there are other texts on the page with the same class in its own <h3> tag. I also know the text I want to hide is 'Session'.
With this how can I write a javascript function to hide only this text?
Here is an image of the developtools from IE.
I'd suggest, if you're restricted (as your tags suggest) to non-library plain JavaScript, the following:
var h3s = document.getElementsByTagName('h3'),
classedH3 = [];
for (var i = 0, len = h3s.length; i < len; i++) {
if (h3s[i].className.indexOf('ms-standardheader') > -1) {
classedH3.push(h3s[i]);
}
}
for (var i = 0, len = classedH3.length; i < len; i++) {
if (classedH3[i].firstChild.nodeValue == 'the text to hide'){
classedH3[i].style.display = 'none';
}
}​
JS Fiddle demo.
References:
push().
document.getElementsByTagName().
element.className.
node.nodeValue.
indexOf().
Can't you give the target element an ID? That would make things much more simple. Otherwise, you have to go through all <h3> elements until you find the one you want to hide:
var headings = document.getElementsByTagName("h3");
for(var i=0; i<headings.length; i++) {
var contentElement = headings[i].getElementsByTagName('nobr');
var content = "";
if(contentElement.length) {
content = contentElement[0].textContent ? contentElement[0].textContent : contentElement[0].innerText;
}
var content = contentElement.length ? contentElement[0].childNodes[0].nodeValue : '';
if(headings[i].className == 'ms-standardheader' && content == 'Session') {
headings[i].style.display = 'none';
}
}
you should try this :
window.onload = function()
{
getElementByClass('ms-standardheader');
}
window.getElementByClass = function(theClass){
var allHTMLTags=document.getElementsByTagName('*');
for (i=0; i<allHTMLTags.length; i++) {
if (allHTMLTags[i].className==theClass) {
var content = allHTMLTags[i].innerHTML;
var search = /session/;
if (search.test(content))
{
alert(search);
allHTMLTags[i].style.display='none';
}
}
}
}
See Demo : http://jsfiddle.net/3ETpf/18/
Always favour a unique Id where possible. If not possible then you have to manually traverse the DOM to find the elements you are looking for. Here's an example using getElementsByTagName().
var i, header, headers = document.getElementsByTagName('h3');
for (i = 0; i < headers.length; i += 1) {
header = headers[i];
if (header.className === 'ms-standardheader' &&
(header.textContent || header.innerText) === 'Session') {
header.style.display = 'none';
}
}
see: http://jsfiddle.net/whP5z/
If you have jquery you may type this :
$("h3.ms-standardheader:contains('Session')").hide();

getElementsByClassName IE resolution issue

I am having issues figuring out how to resolve the getElementsByClassName issue in IE. How would I best implement the robert nyman (can't post the link to it since my rep is only 1) resolution into my code? Or would a jquery resolution be better? my code is
function showDesc(name) {
var e = document.getElementById(name);
//Get a list of elements that have a class name of service selected
var list = document.getElementsByClassName("description show");
//Loop through those items
for (var i = 0; i < list.length; ++i) {
//Reset all class names to description
list[i].className = "description";
}
if (e.className == "description"){
//Set the css class for the clicked element
e.className += " show";
}
else{
if (e.className == "description show"){
return;
}
}}
and I am using it on this page dev.msmnet.com/services/practice-management to show/hide the description for each service (works in Chrome and FF). Any tips would be greatly appreciated.
I was curious to see what a jQuery version of your function would look like, so I came up with this:
function showDesc(name) {
var e = $("#" + name);
$(".description.show").removeClass("show");
if(e.attr("class") == "description") {
e.addClass("show");
} else if(e.hasClass("description") && e.hasClass("show")) {
return;
}
}
This should support multiple classes.
function getElementsByClassName(findClass, parent) {
parent = parent || document;
var elements = parent.getElementsByTagName('*');
var matching = [];
for(var i = 0, elementsLength = elements.length; i < elementsLength; i++){
if ((' ' + elements[i].className + ' ').indexOf(findClass) > -1) {
matching.push(elements[i]);
}
}
return matching;
}
You can pass in a parent too, to make its searching the DOM a bit faster.
If you want getElementsByClassName('a c') to match HTML <div class="a b c" /> then try changing it like so...
var elementClasses = elements[i].className.split(/\s+/),
matchClasses = findClass.split(/\s+/), // Do this out of the loop :)
found = 0;
for (var j = 0, elementClassesLength = elementClasses.length; j < elementClassesLength; j++) {
if (matchClasses.indexOf(elementClasses[j]) > -1) {
found++;
}
}
if (found == matchClasses.length) {
// Push onto matching array
}
If you want this function to only be available if it doesn't already exist, wrap its definition with
if (typeof document.getElementsByClassName != 'function') { }
Even easier jQuery solution:
$('.service').click( function() {
var id = "#" + $(this).attr('id') + 'rt';
$('.description').not(id).hide();
$( id ).show();
}
Why bother with a show class if you are using jQuery?
Heres one I put together, reliable and possibly the fastest. Should work in any situation.
function $class(className) {
var children = document.getElementsByTagName('*') || document.all;
var i = children.length, e = [];
while (i--) {
var classNames = children[i].className.split(' ');
var j = classNames.length;
while (j--) {
if (classNames[j] == className) {
e.push(children[i]);
break;
}
}
}
return e;
}
I used to implement HTMLElement.getElementByClassName(), but at least Firefox and Chrome, only find the half of the elements when those elements are a lot, instead I use something like (actually it is a larger function):
getElmByClass(clm, parent){
// clm: Array of classes
if(typeof clm == "string"){ clm = [clm] }
var i, m = [], bcl, re, rm;
if (document.evaluate) { // Non MSIE browsers
v = "";
for(i=0; i < clm.length; i++){
v += "[contains(concat(' ', #"+clc+", ' '), ' " + base[i] + " ')]";
}
c = document.evaluate("./"+"/"+"*" + v, parent, null, 5, null);
while ((node = c.iterateNext())) {
m.push(node);
}
}else{ // MSIE which doesn't understand XPATH
v = elm.getElementsByTagName('*');
bcl = "";
for(i=0; i < clm.length; i++){
bcl += (i)? "|":"";
bcl += "\\b"+clm[i]+"\\b";
}
re = new RegExp(bcl, "gi");
for(i = 0; i < v.length; i++){
if(v.className){
rm = v[i].className.match(bcl);
if(rm && rm.length){ // sometimes .match returns an empty array so you cannot use just 'if(rm)'
m.push(v[i])
}
}
}
}
return m;
}
I think there would be a faster way to iterate without XPATH, because RegExp are slow (perhaps a function with .indexOf, it shuld be tested), but it is working well
You can replace getElementsByClassName() with the following:
function getbyclass(n){
var elements = document.getElementsByTagName("*");
var result = [];
for(z=0;z<elements.length;z++){
if(elements[z].getAttribute("class") == n){
result.push(elements[z]);
}
}
return result;
}
Then you can use it like this:
getbyclass("description") // Instead of document.getElementsByClassName("description")

Categories