If You Love It
So Much

Why Don’t You Write
a Wrapper Around It?

 

Eric Shepherd (@arkitrave)
Gilt Groupe (@gilttech)
jQuery Conference 2013

Third-Party Code

When to Use It

  • Is complexity sufficient to warrant the dependency?
  • Is the domain core to your business?
  • Is the right licensing available?

Whether to Wrap It

  • Is it maintained, tested, and documented?
  • Does it do everything you need?
  • Does the API style match your own?

Doesn’t matter; the answer is always yes!

Wrappers


function ThirdPartyFoo (bar) {
  this.baz = bar;
}

function myFooWrapperFactory (bar) {
  return new ThirdPartyFoo(bar);
}

var myFoo = myFooWrapperFactory('boom');
          

How to Wrap It

  • Free your mind!
  • Don’t assume a 1:1 relationship
  • You are using underlying logic, not the API
  • If you wrote an API from scratch...?

What to Consider when Wrapping?

What to Consider when Wrapping?

1. API Pattern

Aside: jQuery


var foo = $('.bar');

foo.height(); /* gets */
foo.height(500); /* sets */

foo.innerHeight(); /* gets */
foo.innerHeight(500); /* undocumented */
          

Aside: jQuery


foo.data('bar'); /* gets */
foo.data('bar', 'baz'); /* sets */
foo.removeData('bar'); /* (asymmetric) */
          

Why not getData, setData, removeData?

Aside: jQuery


$el.addClass('boom');
$el.removeClass('boom');
$el.hasClass('boom'); /* false */
          

Class methods are great,
BECAUSE CLASS IS RESERVED!

Hierarchy


var m = moment();
var d = moment.duration();
          

Overloaded accessors

  • Can’t pass options to getters or setters
  • Methods can’t say what they do
  • Later new methods will be asymmetric

var m = moment('1978-06-14');
m.year(); /* 1978 */
m.year(2013); /* 2013-06-14 */
m.day(); /* 5 */
m.day(10); /* 2013-06-19 */
var oops = undefined;
m.day(oops); /* 3 WAT? */
          

All of these go both ways…

What to Consider when Wrapping?

2. Mutability

What is mutation?

UTC

This should not be mutable, but it is


var m = moment('2013-06-14');

m.hours(); /* 0 */
/* Meanwhile, somewhere in a queue */
m.utc(); /* This could be ANYWHERE */
/* Roh roh! */
m.hours(); /* 4 */
          

Durations

This should be mutable, to be symmetric to moments


var m = moment();
m.minutes(); /* 0 */
m.minutes(42); /* Moment instance */
m.minutes(); /* 42 */

var d = moment.duration(60 * 1000);
d.minutes(); /* 10 */
d.minutes(20); /* 10 */
d.minutes(); /* 10 */
          

What to Consider when Wrapping?

3. Standard Compliance

ISO-8601 dates

  • Understood without passing a format string
  • Otherwise, parsing gets very complex
  • If using a non-standard date, write a separate adapter

Moment.js durations


var d = moment.duration(35, 'y');

var d = moment.duration({
  years: 35,
  hours: 8
});
          

No ISO-8601 durations

There’s a standard for that!


var d = new Duration('P 35Y T 8H');
          

Formatting differences with LDML standard

  • It’s so close!
  • LDML strings could come from the server
  • The standard acts as supplementary documentation

f = 'M/d/YYYY H:m:s'; /* LDML */
f = 'M/D/YYYY H:m:s'; /* Moment.js */
          

What to Consider when Wrapping?

4. Feature Set

Incomplete duration formatting

  • Only humanizes
  • What about representing 10 minutes as 10:00?

ICU Message Format


var format = '{ DAYS,
                plural,
                one {1 Day Left}
                other {# Days Left}
              }';
          
  • Not a standard, but…
  • Originated at IBM, common in the JVM world
  • Localization is much more robust

What to Consider when Wrapping?

5. Conflation

Two things

  • Construction of moments and durations
  • Formatting of moments and durations

Single Responsibility Principle

Our Date and Duration Stack

Our Date and Duration Stack

Third-Party Code

Moment.js

  • Constructs moments and durations
  • Does math on moments and durations
  • Handles some formatting

messageformat.js

  • Handles ICU message formatting for durations

Our Date and Duration Stack

common.date_utils

Moment


var M = dateUtils.Moment;
new M();
new M(1370383699043);
new M('2013-06-14');
new M('Wed, 14 Jun 1978 06:14:00 CST');
          

Duration


var D = dateUtils.Duration;
new D(600000);
new D('P 6M 14D T 6H 14M');
          

Our Date and Duration Stack

formatter.date

Moment


momentFormatter('1978-06-14');
momentFormatter('1978-06-14', {
  format: 'LL'
});
momentFormatter('1978-06-14', {
  format: 'EEEE, MMM d, YYYY'
});
          

6/14/1978 | June 14 1978 | Wednesday, June 14, 1978

Duration


durationFormatter(60000);
durationFormatter('P 3D');
durationFormatter(600000, {
  format: 'LT'
});
          

00:01 | 72:00 | 00:10

Humanize Moment


humanizeMomentFormatter(266630400000);
humanizeMomentFormatter(new Date());
          

6/14/1978 | Today at 4:00 PM

Humanize Duration


humanizeDurationFormatter(60000);
humanizeDurationFormatter(1234568, {
  relative: true
});
humanizeDurationFormatter(-840000);
humanizeDurationFormatter('PT 12H 30M');
          

a minute | in 21 minutes | 14 minutes | 13 hours

ICU Duration


icuDurationFormatter('PT 2H 20M', {
  message: '{HOURS, plural, one {1 Hour}
    other {# Hours}} and
    {MINUTES, plural, one {1 Minute}
    other {# Minutes}}'
});
          

2 Hours and 20 Minutes

Summary

  • API Pattern
  • Mutation
  • Standard Compliance
  • Feature Set
  • Conflation

Resources

Also, your console has globals for the two modules, dateUtils and dateFormatter.