Porting Our Dialog to Polymer - Testing, Building, and Deploying Components with Polymer - Developing Web Components (2015)

Developing Web Components (2015)

Part IV. Testing, Building, and Deploying Components with Polymer

Chapter 15. Porting Our Dialog to Polymer

Jarrod Overson

There are 200 ways to write any one thing in browserland JavaScript, 400 ways of writing an encapsulated component, and 800 ways of porting any one component implemented within one framework to another.

We’ll go over two such ways.

Web components provide a lot. They also introduce many questions, and each one of those questions can be answered differently depending on perspective, time constraints, and development attitude. Web components, at their base, provide a layer of abstraction and push an API touchpoint to the HTML itself. This is already a win, and for a lot of people, that is the primary appeal.

At the other extreme, there are people who see web components as an opportunity to implement the next generation of building blocks that will compose the Web of the future. There are already implementations of spec proposals floating around on the Internet, and it’s not a stretch to believe that independently developed individual web components may end up as official elements existing in the actual HTML spec one day.

Neither attitude is necessarily better than the other; they are just different and excel in their own ways. With those two attitudes, though, comes a choice of tools and implementations. Do you reuse what you know, love, and are familiar with to get a job done? Or do you toss the past aside, and implement these building blocks from the ground up?

Why Port Anything to Polymer at All?

Why port anything to Polymer, or even use web components, at all? It’s an extra burden and effort, for what gain? Those are questions we all should be asking ourselves when we incorporate any new technology into our existing workflows.

Libraries, frameworks, and tools hit their peak popularity after people recognize them as filling a need better than the tools around them. Once that need is filled and the best practices are understood, these offerings become the new baseline and developers start looking forward to new holes that need patching. As new libraries start from an existing baseline of experience and solutions, they provide extra value that old libraries may or may not compare to.

The libraries, patterns, and tools you use now will remain just as useful in the future, but they may or may not progress to take advantage of new practices that may start becoming standard. This can affect maintenance, hiring of new employees, and the speed at which new features can be developed.

THE PROGRESSIVE WEB

Have you ever seen a JavaScript project managed by Ant? Or one built by Rhino? They exist, but you’d be hard pressed to find a popular project that deals with either of those technologies today.

Ant is still useful, as is Rhino, but they temporarily filled a gap that was better handled by tools that came later. Grunt and Node.js, by and large, have almost completely replaced Ant and Rhino in the JavaScript developer’s toolbox.

What’s next? Gulp became extremely popular very quickly because it leveraged a lot of the best ideas in Grunt, and iterated upon the ones that weren’t so great.

What will come after Node.js? I don’t know—there are projects attempting to bite off a piece of that pie, but it’s far too early to tell what might stick. One such project is JXcore, a Node.js fork that handles multithreading and thread recovery and is seeking to provide a JavaScript-to-llvm solution.

The Web evolves quickly, and our websites and applications never “go gold” to be left and forgotten about. The ability to maintain and adapt is what separates successful web applications from their counterparts. There is always benefit in looking forward, keeping abreast of what is becoming popular and what is losing favor.

Getting comfortable is the Achilles’ heel of software development, and doubly so for web development. If you seek to provide value and advance in your career, leaning toward the leading edge will pay off in spades. Getting set up with a web component flow now will help you going forward.

Web components are the future. There is tooling around them and there will be more, so a bet on them is one that will keep on paying out. They are not the only future, merely one rung in the ladder of progress—but it’s a strong one that leads to a new, stable platform that we will spring off of again and again.

The Direct Port

It’s relatively simple to do basic ports of your existing UI widgets to Polymer. You can reuse a lot of the logic for widgets written with libraries like jQuery, Backbone, or even Angular and Ember. This is part of the beauty of web components: they aren’t introducing anything very new; they are offering you the opportunity to augment the old.

Managing Dependencies

You’ve already seen a lot of reusable code in this book so far, and it is probably starting to feel similar to code bases you have worked on yourself. There are very few ways to effectively manage JavaScript dependencies in browserland, and there are even fewer ways to manage third-party dependencies. The two major registries for browser based JavaScript are npm and Bower, both of which have very different ways of managing their packages.

While npm, the Node.js package manager, has become very effective for some people as a way to manage browser-based JavaScript, the methods of managing it are more tied to the Node world than to traditional browser development. We won’t be covering npm-based browser code, but if you’re interested, look into tools like Browserify and Webpack; they offer excellent solutions for a specific demographic.

NOTE

npm, Inc., the company formed to manage npm, recently posted on its blog the intent to “officially” support browser-based packages in the future. While we have no idea what that really means right now, it is a very appealing message and is worth keeping track of as an alternate solution in the future.

npm and its core developers have proven to be extremely adept at providing scalable, intuitive, and attractive dependency management solutions, so this is very good news.

Bower is a tool born out of Twitter specifically intended to manage browser-based JavaScript. There is, technically, a “registry,” but it exists almost strictly as a URL-redirection service, mapping common names to destinations serving static files or Git repositories. Modules installed by Bower can specify a list of ignored files and dependencies, allowing you to automate the installation of many third-party files with one command.

One of the established patterns for reusable web components is to expect that all dependencies, components or otherwise, are flattened siblings in the immediate parent directory. This leaves a lot to be desired, but until something better comes along, it’s the standard to abide by. This means that we can and should reference dependencies using relative paths, as siblings to the dependent module.

For example, we might reference an HTML file like this:

<link rel="import" href="../another_component/dependency.html">

and a script like this:

<script src="../jquery/jquery.js"></script>

<!--

Some component that is used by our original file.

-->

The dependency directory ends up looking a lot like this:

dependency_directory

|

|-- jquery

| |-- jquery.js

| |-- jquery.min.js

| +-- jquery.js.map

|-- polymer

| |-- polymer.js

| |-- polymer.js.map

| +-- polymer.html

+-- platform

|-- platform.js

+-- platform.js.map

Conveniently, (or likely because) Bower installs modules in this exact pattern, we can use it effectively to manage our web components and their dependencies. By default, Bower installs its packages in a bower_components directory, but this can be configured via a .bowerrc JSON file installed per project or in your home directory. For example, to use the vendor directory:

{

"directory": "vendor"

}

For convenience and clarity, though, we’ll continue to use bower_components here. That you are able to configure the directory is mentioned solely because that is very often the first question about the tool from people familiar with different development environments.

Installing Dependencies with Bower

Installing dependencies like Polymer with Bower is extremely easy, but first you need to have Node.js installed—it provides you with the runtime for the tools and with the package manager, npm, that is necessary to install another package manager, Bower. Node.js is installable in several different ways, all documented on the website. To get Bower, you install it globally via npm:

$ npm install -g bower

On a standard install in a Linux-like environment like OS X or Ubuntu, you may need to sudo the command in order for it to install globally:

$ sudo npm install -g bower

Bower’s basic usage is no different from nearly any other package manager: you simply issue an install command and then Bower will go out, find the destination for your package (often somewhere on GitHub), download and cache the files, parse the bower.json file for metadata, copy the listed files over to your destination directory, and then repeat the process for all the specified dependencies. If Bower cannot resolve a dependency properly, then it will prompt you for a decision and move on from there.

Library maintainers are able to tag a version in a Git repository for Bower to use as the install version, but in practice, good version tags don’t always exist. The version you get may be based off the head of the master branch, and this can easily lead to unexpected conflicts. Bower and the “anarchaic” development environment of the Web don’t protect developers enough to let them ignore all the nitty gritty details of our frontend dependencies. As it is, it’s useful primarily as an automated installer for libraries and their dependencies, which then still get checked into a source code repository.

To install Polymer and its related dependencies, simply run the following command:

$ bower install polymer

The install command is useful to install libraries for one explicit use but by default, it doesn’t keep track of any of your installed dependencies, which will make further updates, reinstallations, and tree shaking more difficult. To get this we’ll need a bower.json configuration file, which will hold the metadata necessary to track our dependencies. We can do this nearly the same way we set up a default package.json with npm in Node.js:

$ bower init

After you’ve answered the prompts with data relevant to the project, you can save your dependencies via:

$ bower install --save polymer

This will install and save the dependencies necessary to install Polymer. Right now that will also include Polymer’s platform.js, though in the future that will hopefully be minimized or even removed. That’s part of the benefit of using something like Bower to manage dependencies: if the dependencies of your dependencies change, your installation process will manage itself accordingly.

Earlier, I mentioned that Bower and common practice dictate that all dependencies be flat and referenced via relative paths. You might have wondered how that works for local development and referencing dependencies in a maintainable way. I wish I had a great answer for you, but there are actually two (maybe three) only somewhat suitable camps on this topic right now:

1. Have your component be in the root of your development directory, with your dependencies installed as siblings in your parent directory.

This has the side effect of polluting that parent directory with dependencies from any number of components actively in development and each of their respective dependencies.

Development with dependencies would then look like this:

|-- my-element

| |-- my-element.html

+-- polymer

| |-- polymer.html

+-- platform

|-- platform.js

2. Have your component be two directories down in your development directory, with your dependencies installed one directory down:

3. my-element

4. |-- components

5. |-- src

|-- my-element.html

This has the drawback of requiring some kind of build step in order to place the distributable element at the root so that it can reference dependencies in the consuming application’s directory tree.

With dependency installation being in the components directory and the “built” artifact being at the root, the end structure looks like this:

my-element

|-- components

| |-- src

| | |-- my-element.html

| +-- polymer

| | |-- polymer.html

| +-- platform

| |-- platform.js

+-- my-element.html

TIP

The answer to the quick knee-jerk reaction “But how do I ignore those dependencies in source control?” is (for Git) to put the following in your .gitignore file:

components/*/

!components/src/

6. Don’t worry about dependencies at all, and expect the host page to manage them for you.

This is the same as classic dependency management for distributed libraries right now. Libraries are created and their dependencies listed somewhere for the consuming developers to download and manage in their web pages. This has worked for nearly two decades and can work now, but it increases the user’s overhead. It’s also 2015, we’re not cavemen, so we’re going to stick with option 2. The first is too invasive and fragile, and the third is not interesting enough to put in a book.

Getting Started

To begin, set up the source root as x-dialog and set up the source tree so that it looks like this:

x-dialog

|-- index.html

|-- components

| |-- src

| | |-- x-dialog.html

TIP

Are you on an OS X or Linux machine and find yourself making a directory that you immediately cd into frequently? Set this up in your .bashrc to make doing that a breeze:

md ()

{

mkdir -p "$@" && cd "$@"

}

Now you can compress that task into the following command:

$ md x-dialog/components/src

$ touch x-dialog.html

Now I initialize the bower metadata as described earlier, by executing bower init and filling out the appropriate answers:

$ bower init

[?] name: x-dialog

[?] version: 0.0.0

[?] description: a dialog box web component

[?] main file: ./x-dialog.html

[?] what types of modules does this package expose? globals

[?] keywords: dialog

[?] authors: Your Name <email@email.com>

[?] license: MIT

[?] homepage: http://github.com/YOURUSER/x-dialog.html

[?] set currently installed components as dependencies? Yes

[?] add commonly ignored files to ignore list? Yes

[?] would you like to mark this package as private which prevents it from being

accidentally published to the registry? No

{

name: 'x-dialog',

version: '0.0.0',

authors: [

Your Name <email@email.com>

],

description: 'a dialog box web component',

main: './x-dialog.html',

moduleType: [

'globals'

],

keywords: [

'dialog'

],

license: 'MIT',

homepage: 'http://github.com/YOURUSER/x-dialog.html',

ignore: [

'**/.*',

'node_modules',

'bower_components',

'vendor',

'test',

'tests'

]

}

[?] Looks good? Yes

We’ll also want to add a .bowerrc file so that we can specify that we want our test dependencies installed into our components directory:

{

"directory" : "components"

}

If we’re using Git, then we can set up our .gitignore file as follows:

components/*/

!components/src/

Now we can install our dependencies into our components directory and have them saved for the future. We’ll obviously need Polymer, and since we’re doing a basic port, we’ll include jQuery as well. Jenga is a dependency also available in Bower, so we can install them all in one fell swoop:

$ bower install --save polymer jquery jenga

[ ... cut some information logging ... ]

polymer#0.3.3 components/polymer

└── platform#0.3.4

platform#0.3.4 components/platform

jenga#912f71e4cc components/jenga

jquery#2.1.1 components/jquery

Now we have four libraries available with one command, all installed in our components directory. If you ls inside that directory you’ll see how the dependencies will look in somebody else’s project (aside from the src directory being called x-dialog), so this is a great way to develop and test code.

Before we jump into coding our component, let’s get our test page up and running so we can stub out our expected component API and see our work. We’ll start our index.html file with the basic few tags that compose nearly every modern HTML file:

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title><x-dialog></title>

</head>

<body>

</body>

</html>

Next we’ll add some simple references to platform.js and import our element. It’s good practice to include platform.js in our demo page even if our browser is set up for all native web components because it eases the process for anyone else getting into development with our element:

<head>

<meta charset="UTF-8">

<title><x-dialog></title>

<script src="components/platform/platform.js"></script>

<link rel="import" href="src/x-dialog.html">

</head>

Now we can add our element, with some reasonable options that provide the functionality we’re looking to test (actual unit testing is an entirely different matter, as we’ll see in the next chapter; this is for spot checking during the porting process):

<body>

<x-dialog draggable resizable></x-dialog>

</body>

That’s it! Your final index.html should look like this:

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title><x-dialog></title>

<script src="components/platform/platform.js"></script>

<link rel="import" href="components/src/x-dialog.html">

</head>

<body>

<x-dialog draggable resizable></x-dialog>

</body>

</html>

Now for the main course! There are a few Polymer boilerplate seed repositories out there, each with a slightly different style; you should look around to see what suits your fancy. For this book we’ll be using the boilerplate from Addy Osmani and other contributors, slightly modified to fit our data and dependency location:

<!-- Import Polymer -->

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

<!-- Define your custom element -->

<polymer-element name="x-dialog">

<script>

Polymer('x-dialog', {

// Fires when an instance of the element is created

created: function () {},

// Fires when the element's initial set of children and siblings

// are guaranteed to exist

domReady: function () {},

// Fires when the "<polymer-element>" has been fully prepared

ready: function () {},

// Fires when the element was inserted into the document

attached: function () {},

// Fires when the element was removed from the document

detached: function () {},

// Fires when an attribute was added, removed, or updated

attributeChanged: function (attr, oldVal, newVal) {}

});

</script>

</polymer-element>

The boilerplate already includes an import for Polymer. Now we need to include our other dependency libraries, the same way we would in any other HTML file:

<!-- Import Polymer -->

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

<script src="../jquery/dist/jquery.js"></script>

<script src="../jenga/jenga.js"></script>

Notice how we’re including these in the root of the imported HTML document, not within the template. There are reasons to do one or the other (deferred loading, managing dependencies more granularly, etc.), but the easiest way to depend on other resources is to do it at the import level, not the custom element level.

How should we include the other sources in this book? They’re written to be reusable but aren’t yet Bower resources. Should they be? Maybe, but it’s a good practice to wait until something proves to be reusable multiple times before abstracting it into a publishable package. In this case, we’ll directly include all of the libraries we’ve created as source files within this component. Since we’re also directly porting the existing logic into what essentially amounts to just a web component container, we can also bring along our existing implementation unchanged and see how we can use it within Polymer.

After copying those libraries to our src directory, our development structure now looks like this:

├── .bowerrc

├── bower.json

├── components

│ ├── jenga

│ ├── jquery

│ ├── platform

│ ├── polymer

│ └── src

│ ├── ApacheChief.js

│ ├── BaseComponent.js

│ ├── DialogComponent.css

│ ├── DialogComponent.js

│ ├── Duvet.js

│ ├── Shamen.js

│ └── x-dialog.html

└── index.html

And the preface of our component’s import.html looks like this:

<!-- Import Polymer -->

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

<script src="../jquery/dist/jquery.js"></script>

<script src="../jenga/jenga.js"></script>

<script src="Duvet.js"></script>

<script src="Shamen.js"></script>

<script src="ApacheChief.js"></script>

<script src="BaseComponent.js"></script>

<script src="DialogComponent.js"></script>

This gives us the foundation from which we started implementing the API in our original usage, selecting elements and manually “upgrading” them to dialog status. When doing a port of this level, what we’re basically doing is abstracting the manual instantiation of such libraries behind an HTML API. If we think about it this way, we can start by proxying the touchpoints of our API at the point where our elements are created and in the host DOM. We’ll also need a base DOM in our template so that we can render something useful:

<template>

<div id="dialog" role="dialog"

aria-labelledby="title" aria-describedby="content">

<h2 id="title">I am a title</h2>

<p id="content">Look at me! I am content.</p>

</div>

</template>

If you checked this element out right now you’d see a pretty unexcitingly styled bit of text on the page. True, we haven’t done anything useful yet, but during the porting process, any time a page isn’t in an entirely broken state then we’re in a good spot! Pushing forward, we know we need to style the dialog so that it is invisible by default. If we wire in our existing CSS file, we’ll find our unexciting bit of text completely invisible. Success! This porting process is easy! Now we have absolutely nothing styled on the page. We need to get something showing again, so let’s wire up our existing implementation and see if we can get it to display and style itself dynamically:

this.dialog = new Dialog({

$el: this.$.dialog,

draggable: this.attributes.hasOwnProperty('draggable'),

resizable: this.attributes.hasOwnProperty('resizable'),

alignToEl: window,

align: 'M',

hostQrySelector: this.$

});

this.dialog.show();

You’ll notice a few potentially nonobvious references there. First, our $el is pointing to Polymer’s ID tree hanging off the element on a $ property. This allows us to specify the element with the ID dialog in our template easily as this.$.dialog. This is also what we’re using for thehostQrySelector property, specifying our root as the root to query.

Second, we’re referring to the existence of our attributes draggable and resizable in order to set the Boolean values in the constructor.

Normally we wouldn’t show our dialog immediately upon attaching to the DOM, but we’re just trying to get to a midway point where we get our visual component up and beautiful.

Figure 15-1 shows what we see when we reload the page now.

dwbc 16in01

Figure 15-1. The styled dialog

Holy moly, it worked!

Well, that’s that. Book’s over. Port all your jQuery widgets in an hour, right? Well, you know what? That’s not too far off. If you’ve written well-encapsulated code, then that’s close to some version of reality. Our component isn’t quite done—we’ll take care of the finishing touches now—but the important point is that it works, and it didn’t take much effort.

First off, we’ll want to move that show call to a method on its own so that it can be accessed off the DOM element in a consumer page. Add the following as another method on the Polymer prototype, and we’re all set:

show: function () {

this.dialog.show();

}

Now to show the dialog, we can simply add the following to our index:

var dialog = document.querySelector('x-dialog');

dialog.show();

Well, that’s almost all—we’ll need to listen for the polymer-ready event in order to catch our elements when they’ve been “upgraded” by Polymer:

window.addEventListener('polymer-ready', function () {

var dialog = document.querySelector('x-dialog');

dialog.show();

})

This will do it, and can be added anywhere in the index—before the closing </body> works perfectly.

There’s one major problem with this, though. Our DOM is unchangeable, and it certainly wouldn’t make sense to make a new custom element for every dialog we might have in an application. That’s not a very good use for custom elements. What would a good API be defining the inner content of a dialog?

<x-dialog draggable resizable>

<x-title>This is my title!</x-title>

<x-content>This is the content of my dialog</x-content>

</x-dialog>

Yikes—that’s possible, but it wouldn’t make sense strictly for our element. This is one of the first traps people commonly fall into when dealing with custom components (especially when using Polymer, because it’s just so easy). Only make new elements for things that make sense as elements. Maybe title and content elements do make sense, but not specifically for our use case. Let’s try again:

<x-dialog draggable resizable>

<header>

<h1>This is my title!</h1>

</header>

<p>

This is my content

</p>

</x-dialog>

This reuses sane HTML and is much less obscure, but it also complicates the inner structure. It is viable, but what about this?

<x-dialog draggable resizable title="This is my title!">

This is my content

</x-dialog>

This is pretty lightweight and easy to understand. It comes at the cost of limited composability of the title bar, but that might not be a bad thing. To use it effectively in our template we use a mixture of {{}} binding and the <content> tag to selectively decide where to render the “light-DOM” within our component:

<template>

<link rel="stylesheet" href="DialogComponent.css">

<div id="dialog" role="dialog">

<h2 id="title">{{title}}</h2>

<p id="content"><content></content></p>

</div>

</template>

Here is what our final component looks like, trivially ported from a pure jQuery mindset into the world of web components:

<!-- Import Polymer -->

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

<script src="../jquery/dist/jquery.js"></script>

<script src="../jenga/dist/jenga.js"></script>

<script src="Duvet.js"></script>

<script src="Shamen.js"></script>

<script src="ApacheChief.js"></script>

<script src="BaseComponent.js"></script>

<script src="DialogComponent.js"></script>

<!-- Define your custom element -->

<polymer-element name="x-dialog" attributes="title draggable resizable">

<template>

<link rel="stylesheet" href="DialogComponent.css">

<div id="dialog" role="dialog">

<h2 id="title">{{title}}</h2>

<p id="content"><content></content></p>

</div>

</template>

<script>

Polymer('x-dialog', {

attached: function() {

this.dialog = new Dialog({

$el: this.$.dialog,

draggable: this.attributes.hasOwnProperty('draggable'),

resizable: this.attributes.hasOwnProperty('resizable'),

alignToEl: window,

align: 'M',

hostQrySelector: this.$

});

},

show: function() {

this.dialog.show();

}

});

</script>

</polymer-element>

That Was Easy—A Little Too Easy!

This simple process goes to show how easy it can be to embrace web components. Though, having said that, all we really did was go from a jQuery implementation of a dialog box to a jQuery implementation of a dialog box wrapped with Polymer.

There’s nothing wrong with jQuery, but I can understand some of you might have felt a little robbed there. The beautiful world of web components promises progress, large steps forward, and a hearty “goodbye” to the legacy of today. jQuery has traditionally been used to iron out the wrinkles of inconsistent, ancient browsers. Does jQuery have a place in the world of Polymer?

jQuery in a Polymer World

Any professional that relies on his or her toolbox—whether a woodworker, mechanin, or software developer—is likely to recall a time when a new tool was released that promised the world and purported to make the tools of the past irrelevant, clearing up valuable space in the workshop.

Polymer and jQuery are indeed tools, not frameworks. They don’t define how an application should be developed; in fact, they don’t even provide you with the tools necessary to make a maintainable application. Multiple tools aren’t a problem, but can be a “code smell” for browser-based applications. One of the biggest issues in browser JavaScript, with its poor (absent) dependency management and module system, is that popular tools end up having a lot of overlap in order to reduce their own dependencies on others. Including multiple tools that overlap bloats your code base and increases the distributable size.

Polymer, being more modern and able to define its path based on current best practices, tries to be more focused (though it still includes the kitchen sink when it comes to polyfills). On the other hand, jQuery includes an incredible array of features that can make it seem oversized, and the library itself is not uncommonly seen as a red flag, indicating inefficient or amateur development practices. This is a wild oversimplification, but nonetheless it has inspired attempts at creating lighterweight implementations, like Xepto.js and Minified.js.

It’s important to know what a library is offering you when you include it. Every library brings in a potentially substantial download and parse cost, and responsible developers should make sure all that code brings more value than it costs.

What Does jQuery Provide?

Do you know and use everything that jQuery provides? You should make sure. It may well all be excellent and useful, but how much of it you actually use is definitely worth thinking about.

At the least, jQuery provides:

§ An intuitive chaining API

§ Utility functions for objects, arrays, and strings

§ DOM selection

§ DOM traversal

§ DOM manipulation

§ CSS animations

§ Normalized CSS getters/setters

§ Promise/deferred implementation

§ AJAX methods

§ An event API

This is not meant to be an exhaustive list; these are just some of the most commonly used features of jQuery. It’s hard to get a good overview of what jQuery features a project uses, but once you go through the practice once, it gives you a great baseline of what types of things are common in different scenarios.

How much jQuery do you think we’ve used in the book so far? Let’s find out! The quickest way to determine the value of something is to go without it, so let’s go ahead and rip jQuery out of the dependencies and reload the page. It’ll all work, right? Of course not. We’re about to embark on a horrible exercise that everyone attempts at least once in their browser development lives.

We’re going to remove jQuery.

Removing jQuery

Of course, the first errors we get are just undefined errors because we’re trying to pass jQuery into the immediately invoked function expression (IIFE) that contains all our library code. After taking care of that, we can start stepping through the jQuery method invocations and try to port each individual one to a generic method. Remember, if we’re working with web components and Polymer, we’re already stating that we’re working with a very modern matrix of supported browsers. There are a lot of common APIs that replace jQuery methods.

We’re not going to go through all the replacements necessary, but we are going to go through the general themes and how the replacements might work.

The immediate case we run into is using jQuery to create DOM elements, style them with an object filled with CSS properties, and append one onto another:

//Duvet.js

var $inner = $('<div>test</div>').css(innerCss);

var $outer = $('<div></div>').css(outerCss).append($inner);

One thing that becomes immediately apparent is just how much code jQuery saves us from writing. That is actually a nutty amount of functionality in a miniscule number of characters.

We’re not going to work hard at implementing the best solutions for these problems right now. The goal is simply to remove jQuery, so the biggest benefit is abstracting out any changes for refactoring later. No effort is made here for cross-compatibility, edge cases, or security:

function htmlify(html) {

var div = document.createElement('div');

div.innerHTML = html;

return div.children[0];

}

The CSS method looks like it’s doing a lot of work up front, but at the end of the day, a large amount of what it’s doing is merging an object with the style property of an HTML element. We can do that easily by creating our own extend-like function:

function mergeObjects(dest /* varargs */) {

var args = Array.prototype.slice.call(arguments, 1);

args.forEach(function(obj) {

for (var i in obj) {

if (obj.hasOwnProperty(i)) {

dest[i] = obj[i]

}

}

});

return dest;

}

The preceding jQuery code then becomes:

var $inner = htmlify('<div>test</div>');

mergeObjects($inner.style, innerCss);

var $outer = htmlify('<div></div>');

mergeObjects($outer.style, outerCss);

$outer.appendChild($inner);

Not quite as terse, but entirely approachable. The bulk of the cleanups immediately thereafter amount to changing:

§ append() to appendChild()

§ References from the first collection element to the variable itself ($element[0] to element)

§ extend() to mergeObject()

§ clone() to cloneNode(true)

§ el.find() to el.querySelector() or el.querySelectorAll()

§ el.css(obj) to mergeObjects(el.style, obj)

§ el.parent() to el.parentElement()

The first major hiccup comes in the event binding for ApacheChief.js and Shamen.js. These use event namespaces, which is an entirely separate concept added by jQuery that allows you to group events by application or widget for easy unbinding or querying later. Without this, you’ll need to more carefully manage your bindings in order to prevent memory leaks and odd behavior. It’s certainly doable, but it’s a really nice feature. Event handling in JavaScript is a very sensitive proposition prone to human error, and a management framework provides a lot of benefit—not so much because events are necessarily hard to create or unbind, but because a framework provides an interface that proxies all that interaction so the event handlers can be referenced and dereferenced automatically later.

Pushing through for the sake of the exercise, we come across a few more simple things to change:

§ width() to innerWidth()

§ Ensuring that assignments to CSS position integers end with px

The last serious issue that we come across is getting the positions of elements. JQuery’s method el.getPosition does more work than we can easily replace, so we can modify the original jQuery source to provide us with a workable shim:

function getPosition(elem) {

var offsetParent, offset,

parentOffset = { top: 0, left: 0 };

offset = elem.getBoundingClientRect();

// Fixed elements are offset from window (parentOffset = {top:0, left: 0},

// because it is its only offset parent

if ( elem.style.position !== "fixed" ) {

// Get *real* offsetParent

offsetParent = elem.offsetParent;

// Get correct offsets

offset = {

top: offset.top + window.pageYOffset -

document.documentElement.clientTop,

left: offset.left + window.pageXOffset -

document.documentElement.clientLeft

};

// Add offsetParent borders

parentOffset.top += offsetParent.style.borderTopWidth = true;

parentOffset.left += offsetParent.style.borderLeftWidth = true;

}

// Subtract parent offsets and element margins

return {

top: offset.top - parentOffset.top - elem.style.marginTop,

left: offset.left - parentOffset.left - elem.style.marginLeft

};

}

After this our element is dependency-free outside of Polymer. We are resizable, draggable, and showable!

As we’ve already seen, we could even remove Polymer to get a web component that relied on no outside code. That’s kind of fun sounding isn’t it? We have a brand new HTML element with 100% pure JavaScript, and it wasn’t even that hard. But… have you tried to load that component in Firefox? Or Safari? Or Android? What about the memory leaks we neglected to account for in the HTML creation? What about everything else?

The Verdict on jQuery

JQuery gets a lot of flak because a lot of its methods mask seemingly trivial tasks that have their own native counterparts. What people often forget is that jQuery excels at leveling the playing field, ironing out cross-browser issues, providing consistent APIs for inconsistent browser methods, and providing excellent wrappers for difficult or frustrating aspects of the DOM.

You can absolutely work without jQuery, but the simple fact is that you’ll probably want something in its place to provide you with the utility, consistency, and compatibility jQuery offers. If you resign yourself to needing something, then you can choose an existing alternative or attempt to create and maintain one yourself. At first glance, an internally maintained implementation sounds perfectly reasonable, but as you round each corner and find a new mountain of browser bugs to accommodate, the task quickly becomes a frustrating beast. In early 2014 Rick Waldron put together a quick roundup of all the cross-browser bugs that jQuery accounts for, and it is anything but light reading. Certainly, a lot of these issues are for browsers that wouldn’t be in a support matrix for a website implementing web components, but there are some that would. Regardless, it is a certainty that jQuery will continue to fix bugs found in future browsers, so you can imagine the list shrinking in some areas of legacy support but growing to account for new bugs.

There is a place for jQuery, both now and in the future. If your code base makes substantial use of jQuery, don’t worry about it. If you want to write a web component and find the need for utilities that jQuery will provide, you’re not hurting much by depending on it.

Summary

In this chapter we learned how to incorporate current philosophies behind widget creation into the wild world of Polymerized web components. We also learned that a lot of our existing ideas are still relevant and that Polymerized web components are an excellent technology to work into existing code in a piecemeal fashion. Polymer provides intuitive hooks inward and a valuable interface outward that can benefit any encapsulated widget that you’ve written in the past.

We also learned that jQuery, while often jeered at for being a relic of the past, still includes valuable logic in a familiar package. Polymer doesn’t replace jQuery, and even if you have a tremendous amount of code wrapped up in jQuery plugins, you can still benefit from Polymer and web components.