Searchbox in Web Components (Part 1)

I’ve recently been investigating web frameworks. My eyes glazed over as I saw post after post of religious wars on which one was better – Angular? Backbone? Ember? Mootools? React. Woi! There are just too many and none of them play nice with the others. It would take a major effort to even become passingly familiar with them to a point of being able to make a valued judgement on which one is better. Also, migrating from one to another – major pain. Everyone plays the “I’m not standard and lock you in” game.

Then I came across Aurelia. It had ES6 and Web Components. I am familiar with ECMAScript 6 but Web Components was new to me, so I started digging. I came across the specification, tucked away in the corner of the Internet. Web Components is a standard?

Well, it’s a W3C draft and these days that’s about all it takes for people to start coding. Here was something produced within the W3C track that would do the same thing as all the frameworks potentially, or at least make it easier to write web applications in semantic web and with reuseable components. So I set about creating a web component.

My first efforts did not go so well, but I came across Polymer produced by the fine folks at Google – a library that makes it easier to build web components. There are others (ref: Mozilla X-Tag and Bosonic). Bonus – there is a growing library of Web Components distributed via Bower or NPM that you can re-use easily.

Did I mention re-use? Web Components are designed to be re-used. They are little chunks of HTML, CSS and Javascript that play nice together in a certain way so that you can just import them and use them. For example, here is my entire web page now:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Search Page via WebComponents</title>
    <script src="lib/webcomponentsjs/webcomponents.js"></script>
    <link rel="import" href="elements/my-searchbox.html">
</head>
<body>
    <header>
        <ul>
            <li>
                <my-searchbox>
            </li>
        </ul>
    </header>
</body>
</html>

I could have made it smaller, but this is cool. Where is all the code? Wrapped up in that elements/my-searchbox.html. Let’s start at the beginning. First off, only Chrome and Opera really support Web Components right now. Firefox is coming along and IE, well – not so much. You can check out current support on webcomponents.org. So you need a polyfill or shim for this. That is produced by the same team that produces Polymer (and one of the reasons I used Polymer for my test).

Web Components only work over a HTTP transport (you cannot just load the file in the browser). Fortunately, web servers are really easy to set up these days. For this test I created a new ASP.NET 5 Empty Project. My project.json looks like this:

{
    "webroot": "wwwroot",
    "version": "1.0.0-*",
    "dependencies": {
        "Microsoft.AspNet.Server.IIS": "1.0.0-beta3",
        "Microsoft.AspNet.StaticFiles": "1.0.0-beta3",
        "Microsoft.AspNet.Diagnostics": "1.0.0-beta3",
        "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-beta3"
    },
    "frameworks": {
        "aspnet50": { },
        "aspnetcore50": { }
    },
    "bundleExclude": [
        "node_modules",
        "bower_components",
        "**.kproj",
        "**.user",
        "**.vspscc"
    ],
    "exclude": [
        "wwwroot",
        "node_modules",
        "bower_components"
    ]
}

My Startup.cs looks like this:

using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Diagnostics;
using Microsoft.Framework.DependencyInjection;

namespace WebComponents
{
    public class Startup
    {
        // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseBrowserLink();
            app.UseErrorPage(ErrorPageOptions.ShowAll);
            app.UseStaticFiles();
        }
    }
}

I’ve added a bower.json file since Polymer is distributed via bower:

{
    "name": "WebComponents",
    "private": true,
    "dependencies": {
        "polymer": "~0.5.5",
        "webcomponentsjs": "~0.5.5"
    },
    "exportsOverride": {
    },
    "overrides": {
        "polymer": {
            "main": [ "*.js", "*.html" ]
        }
    }
}

I’ve also got a Gulpfile.js for building my libraries out in wwwroot:

var gulp = require("gulp"),
    bower = require("main-bower-files"),
    del = require("del"),
    path = require("path");

var webroot = "wwwroot",
    libroot = path.join(webroot, "lib");

gulp.task("build:libraries", function () {
    return gulp.src(bower(), { base: "bower_components" })
        .pipe(gulp.dest(libroot));
});

gulp.task("build", [
    "build:libraries"
]);

gulp.task("clean", function (cb) {
    del([
        path.join(libroot, "**")
    ], cb);
});

gulp.task("default", ["build"]);

Finally, I’ve got an NPM package.json file that looks like this:

{
    "version": "1.0.0",
    "name": "WebComponents",
    "private": true,
    "devDependencies": {
        "gulp": "~3.8.11",
        "del": "~1.1.1",
        "main-bower-files": "~2.6.2"
    }
}

Yes, I wish that Visual Studio had a project called “ASP.NET 5 Empty Project, but not quite that empty”. Maybe I’ll make one for my preferred tooling. Back to the problem at hand. In my wwwroot directory, I created an index.html file with the contents I showed at the beginning of the article. Let’s take a first look at the my-searchbox.html file, which is located in wwwroot/elements:

<link href="/lib/polymer/polymer.html" rel="import">

<style shim-shadowdom>
    html /deep/ my-searchbox {
        display: inline-block;
        font-size: 28px;
        width: 42px;
        height: 42px;
    }
</style>

<polymer-element name="my-searchbox">
    <template>
        <div class="iconbox"></div>
        <div class="searchbox">
            <div class="search">
                <input type="search" name="search" placeholder="Search...">
            </div>
        </div>
    </template>
    <script>
        Polymer();
    </script>
</polymer-element>

Not much to go on right now. If you run this project you will see some interesting things. Firstly, a screen shot:

webcomponent-screen-1

Bring up the Developer Tools with F12 and click on the COG in the top right corner. This brings up the Settings. You want to find the setting Show user agent Shadow DOM and ensure it is checked before you go further.

enable-shadowdom

This enables you to see the shadow DOM, which (if you have read the specs) is where our component contents are. You won’t see them normally. A person who is using Developer Tools normally will only see the <my-searchbox> tag and nothing else. Once you expand everything, you can see the following:

webcomponent-screen-2

Note the #shadow-root tag – that’s where our component is hiding. Now you can expand it and see the detail:

webcomponent-screen-3

We’ve still got some work to do here. The style information is embedded into the web component, so I have to do all that work to get it shown. I’m sure there is a way to integrate less and ECMAScript 6 into the whole build process, but that’s a post for another day. Right now I’m going to do it the long way – by copying the raw CSS and modifying it for my usage. I’ve added the search-32.png file to the same directory as my web component so this works:

<link href="/lib/polymer/polymer.html" rel="import">

<style shim-shadowdom>
    html /deep/ my-searchbox {
        display: inline-block;
    }
</style>

<polymer-element name="my-searchbox">
    <template>
        <style>
            .iconbox {
                cursor: pointer;
                height: 42px;
            }

            .searchbox {
                background-color: white;
                border: 1px solid #C0C0C0;
                border-radius: 8px;
                box-shadow: 2px 2px 2px 2px #999999;
            }

                .searchbox:before {
                    content: '';
                    position: absolute;
                    left: 20px;
                    top: -16px;
                    height: 0;
                    width: 0;
                    border-left: 16px solid transparent;
                    border-right: 16px solid transparent;
                    border-bottom: 16px solid #C0C0C0;
                }

                .searchbox .search {
                    background: url("search-32.png") no-repeat;
                    padding-left: 40px;
                    height: 32px;
                    margin-top: 8px;
                    margin-left: 8px;
                }

                    .searchbox .search:before {
                        content: '';
                        position: absolute;
                        left: 22px;
                        top: -14px;
                        height: 0;
                        width: 0;
                        border-left: 14px solid transparent;
                        border-right: 14px solid transparent;
                        border-bottom: 14px solid white;
                    }

                    .searchbox .search input {
                        font-size: 18px;
                        border: 0;
                        margin-top: 2px;
                        color: #666666;
                        outline: 0 !important;
                    }
        </style>
        <div class="iconbox">Click me!</div>
        <div class="searchbox" style="display: none;">
            <div class="search">
                <input type="search" name="search" placeholder="Search...">
            </div>
        </div>
    </template>
    <script>
        Polymer();
    </script>
</polymer-element>

That’s a lot of style work and no help from less or sass either. I’ve also got no code in this example yet. Polymer-based web components get initialized and handled slightly differently than other components. That will be the topic of my next post, so stay tuned.