Our Blog

Let us find tech solutions together

Feb 01

Integrating HTML5 and Wicket

By kinabalu | Comments

 

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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:

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    @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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    @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("
<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("
");

            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!