Home

Building Trello Cycles

in JavaScript and CoffeeScript

Recently I built a little utility project for the fine people at UserTesting.com to help them get some insights into project lifecycle and “cycle time,” the time it takes any given project to move through pre-determined phases of the lifecycle. Hence, “Trello Cycles.”

Seeing as how Trello’s API is damn robust and they have a sweet piece of JS to help with authentication and some basic API requests I decided to do the whole thing client side and give Yeoman a try at the same time.

Yeoman

The webapp generator for Yeoman is pretty sweet: it scaffolds out some basic JS, CSS, and HTML; sets up Grunt tasks for local testing with live reload and building to a destination folder; and has support for CoffeeScript, HTML5 Boilerplate, Twitter Bootstrap, and RequireJS.

Grunt the Fuck?

Maybe I’m just not hip to the new JS jazz, but Gruntfiles make no fucking sense to me. Check out the auto-generated Gruntfile.js for Trello Cycles and the Rakefile for my Sinatra Boilerplate project. Both will build assets and run a development server (I realize there isn’t perfect feature parity so an apples-to-apples comparison is impossible, what I’m digging at here isn’t a functionality comparison) but I find the former damn near inscrutable and the latter generally more legible. It’s a minor thing. Sort of.

Statistics in JavaScript

Turns out doing basic statistical work in JavaScript isn’t super straight forward. Calculating something simple like the sum and mean (average) of an array of numbers isn’t as easy as just calling [1, 2, 3].sum() or [1, 2, 3].mean(). Instead, you’d have to do something absolutely goofy looking like pass the array into a dedicated function like arraySum([1, 2, 3]) and just looking at that makes my blood boil. Because I’ve been using Ruby so much I’m used to calling methods directly on objects and monkey patching core classes to do what I want so I decided I’d better get to it. (Sidenote: I think there’s probably a discussion of encapsulation and API consistency in here along the lines of Underscore vs. core extension, but I’m not ready for that one yet).

This script adds some basic mathematical methods to the Array prototype chain including:

  • max
  • min
  • median
  • sum which depends on reduce
  • mean which depends on sum
  • variance which gives you the variance of the set
  • stddev which gives you the standard deviation, or more simply the square root of the variance

With all of that, you can call [1, 2, 3].sum() and [1, 2, 3].mean() to your heart’s content.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# max
if "function" isnt typeof Array::max
  Array::max = ->
    "use strict"

    # throw on null
    throw new TypeError("Array.prototype.max called on null or undefined")  if null is this or "undefined" is typeof this

    Math.max.apply null, this

# min
if "function" isnt typeof Array::min
  Array::min = ->
    "use strict"

    # throw on null
    throw new TypeError("Array.prototype.min called on null or undefined")  if null is this or "undefined" is typeof this

    Math.min.apply null, this

# median
if "function" isnt typeof Array::median
  Array::median = ->
    "use strict"

    # throw on null
    throw new TypeError("Array.prototype.median called on null or undefined")  if null is this or "undefined" is typeof this

    # locals
    sorted = @sort (a,b) => return a - b
    length = @length >>> 0
    half   = Math.floor length/2

    if length % 2
      return this[half]
    else
      return [this[half - 1], this[half]].mean()

# sum
# depends on Array.prototype.reduce to work properly.
# shim for that available here https://gist.github.com/evanleck/6418510
if "function" isnt typeof Array::sum
  Array::sum = ->
    "use strict"

    # throw on null
    throw new TypeError("Array.prototype.sum called on null or undefined")  if null is this or "undefined" is typeof this

    @reduce (a, b) ->
      a + b

# mean
if "function" isnt typeof Array::mean
  Array::mean = ->
    "use strict"

    # throw on null
    throw new TypeError("Array.prototype.mean called on null or undefined")  if null is this or "undefined" is typeof this

    length = @length >>> 0

    @sum()/length

# variance
if "function" isnt typeof Array::variance
  Array::variance = ->
    "use strict"

    # throw on null
    throw new TypeError("Array.prototype.variance called on null or undefined")  if null is this or "undefined" is typeof this

    index  = 0
    length = @length >>> 0
    mean   = @mean()
    powed  = []

    # loop to generate powed
    while length > index
      continue unless @hasOwnProperty(index)
      powed.push Math.pow(this[index] - mean, 2)
      ++index

    # return mean of powed
    powed.mean()

# standard deviation
if "function" isnt typeof Array::stddev
  Array::stddev = ->
    "use strict"

    # throw on null
    throw new TypeError("Array.prototype.stddev called on null or undefined")  if null is this or "undefined" is typeof this

    # square root of variance
    Math.sqrt @variance()