A-Bad attitude about A-Frame (I was wrong)

I haven’t written many posts lately, especially tech related. The reason why is that I’ve been chasing the mixed reality train and learning. 3D development in general has had a number of false starts with me, and I never went in the deep end until now. This past year or so, I’ve been using both Unity and WebVR.

My failed 3D career

Prior to this, I went through a Shockwave 3D phase in the early 2000’s. People forgot about that REALLLLLLL quick. Likewise when I got all excited about Flash’s CPU based 3D engine Papervision (not Macromedia made, but for Flash), I remember learning it and then the hype died down to 0 within a few months. And then of course, there was Adobe Flash’s Stage3D. But as you might recall, that was about the time that Steve Jobs took the wind out of Flash’s sails and it was knocked down several pegs in the public eyes.

Whatever your opinion on Director or Flash, it doesn’t matter. Approachable 3D development never felt like it had a fair shot (sorry, OpenGL/C++ just isn’t approachable to me or lots of people). In my opinion, there were two prime reasons for this. The first is that GPU’s are really just now standard on everyone’s machine. I remember having to worry about how spectacularly crappy things would look with CPU rendering as a fallback. Secondly, and this is huge: visual tooling.

Don’t believe me? I think the huge success of Unity proves my point. Yes, C# is approachable, but also, being able to place objects in a 3D viewport and wire up behaviors via a visual property panel is huge. Maybe seasoned developers will eventually lay of this approach, but it introduces a learning curve that isn’t a 50ft high rock face in front of you.

Unity is fantastic, for sure, but after being a Flash developer in 2010 and my career seemed to be crumbling around me, I’m not going to be super quick to sign up for the same situation with a different company.

Hello, WebVR

So, enter WebVR. It’s Javascript and WebGL based, so I can continue being a web developer and using existing skills. It’s also bleeding edge, so it’s not like I have to worry about IE (hint: VR will never work on IE, though Edge’s Mixed Reality support  is now the only publicly released version of WebVR (FF and Chrome WebVR are in experimental builds)! Point being, is that all those new ES6 features I dig, I can use them freely without worrying about polyfills (I do polyfill for import, though….but that’s another article for another time).

Those of us who were excited about WebVR early on, probably used Three.js with some extensions. As excitement picked up steam, folks started packaging up Three.js with polyfills to support everything from regular desktop mouse interaction, to Google Cardboard, to the Oculus Rift and Vive, all with the same experience with little effort from the developer.

I found that object oriented development with ES6 classes driving Three.js made a lot of sense. If you take a peek at any of the examples in Three.js, they are short, but the code is kind of an unorganized mess. This is certainly forgivable for small examples, but not for big efforts that I might want to try.

So, I was pretty happy here for a while. Having a nice workflow that you ironed out doesn’t necessarily make you the most receptive to even newer ways, especially those that are super early and rough around the edges.

Enter A-Frame

I believe early last year (2016), when I was attending some meetups and conference sessions for WebVR and Mozilla made a splash with A-Frame. Showing such an early release of A-Frame was a double edged sword. On the plus side, Mozilla was showing leadership in the WebVR space and getting web devs and designers interested in the promises of approachable, tag based 3D and VR. The down side was that people like me who were ALREADY interested in WebVR and already had a decent approach for prototyping were shown an alpha quality release with a barely functional inspection and visual editing tool that didn’t seem to offer anything better than the Three.js editor.

As I wasn’t excited about it at all, I also reasoned that the whole premise of A-Frame was silly. Why would a sane person find value in HTML tags for 3D elements?

Months passed, and I was happy doing my own thing without A-Frame. I even made a little prototyping library based on existing WebVR polyfills with an ES6 class based approach for 3D object and lifecycle management. It was fairly lightweight, but it worked for a couple prototypes I was working on.

A-Frame Round 2

Right around when A-Frame released 0.4 or 0.5, the San Francisco HTML5 meetup group invited them on for another session in another WebVR event. The A-Frame community had grown. There were a crazy number of components that their community built because…hey A-Frame is extensible now (I didn’t know that!). The A-Frame visual inspector/editor is now the really nice and accessible as a debug tool from any A-Frame scene as you develop it. Based on the community momentum alone, I knew I had to take a second look.

To overcome my bad A-Frame attitude, I carved out a weekend to accomplish two goals:

  • Reason an organized and scalable workflow that doesn’t look like something someone did in 2007 with jQuery
  • Have a workflow where tags are completely optional

I actually thought these might be unreasonable goals and I was just going to prove failure.

A-Scene

As I mentioned briefly, I had my own library I was using for prototyping. Like I said it was basically a package of some polyfills that had already been created for WebVR with some nice ES6 class based organization around it.

I knew that A-Frame was built much the same way – on top of Three.js with the same polyfills (though slightly modified). What I didn’t count on was that our approach to everything was so similar that it took me just a few hours to completely swap my entire foundational scene out for their <a-scene> tag, and…. it…worked.

This blew my mind, because I had my own 3D objects and groups created with Three.js and the only tag I put on my HTML page was that <a-scene> tag.

Actually, there were a few hiccups along the way, but given that I was shoving what I thought was a square peg into a round hole, two minor code changes are nothing.

My approach is like so:

Have a “BaseApplication” ES6 class. This class would be extended for your application. It used to be that I’d create the underlying Three.js scene here in the class, but with A-Frame, I simply pass the <a-scene> element to the constructor and go from there. One important application or 3D object lifecycle event is to get a render call every frame so you can take action and do animation, interaction, etc. Prior to A-Frame, I just picked this event up from Three.js.

Like I said, two hiccups. First, my application wasn’t rendering it’s children and I didn’t know how to pickup the render event every frame. Easy. First pretend it’s an element by assigning an “el” property to the class and set it to playing:

this.el = { isPlaying: true };

Next, simply register this class with the A-Frame scene behaviors like this:

this._ascene.addBehavior(this);

Once this behavior is added, if your class has a “tick” method, it will be fired:

/**
* a-frame tick
* @param time
*/
tick(time) {
...
}

Likewise, any objects you add to the scene, whom you want to have these tick methods, simply add them to the behavior system in the same way.

In the end my hefty BaseApplication.js class that instantiated a 3D scene, plugins, and polyfills, was chopped down to something 50 lines long (and I DO use block comments)

export default class BaseApplication {
    constructor(ascene, cfg) {
        if (!cfg) {
            cfg = {};
        }
        this._ascene = ascene;
        this._ascene.appConfig = cfg;
        this._ascene.addBehavior(this);
        this.el = { isPlaying: true };
        this.onCreate(ascene);
    }

    get config() {
        return this._ascene.appConfig;
    }

    /**
     * a-frame tick
     * @param time
     */
    tick(time) {
        this.onRender(time)
    }

    /**
     * add objects to scene
     * @param grouplist
     */
    add(grouplist) {
        if (grouplist.length === undefined) {
            grouplist = [grouplist];
        }
        for (var c in grouplist) {
            grouplist[c].addedToScene(this._ascene);

            if (grouplist[c].group) {
                this._ascene.appendChild(grouplist[c].group);
                this._ascene.addBehavior(grouplist[c]);
            } else {
                this._ascene.appendChild(grouplist[c]);
            }
        }
    }
 // meant to be overridden with your app
 onCreate(ascene) {}
 onRender(time) {}
}

As you might be able to tell, the only verbose part is the method to add children where I determine what kind of children they are: A-Frame elements, or my custom ES6 class based Object Groups.

How I learned to Love Markup

So, at this point I said to myself…”Great! I still really think markup is silly, but A-Frame has a team of people that will keep up with WebVR and will update the basics as browsers and the spec evolves, and I should just use their <a-scene> and ignore most everything else.

Then, I hit ctrl-alt-i.

For those that don’t know, this loads the A-Frame visual inspector and editor. Though, of course, it won’t save your changes into your code. Let me say first, the inspector got reallllllly nice and is imperative for debugging your scene. The A-Frame team is forging ahead with more amazing features like recording your interactions in VR so you can replay them at your desk and do development without constantly running around.

So, when I loaded that inspector for the first time, I was disappointed that I didn’t see any of my objects. I can’t fault A-Frame for this, I completely bypassed their tags.

That alone roped me in. We have this perfectly nice visual inspector, and I’m not going to deny my use of it because I can’t be convinced to write some HTML.

Using Tags with Code

At this point, me and A-Frame are BFF’s. But I still want to avoid a 2008 era jQuery mess. Well, turns out, 3D object instantiation is about as easy in A-Frame as it is with code. It’s easier actually because tags are concise, where as instantiating materials, primitives, textures, etc, can get pretty verbose.

My whole perspective has been flipped now.

  • Step 1: Create that element, just set that innerHTML to whatever or createElement and set attributes individually
  • Step 2: appendChild to the scene (or another A-Frame element)

That’s it. I’m actually amazed how responsive the 3D scene is for appending and removing elements. There’s no “refresh” call, nothing. It just works.

I actually created a little utility method to parse JSON into an element that you could append to your scene:

sceneElement.appendChild(AFrameGroup.utils.createNode('a-entity', {
    'scale': '3 3 3',
    'obj_model': 'obj: ./assets/flag/model.obj; mtl: ./assets/flag/materials.mtl',
    'position': '0 -13 0'
}));

AFrameGroup.utils.createNode(tagname, attributes) {
    var el = document.createElement(tagname);
    for (var c in attributes) {
        var key = c.replace(/_/g, '-'); // hyphens not cool in JSON, use underscore, and we convert to hyphen here
        el.setAttribute(key, attributes[c]);
    }
    return el;
}

Yah, there’s some stuff I don’t like all that much, like if I want to change the position of an object, I have to go through element.setAttribute(‘position’, ‘0 50 0′). Seems bit verbose, but I’ll take it.

A-Happy Prototyper

Overall, the markup aspect, early versions, and lack of organization/cleanliness in the code examples made me sad. But examples are just examples. I can’t fault them for highlighting simple examples that don’t scale well as an application when they intend to showcase quick experiences. A-Frame wants to be approachable, and if I yammer on to people about my ES6 class based approach with extendable BaseApplication and BaseGroup/Object classes, yes, I might appeal to some folks, but the real draw of A-Frame right now is for newcomers to fall in love with markup that easily gets them running and experience their own VR creations.

All that said, I did want to share my experience for the more seasoned web dev crowd because if you peel back the layers in A-Frame you’ll find an extremely well made library that proves to be very flexible for however you might choose to use it.

I’m not sure whether I want to link you folks to my library that’s in progress yet, but I’ll do it anyway. It’s majorly in flux, and I keep tearing out stuff as I see better ways in A-Frame to do things. But it’s helping me prototype and create clear distinction of helper code from my prototype logic, so maybe it’ll help you (just don’t rely on it!)

 

 

Progressive Web Apps

Last Friday, the SFHTML5 Meetup group met to discuss something called “Progressive Web Apps.” I had some preconceived notions that the topic would be pretty cool, but actually, it got me more excited about the state of mobile/web/desktop in 2016 than I could have imagined.

This might sound a bit dramatic, especially given the negative tone that Alex Russel (@slightlylate), the speaker, started off with on the mobile web. Despite being negative, he was spot on, and the talk was a real eye-opener for us who have been working on the mobile web for so long that we forget how much it sucks.

And yes, it does suck. A good point was made that every other mobile platform started out mobile. No vendor has ever really proposed, “OK, let’s take this UI platform, along with everything that’s ever been built with it that works on the desktop with mouse and keyboard, and dump it on mobile.” Nobody did that until it came to web browsers. It’s amazing that things work as well as they do right now.

Alex then took us through an exercise of asking for hands up on who used a web-based email client on the desktop. Around 95% of our hands raised. When the question was reversed, “Who uses a web-based email client on their mobile device?” the result was exactly the opposite.

Why does the mobile web suck so much? The folks that have given “Responsive Web Design” (RWD) a shot can’t be blamed for this problem. The rest of the web community…if you want your stuff to work on mobile, it’s time for a redesign.

Even with RWD, some mobile redesign love, and the MOBILE FIRST! mantras we shout, the fundamental user experience with the mobile web, as it is now, will never compete with those for mobile apps. It’s probably not because HTML/JS/CSS is slow. Yeah, native can be faster, but if you think about it, most apps you use really don’t need speed. If you don’t agree with me, tell that to all the app developers using Phonegap, Cordova, or even just a plain WebView for their entire products.

So speed isn’t the issue for most apps. Touch, screen orientation, and size don’t need to be an issue if the web team cares enough to address them. No, to compete with your typical mobile app, design comes down to how the browser itself runs and loads the page.

Real, installed apps have two pretty big advantages over mobile apps:

  • Once installed, the user can jump to the app from the home screen.
  • Even with no network connectivity, the app can still work or at least pretend to work.

There’s a 3rd advantage, and that’s push notifications: messages from the app that appear in the notification area even when the app isn’t running. I think that functionality is big-ish, but unless you have a significant base of users addicted to your app (think Facebook), it isn’t as big of a deal. Smaller guys and gals are just trying to develop a neat app.

Progressive Web Apps attempt to solve all of that missing functionality, and they do so in a way that doesn’t necessarily interfere with the current way we develop for the web.

Step #1: Invade the Home Screen and look like an app

Tackling the first issue of putting your page on the mobile home screen is pretty important. How the application is displayed, both on the home screen and when it loads, is part of that experience. To solve it, use the “Web App Manifest”! It’s a JSON file linked from your HTML head that allows you to define things like app icons, fullscreen display, splash screen, and more.

This is the point when I should confess that I haven’t worked with Progressive Web Apps yet. Luckily for me, this isn’t a “how-to” article. So for great details on how-to do this stuff, run an easy search, or for your convenience, read this nice technical article via MobiForge.

Either way, the idea is that if a user visits your page often enough within a certain time frame, the browser will ask the user if he or she would like to place the page on their home screen. Or, the user can simply add it to the home screen from the options menu in the mobile browser. That’s light-years better than having to open the browser, remember the URL, and load the page. I’m sure it’s a huge reason apps are winning on mobile right now.

Step #2: Be an app even when offline

Secondly, we have “Service Workers.” They sound nerdy and boring, and maybe they are, but the potential they open up for appifying a webpage is huge. Basically, you’d use a Service Worker to intercept a specific set of resources as the webpage fetches them. Yes, if the user is offline that first time they want to access the page, they’re outta luck. However, when they do intercept these resources with a connection, they’ll be cached. You, the developer, control which files get cached via a Javascript array in the code. On subsequent loads, even if the user is offline, the page can load with your cached assets, whether they are images, Javascript, JSON, styles, or whatever. Here’s a better technical description of how that works.

In fact, Google has published documentation and some tools on the similar notion of an “Application Shell Architecture” wherein persistent assets that don’t change can be cached, but dynamic content that isn’t cached will update.

What does this mean and will it all work?

Probably the most exciting thing about Progressive Web Apps is that both the Manifest and the Service Workers will not affect the web page negatively if the browser doesn’t support the features. This means that the worst you can do is waste time and JS code on something that doesn’t pan out as you hoped.

And there is some danger that it won’t work. You may have noticed that Facebook today uses push notifications with Service Workers and that they do increase engagement on their site. So that’s a win! Unfortunately, Service Workers and the Web App Manifest aren’t supported everywhere. Unsurprisingly, that means they’re pretty much everywhere but iOS/Safari. Even worse, browser vendors on iOS can’t use their own web engines to support the Progressive Web Apps — under the hood, both Chrome and Firefox have to use Safari tech.

Apple seems tight-lipped about whether they intend to adopt Progressive Web Apps at all. I’m going to say that for now, it doesn’t matter. If you’ve hung around the SF Bay Area enough, you may have noticed that many companies operate on an “iOS first, Android distant second” agenda. That doesn’t make sense in that Android devices far surpass iOS devices in sales. But it does make sense, in that iOS app sales are greater and it can be daunting to develop apps for the large ecosystem of Android devices on the market.

However you slice it, Android is second for developers, which is bad for consumers. Right now, many companies will adopt a Web + iOS + maybe Android strategy. If they can combine the Android + Web strategy with Progressive Web Apps AND not force folks through the Google App Store, it’ll be a huge win for everyone. I’m guessing Google probably doesn’t even care much about having an app store, save for the fact that it was necessary to maintain a mobile ecosystem.

Meanwhile, the point was made at this Meetup that with every additional step a user must go through to download an app, there’s around a 20% dropoff rate. Think about how many steps there are in clicking an app link, going to the store, starting the download, waiting for the install, and finally opening the app — many apps are losing out on users. And let’s face it, the app gold rush is over. There are some lottery winners still, but most apps are too costly to make and market to justify what they bring in return.

Progressive Web Apps short circuit that whole process by eliminating app discovery and install. While Android users will enjoy a huge user experience win, Apple will most likely try to maintain their stranglehold on their app store and come kicking and screaming only once web devs demand these new features.

What’s more, and what I’m really excited for, is our return to disposable digital experiences. Hate Adobe Flash or not, it really created a heyday for disposable experiences: Flash games to play a couple times and get bored with, nifty digital playgrounds, etc. It’s way harder to convince someone to download an app than it is to go to a webpage and pop it on their homescreen until they get bored of it in a week.

To extend, I think Progressive Web Apps will also be a huge boon for web-based Virtual Reality. Immersive experiences will come from many different places and frankly, will not be wanted as a permanent app install. Already, we’re seeing the rise of VR portals like MilkVR because smaller, one-off VR experiences need some kind of entry onto a device. When Progressive Web Apps make WebVR easier to get before eyes than an app portal, VR will win big.

To reiterate, I think Progressive Web Apps are the next big thing for mobile, potentially replace lots of simple apps, and will mark the return of fun, disposable experiences. I don’t have the technical experience with these new tools to back me up yet, but I will soon. Don’t take my word for it, though! Read up on it and try it yourself.

Here’s another post from the aforementioned Alex Russel: https://infrequently.org/2015/06/progressive-apps-escaping-tabs-without-losing-our-soul/

ES6 Web Components Part 5 – Wrap-Up

In Part 4 of my 5-part write-up, Project Setup and Opinions, I talked about lessons I took away from experimenting with ES6 Web Components. Lastly, is my wrap-up post…

This was a monster write-up! In my four previous parts, I’ve shown you the basics on Web Components, what features make up a Web Component, how ES6 can help, and some coding conventions I’ve stumbled on through my experimentation.

That last sentence is my big caveat – it’s trial and error for me. I’m constantly experimenting and improving my workflow where it needs to be improved. Some pieces I’ve presented here, but I may come up with an even better way. Or worse, I may discover I showed you folks a really bad way to do something.

One particular thing to be cautious of is recognizing I’m not talking about cross-browser compatibility here. I have done a bit of research to show that, theoretically, things should work cross-browser, especially if you use the WebComponents.js polyfill. I have done a little testing in Firefox, but that’s it. I really haven’t tested in IE, Edge, Safari, et cetera. I’m lucky enough to be in a position right now at my job and in my personal experiments where I’m focusing on building in Chrome, Chromium, or Electron (built on Chromium). I’m trying to keep compatibility in mind; however, without a real effort to test in various browsers, you may run into issues I haven’t encountered.

It isn’t all doom and gloom, though. WebComponents.js is used as the Google Polymer polyfill. Its why Polymer claims to have the cross-platform reach it has. See the support grid here for supported browsers.

Even better, as I complete this series, Webkit has just announced support for the Shadow DOM. This is fantastic, because the Shadow DOM is the hardest piece to polyfill. A while back, Polymer/WebComponents.js had removed polyfilled Shadow DOM support for CSS because it wasn’t very performant. Microsoft announced a while back that it’s working on the Shadow DOM, while Firefox has it hidden behind a flag.

All this is to say, if you take anything away from this series of blog posts on ES6 Web Components, takeaway ideas. Treat them as such. Don’t take this to your team and say “Ben Farrell has solved it all; we’re all in on Web Components.” I truly hope everything I’ve said is accurate and a fantastic idea for you to implement, but don’t risk your production project on it.

With all that said, aside from the implementation details, I do think Web Components are a huge leap forward in web development. It’s been encouraging me to use pure vanilla Javascript everywhere. I haven’t needed jQuery, syntactic sugar provided by a framework, nontraditional markup for binding – it’s all pure JS. I have been using JS techniques like addEventListener, querySelector, cloneNode, et cetera. Those are all core JS, CSS, and HTML concepts. When you understand them, you understand what every JS framework and library is built on. They transcend Angular, React, jQuery, Polymer, everything. They will help you learn why your favorite tool is built the way it is and why it has the shortcomings it does.

Not only am I building pure JS here, but I’m organizing my code into reusable and modular components – what every JS framework tries to give you.

For these reasons, I think there is huge potential in Web Components and I think it most likely represents what we’ll be doing as a community years from now, especially when (hopefully not if) all features of Web Components and ES6 are implemented in browsers everywhere.

As I said in my first post, I do like Google’s Polymer a lot. But again, I strive to do less application-like things and more creative-like things. Therefore, MY components are fairly custom and don’t need a library of Google’s Material-designed elements. I’ve started a Github Org called Creative Code Web Components that contains a video player and camera that draw to the canvas and effects can be created for them on the pixels. I’ve created a speech-input component as well, along with a pure ES6 Web Component slide deck viewer.

Those components are all in early stages, but for fabricating various creative projects, I feel like this the right way forward for me. Thus far, I have a real modular set of pieces for creating a neat prototype or project.

Perhaps if you are doing a real business application, Polymer is great for you. Or React. Or Angular. Regardless, I think what I’ve been learning is great info for anyone in web dev today to have. I wouldn’t have written 10,000 words about it otherwise!

This has been my big 5-part post about creating Web Components with ES6. To view the entire thing, check out my first article.

ES6 Web Components Part 4 – Project Setup and Opinions

This article continues my ES6 Web Components series. The last article was the third in the series: Making an ES6 Component Class.

So far, the basics have been pretty….basic. I hope I’ve given some ideas on how to create ES6 Web Components – but these basics only go so far. I do have some opinions on how to take this further, but they are only opinions that have made sense to me. The beauty of this is that you can hear me out and decide for yourself if these ideas are good for you.

Project and File Setup

Lets start with dependencies. I like Babel to compile the ES6 and Gulp to do the tasks. Source maps are also a must in my book for debugging the compiled ES6 as Javascript! Also, given that WebComponents.js has been so instrumental in providing cross platform functionality, lets use that too.

Here’s a sample package.json file:

{
  "name": "ccwc-slideshow",
  "version": "0.1.0",
  "dependencies": {
    "webcomponents.js": "^0.7.2"
  },
  "devDependencies": {
    "babel": "^5.8.21",
    "gulp": "^3.9.0",
    "gulp-babel": "^5.2.0",
    "gulp-sourcemaps": "^1.5.2"
  }
}

Next up is Gulp. I have nothing against Grunt…but I use Gulp. Frankly I stopped caring about the battle of the task runners and landed on the last one that I tried and liked. There probably won’t be too many tasks – I just need to compile the ES6 to Javascript. However, I may have multiple components in my repo. As a result, I’ll have a compile task per component. Here’s a sample Gulpfile:

var gulp = require('gulp');
var sourcemaps = require('gulp-sourcemaps');
var babel = require('gulp-babel');
var concat = require('gulp-concat');

gulp.task('slide', function () {
  return gulp.src('src/ccwc-slide.es6')
    .pipe(sourcemaps.init())
    .pipe(babel())
    .pipe(concat('ccwc-slide.js'))
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest('./src'));
});

gulp.task('slideshow', function () {
  return gulp.src('src/ccwc-slideshow.es6')
    .pipe(sourcemaps.init())
    .pipe(babel())
    .pipe(concat('ccwc-slideshow.js'))
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest('./src'));
});

gulp.task('default', ['slide', 'slideshow']);

One last Javascript note: I like to have an ES6 extension on my ES6 files, and those to live in a “src” older. Some folks seem to be using .js, and then compiling them to “mycomponent-compiled.js”. I don’t like this for a couple of reasons. First, its not obvious that your source JS file is ES6, and secondly I kinda think it’s silly to force devs to use a non-obvious naming convention when including a script. When you make your script tag, you should link to “mycomponent.js”. Not “mycomponent dot, ummm…what was my naming convention last week?”.

Your Web Component HTML files should live in your project root. When you link to a Web Component, you shouldn’t need to remember what folder you put your stuff in…it should be a simple and easy to remember “mycomponent/mycomponent.html”.

Lastly, your demo is important! A Web Component should demonstrate use! When I started out, I was making a “demo” folder in my component root, and putting an index.html or demo.html file in there. There’s a problem with this though: if you use images (or other assets), the relative path to your image will be different from the demo folder than what it is during actual use of your component. Bummer. So I like to put a “demo.html” usage example in my component root. I still have a demo folder, but this folder would contain any assets to support running the demo that aren’t really part of your component (like JSON data).

Actually – one more. This is the last one for real. Documentation for your component. I didn’t think about it here, because I didn’t even think of doing it for my components yet. My bad. My horrible horrible bad. Google’s Polymer actually has a very nice self documenting structure which is very sweet. Maybe someday, I’ll base whatever I plan to do about docs on that.

Here’s a sample project structure of a component I made for showing a slide deck. You’ll notice 2 components here. One is the slide deck viewer, and one is a component to show a single slide. The first uses the second inside it and it all works together. I have some sample slide deck contents in my demo folder:

componentstructure
You’ll notice that I have the compiled .js.map files and the .js files here, too. I check these in to source control. I always feel a little icky about checking in compiled files. For one, they don’t NEED to be in source control since they are generated and don’t need to be diffed. Secondly, you don’t want to allow people to edit these files instead of editing the ES6 files. Lastly, I am occasionally forgetful of building then checking in! Sometimes only the ES6 files get checked in, and I’m left doing another commit of the compiled files when I remember that I didn’t build.

All that said, I DO check these compiled files in. For my workflow, I want these file instantly useable after NPM installing my component. Forcing a compile step for an NPM module and requiring dev dependencies seem like an unnecessary burden on the end user. I’m always trying to think of ideas to make myself happy here on all counts, but I haven’t yet.

Component Class and Method Conventions

I’ve already documented the bare minimum of methods in your Web Component class. These include: “attachedCallback”, “createdCallback”, “detachedCallback”, and “attributeChangedCallback”. These, however, are just HTMLElement lifecycle callbacks. I have some other methods of my own I like to consistently use (all invented by me, of course, and not part of any spec).

setProperties

In ES6, there is no attaching properties directly to the class inline. Properties can only be set on the class from within a method. So I made my own convention that I consistently use. My “setProperties” method initializes all of the variables that would be used for the class. In other languages, public/private variables would be used at the top of a class. In ES6, I use my “setProperties” method for this and give my variables a little extra documentation/commenting.

parseAttributes

Once the component is created or attached, you may want to look at the attributes on your component tag to read in some initial component parameters. You could potentially scatter these all over your code. I like to read them all in one spot: “parseAttributes”.

registerElements and the “dom” object

I really dug Polymer’s “$” syntax. Anything in your component’s HTML that had an ID, you could access with “this.$.myelement”. Well, in my DIY Web Component world, I can’t just magically expect to access this. I COULD querySelector(‘#myelement’) everytime, but its more performant to save these references to a variable if you’re using them often. And it also creates more readable code to save your important element references in well named variables. At the same time, though, it might be confusing to mix elements on your root “this” scope with other variables that aren’t elements.

So here’s what I do…

When I have a bunch of stuff that I want to reference in the imported HTML template at the very start, like buttons, text fields, whatever, I’ll run my custom method “registerElements” in the attachedCallback after appending the template to my Shadow Root.

In “registerElements”, I’ll create a new Object called “dom” on my root scope “this” (this.dom = {};). I’ll then querySelect any elements I want, grab the reference, and populate “this.dom.myelement” with the references. Then elsewhere in my code, I can just reference the property like a normal variable (but I know it’s a DOM element since its in my “this.dom” object).

root

One last thing I do consistently….and this is not a method, but a property…is using a custom variable “root” to represent the Shadow DOM. So when I want to use querySelector on an element, I use “this.root.querySelector(‘myelement’)”. I COULD just call it “shadow”. However, there’s been a couple times I’ve been a bit wishy-washy about using the Shadow DOM, and I can just set “this.root” to the host content, or even the document if I wanted. In this fashion I can keep swapping around what “root” is to whatever I choose and keep my code pretty much the same.

An Example

I’ll leave you with a complete example of my Web Component that functions as a Slide Deck viewer. Remember, the slide inside is a web component on its own! In my next post, I’ll wrap this whole thing up and link you all to my real components.


class CCWCSlideShow extends HTMLElement {
  setProperties() {
    /**
     * slides deck
     *
     * @property deck
     * @type string
     */
    this.deck = '';

    /**
     * next slide key mapping
     *
     * @property nextSlideKey
     * @type integer
     */
    this.nextSlideKey = 39; // right arrow key

    /**
     * previous slide key mapping
     *
     * @property previousSlideKey
     * @type integer
     */
    this.previousSlideKey = 37; // left arrow key

    /**
     * toggle timer key mapping
     *
     * @property toggleTimerKey
     * @type integer
     */
    this.toggleTimerKey = 84; // "t" key

    /**
     * timer start time
     *
     * @property timer start time
     * @type Number
     */
    this.timerStartTime = 0;

    /**
     * current slide/chapter
     *
     * @property current slide/chapter
     * @type object
     */
    this.current = { chapter: 0, slide: 0 };

    /**
     * running
     * is slide deck running (being timed)
     * @property running
     * @type boolean
     */
    this.running = false;

    /**
     * slides
     *
     * @property slides
     * @type array
     */
    this.slides = [];
  };

  /**
   * register dom elements
   */
  registerElements() {
    this.dom = {};
    this.dom.slideviewer = this.root.querySelector('#slideviewer');
    this.dom.slideinfo = this.root.querySelector('.infobar .slides');
    this.dom.runtime = this.root.querySelector('.infobar .runtime');
  };


  /**
   * ready
   *
   * @method ready
   */
   init() {
    this.loadDeck('./deck/manifest.json');
    document.addEventListener('keyup', event => this.onKeyPress(event) );

    setInterval( () => {
      if (this.running) {
        var duration = Math.floor((new Date().getTime() - this.timerStartTime) / 1000);
        var totalSeconds = duration;
        var hours = Math.floor(totalSeconds / 3600);
        totalSeconds %= 3600;
        var minutes = Math.floor(totalSeconds / 60);
        var seconds = totalSeconds % 60;
        if (seconds.toString().length == 1) {
          seconds = "0" + seconds;
        }
        if (minutes.toString().length == 1) {
          minutes = "0" + minutes;
        }
        this.dom.runtime.innerText = hours + ":" + minutes + ":" + seconds;
      }
    }, 1000);
  };

  /**
   * toggle timer
   *
   * @method toggleTimer
   */
  toggleTimer() {
    this.running = !this.running;
    if (this.timerStartTime === 0) {
      this.timerStartTime = new Date().getTime();
    }
  };

  /**
   * on keypress
   * @param event
   */
  onKeyPress(event) {
    switch(event.keyCode) {
      case this.nextSlideKey:
            this.nextSlide();
            break;

      case this.previousSlideKey:
            this.previousSlide();
            break;

      case this.toggleTimerKey:
            this.toggleTimer();
            break;
    }
  }

  /**
   * load chapter in slide deck
   * @param index
   * @param uri
   */
  loadChapter(index, name, uri) {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = () => {
      if (xmlhttp.readyState == 4) {
        if (xmlhttp.status == 200) {
          var chapter = JSON.parse(xmlhttp.responseText);
          chapter.index = index;
          chapter.name = name;
          this.chapters.push(chapter);
          this.chapters.sort(function(a, b) {
            if (a.index > b.index) { return 1; } else { return -1; }
          });
          this.manifest.slideCount += chapter.slides.length;
          this.goSlide(0, 0);
        }
      }
    };
    xmlhttp.open("GET", uri, true);
    xmlhttp.send();
  };

  /**
   * load deck
   * @param uri of manifest
   */
  loadDeck(uri) {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = () => {
      if (xmlhttp.readyState == 4) {
        if (xmlhttp.status == 200) {
          this.manifest = JSON.parse(xmlhttp.responseText);
          this.manifest.slideCount = 0;
          this.dom.slideviewer.imgpath = this.manifest.baseImagePath;
          this.dom.slideviewer.htmltemplatepath = this.manifest.baseHTMLTemplatePath;

          this.chapters = [];
          for (var c = 0; c < this.manifest.content.length; c++) {
            this.loadChapter(c, name, this.manifest.content[c].file);
          }
        }
      }
    };

    xmlhttp.open("GET", uri, true);
    xmlhttp.send();
  };

  /**
   * next slide
   */
  nextSlide() {
    this.current.slide ++;
    if (this.current.slide >= this.chapters[this.current.chapter].slides.length) {
      this.current.slide = 0;
      this.current.chapter ++;

      if (this.current.chapter >= this.chapters.length) {
        this.current.chapter = 0;
      }
    }
    this.goSlide(this.current.chapter, this.current.slide);
  };

  /**
   * previous slide
   */
  previousSlide() {
    this.current.slide --;
    if (this.current.slide < 0) {
      this.current.chapter --;

      if (this.current.chapter < 0) {
        this.current.chapter = this.chapters.length - 1;
      }
      this.current.slide = this.chapters[this.current.chapter].slides.length - 1;
    }
    this.goSlide(this.current.chapter, this.current.slide);
  };

  /**
   * go to slide
   * @param {int} index of chapter
   * @param {int} index of slide
   */
  goSlide(chapter, slide) {
    this.current.chapter = chapter;
    this.current.slide = slide;

    var slidecount = slide;
    for (var c = 0; c < chapter; c++) {
      slidecount += this.chapters[c].slides.length;
    }

    this.dom.slideinfo.innerText = 'Chapter ' + (chapter+1) + '.' + (slide+1) + '    ' + (slidecount + 1) + '/' + this.manifest.slideCount;
    this.dom.slideviewer.clear();
    var sld = this.chapters[chapter].slides[slide];

    if (sld.htmlinclude) {
      this.dom.slideviewer.setHTML(sld.htmlinclude);
    }

    if (sld.webpage) {
      this.dom.slideviewer.setIFrame(sld.webpage);
    }

    if (sld.text) {
      sld.text.forEach( item => {
        this.dom.slideviewer.setText(item.html, item.region);
      });
    }

    if (sld.images) {
      sld.images.forEach( item => {
        this.dom.slideviewer.setImage(item.image, item.region);
      });
    }

    if (sld.background) {
      this.dom.slideviewer.setBackgroundImage(sld.background, sld.backgroundProperties);
    }
  };

  /**
   * getter for slide element
   *
   * @return slide element
   */
  getSlideComponent(id) {
    return this.dom.slideviewer;
  };

  /**
   * getter for slide element
   *
   * @param {string} class name
   * @return {array}
   */
  getHTMLIncludeElementsByClass(clazz) {
    return this.getSlideComponent().getHTMLIncludeElementsByClass(clazz);
  };

  // Fires when an instance was removed from the document.
  detachedCallback() {};

  // Fires when an attribute was added, removed, or updated.
  attributeChangedCallback(attr, oldVal, newVal) {};

  /**
   * parse attributes on element
   */
  parseAttributes() {
    if (this.hasAttribute('deck')) {
      this.deck = this.getAttribute('deck');
    }

    if (this.hasAttribute('nextSlideKey')) {
      this.nextSlideKey = parseInt(this.getAttribute('nextSlideKey'));
    }

    if (this.hasAttribute('previousSlideKey')) {
      this.previousSlideKey = parseInt(this.getAttribute('previousSlideKey'));
    }

    if (this.hasAttribute('toggleTimerKey')) {
      this.toggleTimerKey = parseInt(this.getAttribute('toggleTimerKey'));
    }
  };


  // Fires when an instance of the element is created.
  createdCallback() {
    this.setProperties();
    this.parseAttributes();
  };

  // Fires when an instance was inserted into the document.
  attachedCallback() {
    let template = this.owner.querySelector('template');
    let clone = document.importNode(template.content, true);
    this.root = this.createShadowRoot();
    this.root.appendChild(clone);
    this.registerElements();
    this.init();
  };
}

if (document.createElement('ccwc-slideshow').constructor !== CCWCSlideShow) {
  CCWCSlideShow.prototype.owner = (document._currentScript || document.currentScript).ownerDocument;
  document.registerElement('ccwc-slideshow', CCWCSlideShow);
}

Continue on to the conclusion of my ES6 Web Component Series

ES6 Web Components Part 3 – Making an ES6 Component Class

One thing I didn’t cover in my last post (#2 in the series) about all the pieces that bring Web Components together is actually using ES6! It’s not a feature of Web Components, but its some seriously nice glue that lets you tie your Web Component structure together.

To be honest, I’m not using it to it’s full capacity. In fact, I’m only using 2 or 3 features to make my life easier!

Classes

There are some strong opinions NOT to use classes especially from one prominent person in the JS community. Even if Crockford had all the best intentions, I doubt he had Web Components in mind – because to me, Web Components are the perfect place to use Classes. Components are fairly OO by nature. I want to take an HTMLElement and extend it to my own custom component. One thing I didn’t get into when talking about creating my own elements before was the methods that you get when extending HTMLElement. Now is as good a time as any to bring them up:


class MyCustomClass extends HTMLElement {
    // Fires when an instance was removed from the document.
    detachedCallback() {};

    // Fires when an attribute was added, removed, or updated.
    attributeChangedCallback(attr, oldVal, newVal) {};
    
    // Fires when an instance was inserted into the document.
    attachedCallback() {
        // Remember this? We're cloning the template tag and dropping it
        // onto our page in our newly created Shadow Root
        var template = this.owner.querySelector('template');
        var clone = document.importNode(template.content, true);
        this.root = this.createShadowRoot();
        this.root.appendChild(clone);
    };

    // Fires when an instance of the element is created.
    createdCallback() {};
}
if (document.createElement('my-customclass').constructor !== MyCustomClass) {
MyCustomClass.prototype.owner = (document._currentScript || document.currentScript).ownerDocument;
    document.registerElement('my-customclass', MyCustomClass);
}

So the above code are all the methods you get when you extend an HTMLElement AND what an empty ES6 Class might look like. The methods inline with the comments seem pretty self explanatory, but I will mention this…pay VERY close attention to “attachedCallback” vs “createdCallback”. Yes, its obvious that one fires when the element is created vs added to the DOM, however – make sure you consider that difference especially when you create an instance of your element at runtime with Javascript. If you did a bunch of cool stuff with “attached”, but then did document.createElement(‘my-customclass’) to create your element using JS….it hasn’t been added to the DOM yet! So whatever you have in “attachedCallback” hasn’t been run yet and your component might not act like you expect!

And now the non-obvious stuff…that weird “if” statement I have outside of my component. Well, this is all to register your custom element with the browser.

First, we try to create the custom element. If this is the second time you’ve used the component, it stands to reason that it’s all been registered before and we’d be writing over the original element! So, we create our custom element, and if it’s constructor is our class (MyCustomClass), then it’s already been created and we shouldn’t so it again.

Next, I’m using a little known feature: document.currentScript.ownerDocument. Unfortunately, document.currentScript is not present in all browsers. Yet again, WebComponent.js is there to save our bacon. In this scenario, with a polyfill though, _currentScript appears with an underscore, so you have to handle the logic.

In any event, this “ownerDocument” tells us the “document” owned by our script. In this context, document is our component’s local DOM! So, super useful, right? If you want to append any children, you do it to this ownerDocument. Create a ShadowDOM? Do it on this ownerDocument. Clone your <template> content? Do it on this ownerDocument. Here, I’ve taken this “ownerDocument” and assigned it to the class’s prototype so we can reference it on any instantiation of the class.

After all that, we can simply register the tag to the document, providing our custom class for how it should act.

In the end, we have a custom class with a few component lifecycle methods provided for us, as well as a way to create a brand new CUSTOM tag using that class for our custom features.

Fat Arrows

Kind of a silly name. But it’s easy to remember, because it literally is a fat arrow: => (as opposed to a skinny arrow (->).

Anyway, Fat Arrows help you manage scope. Scope can be a huge pain in the butt in Javascript. Before I got into this workflow with ES6 and Web Components, I was using a little trick when you instantiate your JS object, you can create a variable called “self”. Since when you first instantiate your object, the variable “this” always refers to the newly instantiated object, by just assigning “var self = this;”, you can always refer back to “self” even if you’re in a completely different scope because you’re off on a click handler or a timer.

I’d go into this more, but it’s irrelevant. it’s all out the window when you assign methods and properties to the underlying Object prototype and not the instance. And that’s exactly what ES6 classes do. So how do we get around this, and control our scope?

That’s where fat arrows come in to play. By using them, you’re passing your outer “this” scope to whatever your arrow points at. Take an event listener for example:


this.myawesomeelement.addEventListener('click', event => this.onClick(event) );

With this, you can make a method on your class called “onClick” and when it’s called, the scope of “this” is STILL the scope of your class. Without the fat arrow, when you tried to refer to “this” in “onClick”, it would be in the scope of the event. There would be no way to refer back to your class!

To be honest, I like this a whole lot more than “var self = this” because it always felt like a weird kludge. Yay for ES6! The syntax of the fat arrow can be a little confusing as you try to apply it to different scenarios – expect to look up examples online for your use case as you get used to the syntax.

Next up in part 4: Some opinions, workflows, and project setup for ES6 Web Components…