Building with Polymer and Web Components Using a DB of Pokémon Characters
I’ve been recently experimenting with Polymer to get a feel for what it was all about. While it’s still experimental and only available currently in some of the browsers, it’s also among the more innovative technologies to come to the web in recent memory.
Project by Dave Voyles on GitHub
- Source code: GitHub
- Live demo: http://pokémonwebaudio.azurewebsites.net/
Web Components usher in a new era of web development based on encapsulated and interoperable custom elements that extend HTML itself. Built atop these new standards, Polymer makes it easier and faster to create anything from a button to a complete application across desktop, mobile, and beyond.There are four specifications that form web components. All are under consideration in the new Microsoft Edge browser too (you can see this currently at http://dev.modern.ie/platform/status/ and even vote that you want engineering to prioritize it higher):
- Custom Elements: enabling the author to define and use new types of DOM elements in a document.
- Shadow DOM: combining multiple DOM trees into one hierarchy and how these trees interact with each other within a document, thus enabling better composition of the DOM.
- HTML Imports: include and reuse HTML documents in other HTML documents.
- HTML Templates: declare fragments of HTML that can be cloned and inserted in the document by script.
Why is This Useful?
Have you ever added a framework or library to your web project, only to find that the CSS overwrote some of your styles? Now, you’ve got to edit the order that the styles are loaded, as well as make changes to your own code. It’s time consuming and frustrating, to say the least. Shadow DOM offers reusable components. All of your CSS and markup are scoped to the host element, so the styles defined inside of the Shadow Root (your polymer element) will not affect the rest of the page. You can find an excellent overview of how it works at WebComponents.com.What about Browser Support?
Image courtesy of http://webcomponents.org/ As mentioned you can view the Microsoft Edge roadmap at dev.modern.ie/platform/status/ to see new and historical support for Internet Explorer. Microsoft updates the site frequently to illustrate what is coming down the pipeline and what developers are saying about it. Alternately, caniuse.com is a great resource for seeing what support looks like across the board, including mobile browsers. This site, however, does not display whether or not the browser in question currently has the technology in consideration or under development – you’ll have to put those two together.Are There Any Competing Technologies?
Firefox and Chrome have been duking it out for some time now in terms of support for these various web components. Firefox has its own technology X-tag, which only depends on Custom Elements, and gives the developer the ability to opt-in for Shadow DOM. Here is a great comparison of the two.Finding a Good Starting Point
To get a feel for how polymer works, I first wanted to use some web components that other people had made. In this case I started by browsing the CustomElements.io page in search of some interesting things to learn from and use. The x-Pokémon web component and voice-elements web component immediately grabbed my attention. With those two, I could easily parse the Pokémon database and draw the images of Pokémon on screen, then use voice elements to say the name of the Pokémon! After tinkering with those, I decided that it was time for me to go off on my own and create my first web component.Creating my Own Component
Polymer Designer is a fantastic browser-based tool made by Google, which allows you to design a paper element inside of the browser with their drag-and-drop GUI. Even better — it spits out the code for you! This proved to be invaluable when I was learning how polymer worked. I wanted to design something simple, so I created a container to hold three radial buttons; one button for each language selection. Users now have the ability to select a radial button associated with a language (en-EN, en-GB, and es-ES), which would in turn be tied into the audio web component and change the accent. The “Change the accent” text isn’t part of the polymer element. It’s part of the HTML of the page, but I wanted to include it in this snippet so that you understood what these buttons do. After laying out my content, here is what designer spat out:<link rel="import" href="bower_components/polymer/polymer.html" >
<link rel="import" href="../paper-radio-group/paper-radio-group.html" >
<link rel="import" href="../paper-radio-button/paper-radio-button.html">
<polymer-element name="x-radial-buttons">
<!-- Shadow DOM -->
<template>
<style>
#paper_radio_group {
position: relative;
width: 440px;
height: 50px;
}
</style>
<paper-radio-group selected="English-GB" valueattr="label" selectedindex="1" id="paper_radio_group">
<paper-radio-button label="English-US" id="paper_radio_btn_en-US" on-click="{{ changeAccentUS }}"></paper-radio-button>
<paper-radio-button label="English-GB" id="paper_radio_btn_en-GB" on-click="{{ changeAccentGB }}"></paper-radio-button>
<paper-radio-button label="Spanish" id="paper_radio_btn_es-ES" on-click="{{ changeAccentES }}"></paper-radio-button>
</paper-radio-group>
</template> <!-- /Shadow DOM -->
<script>
Polymer({
})
</script>
</polymer-element>
Paper Elements
You may also notice that these elements make use of paper. It took me a bit to understand what paper was exactly, because if you try to Google it, you likely won’t get what you expect. To add this element to my project, I also needed to add Google’s paper elements. As I understand it, paper elements are Google’s material design brought to the web. To install paper elements, run the following in your command line:bower install Polymer/core-elements --save
Note for Visual Studio users: I’ve found that Bower can throw errors when trying to use the command line tools within Visual Studio. A post on Stack Overflow clarified that I should use a separate command line tool, such as PowerShell, to install Bower packages. Once I did that, everything worked well. I changed directories by with cd “name of directory where my web project is” to have PowerShell pointed towards my project, and then I could use all of the Bower commands to install components. Ex: cd C:\Users\Dave\Documents\Projects\Pokémon-WebAudio
Adding JavaScript to the Polymer Element
I had my HTML and CSS complete, but I needed to start wiring up my JavaScript. It took me a bit of time to understand how Polymer handles its JavaScript, as they have a ton of documentation on the subject, but not many pracitcal examples. Note, they have many examples, but it basically turns to “hello world” without any substance. They go over JavaScript in detail in the API developer guide, but it was Stack Overflow which really proved to be useful here. For example, I’m still not exactly sure how entering code intoPolymer{()}
is different from entering it in the <script> tag. Below is the JavaScript that I use in my Polymer element:
<script>
(function () { // IIFE makes attributes global
/* -- Global Attributes ------------------------------------------------ */
var currentAccent = 'en-US';
var accentArr = ['en-US', 'en-GB', 'es-ES'];
Polymer('x-radial-buttons', {
/* -- Methods --------------------------------------------------- */
getCurrentAccent: function () {
return currentAccent;
},
/* Easily grab different elements from the Shadow DOM, and expose them to other .js files.
* ex: document.querySelector("x-radial-buttons").getFirstElement();
*/
getFirstElement: function () {
return this.$.paper_radio_group.querySelector("#paper_radio_btn_en-US");
},
getSecondElement: function () {
return this.$.paper_radio_group.querySelector("#paper_radio_btn_en-GB");
},
getThirdElement: function () {
return this.$.paper_radio_group.querySelector("#paper_radio_btn_es-ES");
},
changeAccentUS: function(event, detail, sender) {
var accent = accentArr[0];
PokémonApp.changeAccent(accent);
},
changeAccentGB: function() {
var accent = accentArr[1];
PokémonApp.changeAccent(accent);
},
changeAccentES: function () {
var accent = accentArr[2];
PokémonApp.changeAccent(accent);
}
});
})();
</script>
The only reason I knew that wrapping my attributes in an IFFE made them global is because that was something on Stack Overflow. Without that, I wasn’t sure of how to access them from outside of the Polymer element.
Most of the methods here are not being used — I simply wrote them as a way to test things out and debug as I was moving along. For example, I illustrate how to return the first element in my polymer element, which is essentially encapsulated hidden from the browser via Shadow DOM.
That syntax looks like:
this.$.paper_radio_group.querySelector(#paper_radio_btn_en-US");
Sure, the syntax is kind of funky, in that you need to use the this keyword followed by the $ symbol, and then the name of the top level object, which then exposes the ID of the object you are trying to actually grab.
How Does it All Work?
Now that I understood how Polymer elements work, I was able to dissect the work of others, and now create my own, it was time to put it all together. I started with HTML5 Boilerplate, which I cannot recommend enough, as it provides an excellent starting point for most web projects. Out of the box it includes libraries such as jQuery, Modernizr, Bootstrap, and Normalize.css, among many others. Next, I use the Bower command line to add the x-Pokémon and Web Speech components.Inside of index.html
Opening this page, we see that I have some text, followed by my radial button element that I created in Polymer. <article>
<header>
<h1>Gotta say 'em all! </h1>
<p>This web app takes advantage of Web Components and Polymer to enable new HTML features in the browser.</p>
<p>
In this particular case, we are using <a href="https://github.com/passy/x-Pokémon" target="_blank">
the x-Pokémon web component </a> to pull the images from a database, as well as the
<a href="http://zenorocha.github.io/voice-elements/" target="_blank">voice-elements web component</a> to speak the name of the Pokémon we entered.
</p>
<p>
Enter the name of a Pokémon, watch it display on screen, then press the <b>Speak</b> button to hear the pronounciation.
</p>
<p>Not sure of a name? You can find a bunch of them here: <a href="http://Pokémondb.net/sprites" target="_blank">PokémonDB</a></p>
<h2>Change the accent</h2>
<x-radial-buttons id="radButtons"></x-radial-buttons>
</header>
</article>
index.html has no clue of what that tag is though, at least until I import it at the botton of the page. Therefore, add this line to the botton of index.html:
<link rel="import" href="bower_components/radial-buttons/x-radial-buttons.html">
I repeated these steps for the two components listed above.
<!-- Container for Web Components content -->
<aside>
<h3>Enter the name of a Pokémon!</h3>
<form id="player-form" class="pure-form">
<input id="player-input" type="text" value="pikachu">
<button id="player-submit" class="pure-button pure-button-primary">Speak!</button>
<button id="btn-change-accent" class="pure-button pure-button-primary">Change Accent</button>
<!-- Text-to-speech -->
<voice-player id="player-element" accent="es-ES" text=""></voice-player>
</form>
<!-- Draw Pokémon here -->
<x-Pokémon id="x-Pokémon" name="pikachu"></x-Pokémon>
</aside>
Tying the JavaScript Together
With our HTML complete, we now need to wire everything up. In main.js I have two (2) event listeners. One listens for when the text has changed in the input form and performs two operations. After each character, the Pokémon component (with Angular) quickly searches the Pokémon database to see if such a Pokémon exists, and if it does, draws it to the screen. In addition, this also sets the text inside of the audio web component. Here’s the code:input.addEventListener('input', function (e) {
playerElement.setAttribute('text', input.value);
xPokémon.name = input.value;
});
The other listens for when the submit button is pressed, and when it is, calls the function sayTheName. We prevent the default functionalty of the submit button from firing, which would actually submit a form on the page. Instead, we tell the audio component to speak (using the text we set in the previous listener).
form.addEventListener('submit', function (e) { window.PokémonApp.sayTheName(e) });
// Say the text when button is pressed
PokémonApp.sayTheName = function (e) {
e.preventDefault();
playerElement.speak();
console.log(radialButtons.intValue);
};
That’s all fine and good, but we need to add in my functionality, though.
I have a function called changeAccent, which grabs the current accent from the radialButtons I created, then does a console.log to state what the current accent is, then illlustrate what it is about to be changed into. The next line called the audio web component, finds the “accent” attribute in the HTML tag, and sets it to the new accent we passed in from the radial buttons I created.
/*x-radial-buttons.html*/
var currentAccent = 'en-US';
var accentArr = ['en-US', 'en-GB', 'es-ES'];
getCurrentAccent: function () {
return currentAccent;
},
/* Main.js*/
//@Caller: X-radial-buttons web component
//Setting the accent based on the radial button the user selected
PokémonApp.changeAccent = function (accent) {
var currentAccent = radialButtons.getCurrentAccent();
console.log("current accent is: " + currentAccent + ". " + "Changing accent to: " + accent);
var Newaccent = playerElement.setAttribute("accent", accent);
};
Final Notes on the Project
This currently only works in Chrome, as it not only supports the four items listed above which make up Web Components, but it also has support for Web Speech, which is currently under consideration in Microsoft Edge and experimental in Firefox. I’m working on creating a side project which has this Pokémon web app, but removes the dependency on Web Speech. I should have that up soon. Introduced late in 2012, The Web Speech API, allows for speech input and text-to-speech output features in a web browser. Moreover, the API takes care of the privacy of the users. For example, the user must explicitly grant permission before allowing the website to access the voice via microphone. This SitePoint article explains it well. Polymer fills in everything else that was missing, which is why you see that my paper elements (radial buttons) and the other Shadow DOM objects all appear on screen. Unfortunately, Web Speech is not part of Web Components.In Conclusion
That’s all there is to it, really. Now you’ve seen how I learned about Polymer, added my own element, and created a simple project. I plan on cleaning up the UI next and digging into Google’s paper elements a bit more to create a cleaner design. I also wrote a tutorial on how to access member functions in polymer-elements can be found here. I’ve commented all of the code pretty well, so take a look at how I’ve put it all together and read through the comments. Any questions, feel free to ask. Also, if you’d like to host your next Polymer project in the cloud, you can get a free trial of Azure with $200 in credits.More Learning on JavaScript
It might surprise you a bit, but Microsoft has a bunch of free learning on many open source JavaScript topics and we’re on a mission to create a lot more with Microsoft Edge coming. Here’s our team’s broader learning series on HTML, CSS, and JS:- Practical Performance Tips to Make your HTML/JavaScript Faster (a 7-part series from responsive design to casual games to performance optimization)
- The Modern Web Platform JumpStart (the fundamentals of HTML, CSS, and JS)
- Developing Universal Windows App with HTML and JavaScript JumpStart (use the JS you’ve already created to build an app)
- And some free tools: Visual Studio Community, Azure Trial, and cross-browser testing tools for Mac, Linux, or Windows.
This article is part of the web dev tech series from Microsoft. We’re excited to share Microsoft Edge and the new EdgeHTML rendering engine with you. Get free virtual machines or test remotely on your Mac, iOS, Android, or Windows device @ http://dev.modern.ie/(dpe)