Sabre Spark

Table

Option name Type Description
module components/table.js
Example
new Table(el);

Table

function
 Table() 

Table constructor.

Option name Type Description
el Element
params Object
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.

Option name Type Description
_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.

Option name Type Description
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() 

Disable the form field in a table cell.

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

enableCell

method
 enableCell() 

Enable the form field in a table cell.

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

disableRow

method
 disableRow() 

Disable a row and all the cells inside of it.

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

enableRow

method
 enableRow() 

Enable a row and all the cells inside of it.

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

disableColumn

method
 disableColumn() 

Disable a column and all the cells inside of it.

Option name Type Description
el Element
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() 

Enable a column and all the cells inside of it.

Option name Type Description
el Element
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() 

Remove the table anc cleanup.

Option name Type Description
leaveElement Boolean

Leave the element intact.

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

activateRow

method
 activateRow() 

Activate a row.

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

activateRows

method
 activateRows() 

Activate multiple rows.

Option name Type Description
rows Array
activateRows: function(rows) {
  each(rows, this.activateRow.bind(this));
},

deactivateRow

method
 deactivateRow() 

Deactivate a row.

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

deactivateRows

method
 deactivateRows() 

Deactivate multiple rows.

Option name Type Description
rows Array
deactivateRows: function(rows) {
  each(rows, this.deactivateRow.bind(this));
},

getActiveRows

method
 getActiveRows() 

Get an array of currently active rows.

Option name Type Description
return Array
getActiveRows: function() {

  var arr = [];

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

  return arr;
},

_cacheElements

method
 _cacheElements() 

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

Option name Type Description
el Element
_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() 

Toggle the active state on a row.

Option name Type Description
row Object
_toggleRowActive: function(row) {

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

_makeRowActive

method
 _makeRowActive() 

Make a row active

Option name Type Description
row Element
_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() 

Make a row active

Option name Type Description
row Element
_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() 

Toggle active on each row.

Option name Type Description
rows NodeList
active Boolean
_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() 

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

Option name Type Description
el Element
_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() 

Deactivate all the inputs inside an element

Option name Type Description
el 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() 

Make an input field readonly.

Option name Type Description
input Element
_deactivateInput: function(input) {
  input.setAttribute('readonly', '');
  this._removeClass(input.parentNode, 'editing');
},

_activateInputs

method
 _activateInputs() 

Activate all the inputs inside an element

Option name Type Description
el 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() 

Make an input field readable.

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

_activateInputOrFocusDown

method
 _activateInputOrFocusDown() 

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

Option name Type Description
input Element
_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() 

Move our focus up a row from the given element.

Option name Type Description
input Element
force Boolean

Force the move even if the element is active.

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

_focusDown

method
 _focusDown() 

Move our focus down a row from the given element.

Option name Type Description
input Element
force Boolean

Force the move even if the element is active.

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

_focusUpDown

method
 _focusUpDown() 

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

Option name Type Description
input Element
direction String

up|down

force Boolean

Force the move even if the element is active.

_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() 

Move our focus left a cell from the given element.

Option name Type Description
input Element
force Boolean

Force the move even if the element is active.

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

_focusRight

method
 _focusRight() 

Move our focus right a cell from the given element.

Option name Type Description
input Element
force Boolean

Force the move even if the element is active.

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

_focusLeftRight

method
 _focusLeftRight() 

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

Option name Type Description
input Element
direction String

up|down

force Boolean

Force the move even if the element is active.

_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() 

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

Option name Type Description
el Element
_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() 

Enable editing on a row.

Option name Type Description
row Element
_editRow: function(row) {

  if (!row) {
    return;
  }

  this._editingCount++;

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

_cancelRow

method
 _cancelRow() 

Cancel editing a row.

Option name Type Description
row Element
_cancelRow: function(row) {

  if (!row) {
    return;
  }

  this._editingCount--;

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

_saveRow

method
 _saveRow() 

Save a row.

Option name Type Description
row Element
_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() 

Delete a row.

Option name Type Description
row Element
_deleteRow: function(row) {

  if (!row) {
    return;
  }

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

_confirmDelete

method
 _confirmDelete() 

Confirm the deletion of a row.

Option name Type Description
row Element
_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() 

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

Option name Type Description
unit String

Optional

force Boolean

Optional

_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() 

When we are clicked determine the proper action to take.

Option name Type Description
e Object
_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() 

When the change event fires on our element.

Option name Type Description
e Object
_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() 

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

Option name Type Description
e Object
_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() 

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

Option name Type Description
e Object
_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() 

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

Option name Type Description
e Object
_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() 

Listen for a touch and hold on an input.

Option name Type Description
e Object
_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() 

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

Option name Type Description
e Object
_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() 

When the mouse is depressed.

Option name Type Description
e Object
_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() 

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

Option name Type Description
e Object
_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() 

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

Option name Type Description
e Object
_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;
}));