Build a Real Battery Status Web Component with Polymer

Building an battery status Polymer Web Component

What are we building?

We will build a simple Battery Web Component using Polymer and our final goal looks like this:

Source code of this battery component can be found at https://github.com/nemanja-popovic/simple-battery-component.

Polymer

Polymer is an open source library built by Google to make using of Web components easier. Web components are a collection of specifications that give possibility to developers to build their web applications as a set of reusable components.

More about Web Components can be found here, and more about Polymer project can be found here.

Battery API

Battery API is one of great features that is providing us information about battery's current status. It also has events that are fired when changes of the battery level or status take place.

Properties that battery object has are:

  • level: Represents the current battery level scaled from 0 to 1.0. The attribute must be set to 0 if the system's battery is empty, and to 1.0 if the battery is full.
  • charging: Represents if the system's battery is charging or not. Value is false when battery is discharging and true in all other cases (full, charging, no battery, etc.).
  • chargingTime: Represents the time remaining in seconds until the system's battery is fully charged.
  • dischargingTime: Represents the time remaining in seconds until the system's battery is completely discharged.

And battery events are including ondischargingtimechange, onlevelchange, onchargingchange and onchargingtimechange.

Browser support

At the moment Battery API is supported by Firefox, Chrome and Opera. More details about supported browsers and their versions can be found at http://caniuse.com/#feat=battery-status.

How to use Battery API

In order to get battery status we need to call window.navigator.getBattery() method that is returning promise. Here is a small example showing how to get details about battery:

window.navigator.getBattery().then(function(battery) {

    // A few useful battery properties
    console.info('Battery charging: ', battery.charging); // false
    console.info('Battery level: ', (battery.level * 100) + '%'); // 65%
    console.info('Battery discharging time: ', battery.dischargingTime);

    // Few event listeners
    battery.addEventListener('chargingchange', function(e) {
        console.info('Battery charge change: ', battery.charging);
    }, false);
    battery.addEventListener('chargingtimechange', function(e) {
        console.info('Battery charge time change: ', battery.chargingTime);
    }, false);
    battery.addEventListener('dischargingtimechange', function(e) {
        console.info('Battery discharging time change: ', battery.dischargingTime);
    }, false);
    battery.addEventListener('levelchange', function(e) {
        console.info('Battery level change: ', battery.level);
    }, false);
});

For more details about Battery API please visit https://www.w3.org/TR/battery-status/.

Setting up

In this tutorial we will be using polymer seed element. Note: You can also start without seed element repository and in that case not all the steps will be the same. Polymer seed element can be found at https://github.com/PolymerElements/seed-element.

First we should clone this repository. Create a new folder called simple-battery-status and navigate to it. When navigated to it clone the repository using the following command:

$ git clone https://github.com/PolymerElements/seed-element.git .

When repository is cloned install all bower dependencies. If you don't have Bower installed do that using the following command:

$ npm install -g bower

Then, go ahead and install the element's dependencies:

$ bower install

After all this our element files are created. Let's change all occurrences of seed-element to our simple-battery-status element. Make sure that you changed it in all places and also that you renamed all seed-element files.

In order to take a look at current state of the element we should use polyserve. Polyserve is a simple web server used to run components locally. To use polyserve we first need to install it via:

$ npm install -g polyserve

After polyserve is installed we can run:

$ polyserve -o

This command will open our default browser and show us documentation page of seed element (if the command didn't open browser then you can do that manually as our element should be at http://localhost:8080/components/simple-battery-status/). In the top right corner of the opened page DOCS and DEMO buttons are shown. Current seed element is at demo page and we can navigate there clicking on DEMO button. We can see example component provided by Polymer team. Soon here we will see our battery element.

So let's add few simple batteries to our DEMO page. If you open index.html file under demo folder you will see this demo elements under body tag. So replace all this code under body element and add this:

  <h1>Simple battery status demo</h1>

  <style>
    .example{
      width: 100px;
      margin: 0 auto;
      padding: 10px;
    }
  </style>

  <div class="example">
    <simple-battery-status level="85"></simple-battery-status>
  </div>

  <div class="example">
    <simple-battery-status detect></simple-battery-status>
  </div>

This way, when we start implementing our component DEMO part will show two batteries, one showing 85% and the other one showing current battery level of your device.

Simple battery status Web Component

Let's open simple-battery-status.html file. Here you will see initial seed implementation of Polymer web component with some demo properties and methods. First we will get rid of code that is not needed anymore. After doing so our simple-battery-status.html will look like this:

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

<!--
Element used to show battery status using Battery API.

Example:

    <simple-battery-status></simple-battery-status>

@demo demo/index.html
-->

<dom-module id="simple-battery-status">
    <template>
        <style></style>

    </template>

    <script>
        Polymer({
            is: 'simple-battery-status',

            properties: { },

            // Element Lifecycle

            attached: function() {
                // `attached` fires once the element and its parents have been inserted
                // into a document.
                //
                // This is a good place to perform any work related to your element's
                // visual state or active behavior (measuring sizes, beginning animations,
                // loading resources, etc).
            },

            // Element Behavior

        });
    </script>
</dom-module>

If we now refresh http://localhost:8080/components/simple-battery-status/ and go to demo page we will see only title.

So let's start adding features to our element. First let's make simple battery looking shape. We can do that by adding simple html and css to our element. These are added to template tag inside our element definition and after adding them our template will look like:

<template>
    <style>
        .container {
            font-family: Roboto, Noto, 'Trebuchet MS', sans-serif;
        }

        #batteryEnd {
            width: 15px;
        }

        #batteryEnd div {
            width: 35px;
            height: 15px;
            margin-left: 30px;
            background-color: #CCC;
        }

        #batteryBody {
            width: 75px;
            height: 200px;
            border: 10px #CCC solid;
        }
    </style>
    <div class="container">
        <div id="batteryEnd">
            <div></div>
        </div>
        <div id="batteryBody">
        </div>
    </div>
</template>

If we now take a look in our component we will see two "empty" batteries.

So let's add indicator element to our template:

<div id="batteryBody">
    <div id="indicator"></div>
</div>

Also let's add simple css for that indicator.

#indicator {
    width: 100%;
    background-color: var(--battery-status-indicator-background, #FFF);
}

Note that background color is defined as custom css property. This way we allow theming of our element as now this property can be defined from outside of our component. More details about this can be found here.

It is time to start using Battery API. First we will just show current level of battery. Let's first define level property to our element, so now properties object of our element will look like this:

properties: { 
    level: {
        type: Number,
        value: 50
    }
},

We defined level property that is type of Number and we are setting its default value to 50.

Also, we would like to have different colors for different levels of battery and because of that we will now add five different classes. This colors are also going to be custom css properties so we can enable theming of our element and enable changing of this colors from outside of our element.

.full {
    background-color: var(--battery-status-full-color, #BFFF00);
}

.good {
    background-color: var(--battery-status-good-color, #BDE24D);
}

.medium {
    background-color: var(--battery-status-medium-color, #FFFF00);
}

.low {
    background-color: var(--battery-status-low-color, #FFC800);
}

.empty {
    background-color: var(--battery-status-empty-color, #FF5200);
}

In order to show correct color for correct value, we will call calculateLevelStyle method every time level is changed. This method will return class name that is showing current state of our battery level and we will show code for this method a bit later.

<div id="batteryBody" class$="[[_calculateLevelStyle(level)]]">
    <div id="indicator"></div>
</div>

The last thing we need to do is update look of our simple battery every time level is changed. For that we can start observing level value, and when this value is changed we can call method that will check new level value and based on that set different state. So let's update our level property definition:

properties: {
    /**
     * `level` indicates level of battery 
     */
    level: {
        type: Number,
        value: 50,
        observer: '_levelChanged'
    }
}

And let's define function __levelChanged under // Element Behavior part of our file:

_levelChanged: function(){
    var self = this;

    //Make sure that it is not lower than 0
    if(self.level < 0){
        self.level = 0;
    }

    self.$.indicator.style.height = (100 - self.level) + '%';  
    self._calculateLevelStyle();
},

_calculateLevelStyle: function(level){
    if(level <= 10){
        return 'empty';
    }
    else if(level <= 20){
        return 'low';
    }
    else if(level <= 50){
        return 'medium';
    }
    else if(level <= 80){
        return 'good';
    }

    return 'full';
}

NOTE: Make sure that after attached function , is added.

If we now refresh our components demo page we should see something like this:

As you can see we do see that battery level is different, but we don't know exact level. Our battery component would be much nicer if we have level shown. So the last thing we will do in order to improve look and feel of our component is showing percentage of current level. For that we need to add additional element and styling for it. We will add element with id status under indicator element:

<div id="batteryBody" class$="[[_calculateLevelStyle(level)]]">
    <div id="indicator">
        <span id="status">[[level]]%</span>
    </div>
</div>

And style for this element will be:

#status {
    top: 90px;
    position: relative;
    text-align: center;
    width: 100%;
    font-size: 20px;
    font-weight: 700;
    display: block;
}

If we take a look now at current progress we should see initial implementation of our battery component.

Real battery status

So far the battery value was not the real value of our device battery level. In order to get real battery level we should add code that will get battery status in attached event of element and then get current level. We want to make this as an option for users. Because of that we will first add detect property and then start using Battery API is this property is provided as attribute when element is created. Property definition will be:

properties: {
    /**
     * `level` indicates level of battery 
     */
    level: {
        type: Number,
        value: 50,
        observer: '_levelChanged'
    },
    /**
    * `detect` indicates if the component will use Battery API to get 
    * current _state or just show provided value. When detect attribute 
    * provided component will use Battery API
    */
    detect: {
        type: Boolean,
        value: false
    }
},

and this is how the code in attached function will look like:

attached: function() {
    // `attached` fires once the element and its parents have been inserted
    // into a document.
    //
    // This is a good place to perform any work related to your element's
    // visual state or active behavior (measuring sizes, beginning animations,
    // loading resources, etc).
    var self = this;

    //Show initial level of battery
    self.$.indicator.style.height = self.level + '%'; 

    if(!self.detect){
        return;
    }

    navigator.getBattery()
        .then(function (battery) {             
            //Assign level property 
            self.level = battery.level * 100;
        });
}

When we get battery we are setting level property that we defined earlier to battery.level * 100. We are doing this because battery.level has values between 0 and 1.

Now if we take a look in our demo elements we will see our battery level. But level is not changing as our battery level changes. We should add that also. So in the attached method, we should also add event listener to levelchange event that will update our element. This part of code looks like:

navigator.getBattery()
    .then(function (battery) {             
        //Assign level property 
        self.level = battery.level * 100;

        //Listen for level change event
        battery.addEventListener('levelchange', function(e) {
            self.level = battery.level * 100;
        }, false); 
    });

Result

After all this code implemented we should go and take a look in our battery demo page. If we now again run polyserve -o command, and open DEMO part of our component we will see two battery elements. As our battery level changes level in second battery element will change also. Great job!

So now our Polymer Web component element is finished and we can share it easily with everyone!

Now you saw how easy is to build custom elements and hopefully you will start building many cool Web components with Polymer and help in making the world a better place for web developers one component at a time.

Nemanja Popovic

Nemanja is web developer who spends most of his time working with different technologies in different areas of Computer science. When not doing that he is either watching some football or playing some football (European one). Also, fell free to contact him via LinkedIn.