views:

1135

answers:

6

I've tried my best to be a purist with my usage of Javascript/Ajax techniques, ensuring that all Ajax-y behavior is an enhancement of base functionality, while the site is also fully functional when Javascript is disabled. However, this causes some problems.

In some cases, a DOM node should only be visible when Javascript is enabled in the browser. In other cases, it should only be visible when disabled. Take for instance a submit button on a form that has a drop down with an onchange handler that auto-submits (using JQuery's form plugin):

<form method="post" action=".">
    <label for="id_state">State:</label>
    <select name="state" id="id_state" onchange="$(this.form).ajaxSubmit(ajax_submit_handler);">
        <option value="AL">Alabama</option>
        <option value="AK">Alaska</option>
    </select>
    <input class="with_js_disabled" type="submit" value="OK" />
</form>

and the Javascript:

<script type="text/javascript">
    $(document).ready(function()
    {
        $(".with_js_disabled").hide();
    });
</script>

When Javascript is enabled, the submit button is not required (due to the onchange handler). However, JQuery's $(document).ready function (and the more direct document.onload) is only called after the page has been fully loaded and rendered - hence, the submit button is initially displayed and a "flash" occurs when the Javascript is executed and the node's display is set to "none".

I've accepted this as the cost of doing business, and haven't found a way around it. But is there a technique I'm not aware of that will minimize the effect, or even outright eliminate it?

EDIT:

The <noscript> solution mentioned by many people below seems promising, but isn't working for me on Safari. However Prestaul's 2nd suggestion works beautifully:

<body>
    <script type="text/javascript">
        document.body.className += ' has_js';
    </script>
    <!-- the rest of your page -->
</body>

This can then be styled using straight CSS:

body .js_enabled_only { display: none; }
body .js_disabled_only { display: block; }
body.has_js .js_enabled_only { display: block; }
body.has_js .js_disabled_only { display: none; }

This second line is just for reference and can (and should) be removed to avoid circumstances where your element shouldn't be display:block. Likewise, you may need different variations on the third line for other display styles. But this solution is nice and clean, IMO, and in my tests entirely eliminates the flicker effect.

+1  A: 

I can think of two methods:

1 - Pre-detect if Javascript is enabled and save that information in a cookie/session. This could be done at the front page, and should eliminate most flickers.

2 - Use the livequery plugin, which detects elements as they are added to the DOM. You can run it right when Javascript finishes loading, which should be much before the document (it its in the head).

$('.with_js_disabled').livequery(function() {
     $(this).hide();
});
Eran Galperin
#1 is an option I'd prefer to avoid, but thanks for bringing it up. I'm not familiar with livequery - it seems like it's purpose is to trigger events off dom nodes as they are added to the document via javascript. how is it that it is able to hook in prior to document.onload?
Daniel
It can be used for more than events, and it detects elements already existing in the DOM as they are loaded, not just elements added via javascript.
Eran Galperin
You don't need the livequery plugin if you are using the latest release of jQuery (v1.3). It is now part of the events core: $('.className').live('click', function() {});
Prestaul
Those are not the same - the live function added in 1.3 can only be used to bind events.
Eran Galperin
@Eran: That's right, my mistake. With your livequery method do you see the element appear momentarily before the .hide() fires?
Prestaul
it would fire before $(document).ready, that's for sure. If it prevents the flicker completely - that depends on the power of the running computer and type of browser. On my computer with FF3 it does perfectly
Eran Galperin
+1  A: 

Just a thought: I'm not sure if this will work, but you could try putting some Javascript in the document head to write a CSS style element that would hide the button.

David Zaslavsky
The location of the Javascript doesn't seem to make much of a difference.
Daniel
the trick here is that you can't set an inline style on an element which doesn't exist in the dom yet. You have to create a new "style" tagged element with an appropriate css selector and rule. Css can apply to an element before it exists and be applied instantly after it exists.
Breton
+2  A: 

Just like David said, you can add a Javascript, that adds a style sheet to hide all "unnecessary" html-elements:

<head>
  ...
  <script type="text/javascript" language="javascript">
    document.write('<style type="text/css"> .disabled-if-javascript { display: none; } </style>');
  </script>
</head>
...

If Javascript is enabled, it sets all elements of class "disabled-if-javascript" to hidden, before the body is even loaded. Just add this class to all elements that need to be hidden, if javascript is enabled. You might also need a class enabled-if-javascript that does the opposite, to show certain elements that would be hidden for non-javascript. Maybe you need to add "!important" to your style definition there, to override the existing (non-javascript) rules.

sth
if this works, sounds like a simple solution. thanks
Daniel
Nice solution, the "language" attribute of the script tag is deprecated though.
I.devries
+5  A: 

For situations identical to yours, I usually put the submit button around a <noscript> tag. No one has suggested it, so I am not sure if it is considered bad practice around these parts, but it is what I use and it works for me.

If you only want a node visible when Javascript is enabled, you could do in the head:

<noscript>
    <style type="text/css">
    .js-enabled { display: none; }
    </style>
</noscript>

And then give any Javascript-only elements a class of js-enabled.

Paolo Bergantino
Not Ajaxy enough. :)
Jason Kester
I feel like an idiot for not being aware of this tag... but it doesn't solve the reverse problem (node only visible when javascript enabled). +1 for the informative aspect, though :)
Daniel
I don't think this HTML will validate, noscript is meant for alternative content, not for behavior or styling.
I.devries
+8  A: 

How about combining some of these solutions:

<style type="text/javascript">
    .only-without-script {
        display: none;
    }
</style>
<noscript>
    <style type="text/javascript">
        .only-with-script {
            display: none;
        }
        .only-without-script {
            display: block;
        }
    </style>
</noscript>

or I prefer adding a class to the body (place your <script> tag at the top of the body and don't use the .ready event):

<head>
    <style type="text/javascript">
        body.has-script .something-not-ajaxy {
            display: none;
        }

        input.ajaxy-submit {
            display: none;
        }
        body.has-script input.ajaxy-submit {
            display: inline;
        }
    </style>
</head>
<body>
    <script type="text/javascript">
        document.body.className += ' has-script';
    </script>
    <!-- the rest of your page -->
</body>
Prestaul
Your second solution works for me. noscript isn't working for me, on safari
Daniel
@Daniel: The second solution is, I think, the best solution here. It is simple and gives you full control of the pages styles with and without javascript enabled.
Prestaul
I guess I managed to post a somewhat duplicate here: http://stackoverflow.com/questions/1466805/safe-way-to-make-behavioral-css-trigger-based-on-javascript-availability
Robin Smidsrød
A: 

A simple thing to try: add <script>jQuery.ready();</script> right before closing the body tag.

This will force all binds to the 'ready' event to fire. And if you make them fire in the end of the document, the DOM is loaded and ready to go, even if the "native" onDomContentLoaded event is not yet fired.

This usually works for me when I want to remove flickering, especially in IE/win, since IE has it's own domready emulator in jQuery's 'ready' event.

David