Creating Ember CLI Addon with External Library. Part 2

20 February 2016 ·

In my previous post I covered basics about how to wrap the third-party library in Ember CLI addon, so it can be reusable, and used in Ember-way.

Note: This article will cover only a few issues that you might face during addon development, so I wouldn’t say that it’s a tutorial.

In this part I will try to finish the ember-cli-webfontloader addon by doing following:

Problem

After reading the WebFontLoader docs, I found that they are using probably the strangest event system you can find out there. The problem is that you need to use either a global variable or you need to pass configuration directly to .load() function which we use to load fonts.

In our case, we want the configuration to contain only the fonts we want to load, so we need to find a way of having something more flexible.

My first idea was to create a service that will attach its generic functions to configuration object, which will call our “real” event callbacks on event. But unfortunately this idea failed after a few atempts as I couldn’t find a way of injecting a service to initializer, so I decided to go a bit lame way, I decided to extend WebFont object.

We need to track somehow the state of WebFontLoader, add event handlers and if the event is already active (such as active, loading etc.) we need to be able to run the callback immediately. I will implement two functions: on(string event, function callback, boolean runIfActive) and off(string event, function callback).

Idea is such that on() function will add callbacks to the list and when an event is fired, the main event handler will execute all callbacks from the appropriate list.

The full list of events is:

Here I will focus on covering the first three events: loading, active, inactive.

Solution

Let’s outline the extension to the library:


// ember-cli-webfontloader/app/initializers/ember-cli-webfontloader.js
export function initialize(...) {
  ...
  let events = {
      // current state of WebFontLoader
      state: null,

      // Collection of callback functions to be called
      // when the appropriate event is fired
      eventHandlers: {
          active: [],
          inactive: [],
          loading: [],
      },

      // Real `active` callback
      onActive() {  events.state = 'active'; },

      // Real `inactive` callback
      onInactive() {  events.state = 'inactive'; },

      // Real `loading` callback
      onLoading() {  events.state = 'loading'; }
  };
  ...
}

To extend the plugin in a good manner, we need to have some sort of an isolated container. Let’s call ours __events__:

// ember-cli-webfontloader/app/initializers/ember-cli-webfontloader.js
export function initialize(...) {
  ...
  // Extending WebFont
  WebFont.__events__ = events;
  ...
}

And let’s define our on() function directly on the WebFont object so in can be accessable from outer world:

// ember-cli-webfontloader/app/initializers/ember-cli-webfontloader.js
export function initialize(...) {
  ...
  // Adds a callback to the eventHandlers[event] list and 
  // if "toRun" is true and the current state is event name,
  // we need to run callback function after we add it to the list
  WebFont.on = (event, callback, toRun) => {
    // Adding new handler to the list
    WebFont.__events__.eventHandlers[event].push(callback);

    // Checking if we should run it now
    if (toRun && event === WebFont.__events__.state) {
        callback();
    }
  };
  ...
}

Cool, looks promising. Now we need to attach these onActive, onInactive and onLoading functions to the WebFontLoader configuration object.

// ember-cli-webfontloader/app/initializers/ember-cli-webfontloader.js
export function initialize(...) {
    ...
 // Getting all font families that should be loaded from
    // configuration
    let config = Ember.get(ENV, 'webFontConfig') || {};

    config.loading = events.onLoading;
    config.active = events.onActive;
    config.inactive = events.onInactive;

    WebFont.load(config);
    ...
}

After these steps we can inject the WebFont object to the component/route/whatever and do the following:

// ember-cli-webfontloader/tests/dummy/app/routes/application.js
import Ember from 'ember';
import { WebFont } from 'webfontloader';

export default Ember.Route.extend({
    actions: {
        didTransition() {
            WebFont.on('inactive', () => {
                console.log('inactive!');
            }, true);
            WebFont.on('loading', () => {
                console.log('loading!');
            }, true);
            WebFont.on('active', () => {
                console.log('active!');
            }, true);
        }
    }
});

By doing this we will create event handlers that will be executed on each of three events we covered here: loading, active, inactive.

Good Practice

As some of you could notice, we’re injecting our initializer directly to the application namespace, which is not a good idea, let’s keep our application namespace for an application.

We’ll create a new file addon/initializers/ember-cli-webfontloader.js and copy the contents of the according file from app/ folder and switch from exporting an initializer to export a function that will accept a configuration file, to which we have access only from app namespace:

// addon/initializers/ember-cli-webfontloader.js
import { WebFont } from 'webfontloader';

export default function setupWebFont(config) {
    // Extending WebFont
    let events = {
        ...
    };
    
    // Setting up our functions
    config.loading      = events.onLoading;
    config.active       = events.onActive;
    config.inactive     = events.onInactive;
    config.fontloading  = events.onFontloading;
    config.fontactive   = events.onFontactive;    
    config.fontinactive = events.onFontinactive;

    // Load with an updated configuration
    WebFont.load(config);
}

After doing that we still need to export an initializer to app namespace, but we’ll do this more gently:

// app/initializers/ember-cli-webfontloader.js
import Ember from 'ember';
import ENV from '../config/environment'; 
import setupWebFont from 'ember-cli-webfontloader/initializers/ember-cli-webfontloader';
export default { 
    name: 'ember-cli-webfontloader',
    initialize() {
        const config = Ember.get(ENV, 'webFontConfig') || {};
        setupWebFont(config);
    }
};

Ok, that’s it, we keep our actual initializer in addon/ folder and here we just pass environment configuration.

Done.

If you have any comments and/or found some lame mistakes here, please let me know in the comments section below.

Tags:

Might also be interesting for you

Use Elm as a Reducer in Redux

Want to use Elm in your project, but a bit hesitant going cold turkey? In this blog post I'll try to show how you can use the best from both worlds bulletproof Elm logic and ridiculously rich React components library.

Open RSS Reader

I'm starting a new open-source project "Open RSS Reader" that will be modular app so if you don't like the existing clients, you'll be able to quickly build your own. It's gonna be both open source and free to use.

SotM: pet - Awesome Command-line Snippet Manager

`pet` is a simple command-line snippet manager. It allows you to write and store snippets for the command-line, so you don't need to remember all the commands you frequently use in you terminal. It can sync your snippets to the GitHub Gist, so you can always take your snippets with you.

5 Reasons To Give Linux A Try

This time I want to share 5 reasons why I advocate for using Linux whenever I have a chance, and why you should give it a try too.

Comments

comments powered by Disqus