Sabre Spark

TextInput

A text input container.

Option name Type Description
module components/text-input.js
Example
new TextInput(el, {
  // Optional. Validation function, will override default one
  validate: function() {},
  // Optional. Regular expression of validation
  validatePattern: '([^@]{1,})(@)([^\.]{1,})(\.)([^\.]{1,})',
  // Only if set validatePattern work. Optional. Callback for when validate.
  onValidate: function(inputInstance, regResult, value) {},
  // Optional. Callback for when the input value changes.
  onChange: function(inputInstance, value) {},
  // Optional. Callback for when input focus.
  onFocus: function(inputInstance, value) {},
  // Optional. Callback for when the input blur.
  onBlur: function(inputInstance, value) {}
});

TextInput

function
 TextInput() 

TextInput constructor.

Option name Type Description
el Element
params Object
var TextInput = function(el, params) {

  if (!el) {
    return;
  }

  this._setParams(this.defaults, true);
  this._bindEventListenerCallbacks();
  this._cacheElements(el);
  this._setParams(params || {});
  this._addEventListeners();
};

TextInput.prototype = {

_setParams

property
 _setParams 

Include common functionality.

_setParams: Base.setParams,
remove: Base.remove,
_toggleClass: Base.toggleClass,
_hasClass: Base.hasClass,
_getChildIndex: Base.getChildIndex,

_whitelistedParams

property
 _whitelistedParams 

Whitelisted parameters which can be set on construction.

Option name Type Description
_whitelistedParams: ['validate', 'validatePattern', 'onValidate', 'onChange', 'onFocus', 'onBlur'],

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,
  inputEl: null,
  passwordToggleEl: null,
  clearEl: null,
  isActive: false,
  isTextarea: false,
  validatePattern: false,
  type: null,
  showCharacters: false,
  showCharactersRemaining: false,
  maxlength: null,
  typeahead: null,
  onChange: noop,
  onValidate: noop,
  onFocus: noop,
  onBlur: noop,
  _onFocusBound: null,
  _onBlurBound: null,
  _onInputBound: null,
  _onTogglePasswordViewHideBound: null,
  _onClearClickBound: null
},

show

method
 show() 

Show the input by adding the active state and setting character counts (if necessary).

show: function() {
  this.isActive = true;
  this._updateClass();
  this._setCharactersCount();
},

hide

method
 hide() 

Hide the input by removing the active state.

hide: function() {
  this.isActive = false;
  this._updateClass();
},

validate

method
 validate() 

Run the validation.

Option name Type Description
validate: function() {

  var validate = this.validatePattern;

  // Nothing to validate.
  if (!validate) {
    return;
  }

  var re = new RegExp(validate);

  // Passes the component instance, true or false for valid, and the current value.
  (this.onValidate || noop)(this, re.test(this.inputEl.value), this.inputEl.value);
},

setError

method
 setError() 

Set the error state.

Option name Type Description
message String

Optional

setError: function(message) {

  // Animate down
  if (!this._isMessageVisible()) {
    this._showMessage();
  }

  this.clearWarning();
  this.clearSuccess();

  this.el.setAttribute('data-error', true);

  if (message) {
    this.setMessage(message);
  }
},

clearError

method
 clearError() 

Set the error state.

clearError: function() {
  this.el.removeAttribute('data-error', true);
},

setWarning

method
 setWarning() 

Set the warning state.

Option name Type Description
message String

Optional

setWarning: function(message) {

  // Animate down
  if (!this._isMessageVisible()) {
    this._showMessage();
  }

  this.clearError();
  this.clearSuccess();

  this.el.setAttribute('data-warning', true);

  if (message) {
    this.setMessage(message);
  }
},

clearWarning

method
 clearWarning() 

Set the error state.

clearWarning: function() {
  this.el.removeAttribute('data-warning', true);
},

setSuccess

method
 setSuccess() 

Set the success state.

Option name Type Description
message String

Optional

setSuccess: function(message) {

  // Animate down
  if (!this._isMessageVisible()) {
    this._showMessage();
  }

  this.clearError();
  this.clearWarning();

  this.el.setAttribute('data-success', true);

  if (message) {
    this.setMessage(message);
  }
},

clearSuccess

method
 clearSuccess() 

Set the success state.

clearSuccess: function() {
  this.el.removeAttribute('data-success', true);
},

clearMessages

method
 clearMessages() 

Clear all messages.

clearMessages: function() {
  this._hideMessage(function() {
    this.clearError();
    this.clearWarning();
    this.clearSuccess();
  }.bind(this));
},

setMessage

method
 setMessage() 

Set the message text.

Option name Type Description
message String
setMessage: function(message) {
  this.messageEl.innerHTML = message;
},

setValue: function(value) {
  this.inputEl.value = value;
  this._onFocus();
  this._onBlur();
},

_showMessage

method
 _showMessage() 

Show the message

_showMessage: function() {

  animateHeight({
    el: this.el,
    toggleEl: '.spark-input__message'
  });
},

_hideMessage

method
 _hideMessage() 

Hide the message.

Option name Type Description
callback Function
_hideMessage: function(callback) {

  animateHeight({
    el: this.el,
    toggleEl: '.spark-input__message',
    toggleValue: 'none',
    action: 'collapse',
    onComplete: callback
  });
},

_isMessageVisible

method
 _isMessageVisible() 

Is the message currently visible?

Option name Type Description
return Boolean
_isMessageVisible: function() {
  return this.el.getAttribute('data-error') || this.el.getAttribute('data-warning') || this.el.getAttribute('data-success');
},

_cacheElements

method
 _cacheElements() 

Store a reference to the needed elements.

Option name Type Description
el Element
_cacheElements: function(el) {

  this.el = el;
  this.inputEl = this.el.querySelector('input, textarea');
  this.passwordToggleEl = this.el.querySelector('.spark-input__password-toggle');

  if (!this.inputEl) {
    throw new Error('No <input> or <textarea> element present in input container!', this.el);
  }

  this.messageEl = this.el.querySelector('.spark-input__message') || document.createElement('span');
  this.clearEl = this.el.querySelector('.spark-input__clear');

  this._parseParams();

  if (this.inputEl.value) {
    this.show();
    this._onInput();
  }
},

_parseParams

method
 _parseParams() 

Parse parameters from the elements.

_parseParams: function() {

  this.validatePattern = this.validatePattern || this.inputEl.getAttribute('data-validate');
  this.type = this.inputEl.getAttribute('type') || 'text';
  this.showCharacters = this.el.getAttribute('data-characters') !== null ? true : false;
  this.showCharactersRemaining = this.el.getAttribute('data-characters-remaining') !== null ? true : false;
  this.maxlength = this.inputEl.getAttribute('maxlength') || this.inputEl.getAttribute('data-maxlength-soft') || null;
  this.isTextarea = this.inputEl.nodeName.toLowerCase() === 'textarea' ? true : false;
  this.typeahead = this.inputEl.getAttribute('data-typeahead') !== null ? new Typeahead(this.el, {
    onBlur: this._onBlurBound
  }) : null;
  this.isActive = this.inputEl.value ? true : false;
},

_setCharactersCount

method
 _setCharactersCount() 

Set the characters count attribute.

_setCharactersCount: function() {

  if (this.showCharacters) {
    this.el.setAttribute('data-characters', this.inputEl.value.length);
  } else if (this.showCharactersRemaining) {

    var remaining = this.maxlength - this.inputEl.value.length;

    this.el.setAttribute('data-characters-remaining', remaining);

    if (remaining < 1) {
      this.el.setAttribute('data-characters-remaining-danger', true);
    } else {
      this.el.removeAttribute('data-characters-remaining-danger');
    }
  }
},

_setTextareaHeight

method
 _setTextareaHeight() 

Set the height of the textarea so that it doesn't scroll.

_setTextareaHeight: function() {

  var style = window.getComputedStyle(this.inputEl);
  var borders = parseInt(style.borderTopWidth, 10) + parseInt(style.borderBottomWidth, 10);

  this.inputEl.style.height = null;

  var height = this.inputEl.scrollHeight;
  var lines;

  // No height, most likely the element is invisible. Get a rough
  // approximation of height so we have something.
  if (!height) {
    lines = this.inputEl.innerHTML.split('\n');
    height = (Math.max(parseFloat(style.lineHeight)) * (lines.length)) + parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
  }

  this.inputEl.style.height = (height + borders) + 'px';
},

_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._onFocusBound = this._onFocus.bind(this);
  this._onBlurBound = this._onBlur.bind(this);
  this._onInputBound = this._onInput.bind(this);
  this._onTogglePasswordViewHideBound = this._onTogglePasswordViewHide.bind(this);
  this._onClearClickBound = this._onClearClick.bind(this);
},

_addEventListeners

method
 _addEventListeners() 

Add event listeners for focus, blur, input, and click.

_addEventListeners: function() {
  this.inputEl.addEventListener('focus', this._onFocusBound);
  this.inputEl.addEventListener('blur', this._onBlurBound);
  this.inputEl.addEventListener('input', this._onInputBound);
  if (this.passwordToggleEl) {
    this.passwordToggleEl.addEventListener('click', this._onTogglePasswordViewHideBound);
  }

  if (this.clearEl) {
    this.clearEl.addEventListener('click', this._onClearClickBound);
  }
},

_removeEventListeners

method
 _removeEventListeners() 

Remove event listeners for focus, blur and input.

_removeEventListeners: function() {

  this.inputEl.removeEventListener('focus', this._onFocusBound);
  this.inputEl.removeEventListener('blur', this._onBlurBound);
  this.inputEl.removeEventListener('input', this._onInputBound);
  this.passwordToggleEl.removeEventListener('click', this._onTogglePasswordViewHideBound);

  if (this.clearEl) {
    this.clearEl.removeEventListener('click', this._onClearClickBound);
  }
},

_updateClass

method
 _updateClass() 

Update the active class.

_updateClass: function() {
  this._toggleClass(this.el, 'active', this.isActive);
},

_onFocus

method
 _onFocus() 

When the input element gains focus.

Option name Type Description
e Object
_onFocus: function() {
  this.show();
  (this.onFocus || noop)(this, this.inputEl.value);
},

_onBlur

method
 _onBlur() 

When the input element loses focus.

Option name Type Description
e Object
_onBlur: function() {
  if (!this.inputEl.value) {
    this.hide();
  }
  (this.onBlur || noop)(this, this.inputEl.value);
},

_onInput

method
 _onInput() 

When the value is about to change, run the validation, set the characters count
and resize if we're a textarea.

Option name Type Description
e Object
_onInput: function() {

  this.validate();
  this._setCharactersCount();

  if (this.isTextarea) {
    this._setTextareaHeight();
  }

  (this.onChange || noop)(this, this.inputEl.value);
},

_onClearClick

method
 _onClearClick() 

When a clear button is clicked, empty the field.

Option name Type Description
e Object
_onClearClick: function() {
  this.inputEl.value = '';
  this.hide();
  (this.onChange || noop)(this, this.inputEl.value);
},

_onTogglePasswordViewHide

method
 _onTogglePasswordViewHide() 

Toggle the current type value (text/password) of password input.

Option name Type Description
e Object
_onTogglePasswordViewHide: function(e) {
  e.preventDefault();
  this.inputEl.setAttribute('type', this.inputEl.getAttribute('type') === 'password' ? 'text' : 'password');
}
  };

  Base.exportjQuery(TextInput, 'TextInput');

  return TextInput;
}));