From: Charles McGarvey Date: Thu, 20 Oct 2011 01:23:55 +0000 (-0600) Subject: better client-side form validation; layout tweaks X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fchatty;a=commitdiff_plain;h=8a3ece46ef6bfeefaa27f53f199c285d0062f841 better client-side form validation; layout tweaks --- diff --git a/Makefile.PL b/Makefile.PL index c5c28ca..baf385c 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -19,6 +19,7 @@ requires 'Catalyst::Plugin::Session::Store::FastMmap'; requires 'Catalyst::Plugin::Session::State::Cookie'; requires 'Catalyst::Plugin::Unicode::Encoding'; requires 'Catalyst::Action::RenderView'; +requires 'JSON'; requires 'Moose'; requires 'namespace::autoclean'; requires 'Config::General'; # This should reflect the config file format you've chosen diff --git a/lib/Chatty/Controller/Root.pm b/lib/Chatty/Controller/Root.pm index ab0bfcd..f526d41 100644 --- a/lib/Chatty/Controller/Root.pm +++ b/lib/Chatty/Controller/Root.pm @@ -10,6 +10,8 @@ BEGIN { extends 'Catalyst::Controller' } # __PACKAGE__->config(namespace => ''); +use JSON 'encode_json'; + use Chatty::Form::Login; use Chatty::Form::Register; @@ -44,7 +46,9 @@ The root page (/) =cut sub index :Path :Args(0) { - my ( $self, $c ) = @_; + my ($self, $c) = @_; + $c->go('/chat/list') if ($c->user_exists); + $c->go('login'); } =head2 login @@ -68,13 +72,12 @@ sub login :Local :Args(0) { $c->change_session_id; my $user = $c->user->get('username'); $c->flash->{message} .= "Hi, $user! You are now logged in."; - $c->response->redirect($c->uri_for('/')); + $c->res->redirect($c->uri_for_action('index')); + return; } - else { - $c->flash->{error} = "Log-in failed! Try again, I guess."; - $c->response->redirect($c->uri_for('login')); - } - } + }; + $c->flash->{error} = "Log-in failed! Try again, I guess."; + $c->res->redirect($c->uri_for_action('login')); } =head2 logout @@ -89,7 +92,7 @@ sub logout :Local :Args(0) { $c->logout; $c->flash->{message} = "Goodbye! You have been logged out."; } - $c->response->redirect($c->uri_for('/')); + $c->res->redirect($c->uri_for_action('index')); } =head2 register @@ -109,12 +112,71 @@ sub register :Local :Args(0) { params => $c->req->params ); - return unless $self->register_form->is_valid; + if (!$self->register_form->is_valid) { + if ($c->req->method eq 'POST') { + $c->stash->{error} = "The form has a validation error. Try again..."; + } + return; + } $c->flash->{message} = "Registration complete. "; $c->forward('login'); } +=head2 register_validate + +Check whether or not a username is available. + +=cut + +sub register_validate :Local :Args(0) { + my ($self, $c) = @_; + + my $id = $c->req->param('fieldId'); + my $username = $c->req->param('fieldValue'); + + my $json_arr = []; + + if ($username) { + my $account = $c->model('DB::Account')->find({username => $username}); + if (!$account) { + $json_arr = ["$id", 1, "This username is available. Nice!"]; + } + else { + $json_arr = ["$id", 0, "This username is taken."]; + } + } + else { + $json_arr = ["$id", 0, "Invalid arguments to check script."]; + } + $c->res->content_type("application/json"); + $c->res->body(encode_json($json_arr)); +} + +=head2 access_denied + +Standard 403 error page + +=cut + +sub access_denied :Private { + my ($self, $c) = @_; + $c->res->body('Access denied.'); + $c->res->status(403); +} + +=head2 missing + +Standard 404 error page + +=cut + +sub missing :Private { + my ($self, $c) = @_; + $c->res->body('Page not found.'); + $c->res->status(404); +} + =head2 default Standard 404 error page @@ -123,8 +185,7 @@ Standard 404 error page sub default :Path { my ($self, $c) = @_; - $c->response->body('Page not found.'); - $c->response->status(404); + $c->detach('missing'); } =head2 end diff --git a/lib/Chatty/Form/Login.pm b/lib/Chatty/Form/Login.pm index aa25b1d..6e88169 100644 --- a/lib/Chatty/Form/Login.pm +++ b/lib/Chatty/Form/Login.pm @@ -2,11 +2,11 @@ package Chatty::Form::Login; use HTML::FormHandler::Moose; extends 'HTML::FormHandler'; +use namespace::autoclean; has_field 'username' => (label => 'Username', required => 1); has_field 'password' => (type => 'Password', required => 1); has_field 'submit' => (type => 'Submit', value => 'Login'); -no HTML::FormHandler::Moose; __PACKAGE__->meta->make_immutable; 1; diff --git a/lib/Chatty/Form/Register.pm b/lib/Chatty/Form/Register.pm index 9ae939e..bf0ec64 100644 --- a/lib/Chatty/Form/Register.pm +++ b/lib/Chatty/Form/Register.pm @@ -2,19 +2,19 @@ package Chatty::Form::Register; use HTML::FormHandler::Moose; extends 'HTML::FormHandler::Model::DBIC'; +use namespace::autoclean; has '+item_class' => (default => 'Account'); - -has_field 'username' => (label => 'User Nickname', required => 1, unique => 1); -has_field 'password' => (type => 'Password', required => 1); -has_field 'password_confirm' => (type => 'PasswordConf', required => 1); -has_field 'email' => (type => 'Email', label => 'Email address'); -has_field 'submit' => (type => 'Submit', value => 'Register'); - has '+unique_messages' => (default => sub { {username => 'Username is already registered'}; - }); +}); + +has_field 'username' => (input_class => 'validate[required,ajax[register_validate]]', label => 'Username', required => 1, unique => 1); +has_field 'password' => (input_class => 'validate[required]', type => 'Password', required => 1); +has_field 'password_confirm' => (input_class => 'validate[required,equals[password]]', type => 'PasswordConf', required => 1); +has_field 'email' => (input_class => 'validate[custom[email]]', type => 'Email', label => 'Email address'); +has_field 'submit' => (type => 'Submit', value => 'Register'); +has_field 'reset' => (type => 'Reset', value => 'Reset'); -no HTML::FormHandler::Moose; __PACKAGE__->meta->make_immutable; 1; diff --git a/root/static/css/common.css b/root/static/css/common.css index 023d30e..6474f5b 100644 --- a/root/static/css/common.css +++ b/root/static/css/common.css @@ -50,6 +50,7 @@ label { margin: 5px; padding: 10px; border: 2px solid black; + border-radius: 5px; background: white; } @@ -62,7 +63,7 @@ label { } #footer { - padding: 0; + padding: 10px 0 0 0; height: 31px; line-height: 31px; font-size: 0.8em; @@ -95,3 +96,8 @@ label { background: #c66; } +form div { + height: 32px; + line-height: 32px; +} + diff --git a/root/static/css/validationEngine.jquery.css b/root/static/css/validationEngine.jquery.css index 5500bee..b733ad3 100755 --- a/root/static/css/validationEngine.jquery.css +++ b/root/static/css/validationEngine.jquery.css @@ -25,7 +25,7 @@ position:relative; z-index:5001; color: #fff; - width: 150px; + width: 175px; font-family: tahoma; font-size: 11px; border: 2px solid #ddd; diff --git a/root/static/img/chat.png b/root/static/img/chat.png new file mode 100644 index 0000000..3e1af26 Binary files /dev/null and b/root/static/img/chat.png differ diff --git a/root/static/js/jquery.validationEngine-en.js b/root/static/js/jquery.validationEngine-en.js index 2a2b70f..2a3085a 100644 --- a/root/static/js/jquery.validationEngine-en.js +++ b/root/static/js/jquery.validationEngine-en.js @@ -6,162 +6,166 @@ $.validationEngineLanguage.allRules = { "required": { // Add your regex rules here, you can take telephone as an example "regex": "none", - "alertText": "* This field is required", - "alertTextCheckboxMultiple": "* Please select an option", - "alertTextCheckboxe": "* This checkbox is required", - "alertTextDateRange": "* Both date range fields are required" + "alertText": "This field is required.", + "alertTextCheckboxMultiple": "Please select an option.", + "alertTextCheckboxe": "This checkbox is required.", + "alertTextDateRange": "Both date range fields are required." }, "dateRange": { "regex": "none", - "alertText": "* Invalid ", + "alertText": "Invalid ", "alertText2": "Date Range" }, "dateTimeRange": { "regex": "none", - "alertText": "* Invalid ", + "alertText": "Invalid ", "alertText2": "Date Time Range" }, "minSize": { "regex": "none", - "alertText": "* Minimum ", + "alertText": "Minimum ", "alertText2": " characters allowed" }, "maxSize": { "regex": "none", - "alertText": "* Maximum ", + "alertText": "Maximum ", "alertText2": " characters allowed" }, "groupRequired": { "regex": "none", - "alertText": "* You must fill one of the following fields" + "alertText": "You must fill one of the following fields." }, "min": { "regex": "none", - "alertText": "* Minimum value is " + "alertText": "Minimum value is " }, "max": { "regex": "none", - "alertText": "* Maximum value is " + "alertText": "Maximum value is " }, "past": { "regex": "none", - "alertText": "* Date prior to " + "alertText": "Date prior to " }, "future": { "regex": "none", - "alertText": "* Date past " + "alertText": "Date past " }, "maxCheckbox": { "regex": "none", - "alertText": "* Maximum ", + "alertText": "Maximum ", "alertText2": " options allowed" }, "minCheckbox": { "regex": "none", - "alertText": "* Please select ", + "alertText": "Please select ", "alertText2": " options" }, "equals": { "regex": "none", - "alertText": "* Fields do not match" + "alertText": "Fields do not match." }, "phone": { // credit: jquery.h5validate.js / orefalo "regex": /^([\+][0-9]{1,3}[ \.\-])?([\(]{1}[0-9]{2,6}[\)])?([0-9 \.\-\/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/, - "alertText": "* Invalid phone number" + "alertText": "Invalid phone number." }, "email": { // Shamelessly lifted from Scott Gonzalez via the Bassistance Validation plugin http://projects.scottsplayground.com/email_address_validation/ "regex": /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i, - "alertText": "* Invalid email address" + "alertText": "Invalid email address." }, "integer": { "regex": /^[\-\+]?\d+$/, - "alertText": "* Not a valid integer" + "alertText": "Not a valid integer." }, "number": { // Number, including positive, negative, and floating decimal. credit: orefalo "regex": /^[\-\+]?(([0-9]+)([\.,]([0-9]+))?|([\.,]([0-9]+))?)$/, - "alertText": "* Invalid floating decimal number" + "alertText": "Invalid floating decimal number." }, "date": { "regex": /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/, - "alertText": "* Invalid date, must be in YYYY-MM-DD format" + "alertText": "Invalid date, must be in YYYY-MM-DD format." }, "ipv4": { "regex": /^((([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))[.]){3}(([0-1]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))$/, - "alertText": "* Invalid IP address" + "alertText": "Invalid IP address." }, "url": { "regex": /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i, - "alertText": "* Invalid URL" + "alertText": "Invalid URL." }, "onlyNumberSp": { "regex": /^[0-9\ ]+$/, - "alertText": "* Numbers only" + "alertText": "Numbers only." }, "onlyLetterSp": { "regex": /^[a-zA-Z\ \']+$/, - "alertText": "* Letters only" + "alertText": "Letters only." }, "onlyLetterNumber": { "regex": /^[0-9a-zA-Z]+$/, - "alertText": "* No special characters allowed" + "alertText": "No special characters allowed." }, // --- CUSTOM RULES -- Those are specific to the demos, they can be removed or changed to your likings "ajaxUserCall": { "url": "ajaxValidateFieldUser", // you may want to pass extra data on the ajax call "extraData": "name=eric", - "alertText": "* This user is already taken", - "alertTextLoad": "* Validating, please wait" + "alertText": "This user is already taken.", + "alertTextLoad": "Validating, please wait..." }, "ajaxUserCallPhp": { "url": "phpajax/ajaxValidateFieldUser.php", // you may want to pass extra data on the ajax call "extraData": "name=eric", // if you provide an "alertTextOk", it will show as a green prompt when the field validates - "alertTextOk": "* This username is available", - "alertText": "* This user is already taken", - "alertTextLoad": "* Validating, please wait" + "alertTextOk": "This username is available.", + "alertText": "This user is already taken.", + "alertTextLoad": "Validating, please wait..." }, "ajaxNameCall": { // remote json service location "url": "ajaxValidateFieldName", // error - "alertText": "* This name is already taken", + "alertText": "This name is already taken.", // if you provide an "alertTextOk", it will show as a green prompt when the field validates - "alertTextOk": "* This name is available", + "alertTextOk": "This name is available.", // speaks by itself - "alertTextLoad": "* Validating, please wait" + "alertTextLoad": "Validating, please wait..." }, "ajaxNameCallPhp": { // remote json service location "url": "phpajax/ajaxValidateFieldName.php", // error - "alertText": "* This name is already taken", + "alertText": "This name is already taken.", // speaks by itself - "alertTextLoad": "* Validating, please wait" + "alertTextLoad": "Validating, please wait..." }, "validate2fields": { - "alertText": "* Please input HELLO" + "alertText": "Please input HELLO." }, //tls warning:homegrown not fielded "dateFormat":{ "regex": /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$|^(?:(?:(?:0?[13578]|1[02])(\/|-)31)|(?:(?:0?[1,3-9]|1[0-2])(\/|-)(?:29|30)))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(?:(?:0?[1-9]|1[0-2])(\/|-)(?:0?[1-9]|1\d|2[0-8]))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(0?2(\/|-)29)(\/|-)(?:(?:0[48]00|[13579][26]00|[2468][048]00)|(?:\d\d)?(?:0[48]|[2468][048]|[13579][26]))$/, - "alertText": "* Invalid Date" + "alertText": "Invalid Date." }, //tls warning:homegrown not fielded "dateTimeFormat": { "regex": /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])\s+(1[012]|0?[1-9]){1}:(0?[1-5]|[0-6][0-9]){1}:(0?[0-6]|[0-6][0-9]){1}\s+(am|pm|AM|PM){1}$|^(?:(?:(?:0?[13578]|1[02])(\/|-)31)|(?:(?:0?[1,3-9]|1[0-2])(\/|-)(?:29|30)))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^((1[012]|0?[1-9]){1}\/(0?[1-9]|[12][0-9]|3[01]){1}\/\d{2,4}\s+(1[012]|0?[1-9]){1}:(0?[1-5]|[0-6][0-9]){1}:(0?[0-6]|[0-6][0-9]){1}\s+(am|pm|AM|PM){1})$/, - "alertText": "* Invalid Date or Date Format", + "alertText": "Invalid Date or Date Format.", "alertText2": "Expected Format: ", "alertText3": "mm/dd/yyyy hh:mm:ss AM|PM or ", "alertText4": "yyyy-mm-dd hh:mm:ss AM|PM" - } + }, + "register_validate": { + "url": "register_validate", + "alertTextLoad": "Validating, please wait..." + }, }; } }; $.validationEngineLanguage.newLang(); -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/root/tt/index.tt b/root/tt/index.tt deleted file mode 100644 index 06fce92..0000000 --- a/root/tt/index.tt +++ /dev/null @@ -1,11 +0,0 @@ -

Chatty - Toy chat app written in Perl/Catalyst

-

-This app lets you chat with other registered chatters. Cool! Once you are -registered and logged-in, you will be allowed to join a chat room and get -chatty. -

-[% IF ! c.user_exists -%] -[% INCLUDE login.tt -%] -[% ELSE %] -Log Out -[% END -%] diff --git a/root/tt/login.tt b/root/tt/login.tt index 3720e26..74eefa5 100644 --- a/root/tt/login.tt +++ b/root/tt/login.tt @@ -1,5 +1,10 @@ [% META title = 'Log In' -%] -

Log In

+

+This app lets you chat with other registered chatters. Cool! Once you are +registered and logged-in, you will be allowed to join a chat room and get +chatty. +

+

Log In

[% IF ! c.user_exists -%] [% form.render %] -

Register

+

Register

-If you don't already have an account, go register! +If you don't already have an account, you should +go register now!

[% ELSE -%]

You are already logged in.

diff --git a/root/tt/register.tt b/root/tt/register.tt index c73e161..250654b 100644 --- a/root/tt/register.tt +++ b/root/tt/register.tt @@ -1,36 +1,12 @@ [% META title = 'Register' -%] [% BLOCK js -%] -$('#form').validationEngine(); -$('#form button[type=reset]').click(function() { - $('#form').validationEngine('hideAll'); +$('form').validationEngine(); +$('form input[type=reset]').click(function() { + $('form').validationEngine('hideAll'); }); [% END -%] -

Register

+

Register

[% IF ! c.user_exists -%] - [% form.render %] [% ELSE -%]

You are already registered and logged in. There is no need to register again.

diff --git a/root/tt/wrapper.tt b/root/tt/wrapper.tt index 6fad196..f4d497f 100644 --- a/root/tt/wrapper.tt +++ b/root/tt/wrapper.tt @@ -21,8 +21,13 @@ [% END -%]
+
+[% IF c.user_exists -%] + Log Out +[% END -%] +
+ Chat! [% content -%] -