Integrating HTML5 and Wicket

by kinabalu on February 1, 2010

After following some of the debates raging about Apple’s new iPad and the future of Adobe’s Flash, the discussion usually turned to the coming future of HTML5.

Seeing as we love Apache Wicket at Mystic, I thought I’d tinker around to see how hard it would be to start adding some support for the new HTML5 tags. There are quite a few examples out there that show off canvas, geolocation, storage, and of course video and audio.

First thing I set about doing, was to define the video tag. It takes an optional src attribute among others, or multiple source tags for offering up different video streams for the browser to choose from. Firefox uses Ogg Vorbis, and Safari uses H.264, so of course, the browser vendors still don’t agree. Here’s some code to use what I’d want to see from a video component:

        final List<MediaSource> mm = new ArrayList<MediaSource>();
        mm.add(new MediaSource("/dizzy.mp4", "video/mp4"));
        mm.add(new MediaSource("/dizzy.ogv", "video/ogg"));
 
        IModel<List<MediaSource>> mediaSourceList = new AbstractReadOnlyModel<List<MediaSource>>() {
            public List<MediaSource> getObject() {
                return mm;
            }
        };
 
        add(new Html5Video("dizzy", mediaSourceList) {
 
            @Override
            protected boolean isControls() {
                return true;
            }
 
            @Override
            protected boolean isAutoPlay() {
                return true;
            }
        });

We’ve defined a custom Object for use with our new Html5Video component, and it will hold the appropriate attributes we would need to output either a src attribute or a source tag. You can also see from this example that we’ve got a few booleans we’re overriding by default, and more available in the actual implementation. Let’s take a look at the Html5Video component:

public class Html5Video extends Html5Media {
 
    public Html5Video(String id, IModel<List<MediaSource>> model) {
        super(id, model);
    }
 
    protected int getWidth() { return 0; }
 
    protected int getHeight() { return 0; }
 
    @Override
    protected void onComponentTag(final ComponentTag tag) {
        if(getWidth()>0) {
            tag.put("width", getWidth());
        }
 
        if(getHeight()>0) {
            tag.put("height", getHeight());
        }
 
        super.onComponentTag(tag);
    }
 
    protected String getTagName() {
        return "video";
    }
}

So you can see we’ve abstracted this out even further into an Html5Media object which we’ll look at shortly. For now, we have width and height which are specific to just the video tag. And we’re also overriding onComponentTag to throw those attributes into the video tag if they aren’t zero. We also steal from some ideas in wicket core, and implement a method in Html5Media to checkComponentTag based on the results of a method that can be overridden getTagName.

Let’s take a look at Html5Media which is where we’ll find most of the meat:

public class Html5Media extends WebMarkupContainer {
 
    private IModel<List<MediaSource>> sources;
 
    public Html5Media(String id, final IModel<List<MediaSource>> model) {
        super(id, model);
        this.sources = wrap(model);
        add(new Html5UtilsBehavior());
    }

First thing we see, is we’re extending WebMarkupContainer, basically because our component can have body text (useful for fallback support). Next you’ll see that we’re adding a behavior Html5UtilsBehavior. The basic purpose is to header contribute a useful javascript file when working with browsers that don’t yet support HTML5 (Internet Explorer I’m looking at you!). Some more code:

    @Override
    protected void onComponentTag(final ComponentTag tag) {
        String tagName = getTagName();
        if (tagName != null) {
            checkComponentTag(tag, tagName);
        }
 
        List<MediaSource> sources = getSources();
 
        if (sources != null && sources.size() == 1) {
            MediaSource source = sources.get(0);
            tag.put("src", source.getSrc());
        }
 
        if (isAutoBuffer()) {
            tag.put("autobuffer", true);
        }
 
        if (isAutoPlay()) {
            tag.put("autoplay", true);
        }
 
        if (isLoop()) {
            tag.put("loop", true);
        }
 
        if (isControls()) {
            tag.put("controls", true);
        }
 
        // Default handling for component tag
        super.onComponentTag(tag);
    }
 
    protected String getTagName() {
        return null;
    }

Here we check the component tag to ensure it is the acceptable name. Then if we only have a single source, we add this to the video tag instead of separate elements in the body. The next bunch of statements pull from methods and add boolean attributes to the tag if they are true. And we provide an implementation of getTagName that returns null as a sensible default.

onComponentTagBody is where we optionally will define source tags and the optional attributes that go along with it:

    @Override
    protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) {
 
        List<MediaSource> sources = getSources();
 
        if (sources != null && sources.size() > 1) {
            final AppendingStringBuffer buffer = new AppendingStringBuffer();
            for (int index = 0; index < sources.size(); index++) {
                final MediaSource source = sources.get(index);
                buffer.append("\n<source ");
                buffer.append("src='");
                buffer.append(source.getSrc());
                buffer.append("'");
                if (source.getType() != null) {
                    buffer.append(" type='");
                    buffer.append(source.getType());
                    buffer.append("'");
                }
                if (source.getMedia() != null) {
                    buffer.append(" media='");
                    buffer.append(source.getMedia());
                    buffer.append("'");
                }
 
                buffer.append(" />");
            }
 
            buffer.append("\n");
 
            getResponse().write(buffer.toString());
 
        }
        super.onComponentTagBody(markupStream, openTag);
    }

Here we’re ensuring things aren’t empty, and then if we have more than one source element (often the case for compatibility between Firefox and Safari), we’ll output each source tag.

We’ve also gone through the trouble of adding an implementation of Html5Audio which consisted of overriding the getTagName method and returning audio. Pretty simple stuff.

When we put our example into place, we get a video with controls like so:

So what’s next? If you download the project available and linked below, it also contains an example of using the audio component. The Html5UtilsBehavior gives us the ability to CSS style the new HTML5 tags even with Internet Explorer, so our code can be more semantic instead of littering it with div’s for lack of an alternative. There are a ton more interactions and behaviors that can be added to support video and audio, support for canvas, postMessage, storage, Web Database. Web Workers, geolocation, Content Editable, etc. I have no reason to think any of these would be impossible to integrate into a sensible component with Wicket.

If you’d like to download the example and run it locally, or take a look at the components written, I’ve started a project over at Google Code called wicket-html5. Contact me if you’d like to contribute and start hacking away at some of these components.

To infinity, and beyond!

Similar Posts:

Share this:
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • DZone
  • Slashdot
  • Technorati

{ 4 comments… read them below or add one }

rimNo Gravatar April 28, 2010 at 6:53 am

nice …

daveNo Gravatar July 19, 2010 at 2:04 pm

awesome dude!! ty

TGNo Gravatar August 25, 2010 at 8:20 am

Umm – Just because you can put HTML5 tags into the markup doesn’t make it so. Where is the proper html5 doctype ? For that matter – I have yet to see a properly validated Wicket page. A bug supposedly ‘resolved’ on the userlist but thats it. Would like to get into wicket but its pages dont seem to validate and its urls with appended jsession ids are easily exploited in a production app. Hoping someone can set me straight on this…

kinabaluNo Gravatar August 25, 2010 at 8:55 am

If you want your HTML5 page to have the HTML5 doctype, just add it. Nothing stopping you with Wicket. And Wicket pages validate just fine, what problems did you experience with this? They are perfectly standard HTML pages so if they don’t validate, you added something nonstandard to them in your app. And jsessionid’s are most likely a product of the container you’re running under being mismanaged, any J2EE app will add these if it can’t use cookies to save a user’s session.

Leave a Comment

Previous post:

Next post: