From 490b94ab4051adf93abf16a4ed34efb923d6e8fc Mon Sep 17 00:00:00 2001 From: Paul Seamons Date: Fri, 14 Dec 2007 00:00:00 +0000 Subject: [PATCH] CGI::Ex 2.22 --- Changes | 11 + MANIFEST | 11 +- META.yml | 23 +- lib/CGI/Ex.pm | 2 +- lib/CGI/Ex/App.pm | 5 +- lib/CGI/Ex/Auth.pm | 5 +- lib/CGI/Ex/Conf.pm | 3 +- lib/CGI/Ex/Die.pm | 2 +- lib/CGI/Ex/Dump.pm | 2 +- lib/CGI/Ex/Fill.pm | 2 +- lib/CGI/Ex/JSONDump.pm | 2 +- lib/CGI/Ex/Template.pm | 2 +- lib/CGI/Ex/Validate.pm | 538 +++++---- lib/CGI/Ex/validate.js | 1639 ++++++++++++--------------- samples/app/cgi_ex_2.cgi | 2 +- samples/devel/memory_app.pl | 391 +++++++ samples/validate_js_0_tests.html | 430 +++++++ samples/validate_js_1_onsubmit.html | 207 ++++ samples/validate_js_2_onchange.html | 232 ++++ samples/validate_js_yaml_1.html | 206 ++++ samples/validate_js_yaml_2.html | 119 ++ samples/validate_js_yaml_3.html | 74 ++ 22 files changed, 2776 insertions(+), 1132 deletions(-) create mode 100644 samples/devel/memory_app.pl create mode 100644 samples/validate_js_0_tests.html create mode 100644 samples/validate_js_1_onsubmit.html create mode 100644 samples/validate_js_2_onchange.html create mode 100644 samples/validate_js_yaml_1.html create mode 100644 samples/validate_js_yaml_2.html create mode 100644 samples/validate_js_yaml_3.html diff --git a/Changes b/Changes index 72d2875..d051859 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,14 @@ +2.22 + 2007-12-14 + * Allow for no errors with a username of "0" + * Run hash_form hook before others so dump_history shows it in order of use + * Fix Validate error with passing in the field name + * Simplify and modernize validate.js + * Add onevent: change, blur and submit types. + * Add hooks and better overriding to valiate.js + * Cleanup Validate.pm pod + * Be sure Conf json read requires JSON + 2.21 2007-10-18 * Add logout_hook to Auth diff --git a/MANIFEST b/MANIFEST index c800d2f..a9184f9 100644 --- a/MANIFEST +++ b/MANIFEST @@ -29,11 +29,14 @@ samples/benchmark/bench_jsondump.pl samples/benchmark/bench_validation.pl samples/devel/dprof_conf.d samples/devel/dprof_validation.d -samples/generate_js.pl +samples/devel/memory_app.pl samples/index.cgi -samples/js_validate_1.html -samples/js_validate_2.html -samples/js_validate_3.html +samples/validate_js_0_tests.html +samples/validate_js_1_onsubmit.html +samples/validate_js_2_onchange.html +samples/validate_js_yaml_1.html +samples/validate_js_yaml_2.html +samples/validate_js_yaml_3.html samples/yaml_js_1.html samples/yaml_js_2.html samples/yaml_js_3.html diff --git a/META.yml b/META.yml index 1d67b91..91afcc0 100644 --- a/META.yml +++ b/META.yml @@ -1,14 +1,11 @@ ---- #YAML:1.0 -name: CGI-Ex -version: 2.21 -abstract: CGI utility suite - makes powerful application writing fun and easy -license: ~ -generated_by: ExtUtils::MakeMaker version 6.36 -distribution_type: module -requires: +# http://module-build.sourceforge.net/META-spec.html +#XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# +name: CGI-Ex +version: 2.22 +version_from: lib/CGI/Ex.pm +installdirs: site +requires: Template::Alloy: 1.004 -meta-spec: - url: http://module-build.sourceforge.net/META-spec-v1.2.html - version: 1.2 -author: - - Paul Seamons + +distribution_type: module +generated_by: ExtUtils::MakeMaker version 6.30_01 diff --git a/lib/CGI/Ex.pm b/lib/CGI/Ex.pm index bf4bc02..f8b666c 100644 --- a/lib/CGI/Ex.pm +++ b/lib/CGI/Ex.pm @@ -24,7 +24,7 @@ use vars qw($VERSION use base qw(Exporter); BEGIN { - $VERSION = '2.21'; + $VERSION = '2.22'; $PREFERRED_CGI_MODULE ||= 'CGI'; @EXPORT = (); @EXPORT_OK = qw(get_form diff --git a/lib/CGI/Ex/App.pm b/lib/CGI/Ex/App.pm index 4c93364..d03f90a 100644 --- a/lib/CGI/Ex/App.pm +++ b/lib/CGI/Ex/App.pm @@ -13,7 +13,7 @@ BEGIN { eval { use Scalar::Util }; } -our $VERSION = '2.21'; +our $VERSION = '2.22'; sub new { my $class = shift || croak "Usage: ".__PACKAGE__."->new"; @@ -247,9 +247,9 @@ sub prepared_print { my $self = shift; my $step = shift; + my $hash_form = $self->run_hook('hash_form', $step) || {}; my $hash_base = $self->run_hook('hash_base', $step) || {}; my $hash_comm = $self->run_hook('hash_common', $step) || {}; - my $hash_form = $self->run_hook('hash_form', $step) || {}; my $hash_fill = $self->run_hook('hash_fill', $step) || {}; my $hash_swap = $self->run_hook('hash_swap', $step) || {}; my $hash_errs = $self->run_hook('hash_errors', $step) || {}; @@ -650,7 +650,6 @@ sub step_by_path_index { my $i = shift || 0; my $ref = $self->path; return '' if $i < 0; -# return $self->default_step if $i > $#$ref; return $ref->[$i]; } diff --git a/lib/CGI/Ex/Auth.pm b/lib/CGI/Ex/Auth.pm index 2dd895d..33e2a30 100644 --- a/lib/CGI/Ex/Auth.pm +++ b/lib/CGI/Ex/Auth.pm @@ -18,7 +18,7 @@ use MIME::Base64 qw(encode_base64 decode_base64); use Digest::MD5 qw(md5_hex); use CGI::Ex; -$VERSION = '2.21'; +$VERSION = '2.22'; ###----------------------------------------------------------------### @@ -74,6 +74,7 @@ sub get_valid_auth { next if ! defined $hash->{$key}; last if ! $is_form && $had_form_data; # if form info was passed in - we must use it only $had_form_data = 1 if $is_form; + next if ! length $hash->{$key}; ### if it looks like a bare username (as in they didn't have javascript) - add in other items my $data; @@ -403,7 +404,7 @@ sub login_hash_common { sub verify_token { my $self = shift; my $args = shift; - my $token = delete $args->{'token'} || die "Missing token"; + my $token = delete $args->{'token'}; die "Missing token" if ! length $token; my $data = $self->new_auth_data({token => $token, %$args}); my $meth; diff --git a/lib/CGI/Ex/Conf.pm b/lib/CGI/Ex/Conf.pm index f635f20..365ece2 100644 --- a/lib/CGI/Ex/Conf.pm +++ b/lib/CGI/Ex/Conf.pm @@ -29,7 +29,7 @@ use vars qw($VERSION ); @EXPORT_OK = qw(conf_read conf_write in_cache); -$VERSION = '2.21'; +$VERSION = '2.22'; $DEFAULT_EXT = 'conf'; @@ -262,6 +262,7 @@ sub read_handler_json { open (IN, $file) || die "Couldn't open $file: $!"; CORE::read(IN, my $text, -s $file); close IN; + require JSON; return scalar JSON::jsonToObj($text); } diff --git a/lib/CGI/Ex/Die.pm b/lib/CGI/Ex/Die.pm index 4f3aa72..afeab9b 100644 --- a/lib/CGI/Ex/Die.pm +++ b/lib/CGI/Ex/Die.pm @@ -23,7 +23,7 @@ use CGI::Ex; use CGI::Ex::Dump qw(debug ctrace dex_html); BEGIN { - $VERSION = '2.21'; + $VERSION = '2.22'; $SHOW_TRACE = 0 if ! defined $SHOW_TRACE; $IGNORE_EVAL = 0 if ! defined $IGNORE_EVAL; $EXTENDED_ERRORS = 1 if ! defined $EXTENDED_ERRORS; diff --git a/lib/CGI/Ex/Dump.pm b/lib/CGI/Ex/Dump.pm index 796b83e..6065272 100644 --- a/lib/CGI/Ex/Dump.pm +++ b/lib/CGI/Ex/Dump.pm @@ -17,7 +17,7 @@ use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION use strict; use Exporter; -$VERSION = '2.21'; +$VERSION = '2.22'; @ISA = qw(Exporter); @EXPORT = qw(dex dex_warn dex_text dex_html ctrace dex_trace); @EXPORT_OK = qw(dex dex_warn dex_text dex_html ctrace dex_trace debug); diff --git a/lib/CGI/Ex/Fill.pm b/lib/CGI/Ex/Fill.pm index cbf38de..1bc90f3 100644 --- a/lib/CGI/Ex/Fill.pm +++ b/lib/CGI/Ex/Fill.pm @@ -24,7 +24,7 @@ use vars qw($VERSION use base qw(Exporter); BEGIN { - $VERSION = '2.21'; + $VERSION = '2.22'; @EXPORT = qw(form_fill); @EXPORT_OK = qw(fill form_fill html_escape get_tagval_by_key swap_tagval_by_key); }; diff --git a/lib/CGI/Ex/JSONDump.pm b/lib/CGI/Ex/JSONDump.pm index 0c2852c..6fdd2d0 100644 --- a/lib/CGI/Ex/JSONDump.pm +++ b/lib/CGI/Ex/JSONDump.pm @@ -17,7 +17,7 @@ use strict; use base qw(Exporter); BEGIN { - $VERSION = '2.21'; + $VERSION = '2.22'; @EXPORT = qw(JSONDump); @EXPORT_OK = @EXPORT; diff --git a/lib/CGI/Ex/Template.pm b/lib/CGI/Ex/Template.pm index 57ffa0e..c3acbbc 100644 --- a/lib/CGI/Ex/Template.pm +++ b/lib/CGI/Ex/Template.pm @@ -25,7 +25,7 @@ use vars qw($VERSION $VOBJS ); -$VERSION = '2.21'; +$VERSION = '2.22'; ### install true symbol table aliases that can be localized *QR_PRIVATE = *Template::Alloy::QR_PRIVATE; diff --git a/lib/CGI/Ex/Validate.pm b/lib/CGI/Ex/Validate.pm index 1948872..3720195 100644 --- a/lib/CGI/Ex/Validate.pm +++ b/lib/CGI/Ex/Validate.pm @@ -2,7 +2,7 @@ package CGI::Ex::Validate; =head1 NAME -CGI::Ex::Validate - another form validator - but it does javascript in parallel +CGI::Ex::Validate - The "Just Right" form validator with javascript in parallel =cut @@ -22,7 +22,7 @@ use vars qw($VERSION @UNSUPPORTED_BROWSERS ); -$VERSION = '2.21'; +$VERSION = '2.22'; $DEFAULT_EXT = 'val'; $QR_EXTRA = qr/^(\w+_error|as_(array|string|hash)_\w+|no_\w+)/; @@ -115,7 +115,7 @@ sub validate { foreach my $field (@field_keys) { die "Found nonhashref value for field $field" if ref($val_hash->{$field}) ne 'HASH'; if (defined $val_hash->{$field}->{'field'}) { - push @$fields, $val_hash->{$field}->{'field'}; + push @$fields, $val_hash->{$field}; } else { push @$fields, { %{$val_hash->{$field}}, field => $field }; } @@ -1102,14 +1102,14 @@ __END__ email => { required => 1, max_len => 100, + type => 'email', }, email2 => { - validate_if => 'email', - equals => 'email', + equals => 'email', }, }; - ### ordered + ### ordered (only onevent submit needs order) my $val_hash = { 'group order' => [qw(username email email2)], username => {required => 1, max_len => 30}, @@ -1128,48 +1128,56 @@ __END__ required => 1, max_len => 100, }, { - field => 'email2', - validate_if => 'email', - equals => 'email', + field => 'email2', + equals => 'email', }], }; my $vob = CGI::Ex::Validate->new; my $errobj = $vob->validate($form, $val_hash); - # OR # - my $errobj = $vob->validate($form, "/somefile/somewhere.val"); # import config using yaml file - # OR # - my $errobj = $vob->validate($form, "/somefile/somewhere.pl"); # import config using perl file - # OR # - my $errobj = $vob->validate($form, "--- # a yaml document\n"); # import config using yaml str + # OR # + # import config using any type CGI::Ex::Conf supports + my $errobj = $vob->validate($form, "/somefile/somewhere.val"); if ($errobj) { my $error_heading = $errobj->as_string; # OR "$errobj"; my $error_list = $errobj->as_array; # ordered list of what when wrong my $error_hash = $errobj->as_hash; # hash of arrayrefs of errors } else { - # form passed validation + # the form passed validation } ### will add an error for any form key not found in $val_hash my $vob = CGI::Ex::Validate->new({no_extra_keys => 1}); my $errobj = $vob->validate($form, $val_hash); + + my $js_uri_path = '/js/'; # static or dynamic URI path to find CGI/Ex/validate.js + my $form_name = "the_form"; # name of the form to attach javascript to + my $javascript = $vob->generate_js($val_hash, $form_name, $js_uri_path); + + =head1 DESCRIPTION CGI::Ex::Validate is one of many validation modules. It aims to have all of the basic data validation functions, avoid adding all of the millions of possible types, while still giving the capability for the -developer to add their own types. +developer to add their own types for the rare cases that the basic +ones don't suffice. Generally anything more than basic validation +probably needs programmatic or data based validation. -It also has full support for providing the same validation in javascript. -It provides methods for attaching the javascript to existing forms. +CGI::Ex::Validate also has full support for providing the same +validation in javascript. It provides methods for attaching the +javascript to existing forms. This ability is tightly integrated into +CGI::Ex::App, but it should be easy to add validation just about +anywhere using any type of controller. As opposed to other kitchen sync validation modules, CGI::Ex::Validate offers the simple types of validation, and makes it easy to add your -own custom types. +own custom types. Asside from custom and custom_js, all validation +markup is declarative. =head1 METHODS @@ -1275,7 +1283,7 @@ The $JS_URI_PATH of "/cgi-bin/js" could contain the following: use strict; use CGI::Ex; - ### path_info should contain something like /CGI/Ex/yaml_load.js + ### path_info should contain something like /CGI/Ex/validate.js my $info = $ENV{PATH_INFO} || ''; die "Invalid path" if $info !~ m|^(/\w+)+.js$|; $info =~ s|^/+||; @@ -1284,29 +1292,31 @@ The $JS_URI_PATH of "/cgi-bin/js" could contain the following: exit; The print_js method in CGI::Ex is designed to cache the javascript in -the browser (caching is suggested as they are medium sized files). +the browser. =item C<-Ecgix> -Returns a CGI::Ex object. Used internally. +Returns a CGI::Ex object. Used internally if a CGI object is +passed to validate rather than a straight form hash. =back =head1 VALIDATION HASH -The validation hash may be passed as a hashref or as a -filename, or as a YAML document string. If it is a filename, it will -be translated into a hash using the %EXT_HANDLER for the extension on -the file. If there is no extension, it will use $DEFAULT_EXT as a -default. CGI::Ex::Conf is used for the reading of files. +The validation hash may be passed as a hashref or as a filename, or as +a YAML document string. Experience has shown it to be better +programming to pass in a hashref. If the validation "hash" is a +filename or a YAML string, it will be translated into a hash using +CGI::Ex::Conf. -Keys matching the regex m/^(general|group)\s+(\w+)$/ are reserved and -are counted as GROUP OPTIONS. Other keys (if any, should be field names -that need validation). +Keys matching the regex m/^(general|group)\s+(\w+)$/ such as "group +onevent" are reserved and are counted as GROUP OPTIONS. Other keys +(if any, should be field names that need validation). -If the GROUP OPTION 'group validate_if' is set, the validation will only -be validated if the conditions are met. If 'group validate_if' is not -specified, then the validation will proceed. +If the GROUP OPTION 'group validate_if' is set, the validation will +only be validated if the conditions of the validate_if are met. If +'group validate_if' is not specified, then the validation will +proceed. See the validate_if VALIDATION type for more information. Each of the items listed in the validation will be validated. The validation order is determined in one of three ways: @@ -1359,6 +1369,16 @@ tested. 'group order' => [qw(zip OR postalcode state OR region)], +At this time, only "group onevent" submit works with this option. Using +OR is deprecated. Instead you should use min_in_set or max_in_set. + + 'zip' => { + max_in_set: '1 of zip, postalcode', + }, + 'state' => { + max_in_set: '1 of state, region', + }, + Each individual field validation hashref will operate on the field contained in the 'field' key. This key may also be a regular expression in the form of 'm/somepattern/'. If a regular expression is used, all keys @@ -1380,103 +1400,109 @@ types listed in VALIDATION TYPES. This section lists the available validation types. Multiple instances of the same type may be used for some validation types by adding a -number to the type (ie match, match2, match232, match_94). Multiple -instances are validated in sorted order. Types that allow multiple -values are: - - compare - custom - equals - match - max_in_set - min_in_set - replace - required_if - sql - type - validate_if +number to the type (ie match, match2, match232). Multiple instances +are validated in sorted order. Types that allow multiple values are: +compare, custom, custom_js, equals, enum, match, required_if, sql, +type, validate_if, and replace (replace is a MODIFICATION TYPE). =over 4 -=item C - -If validate_if is specified, the field will only be validated -if the conditions are met. Works in JS. - - validate_if => {field => 'name', required => 1, max_len => 30} - # Will only validate if the field "name" is present and is less than 30 chars. - - validate_if => 'name', - # SAME as - validate_if => {field => 'name', required => 1}, +=item C - validate_if => '! name', - # SAME as - validate_if => {field => 'name', max_in_set => '0 of name'}, +Allows for custom comparisons. Available types are +>, <, >=, <=, !=, ==, gt, lt, ge, le, ne, and eq. Comparisons +also work in the JS. - validate_if => {field => 'country', compare => "eq US"}, - # only if country's value is equal to US + { + field => 'my_number', + match => 'm/^\d+$/', + compare1 => '> 100', + compare2 => '< 255', + compare3 => '!= 150', + } - validate_if => {field => 'country', compare => "ne US"}, - # if country doesn't equal US +=item C - validate_if => {field => 'password', match => 'm/^md5\([a-z0-9]{20}\)$/'}, - # if password looks like md5(12345678901234567890) +Custom value - not available in JS. Allows for extra programming types. +May be either a boolean value predetermined before calling validate, or may be +a coderef that will be called during validation. If coderef is called, it will +be passed the field name, the form value for that name, and a reference to the +field validation hash. If the custom type returns false the element fails +validation and an error is added. { - field => 'm/^(\w+)_pass/', - validate_if => '$1_user', - required => 1, + field => 'username', + custom => sub { + my ($key, $val, $type, $field_val_hash) = @_; + # do something here + return 0; + }, } - # will validate foo_pass only if foo_user was present. -The validate_if may also contain an arrayref of validation items. So that -multiple checks can be run. They will be run in order. validate_if will -return true only if all options returned true. - - validate_if => ['email', 'phone', 'fax'] +=item C -Optionally, if validate_if is an arrayref, it may contain the word -'OR' as a special keyword. If the item preceding 'OR' fails validation -the item after 'OR' will be tested instead. If the item preceding 'OR' -passes validation the item after 'OR' will not be tested. +Custom value - only available in JS. Allows for extra programming types. +May be a javascript function (if fully declared in javascript), a string containing +a javascript function (that will be eval'ed into a real function), +a boolean value pre-determined before calling validate, or may be +section of javascript that will be eval'ed (the last value of +the eval'ed javascript will determine if validation passed). A false response indicates +the value did not pass validation. A true response indicates that it did. See +the samples/validate_js_0_tests.html page for a sample of usages. - validate_if => [qw(zip OR postalcode)], + { + field => 'date', + required => 1, + match => 'm|^\d\d\d\d/\d\d/\d\d$|', + match_error => 'Please enter date in YYYY/MM/DD format', + custom_js => "function (args) { + var t=new Date(); + var y=t.getYear()+1900; + var m=t.getMonth() + 1; + var d=t.getDate(); + if (m<10) m = '0'+m; + if (d<10) d = '0'+d; + (args.value > ''+y+'/'+m+'/'+d) ? 1 : 0; + }", + custom_js_error => 'The date was not greater than today.', + } -=item C +=item C -Requires the form field if the condition is satisfied. The conditions -available are the same as for validate_if. This is somewhat the same -as saying: +Allows for checking whether an item matches a set of options. In perl +the value may be passed as an arrayref. In the conf or in perl the +value may be passed of the options joined with ||. - validate_if => 'some_condition', - required => 1 + { + field => 'password_type', + enum => 'plaintext||crypt||md5', # OR enum => [qw(plaintext crypt md5)], + } - required_if => 'some_condition', +=item C -If a regex is used for the field name, the required_if -field will have any match patterns swapped in. +Allows for comparison of two form elements. Can have an optional !. { - field => 'm/^(\w+)_pass/', - required_if => '$1_user', + field => 'password', + equals => 'password_verify', + }, + { + field => 'domain1', + equals => '!domain2', # make sure the fields are not the same } -This example would require the "foobar_pass" field to be set -if the "foobar_user" field was passed. - -=item C - -Requires the form field to have some value. If the field is not present, -no other checks will be run. +=item C -=item C and C +Allows for regular expression comparison. Multiple matches may +be concatenated with ||. Available in JS. -Allows for specifying the maximum number of form elements passed. -max_values defaults to 1 (You must explicitly set it higher -to allow more than one item by any given name). + { + field => 'my_ip', + match => 'm/^\d{1,3}(\.\d{1,3})3$/', + match_2 => '!/^0\./ || !/^192\./', + } -=item C and C +=item C and C Somewhat like min_values and max_values except that you specify the fields that participate in the count. Also - entries that are not @@ -1493,31 +1519,7 @@ be placed after the number for human readability. validate_if => {field => 'whatever', max_in_set => '0 of whatever'}, # only run validation if there were zero occurrences of whatever -=item C - -Allows for checking whether an item matches a set of options. In perl -the value may be passed as an arrayref. In the conf or in perl the -value may be passed of the options joined with ||. - - { - field => 'password_type', - enum => 'plaintext||crypt||md5', # OR enum => [qw(plaintext crypt md5)], - } - -=item C - -Allows for comparison of two form elements. Can have an optional !. - - { - field => 'password', - equals => 'password_verify', - }, - { - field => 'domain1', - equals => '!domain2', # make sure the fields are not the same - } - -=item C +=item C Allows for check on the length of fields @@ -1527,31 +1529,39 @@ Allows for check on the length of fields max_len => 100, } -=item C +=item C and C -Allows for regular expression comparison. Multiple matches may -be concatenated with ||. Available in JS. +Allows for specifying the maximum number of form elements passed. +max_values defaults to 1 (You must explicitly set it higher +to allow more than one item by any given name). - { - field => 'my_ip', - match => 'm/^\d{1,3}(\.\d{1,3})3$/', - match_2 => '!/^0\./ || !/^192\./', - } +=item C -=item C +Requires the form field to have some value. If the field is not present, +no other checks will be run. -Allows for custom comparisons. Available types are ->, <, >=, <=, !=, ==, gt, lt, ge, le, ne, and eq. Comparisons -also work in the JS. +=item C + +Requires the form field if the condition is satisfied. The conditions +available are the same as for validate_if. This is somewhat the same +as saying: + + validate_if => 'some_condition', + required => 1 + + required_if => 'some_condition', + +If a regex is used for the field name, the required_if +field will have any match patterns swapped in. { - field => 'my_number', - match => 'm/^\d+$/', - compare1 => '> 100', - compare2 => '< 255', - compare3 => '!= 150', + field => 'm/^(\w+)_pass/', + required_if => '$1_user', } +This example would require the "foobar_pass" field to be set +if the "foobar_user" field was passed. + =item C SQL query based - not available in JS. The database handle will be looked @@ -1566,60 +1576,62 @@ $self->{dbh} is a coderef - they will be called and should return a dbh. # sql_db_type => 'foo', # will look for a dbh under $self->{dbhs}->{foo} } -=item C +=item C -Custom value - not available in JS. Allows for extra programming types. -May be either a boolean value predetermined before calling validate, or may be -a coderef that will be called during validation. If coderef is called, it will -be passed the field name, the form value for that name, and a reference to the -field validation hash. If the custom type returns false the element fails -validation and an error is added. +Allows for more strict type checking. Currently supported types +include CC (credit card), EMAIL, DOMAIN, IP, URL. Other types will be +added upon request provided we can add a perl and a javascript +version. { - field => 'username', - custom => sub { - my ($key, $val, $type, $field_val_hash) = @_; - # do something here - return 0; - }, + field => 'credit_card', + type => 'CC', } -=item C +=item C -Custom value - only available in JS. Allows for extra programming types. -May be either a boolean value pre-determined before calling validate, or may be -section of javascript that will be eval'ed. The last value (return value) of -the eval'ed javascript will determine if validation passed. A false value indicates -the value did not pass validation. A true value indicates that it did. See -the t/samples/js_validate_3.html page for a sample of usage. +If validate_if is specified, the field will only be validated +if the conditions are met. Works in JS. - { - field => 'date', - required => 1, - match => 'm|^\d\d\d\d/\d\d/\d\d$|', - match_error => 'Please enter date in YYYY/MM/DD format', - custom_js => " - var t=new Date(); - var y=t.getYear()+1900; - var m=t.getMonth() + 1; - var d=t.getDate(); - if (m<10) m = '0'+m; - if (d<10) d = '0'+d; - (value > ''+y+'/'+m+'/'+d) ? 1 : 0; - ", - custom_js_error => 'The date was not greater than today.', - } + validate_if => {field => 'name', required => 1, max_len => 30} + # Will only validate if the field "name" is present and is less than 30 chars. -=item C + validate_if => 'name', + # SAME as + validate_if => {field => 'name', required => 1}, -Allows for more strict type checking. Currently supported types -include CC (credit card). Other types will be added upon request provided -we can add a perl and a javascript version. + validate_if => '! name', + # SAME as + validate_if => {field => 'name', max_in_set => '0 of name'}, + + validate_if => {field => 'country', compare => "eq US"}, + # only if country's value is equal to US + + validate_if => {field => 'country', compare => "ne US"}, + # if country doesn't equal US + + validate_if => {field => 'password', match => 'm/^md5\([a-z0-9]{20}\)$/'}, + # if password looks like md5(12345678901234567890) { - field => 'credit_card', - type => 'CC', + field => 'm/^(\w+)_pass/', + validate_if => '$1_user', + required => 1, } + # will validate foo_pass only if foo_user was present. + +The validate_if may also contain an arrayref of validation items. So that +multiple checks can be run. They will be run in order. validate_if will +return true only if all options returned true. + + validate_if => ['email', 'phone', 'fax'] + +Optionally, if validate_if is an arrayref, it may contain the word +'OR' as a special keyword. If the item preceding 'OR' fails validation +the item after 'OR' will be tested instead. If the item preceding 'OR' +passes validation the item after 'OR' will not be tested. + + validate_if => [qw(zip OR postalcode)], =back @@ -1847,11 +1859,11 @@ used to have the concept of validation groups - these were not commonly used so support has been deprecated as of the 2.10 release). Group options will also be looked for in the Validate object ($self) and can be set when instantiating the object ($self->{raise_error} is -equivalent to $valhash->{'group raise_error'}). The current know -options are: +equivalent to $valhash->{'group raise_error'}). Options may also be set globally before calling validate by -populating the %DEFAULT_OPTIONS global hash. +populating the %DEFAULT_OPTIONS global hash. However, only the options +set properly in the $valhash will be passed to the javascript. =over 4 @@ -1963,32 +1975,79 @@ a string that will be pre-pended on to the error string. If as_hash_join has been set to a true value, as_hash_footer may be set to a string that will be postpended on to the error string. +=item C + +Defaults to {submit => 1}. This controls when the javascript validation +will take place. May be passed any or all or submit, change, or blur. +Multiple events may be passed in the hash. + + 'group onevent' => {submit => 1, change => 1}', + +A comma separated string of types may also be passed: + + 'group onevent' => 'submit,change,blur', + +Currently, change and blur will not work for dynamically matched +field names such as 'm/\w+/'. Support will be added. + +=item C + +Defaults document.validate_set_hook which defaults to nothing. If +"group set_hook" or document.validate_set_hook are set to a function, +they will be passed the key name of a form element that had a +validation error and the error that will be set. If a true value is +returned, then validate will not also the inline error. If no value +or false is returned (default) the validate will continue setting the +inline error. This gives full control over setting inline +errors. samples/validate_js_2_onchange.html has a good example of +using these hooks. + + 'group set_hook' => "function (key, val, val_hash, form) { + alert("Setting error to field "+key); + }", + +The document.validate_set_hook option is probably the better option to use, +as it helps to separate display functionality out into your html templates +rather than storing too much html logic in your CGI. + +=item C + +Similar to set_hook, but called when inline error is cleared. Its +corresponding default is document.validate_clear_hook. The clear hook +is also sampled in samples/validate_js_2_onchange.html + + 'group clear_hook' => "function (key, val_hash, form) { + alert("Clear error on field "+key); + }", + =item C -If set to true, the javascript validation will not attempt to generate inline -errors. Default is true. Inline errors are independent of confirm and alert -errors. +If set to true, the javascript validation will not attempt to generate +inline errors when the only "group onevent" type is "submit". Default +is true. Inline errors are independent of confirm and alert errors. - 'general no_inline' => 1, + 'group no_inline' => 1, =item C -If set to true, the javascript validation will try to use an alert instead -of a confirm to inform the user of errors. Alert and confirm are independent +If set to true, the javascript validation will try to use an alert +instead of a confirm to inform the user of errors when one of the +"group onevent" types is "submit". Alert and confirm are independent or inline errors. Default is false. - 'general no_confirm' => 1, + 'group no_confirm' => 1, =item C If set to true, the javascript validation will not show an alert box when errors occur. Default is false. This option only comes into -play if no_confirm is also set. This option is independent of inline -errors. Although it is possible to turn off all errors by setting -no_inline, no_confirm, and no_alert all to 1, it is suggested that at -least one of the error reporting facilities is left on. +play if no_confirm is also set. This option is only in effect if +"group onevent" includes "submit". This option is independent of +inline errors. Although it is possible to turn off all errors by +setting no_inline, no_confirm, and no_alert all to 1, it is suggested +that at least one of the error reporting facilities is left on. - 'general no_alert' => 1, + 'group no_alert' => 1, =back @@ -2009,9 +2068,31 @@ validation will be read in using CGI::Ex::Conf::read_handler_html. All inline html validation must be written in yaml. -It is anticipated that the html will contain something like either of the +It is anticipated that the html will contain something like one of the following examples: + + + +Prior to the realization of JSON, YAML was part of the method +for introducing validation into the script. + -Alternately we can use element attributes: +Alternately, CGI/Ex/validate.js can parse the YAML from html +form element attributes:
@@ -2056,10 +2138,11 @@ Alternately we can use element attributes: if (document.check_form) document.check_form('my_form_name'); -The read_handler_html from CGI::Ex::Conf will find either of these -types of validation. +The read_handler_html from CGI::Ex::Conf will find the YAML types +of validation. The JSON type is what would be generated by default +when the validation is specified in Perl. -If inline errors are asked for, each error that occurs will attempt +If inline errors are enabled (default), each error that occurs will attempt to find an html element with its name as the id. For example, if the field "username" failed validation and created a "username_error", the javascript would set the html of @@ -2074,11 +2157,34 @@ from the server side as well. If the javascript fails for some reason, the form should still be able to submit as normal (fail gracefully). -If the confirm option is used, the errors will be displayed to the user. -If they choose OK they will be able to try and fix the errors. If they -choose cancel, the form will submit anyway and will rely on the server -to do the validation. This is for fail safety to make sure that if the -javascript didn't validate correctly, the user can still submit the data. +Additionally, there are two hooks that are called when ever an inline +error is set or cleared. The following hooks are used in +samples/validate_js_2_onchange.html. + + document.validate_set_hook = function (key, val, val_hash, form) { + document.getElementById(key+'_img').innerHTML + = '!'; + document.getElementById(key+'_row').style.background + = '#ffdddd'; + }; + + document.validate_clear_hook = function (key, val_hash, form) { + document.getElementById(key+'_img').innerHTML + = '+'; + document.getElementById(key+'_row').style.background + = '#ddffdd'; + }; + +These hooks can also be set as "group clear_hook" and "group set_hook" +which are defined further above. + +If the confirm option is used ("group onevent" includes submit and +"group no_confirm" is false), the errors will be displayed to the +user. If they choose OK they will be able to try and fix the errors. +If they choose cancel, the form will submit anyway and will rely on +the server to do the validation. This is for fail safety to make sure +that if the javascript didn't validate correctly, the user can still +submit the data. =head1 THANKS @@ -2091,6 +2197,6 @@ This module may be distributed under the same terms as Perl itself. =head1 AUTHOR -Paul Seamons +Paul Seamons =cut diff --git a/lib/CGI/Ex/validate.js b/lib/CGI/Ex/validate.js index 40b4c04..72cca57 100644 --- a/lib/CGI/Ex/validate.js +++ b/lib/CGI/Ex/validate.js @@ -1,1066 +1,933 @@ -/**----------------------------------------------------------------*** -* Copyright 2007 - Paul Seamons * -* Distributed under the Perl Artistic License without warranty * -* Based upon CGI/Ex/Validate.pm v1.14 from Perl * -* For instructions on usage, see perldoc of CGI::Ex::Validate * -***----------------------------------------------------------------**/ -// $Revision: 1.42 $ - -function Validate () { - this.error = vob_error; - this.validate = vob_validate; - this.check_conditional = vob_check_conditional; - this.filter_types = vob_filter_types; - this.add_error = vob_add_error; - this.validate_buddy = vob_validate_buddy; - this.check_type = vob_check_type; - this.get_form_value = vob_get_form_value; -} +// Copyright 2007 - Paul Seamons - $Revision: 1.62 $ +// Distributed under the Perl Artistic License without warranty +// See perldoc CGI::Ex::Validate for usage + +var v_did_inline = {}; function ValidateError (errors, extra) { this.errors = errors; this.extra = extra; - this.as_string = eob_as_string; this.as_array = eob_as_array; this.as_hash = eob_as_hash; - this.get_error_text = eob_get_error_text; - this.first_field = eob_first_field; + this.first_field = eob_first_field; } -///----------------------------------------------------------------/// +// -function vob_error (err) { - alert (err); -} - -function vob_validate (form, val_hash) { - if (typeof(val_hash) == 'string') { - if (! document.yaml_load) - return this.error("Cannot parse yaml string - document.yaml_load is not loaded"); - val_hash = document.yaml_load(val_hash); - } +function v_error (err) { alert (err); return 1 } - var ERRORS = new Array (); - var EXTRA = new Array (); - // var USED_GROUPS = new Array(); - - // distinguishing between associative and index based arrays is harder than in perl - if (! val_hash.length) val_hash = new Array(val_hash); - for (var i = 0; i < val_hash.length; i ++) { - var group_val = val_hash[i]; - if (typeof(group_val) != 'object' || group_val.length) return this.error("Validation groups must be a hash"); - var title = group_val['group title']; - var validate_if = group_val['group validate_if']; - - if (validate_if && ! this.check_conditional(form, validate_if)) continue; - // USED_GROUPS.push(group_val); - - /// if the validation items were not passed as an arrayref - /// look for a group order and then fail back to the keys of the group - var fields = group_val['group fields']; - var order = new Array(); - for (var key in group_val) { - if (key == 'extend') continue; // Protoype Array() fix - order[order.length] = key; - } - order = order.sort(); - if (fields) { - if (typeof(fields) != 'object' || ! fields.length) - return this.error("'group fields' must be a non-empty array"); - } else { - fields = new Array(); - var _order = (group_val['group order']) ? group_val['group order'] : order; - if (typeof(_order) != 'object' || ! _order.length) - return this.error("'group order' must be a non-empty array"); - for (var j = 0; j < _order.length; j ++) { - var field = _order[j]; - if (field.match('^(group|general)\\s')) continue; - var field_val = group_val[field]; - if (! field_val) { - if (field == 'OR') field_val = 'OR'; - else return this.error('No element found in group for '+field); - } - if (typeof(field_val) == 'object' && ! field_val['field']) field_val['field'] = field; - fields[fields.length] = field_val; - } - } +function v_clean_val_hash (val_hash) { + if (typeof(val_hash) != 'object') return {error: v_error("Validation must be an associative array (hash)")}; - /// check which fields have been used - var found = new Array(); - for (var j = 0; j < fields.length; j ++) { - var field_val = fields[j]; - var field = field_val['field']; - if (! field) return this.error("Missing field key in validation"); - // if (found[field]) return this.error('Duplicate order found for '+field+' in group order or fields'); - found[field] = 1; - } + var order = []; + for (var key in val_hash) { + if (key == 'extend') continue; // Protoype Array() + if (key.match(/^general\s/)) { + var new_key = key.replace(/^general\s+/, 'group '); + val_hash[new_key] = val_hash[key]; + delete(val_hash[key]); + key = new_key; + } + order.push(key); + } + order = order.sort(); - /// add any remaining fields from the order - for (var j = 0; j < order.length; j ++) { - var field = order[j]; - if (found[field] || field.match('^(group|general)\\s')) continue; - var field_val = group_val[field]; - if (typeof(field_val) != 'object' || field_val.length) return this.error('Found a non-hash value on field '+field); - if (! field_val['field']) field_val['field'] = field; - fields[fields.length] = field_val; - } + var f = val_hash['group set_hook']; + if (f && typeof(f) == 'string') val_hash['group set_hook'] = eval(f); + f = val_hash['group clear_hook']; + if (f && typeof(f) == 'string') val_hash['group clear_hook'] = eval(f); - /// now lets do the validation - var is_found = 1; - var errors = new Array(); - var hold_error; - - for (var j = 0; j < fields.length; j ++) { - var ref = fields[j]; - if (typeof(ref) != 'object' && ref == 'OR') { - if (is_found) j ++; - is_found = 1; - continue; - } - is_found = 1; - if (! ref['field']) return this.error("Missing field key during normal validation"); - var err = this.validate_buddy(form, ref['field'], ref); - - /// test the error - if errors occur allow for OR - if OR fails use errors from first fail - if (err.length) { - if (j <= fields.length && typeof(fields[j + 1] != 'object') && fields[j + 1] == 'OR') { - hold_error = err; - } else { - if (hold_error) err = hold_error; - for (var k = 0; k < err.length; k ++) errors[errors.length] = err[k]; - hold_error = ''; - } - } else { - hold_error = ''; - } - } + var fields = val_hash['group fields']; + if (fields) { + if (typeof(fields) != 'object' || ! fields.length) + return {error:v_error("'group fields' must be a non-empty array")}; + } else { + fields = []; + var _order = (val_hash['group order']) ? val_hash['group order'] : order; + if (typeof(_order) != 'object' || ! _order.length) + return {error:v_error("'group order' must be a non-empty array")}; + for (var i = 0; i < _order.length; i++) { + var field = _order[i]; + if (field.match(/^group\s/)) continue; + var field_val = val_hash[field]; + if (! field_val) { + if (field == 'OR') field_val = 'OR'; + else return {error:v_error('No element found in group for '+field)}; + } + if (typeof(field_val) == 'object' && ! field_val['field']) field_val['field'] = field; + fields.push(field_val); + } + } + + var found = {}; + for (var i = 0; i < fields.length; i++) { + var field_val = fields[i]; + var field = field_val.field; + if (! field) return {error:v_error("Missing field key in validation")}; + found[field] = 1; + } + + for (var i = 0; i < order.length; i++) { + var field = order[i]; + if (found[field] || field.match(/^group\s/)) continue; + var field_val = val_hash[field]; + if (typeof(field_val) != 'object' || field_val.length) {debug(val_hash);alert(field);return {error:v_error('Found a non-hash value on field '+field)};} + if (! field_val.field) field_val.field = field; + fields.push(field_val); + } + + for (var i = 0; i < fields.length; i++) v_clean_field_val(fields[i]); + + return {'fields':fields, 'order':order}; +} - /// add on errors as requested - if (errors.length) { - if (title) ERRORS[ERRORS.length] = title; - for (var j = 0; j < errors.length; j ++) ERRORS[ERRORS.length] = errors[j]; - } +function v_clean_field_val (field_val) { + if (! field_val.order) field_val.order = v_field_order(field_val); + if (! field_val.deps) field_val.deps = {}; + for (var i = 0; i < field_val.order.length; i++) { + var k = field_val.order[i]; + var v = field_val[k]; + if (typeof(v) == 'undefined') return {error:v_error('No matching validation found on field '+field+' for type '+k)}; + if (k.match(/^(min|max)_in_set(\d*)$/)) { + if (typeof(v) == 'string') { + if (! (m = v.match(/^\s*(\d+)(?:\s*[oO][fF])?\s+(.+)\s*$/))) return {error:v_error("Invalid "+k+" check "+v)}; + field_val[k] = m[2].split(/[\s,]+/); + field_val[k].unshift(m[1]); + } + for (var j = 1; j < field_val[k].length; j++) if (field_val[k][j] != field_val.field) field_val.deps[field_val[k][j]] = 1; + } else if (k.match(/^(enum|compare)\d*$/)) { + if (typeof(v) == 'string') field_val[k] = v.split(/\s*\|\|\s*/); + } else if (k.match(/^match\d*$/)) { + if (typeof(v) == 'string') v = field_val[k] = v.split(/\s*\|\|\s*/); + for (var j = 0; j < v.length; j++) { + if (typeof(v[j]) != 'string' || v[j] == '!') continue; + var m = v[j].match(/^\s*(!\s*|)m([^\s\w])(.*)\2([eigsmx]*)\s*$/); + if (! m) return {error:v_error("Not sure how to parse that match ("+v[j]+")")}; + var not = m[1]; + var pat = m[3]; + var opt = m[4]; + if (opt.indexOf('e') != -1) return {error:v_error("The e option cannot be used on field "+field_val.field+", test "+k)}; + opt = opt.replace(/[sg]/g,''); + v[j] = new RegExp(pat, opt); + if (not) v.splice(j, 0, '!'); + } + } else if (k.match(/^custom_js\d*$/)) { + if (typeof(v) == 'string' && v.match(/^\s*function\s*/)) field_val[k] = eval(v); + } + } +} - /// add on general options, and group options if errors in group occurred - var m; - for (var j = 0; j < order.length; j ++) { - var field = order[j]; - if (! (m = field.match('^(general|group)\\s+(\\w+)$'))) continue; - if (m[1] == 'group' && (errors.length == 0 || m[2].match('^(field|order|title)$'))) continue; - EXTRA[m[2]] = group_val[field]; +function v_validate (form, val_hash) { + var clean = v_clean_val_hash(val_hash); + if (clean.error) return; + var order = clean.order; + var fields = clean.fields; + + var ERRORS = []; + var EXTRA = []; + var title = val_hash['group title']; + var validate_if = val_hash['group validate_if']; + if (validate_if && ! v_check_conditional(form, validate_if)) return; + + var is_found = 1; + var errors = []; + var hold_error; + + for (var j = 0; j < fields.length; j++) { + var ref = fields[j]; + if (typeof(ref) != 'object' && ref == 'OR') { + if (is_found) j++; + is_found = 1; + continue; + } + is_found = 1; + if (! ref.field) return v_error("Missing field key during normal validation"); + var err = v_validate_buddy(form, ref.field, ref); + + if (err.length) { + if (j <= fields.length && typeof(fields[j + 1] != 'object') && fields[j + 1] == 'OR') { + hold_error = err; + } else { + if (hold_error) err = hold_error; + for (var k = 0; k < err.length; k++) errors.push(err[k]); + hold_error = ''; } + } else { + hold_error = ''; + } } - /// store any extra items from self - for (var key in this) { - if (key == 'extend') continue; // Protoype Array() fix - if (! key.match('_error$') - && ! key.match('^(raise_error|as_hash_\\w+|as_array_\\w+|as_string_\\w+)$')) continue; - EXTRA[key] = this[key]; + if (errors.length) { + if (title) ERRORS.push(title); + for (var j = 0; j < errors.length; j++) ERRORS.push(errors[j]); } - /// allow for checking for unused keys - // if (EXTRA['no_extra_fields']) - // won't do anything about this for now - let the server handle it + var m; + for (var j = 0; j < order.length; j++) { + var field = order[j]; + if (! (m = field.match(/^group\s+(\w+)$/))) continue; + if (errors.length == 0 || m[1].match(/^(field|order|title|validate_if)$/)) continue; + EXTRA[m[1]] = val_hash[field]; + } - /// return what they want if (ERRORS.length) return new ValidateError(ERRORS, EXTRA); return; } - -/// allow for optional validation on groups and on individual items -function vob_check_conditional (form, ifs, N_level, ifs_match) { - +function v_check_conditional (form, ifs, N_level, ifs_match) { if (! N_level) N_level = 0; - N_level ++; - - /// can pass a single hash - or an array ref of hashes - if (! ifs) { - return this.error("Need reference passed to check_conditional"); - } else if (typeof(ifs) != 'object') { - ifs = new Array(ifs); - } else if (! ifs.length) { // turn hash into array of hash - ifs = new Array(ifs); - } + N_level++; + + if (! ifs) return v_error("Need reference passed to check_conditional"); + if (typeof(ifs) != 'object' || ! ifs.length) ifs = [ifs]; - /// run the if options here - /// multiple items can be passed - all are required unless OR is used to separate var is_found = 1; var m; - for (var i = 0; i < ifs.length; i ++) { - var ref = ifs[i]; - if (typeof(ref) != 'object') { - if (ref == 'OR') { - if (is_found) i++; - is_found = 1; - continue; - } else { - var field = ref; - ref = new Array(); - if (m = field.match('^(\\s*!\\s*)')) { - field = field.substring(m[1].length); - ref['max_in_set'] = '0 of ' + field; - } else { - ref['required'] = 1; - } - ref['field'] = field; - } - } - if (! is_found) break; - - /// get the field - allow for custom variables based upon a match - var field = ref['field']; - if (! field) return this.error("Missing field key during validate_if"); - field = field.replace(new RegExp('\\$(\\d+)','g'), function (all, N) { - if (typeof(ifs_match) != 'object' - || typeof(ifs_match[N]) == 'undefined') return '' - return ifs_match[N]; - }); - - var err = this.validate_buddy(form, field, ref, N_level); - if (err.length) is_found = 0; + for (var i = 0; i < ifs.length; i++) { + var ref = ifs[i]; + if (typeof(ref) != 'object') { + if (ref == 'OR') { + if (is_found) i++; + is_found = 1; + continue; + } else { + var field = ref; + ref = {}; + if (m = field.match(/^(\s*!\s*)/)) { + field = field.substring(m[1].length); + ref.max_in_set = [0, field]; + } else { + ref.required = 1; + } + ref.field = field; + } + } + if (! is_found) break; + + var field = ref.field; + if (! field) return v_error("Missing field key during validate_if"); + field = field.replace(/\$(\d+)/g, function (all, N) { + return (typeof(ifs_match) != 'object' || typeof(ifs_match[N]) == 'undefined') ? '' : ifs_match[N]; + }); + + v_clean_field_val(ref); + var err = v_validate_buddy(form, field, ref, N_level); + if (err.length) is_found = 0; } return is_found; } -function vob_filter_types (type, types) { - var values = new Array(); +function v_filter_types (type, types) { + var values = []; var regexp = new RegExp('^'+type+'_?\\d*$'); for (var i = 0; i < types.length; i++) - if (types[i].match(regexp)) values[values.length] = types[i]; + if (types[i].match(regexp)) values.push(types[i]); return values; } -function vob_add_error (errors,field,type,field_val,ifs_match,form) { - errors[errors.length] = new Array(field, type, field_val, ifs_match); - if (field_val['clear_on_error']) { - var el = form[field]; - if (el) { - var type = el.type; - if (type && (type == 'hidden' || type == 'password' || type == 'text' || type == 'textarea' || type == 'submit')) - el.value = ''; - } +function v_add_error (errors,field,type,field_val,ifs_match,form) { + errors.push([field, type, field_val, ifs_match]); + if (field_val.clear_on_error) { + var el = form[field]; + if (el && el.type && el.type.match(/(hidden|password|text|textarea|submit)/)) el.value = ''; } + return errors; } -/// this is where the main checking goes on -function vob_validate_buddy (form, field, field_val, N_level, ifs_match) { - if (! N_level) N_level = 0; - if (++ N_level > 10) return this.error("Max dependency level reached " + N_level); - if (! form.elements) return; - - var errors = new Array(); - var types = new Array(); - for (var key in field_val) { - if (key == 'extend') continue; // Protoype Array() fix - types[types.length] = key; - } - types = types.sort(); +function v_field_order (field_val) { + var o = []; + for (var k in field_val) if (! k.match(/^(extend|field|name)$/) && ! k.match(/_error$/)) o.push(k); + return o.sort(); +} - /// allow for not running some tests in the cgi - if (this.filter_types('exclude_js', types).length) return errors; +function v_validate_buddy (form, field, field_val, N_level, ifs_match) { + var errors = []; + if (! N_level) N_level = 0; + if (++N_level > 10) { v_error("Max dependency level reached " + N_level); return errors } + if (! form.elements || field_val.exclude_js) return errors; + var types = field_val.order || v_field_order(field_val); - /// allow for field names that contain regular expressions var m; - if (m = field.match('^(!\\s*|)m([^\\s\\w])(.*)\\2([eigsmx]*)$')) { - var not = m[1]; - var pat = m[3]; - var opt = m[4]; - if (opt.indexOf('e') != -1) return this.error("The e option cannot be used on field "+field); - opt = opt.replace(new RegExp('[sg]','g'),''); - var reg = new RegExp(pat, opt); - - var keys = new Array(); - for (var i = 0; i < form.elements.length; i ++) { - var _field = form.elements[i].name; - if (! _field) continue; - if ( (not && ! (m = _field.match(reg))) || (m = _field.match(reg))) { - var err = this.validate_buddy(form, _field, field_val, N_level, m); - for (var j = 0; j < err.length; j ++) errors[errors.length] = err[j]; - } - } - return errors; - } + if (m = field.match(/^(!\s*|)m([^\s\w])(.*)\2([eigsmx]*)$/)) { + var not = m[1]; + var pat = m[3]; + var opt = m[4]; + if (opt.indexOf('e') != -1) { v_error("The e option cannot be used on field "+field); return errors } + opt = opt.replace(/[sg]/g,''); + var reg = new RegExp(pat, opt); + + for (var i = 0; i < form.elements.length; i++) { + var _field = form.elements[i].name; + if (! _field) continue; + if ( (not && ! (m = _field.match(reg))) || (m = _field.match(reg))) { + var err = v_validate_buddy(form, _field, field_val, N_level, m); + for (var j = 0; j < err.length; j++) errors.push(err[j]); + } + } + return errors; + } + + var _value = v_get_form_value(form[field]); + var modified = 0; - var _value = this.get_form_value(form[field]); - var values; - if (typeof(_value) == 'object') { - values = _value; - } else { - values = new Array(); - values[values.length] = _value; + if (typeof(field_val['default']) != 'undefined' + && (typeof(_value) == 'undefined' + || (typeof(_value) == 'object' && _value.length == 0) + || ! _value.length)) { + _value = field_val['default']; + modified = 1; } - var n_values = (typeof(_value) == 'undefined') ? 0 : values.length; - /// allow for default value - var tests = this.filter_types('default', types); - if (n_values == 0 || (n_values == 1 && values[0].length == 0)) { - for (var i = 0; i < tests.length; i ++) { - var el = form[field]; - if (! el) continue; - var type = el.type; - if (type && (type == 'hidden' || type == 'password' || type == 'text' || type == 'textarea' || type == 'submit')) - el.value = values[0] = '' + field_val[tests[i]]; - } - } + var values = (typeof(_value) == 'object') ? _value : [_value]; + var n_values = (typeof(_value) == 'undefined') ? 0 : values.length; - /// allow for a few form modifiers - var modified = 0; - for (var i = 0; i < values.length; i ++) { - if (typeof(values[i]) == 'undefined') continue; - if (! this.filter_types('do_not_trim',types).length) - values[i] = values[i].replace('^\\s+','').replace(new RegExp('\\s+$',''),''); - if (this.filter_types('trim_control_chars',types).length) - values[i] = values[i].replace(new RegExp('\t', 'g'),' ').replace(new RegExp('[\\x00-\\x1F]+','g'),''); - if (this.filter_types('to_upper_case',types).length) { - values[i] = values[i].toUpperCase(); - } else if (this.filter_types('to_lower_case',types).length) { - values[i] = values[i].toLowerCase(); - } - } - var tests = this.filter_types('replace', types); - for (var i = 0; i < tests.length; i ++) { - var ref = field_val[tests[i]]; - ref = (typeof(ref) == 'object') ? ref : ref.split(new RegExp('\\s*\\|\\|\\s*')); - for (var j = 0; j < ref.length; j ++) { - if (! (m = ref[j].match('^\\s*s([^\\s\\w])(.+)\\1(.*)\\1([eigmx]*)$'))) - return this.error("Not sure how to parse that replace "+ref[j]); - var pat = m[2]; - var swap = m[3]; - var opt = m[4]; - if (opt.indexOf('e') != -1) - return this.error("The e option cannot be used on field "+field+", replace "+tests[i]); - var regexp = new RegExp(pat, opt); - for (var k = 0; k < values.length; k ++) { - if (values[k].match(regexp)) modified = 1; - values[k] = values[k].replace(regexp,swap); - } - } + for (var i = 0; i < values.length; i++) { + if (typeof(values[i]) == 'undefined') continue; + var orig = values[i]; + if (! field_val.do_not_trim) values[i] = values[i].replace(/^\s+/,'').replace(/\s+$/,''); + if (field_val.trim_control_chars) values[i] = values[i].replace(/\t/g,' ').replace(/[\x00-\x1F]/g,''); + if (field_val.to_upper_case) values[i] = values[i].toUpperCase(); + if (field_val.to_lower_case) values[i] = values[i].toLowerCase(); + + var tests = v_filter_types('replace', types); + for (var k = 0; k < tests.length; k++) { + var ref = field_val[tests[k]]; + ref = (typeof(ref) == 'object') ? ref : ref.split(/\s*\|\|\s*/); + for (var j = 0; j < ref.length; j++) { + if (! (m = ref[j].match(/^\s*s([^\s\w])(.+)\1(.*)\1([eigmx]*)$/))) + return v_error("Not sure how to parse that replace "+ref[j]); + var pat = m[2]; + var swap = m[3]; + var opt = m[4]; + if (opt.indexOf('e') != -1) { v_error("The e option cannot be used on field "+field+", replace "+tests[i]); return errors } + var regexp = new RegExp(pat, opt); + values[i] = values[i].replace(regexp, swap); + } + } + + if (orig != values[i]) modified = 1; } if (modified && n_values == 1) { - var el = form[field]; - var type = el.type; - if (! type) return ''; - if (type == 'hidden' || type == 'password' || type == 'text' || type == 'textarea' || type == 'submit') - el.value = values[0]; + var el = form[field]; + if (el && el.type && el.type.match(/(hidden|password|text|textarea|submit)/)) el.value = values[0]; } - /// only continue if a validate_if is not present or passes test var needs_val = 0; var n_vif = 0; - var tests = this.filter_types('validate_if', types); - for (var i = 0; i < tests.length; i ++) { - n_vif ++; - var ifs = field_val[tests[i]]; - var ret = this.check_conditional(form, ifs, N_level, ifs_match); - if (ret) needs_val ++; + var tests = v_filter_types('validate_if', types); + for (var i = 0; i < tests.length; i++) { + n_vif++; + var ifs = field_val[tests[i]]; + var ret = v_check_conditional(form, ifs, N_level, ifs_match); + if (ret) needs_val++; } if (! needs_val && n_vif) return errors; - - /// check for simple existence - /// optionally check only if another condition is met var is_required = ''; - var tests = this.filter_types('required', types); - for (var i = 0; i < tests.length; i ++) { - if (! field_val[tests[i]] || field_val[tests[i]] == 0) continue; + var tests = v_filter_types('required', types); + for (var i = 0; i < tests.length; i++) { + if (! field_val[tests[i]] || field_val[tests[i]] == 0) continue; + is_required = tests[i]; + break; + } + if (! is_required) { + var tests = v_filter_types('required_if', types); + for (var i = 0; i < tests.length; i++) { + var ifs = field_val[tests[i]]; + if (! v_check_conditional(form, ifs, N_level, ifs_match)) continue; is_required = tests[i]; break; + } } - if (! is_required) { - var tests = this.filter_types('required_if', types); - for (var i = 0; i < tests.length; i ++) { - var ifs = field_val[tests[i]]; - if (! this.check_conditional(form, ifs, N_level, ifs_match)) continue; - is_required = tests[i]; - break; + if (is_required) { + var found; + for (var i = 0; i < values.length; i++) { + if (values[i].length) { + found = 1; + break; } - } - if (is_required && (typeof(_value) == 'undefined' - || ((typeof(_value) == 'object' && _value.length == 0) - || ! _value.length))) { - this.add_error(errors, field, is_required, field_val, ifs_match, form); - return errors; + } + if (! found) return v_add_error(errors, field, is_required, field_val, ifs_match, form); } - /// min values check - var tests = this.filter_types('min_values', types); - for (var i = 0; i < tests.length; i ++) { - var n = field_val[tests[i]]; - if (n_values < n) { - this.add_error(errors, field, tests[i], field_val, ifs_match, form); - return errors; - } - } + if (field_val.min_values && n_values < field_val.min_values) + return v_add_error(errors, field, 'min_values', field_val, ifs_match, form); - /// max values check - var tests = this.filter_types('max_values', types); - if (! tests.length) { - tests[tests.length] = 'max_values'; - field_val['max_values'] = 1; - } - for (var i = 0; i < tests.length; i ++) { - var n = field_val[tests[i]]; - if (n_values > n) { - this.add_error(errors, field, tests[i], field_val, ifs_match, form); - return errors; - } - } + if (typeof(field_val.max_values) == 'undefined') field_val.max_values = 1; + if (field_val.max_values && n_values > field_val.max_values) + return v_add_error(errors, field, 'max_values', field_val, ifs_match, form); - /// min_in_set and max_in_set check for (var h = 0; h < 2 ; h++) { - var minmax = (h == 0) ? 'min' : 'max'; - var tests = this.filter_types(minmax+'_in_set', types); - for (var i = 0; i < tests.length; i ++) { - if (! (m = field_val[tests[i]].match('^\\s*(\\d+)(?:\\s*[oO][fF])?\\s+(.+)\\s*$'))) - return this.error("Invalid in_set check "+field_val[tests[i]]); - var n = m[1]; - var _fields = m[2].split(new RegExp('[\\s,]+')); - for (var k = 0; k < _fields.length; k ++) { - var _value = this.get_form_value(form[_fields[k]]); - var _values; - if (typeof(_value) == 'undefined') continue; - if (typeof(_value) == 'object') { - _values = _value; - } else { - _values = new Array(); - _values[_values.length] = _value; - } - for (var l = 0; l < _values.length; l ++) { - var _value = _values[l]; - if (typeof(_value) != 'undefined' && _value.length) n --; - } - } - if ( (minmax == 'min' && n > 0) - || (minmax == 'max' && n < 0)) { - this.add_error(errors, field, tests[i], field_val, ifs_match, form); - return errors; - } - } - } - - // the remaining tests operate on each value of a field - for (var n = 0; n < values.length; n ++) { + var minmax = (h == 0) ? 'min' : 'max'; + var tests = v_filter_types(minmax+'_in_set', types); + for (var i = 0; i < tests.length; i++) { + var a = field_val[tests[i]]; + var n = a[0]; + for (var k = 1; k < a.length; k++) { + var _value = v_get_form_value(form[a[k]]); + var _values; + if (typeof(_value) == 'undefined') continue; + _values = (typeof(_value) == 'object') ? _value : [_value]; + for (var l = 0; l < _values.length; l++) { + var _value = _values[l]; + if (typeof(_value) != 'undefined' && _value.length) n--; + } + } + if ( (minmax == 'min' && n > 0) + || (minmax == 'max' && n < 0)) { + v_add_error(errors, field, tests[i], field_val, ifs_match, form); + return errors; + } + } + } + + for (var i = 0; i < types.length; i++) { + var type = types[i]; + var _fv = field_val[type]; + for (var n = 0; n < values.length; n++) { var value = values[n]; - /// allow for enum types - var tests = this.filter_types('enum', types); - for (var i = 0; i < tests.length; i ++) { - var hold = field_val[tests[i]]; - var _enum = (typeof(hold) == 'object') ? hold : hold.split(new RegExp('\\s*\\|\\|\\s*')); - var is_found = 0; - for (var j = 0; j < _enum.length; j ++) { - if (value != _enum[j]) continue; - is_found = 1; - break; - } - if (! is_found) this.add_error(errors, field, tests[i], field_val, ifs_match, form); - } - - /// field equality test - var tests = this.filter_types('equals', types); - for (var i = 0; i < tests.length; i ++) { - var field2 = field_val[tests[i]]; - var not = field2.match('^!\\s*'); - if (not) field2 = field2.substring(not[0].length); - var success = 0; - if (m = field2.match('^(["\'])(.*)\\1$')) { - if (value == m[2]) success = 1; + if (type.match(/^enum\d*$/)) { + var is_found = 0; + for (var j = 0; j < _fv.length; j++) if (value == _fv[j]) { is_found = 1; break } + if (! is_found) v_add_error(errors, field, type, field_val, ifs_match, form); + } + + if (type.match(/^equals\d*$/)) { + var not = _fv.match(/^!\s*/); + if (not) _fv = _fv.substring(not[0].length); + var success = 0; + if (m = _fv.match(/^([\"\'])(.*)\1$/)) { + if (value == m[2]) success = 1; + } else { + var value2 = v_get_form_value(form[_fv]); + if (typeof(value2) == 'undefined') value2 = ''; + if (value == value2) success = 1; + } + if (not && success || ! not && ! success) + v_add_error(errors, field, type, field_val, ifs_match, form); + } + + if (type == 'min_len' && value.length < _fv) v_add_error(errors, field, 'min_len', field_val, ifs_match, form); + if (type == 'max_len' && value.length > _fv) v_add_error(errors, field, 'max_len', field_val, ifs_match, form); + + if (type.match(/^match\d*$/)) { + for (var j = 0; j < _fv.length; j++) { + if (typeof(_fv[j]) == 'string') continue; + var not = (j > 0 && typeof(_fv[j-1]) == 'string' && _fv[j-1] == '!') ? 1 : 0; + if ( ( not && value.match(_fv[j])) + || (! not && ! value.match(_fv[j]))) v_add_error(errors, field, type, field_val, ifs_match, form); + } + } + + if (type.match(/^compare\d*$/)) { + for (var j = 0; j < _fv.length; j++) { + var comp = _fv[j]; + if (! comp) continue; + var hold = false; + var copy = value; + if (m = comp.match(/^\s*(>|<|[>' ) hold = (copy > m[2]) + else if (m[1] == '<' ) hold = (copy < m[2]) + else if (m[1] == '>=') hold = (copy >= m[2]) + else if (m[1] == '<=') hold = (copy <= m[2]) + else if (m[1] == '!=') hold = (copy != m[2]) + else if (m[1] == '==') hold = (copy == m[2]) + } else if (m = comp.match(/^\s*(eq|ne|gt|ge|lt|le)\s+(.+?)\s*$/)) { + if ( m[2].match(/^\"/)) m[2] = m[2].replace(/^"(.*)"$/,'$1'); + else if (m[2].match(/^\'/)) m[2] = m[2].replace(/^'(.*)'$/,'$1'); + if (m[1] == 'gt') hold = (copy > m[2]) + else if (m[1] == 'lt') hold = (copy < m[2]) + else if (m[1] == 'ge') hold = (copy >= m[2]) + else if (m[1] == 'le') hold = (copy <= m[2]) + else if (m[1] == 'ne') hold = (copy != m[2]) + else if (m[1] == 'eq') hold = (copy == m[2]) } else { - var value2 = this.get_form_value(form[field2]); - if (typeof(value2) == 'undefined') value2 = ''; - if (value == value2) success = 1; - } - if (not && success || ! not && ! success) - this.add_error(errors, field, tests[i], field_val, ifs_match, form); - } - - /// length min check - var tests = this.filter_types('min_len', types); - for (var i = 0; i < tests.length; i ++) { - var n = field_val[tests[i]]; - if (value.length < n) this.add_error(errors, field, tests[i], field_val, ifs_match, form); - } - - /// length max check - var tests = this.filter_types('max_len', types); - for (var i = 0; i < tests.length; i ++) { - var n = field_val[tests[i]]; - if (value.length > n) this.add_error(errors, field, tests[i], field_val, ifs_match, form); - } - - /// now do match types - var tests = this.filter_types('match', types); - for (var i = 0; i < tests.length; i ++) { - var ref = field_val[tests[i]]; - ref = (typeof(ref) == 'object') ? ref - : (typeof(ref) == 'function') ? new Array(ref) - : ref.split(new RegExp('\\s*\\|\\|\\s*')); - for (var j = 0; j < ref.length; j ++) { - if (typeof(ref[j]) == 'function') { - if (! value.match(ref[j])) this.add_error(errors, field, tests[i], field_val, ifs_match, form); - } else { - if (! (m = ref[j].match('^\\s*(!\\s*|)m([^\\s\\w])(.*)\\2([eigsmx]*)\\s*$'))) - return this.error("Not sure how to parse that match ("+ref[j]+")"); - var not = m[1]; - var pat = m[3]; - var opt = m[4]; - if (opt.indexOf('e') != -1) - return this.error("The e option cannot be used on field "+field+", test "+tests[i]); - opt = opt.replace(new RegExp('[sg]','g'),''); - var regexp = new RegExp(pat, opt); - if ( ( not && value.match(regexp)) - || (! not && ! value.match(regexp))) { - this.add_error(errors, field, tests[i], field_val, ifs_match, form); - } - } + v_error("Not sure how to compare \""+comp+"\""); + return errors; } + if (! hold) v_add_error(errors, field, type, field_val, ifs_match, form); + } } - /// allow for comparison checks - var tests = this.filter_types('compare', types); - for (var i = 0; i < tests.length; i ++) { - var ref = field_val[tests[i]]; - ref = (typeof(ref) == 'object') ? ref : ref.split(new RegExp('\\s*\\|\\|\\s*')); - for (var j = 0; j < ref.length; j ++) { - var comp = ref[j]; - if (! comp) continue; - var hold = false; - var copy = value; - if (m = comp.match('^\\s*(>|<|[>' ) hold = (copy > m[2]) - else if (m[1] == '<' ) hold = (copy < m[2]) - else if (m[1] == '>=') hold = (copy >= m[2]) - else if (m[1] == '<=') hold = (copy <= m[2]) - else if (m[1] == '!=') hold = (copy != m[2]) - else if (m[1] == '==') hold = (copy == m[2]) - } else if (m = comp.match('^\\s*(eq|ne|gt|ge|lt|le)\\s+(.+?)\\s*$')) { - m[2] = m[2].replace('^(["\'])(.*)\\1$','$1'); - if (m[1] == 'gt') hold = (copy > m[2]) - else if (m[1] == 'lt') hold = (copy < m[2]) - else if (m[1] == 'ge') hold = (copy >= m[2]) - else if (m[1] == 'le') hold = (copy <= m[2]) - else if (m[1] == 'ne') hold = (copy != m[2]) - else if (m[1] == 'eq') hold = (copy == m[2]) - } else { - return this.error("Not sure how to compare \""+comp+"\""); - } - if (! hold) this.add_error(errors, field, tests[i], field_val, ifs_match, form); - } - } + if (type.match(/^type\d*$/)) + if (! v_check_type(value, _fv, field, form)) + v_add_error(errors, field, type, field_val, ifs_match, form); + } - /// do specific type checks - var tests = this.filter_types('type',types); - for (var i = 0; i < tests.length; i ++) - if (! this.check_type(value, field_val[tests[i]], field, form)) - this.add_error(errors, field, tests[i], field_val, ifs_match, form); - - /// do custom_js type checks - // this will allow for a custom piece of javascript - // the js is evaluated and should return 1 for success - // or 0 for failure - the variables field, value, and field_val (the hash) are available - var tests = this.filter_types('custom_js',types); - for (var i = 0; i < tests.length; i ++) - if (! eval(field_val[tests[i]])) - this.add_error(errors, field, tests[i], field_val, ifs_match, form); + // the js is evaluated and should return 1 for success + // or 0 for failure - the variables field, value, and field_val (the hash) are available + if (type.match(/^custom_js\d*$/)) { + var value = values.length == 1 ? values[0] : values; + if (typeof(_fv) == 'function' + ? ! _fv({'value':value, 'field_val':field_val, 'form':form, 'key':field_val.field}) + : ! eval(_fv)) v_add_error(errors, field, type, field_val, ifs_match, form); + } } - /// all done - time to return return errors; } -/// used to validate specific types -function vob_check_type (value, type, field, form) { +function v_check_type (value, type, field, form) { var m; + type = type.toUpperCase(); - /// do valid email address for our system if (type == 'EMAIL') { - if (! value) return 0; - if (! (m = value.match('^(.+)\@(.+?)$'))) return 0; - if (m[1].length > 60) return 0; - if (m[2].length > 100) return 0; - if (! this.check_type(m[2],'DOMAIN') && ! this.check_type(m[2],'IP')) return 0; - if (! this.check_type(m[1],'LOCAL_PART')) return 0; - - /// the "username" portion of an email address + if (! value) return 0; + if (! (m = value.match(/^(.+)@(.+?)$/))) return 0; + if (m[1].length > 60) return 0; + if (m[2].length > 100) return 0; + if (! v_check_type(m[2],'DOMAIN') && ! v_check_type(m[2],'IP')) return 0; + if (! v_check_type(m[1],'LOCAL_PART')) return 0; + } else if (type == 'LOCAL_PART') { - if (typeof(value) == 'undefined' || ! value.length) return 0; - if (value.match('[^a-z0-9.\\-!&+]')) return 0; - if (value.match('^[.\\-]')) return 0; - if (value.match('[.\\-&]$')) return 0; - if (value.match('(\\.-|-\\.|\\.\\.)')) return 0; + if (typeof(value) == 'undefined' || ! value.length) return 0; + if (typeof(v_local_part) != 'undefined') return (value.match(v_local_part) ? 1 : 0); + if (value.match(/[^a-z0-9.\-!&+]/)) return 0; + if (value.match(/^[.\-]/)) return 0; + if (value.match(/[.\-&]$/)) return 0; + if (value.match(/(\.-|-\.|\.\.)/)) return 0; - /// standard IP address } else if (type == 'IP') { - if (! value) return 0; - var dig = value.split(new RegExp('\\.')); - if (dig.length != 4) return 0; - for (var i = 0; i < 4; i ++) - if (typeof(dig[i]) == 'undefined' || dig[i].match('\\D') || dig[i] > 255) return 0; + if (! value) return 0; + var dig = value.split(/\./); + if (dig.length != 4) return 0; + for (var i = 0; i < 4; i++) + if (typeof(dig[i]) == 'undefined' || dig[i].match(/\D/) || dig[i] > 255) return 0; - /// domain name - including tld and subdomains (which are all domains) } else if (type == 'DOMAIN') { - if (! value) return 0; - if (! value.match('^[a-z0-9.-]{4,255}$')) return 0; - if (value.match('^[.\\-]')) return 0; - if (value.match('(\\.-|-\\.|\\.\\.)')) return 0; - if (! (m = value.match('\.([a-z]+)$'))) return 0; - value = value.substring(0,value.lastIndexOf('.')); - - if (m[1] == 'name') { - if (! value.match('^[a-z0-9][a-z0-9\\-]{0,62}\\.[a-z0-9][a-z0-9\\-]{0,62}$')) return 0; - } else - if (! value.match('^([a-z0-9][a-z0-9\\-]{0,62}\\.)*[a-z0-9][a-z0-9\\-]{0,62}$')) return 0; - - /// validate a url + if (! value) return 0; + if (! value.match(/^[a-z0-9.-]{4,255}$/)) return 0; + if (value.match(/^[.\-]/)) return 0; + if (value.match(/(\.-|-\.|\.\.)/)) return 0; + if (! (m = value.match(/\.([a-z]+)$/))) return 0; + value = value.substring(0,value.lastIndexOf('.')); + if (m[1] == 'name') { + if (! value.match(/^[a-z0-9][a-z0-9\-]{0,62}\.[a-z0-9][a-z0-9\-]{0,62}$/)) return 0; + } else + if (! value.match(/^([a-z0-9][a-z0-9\-]{0,62}\.)*[a-z0-9][a-z0-9\-]{0,62}$/)) return 0; + } else if (type == 'URL') { - if (! value) return 0; - if (! (m = value.match(new RegExp('^https?://([^/]+)','i'),''))) return 0; - value = value.substring(m[0].length); - if (! this.check_type(m[1],'DOMAIN') && ! this.check_type(m[1],'IP')) return 0; - if (value && ! this.check_type(value,'URI')) return 0; + if (! value) return 0; + if (! (m = value.match(/^https?:\/\/([^\/]+)/i))) return 0; + value = value.substring(m[0].length); + var dom = m[1].replace(/:\d+$/).replace(/\.$/); + if (! v_check_type(dom,'DOMAIN') && ! v_check_type(m[1],'IP')) return 0; + if (value && ! v_check_type(value,'URI')) return 0; - /// validate a uri - the path portion of a request } else if (type == 'URI') { - if (! value) return 0; - if (value.match('\\s')) return 0; + if (! value) return 0; + if (value.match(/\s/)) return 0; } else if (type == 'CC') { - if (! value) return 0; - if (value.match('[^\\d\\- ]') || value.length > 16 || value.length < 13) return; - /// simple mod10 check - value = value.replace(new RegExp('[\\- ]','g'), ''); - var sum = 0; - var swc = 0; - - for (var i = value.length - 1; i >= 0; i --) { - if (++ swc > 2) swc = 1; - var y = value.charAt(i) * swc; - if (y > 9) y -= 9; - sum += y; - } - if (sum % 10) return 0; - + if (! value) return 0; + if (value.match(/[^\d\- ]/)) return 0; + value = value.replace(/[\- ]/g, ''); + if (value.length > 16 || value.length < 13) return 0; + // mod10 + var sum = 0; + var swc = 0; + for (var i = value.length - 1; i >= 0; i--) { + if (++swc > 2) swc = 1; + var y = value.charAt(i) * swc; + if (y > 9) y -= 9; + sum += y; + } + if (sum % 10) return 0; } return 1; } -// little routine that will get the values from the form -// it will return multiple values as an array -function vob_get_form_value (el) { +function v_get_form_value (el) { if (! el) return ''; if (el.disabled) return ''; var type = el.type ? el.type.toLowerCase() : ''; if (el.length && type != 'select-one') { - var a = new Array(); - for (var j=0;j'); - - /// now add to the hash - var found = new Array(); - var ret = new Array(); - for (var i = 0; i < errors.length; i ++) { - if (typeof(errors[i]) == 'string') continue; - if (! errors[i].length) continue; - - var field = errors[i][0]; - var type = errors[i][1]; - var field_val = errors[i][2]; - var ifs_match = errors[i][3]; - - if (! field) return alert("Missing field name"); - if (field_val['delegate_error']) { - field = field_val['delegate_error']; - field = field.replace(new RegExp('\\$(\\d+)','g'), function (all, N) { - if (typeof(ifs_match) != 'object' - || typeof(ifs_match[N]) == 'undefined') return '' - return ifs_match[N]; - }); - } - - var text = this.get_error_text(errors[i]); - if (! found[field]) found[field] = new Array(); - if (found[field][text]) continue; - found[field][text] = 1; - - field += suffix; - if (! ret[field]) ret[field] = new Array(); - ret[field].push(text); - } - - /// allow for elements returned as - if (joiner) { - var header = eob_get_val('as_hash_header', extra2, extra1, ''); - var footer = eob_get_val('as_hash_footer', extra2, extra1, ''); - for (var key in ret) { - if (key == 'extend') continue; // Protoype Array() fix - ret[key] = header + ret[key].join(joiner) + footer; - } +function v_find_val () { + var key = arguments[0]; + for (var i = 1; i < arguments.length; i++) { + if (typeof(arguments[i]) == 'string') return arguments[i]; + if (typeof(arguments[i]) == 'undefined') continue; + if (typeof(arguments[i][key]) != 'undefined') return arguments[i][key]; } - - return ret; + return ''; } -/// return a user friendly error message -function eob_get_error_text (err) { - var extra = this.extra; +function v_get_error_text (err, extra1, extra2) { var field = err[0]; var type = err[1]; var field_val = err[2]; var ifs_match = err[3]; var m; - var dig = (m = type.match('(_?\\d+)$')) ? m[1] : ''; + var dig = (m = type.match(/(_?\d+)$/)) ? m[1] : ''; var type_lc = type.toLowerCase(); + var v = field_val[type + dig]; - /// allow for delegated field names - only used for defaults - if (field_val['delegate_error']) { - field = field_val['delegate_error']; - field = field.replace(new RegExp('\\$(\\d+)','g'), function (all, N) { - if (typeof(ifs_match) != 'object' - || typeof(ifs_match[N]) == 'undefined') return '' - return ifs_match[N]; - }); - } - - /// the the name of this thing - var name = (field_val['name']) ? field_val['name'] : "The field " +field; - name = name.replace(new RegExp('\\$(\\d+)','g'), function (all, N) { + if (field_val.delegate_error) { + field = field_val.delegate_error; + field = field.replace(/\$(\d+)/g, function (all, N) { if (typeof(ifs_match) != 'object' - || typeof(ifs_match[N]) == 'undefined') return '' + || typeof(ifs_match[N]) == 'undefined') return '' return ifs_match[N]; + }); + } + + var name = field_val.name || "The field " +field; + name = name.replace(/\$(\d+)/g, function (all, N) { + if (typeof(ifs_match) != 'object' + || typeof(ifs_match[N]) == 'undefined') return '' + return ifs_match[N]; }); + var msg = v_find_val(type + '_error', extra1, extra2); + if (! msg) { + if (dig.length) msg = field_val[type + dig + '_error']; + if (! msg) msg = field_val[type + '_error']; + } + if (msg) { + msg = msg.replace(/\$(\d+)/g, function (all, N) { + if (typeof(ifs_match) != 'object' || typeof(ifs_match[N]) == 'undefined') return ''; + return ifs_match[N]; + }); + msg = msg.replace(/\$field/g, field); + msg = msg.replace(/\$name/g, name); + if (v && typeof(v) == 'string') msg = msg.replace(/\$value/g, v); + return msg; + } + + if (type == 'equals') { + var field2 = field_val["equals" + dig]; + var name2 = field_val["equals" +dig+ "_name"]; + if (! name2) name2 = "the field " +field2; + name2 = name2.replace(/\$(\d+)/g, function (all, N) { + return (typeof(ifs_match) != 'object' || typeof(ifs_match[N]) == 'undefined') ? '' : ifs_match[N]; + }); + return name + " did not equal " + name2 +"."; + } + if (type == 'min_in_set') return "Not enough fields were chosen from the set ("+v[0]+' of '+v.join(", ").replace(/^\d+,\s*/,'')+")"; + if (type == 'max_in_set') return "Too many fields were chosen from the set (" +v[0]+' of '+v.join(", ").replace(/^\d+,\s*/,'')+")"; + + return name + ( + (type == 'required' || type == 'required_if') ? " is required." + : type == 'match' ? " contains invalid characters." + : type == 'compare' ? " did not fit comparison." + : type == 'custom_js' ? " did not match custom_js"+dig+" check." + : type == 'enum' ? " is not in the given list." + : type == 'min_values' ? " had less than "+v+" value"+(v == 1 ? '' : 's')+"." + : type == 'max_values' ? " had more than "+v+" value"+(v == 1 ? '' : 's')+"." + : type == 'min_len' ? " was less than "+v+" character"+(v == 1 ? '' : 's')+"." + : type == 'max_len' ? " was more than "+v+" character"+(v == 1 ? '' : 's')+"." + : type == 'type' ? " did not match type "+v+"." + : alert("Missing error on field "+field+" for type "+type+dig)); +} - /// type can look like "required" or "required2" or "required100023" - /// allow for fallback from required100023_error through required_error - var possible_keys = new Array(type + '_error'); - if (dig.length) possible_keys.unshift(type + dig + '_error'); +// - /// look in the passed hash or self first - for (var i = 0; i < possible_keys.length; i ++) { - var key = possible_keys[i]; - var ret = field_val[key]; - if (! ret) { - if (extra[key]) ret = extra[key]; - else continue; - } - ret = ret.replace(new RegExp('\\$(\\d+)','g'), function (all, N) { - if (typeof(ifs_match) != 'object' - || typeof(ifs_match[N]) == 'undefined') return '' - return ifs_match[N]; - }); - ret = ret.replace(new RegExp('\\$field','g'), field); - ret = ret.replace(new RegExp('\\$name' ,'g'), name); - if (field_val[type + dig] && typeof(field_val[type + dig]) == 'string') - ret = ret.replace(new RegExp('\\$value' ,'g'), field_val[type + dig]); - return ret; - } +function eob_as_string (extra) { + var joiner = v_find_val('as_string_join', extra, this.extra, '\n'); + var header = v_find_val('as_string_header', extra, this.extra, ''); + var footer = v_find_val('as_string_footer', extra, this.extra, ''); + return header + this.as_array(extra).join(joiner) + footer; +} - /// set default messages - if (type == 'required' || type == 'required_if') { - return name + " is required."; - - } else if (type == 'min_values') { - var n = field_val["min_values" + dig]; - var values = (n == 1) ? 'value' : 'values'; - return name + " had less than "+n+" "+values+"."; - - } else if (type == 'max_values') { - var n = field_val["max_values" + dig]; - var values = (n == 1) ? 'value' : 'values'; - return name + " had more than "+n+" "+values+"."; - - } else if (type == 'min_in_set') { - var set = field_val["min_in_set" + dig]; - return "Not enough fields were chosen from the set ("+set+")"; - return "Too many fields were chosen from the set ("+set+")"; - - } else if (type == 'max_in_set') { - var set = field_val["max_in_set" + dig]; - return "Too many fields were chosen from the set ("+set+")"; - - } else if (type == 'enum') { - return name + " is not in the given list."; - - } else if (type == 'equals') { - var field2 = field_val["equals" + dig]; - var name2 = field_val["equals" +dig+ "_name"]; - if (! name2) name2 = "the field " +field2; - name2 = name2.replace(new RegExp('\\$(\\d+)','g'), function (all, N) { - if (typeof(ifs_match) != 'object' - || typeof(ifs_match[N]) == 'undefined') return '' - return ifs_match[N]; - }); - return name + " did not equal " + name2 +"."; +function eob_as_array (extra) { + var errors = this.errors; + var title = v_find_val('as_array_title', extra, this.extra, 'Please correct the following items:'); + + var has_headings; + if (title) has_headings = 1; + else for (var i = 0; i < errors.length; i++) if (typeof(errors[i]) == 'string') has_headings = 1; - } else if (type == 'min_len') { - var n = field_val["min_len"+dig]; - var chars = (n == 1) ? 'character' : 'characters'; - return name + " was less than "+n+" "+chars+"."; + var prefix = v_find_val('as_array_prefix', extra, this.extra, (has_headings ? ' ' : '')); - } else if (type == 'max_len') { - var n = field_val["max_len"+dig]; - var chars = (n == 1) ? 'character' : 'characters'; - return name + " was more than "+n+" "+chars+"."; + var arr = []; + if (title && title.length) arr.push(title); - } else if (type == 'match') { - return name + " contains invalid characters."; + var found = {}; + for (var i = 0; i < errors.length; i++) { + if (typeof(errors[i]) == 'string') { + arr.push(errors[i]); + found = {}; + } else { + var text = v_get_error_text(errors[i], extra, this.extra); + if (found[text]) continue; + found[text] = 1; + arr.push(prefix + text); + } + } - } else if (type == 'compare') { - return name + " did not fit comparison."; + return arr; +} - } else if (type == 'type') { - var _type = field_val["type"+dig]; - return name + " did not match type "+_type+"."; +function eob_as_hash (extra) { + var errors = this.errors; + var suffix = v_find_val('as_hash_suffix', extra, this.extra, '_error'); + var joiner = v_find_val('as_hash_join', extra, this.extra, '
'); + + var found = {}; + var ret = {}; + for (var i = 0; i < errors.length; i++) { + if (typeof(errors[i]) == 'string') continue; + if (! errors[i].length) continue; + + var field = errors[i][0]; + var type = errors[i][1]; + var field_val = errors[i][2]; + var ifs_match = errors[i][3]; + + if (! field) return alert("Missing field name"); + if (field_val['delegate_error']) { + field = field_val['delegate_error']; + field = field.replace(/\$(\d+)/g, function (all, N) { + if (typeof(ifs_match) != 'object' + || typeof(ifs_match[N]) == 'undefined') return '' + return ifs_match[N]; + }); + } - } else if (type == 'custom_js') { - return name + " did not match custom_js"+dig+" check."; + var text = v_get_error_text(errors[i], extra, this.extra); + if (! found[field]) found[field] = {}; + if (found[field][text]) continue; + found[field][text] = 1; + field += suffix; + if (! ret[field]) ret[field] = []; + ret[field].push(text); } - return alert("Missing error on field "+field+" for type "+type+dig); + if (joiner) { + var header = v_find_val('as_hash_header', extra, this.extra, ''); + var footer = v_find_val('as_hash_footer', extra, this.extra, ''); + for (var key in ret) { + if (key == 'extend') continue; // Protoype Array() + ret[key] = header + ret[key].join(joiner) + footer; + } + } + + return ret; } function eob_first_field () { for (var i = 0; i < this.errors.length; i++) { - if (typeof(this.errors[i]) != 'object') continue; - if (! this.errors[i][0]) continue; - return this.errors[i][0]; + if (typeof(this.errors[i]) != 'object') continue; + if (! this.errors[i][0]) continue; + return this.errors[i][0]; } return; } -///----------------------------------------------------------------/// +// document.validate = function (form, val_hash) { - // undo previous inline - if (document.did_inline) { - for (var key in document.did_inline) { - if (key == 'extend') continue; // Protoype Array() fix - var el = document.getElementById(key); - if (el) el.innerHTML = ''; - } - document.did_inline = undefined; - } - - // do the validate val_hash = document.load_val_hash(form, val_hash); if (typeof(val_hash) == 'undefined') return true; - if (! document.val_obj) document.val_obj = new Validate(); - var err_obj = document.val_obj.validate(form, val_hash); - // return success + for (var key in v_did_inline) { + if (key == 'extend') continue; // Protoype Array() + v_inline_error_clear(key, val_hash, form); + } + + var err_obj = v_validate(form, val_hash); if (! err_obj) return true; - // focus var field = err_obj.first_field(); - if (field && form[field] && form[field].focus) form[field].focus(); - - // inline - if (! err_obj.extra.no_inline) { - var d = document.did_inline = new Array(); - var hash = err_obj.as_hash(); - for (var key in hash) { - if (key == 'extend') continue; // Protoype Array() fix - var el = document.getElementById(key); - if (el) el.innerHTML = hash[key]; - d[key] = 1; - } - } - - // alert - if (! err_obj.extra.no_confirm) { - return confirm(err_obj.as_string()) ? false : true; - } else if (! err_obj.extra.no_alert) { - alert(err_obj.as_string()); - return false; - } else if (! err_obj.extra.no_inline) { - return false; + if (field && form[field]) { + if (form[field].focus) form[field].focus(); + else if (form[field].length && form[field][0].focus) form[field][0].focus(); + } + + if (! val_hash['group no_inline']) { + var hash = err_obj.as_hash({as_hash_suffix:""}); + for (var key in hash) { + if (key == 'extend') continue; // Protoype Array() + v_inline_error_set(key, hash[key], val_hash, form); + } + } + + if (! val_hash['group no_confirm']) { + return confirm(err_obj.as_string()) ? false : true; + } else if (! val_hash['group no_alert']) { + alert(err_obj.as_string()); + return false; + } else if (! val_hash['group no_inline']) { + return false; } else { - return true; + return true; } } document.load_val_hash = function (form, val_hash) { - // check the form we are using if (! form) return alert('Missing form or form name'); if (typeof(form) == 'string') { - if (! document[form]) return alert('No form by name '+form); - form = document[form]; + if (! document[form]) return alert('No form by name '+form); + form = document[form]; } - // if we already have validation - use it if (form.val_hash) return form.val_hash; - // load in the validation and save it for future use if (typeof(val_hash) != 'object') { - // get the hash from a javascript function - if (typeof(val_hash) == 'function') { - val_hash = val_hash(formname); - } else if (typeof(val_hash) == 'undefined') { - var el; - // get hash from a global js variable - if (typeof(document.validation) != 'undefined') { - val_hash = document.validation; - // get hash from a element by if of validation - } else if (el = document.getElementById('validation')) { - val_hash = el.innerHTML; - val_hash = val_hash.replace(new RegExp('<', 'ig'),'<'); - val_hash = val_hash.replace(new RegExp('>', 'ig'),'>'); - val_hash = val_hash.replace(new RegExp('&','ig'),'&'); - // read hash from - } else { - var order = new Array(); - var str = ''; - var yaml = form.getAttribute('validation'); - if (yaml) { - if (m = yaml.match('^( +)')) yaml = yaml.replace(new RegExp('^'+m[1], 'g'), ''); //unindent - yaml = yaml.replace(new RegExp('\\s*$',''),'\n'); // add trailing - str += yaml; - } - var m; - for (var i = 0; i < form.elements.length; i ++) { - var name = form.elements[i].name; - var yaml = form.elements[i].getAttribute('validation'); - if (! name || ! yaml) continue; - yaml = yaml.replace(new RegExp('\\s*$',''),'\n'); // add trailing - yaml = yaml.replace(new RegExp('^(.)','mg'),' $1'); // indent all - yaml = yaml.replace(new RegExp('^( *[^\\s&*\\[\\{])',''),'\n$1'); // add newline - str += name +':' + yaml; - order[order.length] = name; - } - if (str) val_hash = str + "group order: [" + order.join(', ') + "]\n"; - } - } - if (typeof(val_hash) == 'string') { - if (! document.yaml_load) return; - document.hide_yaml_errors = (! document.show_yaml_errors); - if (location.search && location.search.indexOf('show_yaml_errors') != -1) - document.hide_yaml_errors = 0; - val_hash = document.yaml_load(val_hash); - if (document.yaml_error_occured) return; - } + if (typeof(val_hash) == 'function') { + val_hash = val_hash(formname); + } else if (typeof(val_hash) == 'undefined') { + var el; + if (typeof(document.validation) != 'undefined') { + val_hash = document.validation; + } else if (el = document.getElementById('validation')) { + val_hash = el.innerHTML.replace(/</ig,'<').replace(/>/ig,'>').replace(/&/ig,'&'); + } else { + var order = []; + var str = ''; + var yaml = form.getAttribute('validation'); + if (yaml) { + if (m = yaml.match(/^( +)/)) yaml = yaml.replace(new RegExp('^'+m[1], 'g'), ''); + yaml = yaml.replace(/\s*$/,'\n'); + str += yaml; + } + var m; + for (var i = 0; i < form.elements.length; i++) { + var name = form.elements[i].name; + var yaml = form.elements[i].getAttribute('validation'); + if (! name || ! yaml) continue; + yaml = yaml.replace(/\s*$/,'\n').replace(/^(.)/mg,' $1').replace(/^( *[^\s&*\[\{])/,'\n$1'); + str += name +':' + yaml; + order.push(name); + } + if (str) val_hash = str + "group order: [" + order.join(', ') + "]\n"; + } + } + if (typeof(val_hash) == 'string') { + if (! document.yaml_load) return; + document.hide_yaml_errors = (! document.show_yaml_errors); + if (location.search && location.search.indexOf('show_yaml_errors') != -1) + document.hide_yaml_errors = 0; + val_hash = document.yaml_load(val_hash); + if (document.yaml_error_occured) return; + val_hash = val_hash[0]; + } } - // attach to the form form.val_hash = val_hash; return form.val_hash; } - document.check_form = function (form, val_hash) { - // check the form we are using if (! form) return alert('Missing form or form name'); if (typeof(form) == 'string') { - if (! document[form]) return alert('No form by name '+form); - form = document[form]; + if (! document[form]) return alert('No form by name '+form); + form = document[form]; } - // void call - allow for getting it at run time rather than later - document.load_val_hash(form, val_hash); + val_hash = document.load_val_hash(form, val_hash); + if (! val_hash) return; + + var types = val_hash['group onevent'] || {submit:1}; + if (typeof(types) == 'string') types = types.split(/\s*,\s*/); + if (typeof(types.length) != 'undefined') { + var t = {}; + for (var i = 0; i < types.length; i++) t[types[i]] = 1; + types = t; + } + val_hash['group onevent'] = types; + + if (types.change || types.blur) { + var clean = v_clean_val_hash(val_hash); + if (clean.error) return clean.error; + var h = {}; + _add = function (k, v) { if (! h[k]) h[k] = []; h[k].push(v) }; + for (var i = 0; i < clean.fields.length; i++) { + _add(clean.fields[i].field, clean.fields[i]); + for (var j in clean.fields[i].deps) if (j != clean.fields[i].field) _add(j, clean.fields[i]); + } + for (var k in h) { + if (k == 'extend') continue; // Protoype Array() + var el = form[k]; + if (! el) return v_error("No form element by the name "+k); + v_el_attach(el, h[k], form, val_hash); + } + } + + if (types.submit) { + var orig_submit = form.onsubmit || function () { return true }; + form.onsubmit = function (e) { return document.validate(this) && orig_submit(e, this) }; + } +} - // attach handler - var orig_submit = form.onsubmit || function () { return true }; - form.onsubmit = function (e) { return document.validate(this) && orig_submit(e, this) }; +function v_el_attach (el, fvs, form, val_hash) { + if (! el.type) { + if (el.length) for (var i = 0; i < el.length; i++) v_el_attach(el[i], fvs, form, val_hash); + return; + } + var types = val_hash['group onevent']; + var func = function () { + var e = []; + var f = {}; + for (var i = 0; i < fvs.length; i++) { + var field_val = fvs[i]; + var _e = v_validate_buddy(form, field_val.field, field_val); + for (var j = 0; j < _e.length; j++) e.push(_e[j]); + f[field_val.delegate_error || field_val.field] = 1; + } + if (! e.length) { + for (var k in f) v_inline_error_clear(k, val_hash, form); + return; + } + e = new ValidateError(e, {}); + e = e.as_hash({as_hash_suffix:"", first_only:(val_hash['group first_only']?1:0)}); + for (var k in e) { + if (k == 'extend') continue; // Protoype Array() + v_inline_error_set(k, e[k], val_hash, form); + } + }; + if (types.blur) el.onblur = func; + if (types.change) { + if (el.type.match(/(password|text|textarea)/)) el.onkeyup = func; + else if (el.type.match(/(checkbox|radio)/)) el.onclick = func; + else if (el.type.match(/(select)/)) el.onchange = func; + } } -// the end // +function v_inline_error_clear (key, val_hash, form) { + delete(v_did_inline[key]); + var f = val_hash['group clear_hook'] || document.validate_clear_hook; + if (typeof(f) == 'function') if (f(key, val_hash, form)) return 1; + var el = document.getElementById(key + v_find_val('as_hash_suffix', val_hash, '_error')); + if (el) el.innerHTML = ''; +} + +function v_inline_error_set (key, val, val_hash, form) { + v_did_inline[key] = 1; + var f = val_hash['group set_hook'] || document.validate_set_hook; + if (typeof(f) == 'function') if(f(key, val, val_hash, form)) return 1; + var el = document.getElementById(key + v_find_val('as_hash_suffix', val_hash, '_error')); + if (el) el.innerHTML = val; +} diff --git a/samples/app/cgi_ex_2.cgi b/samples/app/cgi_ex_2.cgi index 73f6e81..1729e52 100755 --- a/samples/app/cgi_ex_2.cgi +++ b/samples/app/cgi_ex_2.cgi @@ -118,7 +118,7 @@ sub _success_file_print {

Success


- print "I can now continue on with the rest of my script!"; + I can now continue on with the rest of my script! }; diff --git a/samples/devel/memory_app.pl b/samples/devel/memory_app.pl new file mode 100644 index 0000000..372b634 --- /dev/null +++ b/samples/devel/memory_app.pl @@ -0,0 +1,391 @@ +#!/usr/bin/perl -w + +=head1 NAME + +memory_app.pl - Test memory usage and benchmark speed comparison with CGI::Application + +=cut + +use Benchmark qw(cmpthese timethese); +use strict; + +my $swap = { + one => "ONE", + two => "TWO", + three => "THREE", + a_var => "a", + hash => {a => 1, b => 2}, + code => sub {"($_[0])"}, +}; + +my $form = q{([% has_errors %])()}; +my $str_ht = $form . (q{Well hello there ()} x 20) ."\n"; +my $str_tt = $form . (q{Well hello there ([% script_name %])} x 20) ."\n"; + +my $template_ht = \$str_ht; +my $template_tt = \$str_tt; + +###----------------------------------------------------------------### +use Scalar::Util; +use Time::HiRes; +use CGI; +use CGI::Ex::Dump qw(debug); +use Template::Alloy load => 'Parse', 'Play', 'HTML::Template', 'Template'; +$Template::VERSION = 2.18; +#use HTML::Template; + +my $tests = { + 'C::A - bare' => sub { + package FooBare; + require CGI::Application; + @FooBare::ISA = qw(CGI::Application); + + sub setup { + my $self = shift; + $self->start_mode('main'); + $self->mode_param(path_info => 1); + $self->run_modes(main => sub { "Simple test" }); + } + + FooBare->new->run; + }, + 'C::E::A - bare' => sub { + package FooBare; + require CGI::Ex::App; + @FooBare::ISA = qw(CGI::Ex::App); + + sub main_run_step { + my $self = shift; + print "Content-Type: text/html\r\n\r\n"; + #$self->cgix->print_content_type; + print "Simple test"; + 1; + } + + FooBare->navigate({form => {}}); + }, + 'Handwritten - bare' => sub { + package FooBare2; + + sub new { bless {}, __PACKAGE__ } + + sub main { + my $self = shift; + print "Content-Type: text/html\r\n\r\n"; + print "Simple test"; + } + + FooBare2->new->main; + }, + #'CGI::Prototype - bare' => sub { + # package FooBare; + # require CGI::Prototype; + #}, + + ###----------------------------------------------------------------### + + #'C::A - simple htonly' => sub { + # require CGI::Application; + # my $t = CGI::Application->new->load_tmpl($template_ht, die_on_bad_params => 0); + # $t->param(script_name => 2); + # print $t->output; + #}, + #'C::E::A - simple htonly' => sub { + # require CGI::Ex::App; + # my $out = ''; + # CGI::Ex::App->new->template_obj({SYNTAX => 'hte'})->process($template_ht, {script_name=>2}, \$out); + # print $out; + #}, + + 'C::A - simple ht' => sub { + package FooHT; + require CGI::Application; + @FooHT::ISA = qw(CGI::Application); + + sub setup { + my $self = shift; + $self->start_mode('main'); + $self->mode_param(path_info => 1); + $self->run_modes(main => sub { + my $self = shift; + my $t = $self->load_tmpl($template_ht, die_on_bad_params => 0); + $t->param('script_name', $0); + return $t->output(); + }); + } + + FooHT->new->run; + }, + 'C::E::A - simple ht' => sub { + package FooHT; + require CGI::Ex::App; + @FooHT::ISA = qw(CGI::Ex::App); + + sub main_file_print { $template_ht } + sub template_args { {SYNTAX => 'hte'} } # , GLOBAL_CACHE => 1, COMPILE_PERL => 2} } + sub fill_template {} + sub print_out { my ($self, $step, $out) = @_; print "Content-Type: text/html\r\n\r\n$$out" } + + FooHT->navigate({no_history => 1, form => {}}); + }, + 'C::A - simple tt' => sub { + package FooTT; + require CGI::Application; + @FooTT::ISA = qw(CGI::Application); + require CGI::Application::Plugin::TT; + CGI::Application::Plugin::TT->import; + + sub setup { + my $self = shift; + $self->start_mode('main'); + + $self->run_modes(main => sub { + my $self = shift; + return $self->tt_process($template_tt, {script_name => $0}); + }); + } + + FooTT->new->run; + }, + 'C::E::A - simple tt' => sub { + package FooTT; + require CGI::Ex::App; + @FooTT::ISA = qw(CGI::Ex::App); + sub main_file_print { $template_tt } + sub fill_template {} + sub print_out { my ($self, $step, $out) = @_; print "Content-Type: text/html\r\n\r\n$$out" } + FooTT->navigate({no_history => 1, form => {}}); + }, + + ###----------------------------------------------------------------### + + 'C::A - complex ht' => sub { + package FooComplexHT; + require CGI::Application; + @FooComplexHT::ISA = qw(CGI::Application); + require CGI::Application::Plugin::ValidateRM; + CGI::Application::Plugin::ValidateRM->import('check_rm'); + require CGI::Application::Plugin::FillInForm; + CGI::Application::Plugin::FillInForm->import('fill_form'); + + sub setup { + my $self = shift; + $self->start_mode('main'); + $self->mode_param(path_info => 1); + $self->run_modes(main => sub { + my $self = shift; + my ($results, $err_page) = $self->check_rm('error_page','_profile'); + return $err_page if $err_page; + die "Got here"; + }); + } + + sub error_page { + my $self = shift; + my $errs = shift; + my $t = $self->load_tmpl($template_ht, die_on_bad_params => 0); + $t->param('script_name', $0); + $t->param($errs) if $errs; + $t->param(has_errors => 1) if $errs; + my $q = $self->query; + $q->param(bar => 'BAROOSELVELT'); + return $self->fill_form(\$t->output, $q); + } + + sub _profile { return {required => [qw(bar baz)], msgs => {prefix => 'err_'}} }; + + FooComplexHT->new->run; + }, + 'C::E::A - complex ht' => sub { + package FooComplexHT; + require CGI::Ex::App; + @FooComplexHT::ISA = qw(CGI::Ex::App); + + sub main_file_print { $template_ht } + sub main_hash_fill { {bar => 'BAROOSELVELT'} } + sub main_hash_validation { {bar => {required => 1}, baz => {required => 1}} } + sub main_finalize { die "Got here" } + sub template_args { {SYNTAX => 'hte'} } # , GLOBAL_CACHE => 1, COMPILE_PERL => 2} } + sub print_out { my ($self, $step, $out) = @_; print "Content-Type: text/html\r\n\r\n$$out" } + + local $ENV{'REQUEST_METHOD'} = 'POST'; + FooComplexHT->navigate({no_history => 1, form => {}}); + }, + 'C::A - complex tt' => sub { + package FooComplexTT; + require CGI::Application; + @FooComplexTT::ISA = qw(CGI::Application); + require CGI::Application::Plugin::TT; + CGI::Application::Plugin::TT->import; + require CGI::Application::Plugin::ValidateRM; + CGI::Application::Plugin::ValidateRM->import('check_rm'); + require CGI::Application::Plugin::FillInForm; + CGI::Application::Plugin::FillInForm->import('fill_form'); + + sub setup { + my $self = shift; + $self->start_mode('main'); + + $self->run_modes(main => sub { + my $self = shift; + my ($results, $err_page) = $self->check_rm('error_page','_profile'); + return $err_page if $err_page; + die "Got here"; + }); + } + + sub error_page { + my $self = shift; + my $errs = shift; + my $out = $self->tt_process($template_tt, {script_name => $0, %{$errs || {}}, has_errors => ($errs ? 1 : 0)}); + my $q = $self->query; + $q->param(bar => 'BAROOSELVELT'); + return $self->fill_form(\$out, $q); + } + + sub _profile { return {required => [qw(bar baz)], msgs => {prefix => 'err_'}} }; + + FooComplexTT->new->run; + }, + 'C::E::A - complex tt' => sub { + package FooComplexTT; + require CGI::Ex::App; + @FooComplexTT::ISA = qw(CGI::Ex::App); + sub main_file_print { $template_tt } + sub main_hash_fill { {bar => 'BAROOSELVELT'} } + sub main_hash_validation { {bar => {required => 1}, baz => {required => 1}} } + sub main_finalize { die "Got here" } + sub print_out { my ($self, $step, $out) = @_; print "Content-Type: text/html\r\n\r\n$$out" } + + local $ENV{'REQUEST_METHOD'} = 'POST'; + FooComplexTT->navigate({no_history => 1, form => {}}); + }, + + #'Template::Alloy - bare ht' => sub { require Template::Alloy; Template::Alloy->import('HTE') }, + #'Template::Alloy - bare tt' => sub { require Template::Alloy; Template::Alloy->import('TT') }, +}; + +#perl -d:DProf samples/devel/memory_app.pl ; dprofpp tmon.out +#select($_) if open($_, ">>/dev/null"); +$tests->{'C::E::A - complex tt'}->() +# for 1 .. 1000 + ; +#exit; + +###----------------------------------------------------------------### + +my %_INC = %INC; +my @pids; +foreach my $name (sort keys %$tests) { + my $pid = fork; + if (! $pid) { + $0 = "$0 - $name"; + my $fh; + select($fh) if open($fh, ">>/dev/null"); + $tests->{$name}->() for 1 .. 1; + sleep 1; + select STDOUT; + print "$name times: (@{[times]})\n"; + print "$name $_\n" foreach sort grep {! $_INC{$_}} keys %INC; + sleep 15; + exit; + } + push @pids, $pid; +} + +sleep 2; +# print "Parent - $_\n" foreach sort keys %INC; +print grep {/\Q$0\E/} `ps fauwx`; +kill 15, @pids; + +###----------------------------------------------------------------### + +exit if grep {/no_?bench/i} @ARGV; + + +foreach my $type (qw(bare simple complex)) { + my $hash = {}; + open(my $fh, ">>/dev/null") || die "Can't access /dev/null: $!"; + foreach my $name (keys %$tests) { + next if $name !~ /\b$type\b/; + (my $copy = $name) =~ s/\s*\b$type\b//; + $hash->{$copy} = sub { + select $fh; + $tests->{$name}->(); + select STDOUT; + }; + } + print "-------------------------------------------------\n"; + print "--- Testing $type\n"; + cmpthese timethese -2, $hash; +} + +=head1 NOTES + +Abbreviations: + + C::E::A - CGI::Ex::App + C::A - CGI::Application + +The tests are currently run with the following code: + + use Template::Alloy load => 'Parse', 'Play', 'HTML::Template', 'Template'; + +This assures that CGI::Application will use the same templating system +as CGI::Ex::App so that template system issues don't affect overall +performance. With the line commented out and CGI::Application using +HTML::Template (ht), C::A has a slight speed benefit, though it still +uses more memory. With the line commented out and CGI::Application +using Template (tt), C::E::A is 2 to 3 times faster and uses a lot +less memory. + +=head1 SAMPLE OUTPUT + + paul 23927 4.3 0.5 8536 6016 pts/1 S+ 11:36 0:00 | \_ perl samples/devel/memory_app.pl + paul 23928 1.0 0.5 8988 5992 pts/1 S+ 11:36 0:00 | \_ samples/devel/memory_app.pl - C::A - bare + paul 23929 2.0 0.6 9988 7152 pts/1 S+ 11:36 0:00 | \_ samples/devel/memory_app.pl - C::A - complex ht + paul 23930 2.5 0.7 10172 7336 pts/1 S+ 11:36 0:00 | \_ samples/devel/memory_app.pl - C::A - complex tt + paul 23931 1.0 0.5 8988 6024 pts/1 S+ 11:36 0:00 | \_ samples/devel/memory_app.pl - C::A - simple ht + paul 23932 1.5 0.6 9308 6276 pts/1 S+ 11:36 0:00 | \_ samples/devel/memory_app.pl - C::A - simple tt + paul 23933 0.0 0.5 8536 5200 pts/1 S+ 11:36 0:00 | \_ samples/devel/memory_app.pl - C::E::A - bare + paul 23934 1.0 0.6 9328 6384 pts/1 S+ 11:36 0:00 | \_ samples/devel/memory_app.pl - C::E::A - complex ht + paul 23935 1.0 0.6 9328 6392 pts/1 S+ 11:36 0:00 | \_ samples/devel/memory_app.pl - C::E::A - complex tt + paul 23936 0.0 0.5 8536 5272 pts/1 S+ 11:36 0:00 | \_ samples/devel/memory_app.pl - C::E::A - simple ht + paul 23937 0.0 0.5 8668 5344 pts/1 S+ 11:36 0:00 | \_ samples/devel/memory_app.pl - C::E::A - simple tt + paul 23938 0.0 0.4 8536 5076 pts/1 S+ 11:36 0:00 | \_ samples/devel/memory_app.pl - Handwritten - bare + ------------------------------------------------- + --- Testing bare + Benchmark: running C::A -, C::E::A -, Handwritten - for at least 2 CPU seconds... + C::A -: 3 wallclock secs ( 2.08 usr + 0.01 sys = 2.09 CPU) @ 3196.17/s (n=6680) + C::E::A -: 3 wallclock secs ( 1.99 usr + 0.19 sys = 2.18 CPU) @ 6164.68/s (n=13439) + Handwritten -: 1 wallclock secs ( 2.15 usr + 0.00 sys = 2.15 CPU) @ 266711.16/s (n=573429) + Rate C::A - C::E::A - Handwritten - + C::A - 3196/s -- -48% -99% + C::E::A - 6165/s 93% -- -98% + Handwritten - 266711/s 8245% 4226% -- + ------------------------------------------------- + --- Testing simple + Benchmark: running C::A - ht, C::A - tt, C::E::A - ht, C::E::A - tt for at least 2 CPU seconds... + C::A - ht: 2 wallclock secs ( 2.04 usr + 0.00 sys = 2.04 CPU) @ 709.80/s (n=1448) + C::A - tt: 2 wallclock secs ( 2.12 usr + 0.01 sys = 2.13 CPU) @ 600.47/s (n=1279) + C::E::A - ht: 2 wallclock secs ( 2.14 usr + 0.01 sys = 2.15 CPU) @ 663.26/s (n=1426) + C::E::A - tt: 3 wallclock secs ( 2.16 usr + 0.01 sys = 2.17 CPU) @ 589.40/s (n=1279) + Rate C::E::A - tt C::A - tt C::E::A - ht C::A - ht + C::E::A - tt 589/s -- -2% -11% -17% + C::A - tt 600/s 2% -- -9% -15% + C::E::A - ht 663/s 13% 10% -- -7% + C::A - ht 710/s 20% 18% 7% -- + ------------------------------------------------- + --- Testing complex + Benchmark: running C::A - ht, C::A - tt, C::E::A - ht, C::E::A - tt for at least 2 CPU seconds... + C::A - ht: 2 wallclock secs ( 2.00 usr + 0.00 sys = 2.00 CPU) @ 438.50/s (n=877) + C::A - tt: 3 wallclock secs ( 2.16 usr + 0.00 sys = 2.16 CPU) @ 383.80/s (n=829) + C::E::A - ht: 2 wallclock secs ( 2.14 usr + 0.01 sys = 2.15 CPU) @ 457.21/s (n=983) + C::E::A - tt: 2 wallclock secs ( 2.13 usr + 0.00 sys = 2.13 CPU) @ 417.37/s (n=889) + Rate C::A - tt C::E::A - tt C::A - ht C::E::A - ht + C::A - tt 384/s -- -8% -12% -16% + C::E::A - tt 417/s 9% -- -5% -9% + C::A - ht 438/s 14% 5% -- -4% + C::E::A - ht 457/s 19% 10% 4% -- + +=cut diff --git a/samples/validate_js_0_tests.html b/samples/validate_js_0_tests.html new file mode 100644 index 0000000..e9c6604 --- /dev/null +++ b/samples/validate_js_0_tests.html @@ -0,0 +1,430 @@ + + +CGI/Ex/validate.js tests + + + + +

CGI/Ex/validate.js tests

+
+Test Form +
+ + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/samples/validate_js_1_onsubmit.html b/samples/validate_js_1_onsubmit.html new file mode 100644 index 0000000..f420527 --- /dev/null +++ b/samples/validate_js_1_onsubmit.html @@ -0,0 +1,207 @@ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Username: +
Try hitting enter rather than tab.
+ +
Password: +
+ +
Verify Password: +
+ +
Email: +
+ +
Verify Email: +
+ +
State/Region: + Specify State
+ OR Region + +
Enum Check: +
+ +
Compare Check: +
+ +
Check one: + Foo
+ Bar
+ Baz
+ +
Check two: + Foo
+ Bar
+ Baz
+ +

Fill In two: +
+ Foo
+ Bar
+ Baz
+
+ +
+
+ + + + + + + diff --git a/samples/validate_js_2_onchange.html b/samples/validate_js_2_onchange.html new file mode 100644 index 0000000..68b8fc1 --- /dev/null +++ b/samples/validate_js_2_onchange.html @@ -0,0 +1,232 @@ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Username:
Password:
Verify Password:
Email:
Verify Email:
State/Region: + Specify State
+ OR Region +
Enum Check:
Compare Check:
Check one: + Foo
+ Bar
+ Baz
+
Check two: + Foo
+ Bar
+ Baz
+

Fill In two: + Foo
+ Bar
+ Baz
+
+ +
+
+ + + + + + diff --git a/samples/validate_js_yaml_1.html b/samples/validate_js_yaml_1.html new file mode 100644 index 0000000..6187228 --- /dev/null +++ b/samples/validate_js_yaml_1.html @@ -0,0 +1,206 @@ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Username: +
Try hitting enter rather than tab.
+ +
Password: +
+ +
Verify Password: +
+ +
Email: +
+ +
Verify Email: +
+ +
State/Region: + Specify State
+ OR Region + +
Enum Check: +
+ +
Compare Check: +
+ +
Check one: + Foo
+ Bar
+ Baz
+ +
Check two: + Foo
+ Bar
+ Baz
+ +

Fill In two: +
+ Foo
+ Bar
+ Baz
+
+ +
+
+ + + + + + + diff --git a/samples/validate_js_yaml_2.html b/samples/validate_js_yaml_2.html new file mode 100644 index 0000000..1e777f3 --- /dev/null +++ b/samples/validate_js_yaml_2.html @@ -0,0 +1,119 @@ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Username: +
+ +
Password: +
+ +
Verify Password: +
+ +
Email: +
+ +
Verify Email: +
+ +
Random Association: +
(type anything - will fill in default if none)
+ +
+ +
+
+ + + + + + \ No newline at end of file diff --git a/samples/validate_js_yaml_3.html b/samples/validate_js_yaml_3.html new file mode 100644 index 0000000..02187d1 --- /dev/null +++ b/samples/validate_js_yaml_3.html @@ -0,0 +1,74 @@ + + + + + + + + + + +
+ + + + + + + + +
Enter a date (YYYY/MM/DD) greater than today:
+ () +
+
+ +
+ +
+
+ + + + + + \ No newline at end of file -- 2.43.0