Often there is a need for client side (JavaScript) DOM oriented manipulation in Web Applications. To achieve a higher degree of user interactivity, more productivity or simply a more visually appealing user interface. And such client side operations come almost invariably with a lot of DOM manipulation – direct interaction with the nodes of the browser in memory representation of the HTML document. This blog discusses an example of such manipulation and introduces a little generic code that may be of use to many people
The example I have to work on is shown below:
In this entry form, the user can enter two sorts of values: amounts (in euros) and percentage for each quarter in the current year. Occasionally, the user must also enter a specific date value. However, since that is relatively rare and this matrix can easily contain dozens of rows, it is deemed better to only show those date fields when they are actually needed.
Toggling the view – aka client side detail disclosure – should unveil the date fields in the table row in which the disclose icon was clicked. The icon should change its appearance into a close date fields icon. Something like:
The main requirements in terms of DOM interaction
:
- from the toggle icon, find its enclosing TR (table row) ancestor (since the disclose acts on all fields in the same row)
- from the Table Row, find all descendants that are a SPAN and satisfy a certain naming condition
- hide the disclose icon/show the close icon; unveil the date fields (and later on hide them again)
The third requirement – hiding and displaying elements – is one I have solved many times, so that’s not the issue. The other two are not really much of an issue, except for the fact that I do not do this sort of thing often enough, so I keep reinventing the same wheel. Now for my own benefit – in order to find this code when next I have a need for it, in six months time or so – the more or less generic functions that help me implement these two requirements:
Find an Element’s Ancestor of a specific type (tag):
function findParentWithTag(el, tagName) {
var parent = el.parentNode;
if (parent)
if (parent.tagName ==tagName)
return parent;
else
return findParentWithTag(parent, tagName);
else
return null;
}//findParentWithTag
Find all descendants – at all levels in the tree – for a given element that satisfy the given condition
function findChildrenWithTagAndCondition( el, tagName, evalfunction) {
var children = new Array();
if (tagName) {
descendants = el.getElementsByTagName(tagName);
for (var j =0; j < descendants.length;j++) {
if ( evalfunction(descendants[j]))
{ children.push(descendants[j]);
} //if
} //for
}
else {
for( var i = 0; i < el.childNodes.length; i++ ) {
if (el.childNodes[i].tagName==tagName ) {
if ( evalfunction(el.childNodes[i]))
{ children.push(el.childNodes[i]);
} //if
} //if
// investigate the children of this element
descendants = findChildrenWithTagAndCondition( el.childNodes[i], tagName, evalfunction);
if (descendants && descendants.length > 0) {
// add all children found (including descendants further down the tree) to the list of qualifying children
for (var j =0; j < descendants.length;j++) {
children.push( descendants[j]);
} //for
}//if
}//for
}
return children;
}// findChildrenWithTagAndCondition
This latter function can be called with the element itself, an optional tag name and the function that should be called to find out whether or not a certain child element qualifies. Here we can use the fun characteristic of JavaScript that allows us to create functions on the fly.
var evaldiv = new Function("div", "result = div.id.indexOf('VerplichtingDatum'); return result > -1;");
divs = findChildrenWithTagAndCondition( tr, "DIV", evaldiv);
This example shows the creation of an evaluation function that has a single input parameter (called div) and executes the evaluation "div.id.indexOf(‘VerplichtingDatum’)" : all div children whose id attribute contain the string VerplichtingDatum qualify.
Hide/Display elements dynamically
To make the story complete – even though this is most trivial part – I will show how to hide/display element through DOM Manipulation.
First of all, I have included the following CSS style in my stylesheet:
.invisible {display:none}
Next, to hide an element, I simply apply this style to it. To make it visible, I set the style to nothing at all. For example, to unveil the Date Fields that are contained in DIV element whose styleClass is initially set to invisible:
function showDateFields(icon) {
// hide the unveil icon
icon.className='invisible';
// show the hide (again) icon
el = document.getElementById(icon.id.replace("show","hide"));
// set its classname to null
el.className='';
// find and unveil DateFields
// find parent TR
tr = findParentWithTag(icon, "TR");
var evaldiv = new Function("div", "result = div.id.indexOf('VerplichtingDatum'); return result > -1;");
divs = findChildrenWithTagAndCondition( tr, "DIV", evaldiv);
for( var i = 0; i < divs.length; i++ ) {
divs[i].className='';
}
}
mhmh
html display problem
I meant that in the function the symbols < and & appear as ampersand codes.
Hi Lucas,
about findChildrenWithTagAndCondition():
1) all “” have a bad editing, so appear as “>” and so on.
2) the function seems to allow the parameter tagName as optional:
if(tagname) do this,
else do that
but in else case, the function still wants tagname few lines after.
did I miss something ?
Jeroen,
Thanks for the tip! I will go and rewrite my code using JQuery. Better do this rightaway before I become attached to my own code. And I like the speed promised with JQuery! (and the fact that it is code that has been tested).
Lucas
Have you looked into jQuery? I’m afraid you rewrote some of the jQuery functions.
Code sample 1 (http://docs.jquery.com/DOM/Traversing):
Find an Element’s Ancestor of a specific type (tag):
$(“span”).parents(“p”)
Find all parent elements of each span that is a paragraph.
For the second sample you can use
find(expr) (though I’m not sure about that, but I think it can do what you want)
For showing and hiding you can use show() and hide():
$(“p”).show() will hide all ‘s
jQuery is a really great Javascript library and is only 20KB. Ajaxian recently pointed to an article to show that jQuery also is one of the fastests libraries out there:
http://ajaxian.com/archives/jquery-113-800-faster-still-20kb
It’s usually more efficient to write your own methods (you only write what you need), but I think it’s very hard to write faster code than jQuery.
But still a very nice article, seems I’m not the only Mr. Dojo at AMIS 😉