Select element by content - javascript

Within javascript, are you able to target elements on a page via psuedo classes? For example:
<table id="loginInnerTable">
<tbody>
<tr>
<td colspan="3">
<span class="required">*</span><span> = required</span>
</td>
</tr>
<tr>
<td>User ID <span class="required">*</span></td>
</tr>
</tbody>
</table>
How would I target the 'User ID' text to change it with .innerHTML? This is an example of a form that I don't have access to the code itself, except via JS.
Otherwise, what is the best method?

You can always make use of DOM traversal and HTML DOM extensions. This depends on your actual markup though. In this specific case you can do the following:
Get a reference to the table:
var table = document.getElementById('loginInnerTable');
Get a reference to the second row:
var row = table.rows[1];
Get a reference to the first cell:
var cell = row.cells[0];
Change the value of the first child node (works because it is a text node):
cell.firstChild.nodeValue = 'Some new text';
Or iterate over all text nodes to find the right one (if you have mixed element and text nodes or the node you want to change is not the first child):
var node = cell.firstChild;
do {
if (node.nodeType === 3 && node.nodeValue.indexOf('User ID') > -1) {
node.nodeValue = 'Some new text';
break;
}
} while(node = node.nextSibling);
If you don't want to restrict your solution too much to the current structure (it might change from time to time), then iterating over all rows/cells and find the desired cell would be a more flexible approach. Karl-André Gagnon showed this in his answer.

How about something like this to select the element, then just use it as a reference to update using innerHTML
var userTD = document.getElementById('loginInnerTable').getElementsByTagName('tbody')[0].getElementsByTagName('tr')[1].getElementsByTagName('td');
I'm not sure how efficient it is, but it should do the trick.
As others have mentioned it's not massively reliable - as if the table structure changes (which you say is out of your control) you'll have to change your code to work again.

Here a simple code that will change the text :
var tdEl = document.getElementsByTagName('td')
for(var cpt = 0; cpt < tdEl.length; cpt++){
if(tdEl[cpt].innerHTML.indexOf('User ID') != -1){
tdEl[cpt].innerHTML = 'some text <span class="required">*</span>';
}
}
If you want to make it a function (to reuse the code) :
changeNode('td', 'User ID', 'some text <span class="required">*</span>')
function changeNode(node, text, replace){
var el = document.getElementsByTagName(node)
for(var cpt = 0; cpt < el.length; cpt++){
if(el[cpt].innerHTML.indexOf(text) != -1){
el[cpt].innerHTML = replace;
}
}
}

How would I target the 'User ID' text to change it with .innerHTML?
You won't. Of course you could do something like
var tab = document.getElementById('loginInnerTable');
tab.innerHTML = tab.innerHTML.replace("User ID", "something else");
but that would replace the whole table, crushing and re-parsing its DOM (and it won't work in IE, of course).
Instead, you will use DOM manipulation to get and change that text node:
var tab = document.getElementById('loginInnerTable'),
td = tab.rows[1].cells[0],
text = td.firstChild;
text.nodeValue = "something else";

Related

JavaScript get textContent excluding children

First, I'm creating a library for JavaScript and I can not use jQuery. I'm trying to get the text content of an HTML element without the text contents of its children.
Both attributes innerText and textContent don't give me what needed, please help.
You can solve using DOM API as childNodes and nodeType.
var elChildNode = document.querySelector("#hello").childNodes;
var sChildTextSum = "";
elChildNode.forEach(function(value){
if(value.nodeType === Node.TEXT_NODE) {
console.log("Current textNode value is : ", value.nodeValue.trim());
sChildTextSum += value.nodeValue;
}
});
console.log("All text value of firstChild : ", sChildTextSum);
I created a sample code as above.
https://jsfiddle.net/nigayo/p7t9bdc3/
To get Author's Name from the following element, excluding <span>...:
<div class="details__instructor">
Author's Name<span ng-show="job_title">, Entrepreneur</span>
</div>
use childNodes[0]. For example:
document.querySelector('div.details__instructor').childNodes[0].textContent
Using only JavaScript (you specified you cannot use jQuery), and given that you have provided and know the id for the parent element:
document.getElementById('parent_element_id').childNodes[0].nodeValue;
You can also use .trim() to remove any trailing space characters left behind from the removal of any child element text:
document.getElementById('parent_element_id').childNodes[0].nodeValue.trim();
var mydiv = getElementByID("id");
function Get_text(element) {
var selected = element.cloneNode(true);
var text;
while (selected.firstChild) {
if (selected.firstChild.nodeType == 3) text = selected.firstChild.nodeValue;
selected.removeChild(selected.firstChild);
}
return text;
}
Get_text(mydiv);
I know many good solutions here exist, but none of them actually achieved what I needed (get the textContent of a single node, none of its children), so sharing this for future searchers.
var html = document.getElementsByTagName("*");
for (var i = 0; i < html.length; i++) {
var el = html[i];
for (var j = 0; j < el.children.length; j++) {
var child = el.children[j],
childTextContent = child.innerHTML;
// Remove all children tags, leaving only the actual text of the node.
childTextContent = childTextContent.replace(/\<.*\>.*\<\/.*\>/gmi, "");
// Also remove <img /> type tags.
childTextContent = childTextContent.replace(/\<.*\ \/\>/gmi, "");
console.log(childTextContent);
// Now you can do any type of text matching (regex) on the result.
}
});

Parsing Data from HTML without IDs

I want to parse (with Javascript or JSoup) an Website.
My Problem is that I don´t knew how to access the wanted data, because in that file are practically no Ids.
I have something like:
<div id content>
<table>
<tbody>
<tr>
<td align >
<div style=>
<table>
<tbody>
<tr></tr>
<tr></tr>
<tr>
<td>
<br></br>
<h2><div class=""></div>Related</h2>
Adaptation:
nameOfBook
<br></br>
Prequel:
nameOfBook2
<br></br>
Other:
<br></br>
<br></br>
<h2></h2>
<table width0"></table>
..........many tables and a.....
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
Hopefully its understandable, the site is quite big.
I want the Stuff after Related.
So I want the Sequel connected with the three names and their links.
And then the Prequel name3.
At the moment I get the #content then I get the Array with all h2 Tags and check the 2nd child, if it equals to "Related".
Then I get the parent (td) and iterate over all "a".
In this one td are over 200 a´s.
My plan was now to iterate over those and check if before that "a" comes the term (prequel, sequel or adaption), but it sounds a little bit complicated.
Or I could parse everything between the two h2 Tags, because it´s always there. Or, I could check the link, because the wanted ones have always the same structure. So, search for that structure and then go to the parent and check what term it is.
Anyone can help me with that? In the whole document are no id´s or names.
I am pretty sure, that I can find an workaround for that, but it would be just too complicated and with some JS Knowledge easy to get.
UPDAtE:
It´s not known how many Prequel/Sequel whatever Tags will be there.
The only thing I really knew is that there will be an "Related" Text between two h2-Tags and the next beginning h2 is the start of something new.
And changed the above example: now it´s the correct Structure, #content is again in an div, but I guess that´s not important because I can access content directly.
You can use document.querySelector or document.querySelectorAll and select the element in a relative manner.
For example:
to select first three a tags within div[id='content']
var allAnchorsInDiv = document.querySelectorAll("div[id='content'] a"); //Basically this is an array of anchors.
//select anchors from array.
If you don't have any Ids at all, then you should probably use a relative path (something like Xpath or CSS selector).
Using CSS selector you will use something like this,
document.querySelectorAll('body>div:first-of-type>a');
Or you could use XPath, refer w3school
Note: If you want things a little easier you could even use jquery to accomplish the same.
Update:
So, for your need you have to do this.
Select the text node with the text.
Find the node anchor nodes next to it.
Thus,
var myKeyTerm = "Sequel"; //Set your keyterm here.
var myAnchorTags = [];
var myTextNode = document.evaluate("//text()[contains(., '"+myKeyTerm +"')]" ,document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue;
if(myTextNode) {
var nextNode = myTextNode;
do {
nextNode = nextNode.nextSibling;
if(nextNode && nextNode.nodeName == "A") {
myAnchorTags.push(nextNode);
}
else nextNode = null;
}
while(nextNode);
}
//All the nodes that follow your required text is in myAnchorTags array.
My take on this would be:
var content = document.getElementById("content");
var h2 = content.getElementsByTagName("h2")[0]; // the first h2 element
var link1 = h2.nextElementSibling;
var link2 = link1.nextElementSibling;
var link3 = link2.nextElementSibling;
var link4 = link3.nextElementSibling;
console.log("Sequel: ", link1.innerHTML, link1.href);
console.log("Sequel: ", link2.innerHTML, link2.href);
console.log("Sequel: ", link3.innerHTML, link3.href);
console.log("Prequel: ", link4.innerHTML, link4.href);
This method has the advantage of working even if there are links inside the first (stripped out) table.
But it won't work if the first (stripped out) table contains h2 elements... In that case, instead of
var h2 = content.getElementsByTagName("h2")[0]; // the first h2 element
You should use
var h2 = Array.prototype.filter.call(content.children, function(c) {return c.tagName.toLowerCase() == "h2"})[0];
EDIT
function listlinks(){
var prequels = [];
var sequels = [];
var all_h2_elems = document.getElementsByTagName("h2");
var h2_start = Array.prototype.filter.call(all_h2_elems, function(el){return el.innerText.indexOf("Related") != -1})[0];
var parent = h2_start.parentElement;
var h2_elems = Array.prototype.filter.call(parent.children, function(c) {return c.tagName.toLowerCase() == "h2"});
if (h2_elems.length < 2) console.log("You lied, you said there were always 2 h2 tags!");
if (!h2_start.isSameNode(h2_elems[0])) console.log("Hmmm, there should not be a h2 tag before the 'Related' one, fix your question.");
var sequel = false;
var prequel = false;
var current = h2_elems[0];
var end = h2_elems[1]
while(current && !current.isSameNode(end))
{
if (current.tagName === undefined)
{
if (current.nodeValue.indexOf("Sequel") != -1)
{
if (sequel || prequel) { console.log("wtf? another sequel?"); break; }
sequel = true;
}
else if (current.nodeValue.indexOf("Prequel") != -1)
{
if (!sequel) { console.log("wtf? prequel should be AFTER sequel"); break; }
prequel = true;
sequel = false;
}
else if (current.nodeValue.match(/[a-z]/)){
prequel = false;
sequel = false;
// stop outputing links if anything else is found
}
} // end if (current.tagName === undefined)
else if (current.tagName.toLowerCase() === "a")
{
if (prequel) prequels.push(current.innerHTML + " : " + current.href);
if (sequel) sequels.push(current.innerHTML + " : " + current.href);
}
current = current.nextSibling;
}
return [prequels,sequels];
}
listlinks().forEach(function(el,i){console.log(i?"Sequels:":"Prequels:",el)})

Trying to remove element based on type of attribute

I am trying to remove an element based on type of attribute. It isn't working for some reason.
The element in question is this:
<p style="width:250px;font-size:11px;text-align:left;margin-left:1.2ex;margin-top:0px;margin-bottom:0px;line-height:1.15em;">– in Europe<span style="font-size:8px;"><span style="white-space:nowrap;"> </span></span>(<span style="font-size:9px;">green & dark grey</span>)<br>
– in the European Union<span style="font-size:8px;"><span style="white-space:nowrap;"> </span></span>(<span style="font-size:9px;">green</span>)</p>
I am trying to remove it this way - item is a container element.
$(item).find("p").filter("[style]").remove();
There are no other <p> tags with the attribute style, however this doesn't appear to remove it.
Other code, like this, works fine:
$(item).find(".reference").remove();
How do I remove all p tags with the style attribute from the item element?
This is how item is created:
$.get(link, function(response) {
var elements = $.parseHTML(response);
var wiki = $(elements).find('#mw-content-text').find("p");
//var ps = [];
var arrayLength = wiki.length;
for (var i = 0; i < arrayLength; i++) {
if (wiki[i].innerHTML === "") {
break;
}
var item = wiki[i];
The link variable is a link to wikipedia.
Maybe try this:
$.each(item.children('p'), function(index) {
if ($(this).attr('style')) {
$(this).remove();
}
});
item refers to p element itself. you don't have to find p in item:
$(item).filter("[style]").remove();
after re-looking over your question ,
$(item).find("p").filter("[style]").remove();
is perfectly valid , instead of trying to come up with alternative ways to write it , find out what is wrong with item, because it is not what you think it is if above code is not working

How do we implement a cancel in plain Javascript?

I have a page and I display data in a table.
In each table I have a column with a checkbox which if is checked the user can modify the specific row via Javascript.
This is done as its td encapsulates either an input or a select and I make these editable for the user.
The user modifies the row and presses save and the changes are saved. So far ok.
My problem is how do I implement a cancel?
The user could choose many row i.e. check boxes and modify them but the user could also press cancel. On cancel the original values should be displayed (and the rows become non-editable again).
But how is a cancel operation implemented in Javascript? Do we store data in some global datastructures? Which would be this in Javascript?
Ok, after the addition of informations you provided I suggest you setup the following mecanism:
function getDatas() {
var oXhr;
//get datas from database:
oXhr = new XMLHttpRequest();
oXhr.onreadystatechange = function() {
if (oXhr.readyState == 4 && (oXhr.status == 200)) {
g_oData = (new DOMParser()).parseFromString(oXhr.responseText, "text/xml");
}
}
oXhr.open("POST", "yourphpscriptthatreturnsthexmldatas.php", true);
oXhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=utf-8");
oXhr.send();
}
function populateGrid() {
//use g_oData to populate your grid, but at first totally clean the body
var mygrid = document.getElementById("mygridid");
//mygrid.innerHtml = "<table><tr><td>...</td></tr></table>";
//use the xml library to parse g_oData and fill up the table:
var xmlRows = g_oData.getElementsByTagName("TAG");
var xmlRow;
iLen = xmlRows.length;
for (var i=0;i<iLen;i++) {
xmlRow = xmlRows[i];
//use xmlRow->textContent to build each cell of your table
}
}
function revertChange() {
//on cancel, revert the changes by populating the grid.
//it will use the global xml/json object loaded directly from database, to refill everything.
populateGrid();
}
I did it myself many times to refresh some datas in a page. That's basically what you're doing except that you're not requesting anything to the database, you just refill the fields.
You can just access the original value attribute of the input to get the defaultValue. Sample implementation:
$("table").on("dblclick", "td", function(e) {
var val = $(this).html();
$(this).empty().append($("<form/>").append(
$("<input/>", {type:"text"}).attr("value", val),
// ^^^^
// set the *attribute*, as if it was present in the parsed HTML
$("<button/>", {type:"reset"}).text("Reset"),
$("<button/>", {type:"button", class:"cancel"}).text("Cancel"),
$("<button/>", {type:"submit"}).text("Submit")
));
}).on("submit", "form", function(e) {
var val = $(this).find("input:text").val();
// ^^^^^
// which is equivalent to .prop("value")
/* then do something with val, e.g. send it to server via ajax */
$(this).parent().html(val);
e.preventDefault();
}).on("click", "button.cancel", function(e) {
var $form = $(this).parent(),
$input = $form.find("input:text"),
oldval = $input.attr("value");
// ^^^^^^^^^^^^^
// or .prop("defaultValue"), but not .val()!
if (oldval == $input.val() || confirm("Do you really want to discard your changes?"))
$(this).parent().html(oldval);
e.preventDefault();
});
(Demo at jsfiddle.net)
A maybe more simple solution might be to use the dblclick-handler that creates the form as a closure and just store the original html in a local variable there.
Here is a pretty simple way:
Don't replace the cell content with the form element. Keep the value (the text) in a span element and hide it when you show the form element. Then you don't have to do anything on cancel. Just show the span again and hide or remove the form element. Only update the span when the user wants to save the value.
Here is an example. The showing and hiding is all done with CSS.
<tr>
<td>
<span>value</span>
<input type='text' value='' />
</td>
<td>
<button class="save">Save</button>
<button class="revert">Revert</button>
</td>
</tr>
JS:
var rows = document.querySelectorAll('table tr');
for(var i = 0, l = rows.length; i < l; i++) {
rows[i].addEventListener('click', function(event) {
// all value display elements in the row
var spans = this.querySelectorAll('span');
// all form elements in the row
var inputs = this.querySelectorAll('input');
// handle click on save button
if (event.target.className === 'save') {
[].forEach.call(inputs, function(input, i) {
spans[i].innerHTML = input.value;
});
this.className = '';
}
// handle click on revert button
else if (event.target.className === 'revert') {
// not much to do
this.className = '';
}
else {
// update form element values
[].forEach.call(inputs, function(input, i) {
input.value = spans[i].innerHTML;
});
this.className = 'edit';
}
});
}
DEMO
You can use the HTML5 data- attributes to implement a revert function. This way, each <input> would hold it's original value in case a revert button would be used.
Here's how it'd look:
<table>
<tr>
<td><input type='text' value='change me' data-original='change me' /></td>
<td><input type='text' value='change me2' data-original='change me2' /></td>
<td><input type='button' value='revert' onclick='revert(this)'/></td>
</tr>
<table>
And the code that reverts:
function revert(btn) {
var parentTr = btn.parentNode.parentNode;
var inputs = parentTr.getElementsByTagName('input');
for(var i = 0; i < inputs.length; i++) {
if (inputs[i].type == 'text') {
inputs[i].value = inputs[i].getAttribute('data-original');
}
}
}
The data-original attribute could be generated:
By the server-side app who serves the page (see (1) demo fiddle here); or
by a JavaScript function that is executed as soon as the DOM is ready (see (2) demo fiddle for this here).
As a side solution, you could store the original values in a map object. Here's the (3) demo for this (notice I added the id for each input, so it can be used as key to the map).
Keep in mind, though, neither solutions (2) or (3) require changing in server side code (the 3 assuming your inputs have ids). And (2) feels clearer.
About the defaultValue attribute: The defaultValue attribute can be a solution only if the value to be reverted never changes and if the fields involved are text inputs.
Firstly, changing the "default value" is rather awkward and may break something else aling the page (one would expect the browsers make the defaultValue attribute read-only, but that does not seem to be the case). Secondly, you would be limited to inputs of the text type.
Still, if none of that is a problem, the code above can be quickly adapted to use them instead of data- attributes.

Inject Code via Jquery Around specific Content

I'm having a bit of a quarrel with jQuery. I'm trying to inject a span with a specific class around a part of the content on an HTML page.
For example, this is the html I have:
<td class="">4 view</td>
And what I want is
<td class=""><span class="num_cell">4</span> view</td>
I feel this may be easier than I'm making it -- can anyone help?
This should also work:
$('td').each(function(){
$(this).contents().first().wrap("<span class='num_cell'>");
})
if you only want to cover only textNode you should use .contents() which also returns textNodes as item.
Please check the documentation http://api.jquery.com/contents/ there is an example which is exact answer of your question.
$("p").contents().filter(function(){ return this.nodeType != 1; }).wrap("<b/>");
After your comment, I don t think you need a loop for this, can you try the below code?
$("td").contents().filter(function(){ return this.previousSibling == null && this.nodeType != this.TEXT_NODE; }).wrap("<span/>");
this.previousSibling == null means it is first if you want to check if it is the first element or not
Cheers.
Have a look at this http://jsfiddle.net/zkjyV/30/
$(document).ready(function() {
$("td").each(function() {
var $div = $(this);
var $a = $div.find("a");
$div.find(a).remove();
var number = $div.html();
$div.html("<span class='num_cell'>" + number + "</span>").append($a);
});
});​
I did it with a div so it would run in jsFiddle. But you can replace the div with a and it should work just fine :)
Here's the final version http://jsfiddle.net/zkjyV/33/
Wow.. lots of answers already, here's a pure javascript answer for what it's worth.. I added an ID to the cell to make it simple, but you could easily take it from there to make it generic..
HTML
<td class="" id="targetCell">4 view</td>​
JS:
//get the parent reference to cache and avoid requerying the DOM
var parentCell = document.getElementById('targetCell');
// get the value of the first node (in this case TextNode)
var result = parentCell.firstChild.nodeValue;
// remove the TextNode
parentCell.removeChild(parentCell.firstChild);
// create a new span
var newSpan = document.createElement("SPAN");
//just for testing
newSpan.style.backgroundColor = 'orange';
//populate the value
newSpan.innerText = result;
//inject before the first child
parentCell.insertBefore(newSpan, parentCell.firstChild);​
JSFIDDLE:
http://jsfiddle.net/RBhLB/1/

Categories