Table

function
 Table() 

Option name Type Description
el Element
params Object

Table constructor.

var Table = function(el, params) {

  if (!el) {
    return;
  }

  this._setParams(this.defaults, true);
  this._setParams(params || {});

  this._cacheElements(el);
  this._parseParams();

  this._bindEventListenerCallbacks();
  this._addEventListeners();

  this._initRows();

  if (this.isSpreadsheet || this.isEditRows) {
    this._deactivateAllInputs();
  }

  if (this.isResizable) {
    this._initResize();
  }

  this._disableRowsColumnsCells();

  this._initExpands();
};

var noop = function() {};

Table.prototype = {

_setParams

property
 _setParams 

Include common functionality.

_setParams: Base.setParams,
_hasClass: Base.hasClass,
_toggleClass: Base.toggleClass,
_triggerEvent: Base.triggerEvent,
_addClass: Base.addClass,
_removeClass: Base.removeClass,
_getElementMatchingParent: Base.getElementMatchingParent,
_getChildIndex: Base.getChildIndex,
_getSiblingBefore: Base.getSiblingBefore,
_getSiblingAfter: Base.getSiblingAfter,
_elementMatches: Base.elementMatches,

_whitelistedParams

property
 _whitelistedParams 

Whitelisted parameters which can be set on construction.

_whitelistedParams: ['isSpreadsheet', 'isEditRows', 'isResizable', 'confirmDeleteCallback', 'onRowSave', 'onRowDelete'],

defaults

property
 defaults 

Default values for internal properties we will be setting.
These are set on each construction so we don't leak properties
into the prototype chain.

defaults: {
  el: null,
  tableEl: null,
  isSpreadsheet: null,
  isEditRows: null,
  isResizable: null,
  onRowSave: null,
  onRowDelete: null,
  confirmDeleteCallback: null,
  _changePaused: false,
  _expands: null,
  _keyCodes: {
    ENTER: 13,
    UP: 38,
    DOWN: 40,
    LEFT: 37,
    RIGHT: 39,
    ESCAPE: 27
  },
  _editingCount: 0,
  _lastClickTime: 0,
  _lastClickEl: null,
  _lastScreenX: 0,
  _touchStartTime: 0,
  _touchStartEl: null,
  _resizeEls: null,
  _resizingEl: null,
  _sizeColumnsRun: false,
  _onClickBound: null,
  _onChangeBound: null,
  _onFocusBound: null,
  _onBlurBound: null,
  _onKeydownBound: null,
  _onTouchstartBound: null,
  _onTouchendBound: null,
  _onMouseDownBound: null,
  _onMouseMoveBound: null,
  _onMouseUpBound: null
},

disableCell

method
 disableCell() 

Option name Type Description
el Element

Disable the form field in a table cell.

disableCell: function(el) {
  el.disabled = true;
  this._addClass(this._getElementMatchingParent(el, 'td'), 'spark-table__disabled-cell');
},

enableCell

method
 enableCell() 

Option name Type Description
el Element

Enable the form field in a table cell.

enableCell: function(el) {
  el.disabled = false;
  this._removeClass(this._getElementMatchingParent(el, 'td'), 'spark-table__disabled-cell');
},

disableRow

method
 disableRow() 

Option name Type Description
el Element

Disable a row and all the cells inside of it.

disableRow: function(el) {
  this._addClass(el, 'spark-table__disabled-row');
  each(el.querySelectorAll('input, button, a'), function(i) {
    i.disabled = true;
  });
},

enableRow

method
 enableRow() 

Option name Type Description
el Element

Enable a row and all the cells inside of it.

enableRow: function(el) {
  this._removeClass(el, 'spark-table__disabled-row');
  each(el.querySelectorAll('input, button, a'), function(i) {
    i.disabled = false;
  });
},

disableColumn

method
 disableColumn() 

Option name Type Description
el Element

Disable a column and all the cells inside of it.

disableColumn: function(el) {

  var index = this._getChildIndex(el.parentNode.children, el);

  each(this.tableEl.querySelectorAll('tbody tr'), function(row) {
    this.disableCell(row.children[index].querySelector('input'));
  }.bind(this));

  this._addClass(el, 'spark-table__disabled-column');
},

enableColumn

method
 enableColumn() 

Option name Type Description
el Element

Enable a column and all the cells inside of it.

enableColumn: function(el) {

  var index = this._getChildIndex(el.parentNode.children, el);

  each(this.tableEl.querySelectorAll('tbody tr'), function(row) {
    this.enableCell(row.children[index].querySelector('input'));
  }.bind(this));

  this._removeClass(el, 'spark-table__disabled-column');
},

remove

method
 remove() 

Option name Type Description
leaveElement Boolean

Leave the element intact.

Remove the table anc cleanup.

remove: function(leaveElement) {
  each(this._expands, function(e) {
    e.remove(leaveElement);
  });
  Base.remove.apply(this, arguments);
},

activateRow

method
 activateRow() 

Option name Type Description
row Number, Element

Activate a row.

activateRow: function(row) {
  row = typeof row === 'number' ? this.tableEl.querySelectorAll('tbody tr')[row] : row;
  if (!row) return;
  this._makeRowActive(row);
},

activateRows

method
 activateRows() 

Option name Type Description
rows Array

Activate multiple rows.

activateRows: function(rows) {
  each(rows, this.activateRow.bind(this));
},

deactivateRow

method
 deactivateRow() 

Option name Type Description
row Number, Element

Deactivate a row.

deactivateRow: function(row) {
  row = typeof row === 'number' ? this.tableEl.querySelectorAll('tbody tr')[row] : row;
  if (!row) return;
  this._makeRowInActive(row);
},

deactivateRows

method
 deactivateRows() 

Option name Type Description
rows Array

Deactivate multiple rows.

deactivateRows: function(rows) {
  each(rows, this.deactivateRow.bind(this));
},

getActiveRows

method
 getActiveRows() 

Get an array of currently active rows.

getActiveRows: function() {

  var arr = [];

  each(this.el.querySelectorAll('tbody tr.active'), function(tr) {
    arr.push(tr);
  });

  return arr;
},

_cacheElements

method
 _cacheElements() 

Option name Type Description
el Element

Store a reference to the tabs list, each tab and each panel.
Set which tab is active, or use the first.

_cacheElements: function(el) {
  this.el = el;
  this.tableEl = el.querySelector('table');
},

_parseParams

method
 _parseParams() 

Parse parameters from the elements.

_parseParams: function() {

  if (!this.tableEl) {
    return;
  }

  this.isSpreadsheet = this.isSpreadsheet !== null ? this.isSpreadsheet : (this._hasClass(this.el, 'spark-table--spreadsheet') ? true : false);
  this.isEditRows = this.isEditRows !== null ? this.isEditRows : (this._hasClass(this.el, 'spark-table--edit-rows') ? true : false);
  this.isResizable = this.isResizable !== null ? this.isResizable : (this._hasClass(this.el, 'spark-table--resizable') ? true : false);
},

_bindEventListenerCallbacks

method
 _bindEventListenerCallbacks() 

Create bound versions of event listener callbacks and store them.
Otherwise we can't unbind from these events later because the
function signatures won't match.

_bindEventListenerCallbacks: function() {

  this._onClickBound = this._onClick.bind(this);
  this._onChangeBound = this._onChange.bind(this);
  this._onFocusBound = this._onFocus.bind(this);
  this._onBlurBound = this._onBlur.bind(this);

  this._onTouchstartBound = this._onTouchstart.bind(this);
  this._onTouchendBound = this._onTouchend.bind(this);
  this._onKeydownBound = this._onKeydown.bind(this);

  this._onMouseDownBound = this._onMouseDown.bind(this);
  this._onMouseMoveBound = this._onMouseMove.bind(this);
  this._onMouseUpBound = this._onMouseUp.bind(this);
},

_addEventListeners

method
 _addEventListeners() 

Add event listeners for DOM events.

_addEventListeners: function() {

  this.el.addEventListener('click', this._onClickBound, false);
  this.el.addEventListener('change', this._onChangeBound, false);
  this.el.addEventListener('focus', this._onFocusBound, true);
  this.el.addEventListener('blur', this._onBlurBound, true);

  if (this.isSpreadsheet) {
    this.el.addEventListener('touchstart', this._onTouchstartBound, false);
    this.el.addEventListener('touchend', this._onTouchendBound, false);
    this.el.addEventListener('keydown', this._onKeydownBound, false);
  }

  if (this.isResizable) {
    this.tableEl.addEventListener('mousedown', this._onMouseDownBound, false);
  }
},

_removeEventListeners

method
 _removeEventListeners() 

Remove event listeners for DOM events..

_removeEventListeners: function() {

  this.el.removeEventListener('click', this._onClickBound);
  this.el.removeEventListener('change', this._onChangeBound);
  this.el.removeEventListener('focus', this._onFocusBound);
  this.el.removeEventListener('blur', this._onBlurBound);

  this.el.removeEventListener('touchstart', this._onTouchstartBound);
  this.el.removeEventListener('touchend', this._onTouchendBound);
  this.el.removeEventListener('keydown', this._onKeydownBound);

  this.tableEl.removeEventListener('mousedown', this._onMouseDownBound);

  this._removeResizeListeners();
},

_addResizeListeners

method
 _addResizeListeners() 

Add listeners for mousemove and mouseup events.

_addResizeListeners: function() {
  window.addEventListener('mousemove', this._onMouseMoveBound, false);
  window.addEventListener('mouseup', this._onMouseUpBound, false);
},

_removeResizeListeners

method
 _removeResizeListeners() 

Remove listeners for mosuemove and mouseup.

_removeResizeListeners: function() {
  window.removeEventListener('mousemove', this._onMouseMoveBound);
  window.removeEventListener('mouseup', this._onMouseUpBound);
},

_toggleRowActive

method
 _toggleRowActive() 

Option name Type Description
row Object

Toggle the active state on a row.

_toggleRowActive: function(row) {

  if (this._hasClass(row, 'active')) {
    this._makeRowInActive(row);
    this._uncheckSelectAll();
  } else {
    this._makeRowActive(row);
  }
},

_makeRowActive

method
 _makeRowActive() 

Option name Type Description
row Element

Make a row active

_makeRowActive: function(row) {

  this._addClass(row, 'active');
  var checkbox = row.querySelector('input[type="checkbox"]:not([disabled])');

  if (checkbox && checkbox.checked !== true) {
    checkbox.checked = true;
    this._changePaused = true;
    this._triggerEvent(checkbox, 'change');
    this._changePaused = false;
  }
},

_makeRowInActive

method
 _makeRowInActive() 

Option name Type Description
row Element

Make a row active

_makeRowInActive: function(row) {

  this._removeClass(row, 'active');
  var checkbox = row.querySelector('input[type="checkbox"]:not([disabled])');

  if (checkbox && checkbox.checked !== false) {
    checkbox.checked = false;
    this._changePaused = true;
    this._triggerEvent(checkbox, 'change');
    this._changePaused = false;
  }
},

_toggleRowsActive

method
 _toggleRowsActive() 

Option name Type Description
rows NodeList
active Boolean

Toggle active on each row.

_toggleRowsActive: function(rows, active) {

  var func = active ? '_makeRowActive' : '_makeRowInActive';
  var i = 0;
  var len = rows.length;

  for (; i < len; i++) {
    this[func](rows[i]);
  }
},

_toggleSelectAll

method
 _toggleSelectAll() 

Option name Type Description
el Element

Toggle whether everything should be selected. Find the checkbox input inside of the
given element and invert its state.

_toggleSelectAll: function(el) {

  var checkbox = el.querySelector('input[type="checkbox"]');

  if (!checkbox) {
    return;
  }

  this._toggleRowsActive(this.el.querySelectorAll('tbody tr'), !checkbox.checked);

  checkbox.checked = !checkbox.checked;
},

_uncheckSelectAll

method
 _uncheckSelectAll() 

Uncheck the select all checkboxes.

_uncheckSelectAll: function() {

  var checkboxes = this.el.querySelectorAll('.spark-table__select-all input[type="checkbox"]');
  var i = 0;
  var len = checkboxes.length;

  for (; i < len; i++) {
    checkboxes[i].checked = false;
  }
},

_deactivateAllInputs

method
 _deactivateAllInputs() 

Deactivate editing in all input fields.

_deactivateAllInputs: function() {

  if (!this.tableEl) {
    return;
  }

  this._deactivateInputs(this.tableEl);
},

_deactivateInputs

method
 _deactivateInputs() 

Option name Type Description
el Element

Deactivate all the inputs inside an element

_deactivateInputs: function(el) {

  var inputs = el.querySelectorAll('input:not([type="checkbox"])');
  var i = 0;

  var len = inputs.length;

  for (; i < len; i++) {
    this._deactivateInput(inputs[i]);
  }
},

_deactivateInput

method
 _deactivateInput() 

Option name Type Description
input Element

Make an input field readonly.

_deactivateInput: function(input) {
  input.setAttribute('readonly', '');
  this._removeClass(input.parentNode, 'editing');
},

_activateInputs

method
 _activateInputs() 

Option name Type Description
el Element

Activate all the inputs inside an element

_activateInputs: function(el) {

  var inputs = el.querySelectorAll('input:not([type="checkbox"])');
  var i = 0;

  var len = inputs.length;

  for (; i < len; i++) {
    this._activateInput(inputs[i]);
  }
},

_activateInput

method
 _activateInput() 

Option name Type Description
input Element

Make an input field readable.

_activateInput: function(input) {
  input.removeAttribute('readonly');
  this._addClass(input.parentNode, 'editing');
  if (input.type !== 'checkbox' && input.type !== 'radio') {
    setCaret(input, -1);
  }
},

_activateInputOrFocusDown

method
 _activateInputOrFocusDown() 

Option name Type Description
input Element

Activate an input, unless it's already enabled in which case
the focus should move down a row.

_activateInputOrFocusDown: function(input) {

  // Currently readonly
  if (input.getAttribute('readonly') === '') {
    this._activateInput(input);
    return;
  }

  this._focusDown(input, true);
},

_disableRowsColumnsCells

method
 _disableRowsColumnsCells() 

Find all the rows, columns and cells that should be disabled.

_disableRowsColumnsCells: function() {
  each(this.tableEl.querySelectorAll('td input[disabled]'), this.disableCell.bind(this));
  each(this.tableEl.querySelectorAll('.spark-table__disabled-row'), this.disableRow.bind(this));
  each(this.tableEl.querySelectorAll('.spark-table__disabled-column'), this.disableColumn.bind(this));
},

_focusUp

method
 _focusUp() 

Option name Type Description
input Element
force Boolean

Force the move even if the element is active.

Move our focus up a row from the given element.

_focusUp: function(input, force) {
  return this._focusUpDown(input, 'up', force);
},

_focusDown

method
 _focusDown() 

Option name Type Description
input Element
force Boolean

Force the move even if the element is active.

Move our focus down a row from the given element.

_focusDown: function(input, force) {
  return this._focusUpDown(input, 'down', force);
},

_focusUpDown

method
 _focusUpDown() 

Option name Type Description
input Element
direction String

up|down

force Boolean

Force the move even if the element is active.

Focus on a row up or down from the given element.

_focusUpDown: function(input, direction, force) {

  // If we're not being told to force and the item is not read only
  if (!force && input.getAttribute('readonly') === null) {
    return;
  }

  this._deactivateInput(input);
  var td = this._getElementMatchingParent(input, 'td', this.el);

  if (!td) {
    return;
  }

  var index = this._getChildIndex(td.parentNode.children, td);
  var nextRow = this[direction === 'up' ? '_getSiblingBefore' : '_getSiblingAfter'](td.parentNode, 'tr');

  if (!nextRow) {
    return;
  }

  var newTd = nextRow.children[index];

  if (!newTd) {
    return;
  }

  var newInput = newTd.querySelector('input:not([type="checkbox"]), select');

  if (newInput) {
    if (newInput.disabled) {
      this._focusUpDown(newInput, direction, force);
    } else {
      newInput.focus();
    }
  }
},

_focusLeft

method
 _focusLeft() 

Option name Type Description
input Element
force Boolean

Force the move even if the element is active.

Move our focus left a cell from the given element.

_focusLeft: function(input, force) {
  return this._focusLeftRight(input, 'left', force);
},

_focusRight

method
 _focusRight() 

Option name Type Description
input Element
force Boolean

Force the move even if the element is active.

Move our focus right a cell from the given element.

_focusRight: function(input, force) {
  return this._focusLeftRight(input, 'right', force);
},

_focusLeftRight

method
 _focusLeftRight() 

Option name Type Description
input Element
direction String

up|down

force Boolean

Force the move even if the element is active.

Focus on a cell left or down from the given element.

_focusLeftRight: function(input, direction, force) {

  // If we're not being told to force and the item is not read only
  if (!force && input.getAttribute('readonly') === null) {
    return;
  }

  this._deactivateInput(input);
  var td = this._getElementMatchingParent(input, 'td', this.el);

  if (!td) {
    return;
  }

  var newTd = this[direction === 'left' ? '_getSiblingBefore' : '_getSiblingAfter'](td, 'td');

  if (!newTd) {
    return;
  }

  var newInput = newTd.querySelector('input:not([type="checkbox"]), select');

  if (newInput) {
    if (newInput.disabled) {
      this._focusLeftRight(newInput, direction, force);
    } else {
      newInput.focus();
    }
  }
},

_checkDoubleClick

method
 _checkDoubleClick() 

Option name Type Description
el Element

Check for two click events on the same element in short succession.

_checkDoubleClick: function(el) {

  var now = Date.now();
  var lastTime = this._lastClickTime;
  var lastEl = this._lastClickEl;

  this._lastClickTime = now;
  this._lastClickEl = el;

  if (el === lastEl && now - 500 < lastTime) {
    return true;
  }

  return false;
},

_clearClicked

method
 _clearClicked() 

Unset the last clicked element.

_clearClicked: function() {
  this._lastClickEl = null;
},

_editRow

method
 _editRow() 

Option name Type Description
row Element

Enable editing on a row.

_editRow: function(row) {

  if (!row) {
    return;
  }

  this._editingCount++;

  this._activateInputs(row);
  formData.store(row);
  this._addClass(row, 'editing');
  this._updateBindings();
},

_cancelRow

method
 _cancelRow() 

Option name Type Description
row Element

Cancel editing a row.

_cancelRow: function(row) {

  if (!row) {
    return;
  }

  this._editingCount--;

  this._deactivateInputs(row);
  formData.restore(row);
  this._removeClass(row, 'editing');
  this._updateBindings();
},

_saveRow

method
 _saveRow() 

Option name Type Description
row Element

Save a row.

_saveRow: function(row) {

  if (!row) {
    return;
  }

  this._editingCount--;

  this._deactivateInputs(row);
  formData.clear(row);
  this._removeClass(row, 'editing');
  this._updateBindings();

  (this.onRowSave || noop)(this._getChildIndex(row.parentNode.children, row), row);
},

_deleteRow

method
 _deleteRow() 

Option name Type Description
row Element

Delete a row.

_deleteRow: function(row) {

  if (!row) {
    return;
  }

  (this.onRowDelete || noop)(this._getChildIndex(row.parentNode.children, row), row);
  row.parentNode.removeChild(row);
},

_confirmDelete

method
 _confirmDelete() 

Option name Type Description
row Element

Confirm the deletion of a row.

_confirmDelete: function(row) {

  if (!this.confirmDeleteCallback || typeof this.confirmDeleteCallback !== 'function') {
    this._deleteRow(row);
  } else {
    this.confirmDeleteCallback(row, this._deleteRow);
  }
},

_updateBindings

method
 _updateBindings() 

Update data bindings.

_updateBindings: function() {
  this._toggleClass(this.el, 'editing', this._editingCount);
},

_initResize

method
 _initResize() 

Add handles to the header that can be grabbed for resizing.

_initResize: function() {

  this._resizeEls = [];

  var ths = this.tableEl.querySelectorAll('thead th');

  each(ths, function(th) {
    th.innerHTML = '<span class="spark-table__resize spark-table__resize--left"></span>' + th.innerHTML + '<span class="spark-table__resize spark-table__resize--right"></span>';
    this._resizeEls.push(th);
  }.bind(this));
},

_initRows

method
 _initRows() 

Initialize rows active states.

_initRows: function() {

  each(this.tableEl.querySelectorAll('td.spark-table__checkbox input:checked'), function(c) {
    this._makeRowActive(this._getElementMatchingParent(c, 'tr'));
  }.bind(this));
},

_sizeColumns

method
 _sizeColumns() 

Option name Type Description
unit String

Optional

force Boolean

Optional

Set the size of each column as a percentage so it can be adjusted
while cells are resized.

_sizeColumns: function(unit, force) {

  unit = unit || '%';

  if (this._sizeColumnsRun && !force) {
    return;
  }

  var width = this.tableEl.offsetWidth;

  each(this.tableEl.querySelectorAll('thead th'), function(th) {
    if (unit === '%')
      th.style.width = (Math.round(th.offsetWidth / width * 100000) / 100000) * 100 + '%';
    else
      th.style.width = th.offsetWidth + 'px';
  }.bind(this));

  this._sizeColumnsRun = true;
},

_initExpands

method
 _initExpands() 

Initialize expand/collapse rows.

_initExpands: function() {

  var expands = this.tableEl.querySelectorAll('.spark-table-expand');

  this._expands = [];

  each(expands, function(e) {
    this._expands.push(new Expand(e, {
      onBeforeExpand: this._onBeforeExpand.bind(this)
    }));
  }.bind(this));
},

_onBeforeExpand

method
 _onBeforeExpand() 

Before an expand is called, size all the columns so that
the expand does cause width changes.

_onBeforeExpand: function() {
  this._sizeColumns();
},

_onClick

method
 _onClick() 

Option name Type Description
e Object

When we are clicked determine the proper action to take.

_onClick: function(e) {

  var target = e.target || e.srcElement;
  var row;
  var selectAll;
  var actionTaken = false;
  var clearClicked = true;

  // Select all rows checkbox
  if ((selectAll = this._getElementMatchingParent(target, '.spark-table__select-all', this.el)) && !this._elementMatches(target, 'input[type="checkbox"]')) {
    this._toggleSelectAll(selectAll);
    actionTaken = true;
  }
  // Editable field
  else if (this._elementMatches(target, 'input:not([type="checkbox"]), select')) {

    if (!target.disabled) {

      // Listen for double clicks on a spreadsheet
      if (this.isSpreadsheet) {
        clearClicked = false;
        if (this._checkDoubleClick(target)) {
          clearClicked = true;
          this._activateInput(target);
        }
      }

      actionTaken = true;
    }
  }
  // Edit button
  else if (this._elementMatches(target, '.spark-table__edit-row')) {
    this._editRow(this._getElementMatchingParent(target, 'tr', this.el));
    actionTaken = true;
  }
  // Delete button
  else if (this._elementMatches(target, '.spark-table__delete-row')) {
    this._confirmDelete(this._getElementMatchingParent(target, 'tr', this.el));
    actionTaken = true;
  }
  // Save button
  else if (this._elementMatches(target, '.spark-table__edit-row-save')) {
    this._saveRow(this._getElementMatchingParent(target, 'tr', this.el));
    actionTaken = true;
  }
  // Cancel button
  else if (this._elementMatches(target, '.spark-table__edit-row-cancel')) {
    this._cancelRow(this._getElementMatchingParent(target, 'tr', this.el));
    actionTaken = true;
  }
  // Select a row
  else if (!this._getElementMatchingParent(target, 'button, a', this.el) && !this._elementMatches(target, 'input[type="checkbox"]') && (row = this._getElementMatchingParent(target, 'tbody tr', this.el))) {
    if (!(row.querySelector('input[type="checkbox"]') || {}).disabled) {
      this._toggleRowActive(row);
      actionTaken = true;
    }
  }

  if (clearClicked) {
    this._clearClicked();
  }

  if (actionTaken) {
    e.preventDefault();
  }
},

_onChange

method
 _onChange() 

Option name Type Description
e Object

When the change event fires on our element.

_onChange: function(e) {

  if (this._changePaused) {
    return;
  }

  var target = e.target || e.srcElement;
  var row;
  var selectAll;

  // Select all rows checkbox. We have to invert the checked value here because it
  // get toggled back in the select all call.
  if ((selectAll = this._getElementMatchingParent(target, '.spark-table__select-all', this.el))) {
    target.checked = !target.checked;
    this._toggleSelectAll(selectAll);
  }
  // Checkbox for a row
  else if (this._elementMatches(target, 'input[type="checkbox"]') && (row = this._getElementMatchingParent(target, 'tbody tr', this.el))) {
    this._toggleRowActive(row);
  }
},

_onFocus

method
 _onFocus() 

Option name Type Description
e Object

If this is a spreadsheet, whenever a field gains focus, highlight its parent.

_onFocus: function(e) {

  var target = e.target || e.srcElement;

  if (!this.isSpreadsheet || !this._elementMatches(target, 'input:not([type="checkbox"]), select')) {
    return;
  }

  var td = this._getElementMatchingParent(target, 'td', this.el);
  this._addClass(td, 'focus');
},

_onBlur

method
 _onBlur() 

Option name Type Description
e Object

If this is a spreadsheet, whenever a field gains focus, highlight its parent.

_onBlur: function(e) {

  if (!this.isSpreadsheet) {
    return;
  }

  var target = e.target || e.srcElement;
  var td = this._getElementMatchingParent(target, 'td', this.el);
  this._removeClass(td, 'focus');
  this._deactivateInput(target);
},

_onKeydown

method
 _onKeydown() 

Option name Type Description
e Object

When a key is pressed, if this is a spreadsheet then we should detect
enter or arrow keys.

_onKeydown: function(e) {

  var target = e.target || e.srcElement;

  if (!this.isSpreadsheet || !this._elementMatches(target, 'input:not([type="checkbox"]), select')) {
    return;
  }

  var code = e.keyCode || e.which;

  switch (code) {
    case this._keyCodes.ENTER:
      this._activateInputOrFocusDown(target);
      break;
    case this._keyCodes.ESCAPE:
      this._deactivateInput(target);
      break;
    case this._keyCodes.DOWN:
      this._focusDown(target);
      break;
    case this._keyCodes.UP:
      this._focusUp(target);
      break;
    case this._keyCodes.LEFT:
      this._focusLeft(target);
      break;
    case this._keyCodes.RIGHT:
      this._focusRight(target);
      break;
  }
},

_onTouchstart

method
 _onTouchstart() 

Option name Type Description
e Object

Listen for a touch and hold on an input.

_onTouchstart: function(e) {

  var target = e.target || e.srcElement;

  if (!this.isSpreadsheet || !this._elementMatches(target, 'input:not([type="checkbox"])')) {
    return;
  }

  this._touchStartEl = target;
  this._touchStartTime = Date.now();
  this._touchStartTimer = setTimeout(this._onTouchHold.bind(this), 1000);
},

_onTouchend

method
 _onTouchend() 

Option name Type Description
e Object

Listen for the end of a touch to cancel the hold timer.

_onTouchend: function(e) {

  var target = e.target || e.srcElement;

  if (!this._touchStartEl || target !== this._touchStartEl) {
    return;
  }

  this._touchStartEl = null;
  this._touchStartTime = null;
  clearTimeout(this._touchStartTimer);
},

_onTouchHold

method
 _onTouchHold() 

When the user has held on an input for the defined amount of time.

_onTouchHold: function() {

  this._activateInput(this._touchStartEl);

  this._touchStartEl = null;
  this._touchStartTime = null;
  clearTimeout(this._touchStartTimer);
},

_onMouseDown

method
 _onMouseDown() 

Option name Type Description
e Object

When the mouse is depressed.

_onMouseDown: function(e) {

  var target = e.target || e.srcElement;

  if (!this.isResizable || !this._elementMatches(target, '.spark-table__resize')) {
    return;
  }

  e.preventDefault();

  this._lastScreenX = e.screenX;

  this._sizeColumns('px', true);

  this._resizingEl = target.parentNode;
  var index = this._resizeEls.indexOf(this._resizingEl);

  if (this._hasClass(target, 'spark-table__resize--left')) {
    this._resizingEl = this._resizeEls[index - 1];
  }

  if (!this._resizingEl) {
    return;
  }

  this._addResizeListeners();
},

_onMouseMove

method
 _onMouseMove() 

Option name Type Description
e Object

When the mouse moves after being depressed, resize the columns.

_onMouseMove: function(e) {

  var x = e.screenX;
  var d = x - this._lastScreenX;

  // No delta change
  if (!d) {
    return;
  }

  e.preventDefault();

  var w = this._resizingEl.offsetWidth;
  var tW = this.tableEl.offsetWidth;
  var newW = w + d;
  var newTW = tW + d;

  this._resizingEl.style.width = newW + 'px';
  this.tableEl.style.width = newTW + 'px';

  // Size was not affected because we're too small
  if (this._resizingEl.offsetWidth === w || this.tableEl.offsetWidth < this.tableEl.parentNode.offsetWidth) {
    this._resizingEl.style.width = w + 'px';
    this.tableEl.style.width = tW + 'px';
  }

  this._lastScreenX = x;
},

_onMouseUp

method
 _onMouseUp() 

Option name Type Description
e Object

When the mouse is released, stop tracking mouse move events and
convert table sizes to percentages.

_onMouseUp: function() {
  this._sizeColumns('%', true);
  this.tableEl.style.width = (this.tableEl.offsetWidth / this.tableEl.parentNode.offsetWidth * 100) + '%';
  this._removeResizeListeners();
}
  };

  Base.exportjQuery(Table, 'Table');

  return Table;
}));