You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@htrace.apache.org by cm...@apache.org on 2015/04/23 01:08:03 UTC

[09/41] incubator-htrace git commit: HTRACE-154. Move go and web to htrace-htraced (abe via cmccabe)

http://git-wip-us.apache.org/repos/asf/incubator-htrace/blob/39e89ea0/htrace-htraced/src/web/lib/js/backgrid-0.3.5.js
----------------------------------------------------------------------
diff --git a/htrace-htraced/src/web/lib/js/backgrid-0.3.5.js b/htrace-htraced/src/web/lib/js/backgrid-0.3.5.js
new file mode 100644
index 0000000..6f8da79
--- /dev/null
+++ b/htrace-htraced/src/web/lib/js/backgrid-0.3.5.js
@@ -0,0 +1,2883 @@
+/*!
+  backgrid
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2014 Jimmy Yuen Ho Wong and contributors <wy...@gmail.com>
+  Licensed under the MIT license.
+*/
+
+(function (factory) {
+
+  // CommonJS
+  if (typeof exports == "object") {
+    module.exports = factory(module.exports,
+                             require("underscore"),
+                             require("backbone"));
+  }
+  // Browser
+  else factory(this, this._, this.Backbone);
+}(function (root, _, Backbone) {
+
+  "use strict";
+
+/*
+  backgrid
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT license.
+*/
+
+// Copyright 2009, 2010 Kristopher Michael Kowal
+// https://github.com/kriskowal/es5-shim
+// ES5 15.5.4.20
+// http://es5.github.com/#x15.5.4.20
+var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" +
+  "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" +
+  "\u2029\uFEFF";
+if (!String.prototype.trim || ws.trim()) {
+  // http://blog.stevenlevithan.com/archives/faster-trim-javascript
+  // http://perfectionkills.com/whitespace-deviations/
+  ws = "[" + ws + "]";
+  var trimBeginRegexp = new RegExp("^" + ws + ws + "*"),
+  trimEndRegexp = new RegExp(ws + ws + "*$");
+  String.prototype.trim = function trim() {
+    if (this === undefined || this === null) {
+      throw new TypeError("can't convert " + this + " to object");
+    }
+    return String(this)
+      .replace(trimBeginRegexp, "")
+      .replace(trimEndRegexp, "");
+  };
+}
+
+function lpad(str, length, padstr) {
+  var paddingLen = length - (str + '').length;
+  paddingLen =  paddingLen < 0 ? 0 : paddingLen;
+  var padding = '';
+  for (var i = 0; i < paddingLen; i++) {
+    padding = padding + padstr;
+  }
+  return padding + str;
+}
+
+var $ = Backbone.$;
+
+var Backgrid = root.Backgrid = {
+
+  Extension: {},
+
+  resolveNameToClass: function (name, suffix) {
+    if (_.isString(name)) {
+      var key = _.map(name.split('-'), function (e) {
+        return e.slice(0, 1).toUpperCase() + e.slice(1);
+      }).join('') + suffix;
+      var klass = Backgrid[key] || Backgrid.Extension[key];
+      if (_.isUndefined(klass)) {
+        throw new ReferenceError("Class '" + key + "' not found");
+      }
+      return klass;
+    }
+
+    return name;
+  },
+
+  callByNeed: function () {
+    var value = arguments[0];
+    if (!_.isFunction(value)) return value;
+
+    var context = arguments[1];
+    var args = [].slice.call(arguments, 2);
+    return value.apply(context, !!(args + '') ? args : []);
+  }
+
+};
+_.extend(Backgrid, Backbone.Events);
+
+/**
+   Command translates a DOM Event into commands that Backgrid
+   recognizes. Interested parties can listen on selected Backgrid events that
+   come with an instance of this class and act on the commands.
+
+   It is also possible to globally rebind the keyboard shortcuts by replacing
+   the methods in this class' prototype.
+
+   @class Backgrid.Command
+   @constructor
+ */
+var Command = Backgrid.Command = function (evt) {
+  _.extend(this, {
+    altKey: !!evt.altKey,
+    "char": evt["char"],
+    charCode: evt.charCode,
+    ctrlKey: !!evt.ctrlKey,
+    key: evt.key,
+    keyCode: evt.keyCode,
+    locale: evt.locale,
+    location: evt.location,
+    metaKey: !!evt.metaKey,
+    repeat: !!evt.repeat,
+    shiftKey: !!evt.shiftKey,
+    which: evt.which
+  });
+};
+_.extend(Command.prototype, {
+  /**
+     Up Arrow
+
+     @member Backgrid.Command
+   */
+  moveUp: function () { return this.keyCode == 38; },
+  /**
+     Down Arrow
+
+     @member Backgrid.Command
+   */
+  moveDown: function () { return this.keyCode === 40; },
+  /**
+     Shift Tab
+
+     @member Backgrid.Command
+   */
+  moveLeft: function () { return this.shiftKey && this.keyCode === 9; },
+  /**
+     Tab
+
+     @member Backgrid.Command
+   */
+  moveRight: function () { return !this.shiftKey && this.keyCode === 9; },
+  /**
+     Enter
+
+     @member Backgrid.Command
+   */
+  save: function () { return this.keyCode === 13; },
+  /**
+     Esc
+
+     @member Backgrid.Command
+   */
+  cancel: function () { return this.keyCode === 27; },
+  /**
+     None of the above.
+
+     @member Backgrid.Command
+   */
+  passThru: function () {
+    return !(this.moveUp() || this.moveDown() || this.moveLeft() ||
+             this.moveRight() || this.save() || this.cancel());
+  }
+});
+
+/*
+  backgrid
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT license.
+*/
+
+/**
+   Just a convenient class for interested parties to subclass.
+
+   The default Cell classes don't require the formatter to be a subclass of
+   Formatter as long as the fromRaw(rawData) and toRaw(formattedData) methods
+   are defined.
+
+   @abstract
+   @class Backgrid.CellFormatter
+   @constructor
+*/
+var CellFormatter = Backgrid.CellFormatter = function () {};
+_.extend(CellFormatter.prototype, {
+
+  /**
+     Takes a raw value from a model and returns an optionally formatted string
+     for display. The default implementation simply returns the supplied value
+     as is without any type conversion.
+
+     @member Backgrid.CellFormatter
+     @param {*} rawData
+     @param {Backbone.Model} model Used for more complicated formatting
+     @return {*}
+  */
+  fromRaw: function (rawData, model) {
+    return rawData;
+  },
+
+  /**
+     Takes a formatted string, usually from user input, and returns a
+     appropriately typed value for persistence in the model.
+
+     If the user input is invalid or unable to be converted to a raw value
+     suitable for persistence in the model, toRaw must return `undefined`.
+
+     @member Backgrid.CellFormatter
+     @param {string} formattedData
+     @param {Backbone.Model} model Used for more complicated formatting
+     @return {*|undefined}
+  */
+  toRaw: function (formattedData, model) {
+    return formattedData;
+  }
+
+});
+
+/**
+   A floating point number formatter. Doesn't understand scientific notation at
+   the moment.
+
+   @class Backgrid.NumberFormatter
+   @extends Backgrid.CellFormatter
+   @constructor
+   @throws {RangeError} If decimals < 0 or > 20.
+*/
+var NumberFormatter = Backgrid.NumberFormatter = function (options) {
+  _.extend(this, this.defaults, options || {});
+
+  if (this.decimals < 0 || this.decimals > 20) {
+    throw new RangeError("decimals must be between 0 and 20");
+  }
+};
+NumberFormatter.prototype = new CellFormatter();
+_.extend(NumberFormatter.prototype, {
+
+  /**
+     @member Backgrid.NumberFormatter
+     @cfg {Object} options
+
+     @cfg {number} [options.decimals=2] Number of decimals to display. Must be an integer.
+
+     @cfg {string} [options.decimalSeparator='.'] The separator to use when
+     displaying decimals.
+
+     @cfg {string} [options.orderSeparator=','] The separator to use to
+     separator thousands. May be an empty string.
+   */
+  defaults: {
+    decimals: 2,
+    decimalSeparator: '.',
+    orderSeparator: ','
+  },
+
+  HUMANIZED_NUM_RE: /(\d)(?=(?:\d{3})+$)/g,
+
+  /**
+     Takes a floating point number and convert it to a formatted string where
+     every thousand is separated by `orderSeparator`, with a `decimal` number of
+     decimals separated by `decimalSeparator`. The number returned is rounded
+     the usual way.
+
+     @member Backgrid.NumberFormatter
+     @param {number} number
+     @param {Backbone.Model} model Used for more complicated formatting
+     @return {string}
+  */
+  fromRaw: function (number, model) {
+    if (_.isNull(number) || _.isUndefined(number)) return '';
+
+    number = number.toFixed(~~this.decimals);
+
+    var parts = number.split('.');
+    var integerPart = parts[0];
+    var decimalPart = parts[1] ? (this.decimalSeparator || '.') + parts[1] : '';
+
+    return integerPart.replace(this.HUMANIZED_NUM_RE, '$1' + this.orderSeparator) + decimalPart;
+  },
+
+  /**
+     Takes a string, possibly formatted with `orderSeparator` and/or
+     `decimalSeparator`, and convert it back to a number.
+
+     @member Backgrid.NumberFormatter
+     @param {string} formattedData
+     @param {Backbone.Model} model Used for more complicated formatting
+     @return {number|undefined} Undefined if the string cannot be converted to
+     a number.
+  */
+  toRaw: function (formattedData, model) {
+    formattedData = formattedData.trim();
+
+    if (formattedData === '') return null;
+
+    var rawData = '';
+
+    var thousands = formattedData.split(this.orderSeparator);
+    for (var i = 0; i < thousands.length; i++) {
+      rawData += thousands[i];
+    }
+
+    var decimalParts = rawData.split(this.decimalSeparator);
+    rawData = '';
+    for (var i = 0; i < decimalParts.length; i++) {
+      rawData = rawData + decimalParts[i] + '.';
+    }
+
+    if (rawData[rawData.length - 1] === '.') {
+      rawData = rawData.slice(0, rawData.length - 1);
+    }
+
+    var result = (rawData * 1).toFixed(~~this.decimals) * 1;
+    if (_.isNumber(result) && !_.isNaN(result)) return result;
+  }
+
+});
+
+/**
+   A number formatter that converts a floating point number, optionally
+   multiplied by a multiplier, to a percentage string and vice versa.
+
+   @class Backgrid.PercentFormatter
+   @extends Backgrid.NumberFormatter
+   @constructor
+   @throws {RangeError} If decimals < 0 or > 20.
+ */
+var PercentFormatter = Backgrid.PercentFormatter = function () {
+  Backgrid.NumberFormatter.apply(this, arguments);
+};
+
+PercentFormatter.prototype = new Backgrid.NumberFormatter(),
+
+_.extend(PercentFormatter.prototype, {
+
+  /**
+     @member Backgrid.PercentFormatter
+     @cfg {Object} options
+
+     @cfg {number} [options.multiplier=1] The number used to multiply the model
+     value for display.
+
+     @cfg {string} [options.symbol='%'] The symbol to append to the percentage
+     string.
+   */
+  defaults: _.extend({}, NumberFormatter.prototype.defaults, {
+    multiplier: 1,
+    symbol: "%"
+  }),
+
+  /**
+     Takes a floating point number, where the number is first multiplied by
+     `multiplier`, then converted to a formatted string like
+     NumberFormatter#fromRaw, then finally append `symbol` to the end.
+
+     @member Backgrid.PercentFormatter
+     @param {number} rawValue
+     @param {Backbone.Model} model Used for more complicated formatting
+     @return {string}
+  */
+  fromRaw: function (number, model) {
+    var args = [].slice.call(arguments, 1);
+    args.unshift(number * this.multiplier);
+    return (NumberFormatter.prototype.fromRaw.apply(this, args) || "0") + this.symbol;
+  },
+
+  /**
+     Takes a string, possibly appended with `symbol` and/or `decimalSeparator`,
+     and convert it back to a number for the model like NumberFormatter#toRaw,
+     and then dividing it by `multiplier`.
+
+     @member Backgrid.PercentFormatter
+     @param {string} formattedData
+     @param {Backbone.Model} model Used for more complicated formatting
+     @return {number|undefined} Undefined if the string cannot be converted to
+     a number.
+  */
+  toRaw: function (formattedValue, model) {
+    var tokens = formattedValue.split(this.symbol);
+    if (tokens && tokens[0] && tokens[1] === "" || tokens[1] == null) {
+      var rawValue = NumberFormatter.prototype.toRaw.call(this, tokens[0]);
+      if (_.isUndefined(rawValue)) return rawValue;
+      return rawValue / this.multiplier;
+    }
+  }
+
+});
+
+/**
+   Formatter to converts between various datetime formats.
+
+   This class only understands ISO-8601 formatted datetime strings and UNIX
+   offset (number of milliseconds since UNIX Epoch). See
+   Backgrid.Extension.MomentFormatter if you need a much more flexible datetime
+   formatter.
+
+   @class Backgrid.DatetimeFormatter
+   @extends Backgrid.CellFormatter
+   @constructor
+   @throws {Error} If both `includeDate` and `includeTime` are false.
+*/
+var DatetimeFormatter = Backgrid.DatetimeFormatter = function (options) {
+  _.extend(this, this.defaults, options || {});
+
+  if (!this.includeDate && !this.includeTime) {
+    throw new Error("Either includeDate or includeTime must be true");
+  }
+};
+DatetimeFormatter.prototype = new CellFormatter();
+_.extend(DatetimeFormatter.prototype, {
+
+  /**
+     @member Backgrid.DatetimeFormatter
+
+     @cfg {Object} options
+
+     @cfg {boolean} [options.includeDate=true] Whether the values include the
+     date part.
+
+     @cfg {boolean} [options.includeTime=true] Whether the values include the
+     time part.
+
+     @cfg {boolean} [options.includeMilli=false] If `includeTime` is true,
+     whether to include the millisecond part, if it exists.
+   */
+  defaults: {
+    includeDate: true,
+    includeTime: true,
+    includeMilli: false
+  },
+
+  DATE_RE: /^([+\-]?\d{4})-(\d{2})-(\d{2})$/,
+  TIME_RE: /^(\d{2}):(\d{2}):(\d{2})(\.(\d{3}))?$/,
+  ISO_SPLITTER_RE: /T|Z| +/,
+
+  _convert: function (data, validate) {
+    if ((data + '').trim() === '') return null;
+
+    var date, time = null;
+    if (_.isNumber(data)) {
+      var jsDate = new Date(data);
+      date = lpad(jsDate.getUTCFullYear(), 4, 0) + '-' + lpad(jsDate.getUTCMonth() + 1, 2, 0) + '-' + lpad(jsDate.getUTCDate(), 2, 0);
+      time = lpad(jsDate.getUTCHours(), 2, 0) + ':' + lpad(jsDate.getUTCMinutes(), 2, 0) + ':' + lpad(jsDate.getUTCSeconds(), 2, 0);
+    }
+    else {
+      data = data.trim();
+      var parts = data.split(this.ISO_SPLITTER_RE) || [];
+      date = this.DATE_RE.test(parts[0]) ? parts[0] : '';
+      time = date && parts[1] ? parts[1] : this.TIME_RE.test(parts[0]) ? parts[0] : '';
+    }
+
+    var YYYYMMDD = this.DATE_RE.exec(date) || [];
+    var HHmmssSSS = this.TIME_RE.exec(time) || [];
+
+    if (validate) {
+      if (this.includeDate && _.isUndefined(YYYYMMDD[0])) return;
+      if (this.includeTime && _.isUndefined(HHmmssSSS[0])) return;
+      if (!this.includeDate && date) return;
+      if (!this.includeTime && time) return;
+    }
+
+    var jsDate = new Date(Date.UTC(YYYYMMDD[1] * 1 || 0,
+                                   YYYYMMDD[2] * 1 - 1 || 0,
+                                   YYYYMMDD[3] * 1 || 0,
+                                   HHmmssSSS[1] * 1 || null,
+                                   HHmmssSSS[2] * 1 || null,
+                                   HHmmssSSS[3] * 1 || null,
+                                   HHmmssSSS[5] * 1 || null));
+
+    var result = '';
+
+    if (this.includeDate) {
+      result = lpad(jsDate.getUTCFullYear(), 4, 0) + '-' + lpad(jsDate.getUTCMonth() + 1, 2, 0) + '-' + lpad(jsDate.getUTCDate(), 2, 0);
+    }
+
+    if (this.includeTime) {
+      result = result + (this.includeDate ? 'T' : '') + lpad(jsDate.getUTCHours(), 2, 0) + ':' + lpad(jsDate.getUTCMinutes(), 2, 0) + ':' + lpad(jsDate.getUTCSeconds(), 2, 0);
+
+      if (this.includeMilli) {
+        result = result + '.' + lpad(jsDate.getUTCMilliseconds(), 3, 0);
+      }
+    }
+
+    if (this.includeDate && this.includeTime) {
+      result += "Z";
+    }
+
+    return result;
+  },
+
+  /**
+     Converts an ISO-8601 formatted datetime string to a datetime string, date
+     string or a time string. The timezone is ignored if supplied.
+
+     @member Backgrid.DatetimeFormatter
+     @param {string} rawData
+     @param {Backbone.Model} model Used for more complicated formatting
+     @return {string|null|undefined} ISO-8601 string in UTC. Null and undefined
+     values are returned as is.
+  */
+  fromRaw: function (rawData, model) {
+    if (_.isNull(rawData) || _.isUndefined(rawData)) return '';
+    return this._convert(rawData);
+  },
+
+  /**
+     Converts an ISO-8601 formatted datetime string to a datetime string, date
+     string or a time string. The timezone is ignored if supplied. This method
+     parses the input values exactly the same way as
+     Backgrid.Extension.MomentFormatter#fromRaw(), in addition to doing some
+     sanity checks.
+
+     @member Backgrid.DatetimeFormatter
+     @param {string} formattedData
+     @param {Backbone.Model} model Used for more complicated formatting
+     @return {string|undefined} ISO-8601 string in UTC. Undefined if a date is
+     found when `includeDate` is false, or a time is found when `includeTime` is
+     false, or if `includeDate` is true and a date is not found, or if
+     `includeTime` is true and a time is not found.
+  */
+  toRaw: function (formattedData, model) {
+    return this._convert(formattedData, true);
+  }
+
+});
+
+/**
+   Formatter to convert any value to string.
+
+   @class Backgrid.StringFormatter
+   @extends Backgrid.CellFormatter
+   @constructor
+ */
+var StringFormatter = Backgrid.StringFormatter = function () {};
+StringFormatter.prototype = new CellFormatter();
+_.extend(StringFormatter.prototype, {
+  /**
+     Converts any value to a string using Ecmascript's implicit type
+     conversion. If the given value is `null` or `undefined`, an empty string is
+     returned instead.
+
+     @member Backgrid.StringFormatter
+     @param {*} rawValue
+     @param {Backbone.Model} model Used for more complicated formatting
+     @return {string}
+   */
+  fromRaw: function (rawValue, model) {
+    if (_.isUndefined(rawValue) || _.isNull(rawValue)) return '';
+    return rawValue + '';
+  }
+});
+
+/**
+   Simple email validation formatter.
+
+   @class Backgrid.EmailFormatter
+   @extends Backgrid.CellFormatter
+   @constructor
+ */
+var EmailFormatter = Backgrid.EmailFormatter = function () {};
+EmailFormatter.prototype = new CellFormatter();
+_.extend(EmailFormatter.prototype, {
+  /**
+     Return the input if it is a string that contains an '@' character and if
+     the strings before and after '@' are non-empty. If the input does not
+     validate, `undefined` is returned.
+
+     @member Backgrid.EmailFormatter
+     @param {*} formattedData
+     @param {Backbone.Model} model Used for more complicated formatting
+     @return {string|undefined}
+   */
+  toRaw: function (formattedData, model) {
+    var parts = formattedData.trim().split("@");
+    if (parts.length === 2 && _.all(parts)) {
+      return formattedData;
+    }
+  }
+});
+
+/**
+   Formatter for SelectCell.
+
+   If the type of a model value is not a string, it is expected that a subclass
+   of this formatter is provided to the SelectCell, with #toRaw overridden to
+   convert the string value returned from the DOM back to whatever value is
+   expected in the model.
+
+   @class Backgrid.SelectFormatter
+   @extends Backgrid.CellFormatter
+   @constructor
+*/
+var SelectFormatter = Backgrid.SelectFormatter = function () {};
+SelectFormatter.prototype = new CellFormatter();
+_.extend(SelectFormatter.prototype, {
+
+  /**
+     Normalizes raw scalar or array values to an array.
+
+     @member Backgrid.SelectFormatter
+     @param {*} rawValue
+     @param {Backbone.Model} model Used for more complicated formatting
+     @return {Array.<*>}
+  */
+  fromRaw: function (rawValue, model) {
+    return _.isArray(rawValue) ? rawValue : rawValue != null ? [rawValue] : [];
+  }
+});
+
+/*
+  backgrid
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT license.
+*/
+
+/**
+   Generic cell editor base class. Only defines an initializer for a number of
+   required parameters.
+
+   @abstract
+   @class Backgrid.CellEditor
+   @extends Backbone.View
+*/
+var CellEditor = Backgrid.CellEditor = Backbone.View.extend({
+
+  /**
+     Initializer.
+
+     @param {Object} options
+     @param {Backgrid.CellFormatter} options.formatter
+     @param {Backgrid.Column} options.column
+     @param {Backbone.Model} options.model
+
+     @throws {TypeError} If `formatter` is not a formatter instance, or when
+     `model` or `column` are undefined.
+  */
+  initialize: function (options) {
+    this.formatter = options.formatter;
+    this.column = options.column;
+    if (!(this.column instanceof Column)) {
+      this.column = new Column(this.column);
+    }
+
+    this.listenTo(this.model, "backgrid:editing", this.postRender);
+  },
+
+  /**
+     Post-rendering setup and initialization. Focuses the cell editor's `el` in
+     this default implementation. **Should** be called by Cell classes after
+     calling Backgrid.CellEditor#render.
+  */
+  postRender: function (model, column) {
+    if (column == null || column.get("name") == this.column.get("name")) {
+      this.$el.focus();
+    }
+    return this;
+  }
+
+});
+
+/**
+   InputCellEditor the cell editor type used by most core cell types. This cell
+   editor renders a text input box as its editor. The input will render a
+   placeholder if the value is empty on supported browsers.
+
+   @class Backgrid.InputCellEditor
+   @extends Backgrid.CellEditor
+*/
+var InputCellEditor = Backgrid.InputCellEditor = CellEditor.extend({
+
+  /** @property */
+  tagName: "input",
+
+  /** @property */
+  attributes: {
+    type: "text"
+  },
+
+  /** @property */
+  events: {
+    "blur": "saveOrCancel",
+    "keydown": "saveOrCancel"
+  },
+
+  /**
+     Initializer. Removes this `el` from the DOM when a `done` event is
+     triggered.
+
+     @param {Object} options
+     @param {Backgrid.CellFormatter} options.formatter
+     @param {Backgrid.Column} options.column
+     @param {Backbone.Model} options.model
+     @param {string} [options.placeholder]
+  */
+  initialize: function (options) {
+    InputCellEditor.__super__.initialize.apply(this, arguments);
+
+    if (options.placeholder) {
+      this.$el.attr("placeholder", options.placeholder);
+    }
+  },
+
+  /**
+     Renders a text input with the cell value formatted for display, if it
+     exists.
+  */
+  render: function () {
+    var model = this.model;
+    this.$el.val(this.formatter.fromRaw(model.get(this.column.get("name")), model));
+    return this;
+  },
+
+  /**
+     If the key pressed is `enter`, `tab`, `up`, or `down`, converts the value
+     in the editor to a raw value for saving into the model using the formatter.
+
+     If the key pressed is `esc` the changes are undone.
+
+     If the editor goes out of focus (`blur`) but the value is invalid, the
+     event is intercepted and cancelled so the cell remains in focus pending for
+     further action. The changes are saved otherwise.
+
+     Triggers a Backbone `backgrid:edited` event from the model when successful,
+     and `backgrid:error` if the value cannot be converted. Classes listening to
+     the `error` event, usually the Cell classes, should respond appropriately,
+     usually by rendering some kind of error feedback.
+
+     @param {Event} e
+  */
+  saveOrCancel: function (e) {
+
+    var formatter = this.formatter;
+    var model = this.model;
+    var column = this.column;
+
+    var command = new Command(e);
+    var blurred = e.type === "blur";
+
+    if (command.moveUp() || command.moveDown() || command.moveLeft() || command.moveRight() ||
+        command.save() || blurred) {
+
+      e.preventDefault();
+      e.stopPropagation();
+
+      var val = this.$el.val();
+      var newValue = formatter.toRaw(val, model);
+      if (_.isUndefined(newValue)) {
+        model.trigger("backgrid:error", model, column, val);
+      }
+      else {
+        model.set(column.get("name"), newValue);
+        model.trigger("backgrid:edited", model, column, command);
+      }
+    }
+    // esc
+    else if (command.cancel()) {
+      // undo
+      e.stopPropagation();
+      model.trigger("backgrid:edited", model, column, command);
+    }
+  },
+
+  postRender: function (model, column) {
+    if (column == null || column.get("name") == this.column.get("name")) {
+      // move the cursor to the end on firefox if text is right aligned
+      if (this.$el.css("text-align") === "right") {
+        var val = this.$el.val();
+        this.$el.focus().val(null).val(val);
+      }
+      else this.$el.focus();
+    }
+    return this;
+  }
+
+});
+
+/**
+   The super-class for all Cell types. By default, this class renders a plain
+   table cell with the model value converted to a string using the
+   formatter. The table cell is clickable, upon which the cell will go into
+   editor mode, which is rendered by a Backgrid.InputCellEditor instance by
+   default. Upon encountering any formatting errors, this class will add an
+   `error` CSS class to the table cell.
+
+   @abstract
+   @class Backgrid.Cell
+   @extends Backbone.View
+*/
+var Cell = Backgrid.Cell = Backbone.View.extend({
+
+  /** @property */
+  tagName: "td",
+
+  /**
+     @property {Backgrid.CellFormatter|Object|string} [formatter=CellFormatter]
+  */
+  formatter: CellFormatter,
+
+  /**
+     @property {Backgrid.CellEditor} [editor=Backgrid.InputCellEditor] The
+     default editor for all cell instances of this class. This value must be a
+     class, it will be automatically instantiated upon entering edit mode.
+
+     See Backgrid.CellEditor
+  */
+  editor: InputCellEditor,
+
+  /** @property */
+  events: {
+    "click": "enterEditMode"
+  },
+
+  /**
+     Initializer.
+
+     @param {Object} options
+     @param {Backbone.Model} options.model
+     @param {Backgrid.Column} options.column
+
+     @throws {ReferenceError} If formatter is a string but a formatter class of
+     said name cannot be found in the Backgrid module.
+  */
+  initialize: function (options) {
+    this.column = options.column;
+    if (!(this.column instanceof Column)) {
+      this.column = new Column(this.column);
+    }
+
+    var column = this.column, model = this.model, $el = this.$el;
+
+    var formatter = Backgrid.resolveNameToClass(column.get("formatter") ||
+                                                this.formatter, "Formatter");
+
+    if (!_.isFunction(formatter.fromRaw) && !_.isFunction(formatter.toRaw)) {
+      formatter = new formatter();
+    }
+
+    this.formatter = formatter;
+
+    this.editor = Backgrid.resolveNameToClass(this.editor, "CellEditor");
+
+    this.listenTo(model, "change:" + column.get("name"), function () {
+      if (!$el.hasClass("editor")) this.render();
+    });
+
+    this.listenTo(model, "backgrid:error", this.renderError);
+
+    this.listenTo(column, "change:editable change:sortable change:renderable",
+                  function (column) {
+                    var changed = column.changedAttributes();
+                    for (var key in changed) {
+                      if (changed.hasOwnProperty(key)) {
+                        $el.toggleClass(key, changed[key]);
+                      }
+                    }
+                  });
+
+    if (Backgrid.callByNeed(column.editable(), column, model)) $el.addClass("editable");
+    if (Backgrid.callByNeed(column.sortable(), column, model)) $el.addClass("sortable");
+    if (Backgrid.callByNeed(column.renderable(), column, model)) $el.addClass("renderable");
+  },
+
+  /**
+     Render a text string in a table cell. The text is converted from the
+     model's raw value for this cell's column.
+  */
+  render: function () {
+    this.$el.empty();
+    var model = this.model;
+    this.$el.text(this.formatter.fromRaw(model.get(this.column.get("name")), model));
+    this.delegateEvents();
+    return this;
+  },
+
+  /**
+     If this column is editable, a new CellEditor instance is instantiated with
+     its required parameters. An `editor` CSS class is added to the cell upon
+     entering edit mode.
+
+     This method triggers a Backbone `backgrid:edit` event from the model when
+     the cell is entering edit mode and an editor instance has been constructed,
+     but before it is rendered and inserted into the DOM. The cell and the
+     constructed cell editor instance are sent as event parameters when this
+     event is triggered.
+
+     When this cell has finished switching to edit mode, a Backbone
+     `backgrid:editing` event is triggered from the model. The cell and the
+     constructed cell instance are also sent as parameters in the event.
+
+     When the model triggers a `backgrid:error` event, it means the editor is
+     unable to convert the current user input to an apprpriate value for the
+     model's column, and an `error` CSS class is added to the cell accordingly.
+  */
+  enterEditMode: function () {
+    var model = this.model;
+    var column = this.column;
+
+    var editable = Backgrid.callByNeed(column.editable(), column, model);
+    if (editable) {
+
+      this.currentEditor = new this.editor({
+        column: this.column,
+        model: this.model,
+        formatter: this.formatter
+      });
+
+      model.trigger("backgrid:edit", model, column, this, this.currentEditor);
+
+      // Need to redundantly undelegate events for Firefox
+      this.undelegateEvents();
+      this.$el.empty();
+      this.$el.append(this.currentEditor.$el);
+      this.currentEditor.render();
+      this.$el.addClass("editor");
+
+      model.trigger("backgrid:editing", model, column, this, this.currentEditor);
+    }
+  },
+
+  /**
+     Put an `error` CSS class on the table cell.
+  */
+  renderError: function (model, column) {
+    if (column == null || column.get("name") == this.column.get("name")) {
+      this.$el.addClass("error");
+    }
+  },
+
+  /**
+     Removes the editor and re-render in display mode.
+  */
+  exitEditMode: function () {
+    this.$el.removeClass("error");
+    this.currentEditor.remove();
+    this.stopListening(this.currentEditor);
+    delete this.currentEditor;
+    this.$el.removeClass("editor");
+    this.render();
+  },
+
+  /**
+     Clean up this cell.
+
+     @chainable
+  */
+  remove: function () {
+    if (this.currentEditor) {
+      this.currentEditor.remove.apply(this.currentEditor, arguments);
+      delete this.currentEditor;
+    }
+    return Cell.__super__.remove.apply(this, arguments);
+  }
+
+});
+
+/**
+   StringCell displays HTML escaped strings and accepts anything typed in.
+
+   @class Backgrid.StringCell
+   @extends Backgrid.Cell
+*/
+var StringCell = Backgrid.StringCell = Cell.extend({
+
+  /** @property */
+  className: "string-cell",
+
+  formatter: StringFormatter
+
+});
+
+/**
+   UriCell renders an HTML `<a>` anchor for the value and accepts URIs as user
+   input values. No type conversion or URL validation is done by the formatter
+   of this cell. Users who need URL validation are encourage to subclass UriCell
+   to take advantage of the parsing capabilities of the HTMLAnchorElement
+   available on HTML5-capable browsers or using a third-party library like
+   [URI.js](https://github.com/medialize/URI.js).
+
+   @class Backgrid.UriCell
+   @extends Backgrid.Cell
+*/
+var UriCell = Backgrid.UriCell = Cell.extend({
+
+  /** @property */
+  className: "uri-cell",
+
+  /**
+     @property {string} [title] The title attribute of the generated anchor. It
+     uses the display value formatted by the `formatter.fromRaw` by default.
+  */
+  title: null,
+
+  /**
+     @property {string} [target="_blank"] The target attribute of the generated
+     anchor.
+  */
+  target: "_blank",
+
+  initialize: function (options) {
+    UriCell.__super__.initialize.apply(this, arguments);
+    this.title = options.title || this.title;
+    this.target = options.target || this.target;
+  },
+
+  render: function () {
+    this.$el.empty();
+    var rawValue = this.model.get(this.column.get("name"));
+    var formattedValue = this.formatter.fromRaw(rawValue, this.model);
+    this.$el.append($("<a>", {
+      tabIndex: -1,
+      href: rawValue,
+      title: this.title || formattedValue,
+      target: this.target
+    }).text(formattedValue));
+    this.delegateEvents();
+    return this;
+  }
+
+});
+
+/**
+   Like Backgrid.UriCell, EmailCell renders an HTML `<a>` anchor for the
+   value. The `href` in the anchor is prefixed with `mailto:`. EmailCell will
+   complain if the user enters a string that doesn't contain the `@` sign.
+
+   @class Backgrid.EmailCell
+   @extends Backgrid.StringCell
+*/
+var EmailCell = Backgrid.EmailCell = StringCell.extend({
+
+  /** @property */
+  className: "email-cell",
+
+  formatter: EmailFormatter,
+
+  render: function () {
+    this.$el.empty();
+    var model = this.model;
+    var formattedValue = this.formatter.fromRaw(model.get(this.column.get("name")), model);
+    this.$el.append($("<a>", {
+      tabIndex: -1,
+      href: "mailto:" + formattedValue,
+      title: formattedValue
+    }).text(formattedValue));
+    this.delegateEvents();
+    return this;
+  }
+
+});
+
+/**
+   NumberCell is a generic cell that renders all numbers. Numbers are formatted
+   using a Backgrid.NumberFormatter.
+
+   @class Backgrid.NumberCell
+   @extends Backgrid.Cell
+*/
+var NumberCell = Backgrid.NumberCell = Cell.extend({
+
+  /** @property */
+  className: "number-cell",
+
+  /**
+     @property {number} [decimals=2] Must be an integer.
+  */
+  decimals: NumberFormatter.prototype.defaults.decimals,
+
+  /** @property {string} [decimalSeparator='.'] */
+  decimalSeparator: NumberFormatter.prototype.defaults.decimalSeparator,
+
+  /** @property {string} [orderSeparator=','] */
+  orderSeparator: NumberFormatter.prototype.defaults.orderSeparator,
+
+  /** @property {Backgrid.CellFormatter} [formatter=Backgrid.NumberFormatter] */
+  formatter: NumberFormatter,
+
+  /**
+     Initializes this cell and the number formatter.
+
+     @param {Object} options
+     @param {Backbone.Model} options.model
+     @param {Backgrid.Column} options.column
+  */
+  initialize: function (options) {
+    NumberCell.__super__.initialize.apply(this, arguments);
+    var formatter = this.formatter;
+    formatter.decimals = this.decimals;
+    formatter.decimalSeparator = this.decimalSeparator;
+    formatter.orderSeparator = this.orderSeparator;
+  }
+
+});
+
+/**
+   An IntegerCell is just a Backgrid.NumberCell with 0 decimals. If a floating
+   point number is supplied, the number is simply rounded the usual way when
+   displayed.
+
+   @class Backgrid.IntegerCell
+   @extends Backgrid.NumberCell
+*/
+var IntegerCell = Backgrid.IntegerCell = NumberCell.extend({
+
+  /** @property */
+  className: "integer-cell",
+
+  /**
+     @property {number} decimals Must be an integer.
+  */
+  decimals: 0
+});
+
+/**
+   A PercentCell is another Backgrid.NumberCell that takes a floating number,
+   optionally multiplied by a multiplier and display it as a percentage.
+
+   @class Backgrid.PercentCell
+   @extends Backgrid.NumberCell
+ */
+var PercentCell = Backgrid.PercentCell = NumberCell.extend({
+
+  /** @property */
+  className: "percent-cell",
+
+  /** @property {number} [multiplier=1] */
+  multiplier: PercentFormatter.prototype.defaults.multiplier,
+
+  /** @property {string} [symbol='%'] */
+  symbol: PercentFormatter.prototype.defaults.symbol,
+
+  /** @property {Backgrid.CellFormatter} [formatter=Backgrid.PercentFormatter] */
+  formatter: PercentFormatter,
+
+  /**
+     Initializes this cell and the percent formatter.
+
+     @param {Object} options
+     @param {Backbone.Model} options.model
+     @param {Backgrid.Column} options.column
+  */
+  initialize: function () {
+    PercentCell.__super__.initialize.apply(this, arguments);
+    var formatter = this.formatter;
+    formatter.multiplier = this.multiplier;
+    formatter.symbol = this.symbol;
+  }
+
+});
+
+/**
+   DatetimeCell is a basic cell that accepts datetime string values in RFC-2822
+   or W3C's subset of ISO-8601 and displays them in ISO-8601 format. For a much
+   more sophisticated date time cell with better datetime formatting, take a
+   look at the Backgrid.Extension.MomentCell extension.
+
+   @class Backgrid.DatetimeCell
+   @extends Backgrid.Cell
+
+   See:
+
+   - Backgrid.Extension.MomentCell
+   - Backgrid.DatetimeFormatter
+*/
+var DatetimeCell = Backgrid.DatetimeCell = Cell.extend({
+
+  /** @property */
+  className: "datetime-cell",
+
+  /**
+     @property {boolean} [includeDate=true]
+  */
+  includeDate: DatetimeFormatter.prototype.defaults.includeDate,
+
+  /**
+     @property {boolean} [includeTime=true]
+  */
+  includeTime: DatetimeFormatter.prototype.defaults.includeTime,
+
+  /**
+     @property {boolean} [includeMilli=false]
+  */
+  includeMilli: DatetimeFormatter.prototype.defaults.includeMilli,
+
+  /** @property {Backgrid.CellFormatter} [formatter=Backgrid.DatetimeFormatter] */
+  formatter: DatetimeFormatter,
+
+  /**
+     Initializes this cell and the datetime formatter.
+
+     @param {Object} options
+     @param {Backbone.Model} options.model
+     @param {Backgrid.Column} options.column
+  */
+  initialize: function (options) {
+    DatetimeCell.__super__.initialize.apply(this, arguments);
+    var formatter = this.formatter;
+    formatter.includeDate = this.includeDate;
+    formatter.includeTime = this.includeTime;
+    formatter.includeMilli = this.includeMilli;
+
+    var placeholder = this.includeDate ? "YYYY-MM-DD" : "";
+    placeholder += (this.includeDate && this.includeTime) ? "T" : "";
+    placeholder += this.includeTime ? "HH:mm:ss" : "";
+    placeholder += (this.includeTime && this.includeMilli) ? ".SSS" : "";
+
+    this.editor = this.editor.extend({
+      attributes: _.extend({}, this.editor.prototype.attributes, this.editor.attributes, {
+        placeholder: placeholder
+      })
+    });
+  }
+
+});
+
+/**
+   DateCell is a Backgrid.DatetimeCell without the time part.
+
+   @class Backgrid.DateCell
+   @extends Backgrid.DatetimeCell
+*/
+var DateCell = Backgrid.DateCell = DatetimeCell.extend({
+
+  /** @property */
+  className: "date-cell",
+
+  /** @property */
+  includeTime: false
+
+});
+
+/**
+   TimeCell is a Backgrid.DatetimeCell without the date part.
+
+   @class Backgrid.TimeCell
+   @extends Backgrid.DatetimeCell
+*/
+var TimeCell = Backgrid.TimeCell = DatetimeCell.extend({
+
+  /** @property */
+  className: "time-cell",
+
+  /** @property */
+  includeDate: false
+
+});
+
+/**
+   BooleanCellEditor renders a checkbox as its editor.
+
+   @class Backgrid.BooleanCellEditor
+   @extends Backgrid.CellEditor
+*/
+var BooleanCellEditor = Backgrid.BooleanCellEditor = CellEditor.extend({
+
+  /** @property */
+  tagName: "input",
+
+  /** @property */
+  attributes: {
+    tabIndex: -1,
+    type: "checkbox"
+  },
+
+  /** @property */
+  events: {
+    "mousedown": function () {
+      this.mouseDown = true;
+    },
+    "blur": "enterOrExitEditMode",
+    "mouseup": function () {
+      this.mouseDown = false;
+    },
+    "change": "saveOrCancel",
+    "keydown": "saveOrCancel"
+  },
+
+  /**
+     Renders a checkbox and check it if the model value of this column is true,
+     uncheck otherwise.
+  */
+  render: function () {
+    var model = this.model;
+    var val = this.formatter.fromRaw(model.get(this.column.get("name")), model);
+    this.$el.prop("checked", val);
+    return this;
+  },
+
+  /**
+     Event handler. Hack to deal with the case where `blur` is fired before
+     `change` and `click` on a checkbox.
+  */
+  enterOrExitEditMode: function (e) {
+    if (!this.mouseDown) {
+      var model = this.model;
+      model.trigger("backgrid:edited", model, this.column, new Command(e));
+    }
+  },
+
+  /**
+     Event handler. Save the value into the model if the event is `change` or
+     one of the keyboard navigation key presses. Exit edit mode without saving
+     if `escape` was pressed.
+  */
+  saveOrCancel: function (e) {
+    var model = this.model;
+    var column = this.column;
+    var formatter = this.formatter;
+    var command = new Command(e);
+    // skip ahead to `change` when space is pressed
+    if (command.passThru() && e.type != "change") return true;
+    if (command.cancel()) {
+      e.stopPropagation();
+      model.trigger("backgrid:edited", model, column, command);
+    }
+
+    var $el = this.$el;
+    if (command.save() || command.moveLeft() || command.moveRight() || command.moveUp() ||
+        command.moveDown()) {
+      e.preventDefault();
+      e.stopPropagation();
+      var val = formatter.toRaw($el.prop("checked"), model);
+      model.set(column.get("name"), val);
+      model.trigger("backgrid:edited", model, column, command);
+    }
+    else if (e.type == "change") {
+      var val = formatter.toRaw($el.prop("checked"), model);
+      model.set(column.get("name"), val);
+      $el.focus();
+    }
+  }
+
+});
+
+/**
+   BooleanCell renders a checkbox both during display mode and edit mode. The
+   checkbox is checked if the model value is true, unchecked otherwise.
+
+   @class Backgrid.BooleanCell
+   @extends Backgrid.Cell
+*/
+var BooleanCell = Backgrid.BooleanCell = Cell.extend({
+
+  /** @property */
+  className: "boolean-cell",
+
+  /** @property */
+  editor: BooleanCellEditor,
+
+  /** @property */
+  events: {
+    "click": "enterEditMode"
+  },
+
+  /**
+     Renders a checkbox and check it if the model value of this column is true,
+     uncheck otherwise.
+  */
+  render: function () {
+    this.$el.empty();
+    var model = this.model, column = this.column;
+    var editable = Backgrid.callByNeed(column.editable(), column, model);
+    this.$el.append($("<input>", {
+      tabIndex: -1,
+      type: "checkbox",
+      checked: this.formatter.fromRaw(model.get(column.get("name")), model),
+      disabled: !editable
+    }));
+    this.delegateEvents();
+    return this;
+  }
+
+});
+
+/**
+   SelectCellEditor renders an HTML `<select>` fragment as the editor.
+
+   @class Backgrid.SelectCellEditor
+   @extends Backgrid.CellEditor
+*/
+var SelectCellEditor = Backgrid.SelectCellEditor = CellEditor.extend({
+
+  /** @property */
+  tagName: "select",
+
+  /** @property */
+  events: {
+    "change": "save",
+    "blur": "close",
+    "keydown": "close"
+  },
+
+  /** @property {function(Object, ?Object=): string} template */
+  template: _.template('<option value="<%- value %>" <%= selected ? \'selected="selected"\' : "" %>><%- text %></option>', null, {variable: null}),
+
+  setOptionValues: function (optionValues) {
+    this.optionValues = optionValues;
+    this.optionValues = _.result(this, "optionValues");
+  },
+
+  setMultiple: function (multiple) {
+    this.multiple = multiple;
+    this.$el.prop("multiple", multiple);
+  },
+
+  _renderOptions: function (nvps, selectedValues) {
+    var options = '';
+    for (var i = 0; i < nvps.length; i++) {
+      options = options + this.template({
+        text: nvps[i][0],
+        value: nvps[i][1],
+        selected: _.indexOf(selectedValues, nvps[i][1]) > -1
+      });
+    }
+    return options;
+  },
+
+  /**
+     Renders the options if `optionValues` is a list of name-value pairs. The
+     options are contained inside option groups if `optionValues` is a list of
+     object hashes. The name is rendered at the option text and the value is the
+     option value. If `optionValues` is a function, it is called without a
+     parameter.
+  */
+  render: function () {
+    this.$el.empty();
+
+    var optionValues = _.result(this, "optionValues");
+    var model = this.model;
+    var selectedValues = this.formatter.fromRaw(model.get(this.column.get("name")), model);
+
+    if (!_.isArray(optionValues)) throw new TypeError("optionValues must be an array");
+
+    var optionValue = null;
+    var optionText = null;
+    var optionValue = null;
+    var optgroupName = null;
+    var optgroup = null;
+
+    for (var i = 0; i < optionValues.length; i++) {
+      var optionValue = optionValues[i];
+
+      if (_.isArray(optionValue)) {
+        optionText  = optionValue[0];
+        optionValue = optionValue[1];
+
+        this.$el.append(this.template({
+          text: optionText,
+          value: optionValue,
+          selected: _.indexOf(selectedValues, optionValue) > -1
+        }));
+      }
+      else if (_.isObject(optionValue)) {
+        optgroupName = optionValue.name;
+        optgroup = $("<optgroup></optgroup>", { label: optgroupName });
+        optgroup.append(this._renderOptions.call(this, optionValue.values, selectedValues));
+        this.$el.append(optgroup);
+      }
+      else {
+        throw new TypeError("optionValues elements must be a name-value pair or an object hash of { name: 'optgroup label', value: [option name-value pairs] }");
+      }
+    }
+
+    this.delegateEvents();
+
+    return this;
+  },
+
+  /**
+     Saves the value of the selected option to the model attribute.
+  */
+  save: function (e) {
+    var model = this.model;
+    var column = this.column;
+    model.set(column.get("name"), this.formatter.toRaw(this.$el.val(), model));
+  },
+
+  /**
+     Triggers a `backgrid:edited` event from the model so the body can close
+     this editor.
+  */
+  close: function (e) {
+    var model = this.model;
+    var column = this.column;
+    var command = new Command(e);
+    if (command.cancel()) {
+      e.stopPropagation();
+      model.trigger("backgrid:edited", model, column, new Command(e));
+    }
+    else if (command.save() || command.moveLeft() || command.moveRight() ||
+             command.moveUp() || command.moveDown() || e.type == "blur") {
+      e.preventDefault();
+      e.stopPropagation();
+      this.save(e);
+      model.trigger("backgrid:edited", model, column, new Command(e));
+    }
+  }
+
+});
+
+/**
+   SelectCell is also a different kind of cell in that upon going into edit mode
+   the cell renders a list of options to pick from, as opposed to an input box.
+
+   SelectCell cannot be referenced by its string name when used in a column
+   definition because it requires an `optionValues` class attribute to be
+   defined. `optionValues` can either be a list of name-value pairs, to be
+   rendered as options, or a list of object hashes which consist of a key *name*
+   which is the option group name, and a key *values* which is a list of
+   name-value pairs to be rendered as options under that option group.
+
+   In addition, `optionValues` can also be a parameter-less function that
+   returns one of the above. If the options are static, it is recommended the
+   returned values to be memoized. `_.memoize()` is a good function to help with
+   that.
+
+   During display mode, the default formatter will normalize the raw model value
+   to an array of values whether the raw model value is a scalar or an
+   array. Each value is compared with the `optionValues` values using
+   Ecmascript's implicit type conversion rules. When exiting edit mode, no type
+   conversion is performed when saving into the model. This behavior is not
+   always desirable when the value type is anything other than string. To
+   control type conversion on the client-side, you should subclass SelectCell to
+   provide a custom formatter or provide the formatter to your column
+   definition.
+
+   See:
+     [$.fn.val()](http://api.jquery.com/val/)
+
+   @class Backgrid.SelectCell
+   @extends Backgrid.Cell
+*/
+var SelectCell = Backgrid.SelectCell = Cell.extend({
+
+  /** @property */
+  className: "select-cell",
+
+  /** @property */
+  editor: SelectCellEditor,
+
+  /** @property */
+  multiple: false,
+
+  /** @property */
+  formatter: SelectFormatter,
+
+  /**
+     @property {Array.<Array>|Array.<{name: string, values: Array.<Array>}>} optionValues
+  */
+  optionValues: undefined,
+
+  /** @property */
+  delimiter: ', ',
+
+  /**
+     Initializer.
+
+     @param {Object} options
+     @param {Backbone.Model} options.model
+     @param {Backgrid.Column} options.column
+
+     @throws {TypeError} If `optionsValues` is undefined.
+  */
+  initialize: function (options) {
+    SelectCell.__super__.initialize.apply(this, arguments);
+    this.listenTo(this.model, "backgrid:edit", function (model, column, cell, editor) {
+      if (column.get("name") == this.column.get("name")) {
+        editor.setOptionValues(this.optionValues);
+        editor.setMultiple(this.multiple);
+      }
+    });
+  },
+
+  /**
+     Renders the label using the raw value as key to look up from `optionValues`.
+
+     @throws {TypeError} If `optionValues` is malformed.
+  */
+  render: function () {
+    this.$el.empty();
+
+    var optionValues = _.result(this, "optionValues");
+    var model = this.model;
+    var rawData = this.formatter.fromRaw(model.get(this.column.get("name")), model);
+
+    var selectedText = [];
+
+    try {
+      if (!_.isArray(optionValues) || _.isEmpty(optionValues)) throw new TypeError;
+
+      for (var k = 0; k < rawData.length; k++) {
+        var rawDatum = rawData[k];
+
+        for (var i = 0; i < optionValues.length; i++) {
+          var optionValue = optionValues[i];
+
+          if (_.isArray(optionValue)) {
+            var optionText  = optionValue[0];
+            var optionValue = optionValue[1];
+
+            if (optionValue == rawDatum) selectedText.push(optionText);
+          }
+          else if (_.isObject(optionValue)) {
+            var optionGroupValues = optionValue.values;
+
+            for (var j = 0; j < optionGroupValues.length; j++) {
+              var optionGroupValue = optionGroupValues[j];
+              if (optionGroupValue[1] == rawDatum) {
+                selectedText.push(optionGroupValue[0]);
+              }
+            }
+          }
+          else {
+            throw new TypeError;
+          }
+        }
+      }
+
+      this.$el.append(selectedText.join(this.delimiter));
+    }
+    catch (ex) {
+      if (ex instanceof TypeError) {
+        throw new TypeError("'optionValues' must be of type {Array.<Array>|Array.<{name: string, values: Array.<Array>}>}");
+      }
+      throw ex;
+    }
+
+    this.delegateEvents();
+
+    return this;
+  }
+
+});
+
+/*
+  backgrid
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT license.
+*/
+
+/**
+   A Column is a placeholder for column metadata.
+
+   You usually don't need to create an instance of this class yourself as a
+   collection of column instances will be created for you from a list of column
+   attributes in the Backgrid.js view class constructors.
+
+   @class Backgrid.Column
+   @extends Backbone.Model
+*/
+var Column = Backgrid.Column = Backbone.Model.extend({
+
+  /**
+     @cfg {Object} defaults Column defaults. To override any of these default
+     values, you can either change the prototype directly to override
+     Column.defaults globally or extend Column and supply the custom class to
+     Backgrid.Grid:
+
+         // Override Column defaults globally
+         Column.prototype.defaults.sortable = false;
+
+         // Override Column defaults locally
+         var MyColumn = Column.extend({
+           defaults: _.defaults({
+             editable: false
+           }, Column.prototype.defaults)
+         });
+
+         var grid = new Backgrid.Grid(columns: new Columns([{...}, {...}], {
+           model: MyColumn
+         }));
+
+     @cfg {string} [defaults.name] The default name of the model attribute.
+
+     @cfg {string} [defaults.label] The default label to show in the header.
+
+     @cfg {string|Backgrid.Cell} [defaults.cell] The default cell type. If this
+     is a string, the capitalized form will be used to look up a cell class in
+     Backbone, i.e.: string => StringCell. If a Cell subclass is supplied, it is
+     initialized with a hash of parameters. If a Cell instance is supplied, it
+     is used directly.
+
+     @cfg {string|Backgrid.HeaderCell} [defaults.headerCell] The default header
+     cell type.
+
+     @cfg {boolean|string|function(): boolean} [defaults.sortable=true] Whether
+     this column is sortable. If the value is a string, a method will the same
+     name will be looked up from the column instance to determine whether the
+     column should be sortable. The method's signature must be `function
+     (Backgrid.Column, Backbone.Model): boolean`.
+
+     @cfg {boolean|string|function(): boolean} [defaults.editable=true] Whether
+     this column is editable. If the value is a string, a method will the same
+     name will be looked up from the column instance to determine whether the
+     column should be editable. The method's signature must be `function
+     (Backgrid.Column, Backbone.Model): boolean`.
+
+     @cfg {boolean|string|function(): boolean} [defaults.renderable=true]
+     Whether this column is renderable. If the value is a string, a method will
+     the same name will be looked up from the column instance to determine
+     whether the column should be renderable. The method's signature must be
+     `function (Backrid.Column, Backbone.Model): boolean`.
+
+     @cfg {Backgrid.CellFormatter | Object | string} [defaults.formatter] The
+     formatter to use to convert between raw model values and user input.
+
+     @cfg {"toggle"|"cycle"} [defaults.sortType="cycle"] Whether sorting will
+     toggle between ascending and descending order, or cycle between insertion
+     order, ascending and descending order.
+
+     @cfg {(function(Backbone.Model, string): *) | string} [defaults.sortValue]
+     The function to use to extract a value from the model for comparison during
+     sorting. If this value is a string, a method with the same name will be
+     looked up from the column instance.
+
+     @cfg {"ascending"|"descending"|null} [defaults.direction=null] The initial
+     sorting direction for this column. The default is ordered by
+     Backbone.Model.cid, which usually means the collection is ordered by
+     insertion order.
+  */
+  defaults: {
+    name: undefined,
+    label: undefined,
+    sortable: true,
+    editable: true,
+    renderable: true,
+    formatter: undefined,
+    sortType: "cycle",
+    sortValue: undefined,
+    direction: null,
+    cell: undefined,
+    headerCell: undefined
+  },
+
+  /**
+     Initializes this Column instance.
+
+     @param {Object} attrs
+
+     @param {string} attrs.name The model attribute this column is responsible
+     for.
+
+     @param {string|Backgrid.Cell} attrs.cell The cell type to use to render
+     this column.
+
+     @param {string} [attrs.label]
+
+     @param {string|Backgrid.HeaderCell} [attrs.headerCell]
+
+     @param {boolean|string|function(): boolean} [attrs.sortable=true]
+
+     @param {boolean|string|function(): boolean} [attrs.editable=true]
+
+     @param {boolean|string|function(): boolean} [attrs.renderable=true]
+
+     @param {Backgrid.CellFormatter | Object | string} [attrs.formatter]
+
+     @param {"toggle"|"cycle"}  [attrs.sortType="cycle"]
+
+     @param {(function(Backbone.Model, string): *) | string} [attrs.sortValue]
+
+     @throws {TypeError} If attrs.cell or attrs.options are not supplied.
+
+     @throws {ReferenceError} If formatter is a string but a formatter class of
+     said name cannot be found in the Backgrid module.
+
+     See:
+
+     - Backgrid.Column.defaults
+     - Backgrid.Cell
+     - Backgrid.CellFormatter
+   */
+  initialize: function () {
+    if (!this.has("label")) {
+      this.set({ label: this.get("name") }, { silent: true });
+    }
+
+    var headerCell = Backgrid.resolveNameToClass(this.get("headerCell"), "HeaderCell");
+
+    var cell = Backgrid.resolveNameToClass(this.get("cell"), "Cell");
+
+    this.set({cell: cell, headerCell: headerCell}, { silent: true });
+  },
+
+  /**
+     Returns an appropriate value extraction function from a model for sorting.
+
+     If the column model contains an attribute `sortValue`, if it is a string, a
+     method from the column instance identifified by the `sortValue` string is
+     returned. If it is a function, it it returned as is. If `sortValue` isn't
+     found from the column model's attributes, a default value extraction
+     function is returned which will compare according to the natural order of
+     the value's type.
+
+     @return {function(Backbone.Model, string): *}
+   */
+  sortValue: function () {
+    var sortValue = this.get("sortValue");
+    if (_.isString(sortValue)) return this[sortValue];
+    else if (_.isFunction(sortValue)) return sortValue;
+
+    return function (model, colName) {
+      return model.get(colName);
+    };
+  }
+
+  /**
+     @member Backgrid.Column
+     @protected
+     @method sortable
+     @return {function(Backgrid.Column, Backbone.Model): boolean | boolean}
+  */
+
+  /**
+     @member Backgrid.Column
+     @protected
+     @method editable
+     @return {function(Backgrid.Column, Backbone.Model): boolean | boolean}
+  */
+
+  /**
+     @member Backgrid.Column
+     @protected
+     @method renderable
+     @return {function(Backgrid.Column, Backbone.Model): boolean | boolean}
+  */
+});
+
+_.each(["sortable", "renderable", "editable"], function (key) {
+  Column.prototype[key] = function () {
+    var value = this.get(key);
+    if (_.isString(value)) return this[value];
+    else if (_.isFunction(value)) return value;
+
+    return !!value;
+  };
+});
+
+/**
+   A Backbone collection of Column instances.
+
+   @class Backgrid.Columns
+   @extends Backbone.Collection
+ */
+var Columns = Backgrid.Columns = Backbone.Collection.extend({
+
+  /**
+     @property {Backgrid.Column} model
+   */
+  model: Column
+});
+
+/*
+  backgrid
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT license.
+*/
+
+/**
+   Row is a simple container view that takes a model instance and a list of
+   column metadata describing how each of the model's attribute is to be
+   rendered, and apply the appropriate cell to each attribute.
+
+   @class Backgrid.Row
+   @extends Backbone.View
+*/
+var Row = Backgrid.Row = Backbone.View.extend({
+
+  /** @property */
+  tagName: "tr",
+
+  /**
+     Initializes a row view instance.
+
+     @param {Object} options
+     @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns Column metadata.
+     @param {Backbone.Model} options.model The model instance to render.
+
+     @throws {TypeError} If options.columns or options.model is undefined.
+  */
+  initialize: function (options) {
+
+    var columns = this.columns = options.columns;
+    if (!(columns instanceof Backbone.Collection)) {
+      columns = this.columns = new Columns(columns);
+    }
+
+    var cells = this.cells = [];
+    for (var i = 0; i < columns.length; i++) {
+      cells.push(this.makeCell(columns.at(i), options));
+    }
+
+    this.listenTo(columns, "add", function (column, columns) {
+      var i = columns.indexOf(column);
+      var cell = this.makeCell(column, options);
+      cells.splice(i, 0, cell);
+
+      var $el = this.$el;
+      if (i === 0) {
+        $el.prepend(cell.render().$el);
+      }
+      else if (i === columns.length - 1) {
+        $el.append(cell.render().$el);
+      }
+      else {
+        $el.children().eq(i).before(cell.render().$el);
+      }
+    });
+
+    this.listenTo(columns, "remove", function (column, columns, opts) {
+      cells[opts.index].remove();
+      cells.splice(opts.index, 1);
+    });
+  },
+
+  /**
+     Factory method for making a cell. Used by #initialize internally. Override
+     this to provide an appropriate cell instance for a custom Row subclass.
+
+     @protected
+
+     @param {Backgrid.Column} column
+     @param {Object} options The options passed to #initialize.
+
+     @return {Backgrid.Cell}
+  */
+  makeCell: function (column) {
+    return new (column.get("cell"))({
+      column: column,
+      model: this.model
+    });
+  },
+
+  /**
+     Renders a row of cells for this row's model.
+  */
+  render: function () {
+    this.$el.empty();
+
+    var fragment = document.createDocumentFragment();
+    for (var i = 0; i < this.cells.length; i++) {
+      fragment.appendChild(this.cells[i].render().el);
+    }
+
+    this.el.appendChild(fragment);
+
+    this.delegateEvents();
+
+    return this;
+  },
+
+  /**
+     Clean up this row and its cells.
+
+     @chainable
+  */
+  remove: function () {
+    for (var i = 0; i < this.cells.length; i++) {
+      var cell = this.cells[i];
+      cell.remove.apply(cell, arguments);
+    }
+    return Backbone.View.prototype.remove.apply(this, arguments);
+  }
+
+});
+
+/**
+   EmptyRow is a simple container view that takes a list of column and render a
+   row with a single column.
+
+   @class Backgrid.EmptyRow
+   @extends Backbone.View
+*/
+var EmptyRow = Backgrid.EmptyRow = Backbone.View.extend({
+
+  /** @property */
+  tagName: "tr",
+
+  /** @property {string|function(): string} */
+  emptyText: null,
+
+  /**
+     Initializer.
+
+     @param {Object} options
+     @param {string|function(): string} options.emptyText
+     @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns Column metadata.
+   */
+  initialize: function (options) {
+    this.emptyText = options.emptyText;
+    this.columns =  options.columns;
+  },
+
+  /**
+     Renders an empty row.
+  */
+  render: function () {
+    this.$el.empty();
+
+    var td = document.createElement("td");
+    td.setAttribute("colspan", this.columns.length);
+    td.appendChild(document.createTextNode(_.result(this, "emptyText")));
+
+    this.el.className = "empty";
+    this.el.appendChild(td);
+
+    return this;
+  }
+});
+
+/*
+  backgrid
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT license.
+*/
+
+/**
+   HeaderCell is a special cell class that renders a column header cell. If the
+   column is sortable, a sorter is also rendered and will trigger a table
+   refresh after sorting.
+
+   @class Backgrid.HeaderCell
+   @extends Backbone.View
+ */
+var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
+
+  /** @property */
+  tagName: "th",
+
+  /** @property */
+  events: {
+    "click a": "onClick"
+  },
+
+  /**
+     Initializer.
+
+     @param {Object} options
+     @param {Backgrid.Column|Object} options.column
+
+     @throws {TypeError} If options.column or options.collection is undefined.
+   */
+  initialize: function (options) {
+    this.column = options.column;
+    if (!(this.column instanceof Column)) {
+      this.column = new Column(this.column);
+    }
+
+    var column = this.column, collection = this.collection, $el = this.$el;
+
+    this.listenTo(column, "change:editable change:sortable change:renderable",
+                  function (column) {
+                    var changed = column.changedAttributes();
+                    for (var key in changed) {
+                      if (changed.hasOwnProperty(key)) {
+                        $el.toggleClass(key, changed[key]);
+                      }
+                    }
+                  });
+    this.listenTo(column, "change:direction", this.setCellDirection);
+    this.listenTo(column, "change:name change:label", this.render);
+
+    if (Backgrid.callByNeed(column.editable(), column, collection)) $el.addClass("editable");
+    if (Backgrid.callByNeed(column.sortable(), column, collection)) $el.addClass("sortable");
+    if (Backgrid.callByNeed(column.renderable(), column, collection)) $el.addClass("renderable");
+
+    this.listenTo(collection.fullCollection || collection, "sort", this.removeCellDirection);
+  },
+
+  /**
+     Event handler for the collection's `sort` event. Removes all the CSS
+     direction classes.
+   */
+  removeCellDirection: function () {
+    this.$el.removeClass("ascending").removeClass("descending");
+    this.column.set("direction", null);
+  },
+
+  /**
+     Event handler for the column's `change:direction` event. If this
+     HeaderCell's column is being sorted on, it applies the direction given as a
+     CSS class to the header cell. Removes all the CSS direction classes
+     otherwise.
+   */
+  setCellDirection: function (column, direction) {
+    this.$el.removeClass("ascending").removeClass("descending");
+    if (column.cid == this.column.cid) this.$el.addClass(direction);
+  },
+
+  /**
+     Event handler for the `click` event on the cell's anchor. If the column is
+     sortable, clicking on the anchor will cycle through 3 sorting orderings -
+     `ascending`, `descending`, and default.
+   */
+  onClick: function (e) {
+    e.preventDefault();
+
+    var column = this.column;
+    var collection = this.collection;
+    var event = "backgrid:sort";
+
+    function cycleSort(header, col) {
+      if (column.get("direction") === "ascending") collection.trigger(event, col, "descending");
+      else if (column.get("direction") === "descending") collection.trigger(event, col, null);
+      else collection.trigger(event, col, "ascending");
+    }
+
+    function toggleSort(header, col) {
+      if (column.get("direction") === "ascending") collection.trigger(event, col, "descending");
+      else collection.trigger(event, col, "ascending");
+    }
+
+    var sortable = Backgrid.callByNeed(column.sortable(), column, this.collection);
+    if (sortable) {
+      var sortType = column.get("sortType");
+      if (sortType === "toggle") toggleSort(this, column);
+      else cycleSort(this, column);
+    }
+  },
+
+  /**
+     Renders a header cell with a sorter, a label, and a class name for this
+     column.
+   */
+  render: function () {
+    this.$el.empty();
+    var column = this.column;
+    var sortable = Backgrid.callByNeed(column.sortable(), column, this.collection);
+    var label;
+    if(sortable){
+      label = $("<a>").text(column.get("label")).append("<b class='sort-caret'></b>");
+    } else {
+      label = document.createTextNode(column.get("label"));
+    }
+
+    this.$el.append(label);
+    this.$el.addClass(column.get("name"));
+    this.$el.addClass(column.get("direction"));
+    this.delegateEvents();
+    return this;
+  }
+
+});
+
+/**
+   HeaderRow is a controller for a row of header cells.
+
+   @class Backgrid.HeaderRow
+   @extends Backgrid.Row
+ */
+var HeaderRow = Backgrid.HeaderRow = Backgrid.Row.extend({
+
+  requiredOptions: ["columns", "collection"],
+
+  /**
+     Initializer.
+
+     @param {Object} options
+     @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns
+     @param {Backgrid.HeaderCell} [options.headerCell] Customized default
+     HeaderCell for all the columns. Supply a HeaderCell class or instance to a
+     the `headerCell` key in a column definition for column-specific header
+     rendering.
+
+     @throws {TypeError} If options.columns or options.collection is undefined.
+   */
+  initialize: function () {
+    Backgrid.Row.prototype.initialize.apply(this, arguments);
+  },
+
+  makeCell: function (column, options) {
+    var headerCell = column.get("headerCell") || options.headerCell || HeaderCell;
+    headerCell = new headerCell({
+      column: column,
+      collection: this.collection
+    });
+    return headerCell;
+  }
+
+});
+
+/**
+   Header is a special structural view class that renders a table head with a
+   single row of header cells.
+
+   @class Backgrid.Header
+   @extends Backbone.View
+ */
+var Header = Backgrid.Header = Backbone.View.extend({
+
+  /** @property */
+  tagName: "thead",
+
+  /**
+     Initializer. Initializes this table head view to contain a single header
+     row view.
+
+     @param {Object} options
+     @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns Column metadata.
+     @param {Backbone.Model} options.model The model instance to render.
+
+     @throws {TypeError} If options.columns or options.model is undefined.
+   */
+  initialize: function (options) {
+    this.columns = options.columns;
+    if (!(this.columns instanceof Backbone.Collection)) {
+      this.columns = new Columns(this.columns);
+    }
+
+    this.row = new Backgrid.HeaderRow({
+      columns: this.columns,
+      collection: this.collection
+    });
+  },
+
+  /**
+     Renders this table head with a single row of header cells.
+   */
+  render: function () {
+    this.$el.append(this.row.render().$el);
+    this.delegateEvents();
+    return this;
+  },
+
+  /**
+     Clean up this header and its row.
+
+     @chainable
+   */
+  remove: function () {
+    this.row.remove.apply(this.row, arguments);
+    return Backbone.View.prototype.remove.apply(this, arguments);
+  }
+
+});
+
+/*
+  backgrid
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT license.
+*/
+
+/**
+   Body is the table body which contains the rows inside a table. Body is
+   responsible for refreshing the rows after sorting, insertion and removal.
+
+   @class Backgrid.Body
+   @extends Backbone.View
+*/
+var Body = Backgrid.Body = Backbone.View.extend({
+
+  /** @property */
+  tagName: "tbody",
+
+  /**
+     Initializer.
+
+     @param {Object} options
+     @param {Backbone.Collection} options.collection
+     @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns
+     Column metadata.
+     @param {Backgrid.Row} [options.row=Backgrid.Row] The Row class to use.
+     @param {string|function(): string} [options.emptyText] The text to display in the empty row.
+
+     @throws {TypeError} If options.columns or options.collection is undefined.
+
+     See Backgrid.Row.
+  */
+  initialize: function (options) {
+
+    this.columns = options.columns;
+    if (!(this.columns instanceof Backbone.Collection)) {
+      this.columns = new Columns(this.columns);
+    }
+
+    this.row = options.row || Row;
+    this.rows = this.collection.map(function (model) {
+      var row = new this.row({
+        columns: this.columns,
+        model: model
+      });
+
+      return row;
+    }, this);
+
+    this.emptyText = options.emptyText;
+    this._unshiftEmptyRowMayBe();
+
+    var collection = this.collection;
+    this.listenTo(collection, "add", this.insertRow);
+    this.listenTo(collection, "remove", this.removeRow);
+    this.listenTo(collection, "sort", this.refresh);
+    this.listenTo(collection, "reset", this.refresh);
+    this.listenTo(collection, "backgrid:sort", this.sort);
+    this.listenTo(collection, "backgrid:edited", this.moveToNextCell);
+  },
+
+  _unshiftEmptyRowMayBe: function () {
+    if (this.rows.length === 0 && this.emptyText != null) {
+      this.rows.unshift(new EmptyRow({
+        emptyText: this.emptyText,
+        columns: this.columns
+      }));
+    }
+  },
+
+  /**
+     This method can be called either directly or as a callback to a
+     [Backbone.Collecton#add](http://backbonejs.org/#Collection-add) event.
+
+     When called directly, it accepts a model or an array of models and an
+     option hash just like
+     [Backbone.Collection#add](http://backbonejs.org/#Collection-add) and
+     delegates to it. Once the model is added, a new row is inserted into the
+     body and automatically rendered.
+
+     When called as a callback of an `add` event, splices a new row into the
+     body and renders it.
+
+     @param {Backbone.Model} model The model to render as a row.
+     @param {Backbone.Collection} collection When called directly, this
+     parameter is actually the options to
+     [Backbone.Collection#add](http://backbonejs.org/#Collection-add).
+     @param {Object} options When called directly, this must be null.
+
+     See:
+
+     - [Backbone.Collection#add](http://backbonejs.org/#Collection-add)
+  */
+  insertRow: function (model, collection, options) {
+
+    if (this.rows[0] instanceof EmptyRow) this.rows.pop().remove();
+
+    // insertRow() is called directly
+    if (!(collection instanceof Backbone.Collection) && !options) {
+      this.collection.add(model, (options = collection));
+      return;
+    }
+
+    var row = new this.row({
+      columns: this.columns,
+      model: model
+    });
+
+    var index = collection.indexOf(model);
+    this.rows.splice(index, 0, row);
+
+    var $el = this.$el;
+    var $children = $el.children();
+    var $rowEl = row.render().$el;
+
+    if (index >= $children.length) {
+      $el.append($rowEl);
+    }
+    else {
+      $children.eq(index).before($rowEl);
+    }
+
+    return this;
+  },
+
+  /**
+     The method can be called either directly or as a callback to a
+     [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove)
+     event.
+
+     When called directly, it accepts a model or an array of models and an
+     option hash just like
+     [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove) and
+     delegates to it. Once the model is removed, a corresponding row is removed
+     from the body.
+
+     When called as a callback of a `remove` event, splices into the rows and
+     removes the row responsible for rendering the model.
+
+     @param {Backbone.Model} model The model to remove from the body.
+     @param {Backbone.Collection} collection When called directly, this
+     parameter is actually the options to
+     [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove).
+     @param {Object} options When called directly, this must be null.
+
+     See:
+
+     - [Backbone.Collection#remove](http://backbonejs.org/#Collection-remove)
+  */
+  removeRow: function (model, collection, options) {
+
+    // removeRow() is called directly
+    if (!options) {
+      this.collection.remove(model, (options = collection));
+      this._unshiftEmptyRowMayBe();
+      return;
+    }
+
+    if (_.isUndefined(options.render) || options.render) {
+      this.rows[options.index].remove();
+    }
+
+    this.rows.splice(options.index, 1);
+    this._unshiftEmptyRowMayBe();
+
+    return this;
+  },
+
+  /**
+     Reinitialize all the rows inside the body and re-render them. Triggers a
+     Backbone `backgrid:refresh` event from the collection along with the body
+     instance as its sole parameter when done.
+  */
+  refresh: function () {
+    for (var i = 0; i < this.rows.length; i++) {
+      this.rows[i].remove();
+    }
+
+    this.rows = this.collection.map(function (model) {
+      var row = new this.row({
+        columns: this.columns,
+        model: model
+      });
+
+      return row;
+    }, this);
+    this._unshiftEmptyRowMayBe();
+
+    this.render();
+
+    this.collection.trigger("backgrid:refresh", this);
+
+    return this;
+  },
+
+  /**
+     Renders all the rows inside this body. If the collection is empty and
+     `options.emptyText` is defined and not null in the constructor, an empty
+     row is rendered, otherwise no row is rendered.
+  */
+  render: function () {
+    this.$el.empty();
+
+    var fragment = document.createDocumentFragment();
+    for (var i = 0; i < this.rows.length; i++) {
+      var row = this.rows[i];
+      fragment.appendChild(row.render().el);
+    }
+
+    this.el.appendChild(fragment);
+
+    this.delegateEvents();
+
+    return this;
+  },
+
+  /**
+     Clean up this body and it's rows.
+
+     @chainable
+  */
+  remove: function () {
+    for (var i = 0; i < this.rows.length; i++) {
+      var row = this.rows[i];
+      row.remove.apply(row, arguments);
+    }
+    return Backbone.View.prototype.remove.apply(this, arguments);
+  },
+
+  /**
+     If the underlying collection is a Backbone.PageableCollection in
+     server-mode or infinite-mode, a page of models is fetched after sorting is
+     done on the server.
+
+     If the underlying collection is a Backbone.PageableCollection in
+     client-mode, or any
+     [Backbone.Collection](http://backbonejs.org/#Collection) instance, sorting
+     is done on the client side. If the collection is an instance of a
+     Backbone.PageableCollection, sorting will be done globally on all the pages
+     and the current page will then be returned.
+
+     Triggers a Backbone `backgrid:sorted` event from the collection when done
+     with the column, direction and a reference to the collection.
+
+     @param {Backgrid.Column} column
+     @param {null|"ascending"|"descending"} direction
+
+     See [Backbone.Collection#comparator](http://backbonejs.org/#Collection-comparator)
+  */
+  sort: function (column, direction) {
+
+    if (!_.contains(["ascending", "descending", null], direction)) {
+      throw new RangeError('direction must be one of "ascending", "descending" or `null`');
+    }
+
+    if (_.isString(column)) column = this.columns.findWhere({name: column});
+
+    var collection = this.collection;
+
+    var order;
+    if (direction === "ascending") order = -1;
+    else if (direction === "descending") order = 1;
+    else order = null;
+
+    var comparator = this.makeComparator(column.get("name"), order,
+                                         order ?
+                                         column.sortValue() :
+                                         function (model) {
+                                           return model.cid.replace('c', '') * 1;
+                                         });
+
+    if (Backbone.PageableCollection &&
+        collection instanceof Backbone.PageableCollection) {
+
+      collection.setSorting(order && column.get("name"), order,
+                            {sortValue: column.sortValue()});
+
+      if (collection.fullCollection) {
+        // If order is null, pageable will remove the comparator on both sides,
+        // in this case the default insertion order comparator needs to be
+        // attached to get back to the order before sorting.
+        if (collection.fullCollection.comparator == null) {
+          collection.fullCollection.comparator = comparator;
+        }
+        collection.fullCollection.sort();
+        collection.trigger("backgrid:sorted", column, direction, collection);
+      }
+      else collection.fetch({reset: true, success: function () {
+        collection.trigger("backgrid:sorted", column, direction, collection);
+      }});
+    }
+    else {
+      collection.comparator = comparator;
+      collection.sort();
+      collection.trigger("backgrid:sorted", column, direction, collection);
+    }
+
+    column.set("direction", direction);
+
+    return this;
+  },
+
+  makeComparator: function (attr, order, func) {
+
+    return function (left, right) {
+      // extract the values from the models
+      var l = func(left, attr), r = func(right, attr), t;
+
+      // if descending order, swap left and right
+      if (order === 1) t = l, l = r, r = t;
+
+      // compare as usual
+      if (l === r) return 0;
+      else if (l < r) return -1;
+      return 1;
+    };
+  },
+
+  /**
+     Moves focus to the next renderable and editable cell and return the
+     currently editing cell to display mode.
+
+     Triggers a `backgrid:next` event on the model with the indices of the row
+     and column the user *intended* to move to, and whether the intended move
+     was going to go out of bounds. Note that *out of bound* always means an
+     attempt to go past the end of the last row.
+
+     @param {Backbone.Model} model The originating model
+     @param {Backgrid.Column} column The originating model column
+     @param {Backgrid.Command} command The Command object constructed from a DOM
+     event
+  */
+  moveToNextCell: function (model, column, command) {
+    var i = this.collection.indexOf(model);
+    var j = this.columns.indexOf(column);
+    var cell, renderable, editable, m, n;
+
+    this.rows[i].cells[j].exitEditMode();
+
+    if (command.moveUp() || command.moveDown() || command.moveLeft() ||
+        command.moveRight() || command.save()) {
+      var l = this.columns.length;
+      var maxOffset = l * this.collection.length;
+
+      if (command.moveUp() || command.moveDown()) {
+        m = i + (command.moveUp() ? -1 : 1);
+        var row = this.rows[m];
+        if (row) {
+          cell = row.cells[j];
+          if (Backgrid.callByNeed(cell.column.editable(), cell.column, model)) {
+            cell.enterEditMode();
+            model.trigger("backgrid:next", m, j, false);
+          }
+        }
+        else model.trigger("backgrid:next", m, j, true);
+      }
+      else if (command.moveLeft() || command.moveRight()) {
+        var right = command.moveRight();
+        for (var offset = i * l + j + (right ? 1 : -1);
+             offset >= 0 && offset < maxOffset;
+             right ? offset++ : offset--) {
+          m = ~~(offset / l);
+          n = offset - m * l;
+          cell = this.rows[m].cells[n];
+          renderable = Backgrid.callByNeed(cell.column.renderable(), cell.column, cell.model);
+          editable = Backgrid.callByNeed(cell.column.editable(), cell.column, model);
+          if (renderable && editable) {
+            cell.enterEditMode();
+            model.trigger("backgrid:next", m, n, false);
+            break;
+          }
+        }
+
+        if (offset == maxOffset) {
+          model.trigger("backgrid:next", ~~(offset / l), offset - m * l, true);
+        }
+      }
+    }
+
+    return this;
+  }
+});
+
+/*
+  backgrid
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT license.
+*/
+
+/**
+   A Footer is a generic class that only defines a default tag `tfoot` and
+   number of required parameters in the initializer.
+
+   @abstract
+   @class Backgrid.Footer
+   @extends Backbone.View
+ */
+var Footer = Backgrid.Footer = Backbone.View.extend({
+
+  /** @property */
+  tagName: "tfoot",
+
+  /**
+     Initializer.
+
+     @param {Object} options
+     @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns
+     Column metadata.
+     @param {Backbone.Collection} options.collection
+
+     @throws {TypeError} If options.columns or options.collection is undefined.
+  */
+  initialize: function (options) {
+    this.columns = options.columns;
+    if (!(this.columns instanceof Backbone.Collection)) {
+      this.columns = new Backgrid.Columns(this.columns);
+    }
+  }
+
+});
+
+/*
+  backgrid
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT license.
+*/
+
+/**
+   Grid represents a data grid that has a header, body and an optional footer.
+
+   By default, a Grid treats each model in a collection as a row, and each
+   attribute in a model as a column. To render a grid you must provide a list of
+   column metadata and a collection to the Grid constructor. Just like any
+   Backbone.View class, the grid is rendered as a DOM node fragment when you
+   call render().
+
+       var grid = Backgrid.Grid({
+         columns: [{ name: "id", label: "ID", type: "string" },
+          // ...
+         ],
+         collections: books
+       });
+
+       $("#table-container").append(grid.render().el);
+
+   Optionally, if you want to customize the rendering of the grid's header and
+   footer, you may choose to extend Backgrid.Header and Backgrid.Footer, and
+   then supply that class or an instance of that class to the Grid constructor.
+   See the documentation for Header and Footer for further details.
+
+       var grid = Backgrid.Grid({
+         columns: [{ name: "id", label: "ID", type: "string" }],
+         collections: books,
+         header: Backgrid.Header.extend({
+              //...
+         }),
+         footer: Backgrid.Paginator
+       });
+
+   Finally, if you want to override how the rows are rendered in the table body,
+   you can supply a Body subclass as the `body` attribute that uses a different
+   Row class.
+
+   @class Backgrid.Grid
+   @extends Backbone.View
+
+   See:
+
+   - Backgrid.Column
+   - Backgrid.Header
+   - Backgrid.Body
+   - Backgrid.Row
+   - Backgrid.Footer
+*/
+var Grid = Backgrid.Grid = Backbone.View.extend({
+
+  /** @property */
+  tagName: "table",
+
+  /** @property */
+  className: "backgrid",
+
+  /** @property */
+  header: Header,
+
+  /** @property */
+  body: Body,
+
+  /** @property */
+  footer: null,
+
+  /**
+     Initializes a Grid instance.
+
+     @param {Object} options
+     @param {Backbone.Collection.<Backgrid.Columns>|Array.<Backgrid.Column>|Array.<Object>} options.columns Column metadata.
+     @param {Backbone.Collection} options.collection The collection of tabular model data to display.
+     @param {Backgrid.Header} [options.header=Backgrid.Header] An optional Header class to override the default.
+     @param {Backgrid.Body} [options.body=Backgrid.Body] An optional Body class to override the default.
+     @param {Backgrid.Row} [options.row=Backgrid.Row] An optional Row class to override the default.
+     @param {Backgrid.Footer} [options.footer=Backgrid.Footer] An optional Footer class.
+   */
+  initialize: function (options) {
+    // Convert the list of column objects here first so the subviews don't have
+    // to.
+    if (!(options.columns instanceof Backbone.Collection)) {
+      options.columns = new Columns(options.columns);
+    }
+    this.columns = options.columns;
+
+    var filteredOptions = _.omit(options, ["el", "id", "attributes",
+                                           "className", "tagName", "events"]);
+
+    // must construct body first so it listens to backgrid:sort first
+    this.body = options.body || this.body;
+    this.body = new this.body(filteredOptions);
+
+    this.header = options.header || this.header;
+    if (this.header) {
+      this.header = new this.header(filteredOptions);
+    }
+
+    this.footer = options.footer || this.footer;
+    if (this.footer) {
+      this.footer = new this.footer(filteredOptions);
+    }
+
+    this.listenTo(this.columns, "reset", function () {
+      if (this.header) {
+        this.header = new (this.header.remove().constructor)(filteredOptions);
+      }
+      this.body = new (this.body.remove().constructor)(filteredOptions);
+      if (this.footer) {
+        this.footer = new (this.footer.remove().constructor)(filteredOptions);
+      }
+      this.render();
+    });
+  },
+
+  /**
+     Delegates to Backgrid.Body#insertRow.
+   */
+  insertRow: function () {
+    this.body.insertRow.apply(this.body, arguments);
+    return this;
+  },
+
+  /**
+     Delegates to Backgrid.Body#removeRow.
+   */
+  removeRow: function () {
+    this.body.removeRow.apply(this.body, arguments);
+    return this;
+  },
+
+  /**
+     Delegates to Backgrid.Columns#add for adding a column. Subviews can listen
+     to the `add` event from their internal `columns` if rerendering needs to
+     happen.
+
+     @param {Object} [options] Options for `Backgrid.Columns#add`.
+   */
+  insertColumn: function () {
+    this.columns.add.apply(this.columns, arguments);
+    return this;
+  },
+
+  /**
+     Delegates to Backgrid.Columns#remove for removing a column. Subviews can
+     listen to the `remove` event from the internal `columns` if rerendering
+     needs to happen.
+
+     @param {Object} [options] Options for `Backgrid.Columns#remove`.
+   */
+  removeColumn: function () {
+    this.columns.remove.apply(this.columns, arguments);
+    return this;
+  },
+
+  /**
+     Delegates to Backgrid.Body#sort.
+   */
+  sort: function () {
+    this.body.sort.apply(this.body, arguments);
+    return this;
+  },
+
+  /**
+     Renders the grid's header, then footer, then finally the body. Triggers a
+     Backbone `backgrid:rendered` event along with a reference to the grid when
+     the it has successfully been rendered.
+   */
+  render: function () {
+    this.$el.empty();
+
+    if (this.header) {
+      this.$el.append(this.header.render().$el);
+    }
+
+    if (this.footer) {
+      this.$el.append(this.footer.render().$el);
+    }
+
+    this.$el.append(this.body.render().$el);
+
+    this.delegateEvents();
+
+    this.trigger("backgrid:rendered", this);
+
+    return this;
+  },
+
+  /**
+     Clean up this grid and its subviews.
+
+     @chainable
+   */
+  remove: function () {
+    this.header && this.header.remove.apply(this.header, arguments);
+    this.body.remove.apply(this.body, arguments);
+    this.footer && this.footer.remove.apply(this.footer, arguments);
+    return Backbone.View.prototype.remove.apply(this, arguments);
+  }
+
+});
+return Backgrid;
+}));
\ No newline at end of file