From da5d88d20da537f81cffa9e6e7d2ee5f19564edc Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Sat, 12 Dec 2015 15:52:56 -0700 Subject: [PATCH] * Validation framework functional * Use validation for FSE, NUA, etc. * Switch to nua.js from apply.js (MenuModule + validation) --- core/fse.js | 34 +++++-- core/mask_edit_text_view.js | 5 + core/system_view_validate.js | 29 +++++- core/view_controller.js | 93 +++++++++---------- mods/menu.hjson | 16 ++++ mods/msg_area_post_fse.js | 19 ---- mods/themes/luciano_blocktronics/MSGEHDR.ANS | Bin 1578 -> 1587 bytes 7 files changed, 121 insertions(+), 75 deletions(-) diff --git a/core/fse.js b/core/fse.js index c49f81f4..ad7d6ab8 100644 --- a/core/fse.js +++ b/core/fse.js @@ -48,6 +48,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 -}; diff --git a/core/mask_edit_text_view.js b/core/mask_edit_text_view.js index 36a90eb5..a25a2553 100644 --- a/core/mask_edit_text_view.js +++ b/core/mask_edit_text_view.js @@ -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); diff --git a/core/system_view_validate.js b/core/system_view_validate.js index 169f481d..5600e2b6 100644 --- a/core/system_view_validate.js +++ b/core/system_view_validate.js @@ -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')); diff --git a/core/view_controller.js b/core/view_controller.js index 02b13b83..d4dce737 100644 --- a/core/view_controller.js +++ b/core/view_controller.js @@ -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() { diff --git a/mods/menu.hjson b/mods/menu.hjson index b392771f..8d310d8d 100644 --- a/mods/menu.hjson +++ b/mods/menu.hjson @@ -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 } } diff --git a/mods/msg_area_post_fse.js b/mods/msg_area_post_fse.js index d6034e51..f5439004 100644 --- a/mods/msg_area_post_fse.js +++ b/mods/msg_area_post_fse.js @@ -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); - }); -}; \ No newline at end of file diff --git a/mods/themes/luciano_blocktronics/MSGEHDR.ANS b/mods/themes/luciano_blocktronics/MSGEHDR.ANS index 2687f5ee2e515b74f61561b40513e2f658926a56..70c36bbec8fa26db7e78beeab495d37646ce6bd7 100644 GIT binary patch delta 75 zcmZ3*vzcc?rZ_Lx?c37P2D#GF=BCcl(T3K>2D$fd^G>W=!^k^Xi}3-F