Sunday, November 15, 2015

Web components lifecycle methods 3: filling in the gaps

As we have seen in my previous posts the web component standard defines only four lifecycle methods:

  1. createdCallback  when the element is created, but possibly not attached to a document
  2. attachedCallback  when the element is added to a document
  3. detachedCallback when the element is removed from the document
  4. attributeChangedCallback, when an attribute is changed
They are also all callbacks: they are called after the standard processing, so you cannot override the standard processing. This is a very small model. If you compare with the lifecycle for an Android Activity you will see that the activity also has calls like:
  • onPause() Another activity comes into the forground
  • onResume() User returns to the activity
  • onStop() The activity is no longer visible
There are no corresponding callbacks in the web component standard. Most importantly ther is no callback method for when your element is displayed to the user. The attachedCallback will be called when your element is added to the DOM, but it might very well be hidden, specially since the practice to use hidden DOM elements for things like tabs, menues etc is so common in the HTML world. And there is no visibleCallback that is called when your element is shown.

Filling the gap 1: listen to the resize event

First step is probably to listen to the 'resize' event. This event is trigger when the browser window is resized, so it's pretty likely that you need to listen to it anyhow, if you ned to re-render or something when the window size changes. The right place to set up this listener is in attachedCallback:
                window.addEventListener("resize", me.redraw);
And you should remove it in detachedCallback:
window.removeEventListener("resize", this.redraw);
Now this might do, if you can make sure that your page triggers a resize event when the visibility of your element changes (like some sort of tabbing, page navigation etc). But it is a bit of overkill, since the resize event will go to all elements in the page and not just the ones affected by the change. It is also far from certain that the components you use do trigger resize events, so you might need to patch them.

Filling the gap 2: expose a draw method

This approach is more precise: you expose a draw method (or event) on your element, and the code in the page calls this method when needed. If you look at Googles implementation of web components for Google Charts this is the approach they have taken with the drawChart() method. Following this approach will simplify for users that use your component and have experience from Google charts. Unfortunately there is as far as I know no standard for this, so you cant really count on other components calling this method.

Of course you can combine these two approaches, expose a draw() method on your element, and then listen to the resize event and call the method when it occurs. 

Monday, October 19, 2015

Web Components lifecycle methods 2:attributeChangedCallback

The Web Component callback attributeChangedCallback seems simple enough. Whenever an attribute of your custoim component is changed, it will be called. The purpose of it is to support dynamic use cases: the web site developer can use standard HTML setAttribute to change attributes for your component, and the callback gives you a chance to react on the changes. Without it, or without an implementation of it, your component will not handle changes. But how does it actually work:

Case 1: modify a custom attribute

The first case is pretty straightforward: we have a custom component that defines some custom attributes. I use my own upper88-wordcloud throughout. It defines a couple of custom attributes, rows and options. When they are updated the callback is called, and I simply rerender the component. I use this in the demo page  with this little code snippet:

window.setInterval(function() {
  var chart = document.getElementById('hello_cloud');
  if (chart) {
   chart.setAttribute('rows', JSON.stringify([
    ['Hello', getRandomValue()],
    ['Word', getRandomValue()],
    ['Cloud', getRandomValue()]
   ], null, 2));
             }
 }, 3000);

As expected this leads to that the component is rerendered with modified data every 3 seconds. Note also that this is a callback: it is called after the attribute has been changed, so you can use the getAttribute method and you will get the new value. Also if you check in the attributes property you will get the new value. So far so good.

Case 2: modify standard attribute

My custom element extends HTMLelement and has of course the standard HTML attributes, like title. It also has the standard support for data-xxx attributes, which will be saved in the dataset property. Are they also covered by the callback??

To try this I added the following to the function above:

    chart.setAttribute('title','New title'+getRandomValue());
    chart.setAttribute('data-xxx','data-yyy'+getRandomValue());

And yes, this also works, and in the same way. The callback is called after the attribute has been updated, so you can use this also in your component.

Case 3: modify standard properties

Now using setAttribute to modify titles works but its a bit complicated. The title is available as a property, and can be modified directly, like this:

chart.title = 'New title'+getRandomValue();

(I use the helper function getRandomValue() to make sure I actually update the value) And yes, this works too. So it seems we can use the callback to monitor changes, it covers changes not made with setAttribute also.

Conclusion

The attributeChangedCallback helps you develop WebComponents that support dynamic changes.  Developers using your component can dynamically modify component instan ces using standard javascript techniques like setAttribute. You can use the callback to monitor changes not only in your custom attributesbut also in standard HTML attributes.

Saturday, October 17, 2015

Web Components lifecycle methods 1: createdCallback and attachedCallback

The Web Components standard includes only four lifecycle callback methods:
MethodCalled when
createdCallbackthe element is created
attachedCallbackthe element is inserted into the DOM
detachedCallbackthe element is removed from the DOM
attributeChangedCallback(attrName, oldVal, newVal)an attribute is changed

This is a very simple model and its simplicity has a cost if you try to build your own custom components or use web componets in your site. But lets see how they work.

Create and add elements

The two first methods are called when your page is loaded. Order is pretty obvious: first elements are created, then they are attached to the DOM. Lets investigate how they work in more detail.
There are different ways to create a HTML element, and part of the beauty with Web Components is that you can use all of them: creating your custom HTML element is just like creating a DIV. In this post I will look at three diferent ways to create a custom HTML element:

  1. include in a standard HTML page
  2. create in javascript, with document.createElement
  3. include in a HTML template (in my case in a Polymer iron-page element)

Use web components in a standard HTML page


For this case I have created a small standard web page loaded a Web Component (in this case my own upper88-wordcloud) and created two elements using this custom element. I then open then page in Chrome and set breakpoints in both createdCallback and attachedCallback.
  1. the callbacks are called in this order:
    1. createdCallback element 1
    2. attachedCallback element 1
    3. createdCallback element 2
    4. attachedCallback element 2
  2. already in createdCallback my elements know of their environment, that is parentElement and parentNode are already set, and parentElement has html content. 
  3. HTML initial reflow has not been made, so your element might have no size yet (I have written about this here)

Create web component in javascript

For a more dynamic case creating your element with javascript might be an alternative. Web Components support this also, but it will work slightly different.
To test this I added an empty div to my plain HTML page and a small javascript script:

var wc88 = document.createElement('upper88-wordcloud');
wc88.setAttribute("rows", '[ ["web",10],["components",15],["rocks!",20] ]');
document.getElementById('wc').appendChild(wc88);
  1. callbacks for the javascript element are of course called after the others, with createdCallback first and attachedCallback after
  2. when createdCallback is called parentElement and parentNode are both null, since the browser does not know where the element will end up. In attachedCallback they are set.
  3. HTML reflow has been made so the element actually has a size in the attachedCallback.

Use web components in a HTML template

If you are building a Polymer site it's verey likely that you do not use any of these to methods but instead use your web component in a HTML template, which in turn another web component will actually attach to the document. This is the way for example the Polymer component iron-page works. An example of this is my homepage upper88.com. In that page I use Polymer and iron-pages for page navigation. Note that irob-pages use css display:none to hide the none active sheets, so they will be added to the dom even if the user does not see them (in fact even if the user never navigates to the page). There are three upper88-wordclod elements in this page.
  1. Callback order:
    1. createdCallback element 1
    2. createdCallback element 2
    3. createdCallback element 3
    4. attachedCallback element 1
    5. attachedCallback element 2
    6. attachedCallback element 3
  2. elements know their environment already in createdCallback, parentElement is set
  3. No reflow has been made, so  the element has no size

Conclusion

As you can see the behavour is slightly different in the different scenarios. Even if you can trust the createdCallback to be called first and the attachedCallback to be called after that for each element, you cannot know if createdCallback has been called for all elements before the first attachedCallback or whether the browser will process the elements one by one. And even if parentElement and parentNode might be available in createdCallback (that surprised me) it also might not.
So, some rules:
  • do not make anything in createdCallback that depends on the element environment even if it might work in your page
  • do not count on knowing element size etc in attachCallback, they might not be available yet
  • test your web component in all three scenarios: standard HTML page, javascript creation and HTML template
And still we have only tested this in Chrome.... 

Wednesday, September 30, 2015

Another web component - upper88-title

In the weekend I published my second web component - upper88-title. Like my first one this is a vanilla JS web component, that does not use Polymer or any other library, apart from the web components polyfill. It might also be the smallest web component ever in terms of lines of code...

What it does is very simple: it allows you to tag some html in your web page as title, and the web component will grab the text (skip html tags) and set it as document title. So you don't have to type the same text over again - and can avoid the risk of forgetting to update the document title, it will always show the same text as that displayed in the actual page.

A little more advanced is that you can use Polymer data binding (even if the component itslef does not depend on Polymer) to set the content of both the HTML tag and document title at the same time. 

Tried to publish it on customelements.io but it seems like the job that updates the site is broken, so you can't see it there yet.

Tuesday, September 15, 2015

Polymer summit 2015

During the day I've followed Polymer Summit 2015 over the net. Lots of good talks and useful information. I do belive that the approach of Custoim HTML elements is the future, but even though Polymer is on it's way, I don'ät think tehey are really there yet.

Pros:

  • the approach of components that can be reused, extended and combined into new components
  • some really awesome components are available out there, both from Google and others
Not cons, but stuff they have to work on:
  • dependencies,build system etc: when you start a polymer project, bower will download that feels like half the internet for you, not really manageable, and my previous experiences from build system have not been to good. You really need a good build system, and preferanbvle a CDN so you don'ät have to manage all this
  • the kiving of HTML, CSS and javascript in one file seems like a step backward.
But yes,. I do think web components is the future, I only wonder when we will be there....

Web Components: no size in attachedCallback

When I made the upper88-wordclod web component everything worked fine when I had hard-coded sizes in the beginning. The library worked well and gave the ouput I expected (well, actually a bit better, it's an awesome library).

But when I switched to a size set with CSS it stopped working. After some debugging I realized that the element had no size yet. When attachedCallback(which is the place where you would render your element for the first time) is called, the element has no size set yet, so if your code (or a library you might use) relies on getting the height or width of the element ity won't work.

The recommended way is to remove all sizing from javascript and do it in CSS instead, which will work, but in my case that was not an option. Instead I had to add a call to setTimeout to make the actual rendering take place a bit later, after sizes have been calculated. That seems to work well, you don't actually see the delay and I have had no problem with the solution.

Monday, September 14, 2015

First web component published - upper88-wordcloud

Yesterday I published my first web component, It is a custom component called upper88-wordcloud. Very basic so far, you set the data using the attribute rows and optionally minimum and maximum font size using an options attribute.

An example:
Goldmedals from Track and Field World Championship 2015
Html for this:
<upper88-wordcloud options='{"maxFont":60}' rows='[["Kenya", 7], ["Jamaica", 7], ["United States", 6], ["Great Britain", 4], ["Ethiopia", 3], ["Poland", 3], ["Canada", 2], ["Germany", 2], ["Russia", 2], ["Cuba", 2]]'>
    </upper88-wordcloud%gt;

Size, background color etc are set using CSS.

For rendering the actual word cloud the component uses a library available on GitHub, wordcloud2.js by Tim Dream. A great library, and it supports rendering the wordcloud as a set of HTML span's, which feels like the best way to go for a custom element.

So far the component is very basic and doesn't expose much of the flexibility that the library has. There is definitely room for improvement.

Thursday, June 18, 2015

Polymer 1.0 First impressions

Recently at Google I/O Google announced Polymer 1.0 to be available and ready for production. Since I think that Web Components might very likely be the future for the web and since I tried Polymer 0.5 in some prototypes I thought I would give it a go and try Polymer starter kit.

If you do this make sure your Internet connection is fast: downloading the starter kitr is fast, but then downloading all dependencies takes ages, so this is no quick start.

Once you actually get started it's easy enough to start building. IUt took me just a few hours to build a small site with a few pages, working navigation etc. And it looked pretty OK:

Some reflections:

  • I do like materialized design, clean, simply and looks modern. You get that more or less for free
  • Materialized design are also good for us that are not graphic designers: you get usefulk set of icons to use, and it uses texts a lot, which is good
  • the polymer elements are easy to work with
So creating a site locally was pretty easy. Deploying is another thing, more on that later, when I actually managed to publish my new site.