* Validation framework functional

* Use validation for FSE, NUA, etc.
* Switch to nua.js from apply.js (MenuModule + validation)
This commit is contained in:
Bryan Ashby 2015-12-12 15:52:56 -07:00
parent e4cfb2b92e
commit da5d88d20d
7 changed files with 121 additions and 75 deletions

View File

@ -48,6 +48,8 @@ exports.moduleInfo = {
TL12 - User1 TL12 - User1
TL13 - User2 TL13 - User2
TL16 - Error / Information area
Footer - Viewing Footer - Viewing
HM1 - Menu (prev/next/etc.) HM1 - Menu (prev/next/etc.)
@ -73,7 +75,9 @@ var MCICodeIds = {
ViewCount : 8, ViewCount : 8,
HashTags : 9, HashTags : 9,
MessageID : 10, MessageID : 10,
ReplyToMsgID : 11 ReplyToMsgID : 11,
ErrorMsg : 16,
}, },
ViewModeFooter : { ViewModeFooter : {
MsgNum : 6, MsgNum : 6,
@ -540,6 +544,8 @@ function FullScreenEditorModule(options) {
self.createInitialViews(mciData, function viewsCreated(err) { self.createInitialViews(mciData, function viewsCreated(err) {
// :TODO: Can probably be replaced with @systemMethod:validateUserNameExists when the framework is in // :TODO: Can probably be replaced with @systemMethod:validateUserNameExists when the framework is in
// place - if this is for existing usernames else validate spec // place - if this is for existing usernames else validate spec
/*
self.viewControllers.header.on('leave', function headerViewLeave(view) { self.viewControllers.header.on('leave', function headerViewLeave(view) {
if(2 === view.id) { // "to" field if(2 === view.id) { // "to" field
@ -551,7 +557,7 @@ function FullScreenEditorModule(options) {
} }
}); });
} }
}); });*/
cb(err); cb(err);
}); });
@ -762,6 +768,26 @@ function FullScreenEditorModule(options) {
}; };
this.menuMethods = { this.menuMethods = {
//
// Validation stuff
//
viewValidationListener : function(err, cb) {
var errMsgView = self.viewControllers.header.getView(MCICodeIds.ViewModeHeader.ErrorMsg);
var newFocusViewId;
if(errMsgView) {
if(err) {
errMsgView.setText(err.message);
if(MCICodeIds.ViewModeHeader.Subject === err.view.getId()) {
// :TODO: for "area" mode, should probably just bail if this is emtpy (e.g. cancel)
}
} else {
errMsgView.clearText();
}
}
cb(newFocusViewId);
},
headerSubmit : function(formData, extraArgs) { headerSubmit : function(formData, extraArgs) {
self.switchToBody(); self.switchToBody();
}, },
@ -880,7 +906,3 @@ FullScreenEditorModule.prototype.mciReady = function(mciData, cb) {
this.mciReadyHandler(mciData, cb); this.mciReadyHandler(mciData, cb);
//this['mciReadyHandler' + _.capitalize(this.editorType)](mciData); //this['mciReadyHandler' + _.capitalize(this.editorType)](mciData);
}; };
FullScreenEditorModule.prototype.validateToUserName = function(un, cb) {
cb(null); // note: to be implemented by sub classes
};

View File

@ -178,6 +178,11 @@ MaskEditTextView.prototype.setPropertyValue = function(propName, value) {
MaskEditTextView.prototype.getData = function() { MaskEditTextView.prototype.getData = function() {
var rawData = MaskEditTextView.super_.prototype.getData.call(this); var rawData = MaskEditTextView.super_.prototype.getData.call(this);
if(!rawData || 0 === rawData.length) {
return rawData;
}
var data = ''; var data = '';
assert(rawData.length <= this.patternArray.length); assert(rawData.length <= this.patternArray.length);

View File

@ -1,12 +1,21 @@
var user = require('./user.js'); var user = require('./user.js');
var Config = require('./config.js').config; var Config = require('./config.js').config;
exports.validateNonEmpty = validateNonEmpty;
exports.validateMessageSubject = validateMessageSubject;
exports.validateUserNameAvail = validateUserNameAvail; exports.validateUserNameAvail = validateUserNameAvail;
exports.validateEmailAvail = validateEmailAvail; exports.validateEmailAvail = validateEmailAvail;
exports.validateBirthdate = validateBirthdate; exports.validateBirthdate = validateBirthdate;
exports.validatePasswordSpec = validatePasswordSpec; exports.validatePasswordSpec = validatePasswordSpec;
function validateNonEmpty(data, cb) {
cb(data && data.length > 0 ? null : new Error('Field cannot be empty'));
}
function validateMessageSubject(data, cb) {
cb(data && data.length > 1 ? null : new Error('Subject too short'));
};
function validateUserNameAvail(data, cb) { function validateUserNameAvail(data, cb) {
if(data.length < Config.users.usernameMin) { if(data.length < Config.users.usernameMin) {
cb(new Error('Username too short')); cb(new Error('Username too short'));
@ -34,6 +43,24 @@ function validateUserNameAvail(data, cb) {
} }
function validateEmailAvail(data, cb) { function validateEmailAvail(data, cb) {
//
// This particular method allows empty data - e.g. no email entered
//
if(!data || 0 === data.length) {
return cb(null);
}
//
// Otherwise, it must be a valid email. We'll be pretty lose here, like
// the HTML5 spec.
//
// See http://stackoverflow.com/questions/7786058/find-the-regex-used-by-html5-forms-for-validation
//
var emailRegExp = /[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9-]+(.[a-z0-9-]+)*/;
if(!emailRegExp.test(data)) {
return cb(new Error('Invalid email address'));
}
user.getUserIdsWithProperty('email_address', data, function userIdsWithEmail(err, uids) { user.getUserIdsWithProperty('email_address', data, function userIdsWithEmail(err, uids) {
if(err) { if(err) {
cb(new Error('Internal system error')); cb(new Error('Internal system error'));

View File

@ -73,7 +73,17 @@ function ViewController(options) {
case 'accept' : case 'accept' :
if(self.focusedView && self.focusedView.submit) { if(self.focusedView && self.focusedView.submit) {
// :TODO: need to do validation here!!! // :TODO: need to do validation here!!!
self.submitForm(key); var focusedView = self.focusedView;
self.validateView(focusedView, function validated(err, newFocusedViewId) {
console.log(err)
if(err) {
var newFocusedView = self.getView(newFocusedViewId) || focusedView;
self.setViewFocusWithEvents(newFocusedView, true);
} else {
self.submitForm(key);
}
});
//self.submitForm(key);
} else { } else {
self.nextFocus(); self.nextFocus();
} }
@ -313,6 +323,27 @@ function ViewController(options) {
view.setFocus(focused); view.setFocus(focused);
}; };
this.validateView = function(view, cb) {
if(view && _.isFunction(view.validate)) {
view.validate(view.getData(), function validateResult(err) {
var viewValidationListener = self.client.currentMenuModule.menuMethods.viewValidationListener;
if(_.isFunction(viewValidationListener)) {
if(err) {
err.view = view; // pass along the view that failed
}
viewValidationListener(err, function validationComplete(newViewFocusId) {
cb(err, newViewFocusId);
});
} else {
cb(err);
}
});
} else {
cb(null);
}
};
} }
util.inherits(ViewController, events.EventEmitter); util.inherits(ViewController, events.EventEmitter);
@ -384,56 +415,20 @@ ViewController.prototype.switchFocus = function(id) {
var self = this; var self = this;
var focusedView = self.focusedView; var focusedView = self.focusedView;
function performSwitch() { self.validateView(focusedView, function validated(err, newFocusedViewId) {
self.attachClientEvents(); if(err) {
var newFocusedView = self.getView(newFocusedViewId) || focusedView;
self.setViewFocusWithEvents(newFocusedView, true);
} else {
self.attachClientEvents();
// remove from old // remove from old
self.setViewFocusWithEvents(focusedView, false); self.setViewFocusWithEvents(focusedView, false);
// set to new // set to new
self.setViewFocusWithEvents(self.getView(id), true); self.setViewFocusWithEvents(self.getView(id), true);
}; }
});
if(focusedView && focusedView.validate) {
focusedView.validate(focusedView.getData(), function validated(err) {
if(_.isFunction(self.client.currentMenuModule.menuMethods.viewValidationListener)) {
if(err) {
err.view = focusedView;
}
self.client.currentMenuModule.menuMethods.viewValidationListener(err, function validateComplete(newFocusId) {
if(err) {
// :TODO: switchFocus() really needs a cb --
var newFocusView;
if(newFocusId) {
newFocusView = self.getView(newFocusId) || focusedView;
}
self.setViewFocusWithEvents(newFocusView, true);
} else {
performSwitch();
}
});
} else {
if(!err) {
performSwitch();
}
}
});
} else {
performSwitch();
}
/*
this.attachClientEvents();
// remove from old
this.setViewFocusWithEvents(this.focusedView, false);
// set to new
this.setViewFocusWithEvents(this.getView(id), true);
*/
}; };
ViewController.prototype.nextFocus = function() { ViewController.prototype.nextFocus = function() {

View File

@ -153,6 +153,7 @@
ET2: { ET2: {
argName: realName argName: realName
maxLength: 32 maxLength: 32
validate: @systemMethod:validateNonEmpty
} }
MET3: { MET3: {
argName: birthdate argName: birthdate
@ -163,10 +164,12 @@
argName: sex argName: sex
maskPattern: A maskPattern: A
textStyle: upper textStyle: upper
validate: @systemMethod:validateNonEmpty
} }
ET5: { ET5: {
argName: location argName: location
maxLength: 32 maxLength: 32
validate: @systemMethod:validateNonEmpty
} }
ET6: { ET6: {
argName: affils argName: affils
@ -239,23 +242,28 @@
focus: true focus: true
argName: username argName: username
maxLength: @config:users.usernameMax maxLength: @config:users.usernameMax
validate: @systemMethod:validateUserNameAvail
} }
ET2: { ET2: {
argName: realName argName: realName
maxLength: 32 maxLength: 32
validate: @systemMethod:validateNonEmpty
} }
MET3: { MET3: {
argName: birthdate argName: birthdate
maskPattern: "####/##/##" maskPattern: "####/##/##"
validate: @systemMethod:validateBirthdate
} }
ME4: { ME4: {
argName: sex argName: sex
maskPattern: A maskPattern: A
textStyle: upper textStyle: upper
validate: @systemMethod:validateNonEmpty
} }
ET5: { ET5: {
argName: location argName: location
maxLength: 32 maxLength: 32
validate: @systemMethod:validateNonEmpty
} }
ET6: { ET6: {
argName: affils argName: affils
@ -264,6 +272,7 @@
ET7: { ET7: {
argName: email argName: email
maxLength: 255 maxLength: 255
validate: @systemMethod:validateEmailAvail
} }
ET8: { ET8: {
argName: web argName: web
@ -273,11 +282,13 @@
argName: password argName: password
password: true password: true
maxLength: @config:users.passwordMax maxLength: @config:users.passwordMax
validate: @systemMethod:validatePasswordSpec
} }
ET10: { ET10: {
argName: passwordConfirm argName: passwordConfirm
password: true password: true
maxLength: @config:users.passwordMax maxLength: @config:users.passwordMax
validate: @method:validatePassConfirmMatch
} }
TM12: { TM12: {
argName: submission argName: submission
@ -362,6 +373,7 @@
maxLength: 72 maxLength: 72
submit: true submit: true
text: New user feedback text: New user feedback
validate: @systemMethod:validateMessageSubject
} }
} }
submit: { submit: {
@ -921,11 +933,13 @@
ET2: { ET2: {
argName: to argName: to
focus: true focus: true
validate: @systemMethod:validateNonEmpty
} }
ET3: { ET3: {
argName: subject argName: subject
maxLength: 72 maxLength: 72
submit: true submit: true
validate: @systemMethod:validateNonEmpty
} }
TL4: { TL4: {
// :TODO: this is for RE: line (NYI) // :TODO: this is for RE: line (NYI)
@ -1068,11 +1082,13 @@
argName: to argName: to
focus: true focus: true
text: All text: All
validate: @systemMethod:validateNonEmpty
} }
ET3: { ET3: {
argName: subject argName: subject
maxLength: 72 maxLength: 72
submit: true submit: true
validate: @systemMethod:validateNonEmpty
// :TODO: Validate -> close/cancel if empty // :TODO: Validate -> close/cancel if empty
} }
} }

View File

@ -64,22 +64,3 @@ AreaPostFSEModule.prototype.enter = function(client) {
AreaPostFSEModule.super_.prototype.enter.call(this, client); AreaPostFSEModule.super_.prototype.enter.call(this, client);
}; };
AreaPostFSEModule.prototype.validateToUserName = function(un, cb) {
var self = this;
if(!un) {
cb(new Error('Username must be supplied!'));
return;
}
if(!self.isLocalEmail()) {
cb(null);
return;
}
user.getUserIdAndName(un, function uidAndName(err, userId, userName) {
self.toUserId = userId;
cb(err);
});
};