CATEGORY: Javascript

Some time ago I wrote an article about CSS PNG Fix for IE by Rogie King, from Komodo Media, which is a great fix. But whether you realized it or not, there are some issues with the script if you monitor it closely, and some observant people have also notified me about it.

The problem is it will make repeated background ‘none’ call to your server, which probably shown as page not found or server error message in your access log, though the transparency fix stilll works. And here are some possible scenarios that may trigger that problem:

  • <img> tag linking to other image types other than PNG, e.g. JPEG or GIF images. This can be quite problematic if you use css selector to automatically fix all * html img tags found, you may get a lot of redundant background ‘none’ calls to your server if there are many tags which link to GIF or JPEG images.
  • If you accidentally put 'png' class name from some your elements without stylesheet background image.

So I took closer look at the code, re-formatted so I knew what was going on. There two types of fixes, one for <img> tag, while the other for background images. And it seems background image fix are still called for some of the scenarios above, and calling this.runtimeStyle.backgroundImage="none" was triggering the “none” request to the server. And two fixes that I did:

  • If it is <img> tag and it does not link to a PNG image file, do not perform background image fix, instead just ignore it.
  • If it is a background image fix, and the background image is not a PNG file, ignore it as well.

Here are the updated codes:

* html img,
* html .png {
  azimuth: expression(
    this.pngSet?
      this.pngSet=true : 
        (this.nodeName == "IMG" ? 
          (this.src.toLowerCase().indexOf('.png')>-1 ? 
            (this.runtimeStyle.backgroundImage = "none", this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "', sizingMethod='image')",
                this.src = "/images/blank.gif") :
            '') :          
          (this.currentStyle.backgroundImage.toLowerCase().indexOf('.png')>-1) ?
            (this.origBg = (this.origBg) ? 
              this.origBg :             
              this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''),
              this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "', sizingMethod='crop')",
              this.runtimeStyle.backgroundImage = "none") :
            ''
        ), this.pngSet=true
  );
}

RolloverImage rollover is the classic effect that we all have been playing for ages. And we often use it to rollover image tabs or menus.

Although I personally prefer to use text for navigation, but at times, when wearing the designer’s hat, I just can’t resist the temptation to make the design looks perfect through images.

Image rollover started with the traditional lengthy Javascript codes that preload the rollover images, and and setting the onmouseover and onmouseout events for each of the link involved.

<script text="type/javascript">
  // some javascript codes to preload images, 
  // rollover and rollback images
</script>

<a href="#" onmouseover="rolloverImage('about_us')" 
    onmouseout="rollbackImage('about_us')">
  <img src="images/about_us.gif" width="100" height="25" 
    alt="About Us" border="0" />
</a>

Many hated to write the Javascript rollover codes, because the codes are lengthy and ugly. So, people found a new way to create the rollover effect through Cascading Style Sheet (CSS) or Flash. I’m not going to talk about flash over here. So, no Javascript involved, great!

But, writing the CSS rollover ain’t a simple task too. Instead of writing Javascript, now you have to write the CSS styles, and the normal state and the hover state.

a.about_us {
  display: block;
  width: 100px;
  height: 50px; 
  background: url(images/about_us.gif) 0 0 no-repeat;
}
a.about_us:hover, a.about_us:active {
  background: url(images/about_us_over.gif) 0 0 no-repeat;
}

If you have 10 links, rest assured, you will need to write a pretty long CSS. And if you need display a different image for the currently selected link.

There is also another disadvantage of using CSS Image rollover, the famous flicker effect on IE 6. So on some occasions, you may want to just use a simple unobtrusive image rollover javascript.

With Rollover, you just have to bother about creating your images, the normal state and the hover state, write your HTML codes like usual without the mouseover or mouseout event, and unobtrusively add the rollover effect through a simple javascript call. Rollover also automatically preloads the rollover images on document load.

<ul id="nav">
  <li><a href="images/about_us.gif" width="100" height="50"
       alt="About Us"></a></li>
  ....
</ul>

<script type="text/javascript">
  window.onload = function() {
    new Rollover('nav');
  }
</script>

// or add you can add it to body onload event
<body onload="new Rollover('nav');">

No CSS involved, and with just a simple javascript call, you are done.

Demo and Download

I used to hate Javascript, but thanks to Sam Stephenson for the Prototype Javascript library, coding Javascript is no longer the same with what it used to be.

One of key features provided by Prototype library is the extension of HTML Elements, Form and Form Elements. Form refers to the form object, while Form Elements refer to the input fields, text area and selection boxes. The extension includes really useful helper methods, such as:

  • for Elements, e.g. addClassName(), descendentOf(), toggle()
  • for Form, e.g. disable(), serialize(), and reset(),
  • for Form Elements, e.g. activate(), clear(), getValue()

Please refer to Prototype API reference documentation for the description of each function, and there are a lot more helper methods available.

To access the helper methods, you need to retrieve the element using Prototype utility method $('[element-name]'):

$('[element-name]').toggle();
$('[form-name]').reset();
$('[form-element-name]').getValue();

While most of the helper methods extension provided by Prototype should satisfy your needs, but at times when you may want to extend those methods. So how do you go about doing that?

A simple example, the Element extension provides the toggle() method that hides and shows an element, but if at times, you may want to toggle an element and at the same time toggle another element. For example, clicking on ‘Add new category’ will hide the link and show the add category form, and vice versa, clicking on ‘Cancel’ will hide the form and show ‘Add new category’ link.

Add Category

So you thought of introducing toggleWith() method, instead of calling two statements seperately:

$('[link-id]').toggle();
$('[form-id]').toggle();

And this is how you can extend the Element methods:

// Extend Element.Methods with new functions 
// For form and form elements, use Form.Methods and 
// Form.Element.Methods
Object.extend(Element.Methods, {
    // the first argument refers to the called element
    toggleWith: function(element, otherElement) {
        element.toggle();
        $(otherElement).toggle();
    }

    [, ... other functions if there's any ]
);

// Call this to reflect the new functions
Element.addMethods();

// Now you can call the new function
$('[link-id]').toggleWith('[form-id]');

If you use Scriptaculous library and you may want to extend your toggle method with the blind down effect:

Object.extend(Element.Methods, {
    toggleWith: function(element, otherElement) {
        element.toggle();
        new Effect.BlindDown(otherElement, {duration: 0.2});
    }
);

If you are learning Prototype, I would suggest that you use Firefox and install DevBoi and DevBoi: Prototype JS Reference; with those in place, you can easily call up Prototype API reference on Firefox sidebar by simply pressing Ctrl-F9.

For form elements like a check box you may want to introduce the toggleAll() method to toggle all other checkboxes with the same class name, and toggleMaster() to uncheck the master checkbox if any of the children boxes is unchecked, they will come in handy.

As for other extensions, no problem for now, you already know how to do it.

Initiating AJAX requests can be addicitive; you start with independent requests, and soon you need to initiate chained requests. Executing chained requests is easy, but how do you pass common variables across these requests.

I have meddled with three ways, the first two depends on the response type, and the third is my favourite. Sample codes shown using Prototype javascript library, but are applicable to other libraries too:

1. Through JSON object response

This way is only applicable if your response type is a JSON object. The common variable is passed through URL parameter from the first request, then embedded in JSON object response to be used by the next request. Example:

Client script

// first request
new Ajax.Request('/url/path', {parameters: 'common_var=hello', oncomplete: handleComplete})

// handle first request response
function handleComplete(xmlHttpReq, json) {
   common_var = json.common_var

  // initiate second request using common_var
  new Ajax.Request('/url/path', {parameters: 'common_var=' + common_var, .... })
}

Server script

# server side JSON object response
{common_var: '<%= params[:common_var] %>', ... <other properties>}

2. Through XML response

Similar to the previous method, this way is applicable if your response type is XHML instead of JSON object. The common variable is embedded in XML response. The element will then referenced by second request using Javascript. Example:

Client script

// first request
new Ajax.Request('/url/path', {parameters: 'common_var=hello', oncomplete: handleComplete})

// handle first request response
function handleComplete(xmlHttpReq, json) {
   resDoc = xmlHttpReq.responseXML
   common_var = resDoc.getElementsByTagName("common_var")[0].firstChild.data

  // initiate second request using common_var
  new Ajax.Request('/url/path', {parameters: 'common_var=' + common_var, .... })
}

Server script

# server side XML response
<response>
   ... other elements
   <common_var><%= params[:common_var] %></common_var>
</response>

3. Through Prototype AJAX.Request options

This is my preferred way, because it does not involve a round-trip to the server. Initiate Ajax.Request with by adding common_var as one of the request options, and register a oncomplete global event handler through AJAX.Responders.

Note you can only do this through global event handler, because the first argument passed to the event handler is the AJAX.Request object. With it, you can easily retrieve the common_var request options. While inline event handler shown in previous example passes XMLHttpRequest object instead of Ajax.Request object. Example:

Client script

// first request
new Ajax.Request('/url', {parameters: 'id=<%= id %>', id: <%= id %>})

// register global event handler
Ajax.Responders.register({onComplete: handleComplete})

// handle first request response
function handleComplete(ajaxReq) {
   common_var = ajaxReq.options.common_var

  // initiate second request using common_var
  new Ajax.Request('/url/path', {parameters: 'common_var=' + common_var, .... })
}