* 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

@ -49,6 +49,8 @@ exports.moduleInfo = {
TL12 - User1
TL13 - User2
TL16 - Error / Information area
Footer - Viewing
HM1 - Menu (prev/next/etc.)
@ -73,7 +75,9 @@ var MCICodeIds = {
ViewCount : 8,
HashTags : 9,
MessageID : 10,
ReplyToMsgID : 11
ReplyToMsgID : 11,
ErrorMsg : 16,
},
ViewModeFooter : {
MsgNum : 6,
@ -540,6 +544,8 @@ function FullScreenEditorModule(options) {
self.createInitialViews(mciData, function viewsCreated(err) {
// :TODO: Can probably be replaced with @systemMethod:validateUserNameExists when the framework is in
// place - if this is for existing usernames else validate spec
/*
self.viewControllers.header.on('leave', function headerViewLeave(view) {
if(2 === view.id) { // "to" field
@ -551,7 +557,7 @@ function FullScreenEditorModule(options) {
}
});
}
});
});*/
cb(err);
});
@ -762,6 +768,26 @@ function FullScreenEditorModule(options) {
};
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) {
self.switchToBody();
},
@ -880,7 +906,3 @@ FullScreenEditorModule.prototype.mciReady = function(mciData, cb) {
this.mciReadyHandler(mciData, cb);
//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() {
var rawData = MaskEditTextView.super_.prototype.getData.call(this);
if(!rawData || 0 === rawData.length) {
return rawData;
}
var data = '';
assert(rawData.length <= this.patternArray.length);

View File

@ -1,12 +1,21 @@
var user = require('./user.js');
var Config = require('./config.js').config;
exports.validateNonEmpty = validateNonEmpty;
exports.validateMessageSubject = validateMessageSubject;
exports.validateUserNameAvail = validateUserNameAvail;
exports.validateEmailAvail = validateEmailAvail;
exports.validateBirthdate = validateBirthdate;
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) {
if(data.length < Config.users.usernameMin) {
cb(new Error('Username too short'));
@ -34,6 +43,24 @@ function validateUserNameAvail(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) {
if(err) {
cb(new Error('Internal system error'));

View File

@ -73,7 +73,17 @@ function ViewController(options) {
case 'accept' :
if(self.focusedView && self.focusedView.submit) {
// :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 {
self.nextFocus();
}
@ -313,6 +323,27 @@ function ViewController(options) {
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);
@ -384,56 +415,20 @@ ViewController.prototype.switchFocus = function(id) {
var self = this;
var focusedView = self.focusedView;
function performSwitch() {
self.attachClientEvents();
self.validateView(focusedView, function validated(err, newFocusedViewId) {
if(err) {
var newFocusedView = self.getView(newFocusedViewId) || focusedView;
self.setViewFocusWithEvents(newFocusedView, true);
} else {
self.attachClientEvents();
// remove from old
self.setViewFocusWithEvents(focusedView, false);
// remove from old
self.setViewFocusWithEvents(focusedView, false);
// set to new
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);
*/
// set to new
self.setViewFocusWithEvents(self.getView(id), true);
}
});
};
ViewController.prototype.nextFocus = function() {

View File

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