TextInput

function
 TextInput() 

Option name Type Description
el Element
params Object

TextInput constructor.

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.

_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.

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.

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

Option name Type Description
message String

Optional

Set the error state.

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

Option name Type Description
message String

Optional

Set the warning state.

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

Option name Type Description
message String

Optional

Set the success state.

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

Option name Type Description
message String

Set the message text.

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

Option name Type Description
callback Function

Hide the message.

_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?

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

_cacheElements

method
 _cacheElements() 

Option name Type Description
el Element

Store a reference to the needed elements.

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

Option name Type Description
e Object

When the input element gains focus.

_onFocus: function() {
  this.show();
  (this.onFocus || noop)(this, this.inputEl.value);
},

_onBlur

method
 _onBlur() 

Option name Type Description
e Object

When the input element loses focus.

_onBlur: function() {
  if (!this.inputEl.value) {
    this.hide();
  }
  (this.onBlur || noop)(this, this.inputEl.value);
},

_onInput

method
 _onInput() 

Option name Type Description
e Object

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

_onInput: function() {

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

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

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

_onClearClick

method
 _onClearClick() 

Option name Type Description
e Object

When a clear button is clicked, empty the field.

_onClearClick: function() {
  this.inputEl.value = '';
  this.hide();
  (this.onChange || noop)(this, this.inputEl.value);
},

_onTogglePasswordViewHide

method
 _onTogglePasswordViewHide() 

Option name Type Description
e Object

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

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

  Base.exportjQuery(TextInput, 'TextInput');

  return TextInput;
}));