Creating a Battery viz Using Node.js: Client
In the first part of this mini-series, we discussed the details of the service we’re building and what you’ll learn. We then covered why we need a server and why I chose to create a RESTful service. While discussing how to develop the server, I took the chance to discuss how you can identify the current operating system and also how to use Node.js to run commands on it.
In this second and final part of this series, you’ll discover how to build the client part to present the information to the users in a nice way. To achieve this goal, we should update the status of the battery every X minutes (or seconds), without reloading the page. Moreover, we should be able to pause/resume updates, to avoid flooding our system when we don’t need the information, or even when we are not looking at the page. To do that, we will:
- Schedule Ajax calls to our backend service over regular intervals of time;
- Use a declarative framework that updates the DOM automatically and efficiently in response to changes to the data;
- Use some jQuery utility function to make our life easier;
- Use some nice images and CSS to make the dashboard visual appealing (as a bonus!).
Reactive Design
Discussing Ajax and asynchronous calls is certainly out of scope of this article (I’ll provide a few useful links at the end of the post). For our purpose we can even treat them as black boxes that allow us to ask the server for some data, and execute some action once the data are sent back.
Let’s take a minute, instead, to discuss reactive design and declarative frameworks.
An HTML page is by default a static entity. That means that for a pure HTML page the content shown on the page remains the same every time it’s rendered in a browser. However, we know that with the use of JavaScript and maybe some templating libraries like Mustache we can update them dynamically.
There are many libraries that help developers binding data to DOM nodes. Most of them use JavaScript to describe the DOM elements to which the data should be translated, and requires updates to the page to be triggered manually (via JavaScript). So, we end up relying on the application’s logic for deciding when the visualization should be updated and what changes ought to be made in response to data changes.
Declarative frameworks bind the data to DOM elements, and automatically update the DOM, every time the data changes. This binding is also provided using templates in the presentation (the HTML markup) rather than in JavaScript.
The added value of these frameworks can be identified in a few key points:
- They enforce a greater degree of separation between content and presentation. This is achieved by letting you define in the presentation layer binding for data, event handlers and even the views’ structure (like for iterative and composite objects, for example tables);
- They provide an easy way to keep your data model and your presentation in sync;
- They generally do it in an extremely efficient way, making sure to reflow only the minimum possible subset of your DOM tree. On that regard, keep in mind that reflowing and repainting are usually bottlenecks for client-side browser applications.
Ractive.js
For Ractive.js, the library we are going to use, the synchronization between data and DOM is obtained through Container objects. The library creates objects which wrap around the data. These objects have access to the data, so every time you set or get any property, the library can capture your action and internally broadcast it to all the subscribers.
Hands-on
Now that we’ve seen what Ractive.js is useful for, It’s time to add our first Ractive template to our page. To do that you can add a script tag with an ID of your choice anywhere inside the <body>
. I suggest you to choose the ID wisely as we’ll need it later. We’ll also need to add a type='text/ractive'
attribute:
<script id='meterVizTemplate' type='text/ractive'></script>
type='text/ractive'
would actually make no sense to your browser since it’ll ignore the script unless you add Ractive’s script to your page as well:
<script src='http://cdn.ractivejs.org/latest/ractive.js'></script>
Now, inside the Ractive script you can add HTML tags and template variables and conditionals/loops. Ractive.js will take care of evaluating everything inside the {{}}
groups.
<script id='meterVizTemplate' type='text/ractive'>
{{#batteryState}}
<br>
<div class='battery-div'>
<div class='battery-shell'>
<div class='battery-percent-text'>{{batteryPercent.toFixed(1) + '%'}}</div>
</div>
<div class='battery-level'>
<div class='battery-mask' style="width:{{(100 - batteryPercent) + '%'}};">
</div>
</div>
{{#batteryCharging}}
<div class='battery-plug' intro-outro='fade:1000'></div>
{{/batteryCharging}}
{{#batteryPercent <= batteryRedThreshold}}
<div class='battery-warning' intro-outro='fade:1000'></div>
{{/batteryLife}}
</div>
<br>
<br>
<span class='key'>Battery state:</span> <span class='value {{batteryStateClass(batteryState)}}'>{{batteryState}}</span>
<br>
{{#batteryLife}}
<span class='key'>Time to empty:</span> <span class='value {{batteryLifeClass(batteryPercent)}}'>{{batteryLife}}</span>
{{/batteryLife}}
{{/batteryState}}
{{^batteryState}}
<br>
LOADING...
{{/batteryState}}
</script>
In the example above, you can see:
- Variables:
{{batteryState}}
- Conditionals:
{{#batteryState}}
- Function invocations:
{{batteryStateClass(batteryState)}}
In order to have these things working, we have to add some binding in JavaScript. To do so, we have to create a new Ractive.js object:
ractive = new Ractive({
el: 'panels',
template: '#meterVizTemplate',
data: {
// Percentage at which the battery goes to 'red' zone (export for Ractive templates)
batteryRedThreshold: BATTERY_RED_THRESHOLD,
// Percentage at which the battery enters 'yellow' zone (export for Ractive templates)
batteryYellowThreshold: BATTERY_YELLOW_THRESHOLD,
// The capacity of the battery, in percentage. Initially empty
batteryPercent: NaN,
// How much more time can the battery last?
batteryLife: "",
// True <=> the update daemon for the battery has been paused
batteryPaused: false,
// True <=> the update daemon for the battery has reported an error at its last try
batteryUpdateError: false,
// Is the battery connected to power?
batteryCharging: false,
batteryStateClass: function (state) {
return state === 'discharging' ? BATTERY_RED_CLASS : BATTERY_GREEN_CLASS;
},
batteryLifeClass: function (percent) {
return percent <= BATTERY_RED_THRESHOLD ? BATTERY_RED_CLASS : (percent <= BATTERY_YELLOW_THRESHOLD ? BATTERY_YELLOW_CLASS : BATTERY_GREEN_CLASS);
}
}
});
The options we pass to the constructor are quite important. Firstly, el
needs to match the ID of a DOM element inside which Ractive.js will render the template. In this case, we need to add a div
to out HTML page:
<div id='panels'></div>
The point where you insert this tag matters. It will be the parent element for all the elements rendered by Ractive.js templating system. The second important parameter you have to be careful about is template
. Its value will have to match the ID of the text/ractive
script on your page. Finally, we assign to data
an object whose keys are variables names we reference in our template, or functions we call.
With Ractive.js, we can even define custom events to which the library will respond:
ractive.on({
"battery-pause": function () {
clearInterval(batteryUpdateTimerId);
ractive.set('batteryPaused', true);
},
"battery-play": function () {
updateBatteryStatus(); //Checks the status immediately, then starts the daemon
batteryUpdateTimerId = setInterval(updateBatteryStatus, BATTERY_CHECK_INTERVAL);
ractive.set('batteryPaused', false);
}
});
In a few lines, we have set up a mechanism to pause/resume our updates. However, we still need to define the updateBatteryStatus()
function.
Asynchronously Retrieving Data
As promised, here it is a function that takes care of retrieving data from our REST service. By using the jQuery Deferred object, we set up a callback to be invoked as soon as some data is received from the server. Since we are also using Ractive.js inside this callback, we won’t have to go through the logic of how we updated the presentation layer. In fact, we just update the value of variables used in the template script, and Ractive.js will take care of everything.
What I’ve just described is implemented by the code reported below:
function updateBatteryStatus() {
$.getJSON(BATTERY_SERVICE_URL)
.then(function (battery) {
ractive.set('batteryUpdateError', false);
var batteryLife = battery.timeToEmpty,
batteryState = battery.state;
ractive.animate('batteryPercent', parseInt(battery.percentage, 10), {
easing: 'easeOut'
});
ractive.set('batteryLife', batteryLife);
ractive.set('batteryState', batteryState);
ractive.set('batteryCharging', batteryState !== BATTERY_STATE_DISCHARGING);
}).fail(function () {
ractive.set('batteryUpdateError', true);
});
}
//Start the daemons that will check the battery and networks status...
batteryUpdateTimerId = setInterval(updateBatteryStatus, BATTERY_CHECK_INTERVAL);
Putting It All Together
There is, of course, some more wiring to put in place to make all of this work together. We skipped altogether the design of the dashboard UX. That’s ultimately up to you, once you get how to make it work with the templating system! For example, how cool would it be if we could have the charge percentage shown both as text and visually with some cool power indicator, using images and animations? With Ractive.js, it is not so hard! Take a look at the final result:
If you’d like to inspect the code, you can once again find it on GitHub.
Conclusions
Our multi-platform battery dashboard should be ready to go now. But this should be a starting point rather than a final result, and the important points I hope you learned about along the way are:
- How to set up an HTTP server using Node.js
- RESTful APIs
- How to run OS terminal commands on a Node.js server
- Basics of declarative frameworks and Ractive.js in particular
If you want to take it to the next level, my advice is to start experimenting on these tools and dig the Net to deepen knowledge in these areas. If you want to deepen the topics covered in this article, I strongly suggest you to take a look at these good resources:
- Architectural Styles and the Design of Network-based Software Architectures
- Guidelines for creating a RESTful API
- What are the advantages/disadvantages of using REST API over native libraries?
- Template method pattern
- Asynchronous requests in JavaScript
- Crockford on JavaScript – Episode IV: The Metamorphosis of Ajax – great insight, as usual, plus a super funny story on the origins of the term Ajax, as a bonus!
- jQuery
$.getJSON
method - RactiveJs tutorial