Get Element ID by String it Contains using Plain Javascript - javascript

How can I get an elemnts ID based on the string it contains?
<span id="th67">This the string I need to match</span>
I can't make use of JQuery or any other Javascript library to do this.
I need to do this for a selenium test.
I didn't realise how useless I am in JS without my libraries!
Thanks all for any help.

Well, if you know what kind of tag you're looking for, you can just do:
var spans = document.getElementsByTagName('span'), targetId;
for (var i = 0; i < spans.length; ++i) {
if (spans[i].innerText === stringToMatch) {
// found it ...
targetId = spans[i].id;
break;
}
}
if (targetId) {
// ... do whatever ...
}
If you want to get fancy you could construct an xpath query, I guess.

If the browsers you're targeting support XPath you can do a simple XPath query:
// Find an element by the text it contains, optionally
// starting at specified parent element.
function getElementByText( text, ctx)
{
return document.evaluate("//*[.='"+text+"']",
ctx || document, null, XPathResult.ANY_TYPE, null).iterateNext();
}
Then just run
var myElement = getElementByText( "This is the string I need to match" );
if ( myElement )
{
// do something with myElement.id
}

Here's a simple recursive function that will do it:
function findByText(node, text) {
if(node.nodeValue == text) {
return node.parentNode;
}
for (var i = 0; i < node.childNodes.length; i++) {
var returnValue = findByText(node.childNodes[i], text);
if (returnValue != null) {
return returnValue;
}
}
return null;
}
Use it as:
var target = findByText(document, "This the string I need to match");
This will end up with either target being null, or it being a DOM node whose id you can get with target.id.
See it in action.

Related

getAttribute by TagName - JS

My specific situation is that I'm trying to remove/make inactive a link element from the DOM (I have no control over it being generated). The way that I plan to do this is through replacing the 'href' attribute with a nonsense value - the reason I've chosen to do it this way rather than simply using disable = true is so that the function can be reused on other occasions to change other attributes.
The problem I'm having is with .getAttribute where it returns the error "TypeError: elemArr.hasAttribute is not a function".
function removeLink(elem, att, value, replacement) {
var elemArr = document.getElementsByTagName(elem);
for (var i = 0; i < elemArr.length; i++) {
var workingAtt = elemArr.hasAttribute(att);
if (workingAtt.value === filePath) {
elemArr[i].setAttribute(att, replacement);
}
}
}
removeLink("link", "href", "filePath", "#");
Any help with why this error is getting thrown is greatly appreciated.
What's going on in there is that elemArr is an array, and arrays don't have a hasAttribute method. Rewrite your code as
function removeLink(elem, att, value, replacement) {
var elemArr = document.getElementsByTagName(elem);
for (var i = 0; i < elemArr.length; i++) {
//this line here wasn't referring to a specific node but the array
var workingAtt = elemArr[i].hasAttribute(att);
if (workingAtt && elemArr[i].getAttribute(att) === value) {
elemArr[i].setAttribute(att, replacement);
}
}
}
removeLink("link", "href", "filePath", "#");
And it will work.
A more succint approach would be something like this:
function removeLink(elem, att, value, replacement){
var selector = elem + '['+ att +'="'+ value +'"]';
[].forEach.call(document.querySelectorAll(selector), function(node){
node.setAttribute(att, replacement);
});
}
It does basically the same thing, but is quite a bit shorter and more explicit.
.hasAttribute() returns a boolean true or false. Therefore, workingAtt will either equal true or false. Boolean values are not HTMLElements, therefore they do not have value attributes. That's why there's an error.
It looks like you're trying to do something like select elements where there is a href attribute.
If so, you can just filter them:
var myElements = [];
[].filter.call(elemArr, function(el) {
if(el.hasAttribute(att)) {
myElements.push(el);
}
});
// then, do something with myElements
You have several errors in your code:
elemArr.hasAttribute instead of elemArr[i].hasAttribute.
var workingAtt = elemArr.hasAttribute(att); — here, workingAtt will be a boolean value, workingAtt.value is non-existent. You should use elemArr[i].getAttribute(att) and later use workingAtt, NOT workingAtt.value (it will be non-existent again!).
if (workingAtt.value === filePath) you're comparing to filePath while you should most definitely compare to value that you pass in the function.

Javascript: look for xpath inside element

I get all the elements via the xpath below. I want to look inside each TD, and search for another xpath inside each TD. I am not sure if the below approach works but this is something I'd like to achieve. I know you can do //td//a but I want to know how I programmatically search each element for more xpaths.
var tds = document.evaluate('//td', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (++index < tds.snapshotLength){
//this link may or may not exist
var link = document.evaluate('//a', tds[index].document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);
}
Its sensible to define two helper functions that do the heavy lifting and return results that are easy to work with:
function selectNodes(path, contextNode) {
var result, item, nodes = [];
result = document.evaluate(path, contextNode || document, null,
XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
while ( item = result.iterateNext() ) nodes.push(item);
return nodes;
}
function selectSingleNode(path, contextNode) {
return selectNodes(path, contextNode)[0];
}
Now using XPath becomes straight-forward:
var tds = selectNodes('//td'), i, a;
for (i = 0; i < tds.length; i++) {
a = selectSingleNode('.//a', tds[i]);
console.log(a.href);
}
Note the relative path ('.//a'). You need to use relative paths when you want correct results with a context node - a plain // always starts at the document root.
Your approach seems fine, but your for syntax is incorrect and you need to use snapshotItem to access the results of tds:
var tds = document.evaluate('//td', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var i;
for (i=0; i < tds.snapshotLength; i++) {
var link = document.evaluate('//a', tds.snapshotItem(i), null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if(link.snapshotLength) {
console.log(link.snapshotItem(0));
}
}
jsFiddle

Use SPAN to change color of a word if a definition is availible in the glossary

I'm teaching myself JavaScript and JQuery and working through a simple Glossary app as I go. Currently my glossary terms are in two json files (one for terms and one for acronyms). I have a page with text on it and code to make a definition display in an alert when I click on a word that is available in the glossary of terms or glossary of acronyms. That part is working. What I would like to do is to be able to change the style of each word in the text that has a matching definition (color, underline, etc). I think I need to use a loop to check if the word in in the glossary (I can already do that) and then apply but I'm not really sure the span works when doing it dynamically. The one span tag in my code is modified example that had been posted in another question here and I have it working for me, I'm just not too certain how it does what it does. Anyone have time to get me going in the right direction?
//breaks the paragraph html into word by word targets
var p = $('p#paragraph');
var words;
p.html(function(index, oldHtml) {
words = oldHtml.replace(/\b(\w+?)\b/g, '<span class="word">$1</span>')
return words;
});
//when word is clicked checks to see if word in the glossary, if so displays alert box with word and definition
p.click(function(event) {
if (this.id != event.target.id) {
var termNeeded = event.target.innerHTML;
//checks Terms json first
var checkAcronyms = true;
for (var i = 0; i < jsonTerms.GlossaryTerms.length; i++) {
var obj = jsonTerms.GlossaryTerms[i];
if (obj.term == termNeeded) {
alert(obj.term + ": " + obj.definition);
checkAcronyms = false;
break;
};
};
//if the word is not in the terms, then checks in the acronyms
if (checkAcronyms == true){
for (var i = 0; i < jsonAcronyms.GlossaryAcronyms.length; i++) {
var obj = jsonAcronyms.GlossaryAcronyms[i];
if (obj.term == termNeeded) {
alert(obj.term + ": " + obj.definition);
break;
};
};
};
};
});
//brings in the JSON data
var jsonTerms;
$.getJSON("GlossaryTerms.json", function(data) {
jsonTerms = data;
//console.log(jsonTerms);
});
var jsonAcronyms;
$.getJSON("GlossaryAcronyms.json", function(data) {
jsonAcronyms = data;
//console.log(jsonAcronyms);
});
Maybe something like this would do the trick:
I changed your code around a bit, and please beware that it is untested.
You would have to define a CSS style with the name "defined", which will indicate that the word has a definition.
I extracted your logic into a separate function for reuse. Also, created the addStyleToWords function, which should iterate over all your words, check if they have a definition, and if they do, then add an extra class to that element.
var jsonTerms;
var jsonAcronyms;
function checkWord(termNeeded) {
//checks Terms json first
for (var i = 0; i < jsonTerms.GlossaryTerms.length; i++) {
var obj = jsonTerms.GlossaryTerms[i];
if (obj.term == termNeeded) {
return obj;
}
}
//if the word is not in the terms, then checks in the acronyms
for (var i = 0; i < jsonAcronyms.GlossaryAcronyms.length; i++) {
var obj = jsonAcronyms.GlossaryAcronyms[i];
if (obj.term == termNeeded) {
return obj;
}
}
return null;
}
function addStyleToWords() {
$(".word").each(function() {
var el = $(this);
var obj = checkWord(el.text());
if (obj != null) el.addClass("defined");
});
}
//breaks the paragraph html into word by word targets
var p = $('p#paragraph');
p.html(function(index, oldHtml) {
return oldHtml.replace(/\b(\w+?)\b/g, '<span class="word">$1</span>');
});
//when word is clicked checks to see if word in the glossary, if so displays alert box with word and definition
p.click(function(event) {
if (this.id != event.target.id) {
var obj = checkWord(event.target.innerHTML);
if (obj != null) alert(obj.term + ": " + obj.definition);
});
//brings in the JSON data
$.getJSON("GlossaryTerms.json", function(data) {
jsonTerms = data;
$.getJSON("GlossaryAcronyms.json", function(data) {
jsonAcronyms = data;
addStyleToWords();
});
});
Once you have added in your spans and the JSON data has loaded you need to loop through each
word span testing them for matches as you go.
p.find('span.word').each(function(){
// "this" now refers to the span element
var txt=this.innerHTML;
if(isInGlossary(txt)){
$(this).addClass('in_glossary');
}
})
You will need to define the isInGlossary(term) function, pretty much what you have done already in your p.click code.
I don't get it...
To if I understand you correctly, look at: JQuery addClass
My Suggestions:
If you want to iterate over each work in the paragraph, then, in your click handler find each span tag using $('p#paragraph).find('span').each(function(){...});
In your each function, get the work with $(this).html()
To style your word, add a class or css to $(this). see:JQuery addClass
Rather return your JSONArray as a JSONObject (much like an associative array) with the word being the property and the description being the value, that way you can search through it like so: var definition = json[word].

get SINGLE text node from DOM object

Need to get all direct nodes from DOM element and don't actually know, how it many and what kind they are.
.contents()?
Ok, let's see..
$('<div />').html('<p>p</p>').contents() ->
[<p>​p​</p>​]
Ok.
$('<div />').html('textNode').contents() -> []
WTF?
$('<div />').html('textNode').append('another').contents() ->
["textNode", "another"]
Ok, so what about single text node?
I don't know if this is helpful. A while ago I built a Document Fragment generator using JSON styled input. I also wrote a (somewhat working) reverse function for it so you could turn your nodeList into a JSON string.
https://gist.github.com/2313580
var reverseFunction = function(DOM /* DOM tree or nodeList */) {
var tree = [];[].forEach.call(DOM, function(obj) {
if (obj instanceof Text) {
tree.push({
'textContent': obj.textContent
});
} else {
var tmp = {};
tmp['tagName'] = obj.nodeName;
for( var data in obj.dataset ) {
tmp['data-' + data] = obj.dataset[data];
}
for (var i = 0, l = obj.attributes.length; i < l; i++) {
var key = obj.attributes[i].name,
val;
if (key.indexOf('data-') === -1) {
switch (key) {
case ('class'):
key = 'className';
break;
case ('style'):
val = {};
obj.attributes[i].value.split(';').forEach(function(rule) {
var parts = rule.split(':');
val[parts[0]] = parts[1];
});
break;
};
tmp[key] = val || obj.attributes[i].value;
}
}
if (obj.childNodes.length > 0) {
tmp['childNodes'] = reverseFunction(obj.childNodes);
}
tree.push(tmp);
}
});
return tree;
};
This does find textNodes and separates them... You may be able to extract something from it.
Update: to answer a comment in your question above...
var div = document.createElement('div');
div.appendChild(document.createTextNode('dsf'));
console.log( div.childNodes.length, div.childNodes, div.childNodes[0].textContent);​
I hope this makes a bit more sense to you know. The array appears empty in the console but it is not. check the length and attempt to access it and you will see.
.contents() is concerned with DOM nodes. That string in the 2nd example is not a DOM element.

Selecting elements without jQuery

I guess this will be voted down, as it doesn't contain enough jQuery, but here it goes :)
What is the most effective way to get the element(s) returned by the jQuery selector below using plain old javascript?
$('a[title="some title text here"]', top.document)
If you're using a modern browser, you could use this:
window.top.document.querySelectorAll('a[title="some title text here"]')
Not sure if it’s the most effective, but at least it works.
var links = top.document.getElementsByTagName('a');
var result = [];
var linkcount = links.length;
for ( var i = 0; i < linkcount; i++) {
if (links[i].getAttribute('title') === 'some title text here') {
result.push(links[i]);
}
}
Here is an example
var getElements = function(tagName, attribute, value, callback) {
var tags = window.document.getElementsByTagName(tagName);
for (var i=0; i < tags.length; i++) {
var tag = tags[i];
if (tag.getAttribute(attribute) == value) {
callback(tag);
}
};
};
getElements("a", "title", "PHP power player at Hettema & Bergsten. Click to learn more.", function(tag) {
console.log(tag);
});

Categories