views:

182

answers:

6

I have a function that gets an array of DOM elements (based on tags) within a div.

Pseudocode:
1. Say I wanted to get all input and textarea elements within a table myTbl
2. Declare resultingArray = null
3. For each tag (ex: input, textarea)
4.   tagArray = Get all elements based on tag
5.   Create another array by manually looping through tagArray and adding 
it to resultingArray (the return type is dynamic collection and not an array.

Functionally, it works but it takes too long. Is there a way to do what I am trying to do faster?

A: 

i had similar situation with a table of 5 columns and up to 1200 rows (not all rows had all columns filled)

So all i did was put onclick handlers on onmouseover event for a table cell (putting onclick hanlders immediately on td would take too long too). And onclick handler would create textarea with text found in table cell.

Then i just got away with jQuery selectors!

Eimantas
+1  A: 

If your inputs and textareas are all within the same <form>, take a look at the form.elements DOM property. That way your code could be simplified to:

var resultingArray = document.getElementById("form-name").elements;

Edit:

If your list of tag names is dynamic and you can't use a library, I don't think you'll be able to easily get away from the looping approach, but you can try to make it as light-weight as possible:

var result = [], nTags = tags.length, elements, nElements;

for (var i = 0; i < nTags; i++) {
    elements = table.getElementsByTagName(tag);
    nElements = elements.length;

    for (var j = 0; j < nElements; j++) {
        result.push(element);
    }
}

You could perhaps look into XPath expressions as well, but beware browser differences (for example, the code below won't work in IE, though there is an alternative for it).

var result = document.evaluate(tags.join("|"), table, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
Ben Blank
I dont want to get all the elements. Only a selected few (input, span etc - this list is dynamically constructed)
I tried your solution. It works functionally. But it's actually taking 30% more than my previous solution :(
Good heavens. I can't imagine why; that should be about as straightforward as it gets! ;-)
Ben Blank
`for...in` isn't the right construct for looping over an Array or NodeList in JS, you need the `for(i= 0; i<n; i++)` form. For an Array, the values of tag will be 0, 1, 2 and so on; for the NodeList you would get method names. The XPath, on the other hand, should work fast where available.
bobince
@bobince — Right you are! Clearly, I was conflating `for...in` with `foreach...in`, which isn't even cross-browser. I've corrected my example.
Ben Blank
A: 

I'm not 100% sure I understand what you're trying to do, but this might point you in the right direction (note that it uses jQuery):

<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
    function getStuff(){
        resultArr = [];
        var inputs = $('#myTbl').find('input');
        var tareas = $('#myTbl').find('textarea'); 
        // do the looping bit which I don't completely understand
    }
</script>
inkedmn
well the problem is I can't combine two arrays without looping through the elements manually. Maybe this will help : http://www.codingforums.com/showthread.php?t=47683
I cannot use JQuery. I am looking for a Javascript solution
+2  A: 

This definitely the type of problem that begs for a jQuery solution

var $elements = jQuery( '#id-of-table input, #id-of-table textarea' );
$elements.each( function( i, element )
{
   // whatever you need here
} );
Peter Bailey
I cannot use JQuery
+4  A: 

Based on Ben's elements idea, here's another shot with a non nested loop.

var tagNames = { 'SELECT' : true, 'INPUT' : true, 'TEXTAREA' : true }; //use an object for faster lookups
var rawElemsArray = document.getElementById("form-name").elements;
var elems = [];
for (var i = rawElemsArray.length - 1; i >= 0; i--) {
    if (tagNames[rawElemsArray[i].tagName]) {
        elems.push(rawElemsArray[i]);
    }
}

EDIT: form.elements is defined in level 0 DOM, so I bet it is cross-browser. You can also use childNodes (which is cross-browser too) if that serves your purpose. The difference between them is that childNodes selects all nodes (div, p, span etc) and also empty text nodes (in non-IE browsers) while elements returns only the form controls.

Chetan Sastry
Very elegant. This should be considerably faster than looping over `getElementsByTagName`.
Ben Blank
This is great, but it does only work for form elements, and specifically form elements that are all in the same form. I know that has been mentioned in Ben's post, but it seems important enough to mention again.
Prestaul
Is this solution cross browser compatible?
+1  A: 

You can call or apply Array.slice to an HTMLCollection to convert it to a string. That allows you to use concat, which I think is the fastest possible solution:

function getElementsByTagNames(context, tags) {
    var res = [], 
        i = tags.length,
        slice = Array.prototype.slice;

    // Convert HTMLCollections to arrays and push onto the res array
    while(i--) res.push(slice.call(context.getElementsByTagName(tags[i])));

    // Use one concat call to merge all the arrays
    return Array.prototype.concat.apply([], res);
}

getElementsByTagNames(document.body, ['input', 'textarea']);

Keep in mind that this does not return the nodes in document order. It will return all the <input>s grouped together and all of the <textarea>s grouped together.

Prestaul