Rough Book

random musings of just another computer nerd

Tag: web design

Creating a custom SELECT tag with OPTGROUP in Grails

I’ve recently started working with Groovy on Grails at work. Groovy is a dynamic language that runs on the JVM (Java Virtual Machine), and Grails is a high-productivity open-source web-application framework that leverages the Groovy language. Grails follows the “convention over configuration” principle, taking away a lot of the grunt work that goes into creating web applications. It also uses proven technology like Hibernate and Spring with a consistent interface. You can have a Grails app up and ready to go with only a few lines of code. This high productivity is also due to the fact that Grails uses Groovy, which is a dynamic language and is much more expressive than Java. Finally, because Groovy runs on the JVM, it has full access to Java’s rich API.

Groovy comes with a number of built-in tags which allow you to quickly create forms, form controls, or any number of other HTML constructs or elements. However, I noticed that Groovy didn’t provide a built-in tag to build a SELECT that includes OPTGROUPs. OPTGROUPs allow you to group options within a drop-down select-box. But this problem can be solved because Groovy supports the creation of custom tags. After reading a bunch of documentation and looking at a few examples, I was able to create my own SELECT tag that supports OPTGROUPs. Here is an example of the usage of the tag:

<g:selectWithOptGroup name = "song.id" 
                      from = "${Song.list()}" 
                      optionKey = "id" 
                      optionValue = "songName" 
                      groupBy = "album" />

And the code to implement this tag:

import org.springframework.beans.SimpleTypeConverter
import org.springframework.web.servlet.support.RequestContextUtils as RCU
import org.codehaus.groovy.grails.commons.DomainClassArtefactHandler

class MyAppTagLib {

    def selectWithOptGroup = {attrs ->
        def messageSource = grailsAttributes.getApplicationContext().getBean("messageSource")
        def locale = RCU.getLocale(request)
        def writer = out
        def from = attrs.remove('from')
        def keys = attrs.remove('keys')
        def optionKey = attrs.remove('optionKey')
        def optionValue = attrs.remove('optionValue')
        def groupBy = attrs.remove('groupBy')
        def value = attrs.remove('value')
        def valueMessagePrefix = attrs.remove('valueMessagePrefix')
        def noSelection = attrs.remove('noSelection')
        def disabled = attrs.remove('disabled')
        Set optGroupSet = new TreeSet();
        attrs.id = attrs.id ? attrs.id : attrs.name

        if (value instanceof Collection && attrs.multiple == null) {
            attrs.multiple = 'multiple'
        }

        if (noSelection != null) {
            noSelection = noSelection.entrySet().iterator().next()
        }

        if (disabled && Boolean.valueOf(disabled)) {
            attrs.disabled = 'disabled'
        }

        // figure out the groups 
        from.each {
            optGroupSet.add(it.properties[groupBy])
        }

        writer << "<select name=\"${attrs.remove('name')}\" "
        // process remaining attributes
        outputAttributes(attrs)
        writer << '>'
        writer.println()

        if (noSelection) {
            renderNoSelectionOption(noSelection.key, noSelection.value, value)
            writer.println()
        }

        // create options from list
        if (from) {
            //iterate through group set
            for(optGroup in optGroupSet) {
                writer << " <optgroup label=\"${optGroup.encodeAsHTML()}\">"
                writer.println()

                from.eachWithIndex {el, i ->
                    if(el.properties[groupBy].equals(optGroup)) {

                        def keyValue = null
                        writer << '<option '

                        if (keys) {
                            keyValue = keys&#91;i&#93;
                            writeValueAndCheckIfSelected(keyValue, value, writer)
                        }

                        else if (optionKey) {
                            if (optionKey instanceof Closure) {
                                keyValue = optionKey(el)
                            }

                            else if (el != null && optionKey == 'id' && grailsApplication.getArtefact(DomainClassArtefactHandler.TYPE, el.getClass().name)) {
                                keyValue = el.ident()
                            }

                            else {
                                keyValue = el&#91;optionKey&#93;
                            }

                            writeValueAndCheckIfSelected(keyValue, value, writer)
                        }

                        else {
                            keyValue = el
                            writeValueAndCheckIfSelected(keyValue, value, writer)
                        }

                        writer << '>'

                        if (optionValue) {
                            if (optionValue instanceof Closure) {
                                writer << optionValue(el).toString().encodeAsHTML()
                            }

                            else {
                                writer << el&#91;optionValue&#93;.toString().encodeAsHTML()
                            }

                        }

                        else if (valueMessagePrefix) {
                            def message = messageSource.getMessage("${valueMessagePrefix}.${keyValue}", null, null, locale)

                            if (message != null) {
                                writer << message.encodeAsHTML()
                            }

                            else if (keyValue) {
                                writer << keyValue.encodeAsHTML()
                            }

                            else {
                                def s = el.toString()
                                if (s) writer << s.encodeAsHTML()
                            }
                        }

                        else {
                            def s = el.toString()
                            if (s) writer << s.encodeAsHTML()
                        }

                        writer << '</option>'
                        writer.println()
                    }
                }

                writer << '</optgroup>'
                writer.println()
            }
        }
        // close tag
        writer << '</select>'
    }

    void outputAttributes(attrs) {
        attrs.remove('tagName') // Just in case one is left
        attrs.each {k, v ->
            out << k << "=\"" << v.encodeAsHTML() << "\" "
        }
    }

    def typeConverter = new SimpleTypeConverter()
    private writeValueAndCheckIfSelected(keyValue, value, writer) {
        boolean selected = false
        def keyClass = keyValue?.getClass()
        if (keyClass.isInstance(value)) {
            selected = (keyValue == value)
        }
        else if (value instanceof Collection) {
            selected = value.contains(keyValue)
        }
        else if (keyClass && value) {
            try {
                value = typeConverter.convertIfNecessary(value, keyClass)
                selected = (keyValue == value)
            } catch (Exception) {
                // ignore
            }
        }
        writer << "value=\"${keyValue}\" "
        if (selected) {
            writer << 'selected="selected" '
        }
    }

    def renderNoSelectionOption = {noSelectionKey, noSelectionValue, value ->
        // If a label for the '--Please choose--' first item is supplied, write it out
        out << '<option value="' << (noSelectionKey == null ? "" : noSelectionKey) << '"'
        if (noSelectionKey.equals(value)) {
            out << ' selected="selected" '
        }
        out << '>' << noSelectionValue.encodeAsHTML() << '</option>'
    }

    private String optionValueToString(def el, def optionValue) {
        if (optionValue instanceof Closure) {
            return optionValue(el).toString().encodeAsHTML()
        }

        el[optionValue].toString().encodeAsHTML()
    }
}

This is my very first attempt and so I’m sure some not-so-best practices abound. Feedback and comments are welcome.

Update

  • Previously, the tag wouldn’t pass through extra attributes that were defined. This has been fixed.
  • My previous version didn’t take into account a bunch of stuff that Grails’ actual SELECT tag does. I was able to find the Grails implementation here. I modified it to take into account OPTGROUPs.

Drop-shadows with CSS and Javascript

You have probably seen websites that have images with drop-shadows. It gives the image a three-dimensional floating effect. Usually the images are given drop-shadows with an image editing software like Photoshop. In IE, a drop-shadow is easy to achieve using the DropShadow filter. Unfortunately, this method is not standards compliant and only works on Internet Explorer 5.5 or later. For all the gripes I have against Internet Explorer, I actually like the filters on Internet Explorer and I wish that the W3C would figure out a standards-compliant way to implement filters in their CSS design.

Currently, the only way to implement drop-shadows without resorting to IE’s DropShadow filter is to have an image that was previously edited through an image-editing software, dynamically generate the drop-shadow for the image through a dynamic graphics generation tool like GD or ImageMagick, or (complicated} CSS. I have run across a number of CSS-based drop-shadow solutions on the web. I either found them to be too complicated or unsatisfactory. My major problem was that I couldn’t easily accommodate them into my site design. One major problem I had was that many of the solutions employ floated images. This was an issue for me, because I like to center my images and this is impossible to do with floated elements. In addition, I have a major case of Re-inventing The Wheel So I Can Say It’s Mine! syndrome. Anyway, I decide to muck around and come up with my own solution. I have come up with one that doesn’t look that complicated and should work… in theory. But I ran into some issues when I tried to apply it. As a result the final solution is… well, clunky. But it works for me!

My solution involves the use of two divs. The first (parent) div contains the shadow image (which I shamelessly stole from here). The child div contains the actual image for which we want to create a drop-shadow. Essentially, the layout looks like this:

Structure

Pretty simple. In HTML, the structure looks like this:

<div style = "position:relative;">

 <img id = "sh_image" src = "http://vivin.net/images/shadow.png" />

 <div style = "position:absolute; top:-Xpx; left:-Ypx;">
  <img id = "img_image" src = "http://path.to.image/image.png" />
 </div>

</div>

You might be wondering what the Xpx and Ypx things are. Well, this is where some of the clunky parts come in. One problem I faced was that I couldn’t simply offset it by a constant amount, like 5px, for example. For some reason, the amount needs to be changed based on the dimensions of the image. I am not sure why this happens. Theoretically, the dimensions shouldn’t matter. Well, that’s one of the clunky issues. The other one has to do with Javascript. If you implement what I have so far, you’ll notice that the parent div would extend to accommodate the entire shadow image. This is obviously not what we want. My solution for this problem was to use Javascript to scale the shadow image to the size of the child image:

function dropShadow(img_name, shadowWidthExtend, shadowHeightExtend)
{
         var shadow = document.getElementById("sh_" + img_name);
         var img = document.getElementById("img_" + img_name);

         shadow.width = img.width + shadowWidthExtend;
         shadow.height = img.height + shadowHeightExtend;
}

What’s shadowWidthExtend and shadowHeightExtend you ask? Well, that’s another clunky part. So even after I set my offsets (through trial and error) as per the image dimensions, I still found that my drop-shadows didn’t look perfect. In order to make them look good, I had to “extend” the shadow image’s height and width. Yeah, not that ideal. But like I said, it works. I am not sure why these issues exist. I think it might have to do with the scaling of the shadowy parts of the shadow image (where it actually fades).

Now we can put it all together:

In your HTML file:

<div class = "shadow">

 <img id = "sh_image" src = "http://vivin.net/images/shadow.png" />

 <div class = "image">
  <img id = "img_image" src = "http://path.to.image/image.png" />
 </div>

</div>

<script language = "Javascript">

 dropShadow("image", A, B);

</script>

In your CSS file:

div.shadow
{
    position:relative;
}


div.image
{
    position:absolute;
    top:-Xpx;   /* determined based on image dimensions */
    left:-Ypx;  /* determined based on image dimensions */
}

In your Javascript file:

function dropShadow(img_name, shadowWidthExtend, shadowHeightExtend)
{
         var shadow = document.getElementById("sh_" + img_name);
         var img = document.getElementById("img_" + img_name);

         shadow.width = img.width + shadowWidthExtend;
         shadow.height = img.height + shadowHeightExtend;
}

As you can see, far from ideal. If I was only able to figure out how to make the offsets constant then this would be a pretty decent solution. If anybody knows why this is happening, please let me know. I am decently proficient in CSS and I am not a guru by any means. But I love to learn!

Oh, and as far as centering the drop-shadowed image, that’s easy to do by wrapping the whole thing in a center-aligned table with only one row and one cell.

innerHTML alternative for XHTML documents in Firefox

I finally figured out an alternative to using innerHTML for an XHTML document in Firefox. <a target = “_blank” href = http://vivin.net/2005/06/30/innerhtml-and-createcontextualfragment-problems-when-firefox-renders-xhtml/”>Earlier I talked about trying to use innerHTML on my website, and how I ran into problems. Basically, the property is not supported for XHTML documents in Firefox. This makes sense if you think about it, because HTML isn’t the same as XHTML. Since HTML isn’t as strict as XHTML, there exists the possiblity of inserting badly formed code into the document, and result wouldn’t be valid. From what I understand, this “problem” will be fixed in the 1.1 release of Firefox. Until then, I have an alternative. It may seem a little convoluted, but it really isn’t all that bad.

My approach revolves around using the DOM Core Methods. There is also another way, and that uses XSLT. I haven’t really looked into it, but I plan to, very soon. It could be faster.

Anyway, I realized that there was no way I could actually plug the XHTML code in directly into the document. I would have to parse it and create objects for each of the elements. But how do we parse the code? There really is no point writing our own parser, since it will be slow. Luckily, you can use the DOMParser object. This parser will let you parse an XML document. Since an XHTML document is essentially XML, you can use the DOMParser object to parse your code. The DOMParser object has three methods – parseFromBuffer, parseFromStream, and parseFromString. The one I used, is the last one. parseFromStream works like XMLHttpRequest, and allows you to read XML from a URI. I could have used that also, but parseFromString worked better for me.

The first thing you want to do, is create a DOMParser object. After that, you use parseFromString to parse your XML. parseFromString returns a Document object, which as you will see, can help us out a lot. So the code for parsing the XHTML looks like this:

var parser = new DOMParser();
var XMLdoc = parser.parseFromString("<root>" + myXHTML + "</root>");

It is not necessary to enclose your XHTML snippet with a root tag, but it will take care of the situation where your snippet doesn’t have a root element (so it would seem like it has more than one root element). In my case, I am inserting a snippet of XHTML code, and there is no root element, which is why I am enclosing the code with those tags.

I thought that once I had the Document object, I could simply figure out what the children of root are, and simply add them to the DOM by using appendChild. But that didn’t work out. This is due to the fact that XMLdoc is an XML Document, and not an HTML Document. Plugging it into the DOM directly won’t really help. What we have to do is figure out a way to transform the XML Document into an HTML Document. The sure-fire way to do that is to use XSLT. I poked around some code and realized that there was much I had to learn about XSLT before I could try to use it. My only other option was to do it through Javascript. This, as it turned out, wasn’t so bad. The Document object is essentially a tree, and all you’d have to do is “walk” the tree. While walking the tree, you can also create an object tree that you can later append to the main DOM.

I wrote a recursive function called walkTree that does exactly this. On a side-note, I am aware of the existence of the TreeWalker object, but I was too impatient to figure out how exactly it worked. It may actually prove to be faster, but who knows – maybe I will give it a try someday. Anyway, all those Data Structures and Algorithms classes that I had to take in College came to good use. My basic algorithm is this – you walk the tree, and every time you encounter a node, you create the appropriate XHTML element using createElementNS. First let’s look at the code to traverse the tree. Here is the basic skeleton that I use:

function walkTree(node)
{
         if(node.hasChildNodes())
         {
            node = node.firstChild;

            do
            {
                  alert("The node is " + node.nodeName +
                        " and its value is " + node.nodeValue
                       );

                  walkTree(node);
                  node = node.nextSibling;
            }
            while(node);
         }
}

Now we have to figure out how to use this skeleton to create elements and append them to the DOM. Like I mentioned before, you can create elements in XHTML using the createElementNS object. So you could do:

var element = createElementNS("http://www.w3.org/1999/xhtml", tagName);

The URI in createElementNS basically identifies the namespace we want to use. In this case, we want to use the XHTML namespace defined by the W3C. Now that we have created the element, we want to set its attributes. However, we don’t really know what the attributes are at runtime. But we can still access them using the attributes property of a node. The attributes property of a node is an array that contains all the attributes and their values. Now we have to figure out how to copy the attributes over to the element. There really is no easy way to do this. The only way I could figure out how to do this, was to construct Javascript code and use the eval() function. The reason for this is that every location in the attributes array is of type Node. The nodeName and nodeValue properties give us the attribute name and the attribute value respectively. So the code to set attributes would look like:

for(var i = 0; i < node.attributes.length; i++)
{
    var currAttribute = node.attributes[i];

    if(currAttribute.nodeName == "style")
    {
       applyStyle(element, currAttribute.nodeValue);
    }

    else if(currAttribute.nodeName == "class")
    {
       element.className = currAttribute.nodeValue;
    }

    else
    {
       eval("element." +
            node.attributes[j].nodeName +
            " = \"" +
            node.attributes[j].nodeValue +
            "\""
           );
    }
}

Pretty straightforward, but why do we have the if statements for “style” and “class”? Well, I’m sure you can see why you need the if statement for “class”. The attribute name in Javascript is different from the attribute name used in the XHTML code. But what’s that applyStyle function? Well, that function translates the CSS style into the Javascript format. So for example, if we had a style background-color:#ffffff;, the Javascript equivalent is obj.style.backgroundColor = “#ffffff”;. I wrote a simple function that performs this translation:

function applyStyle(obj, style)
{
         // Remove all whitespaces, and remove terminating ; if any
         style = new String(style).replace(/\s/g, "").replace(/;&#36;/, "");

         // Split into an array of attribute:value pairs
         var attributes = style.split(";");

         for(var i = 0; i < attributes.length; i++)
         {
             // Split the pair into the attribute and the value
             // Then translate the CSS style attribute into Javascript

             var attrvalue_pair = attributes[i].split(":");
             var attr = attrvalue_pair[0].replace(/-[a-z]/g, function($1)
                                                             {
                                                                      return new String($1).toUpperCase().replace(/-/g, "");
                                                             }
                                                 );

            // eval the constructed Javascript code

            eval("obj.style." + attr + " = \"" + attrvalue_pair[1] + "\"");
         }
}

Now we can put it all together:

function insertXHTML(myXHTML, myObject)
{
         var parser = new DOMParser();
         var XMLdoc = parser.parseFromString("<root>;" + myXHTML + "</root>", "text/xml");

         if(XMLdoc.documentElement.nodeName == "parserror")
         {
            alert("Your XML document has errors");
         }

         else
         {
            walkTree(XMLdoc.childNodes[0], myObject);
         }
}

function walkTree(node, parent)
{
         // Failing case for recursion. We don't want to continue 
         // if the node has no children.

         if(node.hasChildNodes())
         {
            // Set the node pointer to the first child of the current node
            // and start looping through all the children

            node = node.firstChild;

            do
            {
                  // We don't want any whitespace (line breaks, carriage returns, tabs or spaces)

                  if(!/^[\t\r\n ]+$/.test(node.data))
                  {
                     var element;

                     // Each node has a nodeType. The nodeType for a TextNode is 3. We want to handle
                     // Text Nodes separately from nodes that are tags

                     if(node.nodeType != 3)
                     {
                        // Create a new element using createElementNS as this is an XHTML file

                        element = document.createElementNS("http://www.w3.org/1999/xhtml", node.nodeName);

                        // Here, we manage the set the element's attributes by copying them from the node.
                        // Notice that we want to handle the style and class attributes separately. 
                        // The applyStyle function uses the CSS and creates the equivalent Javascript code
                        // for the style object of the current element. Look at the applyStyle function for
                        // more information

                        if(node.attributes)
                        {
                           for(var j = 0; j < node.attributes.length; j++)
                           {
                               var currAttribute = node.attributes[j];

                               if(currAttribute.nodeName == "style")
                               {
                                  applyStyle(element, currAttribute.nodeValue);
                               }

                               else if(currAttribute.nodeName == "class")
                               {
                                  element.className = currAttribute.nodeValue;
                               }

                               else
                               {
                                  // I guess this might be a hack, but I couldn't think of any other way to do it.
                                  // here we set any other attributes that the node may have. We construct Javascript
                                  // code to set the attributes and eval it. nodeName is the attribute name, and nodeValue
                                  // is the attribute value

                                  eval("element." + currAttribute.nodeName + " = \"" + currAttribute.nodeValue + "\"");
                               }
                           }
                        }
                     }

                     else
                     {
                        // If the element is a text node, we create a TextNode element

                        element = document.createTextNode(node.nodeValue);
                     }

                     // We make a recursive call to the function, to continue traversing the tree

                     walkTree(node, element);

                     // We append the element that we have created, to the parent element

                     parent.appendChild(element);
                  }

                  // Move onto the next sibling of the current node

                  node = node.nextSibling;
            }
            while(node);
         }
}

And there it is, folks. As easy as pie! I’ve found that it’s not that slow, especially if you’re parsing it just once. I’m using this same code for the Live! Preview, and it gets noticeably laggy when the code to be inserted is large. I guess if I was using XSLT, it wouldn’t be this slow. That’s going to be my next project! Feel free to use this code, and I hope it helps you out. Also, if you find ways to make it better/faster, or find bugs, or if you hate/love it, don’t hesitate to comment on it.

Update:

It was pointed out to me that there is a much simpler way of inserting XHTML into the document. You can use the importNode function, instead of walking the tree and manually copying the attributes. There doesn’t seem to be a speed advantage, but it is much simpler:

function insertXHTML(myXHTML, myObject)
{
         var parser = new DOMParser();
         var XMLdoc = parser.parseFromString("<div xmlns = "http://www.w3.org/1999/xhtml">" + comment + "</div>", "application/xhtml+xml");

         var root = XMLdoc.documentElement;

         for(i = 0; i < root.childNodes.length; i++)
         {
             myObject.appendChild(document.importNode(root.childNodes[i], true));
         }
}

I wish I had known about this function earlier – it would have saved me a whole lot of trouble!

References

This site is XHTML 1.1 Valid

I finally got this site to validate! The folks over here were really helpful, and pointed out that I wasn’t assigning values to my objects’ style attributes properly. For example, I was doing:

obj.style.width = 200;

instead of:

obj.style.width = "200px";

The second way is the correct way because it is important to specify units for your attributes. In addition, Firefox won’t parse it because it’s very strict about XHTML and CSS. Furthermore, certain things weren’t where I expected them to be. I was used to writing:

obj.style.height = document.body.scrollHeight;

But that no longer worked (the value being returned was 0). The method that works now is:

obj.style.height = document.body.parentNode.scrollHeight + "px";

You have to add in that “px” because the scroll height doesn’t have any units. Once I made these changes, the site started working and validating perfectly. Thanks a bunch to all those who helped!

I guess I did most of this just to jump on the bandwagon and because it did have a “geek” factor. But eitherway, since my website adheres to the standards now, I can be reasonably sure that it looks like the way it should… except for IE though. IE lets you get away with almost anything, and that’s why 90% of the websites out there have such terrible code. IE is very forgiving and can recover from bad HTML, but that only encourages sloppy coding. But anway, now that my site validates, I can proudly display the W3C’s “Valid XHTML 1.1” icon!

Live! Preview and XHTML Compatibility

I’ve added a new feature called “Live Preview”. You can see your comment as it will appear, right as you type it. I randomly just came up with the idea yesterday – I figured I could use the same code I have in place for the preview, for the Live Preview as well. I just trap the onkeyup event for the textarea. It works pretty well. Of course, there is a lag, but it’s not too bad. Oh yeah, I also removed the numbers in the comment list and replaced them with little blurbs that I made. The blurbs won’t show correctly in IE, because IE is stupid and doesn’t handle PNG transparencies well.

Then there’s XHTML compatibility. I’ve been trying really hard to get this page to validate. I was finally able to get rid of all the errors. This page validates (if you set the DOCTYPE to XHTML 1.1 explicitly, but I’ll get to that later) without any errors, as does all the CSS. However, the problem comes when I add in the DOCTYPE. The page validates perfectly, but displays completely messed up in Firefox (IE can’t handle the mime type so I didn’t even bother checking it with IE). I tracked the problem down to the Javascript. Basically, I can’t set any DOM object attribute values from Javascript. It’s as if all attributes are read-only. It’s really quite bizarre. I’ve posted the problem on a message board, so we’ll see what happens. If any of you know why I could be facing this problem, please let me know.

Here’s what the DOCTYPE (and other information) looks like:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">

Also, I have a sandboxed version of the website here. You can see the problem directly there.

Preview Button and no more Iframes

I have added a “Preview Comments” button to the comment submission form. So now, you’re able to preview your comments before submitting them. The preview will show you your comments exactly as they will appear (after they have been submitted).

The other big thing is the removal of iframes. I thought iframes were really freaking awesome when I first found out about them. And they are. There are a bunch of cool things you can do with them. I always used iframes to update the contents of a page without refreshing the entire page itself. This was accomplished by submitting to a hidden iframe. The server-side script in the iframe would generate Javascript, which would then manipulate the DOM on the parent page. Pretty nifty. I learnt that when I was working at College of Business at ASU. It was something that the bunch of us (Chris, Kelly, Gravey, Cameron, and I) came up with in the Fall of 2001. We thought it was pretty novel.

I used iframes extensively in the last incarnation of my website. When that crashed and burned, I needed to make a new website. So I went ahead and used iframes on this one too. Somewhere along the way, I was possessed by the urge to make this website XHTML compatible. But alas, iframes didn’t pass the compatibility test. I then tried to find a replacement for iframes. The object tag seemed to work, except there was no way to make it reload. Then I tried XMLHttpRequest. This also seemed promising, but I had to make a bunch of changes to the Javascript. I didn’t feel like doing that. I also then realized that I wanted content to show up on search engines, and having an iframe wasn’t going to do that. So I then decided to re-design the backend completely. Instead of using an iframe, I would just dump content directly into the page. Of course, now it wouldn’t load the content seamlessly when you clicked on a date, but what the heck. I still had one iframe though – the one that populates the calendar. I figured there simply wasn’t any other way to do it, so I left it as it is.

It stayed that way until Marc left me a comment talking about the need for the functionality to preview comments. I was going to use the iframe, before I realized that the comments could have line-breaks, and all other kinds of nasty stuff that could mess up Javascript. I could have accessed the HTML from the iframe, and dumped it into the page, but the first thing that came to my mind was the XMLHttpRequest object. I used it to replace the iframe. I think it actually works a whole lot better, and is definitely more flexible. Even though the object is used for XML, you can get HTML through it as well through the responseText attribute. This way, you can simulate the action of submitting to a form, and then access the data generated by the server-side script. You can then do whatever you want with the data. This is definitely much more flexible than having to deal with Javascript from the action iframe.

In the course of all this, I wrote some (I think) pretty nifty code. I wrote two functions; getXMLHttpRequestObject creates an XMLHttpRequest object and returns it. I needed this function because IE and Firefox create the object differently. The other function, sendXMLHttpRequest sends the request to the server. The cool part is that I also send in a custom handler. When sendXMLHttpRequest finishes receiving the data from the server, it calls the custom handler with that data. Maybe not all that fancy, but I think it’s cool :). Here are what the functions look like:

getXMLHttpRequestObject:

function getXMLHttpRequestObject()
 {
          if(Firefox)
          {
             return new XMLHttpRequest();
          }

          else
          {
             return new ActiveXObject('Microsoft.XMLHTTP');
          }
 }

sendXMLHttpRequest:

function sendXMLHttpRequest(Request, uri, POSTdata, responseHandler)
 {
          Request.open("POST", uri, true);
          Request.setRequestHeader(
                                   'Content-Type',
                                   'application/x-www-form-urlencoded'
                                  );

          Request.onreadystatechange = function()
                                       {
                                                if(Request.readyState == 4)
                                                {
                                                   if(Request.status == 200)
                                                   {
                                                      responseHandler(Request.responseText);
                                                   }

                                                   else
                                                   {
                                                      alert("There was a problem retrieving data: " + Request.statusText);
                                                   }
                                                }
                                       }

          Request.send(POSTdata);
 }

A sample call is as follows:

function doSomething(param1, param2)
 {
          var Request = getXMLHttpRequestObject();
          var FormData = "param1=" + param1 + "&amp;param2=" + param2;
          sendXMLHttpRequest(Request, "doit.php", FormData, myResponseTextHandler);
 }

 function myResponseTextHandler(text)
 {
          //do something with text
 }

Neat huh? Javascript is a whole lot more powerful than some people think. Ok, enough geek talk!

References:

I have work to do!

Work on this page might slow down for a while. I have a small web designing job coming up. Well, it’s not really a job per se. I go to this forum a lot, and they would like a new design on the front page… So that’s what I’m doing. It should be fun!

All original content on these pages is fingerprinted and certified by Digiprove
%d bloggers like this: