menus in JS or CSS - pros? cons?

F

firewood

What to do? Should a menu system be constructed in JavaScript or CSS?
A typical problem:
Using PHP/MySQL or other server side db/scripting, one generates an
array of categories and subcategories for site content that must be
displayed in a menu system on the browser. Of course, the menu should
show up properly in all modern browsers, but most importantly, it must
render legibly in IE6+ and Firefox.

Is this best handled by JavaScript, CSS or a combination?

Several posts for the last few months on this newsgroup have attempted
to address various details of implementing a JS solution, but there is
nothing that I have found, after thorough searching, that addresses
which approach (JS or CSS) is better. People have their opinions -
but why?

Finally, what are the best resources on the Net for menu systems?
There are many sites and many scripts. Do the experts here at CLJ
have reliable, robust code you can recommend?
 
I

Ian Collins

firewood said:
What to do? Should a menu system be constructed in JavaScript or CSS?
A typical problem:
Using PHP/MySQL or other server side db/scripting, one generates an
array of categories and subcategories for site content that must be
displayed in a menu system on the browser. Of course, the menu should
show up properly in all modern browsers, but most importantly, it must
render legibly in IE6+ and Firefox.

Is this best handled by JavaScript, CSS or a combination?

Several posts for the last few months on this newsgroup have attempted
to address various details of implementing a JS solution, but there is
nothing that I have found, after thorough searching, that addresses
which approach (JS or CSS) is better. People have their opinions -
but why?

Finally, what are the best resources on the Net for menu systems?
There are many sites and many scripts. Do the experts here at CLJ
have reliable, robust code you can recommend?

While CSS menus work well in modern browsers, considerable JavaScript
hackery is required for IE due to poor hover support.

So you may as well do the menu in JavaScript. JS also gives you the
choice on how to activate a menu, in some cases click to display is more
appropriate than hover.
 
J

Julian Turner

firewood wrote:

[snip]
What to do? Should a menu system be constructed in JavaScript or CSS?
[/snip]

For many examples of constructing cross-browser validating
multi-leveled dropdown lists in pure CSS, you could do worse than look
at:-

<URL: http://www.cssplay.co.uk>

There is some amazing work on that site.

JS does give you the option of adding additional features to your
menus, but one must also think of how the site will look without
JavaScript turned on.

Regards

Julian Turner
 
A

ASM

firewood a écrit :
What to do? Should a menu system be constructed in JavaScript or CSS?

No doubt : CSS

with a little JS help for IE
or the famous *.htc (only IE Windows)


Basic CSS :

ul#menu li ul { display: none }
ul#menu li:hover ul, ul#menu li.ie ul { display: block }

JS IE-helper :

var IE = false; /*@cc_on IE=true; @*/

onload = function() {
if(IE) {
function roll(what) {
what.className = what.className==''? 'ie' : '';
}
var L=document.getElementById('menu').getElementsByTagName('LI');
for(var i=0; i<L.length; i++) {
L.onmouseover = function() { roll(this); };
L.onmouseout = function() { roll(this); };
}
}
}

typical html :

<ul id="menu>
<li><a href="m1.htm">menu 1</a>
<ul>
<li><a href="m11.htm">sub-menu 1.1</a></li>
<li><a href="m12.htm">sub-menu 1.2</a></li>
</ul>
</li>
<li><a href="m2.htm">menu 2</a>
<ul>
<li><a href="m21.htm">sub-menu 2.1</a></li>
<li><a href="m22.htm">sub-menu 2.2</a></li>
</ul>
</li>
</ul>
 
M

Matt Kruse

ASM said:
onload = function() {
if(IE) {
function roll(what) {
what.className = what.className==''? 'ie' : '';
}
var L=document.getElementById('menu').getElementsByTagName('LI');
for(var i=0; i<L.length; i++) {
L.onmouseover = function() { roll(this); };
L.onmouseout = function() { roll(this); };
}
}
}


I could be wrong, but won't this introduce a memory leak in IE?
 
R

RobG

Matt said:
ASM said:
onload = function() {
if(IE) {
function roll(what) {
what.className = what.className==''? 'ie' : '';
}
var L=document.getElementById('menu').getElementsByTagName('LI');
for(var i=0; i<L.length; i++) {
L.onmouseover = function() { roll(this); };
L.onmouseout = function() { roll(this); };
}
}
}


I could be wrong, but won't this introduce a memory leak in IE?


Can you identify a closure? If not, then there is no problem.
 
I

Ian Collins

RobG said:
Matt said:
ASM said:
onload = function() {
if(IE) {
function roll(what) {
what.className = what.className==''? 'ie' : '';
}
var L=document.getElementById('menu').getElementsByTagName('LI');
for(var i=0; i<L.length; i++) {
L.onmouseover = function() { roll(this); };
L.onmouseout = function() { roll(this); };
}
}
}


I could be wrong, but won't this introduce a memory leak in IE?



Can you identify a closure? If not, then there is no problem.

Doesn't "L.onmouseover = function() { roll(this); };" create a
closure with 'this'?
 
R

RobG

Ian said:
RobG said:
Matt said:
ASM wrote:

onload = function() {
if(IE) {
function roll(what) {
what.className = what.className==''? 'ie' : '';
}
var L=document.getElementById('menu').getElementsByTagName('LI');
for(var i=0; i<L.length; i++) {
L.onmouseover = function() { roll(this); };
L.onmouseout = function() { roll(this); };
}
}
}

I could be wrong, but won't this introduce a memory leak in IE?



Can you identify a closure? If not, then there is no problem.

Doesn't "L.onmouseover = function() { roll(this); };" create a
closure with 'this'?


No. The value of a function's this keyword is established when it is
called, at which point it is assigned a reference the object that the
function has been called as a method of (in this case, the LI element
the function has been assigned to).
 
R

Richard Cornford

RobG said:
Matt said:
ASM said:
onload = function() {
if(IE) {
function roll(what) {
what.className = what.className==''? 'ie' : '';
}
var L=document.getElementById('menu').getElementsByTagName('LI');
for(var i=0; i<L.length; i++) {
L.onmouseover = function() { roll(this); };
L.onmouseout = function() { roll(this); };
}
}
}


I could be wrong, but won't this introduce a memory
leak in IE?


Can you identify a closure?


The closures are obvious, but not important themselves. It is circular
references that cause the memory leak issue.
If not, then there is no problem.

Here the shortest circle goes - L - refers to a node list that refers to
all the LI elements. Each LI element refers to its onmouover and
onmouseout event handlers and each event handler's [[Scope]] property
refers to the outer function's activation/variable object with the - L -
property referring to the node list.

There is a larger circle resulting from IE's assigning IDed elements as
properties of the global object (so the 'menu' property refers to a DOM
node, its childNodes chains references to its LI descendants and their
event handlers have the global object on their scope chain). The larger
circle will produce the bigger leak, but both are cured by stripping the
event handlers onunload.

Richard.
 
M

Matt Kruse

Richard said:
Here the shortest circle goes ...

That is indeed what I saw, which I thought was obvious. Checking the scope
chain of anonymous inner functions is something I'm trying to do every time
I write code. It's unfortunate that this is even necessary. Is it true that
IE7 will still not fix the DOM said:
There is a larger circle resulting from IE's assigning IDed elements
as properties of the global object (so the 'menu' property refers to
a DOM node, its childNodes chains references to its LI descendants
and their event handlers have the global object on their scope
chain).

Could you explain this one a bit more? Since 'menu' is a quoted string, it
wouldn't refer to the global property with id 'menu' would it? How is the
reference being made?
The larger circle will produce the bigger leak, but both are
cured by stripping the event handlers onunload.

Or by not using inner anonymous functions. Since the inner closures aren't
necessary, couldn't you do:

L.onmouseover = roll;

then outside the function:

function roll() {
this.className = this.className==''? 'ie' : '';
}

correct?
It introduces a global function, but I think that's often cleaner than
trying to cleanup event handlers onunload.
 
R

Richard Cornford

Matt said:
Richard Cornford wrote:
Is it true that IE7 will still not fix the DOM<-->JS circular reference
memory leak problem?

They have been asked to fix it, but probably will not (or, more
realistically, cannot).
Could you explain this one a bit more? Since 'menu' is a quoted string, it
wouldn't refer to the global property with id 'menu' would it?

If IE sees an element with the ID "menu" it is likely to create a
property of the global object with the name 'menu' that holds a
reference to that DOM element.
How is the reference being made?

The global object has a property named 'menu' that refers to the DOM
element with the ID "menu", the LI elements that have the event
handlers are descendants of the element with the ID "menu" and so will
be referred to through its childNodes, firstChild and lastChild
properties (though possibly through a number of intervening elements).
The LI elements refer to the event handling functions and the event
handling functions have the global object on their scope chains (that
is, their scope chains refer to the global object).
The larger circle will produce the bigger leak, but both are
cured by stripping the event handlers onunload.

Or by not using inner anonymous functions. Since the inner closures
aren't necessary, couldn't you do:

L.onmouseover = roll;

then outside the function:

function roll() {
this.className = this.className==''? 'ie' : '';
}

correct?


No, the - roll - function still has the global object on its scope
chain.
It introduces a global function, but I think that's often cleaner than
trying to cleanup event handlers onunload.

Because there is an element with an ID involved it doesn't solve the
problem in this case.

Richard.
 
F

firewood

This is getting way too complicated for me. I do not understand the
implications of the issues raised by the replies to my original post.
Clearly, there is a memory leak problem with IE, but I do not
understand what to do about it. (The discussion has been on a
programming level way off the charts, as far as my skills are
concerned.)

Can I just use the code recommended by ASM?
Should I make some corrections to take care of the IE problem?
Should I just go for a CSS-only solution, like what is shown at :
http://www.cssplay.co.uk/menus/final_drop.html
 
M

Matt Kruse

Richard said:
The global object has a property named 'menu' that refers to the DOM
element with the ID "menu", the LI elements that have the event
handlers are descendants of the element with the ID "menu" and so will
be referred to through its childNodes, firstChild and lastChild
properties (though possibly through a number of intervening elements).
The LI elements refer to the event handling functions and the event
handling functions have the global object on their scope chains (that
is, their scope chains refer to the global object).

Ah, I see. So in theory, attaching event handlers to _any_ object that has
an ID could cause a problem in IE because a function defined anywhere will
have the global object in its scope, and therefore a reference to the object
being attached to, causing the circular reference?
Has this been proven in a simple example? I've not seen any references to
this specific problem in my reading about IE's memory leaks.

I'm still not clear where these references "live" for IE, since previous
discussions hint that there is a hidden layer in IE's scope chain for things
like this. So does that still cause a leak? Time to fire up Drip...
 
A

ASM

Matt Kruse a écrit :
ASM said:
onload = function() {
if(IE) {
function roll(what) {
what.className = what.className==''? 'ie' : '';
}
var L=document.getElementById('menu').getElementsByTagName('LI');
for(var i=0; i<L.length; i++) {
L.onmouseover = function() { roll(this); };
L.onmouseout = function() { roll(this); };
}
}
}


I could be wrong, but won't this introduce a memory leak in IE?


I haven't IE nor Windows ...
From my tests on my IE Mac it seems to work and don't kwnow how to see
what is about memory.
 
R

RobG

ASM said:
Matt Kruse a écrit :
ASM said:
onload = function() {
if(IE) {
function roll(what) {
what.className = what.className==''? 'ie' : '';
}
var L=document.getElementById('menu').getElementsByTagName('LI');
for(var i=0; i<L.length; i++) {
L.onmouseover = function() { roll(this); };
L.onmouseout = function() { roll(this); };
}
}
}


I could be wrong, but won't this introduce a memory leak in IE?


I haven't IE nor Windows ...
From my tests on my IE Mac it seems to work and don't kwnow how to see
what is about memory.


Use:
Applications -> Utilities -> Activity Monitor

It is more-or-less equivalent to Windows Task Manager. You can also
use Terminal and UNIX top (open the Terminal application and enter
'top') - Activity Monitor is simpler, but top is very flexible in what
it displays (type 'man top' to get the manual pages) :)
 
R

RobG

Richard said:
They have been asked to fix it, but probably will not (or, more
realistically, cannot).


If IE sees an element with the ID "menu" it is likely to create a
property of the global object with the name 'menu' that holds a
reference to that DOM element.

Seems I wasn't clear on this either - I thought it was only an issue
where the function being attached to the element had a reference back
to a local variable of the function attaching it. Clearly that is one
possible issue, but not the only one. That may also have been Ian's
thinking with his reference to the this keyword.

How is the reference being made?

The global object has a property named 'menu' that refers to the DOM
element with the ID "menu", the LI elements that have the event
handlers are descendants of the element with the ID "menu" and so will
be referred to through its childNodes, firstChild and lastChild
properties (though possibly through a number of intervening elements).
The LI elements refer to the event handling functions and the event
handling functions have the global object on their scope chains (that
is, their scope chains refer to the global object).
The larger circle will produce the bigger leak, but both are
cured by stripping the event handlers onunload.

Or by not using inner anonymous functions. Since the inner closures
aren't necessary, couldn't you do:

L.onmouseover = roll;

then outside the function:

function roll() {
this.className = this.className==''? 'ie' : '';
}

correct?


No, the - roll - function still has the global object on its scope
chain.
It introduces a global function, but I think that's often cleaner than
trying to cleanup event handlers onunload.

Because there is an element with an ID involved it doesn't solve the
problem in this case.


Even if:

L.onmouseover.onclick = (function(){
return function(){ roll(this); }
})();

is used?
 
A

ASM

RobG a écrit :
ASM said:
Matt Kruse a écrit :
ASM wrote:
onload = function() {
if(IE) {
function roll(what) {
what.className = what.className==''? 'ie' : '';
}
var L=document.getElementById('menu').getElementsByTagName('LI');
for(var i=0; i<L.length; i++) {
L.onmouseover = function() { roll(this); };
L.onmouseout = function() { roll(this); };
}
}
}
I could be wrong, but won't this introduce a memory leak in IE?

I haven't IE nor Windows ...
From my tests on my IE Mac it seems to work and don't kwnow how to see
what is about memory.


Use:
Applications -> Utilities -> Activity Monitor


Not very busy(*) with examples using this kind of code and much more :
http://perso.orange.fr/stephane.moriaux/truc/couleurs_palette_gris.shtml
( when radio-button clicked colors palettes are displayed via JS-DOM
with their created elements including onclick as above)
http://perso.orange.fr/stephane.moriaux/truc/boulier
(false same images displayed and moved by JS-css function, onclick given
to elements same way above and much more : 'this' calling parentNode's
childs collection on each click)

(*) very busy :
sometimes complete assigned memory to IE is used
(on overring elements) but in general not much more than
5 to 15% of CPU that fall down rapidly.
Activity (memory and CPU) to zero when in background
Memory use doesn't seem same in Windows than in Mac OS, I don't notice
leak (if I understood what it is).
It is more-or-less equivalent to Windows Task Manager. You can also
use Terminal and UNIX top (open the Terminal application and enter
'top') - Activity Monitor is simpler, but top is very flexible in what
it displays (type 'man top' to get the manual pages) :)

top's manual page ! at least 20 pages !
top confirms what I saw in Activity Monitor
 
E

Eric B. Bednarz

ASM said:
firewood a écrit :

For a website, in HTML.
No doubt : CSS

While it may be fashionable to do stuff 'with only CSS', it is not
undoubtedly better. You could, for example, resort to surgery 'with only
a knife', but you probably wouldn't if you had a better choice.
ul#menu li ul { display: none }
ul#menu li:hover ul, ul#menu li.ie ul { display: block }

If you must have -- e.g. a client categorically wants -- a dropdown menu
(or whatcha callit) and only know CSS, a better choice is available in
the form of outsourcing.

The above is a usability and accessibility nightmare. For starters,
there's no keyboard access, you'd want at least add the :focus
pseudo-class. Secondly, javascript might not be available in IE, the
class 'ie' is never assigned and the rest of the menu never shows
up. Thirdly, it doesn't cater for any other UAs than IE that support
only CSS 1 or implement the CSS 2 axiom "CSS doesn't define which
elements may be in the above states [:hover, :active, and :focus]" to
the letter. Last but not least, for a halfway decent dynamic menu you'd
need timeouts, for showing as well as for hiding it; I have fine-motoric
capabilities above par but cannot properly use 'CSS dropdowns' with the
touchpad of my laptop, they are bloody annoying (the CSS 'menus', but
arguably also the touchpads).
 
A

ASM

Eric B. Bednarz a écrit :
If you must have -- e.g. a client categorically wants -- a dropdown menu
(or whatcha callit) and only know CSS, a better choice is available in
the form of outsourcing.

As we talk about JS ...
The above is a usability and accessibility nightmare. For starters,
there's no keyboard access,

Sure it's a minimalist example.
you'd want at least add the :focus
pseudo-class. Secondly, javascript might not be available in IE,

No importance (as you know it)
The menu *absolutely must* reach a page with *sub-menus* repeated in clear.
for a halfway decent dynamic menu you'd
need timeouts, for showing as well as for hiding it;

And this poor IE without JS ? :)

Of course, you can add a lot of interesting functions.
(minimal example)
 
R

Richard Cornford

Matt said:
Ah, I see. So in theory, attaching event handlers to
_any_ object that has an ID could cause a problem in
IE because a function defined anywhere will have the
global object in its scope, and therefore a reference
to the object being attached to, causing the circular
reference?

The theory that says 'all circular references including DOM nodes causes
the memory leak issue' certainly would carry that implication (and worse
as the global object has a - document - property that refers to the
document, and the child relationships in the DOM link every element form
the document, so any event handler with the global object on its scope
chain would form a circular reference regardless of its having an IE).
Has this been proven in a simple example?

Now that is a very good question, and I don't think I have ever seen
this aspect of the issue demonstrated. It turns out that there is a very
good reason for that.
I've not seen any references to this specific problem
in my reading about IE's memory leaks.

I have seen it mentioned on quirksmode at least once.
I'm still not clear where these references "live" for
IE, since previous discussions hint that there is a
hidden layer in IE's scope chain for things like this.

No evidence has been presented do support that suggestion. The symptoms
observed are amenable to simpler explanation (and without a
discriminating test Ockam's razor favours the simpler explanation).
So does that still cause a leak? Time to
fire up Drip...

Drip may be better than nothing (what it does identify is significant),
but it does not identify all leak scenarios.

Now, to the actual testing of the issues related to IDed elements, and
circular references through the global object into the document; there
is not real issue. The theory that all circular references between DOM
nodes and javascript objects cause memory leaks in IE is insufficient as
an explanation of what happens.

Because the DOM is a tree Nodes will refer to their childbed, and their
children to their parents. As a result there must be circular references
all over the palace when event handlers are assigned to Elements by
scripts.

I resurrected my original leak testing scripts; the ones that oscillate
between a 'leaky' page and a normal page. Setting those pages up such
that event handlers were assigned to IDed elements showed no leaking
resulting form the references to the global object on the event
handler's scope chain.

Thinking about this I was forced to conclude that the circular
references must have been being broken by the browser itself. I
suspected that the references being removed would be the parent/child
references in the DOM, as IE could do that automatically as it unloads a
page.

To test that specific idea I set up a closure based leak scenario as
follows:-

window.onload = function(){
var el = anId; // where a Element has the ID "anId"
el.onclick = function(){
return true;
}
el.onclick.balast = getBigString(); // getBigString returns an 8
// Megabyte string which makes
// any leak very obvious.
};

This code steady leaks memory as the test script oscillates between this
page and the other page, because of the Node-> Node.onclick ->
Node.onclick.[[Scope]] -> Node circular reference.

Nulling the - el - variable:-

window.onload = function(){
var el = anId; // where a Element has the ID "anId"
el.onclick = function(){
return true;
}
el.onclick.balast = getBigString(); // getBigString returns an 8
// Megabyte string which makes
// any leak very obvious.
el = null;
};

- breaks the circle and prevents the leak.

Now, suppose instead of nulling - el - a reference to el.parentNode was
assigned:-

window.onload = function(){
var el = anId; // where a Element has the ID "anId"
el.onclick = function(){
return true;
}
el.onclick.balast = getBigString(); // getBigString returns an 8
// Megabyte string which makes
// any leak very obvious.
el = el.parentNode;
};

In theory we have a circular reference Node-> Node.onclick ->
Node.onclick.[[Scope]] -> Node.parentNode -> Node, but this arrangement
does not leak. The only differnce between this and the first leaking
example is that the reference to the DOM node on the event handlers
scope chain (via the - el - local variable) has been replaced by a
reference to its parentNode. There is a circular reference but no leak
happens, and the only point at which the circle could be broken is the
reference between the parenNode and its child.

A similar experiment putting a child of the Node on the event handler's
scope chain had a similar result, with no leak being observed.

My conclusion has to be that IE explicitly breaks all inter DOM Node
references as it unloads pages. Thus circular references that include
these references are _not_ significant to the memory leak issue.

The IE leak theory should be clarified along the lines of 'Circular
references between DOM nodes and JS objects cause memory leaks so long
as no reference in that circle is a parent/child reference from the
DOM'.

As far as your original question goes, I neglected to test whether
NodeLIst (and/or NamedNodeMap) objects are treated like DOM nodes. I may
get round to that in the next few days.

Richard.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,778
Messages
2,569,605
Members
45,237
Latest member
AvivMNS

Latest Threads

Top