Searchbox in Web Components (Part 2)

In my last article I went through what was actually the hard work of getting going with Web Components. All I’ve got left now is to wire up the logic for showing off the search box.

To do this, I did have to do a little bit of adjustment to my code that is already present. Firstly, Polymer gives you an easy way to access your shadow DOM elements. Any element with an ID is registered inside of Polymer({...}) in the variable this.$ – this allows you to easily access elements. I’ve altered the HTML part of my template to look like this:

        <div class="iconbox">Click me!</div>
        <div id="searchbox" class="searchbox" style="display: none;">
            <div class="search">
                <input type="search" name="search" placeholder="Search...">
            </div>
        </div>

Wait though! IDs are unique – you shouldn’t use them normally, and especially in reuseable components right? Well, in the case of web components, that’s ok. The shadow DOM is insulated from the rest of the DOM so IDs can be re-used. They still have to be unique within your DOM, but you can use them. In fact, Polymer recommends that you wrap your entire component inside of a DIV with an ID so you can easily access it.

Now, back to that Polymer function. You pass Polymer() on object with your code in it. Some properties have special meaning, and you will find a complete list on the Polymer website. They deal primarily with lifecycle events like when someone inserts or removes your element into the regular (or “light” to contrast it with “shadow”) DOM. There is a mechanism in there to specify which events you are interested in:

        Polymer({
            eventDelegates: {
                click: "clickHandler"
            },

            clickHandler: function (event, detail, sender) { ... }
        }); 

Here I am registering the clickHandler to be called when the <my-searchbox> element is clicked. Note that I am not registering the clickHandler on the iconbox with this. It’s very different code if I want to do that:

Polymer({
    ready: function() {
        var iconbox = this.$.iconbox;

        iconbox.addEventListener("click", this.clickHandler.bind(this), false);
    }
});

In this version I need to give my iconbox DIV an ID of iconbox so I can access it. Then I add an event listener in the standard way. The ready() event handler is a standard event handler that fires when the polyment-element has been fully prepared (with shadow DOM created, observers registered and so on). Finally, Polymer recommends a declarative mode – instead of eventDelegates, change your polyment-element to read:

<polymer-element name="my-searchbox" on-click="{{clickHandler}}">

All three mechanisms will work in this instance. If I were writing something more serious, I would probably use the eventDelegates to add an event handler to my element and the ready event handler to add an event handler to something within my element. Using eventDelegates keeps the definition of the event handler with the code for handling that event, which I like.

On to my event handler. It’s actually relatively simple now:

        Polymer({
            eventDelegates: {
                click: "clickHandler"
            },

            clickHandler: function (event, detail, sender) {
                var searchBox = this.$.searchbox;

                if (searchBox.style.display !== "none") {
                    searchBox.style.display = "none";
                    return;
                }

                var position = event.currentTarget.getBoundingClientRect();
                console.log("Position = %o", position);
                var left = position.left - 22;
                var top = position.bottom;
                var width = 350;

                searchBox.style.display = "block";
                searchBox.style.position = "fixed";
                searchBox.style.width = width.toString() + "px";
                searchBox.style.top = top.toString() + "px";
                searchBox.style.left = left.toString() + "px";
                searchBox.querySelector("input").focus();
            }
        });

It’s simpler because I don’t have to set up new elements within my shadow DOM. In the prior examples, the search box “pollutes” the DOM, creating an element that must be managed. In order to do that, I needed to create a new DIV element and give it a name, then destroy it again when I didn’t need it. Since I’m operating in a shadow DOM now, I can leave that DIV there all the time.

So there you have it – a nice Web Component version of the Searchbox to add to the collection.

Some final thoughts on Web Components. I find tooling a problem and this is one area where I think I will be experimenting with WebPack to ease my pain. I like to write CSS code in LESS and Javascript in ECMAScript 6, but these require preprocessing. Aside from this, Web Components look like the wave of the future and frameworks like Aurelia look to take advantage of it.

One of the downsides of Web Components is lots of little requests for HTML files. Every component you use requires another round-trip request to the server. If you separate out a stylesheet or script, then that’s another 1 or 2 round trips to the server. All these add up to delay in loading your page. Right now, it’s not clear to me how many you would need to handle a complete application or how it would affect your page load times. However, this is an area to keep an eye on. All the more reason for getting some tooling going to link the HTML, Javascript and Stylesheet together!