ampersand-input-view
Advanced tools
Comparing version 0.3.0 to 1.0.0
@@ -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 |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
9031
1
170
0
1
+ Addedampersand-view@~5.1.5
+ Addedampersand-class-extend@1.0.2(transitive)
+ Addedampersand-collection-view@1.0.11(transitive)
+ Addedampersand-dom@1.5.0(transitive)
+ Addedampersand-state@4.2.7(transitive)
+ Addedampersand-version@1.0.2(transitive)
+ Addedampersand-view@5.1.11(transitive)
+ Addedarray-next@0.0.1(transitive)
+ Addedbackbone-events-standalone@0.2.10.2.7(transitive)
+ Addedclosest@0.0.1(transitive)
+ Addedcomponent-classes@1.2.6(transitive)
+ Addedcomponent-event@0.1.4(transitive)
+ Addedcomponent-indexof@0.0.3(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addeddelegate-events@1.1.1(transitive)
+ Addeddom-bindings@1.0.1(transitive)
+ Addeddomify@1.4.2(transitive)
+ Addedevents-mixin@1.3.0(transitive)
+ Addedfind-root@0.1.2(transitive)
+ Addedget-object-path@0.0.2(transitive)
+ Addedis-array@1.0.1(transitive)
+ Addedisarray@0.0.1(transitive)
+ Addedkey-tree-store@0.1.20.3.01.3.0(transitive)
+ Addedlodash._baseassign@3.2.0(transitive)
+ Addedlodash._basecopy@3.0.1(transitive)
+ Addedlodash._bindcallback@3.0.1(transitive)
+ Addedlodash._createassigner@3.1.1(transitive)
+ Addedlodash._getnative@3.9.1(transitive)
+ Addedlodash._isiterateecall@3.0.9(transitive)
+ Addedlodash.assign@3.2.0(transitive)
+ Addedlodash.isarguments@3.1.0(transitive)
+ Addedlodash.isarray@3.0.4(transitive)
+ Addedlodash.keys@3.1.2(transitive)
+ Addedlodash.restparam@3.6.1(transitive)
+ Addedmatches-selector@0.0.11.2.0(transitive)
+ Addedreadable-stream@1.0.34(transitive)
+ Addedstring_decoder@0.10.31(transitive)
+ Addedthrough2@0.6.5(transitive)
+ Addedunderscore@1.13.7(transitive)
+ Addedxtend@4.0.2(transitive)
- Removedampersand-dom@~0.1.1
- Removeddomify@~1.2.2
- Removedampersand-dom@0.1.2(transitive)
- Removeddeep-equal@0.2.2(transitive)
- Removeddefined@0.0.0(transitive)
- Removeddomify@1.2.2(transitive)
- Removedglob@3.2.11(transitive)
- Removedlru-cache@2.7.3(transitive)
- Removedminimatch@0.3.0(transitive)
- Removedobject-inspect@0.4.0(transitive)
- Removedresumer@0.0.0(transitive)
- Removedsigmund@1.0.1(transitive)
- Removedtape@2.13.4(transitive)
- Removedthrough@2.3.8(transitive)
- Removedupdate@0.1.0(transitive)