Modifiers

Augment existing time periods with new behaviors.

Overview

With Later, not only can you write your own custom time periods, you can also write custom modifiers that can change the behavior of existing time periods. The modifiers sit in between the scheduling engine and the time period allowing you to intercept and modify the results that are returned by the time period.


Modifies are specified by attaching _(modifier-id) to the time period id that you want to modify. The same time period can be used with different modifiers within the same schedule.

after (_a)

Modifies the corresponding time period such that all values after and including the specified value is considered valid. This modifier can be used with any time period. Useful for creating more compact schedules when a time period has a lot of consecutive valid values.

  // all hours after 5:00pm will be valid
  var sched = {schedules: [{h_a: [17]}]};

  // equivalent to
  var sched = {schedules: [{h: [17,18,19,20,21,22,23]}]};

before (_b)

Modifies the corresponding time period such that all values before (but not including) the specified value is considered valid. This modifier can be used with any time period. Useful for creating more compact schedules when a time period has a lot of consecutive valid values.

  // all hours before 5:00pm will be valid
  var sched = {schedules: [{h_b: [17]}]};

  // equivalent to
  var sched = {schedules: [{h: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}]};

Writing a custom modifier

Custom modifiers are very similar to custom time periods and share the same interface. To keep things simple, we'll walk through creating a modifier to change the month period to work with values 0-11 instead of 1-12.


The first step is to create a name and id for the modifier and add it to the modifier namespace. Modifiers take the time period that is being modified along with the specified values as arguments.

  later.modifier.month = later.modifier.m = function(period, values) {
    if(period.name !== 'month') {
      throw new Error('Month modifier only works with months!');
    }

    return {
      // interface implementation goes here
    };
  };

Next, we need to implement the same interface that time periods implement and modify them to work with the new set of values. First we will just modify the name to include a reference to the modifier.

  name: 'reIndexed ' + period.name,

The range is the same, so we just pass it through.

  range: period.range,

We then modify the val returned by subtracting 1 since our new indicies are one less than the original ones.

  val: function(d) { return period.val(d) - 1; },

Next, isValid is modified by tweaking the value that is passed in so that it is in the range that the month time period expects.

  isValid: function(d, val) { return period.isValid(d, val+1); },

The extent also needs to be modified to reflect the new extent that goes from 0-11. Now that the extent starts at 0, the schedule engine will no longer assume that a 0 value means 'last'. There is nothing else that we need to do to correct for that behavior.

  extent: function(d) { return [0, 11]; },

The start and end dates for the month will be the same, so we can just pass those through to the time period without modification.

  start: period.start,
  end: period.end,

Finally, the values passed into next and prev need to be updated to be in the range that the month time period expects.

  next: function(d, val) { return period.next(d, val+1); },
  prev: function(d, val) { return period.prev(d, val+1); }

Full implementation

Here is the code for the completed example. To use the modifier, just add this code after including Later into your project and before you use it in any schedules.

  later.modifier.month = later.modifier.m = function(period, values) {
    if(period.name !== 'month') {
      throw new Error('Month modifier only works with months!');
    }

    return {
      name:     'reIndexed ' + period.name,
      range:    period.range,
      val:      function(d) { return period.val(d) - 1; },
      isValid:  function(d, val) { return period.isValid(d, val+1); },
      extent:   function(d) { return [0, 11]; },
      start:    period.start,
      end:      period.end,
      next:     function(d, val) { return period.next(d, val+1); },
      prev:     function(d, val) { return period.prev(d, val+1); }
    };
  };

Usage

Using the custom modifier is exactly the same as using a built-in modifier.

  // wihtout our modifier, 2 means February
  var sched1 = {schedules: [{M: [2]}]};

  later.schedule(sched1).next(1, new Date(2013, 3, 21));
  --> Sat, 01 Feb 2014 00:00:00 GMT

  // use our new modifier so that 2 now means March
  var sched = later.parse.recur().customModifier('m', 2).month();

  next = later.schedule(sched2).next(1, new Date(2013, 3, 21));
  --> Sat, 01 Mar 2014 00:00:00 GMT