Hook Tutorial

WARNING! v0.13 was just released, and the tutorial is currently undergoing final testing. It's recommended that you DO NOT follow along until this message is removed (please check back tomorrow).

A tutorial for learning to create your own hooks

Step 4: Make Configurable

In this step we're going to make our hook configurable so it can be overridden on an app-level or on a per-model basis.

You can view the finished code for this step by checking out the step-4 branch.

Hook Configuration

It's good practice to expose configuration values that will allow the user to tailor the behavior of your hook, provided there are configuration values that make sense to expose.

Lore exposes many configuration values, and it's these values that allow the framework to adapt to different needs such as varying API conventions, hash-based or push-state routes, and adding headers to AJAX requests.

In addition to exposing config values, Lore also encourages a pattern cascading overrides, which looks like this:

  1. Hooks decide what values to make configurable, and expose those values to the framework (they also provide a default value)
  2. Config files in the config/ directory (such as config/models.js) allow the user to override those defaults and change the behavior of that hook across the whole application.
  3. Config files in the src/models and src/collections directories allow the user to override the application level configuration and change the behavior a per-model (or per-endpoint) basis.

This approach is designed to require the least amount of configuration by the user, while also providing a high level of adaptability, and we'll be configuring our custom hook to use this approach.

Add Default Values

First, we need to think about what config values to expose. In this case, our hook is going to invoke an action on some interval (every X seconds), so let's make that interval configurable.

Open up polling-hook/src/index.js and modify the defaults object to look like this:

...
  defaults: {
    polling: {
      interval: 3000
    }
  },
...

The default config for Lore is composed by combining all the of the defaults from every hook that gets loaded. To prove that, open the console in the browser's developer tools and type this command:

lore.config.polling.interval

You should see 3000 printed out as the value. At this point we have successfully exposed the polling interval to the user and set it's default value to 3 seconds.

The reason you have to specify polling inside the defaults object is because hooks have the potential to define or override config values outside their own hook.

Add Application-Level Config

Now that we've added a default config to our hook, let's give the user the ability to change the default interval. To do that, create a config file called polling, located at config/polling.js. Paste this code into that file:

module.exports = {

   /**
    * The frequency at which the action should be invoked (in milliseconds)
    */

  interval: 5000

};
export default {

   /**
    * The frequency at which the action should be invoked (in milliseconds)
    */

  interval: 5000

}
export default {

   /**
    * The frequency at which the action should be invoked (in milliseconds)
    */

  interval: 5000

}

With that file in place, the user can now change the interval value and it will override the default provided by the hook. In this case, we're changing the default interval of 3000 milliseconds to 5000 milliseconds.

Add Model-Level Config

While it's great that the user can now override the default at an app level (through config/polling.js) they might need to change that behavior on a per-model basis. For example, maybe some models change more quickly than others, and the user may need to adjust the polling frequency accordingly.

Luckily accommodating that is pretty easy to. Open up models/tweet.js and add a section for a polling config:

module.exports = {
  attributes: {
    // ...
  },

  polling: {
    interval: 2000
  },

  properties: {
    // ...
  }
};
export default {
  attributes: {
    // ...
  },

  polling: {
    interval: 2000
  },

  properties: {
    // ...
  }
}
export default {
  attributes: {
    // ...
  },

  polling: {
    interval: 2000
  },

  properties: {
    // ...
  }
}

Which this change, we've now declared that we want to change the application interval of 5000 milliseconds to be 2000 milliseconds, but only when polling for tweets.

Update Hook to Read Config Values

At this point we've set up our application to declare cascading overrides, but we still need to implement that behavior in our hook. Let's work on that now.

Open up polling-hook/src/index.js and update the load method to look like this:

...
  load: function(lore) {
    // 1. Get the actions so we can make them pollable
    var actions = lore.actions;

    // 2. Get the application level config (defaults + config/polling.js)
    var appConfig = lore.config.polling;

    // 3. Get the model specific configs
    var modelConfigs = lore.loader.loadModels();

    // TODO: create a pollable version of each action
  }
...
...
  load: (lore) => {
    // 1. Get the actions so we can make them pollable
    const actions = lore.actions;

    // 2. Get the application level config (defaults + config/polling.js)
    const appConfig = lore.config.polling;

    // 3. Get the model specific configs
    const modelConfigs = lore.loader.loadModels();

    // TODO: create a pollable version of each action
  }
...
...
  load: (lore) => {
    // 1. Get the actions so we can make them pollable
    const actions = lore.actions;

    // 2. Get the application level config (defaults + config/polling.js)
    const appConfig = lore.config.polling;

    // 3. Get the model specific configs
    const modelConfigs = lore.loader.loadModels();

    // TODO: create a pollable version of each action
  }
...

There's a few things happening here, so let's break down each line to discuss what this code means.

1. Actions

Because we declared a dependency on bindActions, we know that lore.actions is going to exist, and that it will be populated with all of the actions in the application, a structure that looks like this:

lore.actions = {
  ...
  tweet: {
    create: function() {...},
    destroy: function() {...},
    find: function() {...},
    get: function() {...},
    update: function() {...}
  },
  ...
}

2. App Config

The application configuration is composed before any actions load, and is composed of the hook defaults overridden with any values specified in files in the config/ directory. Here we're accessing the config object, and extracting the portion of the configuration specific to polling behavior.

3. Model Configs

To get the model-specific configuration files, we're invoking lore.loader.loadModels().

The framework includes a series of loaders that convert project directories into objects. In this case, we want all the files in src/models/, since those are the config files we need to examine for polling overrides. To get them we can use the models loader invoked by calling lore.loader.loadModels(). This method will return an object that looks like this:

{
  currentUser: {/* files contents */},
  tweet: {/* files contents */},
  user: {/* files contents */}
}

In this case, we don't want lore.models because that contains the actual Model classes the framework uses to make AJAX calls.

The models hook uses this same loader to get the config files, and then converts those files into instances of Model stored under lore.models.

Next Steps

Next we're going to implement the functionality of our hook.