Skip to content

Instantly share code, notes, and snippets.

@codepunkt
Last active August 29, 2015 14:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save codepunkt/ab2409b11a71802c3da5 to your computer and use it in GitHub Desktop.
Save codepunkt/ab2409b11a71802c3da5 to your computer and use it in GitHub Desktop.
Async form validation in ampersand
// Example of async form validation in ampersand
// Has default `ampersand-input-view` clientside validation.
// If clientside validation passes, async validation is called 1 sec after input to check if given username exists
var FormView = require('ampersand-form-view');
var InputView = require('ampersand-input-view');
var debounce = require('amp-debounce');
var extend = require('amp-extend');
var AsyncInputView = InputView.extend({
bindings: extend({}, InputView.prototype.bindings, {
showMessage: {
type: 'toggle',
hook: 'message-text'
}
}),
props: {
asyncMs: [ 'number', true, 500 ],
asyncValid: [ 'boolean', true, true ],
pending: [ 'boolean', true, false ],
pendingClass: [ 'string', true, 'input-pending' ],
},
derived: {
valid: {
deps: [ 'inputValue', 'asyncValid' ],
fn: function () {
return this.asyncValid && !this.runTests();
}
},
validityClass: {
deps: [ 'pending', 'pendingClass', 'valid', 'validClass', 'invalidClass', 'shouldValidate' ],
fn: function () {
if (!this.shouldValidate) {
return '';
} else {
if (this.pending) {
return this.pendingClass;
} else {
return this.valid ? this.validClass : this.invalidClass;
}
}
}
}
},
abortAsyncTest: function () {},
runAsyncTest: function () {
if (!this.asyncTest || this.message || this.value === this.asyncValue) {
return;
}
if (this.pending) {
this.abortAsyncTest();
} else {
this.pending = true;
}
this.asyncTest.call(this, this.value, function (message) {
this.pending = false;
this.asyncValue = this.value;
if (message) {
this.shouldValidate = true;
this.asyncValid = false;
this.message = message;
}
}.bind(this));
},
initialize: function (spec) {
InputView.prototype.initialize.call(this, spec);
spec = spec || {};
this.asyncTest = spec.asyncTest || undefined;
this.abortAsyncTest = spec.abortAsyncTest || this.abortAsyncTest;
this.runAsyncTest = debounce(this.runAsyncTest, this.asyncMs);
},
handleChange: function (e) {
InputView.prototype.handleChange.call(this, e);
this.runAsyncTest();
},
handleInputChanged: function (e) {
InputView.prototype.handleInputChanged.call(this, e);
this.asyncValid = true;
this.runAsyncTest();
},
getErrorMessage: function () {
this.asyncValue = null;
return InputView.prototype.getErrorMessage.call(this);
}
});
var userNames = [ 'user', 'asdf', 'user123', 'testuser', 'myuser' ];
var MyForm = FormView.extend({
fields: function () {
return [
new AsyncInputView({
label: 'Username',
name: 'username',
placeholder: 'Choose a username',
tests: [
function (value) {
if (value.length < 3) {
return 'Username has to be at least 3 characters long';
}
}
],
// THIS IS YOUR ASYNC REQUEST
// Instead of setTimeout it can be a $.ajax or xhr call, or anything
// that accepts value and callback.
asyncTest: function(value, callback) {
window.setTimeout(function () {
callback(userNames.indexOf(value) > -1 ? 'Username is already taken' : '');
}, 250);
},
// ASYNC TEST IS PERFORMED AFTER THIS MANY MS
// When there is no clientside validation error. Otherwise, clientside validation error is shown.
asyncMs: 1000
})
];
}
});
var myForm = new MyForm();
document.body.appendChild(myForm.el);
var FormView=require("ampersand-form-view"),InputView=require("ampersand-input-view"),debounce=require("amp-debounce"),extend=require("amp-extend"),AsyncInputView=InputView.extend({bindings:extend({},InputView.prototype.bindings,{showMessage:{type:"toggle",hook:"message-text"}}),props:{asyncMs:["number",!0,500],asyncValid:["boolean",!0,!0],pending:["boolean",!0,!1],pendingClass:["string",!0,"input-pending"]},derived:{valid:{deps:["inputValue","asyncValid"],fn:function(){return this.asyncValid&&!this.runTests()}},validityClass:{deps:["pending","pendingClass","valid","validClass","invalidClass","shouldValidate"],fn:function(){return this.shouldValidate?this.pending?this.pendingClass:this.valid?this.validClass:this.invalidClass:""}}},abortAsyncTest:function(){},runAsyncTest:function(){this.asyncTest&&!this.message&&this.value!==this.asyncValue&&(this.pending?this.abortAsyncTest():this.pending=!0,this.asyncTest.call(this,this.value,function(a){this.pending=!1,this.asyncValue=this.value,a&&(this.shouldValidate=!0,this.asyncValid=!1,this.message=a)}.bind(this)))},initialize:function(a){InputView.prototype.initialize.call(this,a),a=a||{},this.asyncTest=a.asyncTest||void 0,this.abortAsyncTest=a.abortAsyncTest||this.abortAsyncTest,this.runAsyncTest=debounce(this.runAsyncTest,this.asyncMs)},handleChange:function(a){InputView.prototype.handleChange.call(this,a),this.runAsyncTest()},handleInputChanged:function(a){InputView.prototype.handleInputChanged.call(this,a),this.asyncValid=!0,this.runAsyncTest()},getErrorMessage:function(){return this.asyncValue=null,InputView.prototype.getErrorMessage.call(this)}}),userNames=["user","asdf","user123","testuser","myuser"],MyForm=FormView.extend({fields:function(){return[new AsyncInputView({label:"Username",name:"username",placeholder:"Choose a username",tests:[function(a){return a.length<3?"Username has to be at least 3 characters long":void 0}],asyncTest:function(a,b){window.setTimeout(function(){b(userNames.indexOf(a)>-1?"Username is already taken":"")},250)},asyncMs:1e3})]}}),myForm=new MyForm;document.body.appendChild(myForm.el);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment