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 = {
Include common functionality.
_setParams: Base.setParams,
remove: Base.remove,
_toggleClass: Base.toggleClass,
_hasClass: Base.hasClass,
_getChildIndex: Base.getChildIndex,
Whitelisted parameters which can be set on construction.
_whitelistedParams: ['validate', 'validatePattern', 'onValidate', 'onChange', 'onFocus', 'onBlur'],
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 the input by adding the active state and setting character counts (if necessary).
show: function() {
this.isActive = true;
this._updateClass();
this._setCharactersCount();
},
Hide the input by removing the active state.
hide: function() {
this.isActive = false;
this._updateClass();
},
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);
},
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);
}
},
Set the error state.
clearError: function() {
this.el.removeAttribute('data-error', true);
},
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);
}
},
Set the error state.
clearWarning: function() {
this.el.removeAttribute('data-warning', true);
},
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);
}
},
Set the success state.
clearSuccess: function() {
this.el.removeAttribute('data-success', true);
},
Clear all messages.
clearMessages: function() {
this._hideMessage(function() {
this.clearError();
this.clearWarning();
this.clearSuccess();
}.bind(this));
},
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();
},
Show the message
_showMessage: function() {
animateHeight({
el: this.el,
toggleEl: '.spark-input__message'
});
},
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
});
},
Is the message currently visible?
_isMessageVisible: function() {
return this.el.getAttribute('data-error') || this.el.getAttribute('data-warning') || this.el.getAttribute('data-success');
},
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();
}
},
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;
},
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');
}
}
},
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';
},
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);
},
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);
}
},
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);
}
},
Update the active class.
_updateClass: function() {
this._toggleClass(this.el, 'active', this.isActive);
},
Option name | Type | Description |
---|---|---|
e | Object |
When the input element gains focus.
_onFocus: function() {
this.show();
(this.onFocus || noop)(this, this.inputEl.value);
},
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);
},
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);
},
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);
},
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;
}));