Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ampersand-input-view

Package Overview
Dependencies
Maintainers
2
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ampersand-input-view - npm Package Compare versions

Comparing version 0.3.0 to 1.0.0

328

ampersand-input-view.js

@@ -1,166 +0,172 @@

var domify = require('domify');
var dom = require('ampersand-dom');
var View = require('ampersand-view');
// can be overwritten by anything with:
// <input>, <label> and <the same `role` attributes
var template = [
'<label>',
'<span role="label"></span>',
'<input>',
'<div role="message-container" class="message message-below message-error">',
'<p role="message-text"></p>',
'</div>',
'</label>'
].join('');
function TextInputView(opts) {
if (!opts.name) throw new Error('must pass in a name');
// settings
this.name = opts.name;
this.value = opts.value || '';
this.originalValue = opts.value || '';
this.el = opts.el;
this.template = opts.template || template;
this.placeholder = opts.placeholder || '';
this.label = opts.label || '';
this.type = opts.type || 'text';
this.required = (typeof opts.required === 'boolean') ? opts.required : true;
this.validClass = opts.validClass || 'input-valid';
this.invalidClass = opts.invalidClass || 'input-invalid';
this.requiredMessage = opts.requiredMessage || 'This field is required.';
this.valid = false;
this.parent = opts.parent;
// render right away
this.render();
// add our event handlers
this.handleBlur = this.handleBlur.bind(this);
this.handleInputChanged = this.handleInputChanged.bind(this);
this.input.addEventListener('blur', this.handleBlur, false);
this.input.addEventListener('input', this.handleInputChanged, false);
// tests for validity
this.tests = opts.tests || [];
this.checkValid();
}
// remove and destroy element
TextInputView.prototype.remove = function () {
this.input.removeEventListener('input', this.handleInputChanged, false);
this.input.removeEventListener('blur', this.handleBlur, false);
var parent = this.el.parentNode;
if (parent) parent.removeChild(this.el);
};
// handle input events and show appropriate errors
TextInputView.prototype.handleInputChanged = function () {
// track whether user has edited directly
if (document.activeElement === this.input) this.directlyEdited = true;
this.value = this.input.value;
this.edited = true;
this.runTests();
if (this.parent) this.parent.update(this);
};
// Expose a method for explicitly setting the value
TextInputView.prototype.setValue = function (value, runValidation) {
this.input.value = value;
this.handleInputChanged();
};
// set the error message if exists
// hides the message container entirely otherwise
TextInputView.prototype.setMessage = function (message) {
var input = this.input;
this.message = message;
// there is an error
if (message && this.hasBeenValid) {
this.messageContainer.style.display = 'block';
this.messageEl.textContent = message;
dom.addClass(input, this.invalidClass);
dom.removeClass(input, this.validClass);
} else {
this.messageContainer.style.display = 'none';
if (this.hasBeenValid && this.edited) {
dom.addClass(input, this.validClass);
dom.removeClass(input, this.invalidClass);
module.exports = View.extend({
template: [
'<label>',
'<span role="label"></span>',
'<input>',
'<div role="message-container" class="message message-below message-error">',
'<p role="message-text"></p>',
'</div>',
'</label>'
].join(''),
bindings: {
'name': {
type: 'attribute',
selector: 'input, textarea',
name: 'name'
},
'label': [
{
role: 'label'
},
{
type: 'toggle',
role: 'label'
}
],
'message': {
type: 'text',
role: 'message-text'
},
'showMessage': {
type: 'toggle',
role: 'message-container'
},
'placeholder': {
type: 'attribute',
selector: 'input, textarea',
name: 'placeholder'
},
'validityClass': {
type: 'class',
selector: 'input, textarea'
}
}
};
// this is so we don't throw validation errors while typing
TextInputView.prototype.handleBlur = function () {
if (this.value) {
if (this.edited) this.hasBeenValid = true;
},
initialize: function (spec) {
this.tests = this.tests || spec.tests || [];
this.on('change:type', this.handleTypeChange, this);
this.handleBlur = this.handleBlur.bind(this);
this.handleInputChanged = this.handleInputChanged.bind(this);
this.startingValue = this.value;
this.on('change:valid change:value', this.reportToParent, this);
},
render: function () {
this.renderWithTemplate();
this.input = this.get('input') || this.get('textarea');
// switches out input for textarea if that's what we want
this.handleTypeChange();
this.initInputBindings();
},
props: {
value: 'string',
startingValue: 'string',
name: 'string',
type: ['string', true, 'text'],
placeholder: ['string', true, ''],
label: ['string', true, ''],
required: ['boolean', true, true],
directlyEdited: ['boolean', true, false],
shouldValidate: ['boolean', true, false],
message: ['string', true, ''],
requiredMessage: ['string', true, 'This fields is required.'],
validClass: ['string', true, 'input-valid'],
invalidClass: ['string', true, 'input-invalid']
},
derived: {
valid: {
deps: ['value'],
fn: function () {
return !this.runTests();
}
},
showMessage: {
deps: ['message', 'shouldValidate'],
fn: function () {
return this.shouldValidate && this.message;
}
},
changed: {
deps: ['value', 'startingValue'],
fn: function () {
return this.value !== this.startingValue;
}
},
validityClass: {
deps: ['valid', 'validClass', 'invalidClass', 'shouldValidate'],
fn: function () {
if (!this.shouldValidate) {
return '';
} else {
return this.valid ? this.validClass : this.invalidClass;
}
}
}
},
getErrorMessage: function () {
var message = '';
if (this.required && !this.value) {
return this.requiredMessage;
} else {
(this.tests || []).some(function (test) {
message = test.call(this, this.value) || '';
return message;
}, this);
return message;
}
},
handleTypeChange: function () {
if (this.type === 'textarea' && this.input.tagName.toLowerCase() !== 'textarea') {
var parent = this.input.parentNode;
var textarea = document.createElement('textarea');
parent.replaceChild(textarea, this.input);
this.input = textarea;
this._applyBindingsForKey('');
} else {
this.input.type = this.type;
}
},
handleInputChanged: function () {
if (document.activeElement === this.input) {
this.directlyEdited = true;
}
this.value = this.input.value;
},
handleBlur: function () {
if (this.value && this.changed) {
this.shouldValidate = true;
}
this.runTests();
},
beforeSubmit: function () {
// at the point where we've tried
// to submit, we want to validate
// everything from now on.
this.shouldValidate = true;
this.runTests();
},
runTests: function () {
var message = this.getErrorMessage();
if (!message && this.value && this.changed) {
// if it's ever been valid,
// we want to validate from now
// on.
this.shouldValidate = true;
}
this.message = message;
return message;
},
initInputBindings: function () {
this.input.addEventListener('blur', this.handleBlur, false);
this.input.addEventListener('input', this.handleInputChanged, false);
},
remove: function () {
this.input.removeEventListener('input', this.handleInputChanged, false);
this.input.removeEventListener('blur', this.handleBlur, false);
View.prototype.remove.apply(this, arguments);
},
reportToParent: function () {
if (this.parent) this.parent.update(this);
}
};
TextInputView.prototype.beforeSubmit = function () {
this.hasBeenValid = true;
this.edited = true;
this.runTests();
};
TextInputView.prototype.checkValid = function () {
var message = '';
if (this.required && !this.value) {
message = this.requiredMessage;
} else {
this.tests.some(function (test) {
message = test.call(this, this.value);
return message;
}, this);
}
this.valid = !message;
return message;
};
// runs tests and sets first failure as message
TextInputView.prototype.runTests = function () {
var message = this.checkValid();
if (!message) this.hasBeenValid = true;
this.setMessage(message);
};
// expose option to set type dynamically, this
// can be useful in mobile situations where you
// may want a password field to be visible
TextInputView.prototype.setInputType = function (type) {
if (type) this.type = type;
this.input.setAttribute('type', this.type);
};
// very `manual` render to avoid dependencies
TextInputView.prototype.render = function () {
// only allow this to be called once
if (this.rendered) return;
var newDom = domify(this.template);
var parent = this.el && this.el.parentNode;
if (parent) parent.replaceChild(newDom, this.el);
this.el = newDom;
this.input = this.el.querySelector('input');
if (this.type === 'textarea') {
var parent = this.input.parentNode;
var textarea = document.createElement('textarea');
parent.replaceChild(textarea, this.input);
this.input = textarea;
}
this.labelEl = this.el.querySelector('[role=label]');
this.messageContainer = this.el.querySelector('[role=message-container]');
this.messageEl = this.el.querySelector('[role=message-text]');
this.setInputType();
this.setMessage(this.message);
if (this.required) this.input.required = true;
this.input.setAttribute('placeholder', this.placeholder);
this.input.value = this.value;
this.labelEl.textContent = this.label;
this.rendered = true;
};
module.exports = TextInputView;
});
{
"name": "ampersand-input-view",
"description": "A view module for intelligently rendering and validating input. Works well with ampersand-form-view.",
"version": "0.3.0",
"version": "1.0.0",
"author": "Henrik Joreteg <henrik@andyet.net>",

@@ -10,4 +10,3 @@ "bugs": {

"dependencies": {
"ampersand-dom": "~0.1.1",
"domify": "~1.2.2"
"ampersand-view": "~5.1.5"
},

@@ -14,0 +13,0 @@ "homepage": "https://github.com/ampersandjs/ampersand-input-view",

@@ -8,6 +8,6 @@ # ampersand-input-view

- Automatically shows/hides error messages based on tests
- Will not show error messages pre-submit or it's ever had a valid value. This lets people tab-through a form without triggering a bunch of error message.
- Will not show error messages pre-submit or it's never had a valid value. This lets people tab-through a form without triggering a bunch of error messages.
- Live-validates to always report if in valid state, but only shows messages when sane to do so.
It only have one small dependency. Works well with ampersand apps, backbone apps or anything else, really.
It's built on [ampersand-view](ampersandjs/ampersand-view) so it can be extended with `extend` as you might expect.

@@ -14,0 +14,0 @@ ## install

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc