I wan to collect all text from a list of elements obtains using
var elements =document.body.getElementsByTagName("*");
What I've done so far:
var text = '';
for (var i = 0; i < elements.length; i++) {
text = text + ' ' + elements[i].innerText
}
This will return duplicated text because it get the own text of each element plus its children's. I want to know if there is a way to get element's owntext using pure javasript?
I think the issue is that nested matching elements of a particular tag are being counted twice. The solution is to check if we've already visited a parent element and to skip the child if that's the case.
var text = '';
var visited = [];
for (var i = 0; i < elements.length; i++) {
var found = false;
for (var e = elements[i]; e != null; e = e.parentNode) {
if (visited.indexOf(e) > -1) {
found = true;
break;
}
}
if (!found) {
text = text + ' ' + elements[i].innerText;
visited.push(elements[i]);
}
}
http://jsfiddle.net/h8k0xx82/
Related
On my page I use searching for a text and highlighting it like this:
document.querySelector('#search').addEventListener('keyup', function() {
var inputValue = this.value;
var tableTDs = document.querySelectorAll('table td');
for(var i = 0; i < tableTDs.length; i++) {
tableTDs[i].scrollIntoView();
if(document.createRange) {
var range = document.createRange();
var childs = tableTDs[i].childNodes;
for(var n = 0; n < childs.length; n++) {
var childNode = childs[n];
if(childNode.nodeValue) { // if child is a text node
var childValue = childNode.nodeValue;
} else { // if child is a nested element e.g. a link
var childValue = childNode.firstChild.nodeValue;
var childNode = childNode.firstChild;
}
if(childValue && childValue.indexOf(inputValue) != -1) {
range.setStart(childNode, childValue.indexOf(inputValue));
range.setEnd(childNode, childValue.indexOf(inputValue) + inputValue.length);
var span = document.createElement('span');
span.style.backgroundColor = 'yellow';
range.surroundContents(span);
return;
}
}
}
}
});
<input type="text" id="search">
<table>
<tr><td>First cell</td><td>Second cell and link anchor</td></tr>
</table>
If I want to find for example the word "anchor", I type "a", next "n" and next "c" and I see two highlightings, but not one highlighting of "anc".
So as I can understand, I need to remove all the wrappers before new range.surroundContents()
How can I resolve the issue?
UPDATED
Ok, the first possible solution is adding before tableTDs[i].scrollIntoView() the following code
tableTDs[i].innerHTML = tableTDs[i].innerHTML.replace(/<span style=\"background-color: yellow;\">/g,'');
tableTDs[i].innerHTML = tableTDs[i].innerHTML.replace(/<\/span>/g,'');
But is there something better?
I am working on a form, and I would like to reset the lines individually without using reset.
How to vary the values โโof the attribute passed as parameter of the method getElementById in JavaScript using a loop?
Here is an example of my source code below:
<script>
var element = document.getElementById('#re');
element.addEventListener('click', function() {
document.getElementById("#id1").value = "";
document.getElementById("#id2").value = "";
document.getElementById("#id3").value = "";
});
</script>
Assuming your IDs have the format shown in your example:
for (var i = 1; i <= 3; i++) {
document.getElementById("id" + i).value = "";
}
If that's not the case but you know the ID of every element you can put all IDs in an array and use that:
var elementIds = ["id1", "id2", "id3"];
elementIds.forEach(function(id) {
document.getElementById(id).value = "";
});
Another solution is to give all the elements you want to reset a specific class and target that:
var elements = document.getElementsByClassName("resetable-element");
[].slice.call(elements).forEach(function(element) {
element.value = "";
});
Instead of using ids, you can loop through your inputs for example:
var inputs = document.querySelectorAll("input");
for (var i = 0; i < inputs.length; i++) {
inputs[i].value = "";
}
For your simple example, you could loop through the values 1 - 3 (I assume the # in the ID is a typo?):
for(var i = 1; i <= 3; i++) {
document.getElementById('id' + i).value = '';
}
If you can identify the elements by something else, such as a class name, you might prefer to iterate over that:
var elements = document.querySelectorAll('.field');
Array.prototype.slice.call(elements).forEach(function(element) {
element.value = '';
});
I thought this would be easier, but running into a weird issue.
I want to split the following:
theList = 'firstword:subwordone;subwordtwo;subwordthree;secondword:subwordone;thirdword:subwordone;subwordtwo;';
and have the output be
firstword
subwordone
subwordtwo
subwordthree
secondword
subwordone
thirdword
subwordone
subwordtwo
The caveat is sometimes the list can be
theList = 'subwordone;subwordtwo;subwordthree;subwordfour;'
ie no ':' substrings to print out, and that would look like just
subwordone
subwordtwo
subwordthree
subwordfour
I have tried variations of the following base function, trying recursion, but either get into infinite loops, or undefined output.
function getUl(theList, splitOn){
var r = '<ul>';
var items = theList.split(splitOn);
for(var li in items){
r += ('<li>'+items[li]+'</li>');
}
r += '</ul>';
return r;
}
The above function is just my starting point and obviously doesnt work, just wanted to show what path I am going down, and to be shown the correct path, if this is totally off base.
It seems you need two cases, and the difference between the two is whether there is a : in your string.
if(theList.indexOf(':') == -1){
//Handle the no sublist case
} else {
//Handle the sublist case
}
Starting with the no sublist case, we develop the simple pattern:
var elements = theList.split(';');
for(var i = 0; i < elements.length; i++){
var element = elements[i];
//Add your element to your list
}
Finally, we apply that same pattern to come up with the implementation for the sublist case:
var elements = theList.split(';');
for(var i = 0; i < elements.length; i++){
var element = elements[i];
if(element.indexOf(':') == -1){
//Add your simple element to your list
} else {
var innerElements = element.split(':');
//Add innerElements[0] as your parent element
//Add innerElements[1] as your child element
//Increment i until you hit another element with ':', adding the single elements each increment as child elements.
//Decrement i so it considers the element with the ':' as a parent element.
}
}
Keep track of the current list to add items to, and create a new list when you find a colon in an item:
var baseParent = $('ul'), parent = baseParent;
$.each(theList.split(';'), function(i, e) {
if (e.length) {
var p = e.split(':');
if (p.length > 1) {
baseParent.append($('<li>').append($('<span>').text(p[0])).append(parent = $('<ul>')));
}
parent.append($('<li>').text(p[p.length - 1]));
}
});
Demo: http://jsfiddle.net/Guffa/eWQpR/
Demo for "1;2;3;4;": http://jsfiddle.net/Guffa/eWQpR/2/
There's probably a more elegant solution but this does the trick. (See edit below)
function showLists(text) {
// Build the lists
var lists = {'': []};
for(var i = 0, listKey = ''; i < text.length; i += 2) {
if(text[i + 1] == ':') {
listKey = text[i];
lists[listKey] = [];
} else {
lists[listKey].push(text[i]);
}
}
// Show the lists
for(var listName in lists) {
if(listName) console.log(listName);
for(var j in lists[listName]) {
console.log((listName ? ' ' : '') + lists[listName][j]);
}
}
}
EDIT
Another interesting approach you could take would be to start by breaking it up into sections (assuming text equals one of the examples you gave):
var lists = text.match(/([\w]:)?([\w];)+/g);
Then you have broken down the problem into simpler segments
for(var i = 0; i < lists.length; i++) {
var listParts = lists[i].split(':');
if(listParts.length == 1) {
console.log(listParts[0].split(';').join("\n"));
} else {
console.log(listParts[0]);
console.log(' ' + listParts[1].split(';').join("\n "));
}
}
The following snippet displays the list depending on your requirements
var str = 'subwordone;subwordtwo;subwordthree;';
var a = []; var arr = [];
a = str;
var final = [];
function split_string(a){
var no_colon = true;
for(var i = 0; i < a.length; i++){
if(a[i] == ':'){
no_colon = false;
var temp;
var index = a[i-1];
var rest = a.substring(i+1);
final[index] = split_string(rest);
return a.substring(0, i-2);
}
}
if(no_colon) return a;
}
function display_list(element, index, array) {
$('#results ul').append('<li>'+element+'</li>');
}
var no_colon_string = split_string(a).split(';');
if(no_colon_string){
$('#results').append('<ul><ul>');
}
no_colon_string.forEach(display_list);
console.log(final);
working fiddle here
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();
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")