pebble/devsite/source/_posts/2016-02-19-JavaScript-Libraries-for-C-Developers-pt-1.md
2025-02-24 18:58:29 -08:00

8.5 KiB
Raw Permalink Blame History

title author tags
JavaScript Libraries for C Developers (pt. 1) cat
Freshly Baked
Beautiful Code

One of the exciting changes introduced in SDK 3.9 sdk 3.9 was support for including [Multiple JavaScript Files] js blog to your projects. While this feature doesnt allow you to run any JavaScript you previously couldnt, it makes organizing your PebbleKit JS code a heck of a lot easier.

In this blog post, we'll look at how to refactor some exisiting JavaScript 'library code' into an actual module for PebbleKit JS, making it simpler to use and share!

OWM-Weather

A few months ago we published a very simple 'library' for working with the [Open Weather Map] owm API. The JavaScript side of the code takes care of grabbing the users location with the built in [geolocation.getCurrentPosition] getcurrentposition API, then fetches weather information for the returned position.

The initial version of the code were working with can be found [here] owm lib 0.

Creating JavaScript Classes

Note: While JavaScript (with ECMAS5) does not allow for true "Classes", JavaScript does a support a mechanism that allows you write Class-like code. For convience, we'll be refering to these objects as 'classes' throughout this post.

The first thing we want to do is wrap our library code in an Object constructor function, which is what allows us to treat OWMWeather as a class, and rewrite our functions so they're properties belonging to this (an instantiated version of the object). If you're interested in diving a bit deeper into how this works, take a look at Pivotal's great blog post about [JavaScript constructors, prototypes, and the 'new' keyword] pivotal blog.

var OWMWeather = function() {
  this.owmWeatherAPIKey = '';

  this.owmWeatherXHR = function(url, type, callback) {
    //...
  };
  //...
};

Well also need to change our JS application code to construct a new OWMWeather object, and change our reference of appMessageHandler() in the in the Pebble.addEventListener('appmessage', ...) callback to owmWeather.appMessageHandler().

var owmWeather = new OWMWeather();

Pebble.addEventListener('ready', function(e) {
  console.log('PebbleKit JS ready!');
});

Pebble.addEventListener('appmessage', function(e) {
  console.log('appmessage: ' + JSON.stringify(e.payload));
  owmWeather.appMessageHandler(e);
});

Using the .bind() function

If we try to run our code at this point in time, we're going to run into a slew of errors because we've changed the scope of the functions, but we haven't updated the places where we call them. We need to change owmWeatherFunctionName() to this.owmWeatherFunctionName() (same goes for the API key). Here's what the new owmWeatherLocationSuccess method looks like (changes are indicated with in-line comments):

this.owmWeatherLocationSuccess = function(pos) {
  // Change owmWeatherAPIKey to this.owmWeatherAPIKey
  var url = 'http://api.openweathermap.org/data/2.5/weather?' +
    'lat=' + pos.coords.latitude + '&lon=' + pos.coords.longitude +
    '&appid=' + this.owmWeatherAPIKey;
  console.log('owm-weather: Location success. Contacting OpenWeatherMap.org..');

  this.owmWeatherXHR(url, 'GET', function(responseText) {
    console.log('owm-weather: Got API response!');
    if(responseText.length > 100) {
      // Change owmWeatherSendToPebble(..) to this.owmWeatherSendToPebble(..)
      this.owmWeatherSendToPebble(JSON.parse(responseText));
    } else {
      console.log('owm-weather: API response was bad. Wrong API key?');
      Pebble.sendAppMessage({ 'OWMWeatherAppMessageKeyBadKey': 1 });
    }
  // Add .bind(this) to the closing brace of the callback
  }.bind(this));
};

An observant developer might notice that along with changing owmWeatherXHR to this.owmWeatherXHR, we've also added .bind(this) to the end of the callback function.

We're not going to dive too deeply into how this and bind works (that's a whole blog post on its own), but what I will say is that the bind method can be thought of as modifying a function so that it will, when invoked, have its this keyword set to the provided parameter.

If you want to learn more about JavaScript Objects, scope, and bind, I will encourage you to read [You Don't Know JS: this & Object Prototypes] js book.

We'll want use bind(this) (where this will be the instance of the OWMWeather class) whenever we're using an OWMWeather method as a callback from within the OWMWeather code.

navigator.geolocation.getCurrentPosition(
  this.owmWeatherLocationSuccess.bind(this),
  this.owmWeatherLocationError.bind(this), {
    timeout: 15000,
    maximumAge: 60000
});

At this point, our code should look like [this] owm lib 1.

Using module.exports

The last thing we want to do to make this into a handy module is extract the code into its own file (/src/js/lib/owm_weather.js), and use the [module.exports] module exports API to export our OWMWeather class.

var OWMWeather = new function() {
  //...
};

module.exports = OWMWeather;

In order to use this in our PebbleKit JS application, we need to do a couple things..

If you're using CloudPebble:

  • Change JS Handling to CommonJS-style in the project settings

If you're using the SDK:

  • Update our appinfo.json to include 'enableMultiJS': true if it isn't already there

  • Rename src/js/pebble-js-app.js to src/js/app.js

Once we've made these changes to our files, we're ready to include OWMWeather in our app.js file with the require API require api.

var OWMWeather = require('./lib/owm_weather.js');
var owmWeather = new OWMWeather();

Pebble.addEventListener('ready', function(e) {
  console.log('PebbleKit JS ready!');
});

Pebble.addEventListener('appmessage', function(e) {
  console.log('appmessage: ' + JSON.stringify(e.payload));
  owmWeather.appMessageHandler(e);
});

At this point, our code should something look like this.

Refactoring Variable and Function Names

Since weve moved all of the OWMWeather code into a class, we can safely remove the owmWeather prefixes on all of our methods and properties. While were at it, we're also going to rename functions and properties that are intended to be private to begin with an _, which is fairly common practice:

var OWMWeather = function() {
  this._apiKey = '';

  this._xhrWrapper = function(url, type, callback) {
    //...
  };
  //...
};

What's next..

And that's it - we've successfuly refactored our code into a module that should be easier for developers to use and share (which is exactly what we want). You can view the full source code for this blog post here.

In the next blog post, we'll take this library a step further and look at how you can abstract away the need for appKeys in your appinfo.json file to make working with the library even easier...

Until then, happy hacking!