The Artima Developer Community
Sponsored Link

Computing Thoughts
Hybridizing HTML
by Bruce Eckel
November 8, 2007
Summary
How to create Flex forms within HTML pages to easily achieve cross-browser and cross-platform functionality.

Advertisement

I see more and more web sites with unreadable, overlaid text and components that get hidden or don't work. I don't know if this is primarily due to an over-belief in the validity of AJAX components, CSS not living up to its cross-platform promises, or both.

John Dvorak recently went on a rant -- in fairly significant detail -- where he decided that it's AJAX that makes web apps sucky. He wasn't really talking about AJAX (as his readers were quick to point out) but rather the CSS/JavaScript/DHTML UI on many AJAX components. It appears that a lot of AJAX component libraries focus more on the underlying technology and not enough on getting the presentation details right. Basically, it's the CSS and JavaScript that the AJAX component libraries use that isn't working right, but the result is that AJAX is getting a black eye.

His basic argument holds, however: anyone can add some cute stuff to a web page, but how hard is it to make that work all the time (regardless of screen resolution), everywhere (no matter what OS/Browser), and actually be useful? The path of least resistance with AJAX is not to get it working right.

I don't have Dvorak's certainty, but the increase in the use of AJAX and the proliferation of unusable sites seems to coincide. What I do know is that developers are not taking the time to test their sites in different browsers -- apparently they're assuming that if they use AJAX and CSS and it looks OK in IE, then it will work elsewhere. Even the mighty GMail recently introduced a bug that caused overlapping text with FireFox on my machine.

It seems like every travel site I go to (airlines, hotels, general travel) is usable only with Internet Explorer (IE). If I start with Firefox and go to these sites I find that date choosers (almost certainly AJAX components) may be invisible, or when they pop up not all the information is visible. The rest of the components may or may not be visible, but by that time I've remembered that I have to switch to IE.

These sites are created by professional developers. Perhaps they all went to a conference for people who write travel site software and there were lots of talks about how AJAX components will solve all their problems without testing on multiple configurations, and they believed it.

I don't blame them at all. I certainly don't want to take the time to verify everything under all browsers. I want to "write once, run everywhere," which I know has become a joke because of Java's claims but the idea is still worth trying for, otherwise we're all doomed to a nightmare of testing. The result will be a narrowing of scope in what we're willing to attempt on the web.

It's clearly not impossible to write sites that work across platforms, because there are many sites which are complex on the same order that a travel site is complex but they work fine under Firefox, and by implication on other non-IE browsers. Google applications, Wells Fargo, Amazon all seem to work. Sites like TigerDirect appear to solve the problem by avoiding AJAX components and sticking with HTML.

My solution on my own site is to be ultra-conservative and mostly very simple. I stay far away from the edge of what's possible, and I try to make all pages use the same simple layout and CSS. Even then I've had to work to try to get that simple format to behave the same way everywhere, and it certainly hasn't been as easy as CSS promised. As an incremental step (separating content from layout) CSS moves in the right direction, but in the big picture it's a failure. And poorly-designed, at that -- why can't I just create and use my own simple tags? Why do I have to have DIVs and classes and all that everywhere? And why do I have to go to PHP or another system just to include a standard header and footer? If they were going to "fix" it, why couldn't they have made it simple and functional?

I've read about how great CSS is supposed to be, and I've struggled with the reality. Until something better comes along I'm going to say it doesn't solve the problem and that we'll have to come up with a better solution ourselves.

And whenever I criticize the state of the web, CSS, AJAX, whatever, people come out of the woodwork saying "you're just not doing it right" or "you just don't understand." Without any acknowledgment that it seems like NO one is getting it right. Dvorak goes further and says: "Except for a precious few exceptions, AJAX is inherently bad." And although he appears to misunderstand the technology issues, he's still connected the dots.

Many people assert that it's not any of these technologies that are at fault, but rather those technologies in the hands of incompetent programmers (who, apparently, create AJAX libraries that are widely distributed and used). I've seen this argument many times before: a technology is labeled "good" simply because it's possible to use that technology properly (acutally, many discussions seem to come down to this assertion). The problem with this argument is that the real issue is not about possibility, it's about ease. If a technology is relatively easy to use and get right, then more people will be able to quickly acquire and use that technology. If it's not easy to get right, then there will be a proliferation of bad examples, which will either cause it to be fixed or cause people to look elsewhere.

I suspect the bad ecommerce website experience comes from a combination of CSS either not being standardized or being misused, along with developers trusting that AJAX components (which typically use CSS and JavaScript for display) do what they claim and can just be thrown into the mix without testing the result across browsers.

The basic problem is this: How do we get things to display consistently without wasting enormous amounts of time? This issue seems to be most problematic where forms are concerned; text and graphics seem to work OK (what a relief, since this was originally the only thing that the web was supposed to do, back when it was created).

Forms in Flex

The Flex designers have gone to a lot of trouble to make it easy to create forms in Flex -- easier, in fact, than creating their equivalent in HTML. And because the cross-platform issues are solved by the Flash VM, you aren't left wondering whether the forms will render correctly across browsers. In this article I will show the power of forms and form components in Flex, and how easy it is to combine Flex forms with HTML. Once you see this you'll think twice the next time you want to create a form, especially a complex one, using CSS/JavaScript/AJAX.

There are three approaches to creating any UI with Flex:

  1. Hand-code everything. For this, you're best off starting with the free trial of FlexBuilder to generate the basic template which you can then duplicate by hand or with an automation script.
  2. Use the GUI builder in FlexBuilder. This allows you to create the UI by dragging and dropping components. It's common for people to start this way, and then switch to:
  3. Hand-code the interface within FlexBuilder (which provides context help and completion). The MXML layout language includes directives for making form layout easy, so that it's actually less code than laying out a form in HTML.

In addition, FlexBuilder generates the code for detecting the Flash player (and allowing the user to install it if it's not there, or upgrade it if the version is too old). As you'll see later, we'll instead use a JavaScript library to solve that particular problem.

Creating the Form

Because many of my negative experiences with CSS/JavaScript/AJAX forms have involved date choosers that are difficult or impossible to use, and many of those have occurred on travel sites, let's create a simple form that finds out your departure and return dates, your first and last name and your email address.

But let's go a step further and validate the inputs on the client side, before they can be submitted to the server. Because this eliminates a lot of tedious back-and-forth for the user, various sites have tried to do it with AJAX and JavaScript, but it's hard and doesn't always seem to work. However, Flex provides built-in Validator objects to do the work for you, quite easily. It's also straightforward to create your own Validator subtypes for special cases, as you'll see. Basically, we can guarantee that all the input data is correct before the form is submitted, but without the effort and uncertainty that comes when trying to do it in browser JavaScript.

This form lays out the components using the MXML Form component, which holds FormItems, each of which has a label and holds some other type of component. What you'll notice is that the label and the component are neatly organized -- something you'd ordinarily do with more code using an HTML table.

I've provided a general introduction to MXML in this article. Here's the code, which I'll explain afterward. Everything within the <Form> tags is the form labels and components; everything afterwards is validation and submission to the server. Much of this code is generated automatically by FlexBuilder via context-completion:


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:validators="validators.*" applicationComplete="validator()">
    <mx:Style> global { font-size: 15;    } </mx:Style>
    <mx:Form>
        <mx:FormItem label="Departure Date" required="true">
            <mx:DateField id="departureDate" change="departureChanged()"
                selectedDate="{new Date()}" />
        </mx:FormItem>
        <mx:FormItem label="Return Date" required="true">
            <mx:DateField id="returnDate" change="validator()"
                selectedDate="{new Date(new Date().time + validators.ReturnDateValidator.ONEDAY)}" />
        </mx:FormItem>
        <mx:FormItem label="Traveler Last Name" required="true">
            <mx:TextInput id="lastName" change="validator()"/>
        </mx:FormItem>
        <mx:FormItem label="Traveler First Name" required="true">
            <mx:TextInput id="firstName" change="validator()"/>
        </mx:FormItem>
        <mx:FormItem label="Traveler's Email" required="true">
            <mx:TextInput id="email" change="validator()"/>
        </mx:FormItem>
        <mx:FormItem>
            <mx:Button id="submitButton" label="Submit" click="submit()"/>
        </mx:FormItem>
    </mx:Form>
    <mx:Validator id="requireLastName" required="true" source="{lastName}" property="text" />
    <mx:Validator id="requireFirstName" required="true" source="{firstName}" property="text" />
    <mx:EmailValidator id="validateEmail" required="true" source="{email}"  property="text" />
    <validators:ReturnDateValidator id="validateReturnDate"
        source="{returnDate}" property="selectedDate" departureDate="{departureDate}"/>
    <mx:Script> <![CDATA[
    private function departureChanged():void {
        const ONEDAY:int = validators.ReturnDateValidator.ONEDAY;
        if(returnDate.selectedDate.time >= departureDate.selectedDate.time + ONEDAY) return;
        returnDate.selectedDate = new Date(departureDate.selectedDate.getTime() + ONEDAY);
        validator();
    }
    private function validator():void {
        submitButton.enabled = Validator.validateAll(
            [requireLastName, requireFirstName, validateEmail, validateReturnDate]).length == 0;
    }
    private function submit():void {
        var args:URLVariables = new URLVariables();
        args.FirstName = firstName.text;
        args.LastName = lastName.text;
        args.Email = email.text;
        args.DepartureDate = departureDate.selectedDate.toDateString();
        args.ReturnDate = returnDate.selectedDate.toDateString();
        var url:URLRequest = new URLRequest("http://www.mindviewinc.com/demos/flex/trip.php");
        url.method = "POST";
        url.data = args;
        navigateToURL(url,"_blank");
    }
    ]]> </mx:Script>
</mx:Application>

It's very easy to adjust styles globally. Here, I've set the font size to 15 for everything on the form by using the <Style> component.

You can see that each FormItem has required set to true. This flag is slightly misleading because it doesn't force the user to fill the form; all it does is put a red asterisk next to the component to tell the user that the field is required. In order to programmatically force the field to be required, we'll use validation as you'll see later.

A DateField is a built-in Flex component that pops up a calendar when you want to choose a date. So it's very much like the AJAX components that you see used, except that it always works, across all platforms and configurations. In the departureDate, selectedDate is set to the current date using a binding expression (the curly braces). The returnDate is initialized to the current date plus one day.

The rest of the FormItems are TextInputs, and finally there's a submit button. This button will only be enabled when all the inputs are correct, and when you press it the data will be submitted to the server.

Note that each of the input components on the form set the change property to a method. These methods perform the validation.

Validating the Form Data

After the <Form> you can see four Validator components: three that are part of the standard Flex library and one that I've written. The first two just ensure that there is something in the lastName and firstName TextInputs, the third one also requires that the contents of the email TextInput follows the pattern of an email address (because it's a tried-and-tested standard Flex component, you never have to reinvent it). The custom ReturnDateValidator verifies that the return date occurs after the departure date.

Each Validator must be told where to find the data to validate by setting both its source (the component to validate; note the binding expression) and its property field (the property to test within that component). The ReturnDateValidator must also know the departureDate to compare to.

The easiest way to validate the whole form is to call the static validateAll() method, as you can see in the validator() method within the <Script> block. validateAll() takes an array of Validator objects and tests each one, returning an array of results. Because any result indicates a failed validation, if the array length is nonzero the form's validation has failed (and the submit button will not be enabled).

This is a slightly more complex form because we don't just want to validate the inputs; we also want to constrain the dates so that the return from the trip always happens at least one day after the departure. If the user is selecting the departure date, it seems reasonable to automatically initialize the return date to one day after the departure, which is why the departureDate DateField calls departureChanged() (rather than just validator() like the rest do). You can see that departureChanged() forces the return date to be at least one day after the departure -- and then it also calls validator().

On the other hand, if the user is selecting the return date, we can't really assume anything about the departure date; all we can do is tell the user why the field is invalid. This is where the custom ReturnDateValidator comes in.

A Custom Validator

A custom Validator is just another kind of custom component, which I introduced here. When you subclass Validator, the only method you must override is doValidation(). During validation, this method is automatically handed an argument based on the source and property that were provided in the Validator's MXML declaration.

package validators {
    import mx.controls.DateField;
    import mx.validators.ValidationResult;
    import mx.validators.Validator;

    public class ReturnDateValidator extends Validator {
        private var departure:DateField;
        public static const ONEDAY:int = 1000 * 60 * 60 * 24; // In milliseconds
        public function set departureDate(depart:DateField):void {
            departure = depart;
        }
        protected override function doValidation(value:Object):Array {
            var results:Array = super.doValidation(value);
            if(results.length > 0)
                return results;
            if(value.time < departure.selectedDate.time + ONEDAY)
                 results.push(new ValidationResult(true, null, "returnBeforeDeparture",
                     "Return date must happen after departure date"));
            return results;
        }
    }
}

This Validator is slightly different than the average because it validates using two UI components whereas most Validators just focus on a single component. To connect to the second component (the other DateField), I introduce a property called departureDate which stores into a field called departure via a set method.

I stumbled a bit when dealing with the slightly odd design of the Validator. I first overrode the validate() method instead, and that worked fine, even with validateAll(). However, the docs say that you must override doValidation() and that you should not override validate(). When I did this, I got a runtime error because both source and property must be configured when the Validator object is created, and the value argument is handed to doValidation() by the framework by combining source and property. I had originally thought I should only have to use source, but to satisfy the framework I had to set it up to use both. This is the nature of defining your own Validator; the reasons for the design are not intuitively obvious to me but those are the hoops you must jump through to make it work.

In ReturnDateValidator, the value that's passed as the argument is from the source (the DateField) combined with the property (the selectedDate). So we just say value.time to get the number of seconds after 1970 for the returnDate, but we have to say departure.selectedDate.time to get the number of seconds for departureDate. Again, the design can catch you at first but it's not intolerable.

In doValidation(), you typically first call the base-class doValidation() method, which returns an array. If the array is empty then the base-class validation succeeded. In this case the base-class call is probably redundant because it only ensures that something is in the field, and with DateFields I think something is always there so the call will always succeed. In any event this code shows the ordinary form when creating as custom Validator.

If the return date is not at least a day after departure, there's a problem so we append a ValidationResult to the results array. This notifies anyone interested that the validation failed, but more importantly it allows us to pass a message back to tell the user what the problem is. This message appears when the user moves their mouse over the input box (which will have a red line around it if it's invalid).

That's really the essence of Validators; it's not hard to create custom versions whenever you have special form constraints.

Submitting the Form Data to the Server

The submit() function in the main application is called when the user presses the submit button (which is only possible when validation succeeds). Many Flex application examples you'll see take the more sophisticated approach of talking to the server using an HTTPRequest object. This kind of Flex UI carries on a conversation with the server and modifies itself in response (which is also what AJAX does), so the user doesn't have the usual experience of going from page to page.

That's a good way to do it, but in this example I'm going to provide a transition path, so you could take an existing web page and just change the input form, but leave everything else the same. It's a bit of a throwback but it is easy to understand since it uses the old model of the web.

As such, the data in the form needs to be passed to the server in a call to that server's URL. You can use a GET to do this, but in this example we'll use POST. Flex provides the facility to make the call and to transfer to the page returned by the server using the navigateToURL() function.

To use this function, you must pass it a URLRequest object which contains the target URL and may have data attached in the form of a URLVariables object. So the first thing that submit() does is create and populate the URLVariables object, then attach it to the URLRequest, which is the first argument to navigateToURL(); the second tells it how to display the resulting web page in the browser; typically you'll use either "_blank" to open a new browser window or "_self" to use the current window. Because of security issues on this weblog platform I couldn't use "_self" so you'll have to allow the popup window to see the results.

The Server Side

For simplicity's sake, let's assume you're just going to replace typical forms in web pages with the equivalent Flex forms. This means we'll only have one form and submit button on a page, although with Flex this doesn't have to be the case at all, since the Flex component can easily morph according to the information it gets back from the browser in response to the submission. Basically, you can do what you can do with AJAX, but nicer.

In our scenario, when the user presses the submit button, the Flex form will submit its data just as an HTML form would, so you can use any back-end technology you'd like on the server: Python, Perl, Java, Ruby, etc. My site makes very basic use of PHP (I drop into Python for anything even slightly complex) so I'll use that. Here's the code for the target page:

<?php include "standard.php"; head("Trip Information from Flex Form"); ?>

<h1>Trip Information<br>from Flex Form</h1>

<table cellspacing="20">
<?php
foreach($_POST as $key => $val)
    printf("<tr><td>%s</td><td>%s</td></tr>\n", $key, $val);
?>
</table>

<?php foot(); ?>

The first and last lines are my own creation and appear on all the pages on the site (the new site, that is; I'm in the process of moving everything over so you'll still see plenty of pages that don't conform). These establish standard header and footer information along with style sheets that can be changed centrally and reflected throughout the site.

The table is created by producing basic HTML using PHP; most of the code should look familiar to a C programmer except the foreach, which in this case takes each key-value pair from the "POST" data and divides it into $key and $value pieces, respectively.

The point is that this is the same code you'd write for any CGI request, whether it comes from a regular HTML form or our Flex form. Which means that you can evolve your site towards using Flex, picking and choosing when to use HTML forms and when to use Flex forms as the need arises.

Embedding Flash within HTML

Years ago I embedded a little Flash animation in my home page. The first thing I discovered was that it was amazingly hard to get it right, and in fact I didn't. I assumed this was because I was inexperienced with Flash, which was true. But it turns out that properly embedding Flash within HTML has been a surprisingly messy business that runs full-on into browser bugs and incompatibilities.

Here is the best article I've seen, which summarizes the situation and also describes the development of the solution, which has itself gone through significant evolution.

The best approach appears to be the use of a JavaScript library which contains all the tests and solutions necessary to deal with cross-platform and cross-browser issues. Conveniently, someone else has solved the problem in a once-and-only-once fashion so you can ignore the messy issues and "do the simplest thing that could possibly work."

The above article details the evolution of the JavaScript library; in short there was one called SWFObject, another called UFO, then the two unified into SWFFix, which has been renamed SWFObject. This makes it a little confusing; SWFObject 1.5 is the last version of the project that originally went by that name, and SWFObject 2.0 is in beta 6 as of this writing. You can download it here.

Even though it's in beta, the simplicity of SWFObject's Option 2 is exceptionally compelling so I'll use that. You just include swfobject.js, then call swfobject.embedSWF(), where you provide as arguments the SWF file and the div id where the SWF will be placed in the page, along with the width, height and minimum required Flash VM version. Within that target div, you place the alternative content; if the SWF is unable to load the alternative will be shown.

Here's what I used to embed the form into this page:


<script type="text/javascript" src="http://www.mindviewinc.com/demos/flex/swfobject.js"></script>
<script type="text/javascript">
swfobject.embedSWF("http://www.mindviewinc.com/demos/flex/Traveler.swf", "myContent", "500", "300", "9.0.0");
</script>

<div id="myContent">
  <p>The Flex form should appear here.</p>
</div>

Details of how to use swfobject can be found at that site.

If someone has JavaScript turned off they don't get any of the benefits, but if someone has JavaScript turned off AJAX won't work either; in both cases these are probably not the visitors that are going to do interesting things like make online purchases.

The Form

Here it is, live. Notice that the empty fields have red lines around them; hover your mouse over those fields and you'll see the error message. If you set the return date earlier than the departure, that will produce an error message, too.

If you fill it out properly the submit button will be enabled, and you can see the resulting web page. (You'll have to allow the popup window).

The Flex form should appear here.

Conclusion

Notice the beautiful simplicity of the result. Not only is the Flex code itself typically easier to write than the HTML that is otherwise required, you:

There's another significant improvement over HTML: robots can't detect your submit forms, so you greatly reduce spam submissions.

Personally, I will still "do the simplest thing" and have plain links and HTML forms when that level of simplicity works. However, I've avoided including things on my site that are as simple as calendar popup AJAX components because my own experience as a consumer has been so bad that I don't want to risk it. With the reliability and consistency of Flex components, however, I'll be bolder when considering richer web user interfaces, because I know the Flex approach will work without the pain and suffering.

Thanks to James Ward for his technical assistance on this article.

Talk Back!

Have an opinion? Readers have already posted 27 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Bruce Eckel adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Bruce Eckel (www.BruceEckel.com) provides development assistance in Python with user interfaces in Flex. He is the author of Thinking in Java (Prentice-Hall, 1998, 2nd Edition, 2000, 3rd Edition, 2003, 4th Edition, 2005), the Hands-On Java Seminar CD ROM (available on the Web site), Thinking in C++ (PH 1995; 2nd edition 2000, Volume 2 with Chuck Allison, 2003), C++ Inside & Out (Osborne/McGraw-Hill 1993), among others. He's given hundreds of presentations throughout the world, published over 150 articles in numerous magazines, was a founding member of the ANSI/ISO C++ committee and speaks regularly at conferences.

This weblog entry is Copyright © 2007 Bruce Eckel. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use