PageRenderTime 6ms CodeModel.GetById 3ms app.highlight 51ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/backgrid.js

https://github.com/antonyraj/backgrid
JavaScript | 2531 lines | 1117 code | 328 blank | 1086 comment | 238 complexity | 39f78fb6e0e210d7c7dcd6173e5cbdf1 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/*
   2  backgrid
   3  http://github.com/wyuenho/backgrid
   4
   5  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
   6  Licensed under the MIT @license.
   7*/
   8(function (root, $, _, Backbone) {
   9
  10  "use strict";
  11/*
  12  backgrid
  13  http://github.com/wyuenho/backgrid
  14
  15  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
  16  Licensed under the MIT @license.
  17*/
  18
  19var window = root;
  20
  21// Copyright 2009, 2010 Kristopher Michael Kowal
  22// https://github.com/kriskowal/es5-shim
  23// ES5 15.5.4.20
  24// http://es5.github.com/#x15.5.4.20
  25var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" +
  26  "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" +
  27  "\u2029\uFEFF";
  28if (!String.prototype.trim || ws.trim()) {
  29  // http://blog.stevenlevithan.com/archives/faster-trim-javascript
  30  // http://perfectionkills.com/whitespace-deviations/
  31  ws = "[" + ws + "]";
  32  var trimBeginRegexp = new RegExp("^" + ws + ws + "*"),
  33  trimEndRegexp = new RegExp(ws + ws + "*$");
  34  String.prototype.trim = function trim() {
  35    if (this === undefined || this === null) {
  36      throw new TypeError("can't convert " + this + " to object");
  37    }
  38    return String(this)
  39      .replace(trimBeginRegexp, "")
  40      .replace(trimEndRegexp, "");
  41  };
  42}
  43
  44function capitalize(s) {
  45  return String.fromCharCode(s.charCodeAt(0) - 32) + s.slice(1);
  46}
  47
  48function lpad(str, length, padstr) {
  49  var paddingLen = length - (str + '').length;
  50  paddingLen =  paddingLen < 0 ? 0 : paddingLen;
  51  var padding = '';
  52  for (var i = 0; i < paddingLen; i++) {
  53    padding = padding + padstr;
  54  }
  55  return padding + str;
  56}
  57
  58var Backgrid = root.Backgrid = {
  59
  60  VERSION: "0.2.6",
  61
  62  Extension: {},
  63
  64  requireOptions: function (options, requireOptionKeys) {
  65    for (var i = 0; i < requireOptionKeys.length; i++) {
  66      var key = requireOptionKeys[i];
  67      if (_.isUndefined(options[key])) {
  68        throw new TypeError("'" + key  + "' is required");
  69      }
  70    }
  71  },
  72
  73  resolveNameToClass: function (name, suffix) {
  74    if (_.isString(name)) {
  75      var key = _.map(name.split('-'), function (e) { return capitalize(e); }).join('') + suffix;
  76      var klass = Backgrid[key] || Backgrid.Extension[key];
  77      if (_.isUndefined(klass)) {
  78        throw new ReferenceError("Class '" + key + "' not found");
  79      }
  80      return klass;
  81    }
  82
  83    return name;
  84  }
  85};
  86_.extend(Backgrid, Backbone.Events);
  87
  88/**
  89   Command translates a DOM Event into commands that Backgrid
  90   recognizes. Interested parties can listen on selected Backgrid events that
  91   come with an instance of this class and act on the commands.
  92
  93   It is also possible to globally rebind the keyboard shortcuts by replacing
  94   the methods in this class' prototype.
  95
  96   @class Backgrid.Command
  97   @constructor
  98 */
  99var Command = Backgrid.Command = function (evt) {
 100  _.extend(this, {
 101    altKey: !!evt.altKey,
 102    char: evt.char,
 103    charCode: evt.charCode,
 104    ctrlKey: !!evt.ctrlKey,
 105    key: evt.key,
 106    keyCode: evt.keyCode,
 107    locale: evt.locale,
 108    location: evt.location,
 109    metaKey: !!evt.metaKey,
 110    repeat: !!evt.repeat,
 111    shiftKey: !!evt.shiftKey,
 112    which: evt.which
 113  });
 114};
 115_.extend(Command.prototype, {
 116  /**
 117     Up Arrow
 118
 119     @member Backgrid.Command
 120   */
 121  moveUp: function () { return this.keyCode == 38; },
 122  /**
 123     Down Arrow
 124
 125     @member Backgrid.Command
 126   */
 127  moveDown: function () { return this.keyCode === 40; },
 128  /**
 129     Shift Tab
 130
 131     @member Backgrid.Command
 132   */
 133  moveLeft: function () { return this.shiftKey && this.keyCode === 9; },
 134  /**
 135     Tab
 136
 137     @member Backgrid.Command
 138   */
 139  moveRight: function () { return !this.shiftKey && this.keyCode === 9; },
 140  /**
 141     Enter
 142
 143     @member Backgrid.Command
 144   */
 145  save: function () { return this.keyCode === 13; },
 146  /**
 147     Esc
 148
 149     @member Backgrid.Command
 150   */
 151  cancel: function () { return this.keyCode === 27; },
 152  /**
 153     None of the above.
 154
 155     @member Backgrid.Command
 156   */
 157  passThru: function () {
 158    return !(this.moveUp() || this.moveDown() || this.moveLeft() ||
 159             this.moveRight() || this.save() || this.cancel());
 160  }
 161});
 162
 163/*
 164  backgrid
 165  http://github.com/wyuenho/backgrid
 166
 167  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
 168  Licensed under the MIT @license.
 169*/
 170
 171/**
 172   Just a convenient class for interested parties to subclass.
 173
 174   The default Cell classes don't require the formatter to be a subclass of
 175   Formatter as long as the fromRaw(rawData) and toRaw(formattedData) methods
 176   are defined.
 177
 178   @abstract
 179   @class Backgrid.CellFormatter
 180   @constructor
 181*/
 182var CellFormatter = Backgrid.CellFormatter = function () {};
 183_.extend(CellFormatter.prototype, {
 184
 185  /**
 186     Takes a raw value from a model and returns an optionally formatted string
 187     for display. The default implementation simply returns the supplied value
 188     as is without any type conversion.
 189
 190     @member Backgrid.CellFormatter
 191     @param {*} rawData
 192     @return {*}
 193  */
 194  fromRaw: function (rawData) {
 195    return rawData;
 196  },
 197
 198  /**
 199     Takes a formatted string, usually from user input, and returns a
 200     appropriately typed value for persistence in the model.
 201
 202     If the user input is invalid or unable to be converted to a raw value
 203     suitable for persistence in the model, toRaw must return `undefined`.
 204
 205     @member Backgrid.CellFormatter
 206     @param {string} formattedData
 207     @return {*|undefined}
 208  */
 209  toRaw: function (formattedData) {
 210    return formattedData;
 211  }
 212
 213});
 214
 215/**
 216   A floating point number formatter. Doesn't understand notation at the moment.
 217
 218   @class Backgrid.NumberFormatter
 219   @extends Backgrid.CellFormatter
 220   @constructor
 221   @throws {RangeError} If decimals < 0 or > 20.
 222*/
 223var NumberFormatter = Backgrid.NumberFormatter = function (options) {
 224  options = options ? _.clone(options) : {};
 225  _.extend(this, this.defaults, options);
 226
 227  if (this.decimals < 0 || this.decimals > 20) {
 228    throw new RangeError("decimals must be between 0 and 20");
 229  }
 230};
 231NumberFormatter.prototype = new CellFormatter();
 232_.extend(NumberFormatter.prototype, {
 233
 234  /**
 235     @member Backgrid.NumberFormatter
 236     @cfg {Object} options
 237
 238     @cfg {number} [options.decimals=2] Number of decimals to display. Must be an integer.
 239
 240     @cfg {string} [options.decimalSeparator='.'] The separator to use when
 241     displaying decimals.
 242
 243     @cfg {string} [options.orderSeparator=','] The separator to use to
 244     separator thousands. May be an empty string.
 245   */
 246  defaults: {
 247    decimals: 2,
 248    decimalSeparator: '.',
 249    orderSeparator: ','
 250  },
 251
 252  HUMANIZED_NUM_RE: /(\d)(?=(?:\d{3})+$)/g,
 253
 254  /**
 255     Takes a floating point number and convert it to a formatted string where
 256     every thousand is separated by `orderSeparator`, with a `decimal` number of
 257     decimals separated by `decimalSeparator`. The number returned is rounded
 258     the usual way.
 259
 260     @member Backgrid.NumberFormatter
 261     @param {number} number
 262     @return {string}
 263  */
 264  fromRaw: function (number) {
 265    if (_.isNull(number) || _.isUndefined(number)) return '';
 266
 267    number = number.toFixed(~~this.decimals);
 268
 269    var parts = number.split('.');
 270    var integerPart = parts[0];
 271    var decimalPart = parts[1] ? (this.decimalSeparator || '.') + parts[1] : '';
 272
 273    return integerPart.replace(this.HUMANIZED_NUM_RE, '$1' + this.orderSeparator) + decimalPart;
 274  },
 275
 276  /**
 277     Takes a string, possibly formatted with `orderSeparator` and/or
 278     `decimalSeparator`, and convert it back to a number.
 279
 280     @member Backgrid.NumberFormatter
 281     @param {string} formattedData
 282     @return {number|undefined} Undefined if the string cannot be converted to
 283     a number.
 284  */
 285  toRaw: function (formattedData) {
 286    var rawData = '';
 287
 288    var thousands = formattedData.trim().split(this.orderSeparator);
 289    for (var i = 0; i < thousands.length; i++) {
 290      rawData += thousands[i];
 291    }
 292
 293    var decimalParts = rawData.split(this.decimalSeparator);
 294    rawData = '';
 295    for (var i = 0; i < decimalParts.length; i++) {
 296      rawData = rawData + decimalParts[i] + '.';
 297    }
 298
 299    if (rawData[rawData.length - 1] === '.') {
 300      rawData = rawData.slice(0, rawData.length - 1);
 301    }
 302
 303    var result = (rawData * 1).toFixed(~~this.decimals) * 1;
 304    if (_.isNumber(result) && !_.isNaN(result)) return result;
 305  }
 306
 307});
 308
 309/**
 310   Formatter to converts between various datetime formats.
 311
 312   This class only understands ISO-8601 formatted datetime strings and UNIX
 313   offset (number of milliseconds since UNIX Epoch). See
 314   Backgrid.Extension.MomentFormatter if you need a much more flexible datetime
 315   formatter.
 316
 317   @class Backgrid.DatetimeFormatter
 318   @extends Backgrid.CellFormatter
 319   @constructor
 320   @throws {Error} If both `includeDate` and `includeTime` are false.
 321*/
 322var DatetimeFormatter = Backgrid.DatetimeFormatter = function (options) {
 323  options = options ? _.clone(options) : {};
 324  _.extend(this, this.defaults, options);
 325
 326  if (!this.includeDate && !this.includeTime) {
 327    throw new Error("Either includeDate or includeTime must be true");
 328  }
 329};
 330DatetimeFormatter.prototype = new CellFormatter();
 331_.extend(DatetimeFormatter.prototype, {
 332
 333  /**
 334     @member Backgrid.DatetimeFormatter
 335
 336     @cfg {Object} options
 337
 338     @cfg {boolean} [options.includeDate=true] Whether the values include the
 339     date part.
 340
 341     @cfg {boolean} [options.includeTime=true] Whether the values include the
 342     time part.
 343
 344     @cfg {boolean} [options.includeMilli=false] If `includeTime` is true,
 345     whether to include the millisecond part, if it exists.
 346   */
 347  defaults: {
 348    includeDate: true,
 349    includeTime: true,
 350    includeMilli: false
 351  },
 352
 353  DATE_RE: /^([+\-]?\d{4})-(\d{2})-(\d{2})$/,
 354  TIME_RE: /^(\d{2}):(\d{2}):(\d{2})(\.(\d{3}))?$/,
 355  ISO_SPLITTER_RE: /T|Z| +/,
 356
 357  _convert: function (data, validate) {
 358    var date, time = null;
 359    if (_.isNumber(data)) {
 360      var jsDate = new Date(data);
 361      date = lpad(jsDate.getUTCFullYear(), 4, 0) + '-' + lpad(jsDate.getUTCMonth() + 1, 2, 0) + '-' + lpad(jsDate.getUTCDate(), 2, 0);
 362      time = lpad(jsDate.getUTCHours(), 2, 0) + ':' + lpad(jsDate.getUTCMinutes(), 2, 0) + ':' + lpad(jsDate.getUTCSeconds(), 2, 0);
 363    }
 364    else {
 365      data = data.trim();
 366      var parts = data.split(this.ISO_SPLITTER_RE) || [];
 367      date = this.DATE_RE.test(parts[0]) ? parts[0] : '';
 368      time = date && parts[1] ? parts[1] : this.TIME_RE.test(parts[0]) ? parts[0] : '';
 369    }
 370
 371    var YYYYMMDD = this.DATE_RE.exec(date) || [];
 372    var HHmmssSSS = this.TIME_RE.exec(time) || [];
 373
 374    if (validate) {
 375      if (this.includeDate && _.isUndefined(YYYYMMDD[0])) return;
 376      if (this.includeTime && _.isUndefined(HHmmssSSS[0])) return;
 377      if (!this.includeDate && date) return;
 378      if (!this.includeTime && time) return;
 379    }
 380
 381    var jsDate = new Date(Date.UTC(YYYYMMDD[1] * 1 || 0,
 382                                   YYYYMMDD[2] * 1 - 1 || 0,
 383                                   YYYYMMDD[3] * 1 || 0,
 384                                   HHmmssSSS[1] * 1 || null,
 385                                   HHmmssSSS[2] * 1 || null,
 386                                   HHmmssSSS[3] * 1 || null,
 387                                   HHmmssSSS[5] * 1 || null));
 388
 389    var result = '';
 390
 391    if (this.includeDate) {
 392      result = lpad(jsDate.getUTCFullYear(), 4, 0) + '-' + lpad(jsDate.getUTCMonth() + 1, 2, 0) + '-' + lpad(jsDate.getUTCDate(), 2, 0);
 393    }
 394
 395    if (this.includeTime) {
 396      result = result + (this.includeDate ? 'T' : '') + lpad(jsDate.getUTCHours(), 2, 0) + ':' + lpad(jsDate.getUTCMinutes(), 2, 0) + ':' + lpad(jsDate.getUTCSeconds(), 2, 0);
 397
 398      if (this.includeMilli) {
 399        result = result + '.' + lpad(jsDate.getUTCMilliseconds(), 3, 0);
 400      }
 401    }
 402
 403    if (this.includeDate && this.includeTime) {
 404      result += "Z";
 405    }
 406
 407    return result;
 408  },
 409
 410  /**
 411     Converts an ISO-8601 formatted datetime string to a datetime string, date
 412     string or a time string. The timezone is ignored if supplied.
 413
 414     @member Backgrid.DatetimeFormatter
 415     @param {string} rawData
 416     @return {string|null|undefined} ISO-8601 string in UTC. Null and undefined
 417     values are returned as is.
 418  */
 419  fromRaw: function (rawData) {
 420    if (_.isNull(rawData) || _.isUndefined(rawData)) return '';
 421    return this._convert(rawData);
 422  },
 423
 424  /**
 425     Converts an ISO-8601 formatted datetime string to a datetime string, date
 426     string or a time string. The timezone is ignored if supplied. This method
 427     parses the input values exactly the same way as
 428     Backgrid.Extension.MomentFormatter#fromRaw(), in addition to doing some
 429     sanity checks.
 430
 431     @member Backgrid.DatetimeFormatter
 432     @param {string} formattedData
 433     @return {string|undefined} ISO-8601 string in UTC. Undefined if a date is
 434     found when `includeDate` is false, or a time is found when `includeTime` is
 435     false, or if `includeDate` is true and a date is not found, or if
 436     `includeTime` is true and a time is not found.
 437  */
 438  toRaw: function (formattedData) {
 439    return this._convert(formattedData, true);
 440  }
 441
 442});
 443
 444/**
 445   Formatter to convert any value to string.
 446
 447   @class Backgrid.StringFormatter
 448   @extends Backgrid.CellFormatter
 449   @constructor
 450 */
 451var StringFormatter = Backgrid.StringFormatter = function () {};
 452StringFormatter.prototype = new CellFormatter();
 453_.extend(StringFormatter.prototype, {
 454  /**
 455     Converts any value to a string using Ecmascript's implicit type
 456     conversion. If the given value is `null` or `undefined`, an empty string is
 457     returned instead.
 458
 459     @member Backgrid.StringFormatter
 460     @param {*} rawValue
 461     @return {string}
 462   */
 463  fromRaw: function (rawValue) {
 464    if (_.isUndefined(rawValue) || _.isNull(rawValue)) return '';
 465    return rawValue + '';
 466  }
 467});
 468
 469/**
 470   Simple email validation formatter.
 471
 472   @class Backgrid.EmailFormatter
 473   @extends Backgrid.CellFormatter
 474   @constructor
 475 */
 476var EmailFormatter = Backgrid.EmailFormatter = function () {};
 477EmailFormatter.prototype = new CellFormatter();
 478_.extend(EmailFormatter.prototype, {
 479  /**
 480     Return the input if it is a string that contains an '@' character and if
 481     the strings before and after '@' are non-empty. If the input does not
 482     validate, `undefined` is returned.
 483
 484     @member Backgrid.EmailFormatter
 485     @param {*} formattedData
 486     @return {string|undefined}
 487   */
 488  toRaw: function (formattedData) {
 489    var parts = formattedData.trim().split("@");
 490    if (parts.length === 2 && _.all(parts)) {
 491      return formattedData;
 492    }
 493  }
 494});
 495
 496/**
 497   Formatter for SelectCell.
 498
 499   @class Backgrid.SelectFormatter
 500   @extends Backgrid.CellFormatter
 501   @constructor
 502*/
 503var SelectFormatter = Backgrid.SelectFormatter = function () {};
 504SelectFormatter.prototype = new CellFormatter();
 505_.extend(SelectFormatter.prototype, {
 506
 507  /**
 508     Normalizes raw scalar or array values to an array.
 509
 510     @member Backgrid.SelectFormatter
 511     @param {*} rawValue
 512     @return {Array.<*>}
 513  */
 514  fromRaw: function (rawValue) {
 515    return _.isArray(rawValue) ? rawValue : rawValue != null ? [rawValue] : [];
 516  }
 517});
 518
 519/*
 520  backgrid
 521  http://github.com/wyuenho/backgrid
 522
 523  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
 524  Licensed under the MIT @license.
 525*/
 526
 527/**
 528   Generic cell editor base class. Only defines an initializer for a number of
 529   required parameters.
 530
 531   @abstract
 532   @class Backgrid.CellEditor
 533   @extends Backbone.View
 534*/
 535var CellEditor = Backgrid.CellEditor = Backbone.View.extend({
 536
 537  /**
 538     Initializer.
 539
 540     @param {Object} options
 541     @param {Backgrid.CellFormatter} options.formatter
 542     @param {Backgrid.Column} options.column
 543     @param {Backbone.Model} options.model
 544
 545     @throws {TypeError} If `formatter` is not a formatter instance, or when
 546     `model` or `column` are undefined.
 547  */
 548  initialize: function (options) {
 549    Backgrid.requireOptions(options, ["formatter", "column", "model"]);
 550    this.formatter = options.formatter;
 551    this.column = options.column;
 552    if (!(this.column instanceof Column)) {
 553      this.column = new Column(this.column);
 554    }
 555
 556    this.listenTo(this.model, "backgrid:editing", this.postRender);
 557  },
 558
 559  /**
 560     Post-rendering setup and initialization. Focuses the cell editor's `el` in
 561     this default implementation. **Should** be called by Cell classes after
 562     calling Backgrid.CellEditor#render.
 563  */
 564  postRender: function (model, column) {
 565    if (column == null || column.get("name") == this.column.get("name")) {
 566      this.$el.focus();
 567    }
 568    return this;
 569  }
 570
 571});
 572
 573/**
 574   InputCellEditor the cell editor type used by most core cell types. This cell
 575   editor renders a text input box as its editor. The input will render a
 576   placeholder if the value is empty on supported browsers.
 577
 578   @class Backgrid.InputCellEditor
 579   @extends Backgrid.CellEditor
 580*/
 581var InputCellEditor = Backgrid.InputCellEditor = CellEditor.extend({
 582
 583  /** @property */
 584  tagName: "input",
 585
 586  /** @property */
 587  attributes: {
 588    type: "text"
 589  },
 590
 591  /** @property */
 592  events: {
 593    "blur": "saveOrCancel",
 594    "keydown": "saveOrCancel"
 595  },
 596
 597  /**
 598     Initializer. Removes this `el` from the DOM when a `done` event is
 599     triggered.
 600
 601     @param {Object} options
 602     @param {Backgrid.CellFormatter} options.formatter
 603     @param {Backgrid.Column} options.column
 604     @param {Backbone.Model} options.model
 605     @param {string} [options.placeholder]
 606  */
 607  initialize: function (options) {
 608    CellEditor.prototype.initialize.apply(this, arguments);
 609
 610    if (options.placeholder) {
 611      this.$el.attr("placeholder", options.placeholder);
 612    }
 613  },
 614
 615  /**
 616     Renders a text input with the cell value formatted for display, if it
 617     exists.
 618  */
 619  render: function () {
 620    this.$el.val(this.formatter.fromRaw(this.model.get(this.column.get("name"))));
 621    return this;
 622  },
 623
 624  /**
 625     If the key pressed is `enter`, `tab`, `up`, or `down`, converts the value
 626     in the editor to a raw value for saving into the model using the formatter.
 627
 628     If the key pressed is `esc` the changes are undone.
 629
 630     If the editor goes out of focus (`blur`) but the value is invalid, the
 631     event is intercepted and cancelled so the cell remains in focus pending for
 632     further action. The changes are saved otherwise.
 633
 634     Triggers a Backbone `backgrid:edited` event from the model when successful,
 635     and `backgrid:error` if the value cannot be converted. Classes listening to
 636     the `error` event, usually the Cell classes, should respond appropriately,
 637     usually by rendering some kind of error feedback.
 638
 639     @param {Event} e
 640  */
 641  saveOrCancel: function (e) {
 642
 643    var formatter = this.formatter;
 644    var model = this.model;
 645    var column = this.column;
 646
 647    var command = new Command(e);
 648    var blurred = e.type === "blur";
 649
 650    if (command.moveUp() || command.moveDown() || command.moveLeft() || command.moveRight() ||
 651        command.save() || blurred) {
 652
 653      e.preventDefault();
 654      e.stopPropagation();
 655
 656      var val = this.$el.val();
 657      var newValue = formatter.toRaw(val);
 658      if (_.isUndefined(newValue)) {
 659        model.trigger("backgrid:error", model, column, val);
 660      }
 661      else {
 662        model.set(column.get("name"), newValue);
 663        model.trigger("backgrid:edited", model, column, command);
 664      }
 665    }
 666    // esc
 667    else if (command.cancel()) {
 668      // undo
 669      e.stopPropagation();
 670      model.trigger("backgrid:edited", model, column, command);
 671    }
 672  },
 673
 674  postRender: function (model, column) {
 675    if (column == null || column.get("name") == this.column.get("name")) {
 676      // move the cursor to the end on firefox if text is right aligned
 677      if (this.$el.css("text-align") === "right") {
 678        var val = this.$el.val();
 679        this.$el.focus().val(null).val(val);
 680      }
 681      else this.$el.focus();
 682    }
 683    return this;
 684  }
 685
 686});
 687
 688/**
 689   The super-class for all Cell types. By default, this class renders a plain
 690   table cell with the model value converted to a string using the
 691   formatter. The table cell is clickable, upon which the cell will go into
 692   editor mode, which is rendered by a Backgrid.InputCellEditor instance by
 693   default. Upon encountering any formatting errors, this class will add an
 694   `error` CSS class to the table cell.
 695
 696   @abstract
 697   @class Backgrid.Cell
 698   @extends Backbone.View
 699*/
 700var Cell = Backgrid.Cell = Backbone.View.extend({
 701
 702  /** @property */
 703  tagName: "td",
 704
 705  /**
 706     @property {Backgrid.CellFormatter|Object|string} [formatter=new CellFormatter()]
 707  */
 708  formatter: new CellFormatter(),
 709
 710  /**
 711     @property {Backgrid.CellEditor} [editor=Backgrid.InputCellEditor] The
 712     default editor for all cell instances of this class. This value must be a
 713     class, it will be automatically instantiated upon entering edit mode.
 714
 715     See Backgrid.CellEditor
 716  */
 717  editor: InputCellEditor,
 718
 719  /** @property */
 720  events: {
 721    "click": "enterEditMode"
 722  },
 723
 724  /**
 725     Initializer.
 726
 727     @param {Object} options
 728     @param {Backbone.Model} options.model
 729     @param {Backgrid.Column} options.column
 730
 731     @throws {ReferenceError} If formatter is a string but a formatter class of
 732     said name cannot be found in the Backgrid module.
 733  */
 734  initialize: function (options) {
 735    Backgrid.requireOptions(options, ["model", "column"]);
 736    this.column = options.column;
 737    if (!(this.column instanceof Column)) {
 738      this.column = new Column(this.column);
 739    }
 740    this.formatter = Backgrid.resolveNameToClass(this.column.get("formatter") || this.formatter, "Formatter");
 741    this.editor = Backgrid.resolveNameToClass(this.editor, "CellEditor");
 742    this.listenTo(this.model, "change:" + this.column.get("name"), function () {
 743      if (!this.$el.hasClass("editor")) this.render();
 744    });
 745    this.listenTo(this.model, "backgrid:error", this.renderError);
 746  },
 747
 748  /**
 749     Render a text string in a table cell. The text is converted from the
 750     model's raw value for this cell's column.
 751  */
 752  render: function () {
 753    this.$el.empty();
 754    this.$el.text(this.formatter.fromRaw(this.model.get(this.column.get("name"))));
 755    this.delegateEvents();
 756    return this;
 757  },
 758
 759  /**
 760     If this column is editable, a new CellEditor instance is instantiated with
 761     its required parameters. An `editor` CSS class is added to the cell upon
 762     entering edit mode.
 763
 764     This method triggers a Backbone `backgrid:edit` event from the model when
 765     the cell is entering edit mode and an editor instance has been constructed,
 766     but before it is rendered and inserted into the DOM. The cell and the
 767     constructed cell editor instance are sent as event parameters when this
 768     event is triggered.
 769
 770     When this cell has finished switching to edit mode, a Backbone
 771     `backgrid:editing` event is triggered from the model. The cell and the
 772     constructed cell instance are also sent as parameters in the event.
 773
 774     When the model triggers a `backgrid:error` event, it means the editor is
 775     unable to convert the current user input to an apprpriate value for the
 776     model's column, and an `error` CSS class is added to the cell accordingly.
 777  */
 778  enterEditMode: function () {
 779    var model = this.model;
 780    var column = this.column;
 781
 782    if (column.get("editable")) {
 783
 784      this.currentEditor = new this.editor({
 785        column: this.column,
 786        model: this.model,
 787        formatter: this.formatter
 788      });
 789
 790      model.trigger("backgrid:edit", model, column, this, this.currentEditor);
 791
 792      // Need to redundantly undelegate events for Firefox
 793      this.undelegateEvents();
 794      this.$el.empty();
 795      this.$el.append(this.currentEditor.$el);
 796      this.currentEditor.render();
 797      this.$el.addClass("editor");
 798
 799      model.trigger("backgrid:editing", model, column, this, this.currentEditor);
 800    }
 801  },
 802
 803  /**
 804     Put an `error` CSS class on the table cell.
 805  */
 806  renderError: function (model, column) {
 807    if (column == null || column.get("name") == this.column.get("name")) {
 808      this.$el.addClass("error");
 809    }
 810  },
 811
 812  /**
 813     Removes the editor and re-render in display mode.
 814  */
 815  exitEditMode: function () {
 816    this.$el.removeClass("error");
 817    this.currentEditor.remove();
 818    this.stopListening(this.currentEditor);
 819    delete this.currentEditor;
 820    this.$el.removeClass("editor");
 821    this.render();
 822  },
 823
 824  /**
 825     Clean up this cell.
 826
 827     @chainable
 828  */
 829  remove: function () {
 830    if (this.currentEditor) {
 831      this.currentEditor.remove.apply(this, arguments);
 832      delete this.currentEditor;
 833    }
 834    return Backbone.View.prototype.remove.apply(this, arguments);
 835  }
 836
 837});
 838
 839/**
 840   StringCell displays HTML escaped strings and accepts anything typed in.
 841
 842   @class Backgrid.StringCell
 843   @extends Backgrid.Cell
 844*/
 845var StringCell = Backgrid.StringCell = Cell.extend({
 846
 847  /** @property */
 848  className: "string-cell",
 849
 850  formatter: new StringFormatter()
 851
 852});
 853
 854/**
 855   UriCell renders an HTML `<a>` anchor for the value and accepts URIs as user
 856   input values. No type conversion or URL validation is done by the formatter
 857   of this cell. Users who need URL validation are encourage to subclass UriCell
 858   to take advantage of the parsing capabilities of the HTMLAnchorElement
 859   available on HTML5-capable browsers or using a third-party library like
 860   [URI.js](https://github.com/medialize/URI.js).
 861
 862   @class Backgrid.UriCell
 863   @extends Backgrid.Cell
 864*/
 865var UriCell = Backgrid.UriCell = Cell.extend({
 866
 867  /** @property */
 868  className: "uri-cell",
 869
 870  render: function () {
 871    this.$el.empty();
 872    var formattedValue = this.formatter.fromRaw(this.model.get(this.column.get("name")));
 873    this.$el.append($("<a>", {
 874      tabIndex: -1,
 875      href: formattedValue,
 876      title: formattedValue,
 877      target: "_blank"
 878    }).text(formattedValue));
 879    this.delegateEvents();
 880    return this;
 881  }
 882
 883});
 884
 885/**
 886   Like Backgrid.UriCell, EmailCell renders an HTML `<a>` anchor for the
 887   value. The `href` in the anchor is prefixed with `mailto:`. EmailCell will
 888   complain if the user enters a string that doesn't contain the `@` sign.
 889
 890   @class Backgrid.EmailCell
 891   @extends Backgrid.StringCell
 892*/
 893var EmailCell = Backgrid.EmailCell = StringCell.extend({
 894
 895  /** @property */
 896  className: "email-cell",
 897
 898  formatter: new EmailFormatter(),
 899
 900  render: function () {
 901    this.$el.empty();
 902    var formattedValue = this.formatter.fromRaw(this.model.get(this.column.get("name")));
 903    this.$el.append($("<a>", {
 904      tabIndex: -1,
 905      href: "mailto:" + formattedValue,
 906      title: formattedValue
 907    }).text(formattedValue));
 908    this.delegateEvents();
 909    return this;
 910  }
 911
 912});
 913
 914/**
 915   NumberCell is a generic cell that renders all numbers. Numbers are formatted
 916   using a Backgrid.NumberFormatter.
 917
 918   @class Backgrid.NumberCell
 919   @extends Backgrid.Cell
 920*/
 921var NumberCell = Backgrid.NumberCell = Cell.extend({
 922
 923  /** @property */
 924  className: "number-cell",
 925
 926  /**
 927     @property {number} [decimals=2] Must be an integer.
 928  */
 929  decimals: NumberFormatter.prototype.defaults.decimals,
 930
 931  /** @property {string} [decimalSeparator='.'] */
 932  decimalSeparator: NumberFormatter.prototype.defaults.decimalSeparator,
 933
 934  /** @property {string} [orderSeparator=','] */
 935  orderSeparator: NumberFormatter.prototype.defaults.orderSeparator,
 936
 937  /** @property {Backgrid.CellFormatter} [formatter=Backgrid.NumberFormatter] */
 938  formatter: NumberFormatter,
 939
 940  /**
 941     Initializes this cell and the number formatter.
 942
 943     @param {Object} options
 944     @param {Backbone.Model} options.model
 945     @param {Backgrid.Column} options.column
 946  */
 947  initialize: function (options) {
 948    Cell.prototype.initialize.apply(this, arguments);
 949    this.formatter = new this.formatter({
 950      decimals: this.decimals,
 951      decimalSeparator: this.decimalSeparator,
 952      orderSeparator: this.orderSeparator
 953    });
 954  }
 955
 956});
 957
 958/**
 959   An IntegerCell is just a Backgrid.NumberCell with 0 decimals. If a floating
 960   point number is supplied, the number is simply rounded the usual way when
 961   displayed.
 962
 963   @class Backgrid.IntegerCell
 964   @extends Backgrid.NumberCell
 965*/
 966var IntegerCell = Backgrid.IntegerCell = NumberCell.extend({
 967
 968  /** @property */
 969  className: "integer-cell",
 970
 971  /**
 972     @property {number} decimals Must be an integer.
 973  */
 974  decimals: 0
 975});
 976
 977/**
 978   DatetimeCell is a basic cell that accepts datetime string values in RFC-2822
 979   or W3C's subset of ISO-8601 and displays them in ISO-8601 format. For a much
 980   more sophisticated date time cell with better datetime formatting, take a
 981   look at the Backgrid.Extension.MomentCell extension.
 982
 983   @class Backgrid.DatetimeCell
 984   @extends Backgrid.Cell
 985
 986   See:
 987
 988   - Backgrid.Extension.MomentCell
 989   - Backgrid.DatetimeFormatter
 990*/
 991var DatetimeCell = Backgrid.DatetimeCell = Cell.extend({
 992
 993  /** @property */
 994  className: "datetime-cell",
 995
 996  /**
 997     @property {boolean} [includeDate=true]
 998  */
 999  includeDate: DatetimeFormatter.prototype.defaults.includeDate,
1000
1001  /**
1002     @property {boolean} [includeTime=true]
1003  */
1004  includeTime: DatetimeFormatter.prototype.defaults.includeTime,
1005
1006  /**
1007     @property {boolean} [includeMilli=false]
1008  */
1009  includeMilli: DatetimeFormatter.prototype.defaults.includeMilli,
1010
1011  /** @property {Backgrid.CellFormatter} [formatter=Backgrid.DatetimeFormatter] */
1012  formatter: DatetimeFormatter,
1013
1014  /**
1015     Initializes this cell and the datetime formatter.
1016
1017     @param {Object} options
1018     @param {Backbone.Model} options.model
1019     @param {Backgrid.Column} options.column
1020  */
1021  initialize: function (options) {
1022    Cell.prototype.initialize.apply(this, arguments);
1023    this.formatter = new this.formatter({
1024      includeDate: this.includeDate,
1025      includeTime: this.includeTime,
1026      includeMilli: this.includeMilli
1027    });
1028
1029    var placeholder = this.includeDate ? "YYYY-MM-DD" : "";
1030    placeholder += (this.includeDate && this.includeTime) ? "T" : "";
1031    placeholder += this.includeTime ? "HH:mm:ss" : "";
1032    placeholder += (this.includeTime && this.includeMilli) ? ".SSS" : "";
1033
1034    this.editor = this.editor.extend({
1035      attributes: _.extend({}, this.editor.prototype.attributes, this.editor.attributes, {
1036        placeholder: placeholder
1037      })
1038    });
1039  }
1040
1041});
1042
1043/**
1044   DateCell is a Backgrid.DatetimeCell without the time part.
1045
1046   @class Backgrid.DateCell
1047   @extends Backgrid.DatetimeCell
1048*/
1049var DateCell = Backgrid.DateCell = DatetimeCell.extend({
1050
1051  /** @property */
1052  className: "date-cell",
1053
1054  /** @property */
1055  includeTime: false
1056
1057});
1058
1059/**
1060   TimeCell is a Backgrid.DatetimeCell without the date part.
1061
1062   @class Backgrid.TimeCell
1063   @extends Backgrid.DatetimeCell
1064*/
1065var TimeCell = Backgrid.TimeCell = DatetimeCell.extend({
1066
1067  /** @property */
1068  className: "time-cell",
1069
1070  /** @property */
1071  includeDate: false
1072
1073});
1074
1075/**
1076   BooleanCellEditor renders a checkbox as its editor.
1077
1078   @class Backgrid.BooleanCellEditor
1079   @extends Backgrid.CellEditor
1080*/
1081var BooleanCellEditor = Backgrid.BooleanCellEditor = CellEditor.extend({
1082
1083  /** @property */
1084  tagName: "input",
1085
1086  /** @property */
1087  attributes: {
1088    tabIndex: -1,
1089    type: "checkbox"
1090  },
1091
1092  /** @property */
1093  events: {
1094    "mousedown": function () {
1095      this.mouseDown = true;
1096    },
1097    "blur": "enterOrExitEditMode",
1098    "mouseup": function () {
1099      this.mouseDown = false;
1100    },
1101    "change": "saveOrCancel",
1102    "keydown": "saveOrCancel"
1103  },
1104
1105  /**
1106     Renders a checkbox and check it if the model value of this column is true,
1107     uncheck otherwise.
1108  */
1109  render: function () {
1110    var val = this.formatter.fromRaw(this.model.get(this.column.get("name")));
1111    this.$el.prop("checked", val);
1112    return this;
1113  },
1114
1115  /**
1116     Event handler. Hack to deal with the case where `blur` is fired before
1117     `change` and `click` on a checkbox.
1118  */
1119  enterOrExitEditMode: function (e) {
1120    if (!this.mouseDown) {
1121      var model = this.model;
1122      model.trigger("backgrid:edited", model, this.column, new Command(e));
1123    }
1124  },
1125
1126  /**
1127     Event handler. Save the value into the model if the event is `change` or
1128     one of the keyboard navigation key presses. Exit edit mode without saving
1129     if `escape` was pressed.
1130  */
1131  saveOrCancel: function (e) {
1132    var model = this.model;
1133    var column = this.column;
1134    var formatter = this.formatter;
1135    var command = new Command(e);
1136    // skip ahead to `change` when space is pressed
1137    if (command.passThru() && e.type != "change") return true;
1138    if (command.cancel()) {
1139      e.stopPropagation();
1140      model.trigger("backgrid:edited", model, column, command);
1141    }
1142
1143    var $el = this.$el;
1144    if (command.save() || command.moveLeft() || command.moveRight() || command.moveUp() ||
1145        command.moveDown()) {
1146      e.preventDefault();
1147      e.stopPropagation();
1148      var val = formatter.toRaw($el.prop("checked"));
1149      model.set(column.get("name"), val);
1150      model.trigger("backgrid:edited", model, column, command);
1151    }
1152    else if (e.type == "change") {
1153      var val = formatter.toRaw($el.prop("checked"));
1154      model.set(column.get("name"), val);
1155      $el.focus();
1156    }
1157  }
1158
1159});
1160
1161/**
1162   BooleanCell renders a checkbox both during display mode and edit mode. The
1163   checkbox is checked if the model value is true, unchecked otherwise.
1164
1165   @class Backgrid.BooleanCell
1166   @extends Backgrid.Cell
1167*/
1168var BooleanCell = Backgrid.BooleanCell = Cell.extend({
1169
1170  /** @property */
1171  className: "boolean-cell",
1172
1173  /** @property */
1174  editor: BooleanCellEditor,
1175
1176  /** @property */
1177  events: {
1178    "click": "enterEditMode"
1179  },
1180
1181  /**
1182     Renders a checkbox and check it if the model value of this column is true,
1183     uncheck otherwise.
1184  */
1185  render: function () {
1186    this.$el.empty();
1187    this.$el.append($("<input>", {
1188      tabIndex: -1,
1189      type: "checkbox",
1190      checked: this.formatter.fromRaw(this.model.get(this.column.get("name")))
1191    }));
1192    this.delegateEvents();
1193    return this;
1194  }
1195
1196});
1197
1198/**
1199   SelectCellEditor renders an HTML `<select>` fragment as the editor.
1200
1201   @class Backgrid.SelectCellEditor
1202   @extends Backgrid.CellEditor
1203*/
1204var SelectCellEditor = Backgrid.SelectCellEditor = CellEditor.extend({
1205
1206  /** @property */
1207  tagName: "select",
1208
1209  /** @property */
1210  events: {
1211    "change": "save",
1212    "blur": "close",
1213    "keydown": "close"
1214  },
1215
1216  /** @property {function(Object, ?Object=): string} template */
1217  template: _.template('<option value="<%- value %>" <%= selected ? \'selected="selected"\' : "" %>><%- text %></option>'),
1218
1219  setOptionValues: function (optionValues) {
1220    this.optionValues = optionValues;
1221  },
1222
1223  setMultiple: function (multiple) {
1224    this.multiple = multiple;
1225    this.$el.prop("multiple", multiple);
1226  },
1227
1228  _renderOptions: function (nvps, selectedValues) {
1229    var options = '';
1230    for (var i = 0; i < nvps.length; i++) {
1231      options = options + this.template({
1232        text: nvps[i][0],
1233        value: nvps[i][1],
1234        selected: selectedValues.indexOf(nvps[i][1]) > -1
1235      });
1236    }
1237    return options;
1238  },
1239
1240  /**
1241     Renders the options if `optionValues` is a list of name-value pairs. The
1242     options are contained inside option groups if `optionValues` is a list of
1243     object hashes. The name is rendered at the option text and the value is the
1244     option value. If `optionValues` is a function, it is called without a
1245     parameter.
1246  */
1247  render: function () {
1248    this.$el.empty();
1249
1250    var optionValues = _.result(this, "optionValues");
1251    var selectedValues = this.formatter.fromRaw(this.model.get(this.column.get("name")));
1252
1253    if (!_.isArray(optionValues)) throw TypeError("optionValues must be an array");
1254
1255    var optionValue = null;
1256    var optionText = null;
1257    var optionValue = null;
1258    var optgroupName = null;
1259    var optgroup = null;
1260
1261    for (var i = 0; i < optionValues.length; i++) {
1262      var optionValue = optionValues[i];
1263
1264      if (_.isArray(optionValue)) {
1265        optionText  = optionValue[0];
1266        optionValue = optionValue[1];
1267
1268        this.$el.append(this.template({
1269          text: optionText,
1270          value: optionValue,
1271          selected: selectedValues.indexOf(optionValue) > -1
1272        }));
1273      }
1274      else if (_.isObject(optionValue)) {
1275        optgroupName = optionValue.name;
1276        optgroup = $("<optgroup></optgroup>", { label: optgroupName });
1277        optgroup.append(this._renderOptions(optionValue.values, selectedValues));
1278        this.$el.append(optgroup);
1279      }
1280      else {
1281        throw TypeError("optionValues elements must be a name-value pair or an object hash of { name: 'optgroup label', value: [option name-value pairs] }");
1282      }
1283    }
1284
1285    this.delegateEvents();
1286
1287    return this;
1288  },
1289
1290  /**
1291     Saves the value of the selected option to the model attribute. Triggers a
1292     `backgrid:edited` Backbone event from the model.
1293  */
1294  save: function (e) {
1295    var model = this.model;
1296    var column = this.column;
1297    model.set(column.get("name"), this.formatter.toRaw(this.$el.val()));
1298    model.trigger("backgrid:edited", model, column, new Command(e));
1299  },
1300
1301  /**
1302     Triggers a `backgrid:edited` event from the model so the body can close
1303     this editor.
1304  */
1305  close: function (e) {
1306    var model = this.model;
1307    var column = this.column;
1308    var command = new Command(e);
1309    if (command.cancel()) {
1310      e.stopPropagation();
1311      model.trigger("backgrid:edited", model, column, new Command(e));
1312    }
1313    else if (command.save() || command.moveLeft() || command.moveRight() ||
1314             command.moveUp() || command.moveDown() || e.type == "blur") {
1315      e.preventDefault();
1316      e.stopPropagation();
1317      if (e.type == "blur" && this.$el.find("option").length === 1) {
1318        model.set(column.get("name"), this.formatter.toRaw(this.$el.val()));
1319      }
1320      model.trigger("backgrid:edited", model, column, new Command(e));
1321    }
1322  }
1323
1324});
1325
1326/**
1327   SelectCell is also a different kind of cell in that upon going into edit mode
1328   the cell renders a list of options to pick from, as opposed to an input box.
1329
1330   SelectCell cannot be referenced by its string name when used in a column
1331   definition because it requires an `optionValues` class attribute to be
1332   defined. `optionValues` can either be a list of name-value pairs, to be
1333   rendered as options, or a list of object hashes which consist of a key *name*
1334   which is the option group name, and a key *values* which is a list of
1335   name-value pairs to be rendered as options under that option group.
1336
1337   In addition, `optionValues` can also be a parameter-less function that
1338   returns one of the above. If the options are static, it is recommended the
1339   returned values to be memoized. `_.memoize()` is a good function to help with
1340   that.
1341
1342   During display mode, the default formatter will normalize the raw model value
1343   to an array of values whether the raw model value is a scalar or an
1344   array. Each value is compared with the `optionValues` values using
1345   Ecmascript's implicit type conversion rules. When exiting edit mode, no type
1346   conversion is performed when saving into the model. This behavior is not
1347   always desirable when the value type is anything other than string. To
1348   control type conversion on the client-side, you should subclass SelectCell to
1349   provide a custom formatter or provide the formatter to your column
1350   definition.
1351
1352   See:
1353     [$.fn.val()](http://api.jquery.com/val/)
1354
1355   @class Backgrid.SelectCell
1356   @extends Backgrid.Cell
1357*/
1358var SelectCell = Backgrid.SelectCell = Cell.extend({
1359
1360  /** @property */
1361  className: "select-cell",
1362
1363  /** @property */
1364  editor: SelectCellEditor,
1365
1366  /** @property */
1367  multiple: false,
1368
1369  /** @property */
1370  formatter: new SelectFormatter(),
1371
1372  /**
1373     @property {Array.<Array>|Array.<{name: string, values: Array.<Array>}>} optionValues
1374  */
1375  optionValues: undefined,
1376
1377  /** @property */
1378  delimiter: ', ',
1379
1380  /**
1381     Initializer.
1382
1383     @param {Object} options
1384     @param {Backbone.Model} options.model
1385     @param {Backgrid.Column} options.column
1386
1387     @throws {TypeError} If `optionsValues` is undefined.
1388  */
1389  initialize: function (options) {
1390    Cell.prototype.initialize.apply(this, arguments);
1391    Backgrid.requireOptions(this, ["optionValues"]);
1392    this.listenTo(this.model, "backgrid:edit", function (model, column, cell, editor) {
1393      if (column.get("name") == this.column.get("name")) {
1394        editor.setOptionValues(this.optionValues);
1395        editor.setMultiple(this.multiple);
1396      }
1397    });
1398  },
1399
1400  /**
1401     Renders the label using the raw value as key to look up from `optionValues`.
1402
1403     @throws {TypeError} If `optionValues` is malformed.
1404  */
1405  render: function () {
1406    this.$el.empty();
1407
1408    var optionValues = this.optionValues;
1409    var rawData = this.formatter.fromRaw(this.model.get(this.column.get("name")));
1410
1411    var selectedText = [];
1412
1413    try {
1414      if (!_.isArray(optionValues) || _.isEmpty(optionValues)) throw new TypeError;
1415
1416      for (var k = 0; k < rawData.length; k++) {
1417        var rawDatum = rawData[k];
1418
1419        for (var i = 0; i < optionValues.length; i++) {
1420          var optionValue = optionValues[i];
1421
1422          if (_.isArray(optionValue)) {
1423            var optionText  = optionValue[0];
1424            var optionValue = optionValue[1];
1425
1426            if (optionValue == rawDatum) selectedText.push(optionText);
1427          }
1428          else if (_.isObject(optionValue)) {
1429            var optionGroupValues = optionValue.values;
1430
1431            for (var j = 0; j < optionGroupValues.length; j++) {
1432              var optionGroupValue = optionGroupValues[j];
1433              if (optionGroupValue[1] == rawDatum) {
1434                selectedText.push(optionGroupValue[0]);
1435              }
1436            }
1437          }
1438          else {
1439            throw new TypeError;
1440          }
1441        }
1442      }
1443
1444      this.$el.append(selectedText.join(this.delimiter));
1445    }
1446    catch (ex) {
1447      if (ex instanceof TypeError) {
1448        throw TypeError("'optionValues' must be of type {Array.<Array>|Array.<{name: string, values: Array.<Array>}>}");
1449      }
1450      throw ex;
1451    }
1452
1453    this.delegateEvents();
1454
1455    return this;
1456  }
1457
1458});
1459/*
1460  backgrid
1461  http://github.com/wyuenho/backgrid
1462
1463  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
1464  Licensed under the MIT @license.
1465*/
1466
1467/**
1468   A Column is a placeholder for column metadata.
1469
1470   You usually don't need to create an instance of this class yourself as a
1471   collection of column instances will be created for you from a list of column
1472   attributes in the Backgrid.js view class constructors.
1473
1474   @class Backgrid.Column
1475   @extends Backbone.Model
1476 */
1477var Column = Backgrid.Column = Backbone.Model.extend({
1478
1479  defaults: {
1480    name: undefined,
1481    label: undefined,
1482    sortable: true,
1483    editable: true,
1484    renderable: true,
1485    formatter: undefined,
1486    cell: undefined,
1487    headerCell: undefined
1488  },
1489
1490  /**
1491     Initializes this Column instance.
1492
1493     @param {Object} attrs Column attributes.
1494     @param {string} attrs.name The name of the model attribute.
1495     @param {string|Backgrid.Cell} attrs.cell The cell type.
1496     If this is a string, the capitalized form will be used to look up a
1497     cell class in Backbone, i.e.: string => StringCell. If a Cell subclass
1498     is supplied, it is initialized with a hash of parameters. If a Cell
1499     instance is supplied, it is used directly.
1500     @param {string|Backgrid.HeaderCell} [attrs.headerCell] The header cell type.
1501     @param {string} [attrs.label] The label to show in the header.
1502     @param {boolean} [attrs.sortable=true]
1503     @param {boolean} [attrs.editable=true]
1504     @param {boolean} [attrs.renderable=true]
1505     @param {Backgrid.CellFormatter|Object|string} [attrs.formatter] The
1506     formatter to use to convert between raw model values and user input.
1507
1508     @throws {TypeError} If attrs.cell or attrs.options are not supplied.
1509     @throws {ReferenceError} If attrs.cell is a string but a cell class of
1510     said name cannot be found in the Backgrid module.
1511
1512     See:
1513
1514     - Backgrid.Cell
1515     - Backgrid.CellFormatter
1516   */
1517  initialize: function (attrs) {
1518    Backgrid.requireOptions(attrs, ["cell", "name"]);
1519
1520    if (!this.has("label")) {
1521      this.set({ label: this.get("name") }, { silent: true });
1522    }
1523
1524    var headerCell = Backgrid.resolveNameToClass(this.get("headerCell"), "HeaderCell");
1525    var cell = Backgrid.resolveNameToClass(this.get("cell"), "Cell");
1526    this.set({ cell: cell, headerCell: headerCell }, { silent: true });
1527  }
1528
1529});
1530
1531/**
1532   A Backbone collection of Column instances.
1533
1534   @class Backgrid.Columns
1535   @extends Backbone.Collection
1536 */
1537var Columns = Backgrid.Columns = Backbone.Collection.extend({
1538
1539  /**
1540     @property {Backgrid.Column} model
1541   */
1542  model: Column
1543});
1544/*
1545  backgrid
1546  http://github.com/wyuenho/backgrid
1547
1548  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
1549  Licensed under the MIT @license.
1550*/
1551
1552/**
1553   Row is a simple container view that takes a model instance and a list of
1554   column metadata describing how each of the model's attribute is to be
1555   rendered, and apply the appropriate cell to each attribute.
1556
1557   @class Backgrid.Row
1558   @extends Backbone.View
1559*/
1560var Row = Backgrid.Row = Backbone.View.extend({
1561
1562  /** @property */
1563  tagName: "tr",
1564
1565  requiredOptions: ["columns", "model"],
1566
1567  /**
1568     Initializes a row view instance.
1569
1570     @param {Object} options
1571     @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns Column metadata.
1572     @param {Backbone.Model} options.model The model instance to render.
1573
1574     @throws {TypeError} If options.columns or options.model is undefined.
1575  */
1576  initialize: function (options) {
1577
1578    Backgrid.requireOptions(options, this.requiredOptions);
1579
1580    var columns = this.columns = options.columns;
1581    if (!(columns instanceof Backbone.Collection)) {
1582      columns = this.columns = new Columns(columns);
1583    }
1584
1585    var cells = this.cells = [];
1586    for (var i = 0; i < columns.length; i++) {
1587      cells.push(this.makeCell(columns.at(i), options));
1588    }
1589
1590    this.listenTo(columns, "change:renderable", function (column, renderable) {
1591      for (var i = 0; i < cells.length; i++) {
1592        var cell = cells[i];
1593        if (cell.column.get("name") == column.get("name")) {
1594          if (renderable) cell.$el.show(); else cell.$el.hide();
1595        }
1596      }
1597    });
1598
1599    this.listenTo(columns, "add", function (column, columns) {
1600      var i = columns.indexOf(column);
1601      var cell = this.makeCell(column, options);
1602      cells.splice(i, 0, cell);
1603
1604      if (!cell.column.get("renderable")) cell.$el.hide();
1605
1606      var $el = this.$el;
1607      if (i === 0) {
1608        $el.prepend(cell.render().$el);
1609      }
1610      else if (i === columns.length - 1) {
1611        $el.append(cell.render().$el);
1612      }
1613      else {
1614        $el.children().eq(i).before(cell.render().$el);
1615      }
1616    });
1617
1618    this.listenTo(columns, "remove", function (column, columns, opts) {
1619      cells[opts.index].remove();
1620      cells.splice(opts.index, 1);
1621    });
1622  },
1623
1624  /**
1625     Factory method for making a cell. Used by #initialize internally. Override
1626     this to provide an appropriate cell instance for a custom Row subclass.
1627
1628     @protected
1629
1630     @param {Backgrid.Column} column
1631     @param {Object} options The options passed to #initialize.
1632
1633     @return {Backgrid.Cell}
1634  */
1635  makeCell: function (column) {
1636    return new (column.get("cell"))({
1637      column: column,
1638      model: this.model
1639    });
1640  },
1641
1642  /**
1643     Renders a row of cells for this row's model.
1644  */
1645  render: function () {
1646    this.$el.empty();
1647
1648    var fragment = document.createDocumentFragment();
1649
1650    for (var i = 0; i < this.cells.length; i++) {
1651      var cell = this.cells[i];
1652      fragment.appendChild(cell.render().el);
1653      if (!cell.column.get("renderable")) cell.$el.hide();
1654    }
1655
1656    this.el.appendChild(fragment);
1657
1658    this.delegateEvents();
1659
1660    return this;
1661  },
1662
1663  /**
1664     Clean up this row and its cells.
1665
1666     @chainable
1667  */
1668  remove: function () {
1669    for (var i = 0; i < this.cells.length; i++) {
1670      var cell = this.cells[i];
1671      cell.remove.apply(cell, arguments);
1672    }
1673    return Backbone.View.prototype.remove.apply(this, arguments);
1674  }
1675
1676});
1677
1678/**
1679   EmptyRow is a simple container view that takes a list of column and render a
1680   row with a single column.
1681
1682   @class Backgrid.EmptyRow
1683   @extends Backbone.View
1684*/
1685var EmptyRow = Backgrid.EmptyRow = Backbone.View.extend({
1686
1687  /** @property */
1688  tagName: "tr",
1689
1690  /** @property */
1691  emptyText: null,
1692
1693  /**
1694     Initializer.
1695
1696     @param {Object} options
1697     @param {string} options.emptyText
1698     @param {Backbone.Collection.<Backgrid.Column>|Array.<Backgrid.Column>|Array.<Object>} options.columns Column metadata.
1699   */
1700  initialize: function (options) {
1701    Backgrid.requireOptions(options, ["emptyText", "columns"]);
1702
1703    this.emptyText = options.emptyText;
1704    this.columns =  options.columns;
1705  },
1706
1707  /**
1708     Renders an empty row.
1709  */
1710  render: function () {
1711    this.$el.empty();
1712
1713    var td = document.createElement("td");
1714    td.setAttribute("colspan", this.columns.length);
1715    td.textContent = this.emptyText;
1716
1717    this.el.setAttribute("class", "empty");
1718    this.el.appendChild(td);
1719
1720    return this;
1721  }
1722});
1723/*
1724  backgrid
1725  http://github.com/wyuenho/backgrid
1726
1727  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
1728  Licensed under the MIT @license.
1729*/
1730
1731/**
1732   HeaderCell is a special cell class that renders a column header cell. If the
1733   column is sortable, a sorter is also rendered and will trigger a table
1734   refresh after sorting.
1735
1736   @class Backgrid.HeaderCell
1737   @extends Backbone.View
1738 */
1739var HeaderCell = Backgrid.HeaderCell = Backbone.View.extend({
1740
1741  /** @property */
1742  tagName: "th",
1743
1744  /** @property */
1745  events: {
1746    "click a": "onClick"
1747  },
1748
1749  /**
1750    @property {null|"ascending"|"descending"} _direction The current sorting
1751    direction of this column.
1752  */
1753  _direction: null,
1754
1755  /**
1756     Initializer.
1757
1758     @param {Object} options
1759     @param {Backgrid.Column|Object} options.column
1760
1761     @throws {TypeError} If options.column or options.collection is undefined.
1762   */
1763  initialize: function (options) {
1764    Backgrid.requireOptions(options, ["column", 

Large files files are truncated, but you can click here to view the full file