Forum Moderators: open

Message Too Old, No Replies

instantaneous javascript enrichment

how does wikipedia do it?

         

Skier88

4:34 am on Dec 22, 2010 (gmt 0)

10+ Year Member



I know the topic of non-js fallbacks has been discussed too many times, but nothing I've read explains the performance of several websites, most notably wikipedia. You'll notice that loading a wikipedia article with no data stored client or server side will load a collapsible navigation menu if you have javascript enabled, and a static menu if you do not.

I can think of more than a few ways to do this, but none that have all the benefits of whatever wikipedia is using. Namely: it gets it right on the first page load, never gets it wrong, does not collapse the menu after loading it (if js is enabled, it loads collapsed), and doesn't use noscript tags.

How does wikipedia avoid every pitfall of common js detection methods? My current interest is actually for the same functionality - degrading collapsible menus - so a case specific explanation would be as good as a general solution for me. Thanks for reading.

Fotiman

2:43 pm on Dec 22, 2010 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Wikipedia is not really a "great" example (looking at their markup is not fun at all). However, the concept is not really to provide a non-js "fallback", but rather to use "progressive enhancement" to improve upon an already functional page. In the case of the Wikipedia page, they have sections of code that look like this:

<h5>COLLAPSIBLE TITLE</h5>
<div class="body">
<ul>
<li><a href="...">MENU ITEM 1</a></li>
<li><a href="...">MENU ITEM 2</a></li>
<li><a href="...">MENU ITEM 3</a></li>
</ul>
</div>

Therefore, they've provided a static menu as the default content, then they use JavaScript to find those menu items and "enhance" them by making them collapsible.

If you're trying to do something similar, you might have a look at the YUI 2 Menu [developer.yahoo.com], which has a section for creating menus from semantic markup on the page.

Skier88

6:06 pm on Dec 22, 2010 (gmt 0)

10+ Year Member



Thanks, but then how do they collapse the menus before the page loads? As I mentioned, if you have js enabled there is no point at which expanded menus are displayed. So if it does work as you say (which would make a lot of sense), they are doing it before the window.onload event, which is when I thought all scripts should be executed.

If a script were placed earlier in the body, would it start running before the rest of the content is loaded and after the preceding content is loaded? Or, if it's in the head section, before the body starts loading? If so, I'm thinking maybe one could modify class styles with js before the content loads?

Fotiman

7:34 pm on Dec 22, 2010 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



They probably do it just before the window.onload event or when that content becomes available. See the YUI2 onavailable [developer.yahoo.com] and ondomready [developer.yahoo.com] events for example.

Basically, an element simply needs to exist within the DOM for you to manipulate it with JavaScript, so you could do this:


<body>
<div id="foo">...</div>
<script type="text/javascript">
// DOM now contains a foo element
var foo = document.getElementById('foo');
foo.style.display = 'none';
</script>
... More content ...
</body>

And it would work fine. However, that same example would not work if you put the script in the head.

The onload event fires after EVERYTHING (including images, etc.) has loaded, so it may be more beneficial to perform certain actions as soon as an element becomes available (or as soon as the DOM has completed loading). The YUI methods mentioned above make that easy. You'll notice, for example, that some of the YUI examples look like this:

YAHOO.util.Event.onContentReady("basicmenu", function () {
var oMenu = new YAHOO.widget.Menu("basicmenu");
oMenu.render();
oMenu.show();
});

So as soon as the "basicmenu" element is ready, it will enhance it with a YAHOO.widget.Menu.

Skier88

8:35 pm on Dec 22, 2010 (gmt 0)

10+ Year Member



OK, thanks for the example, that answers the meat of the issue. I do have one further question about processing order though - let's say you place a script in the head section after a link to an external stylesheet, set to run immediatly. Will that script be executed before the body loads? And will the script be able to access styles introduced by the stylesheet preceding it in the head section?

onContentReady sounds like an extremely useful function, but I don't think I should adopt a framework just for that. Do you have any idea how this function works? I'm looking through the YUI source now, but if you know off the top of your head that could save some time.

Skier88

9:13 pm on Dec 22, 2010 (gmt 0)

10+ Year Member



I was playing around with this, and realized that I don't actually have to alter style sheets. document.write is more literal than I thought, and it works in the head section too. So I have a working solution now: put this code at the end of the head section.
<script type='text/javascript'>
document.write("<style type='text/css'>"+
".collapsible {height:0;}"+
"</style>");
</script>

I'm still looking into onContentReady though, since that seems like a generally very useful function.

Fotiman

2:59 pm on Dec 23, 2010 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month




let's say you place a script in the head section after a link to an external stylesheet, set to run immediatly. Will that script be executed before the body loads?

The browser actually blocks additional downloads while it processes scripts. It will only download and execute 1 script at a time, which is why it's a good idea to avoid putting scripts in the head of your document. The best place to put scripts is just before the closing </body> tag, because your browser can download other resources (images, etc.) in parallel, so you'll have what appears as a more responsive UI.

In the example you wrote, that script is simply writing out static CSS. You would not want to do that unless you are writing out some dynamic value because rather than use a script to document.write it into the document, you could simply have added:

<style type='text/css'>
.collapsible {height:0;}
</style>

Now, if your goal is to use JavaScript to write this so that it's NOT collapsed for users that have JavaScript disabled, an alternative would be to give your <body> a class of "no-js" and then use JavaScript to remove the no-js class from the body. They in your CSS you could have:

.collapsible { height: 0; }
.no-js .collapsible { height: auto; }


That allows you to keep a nice separation of content/presentation/behavior.

rocknbil

4:58 pm on Dec 23, 2010 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Another approach - don't have the code handy - is to modify the stylesheets before the document loads. Let's say you have

#some-block { display:none; }

... intended to open with Javascript. But if JS is disabled, that's won't work, so you set it to

#some-block { display:block; }

(or if a div, display is not required. . . . .)

Normally using window.onload or $(document.ready) you'd get a flash of it open before JS takes over. To avoid that, you dynamically add a stylesheet rule. The first downside (if you consider it one) is that the JS must come immediately after the stylesheet in the head:

<link rel="stylesheet" type="text/css" href="yourstyle.css">
<script type="text/javascript" src="your-script-modifier.js"></script>

in which you do something like


if (document.styleSheets[0]) {
// IE only - this is downside two
if (document.styleSheets[0].addRule()) {
document.styleSheets[0].addRule("#some-block", "display:none");
}
// Everyone else, as usual
else {
document.styleSheets[0].insertRule("#some-block{display:none;}", 0);
}
}


The previous is probably incorrect code - as I said, don't have working code handy, but that's the concept. It does work.

The "0" is important, it means insert at the START of the style sheet, which won't work if there is some defined style for that selector. You'd need to read the length of the style sheet, add one to the index, and insert it at the end, like


document.styleSheets[0].insertRule("#some-block{display:none;}", 1234567);

Fotiman

5:17 pm on Dec 23, 2010 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



My thoughts on rocknbil's solution:
Personally, I don't like that approach because you've not intermingled presentation logic with behavioral logic. I prefer the approach of setting a simple flag which indicates that yes, JavaScript is enabled or no, it's not. Then you can keep all of the presentation code in the .css file, and all of the behavior code in the .js code, and all the JavaScript needs to worry about is setting a className value.

That is just my opinion. Nothing wrong with talking about multiple options. :)

Skier88

6:13 pm on Dec 23, 2010 (gmt 0)

10+ Year Member



OK, thanks for the replies. rocknbil, what advantage does your solution offer over the one I posted? I was originally looking into inserting a rule into the stylesheet (at this point I only use one), but that is certainly more complicated than just adding css, and it doesn't look like it would avoid the issues Fotiman pointed out with mine.

Fotiman, you're right, I certainly see the advantages in only removing a class and in placing code at the end of the body. But I can't do the latter without having the menu flash open before it closes (although the rest of my javascript is already there). As for removing the class, that would mean the script has to be somewhere in the body tag and before the menu, which would mean it still delays loading slightly. So I'll try it, but the only advantage seems to be content separation, at the expense of a logical code location.

dexus

8:13 pm on Dec 23, 2010 (gmt 0)

10+ Year Member



I would look into JQuery as a Javascript library.

Its fast and easy to use.

Using the following HTML markup:

<div id="slideMenu">
<ul>
<li>Menu item #1</li>
<li>Menu item #2</li>
<li>Menu item #3
<ul>
<li>Submenu item #1</li>
</ul>
</li>
</ul>
</div>


You could use JavaScript/JQuery to enhance it.


// Locate the UL menu on my page by passing the ID, and also locate the child UL item

var slidemenu = {

animateduration: {over: 200, out: 200}, //duration of slide in/ out animation, in milliseconds

buildmenu: function(menuid) {

$(document).ready(function() {

var $mainmenu = $("#" + menuid + " > ul");
var $headers = $mainmenu.find("ul").parent();

$headers.each(function(i) {

var $curobj = $(this);

this._dimensions = {
w: $(this).outerWidth(),
h: $(this).outerHeight()
};

$curobj.hover(function(e){

var $targetul = $(this).children("ul:eq(0)");

this._offsets = {
left: $(this).position().left,
top: $(this).position().top
};

var x = this._offsets.left + this._dimensions.w;
var y = this._offsets.top;

if(1 > $targetul.queue().length) //if 1 or less queued animations
$targetul.css({
top: y,
left: ($(this).parent().position().left+$(this).parent().outerWidth()) + "px",
width: this._dimensions.w + "px"
}).show("fast");
},
function(e){
var $targetul = $(this).children("ul:eq(0)");

$targetul.hide("fast");
}
);

//end hover
$curobj.click(function(){
$(this).children("ul:eq(0)").hide()
})

});

$mainmenu.find("ul").css({display:'none', visibility:'visible'});

});

}
}

slidemenu.buildmenu('slideMenu');



The above code made it easy for me to create a dynamic menu on my Resources button over at www.adult-care.org.

Keep in mind, you still have to style it to make it look pretty.

Hopefully this opens your mind on how to approach your future designs.

Fotiman

5:40 am on Dec 25, 2010 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



As for removing the class, that would mean the script has to be somewhere in the body tag and before the menu, which would mean it still delays loading slightly.

Actually, you could apply/remove the classes to/from the <html> element instead of the <body>. That way, you could add the code to modify the class right in your head, leaving the rest of your JavaScript at the bottom. Modernizr [modernizr.com] does this same thing.

Skier88

9:32 pm on Dec 25, 2010 (gmt 0)

10+ Year Member



Oh, I didn't know that was possible/valid. So that sounds like the best solution then - changing the class alone should hardly have an effect on load time. Is there a more direct way to access the html element than document.head.parentNode?

Modernizr looks like something I could really use, but the site I'm working on doesn't really warrant it; I'll keep it in mind though. And dexus, thank you very much for your code, but I think this site doesn't warrant jQuery either. Although I will certainly consider it for any especially js-rich projects in the future.