Javascript Table Iteration - javascript

I have an x-y table called "num", and I am trying to iterate through the table and obtain the numerical values in the cells. The alert keep showing me "undefined", and I don't know why. I've tried the innerHTML function, but it doesn't give me the value I need. Any help would be appreciated (I'm new to Javascript).
var x=0;
var y=0;
var table=document.getElementById("num");
for (var r = 1, n = table.rows.length; r < n; r++) {
x= table.rows[r].cells[0].value;
y= table.rows[r].cells[1].value;
alert(x);
}

> x= table.rows[r].cells[0].value
Table cells (td and th elements) don't have a default value property. If you wish to get the text content of a DOM element, you can use the W3C textContent property or the IE innerText property depending on which one is supported in the host. Browsers support one or the other, some both, so you can do:
var cell;
...
cell = table.rows[r].cells[0];
x = cell.textContent || cell.innerText;
You can also write a small function to do the job:
function getText(el) {
if (typeof el.textContent == 'string') {
return el.textContent;
} else if (typeof el.innerText == 'string') {
return el.innerText;
}
}
Then you can just do:
x = getText(table.rows[r].cells[0]);
Note that the value of x will be a string, so be careful if you intend using the + operator for addition.

You'll want .innerText instead of innerHTML
.value is for form elements like inputs.

Related

Find the element with the most text

It's like this:
text = $('#content').find('.text'),
length = 0;
longest = '';
$(text).each(function(){
if($(this).text().length > length){
length = $(this).text().length;
longest = $(this);
}
});
alert($(longest).text());
But can I do it without the loop? I mean somehow directly select the longest text element? Because this is too slow
You cannot do it without a loop. There is no built-in DOM function to find the element with the longest text, thus some piece of code has to iterate through them and find which one is longest.
Here's a cleaned up version of your code:
var longestSoFar = -1;
var longestItem;
$('#content .text').each(function(){
var text = $(this).text();
if (text.length > longestSoFar) {
longestSoFar = text.length;
longestItem = this;
}
});
alert($(longestItem).text());
The only other thing I can think of would be to first compare heights of .text elements and find the ones that taller than the others and then compare text length in just those. This is probably a lot quicker than computing the actual length of the text, but whether it would be useful or not depends upon your layout and whether height would be an accurate filter or not.
To speed it up further, we'd probably have to see what your HTML generally looks like to have an idea of where the slow-down is coming from. .text() isn't a particularly fast operation because it has to walk all the text nodes in a given element and accumulate all their text. If there's only one node in the element, it's quick, but if there's lots of involved HTML in the node, then it has a lot of nodes and childnodes to walk to find all the text nodes.
Note, in your code you were making a jQuery object out of something that was already a jQuery object. It works that way, but it's a waste of a function call when it's already a jQuery object.
The plain script version is pretty similar:
var longestSoFar = -1;
var longestItem;
var els = document.querySelectorAll('#content .text');
for (var i=0, iLen=els.length, len; i<iLen; i++) {
len = (els[i].textContent || els[i].innerText).length;
if ( len > longestSoFar) {
longestItem = els[i];
longestSoFar = len;
}
}
alert(longestItem.textContent || longestItem.innerText);
Other way use get() to save it to Array and then sort the array with a custom sort using a function which uses length as way of sorting and not in alphabetical order, like:
var listitems = $(".text").get();
listitems.sort(function(a, b) {
var compA = $(a).text().length;
var compB = $(b).text().length;
return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;});
// Get the last element of the array which has the longest length
alert("Longest = "+$(listitems[listitems.length-1]).text());
See JSFiddle
fiddle DEMO
fiddle DEMO-1
var con = $('#content');
var arr = con.find('.text').map(function () {
return $(this).text().length;
}).get();
var longest = arr.indexOf(Math.max.apply(Math,arr));
alert($('#content .text:nth-child(' + ++longest + ')').text());

Javascript error : catChildNotes[y].setAttribute is not an function

So, I have some JavaScript code which I test with Greasemonkey locally. But I get this persistent error in the Firefox Error Console:
catChildNotes[y].setAttribute is not an function
Code:
var i = prompt("How many videos have you got?", "");
function remove_mp4()
{
titleElems=document.getElementsByName("title");
for(i=0; i<titleElems.length; i++)
{
titleInner=titleElems[i].innerHTML;
titleElems[i].innerHTML=titleInner.replace(titleInner.match(".mp4"), "");
}
}
for (var x = 0; x < i; i++)
{
document.getElementsByName("description")[x].value = "Visit me on my web-site :\
\
http://www.sample.com/";
document.getElementsByName("keywords")[x].value = prompt("Enter keywords : ","");
catChildNodes=document.getElementsByName("category")[x].childNodes;
catChildNodes[x + 1].removeAttribute("selected");
for(y=0; y<catChildNodes.length; y++)
{
if(catChildNodes[y].value="27")
{
catChildNodes[y].setAttribute("selected","");
}
}
}
remove_mp4();
This script should be run on Youtube upload page and do the following :
Remove ".mp4" from the title
Add default description
Add keywords (which are equal to the prompt value)
Change category to "Education"
Typically, when you get the child nodes of an element you will get other element nodes and text nodes. The former have a setAttribute method, the latter don't (simply because text nodes don't have any attributes). If you need only the element children and no the text nodes then you should use children property instead of childNodes.
There is at least one more bug in your code, this is not a comparison:
if (catChildNodes[y].value = "27")
This will assign the value 27 to catChildNodes[y].value. If you actually want to compare then you should use the comparison operator:
if (catChildNodes[y].value == "27")

How to remove eval() from dynamic property calls and Uncaught ReferanceError

I'm trying to call object properties dynamically. I have solved the problem with eval() but i know that eval is evil and i want to do this on a better and safer way. My eval code:
// onfocus
var classes = this.getAttribute('class').split(' ');
for(var i = 0; i < classes.length; ++i) {
if(classes[i].match(/val\- */) !== null) {
var rule = classes[i].substr(4);
var instruction = eval('validate.instructionTexts.'+ rule +'()');
tooltip.appendChild( document.createTextNode(instruction) );
}
}
And I also have this code:
// onblur
var classes = this.getAttribute('class').split(' ');
for( var i = 0; i < classes.length; ++i ){
if(classes[i].match(/val\- */) !== null) {
var rule = classes[ i ].substr( 4 );
var tooltip = document.getElementsByClassName( 'tooltip' );
for( i = 0; i < tooltip.length; ++i){
tooltip[ i ].style.display = 'none';
}
eval('validate.rules.'+ rule +'(' + (this.value) + ')');
}
the problem with the second code is that I want to send a string to my property. this.value = the text i type in my textbox so i get correct string from this.value but i got this error.
if i type foo.
Uncaught ReferenceError: foo is not defined. Javascript thinks I trying to send a variabel but i want it to send a string. How can i solve this problems?
An HTML element's CSS class can be accessed directly from JS thru the className property.
JS object properties can be accessed via the dot-notation object.property or via the square-bracket-notation object['property'].
The regex /val\- */ matches the characters v, a, l, a '-' hyphen, and zero or more spaces, anywhere in the string.
The spaces are completely irrelevant since you're testing the result of a string that was split on spaces (and so it won't contain any spaces anymore).
Also, you're not anchoring the regex so a class of 'eval-test' will also be matched. I doubt that's what you're looking for.
If you were just testing for the classes starting with val-, then the indexOf method is much easier to read, and probably also a lot more efficient.
I've adjusted your bits of code accordingly. I'm assuming that the class names for your validation rules all start with val-, and that the rest of the class name is the name for the rule:
// onfocus
var classes = this.className.split(' ');
for(var i = 0; i < classes.length; ++i) {
if(classes[i].indexOf('val-') === 0) { // the class name starts with 'val-'
var rule = classes[i].substr(4);
var instruction = validate.instructionTexts[rule]();
tooltip.appendChild(document.createTextNode(instruction));
}
}
// onblur
var classes = this.className.split(' ');
for (var i = 0; i < classes.length; ++i ){
if(classes[i].indexOf('val-') === 0) { // the class name starts with 'val-'
var rule = classes[i].substr(4);
var tooltip = document.getElementsByClassName('tooltip');
for (i = 0; i < tooltip.length; ++i){
tooltip[i].style.display = 'none';
}
validate.rules[rule](this.value);
}
}
You do not need to use eval, you can access it as:
validate.rules[rule](this.value);
Which will solve your other problem too, which is that you are passing in the value of this.value which when eval()'d is not quoted as a string (which 'foo' is) so is being interpreted as a variable.
to get a property foo from object obj, you could use either
obj.foo
or
obj["foo"]
The first one won't allow reserved words or if the property contains spaces.
So your first example could change to
validate.instructionTexts[rule]()

How to get text from all descendents of an element, disregarding scripts?

My current project involves gathering text content from an element and all of its descendants, based on a provided selector.
For example, when supplied the selector #content and run against this HTML:
<div id="content">
<p>This is some text.</p>
<script type="text/javascript">
var test = true;
</script>
<p>This is some more text.</p>
</div>
my script would return (after a little whitespace cleanup):
This is some text. var test = true; This is some more text.
However, I need to disregard text nodes that occur within <script> elements.
This is an excerpt of my current code (technically, it matches based on one or more provided selectors):
// get text content of all matching elements
for (x = 0; x < selectors.length; x++) { // 'selectors' is an array of CSS selectors from which to gather text content
matches = Sizzle(selectors[x], document);
for (y = 0; y < matches.length; y++) {
match = matches[y];
if (match.innerText) { // IE
content += match.innerText + ' ';
} else if (match.textContent) { // other browsers
content += match.textContent + ' ';
}
}
}
It's a bit simplistic in that it just returns all text nodes within the element (and its descendants) that matches the provided selector. The solution I'm looking for would return all text nodes except for those that fall within <script> elements. It doesn't need to be especially high-performance, but I do need it to ultimately be cross-browser compatible.
I'm assuming that I'll need to somehow loop through all children of the element that matches the selector and accumulate all text nodes other than ones within <script> elements; it doesn't look like there's any way to identify JavaScript once it's already rolled into the string accumulated from all of the text nodes.
I can't use jQuery (for performance/bandwidth reasons), although you may have noticed that I do use its Sizzle selector engine, so jQuery's selector logic is available.
function getTextContentExceptScript(element) {
var text= [];
for (var i= 0, n= element.childNodes.length; i<n; i++) {
var child= element.childNodes[i];
if (child.nodeType===1 && child.tagName.toLowerCase()!=='script')
text.push(getTextContentExceptScript(child));
else if (child.nodeType===3)
text.push(child.data);
}
return text.join('');
}
Or, if you are allowed to change the DOM to remove the <script> elements (which wouldn't usually have noticeable side effects), quicker:
var scripts= element.getElementsByTagName('script');
while (scripts.length!==0)
scripts[0].parentNode.removeChild(scripts[0]);
return 'textContent' in element? element.textContent : element.innerText;
EDIT:
Well first let me say im not too familar with Sizzle on its lonesome, jsut within libraries that use it... That said..
if i had to do this i would do something like:
var selectors = new Array('#main-content', '#side-bar');
function findText(selectors) {
var rText = '';
sNodes = typeof selectors = 'array' ? $(selectors.join(',')) : $(selectors);
for(var i = 0; i < sNodes.length; i++) {
var nodes = $(':not(script)', sNodes[i]);
for(var j=0; j < nodes.length; j++) {
if(nodes[j].nodeType != 1 && node[j].childNodes.length) {
/* recursion - this would work in jQ not sure if
* Sizzle takes a node as a selector you may need
* to tweak.
*/
rText += findText(node[j]);
}
}
}
return rText;
}
I didnt test any of that but it should give you an idea. Hopefully someone else will pipe up with more direction :-)
Cant you just grab the parent node and check the nodeName in your loop... like:
if(match.parentNode.nodeName.toLowerCase() != 'script' && match.nodeName.toLowerCase() != 'script' ) {
match = matches[y];
if (match.innerText) { // IE
content += match.innerText + ' ';
} else if (match.textContent) { // other browsers
content += match.textContent + ' ';
}
}
ofcourse jquery supports the not() syntax in selectors so could you just do $(':not(script)')?

Fetch the data of a particular <td> in a table

I need to get the data of an particular <td>, but I don't have any id or name for that particular <td>. How do you get the contents of that <td>?
For example:
<table>
<tr><td>name</td><td>praveen</td></tr>
<tr><td>designation</td><td>software engineer</td></tr>
</table>
Is it possible to get the value "designation" from this table.. I need to extract the word "software engineer" using javascript.
I prefer to use jQuery to do all the heavy lifting for this sort of task.
For example, the following function will return the text of the next element of the same type that you're searching for:
function GetNextChildText(tagToFind, valueToFind) {
var nextText = "";
$(tagToFind).each(function(i) {
if ($(this).text() == valueToFind) {
if ($(this).next() != null && $(this).next() != undefined) {
nextText = $(this).next().text();
}
}
});
return (nextText);
}
So, for your example table, your call to return the designation could be:
var designationText = GetNextChildText('td', 'designation');
And the result is that the variable designationText would contain the value 'software engineer'.
A quick solution:
function GetTdContent(label)
{
var TDs = document.getElementsByTagName("TD");
var foundFlag = false;
for (i = 0; i < TDs.length; i++)
{
if (foundFlag) return TDs[i].innerHTML;
foundFlag = TDs[i].innerHTML.toLower() == label.toLower();
}
}
elsewhere call:
var value = GetTdContent("designation");
Explanation:
The function iterates all TDs in the document. If it finds one with the given label, say "designation", it loops one more time and returns the next TD content.
This makes a few assumptions about your source HTML. If you know your data though, it can be enough.
Something along the line of:
(not tested, just quick code to give an idea)
var tables = document.getElementById('TABLE'); // instead of document.all.tag
var rows;
var cells;
var maxCells = 1;
var designation;
if (tables) {
for (var t=0; t<tables.length; t++) {
rows = tables[t].all.tags('TR');
if (tables[t].all.tags('TABLE').length == 0) {
for (var r=0; r<rows.length; r++) {
if (rows[r].innerText != '') {
cells = rows[r].all.tags('TD');
for (var c=0; c<cells.length; c++) {
if (cells[c].innerText == 'designation' && c<(cells.length-1)) {
designation = cells[c+1].innerText;
}
}
}
}
}
}
}
Since document.all is IE specific, you should rather user getElementById, with the following to redefine that function for IE:
if (/msie/i.test (navigator.userAgent)) //only override IE
{
document.nativeGetElementById = document.getElementById;
document.getElementById = function(id)
{
var elem = document.nativeGetElementById(id);
if(elem)
{
//make sure that it is a valid match on id
if(elem.attributes['id'].value == id)
{
return elem;
}
else
{
//otherwise find the correct element
for(var i=1;i<document.all[id].length;i++)
{
if(document.all[id][i].attributes['id'].value == id)
{
return document.all[id][i];
}
}
}
}
return null;
};
}
Use XPath (tutorial here, including instructions for IE and other browsers: http://www.w3schools.com/XPath/xpath_examples.asp)
The xpath for your example is
//table/tr/td[text()="designation"]/following::td
("the td that follows the td with text "designation" that's in a tr that's in a table somewhere in the document")
Simpler Xpaths are possible - if it's the only table cell that could contain 'designation' you could use
//td[text()="designation"]/following::td
One issue with writing code to do the particular search is that it needs changing, possibly significantly, if the structure of your page changes. The Xpath may not need to change at all, and if it does, it's only one line.
Tomalak's a little quicker:
<script type="text/javascript">
function getText(tText){
var tds = document.getElementsByTagName("td");
for(var i=0, im=tds.length; im>i; i++){
if(tds[i].firstChild.nodeValue == tText)
return tds[i].nextSibling.firstChild.nodeValue;
}
}
</script>

Categories

Resources