[dw-free] screenreader improvements for create.bml
[commit: http://hg.dwscoalition.org/dw-free/rev/f7a7aa9b6525]
http://bugs.dwscoalition.org/show_bug.cgi?id=1016
Accessibility: Add screen reader and keyboard functionality for account
creation flow.
Patch by
jadelennox.
Files modified:
http://bugs.dwscoalition.org/show_bug.cgi?id=1016
Accessibility: Add screen reader and keyboard functionality for account
creation flow.
Patch by
![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Files modified:
- bin/upgrading/en.dat
- cgi-bin/LJ/Widget/CreateAccount.pm
- cgi-bin/htmlcontrols.pl
- htdocs/js/widgets/createaccount.js
- htdocs/stc/widgets/createaccount.css
-------------------------------------------------------------------------------- diff -r 759db912e36f -r f7a7aa9b6525 bin/upgrading/en.dat --- a/bin/upgrading/en.dat Wed Jan 06 02:14:11 2010 +0000 +++ b/bin/upgrading/en.dat Thu Jan 07 05:27:39 2010 +0000 @@ -3879,12 +3879,6 @@ widget.communitymanagement.pending.membe widget.communitymanagement.title=Community Management -widget.createaccount.alt_layout.error.tos=You must agree to the Terms of Service. - -widget.createaccount.alt_layout.field.captcha=Enter the text below to help verify the authenticity of this account: - -widget.createaccount.alt_layout.field.tos=Check here to agree. - widget.createaccount.btn=Create Account widget.createaccount.error.birthdate.invalid=You must enter a valid birthdate. @@ -3897,6 +3891,8 @@ widget.createaccount.error.captcha.inval widget.createaccount.error.email.lj_domain=You cannot use a [[domain]] alias when creating an account. Please enter a different email address. +widget.createaccount.error.list=Errors in form: + widget.createaccount.error.password.asciionly=You can only use ASCII symbols in the password. widget.createaccount.error.password.bad=Bad password: @@ -3920,6 +3916,8 @@ widget.createaccount.error.password.tool widget.createaccount.error.password.toolong=Password must not be longer than 30 characters. widget.createaccount.error.password.tooshort=Password must be at least 6 characters. + +widget.createaccount.error.tos=You must agree to the Terms of Service. widget.createaccount.error.username.inuse=Sorry, this username is already in use. diff -r 759db912e36f -r f7a7aa9b6525 cgi-bin/LJ/Widget/CreateAccount.pm --- a/cgi-bin/LJ/Widget/CreateAccount.pm Wed Jan 06 02:14:11 2010 +0000 +++ b/cgi-bin/LJ/Widget/CreateAccount.pm Thu Jan 07 05:27:39 2010 +0000 @@ -40,12 +40,7 @@ sub render_body { return "$pre $msg $post"; }; - my $alt_layout = $opts{alt_layout} ? 1 : 0; my $ret; - - if ($alt_layout) { - $ret .= "<div class='signup-container'>"; - } $ret .= $class->start_form(%{$opts{form_attr}}); @@ -54,159 +49,172 @@ sub render_body { my $tip_password = LJ::ejs($class->ml('widget.createaccount.tip.password')); my $tip_username = LJ::ejs($class->ml('widget.createaccount.tip.username')); - # tip module - if ($alt_layout) { - $ret .= "<script type='text/javascript'>\n"; - $ret .= "CreateAccount.alt_layout = true;\n"; - $ret .= "</script>\n"; - } else { - $ret .= "<script type='text/javascript'>\n"; - $ret .= "CreateAccount.birthdate = \"$tip_birthdate\"\n"; - $ret .= "CreateAccount.email = \"$tip_email\"\n"; - $ret .= "CreateAccount.password = \"$tip_password\"\n"; - $ret .= "CreateAccount.username = \"$tip_username\"\n"; - $ret .= "</script>\n"; - $ret .= "<div id='tips_box_arrow'></div>"; - $ret .= "<div id='tips_box'></div>"; + $ret .= "<script type='text/javascript'>\n"; + $ret .= "CreateAccount.birthdate = \"$tip_birthdate\"\n"; + $ret .= "CreateAccount.email = \"$tip_email\"\n"; + $ret .= "CreateAccount.password = \"$tip_password\"\n"; + $ret .= "CreateAccount.username = \"$tip_username\"\n"; + $ret .= "</script>\n"; + + # Errors container, listed in a TOC for screen-reader convenience + # Don't even build if there are no errors in the page + # IMPORTANT: The placement of this list in the HTML is necessary for + # screen readers to announce it correctly after form submission. If you want to + # move it, use CSS. + if ( keys %$errors ) { + $ret .= "<div tabindex=1 id='error-list' class='error-list' role='alert'>"; + $ret .= "<h2 class='nav' id='errorlist_label'>" + . LJ::ejs($class->ml('widget.createaccount.error.list')) + . "</h2>"; + $ret .= "<ol role='alert' labelledby='errorlist_label'>"; + + # Print out all of the error messages that exist. + # Do this manually as opposed to in a for loop in order to guarantee the order + # matches the layout of the page + $ret .= $error_msg->('username', '<li class="formitemFlag" role="alert">', '</li>'); + $ret .= $error_msg->('email', '<li class="formitemFlag" role="alert">', '</li>'); + $ret .= $error_msg->('password', '<li class="formitemFlag" role="alert">', '</li>'); + $ret .= $error_msg->('confirmpass', '<li class="formitemFlag" role="alert">', '</li>'); + $ret .= $error_msg->('bday', '<li class="formitemFlag" role="alert">', '</li>'); + $ret .= $error_msg->('captcha', '<li class="formitemFlag" role="alert">', '</li>'); + $ret .= $error_msg->('tos', '<li class="formitemFlag" role="alert">', '</li>'); + + $ret .= "</ol>"; + $ret .= "</div> <!-- error-list -->\n"; } - $ret .= "<table class='create-form' cellspacing='0' cellpadding='3'>\n" unless $alt_layout; + # FIXME: this table should be converted to fieldsets and css layout + # instead of tables for maximum accessibility. Eventually. + + $ret .= "<div class='relative-container'>\n"; + $ret .= "<div id='tips_box_arrow'></div>"; + $ret .= "<div id='tips_box'></div>"; + $ret .= "<table class='create-form' cellspacing='0' cellpadding='3'>\n"; ### username - if ($alt_layout) { - $ret .= "<label for='create_user' class='label_create'>" . $class->ml('widget.createaccount.field.username') . "</label>"; - $ret .= "<div class='bubble' id='bubble_user'>"; - $ret .= "<div class='bubble-arrow'></div>"; - $ret .= "<div class='bubble-text'>$tip_username</div>"; - $ret .= "</div>"; - } else { - $ret .= "<tr><td class='field-name'>" . $class->ml('widget.createaccount.field.username') . "</td>\n<td>"; - } + + # Highlight the field if the user needs to fix errors + my $label_username = $errors->{'username'} ? "errors-present" : "errors-absent"; + + $ret .= "<tr><td class='$label_username'>" + . $class->ml('widget.createaccount.field.username') + . "</td>\n<td>"; + # maxlength 26, so if people don't notice that they hit the limit, # we give them a warning. (some people don't notice/proofread) $ret .= $class->html_text( name => 'user', id => 'create_user', - size => $alt_layout ? undef : 20, + size => 20, maxlength => 26, - raw => 'style="<?loginboxstyle?>"', + raw => 'tabindex=1 style="<?loginboxstyle?>" aria-required="true"', value => $post->{user} || $get->{user}, ); - $ret .= " <img id='username_check' src='$LJ::IMGPREFIX/create/check.png' alt='" . $class->ml('widget.createaccount.field.username.available') . "' title='" . $class->ml('widget.createaccount.field.username.available') . "' />"; - $ret .= $error_msg->('username', '<span id="username_error_main"><br /><span class="formitemFlag">', '</span></span>'); - $ret .= "<span id='username_error'><br /><span id='username_error_inner' class='formitemFlag'></span></span>"; - $ret .= "</td></tr>\n" unless $alt_layout; + + # If JavaScript is available, check to see if the username is available + # before submitting the form. Make sure that responses are returned as + # ARIA live region for screen reader compatibility. + $ret .= " <img id='username_check' src='$LJ::IMGPREFIX/create/check.png' alt='" + . $class->ml('widget.createaccount.field.username.available') + . "' title='" + . $class->ml('widget.createaccount.field.username.available') + . "' aria-live='polite' />"; + $ret .= "<span id='username_error'><br /><span id='username_error_inner' class='formitemFlag' role='alert'></span></span>"; + + $ret .= "</td></tr>\n"; ### email - if ($alt_layout) { - $ret .= "<label for='create_email' class='label_create'>" . $class->ml('widget.createaccount.field.email') . "</label>"; - $ret .= "<div class='bubble' id='bubble_email'>"; - $ret .= "<div class='bubble-arrow'></div>"; - $ret .= "<div class='bubble-text'>$tip_email</div>"; - $ret .= "</div>"; - } else { - $ret .= "<tr><td class='field-name'>" . $class->ml('widget.createaccount.field.email') . "</td>\n<td>"; - } + + # Highlight the field if the user needs to fix errors + my $label_email = $errors->{'email'} ? "errors-present" : "errors-absent"; + + $ret .= "<tr><td class='$label_email'>" + . $class->ml('widget.createaccount.field.email') + . "</td>\n<td>"; $ret .= $class->html_text( name => 'email', id => 'create_email', size => 28, maxlength => 50, + raw => 'tabindex=1 aria-required="true"', value => $post->{email}, ); - $ret .= $error_msg->('email', '<br /><span class="formitemFlag">', '</span>'); - $ret .= "</td></tr>\n" unless $alt_layout; + $ret .= "</td></tr>\n"; ### password + + # Highlight the field if the user needs to fix errors + my $label_password = $errors->{'password'} ? "errors-present" : "errors-absent"; + my $pass_value = $errors->{password} ? "" : $post->{password1}; - if ($alt_layout) { - $ret .= "<label for='create_password1' class='label_create'>" . $class->ml('widget.createaccount.field.password') . "</label>"; - $ret .= "<div class='bubble' id='bubble_password1'>"; - $ret .= "<div class='bubble-arrow'></div>"; - $ret .= "<div class='bubble-text'>$tip_password</div>"; - $ret .= "</div>"; - } else { - $ret .= "<tr><td class='field-name'>" . $class->ml('widget.createaccount.field.password') . "</td>\n<td>"; - } + + $ret .= "<tr><td class='$label_password'>" + . $class->ml('widget.createaccount.field.password') + . "</td>\n<td>"; $ret .= $class->html_text( name => 'password1', id => 'create_password1', size => 28, maxlength => 31, type => "password", + raw => 'tabindex=1 aria-required="true"', value => $pass_value, ); - $ret .= $error_msg->('password', '<br /><span class="formitemFlag">', '</span>'); - $ret .= "</td></tr>\n" unless $alt_layout; + $ret .= "</td></tr>\n"; ### confirm password - if ($alt_layout) { - $ret .= "<label for='create_password2' class='label_create'>" . $class->ml('widget.createaccount.field.confirmpassword') . "</label>"; - $ret .= "<div class='bubble' id='bubble_password1'>"; - $ret .= "<div class='bubble-arrow'></div>"; - $ret .= "<div class='bubble-text'>$tip_password</div>"; - $ret .= "</div>"; - } else { - $ret .= "<tr><td class='field-name'>" . $class->ml('widget.createaccount.field.confirmpassword') . "</td>\n<td>"; - } + + # Highlight the field if the user needs to fix errors + my $label_confirmpass = $errors->{'confirmpass'} ? "errors-present" : "errors-absent"; + + $ret .= "<tr><td class='$label_confirmpass'>" + . $class->ml('widget.createaccount.field.confirmpassword') + . "</td>\n<td>"; $ret .= $class->html_text( name => 'password2', id => 'create_password2', size => 28, maxlength => 31, type => "password", + raw => 'tabindex=1 aria-required="true"', value => $pass_value, ); - $ret .= $error_msg->('confirmpass', '<br /><span class="formitemFlag">', '</span>'); - $ret .= "</td></tr>\n" unless $alt_layout; + $ret .= "</td></tr>\n"; ### birthdate - if ($alt_layout) { - $ret .= "<label for='create_bday_mm' class='label_create'>" . $class->ml('widget.createaccount.field.birthdate') . "</label>"; - $ret .= "<div class='bubble' id='bubble_bday_mm'>"; - $ret .= "<div class='bubble-arrow'></div>"; - $ret .= "<div class='bubble-text'>$tip_birthdate</div>"; - $ret .= "</div>"; - $ret .= $class->html_select( - name => "bday_mm", - id => "create_bday_mm", - selected => $post->{bday_mm} || 1, - list => [ map { $_, LJ::Lang::month_long_ml( $_ ) } (1..12) ], - ) . " "; - $ret .= $class->html_text( - name => "bday_dd", - id => "create_bday_dd", - class => 'date', - maxlength => '2', - value => $post->{bday_dd} || "", - ); - $ret .= $class->html_text( - name => "bday_yyyy", - id => "create_bday_yyyy", - class => 'year', - maxlength => '4', - value => $post->{bday_yyyy} || "", - ); - } else { - $ret .= "<tr><td class='field-name'>" . $class->ml('widget.createaccount.field.birthdate') . "</td>\n<td>"; - $ret .= $class->html_datetime( - name => 'bday', - id => 'create_bday', - notime => 1, - default => sprintf("%04d-%02d-%02d", $post->{bday_yyyy}, $post->{bday_mm}, $post->{bday_dd}), - ); - } - $ret .= $error_msg->('bday', '<br /><span class="formitemFlag">', '</span>'); - $ret .= "</td></tr>\n" unless $alt_layout; + + # Highlight the field if the user needs to fix errors + my $label_bday = $errors->{'bday'} ? "errors-present" : "errors-absent"; + + + $ret .= "<tr>" + . "<td class='$label_bday'><label for='create_bday_mm'>" + . $class->ml('widget.createaccount.field.birthdate') + . "</label></td>\n<td>"; + $ret .= $class->html_datetime( + name => 'bday', + id => 'create_bday', + raw => 'aria-required="true" tabindex=1', + notime => 1, + default => sprintf("%04d-%02d-%02d", $post->{bday_yyyy}, $post->{bday_mm}, $post->{bday_dd}), + ); + + $ret .= "</td></tr>\n"; ### captcha + + # Highlight the field if the user needs to fix errors + # NOTE: Because captcha is not currently in use on + # dreamwidth.org, and because its accessibility is negligible + # at best, WAI-ARIA code is not wrapped around the + # captcha functionality. + my $label_captcha = $errors->{'captcha'} ? "errors-present" : "errors-absent"; + if ($LJ::HUMAN_CHECK{create}) { if (LJ::is_enabled("recaptcha")) { - if ($alt_layout) { - $ret .= "<label class='text'>" . $class->ml('widget.createaccount.alt_layout.field.captcha') . "</label>"; - } else { - $ret .= "<tr valign='top'><td class='field-name'>" . $class->ml('widget.createaccount.field.captcha') . "</td>\n<td>"; - } + $ret .= "<tr valign='top'><td class='$label_captcha'>" + . $class->ml('widget.createaccount.field.captcha') + . "</td>\n<td>"; my $c = Captcha::reCAPTCHA->new; $ret .= $c->get_options_setter({ theme => 'white' }); @@ -230,7 +238,9 @@ sub render_body { $captcha_chal = $captcha_chal || LJ::challenge_generate(900); $captcha_sess = LJ::get_challenge_attributes($captcha_chal); - $ret .= "<tr valign='top'><td class='field-name'>" . $class->ml('widget.createaccount.field.captcha') . "</td>\n<td>"; + $ret .= "<tr valign='top'><td class='$label_captcha'>" + . $class->ml('widget.createaccount.field.captcha') + . "</td>\n<td>"; if ($wants_audio || $post->{audio_chal}) { # audio my $url = $capid && $anum ? # previously entered correctly @@ -258,69 +268,43 @@ sub render_body { $ret .= $class->html_hidden( captcha_chal => $captcha_chal ); } - $ret .= $error_msg->('captcha', '<span class="formitemFlag">', '</span><br />'); $ret .= "</td></tr>\n"; } - if ($alt_layout) { - $ret .= "<p class='terms'>"; + ### TOS - ### TOS - my $tos_string = $class->ml( 'widget.createaccount.alt_layout.tos', { sitename => $LJ::SITENAMESHORT } ); - if ( $tos_string ) { - $ret .= "$tos_string<br />"; - $ret .= $class->html_check( - name => 'tos', - id => 'create_tos', - value => '1', - selected => LJ::did_post() ? $post->{tos} : 0, - ); - $ret .= " <label for='create_tos' class='text'>" . $class->ml( 'widget.createaccount.alt_layout.field.tos' ) . "</label><br /><br />"; - } else { - $ret .= LJ::html_hidden( tos => 1 ); - } + # Highlight the field if the user needs to fix errors + my $label_tos = $errors->{'tos'} ? "errors-present" : "errors-absent"; + + # site news + $ret .= "<tr valign='top'><td class='field-name'> </td>\n<td>"; + $ret .= $class->html_check( + name => 'news', + id => 'create_news', + value => '1', + raw => 'tabindex=1', + selected => LJ::did_post() ? $post->{news} : 1, + label => $class->ml('widget.createaccount.field.news', { sitename => $LJ::SITENAMESHORT }), + ); + $ret .= "</td></tr>\n"; - ### site news - $ret .= $class->html_check( - name => 'news', - id => 'create_news', - value => '1', - selected => LJ::did_post() ? $post->{news} : 0, - ); - $ret .= " <label for='create_news' class='text'>" . $class->ml('widget.createaccount.field.news', { sitename => $LJ::SITENAMESHORT }) . "</label>"; - - $ret .= "</p>"; - $ret .= $error_msg->('tos', '<span class="formitemFlag">', '</span><br />'); - } else { - ### site news - $ret .= "<tr valign='top'><td class='field-name'> </td>\n<td>"; - $ret .= $class->html_check( - name => 'news', - id => 'create_news', - value => '1', - selected => LJ::did_post() ? $post->{news} : 1, - label => $class->ml('widget.createaccount.field.news', { sitename => $LJ::SITENAMESHORT }), - ); - $ret .= "</td></tr>\n"; - - ### TOS - $ret .= "<tr valign='top'><td class='field-name'> </td>\n<td>"; - $ret .= $class->html_check( - name => 'tos', - id => 'create_tos', - value => '1', - selected => LJ::did_post() ? $post->{tos} : 0, - ); - $ret .= " <label for='create_tos' class='text'>"; - $ret .= $class->ml( 'widget.createaccount.field.tos', { - sitename => $LJ::SITENAMESHORT, - aopts1 => "href='$LJ::SITEROOT/legal/tos' target='_new'", - aopts2 => "href='$LJ::SITEROOT/legal/privacy' target='_new'", - } ); - $ret .= "</label>"; - $ret .= $error_msg->( 'tos', '<span class="formitemFlag">', '</span><br />' ); - $ret .= "</td></tr>\n"; - } + # TOS + $ret .= "<tr valign='top'><td class='$label_tos'> </td>\n<td>"; + $ret .= $class->html_check( + name => 'tos', + id => 'create_tos', + value => '1', + raw => 'tabindex=1', + selected => LJ::did_post() ? $post->{tos} : 0, + ); + $ret .= " <label for='create_tos' class='text'>"; + $ret .= $class->ml( 'widget.createaccount.field.tos', { + sitename => $LJ::SITENAMESHORT, + aopts1 => "href='$LJ::SITEROOT/legal/tos' target='_new'", + aopts2 => "href='$LJ::SITEROOT/legal/privacy' target='_new'", + } ); + $ret .= "</label>"; + $ret .= "</td></tr>\n"; if ( $LJ::USE_ACCT_CODES && !DW::InviteCodes->is_promo_code( code => $code ) ) { my $item = DW::InviteCodes->paid_status( code => $code ); @@ -336,24 +320,21 @@ sub render_body { } ### submit button - if ($alt_layout) { - $ret .= $class->html_submit( submit => $class->ml('widget.createaccount.btn'), { class => "login-button" }) . "\n"; - } else { - $ret .= "<tr valign='top'><td class='field-name'> </td>\n<td>"; - $ret .= $class->html_submit( submit => $class->ml('widget.createaccount.btn'), { class => "create-button" }) . "\n"; - $ret .= "</td></tr>\n"; - } - - $ret .= "</table>\n" unless $alt_layout; + $ret .= "<tr valign='top'><td class='field-name'> </td>\n<td>"; + $ret .= $class->html_submit( + submit => $class->ml('widget.createaccount.btn'), + { class => "create-button", + raw => 'tabindex=1', + }, + ) . "\n"; + $ret .= "</td></tr>\n"; + $ret .= "</table>\n"; + $ret .= "</div> <!-- relative-container -->\n"; $ret .= $class->html_hidden( from => $from ) if $from; $ret .= $class->html_hidden( code => $code ) if $LJ::USE_ACCT_CODES; $ret .= $class->end_form; - - if ($alt_layout) { - $ret .= "</div>"; - } return $ret; } @@ -365,7 +346,6 @@ sub handle_post { my %from_post; my $remote = LJ::get_remote(); - my $alt_layout = $opts{alt_layout} ? 1 : 0; # flag to indicate they've submitted with 'audio' as the answer to the captcha my $wants_audio = $from_post{wants_audio} = 0; @@ -504,7 +484,7 @@ sub handle_post { } # check TOS agreement - $from_post{errors}->{tos} = $class->ml( 'widget.createaccount.alt_layout.error.tos' ) unless $post->{tos}; + $from_post{errors}->{tos} = $class->ml( 'widget.createaccount.error.tos' ) unless $post->{tos}; # create user and send email as long as the user didn't double-click submit # (or they tried to re-create a purged account) diff -r 759db912e36f -r f7a7aa9b6525 cgi-bin/htmlcontrols.pl --- a/cgi-bin/htmlcontrols.pl Wed Jan 06 02:14:11 2010 +0000 +++ b/cgi-bin/htmlcontrols.pl Thu Jan 07 05:27:39 2010 +0000 @@ -49,28 +49,60 @@ sub html_datetime $5 > 0 ? $5 : "", $6 > 0 ? $6 : ""); } - $ret .= html_select({ 'name' => "${name}_mm", 'id' => "${id}_mm", 'selected' => $mm, 'class' => 'select', - 'disabled' => $disabled, %extra_opts }, - map { $_, LJ::Lang::month_long_ml($_) } (1..12)); - $ret .= html_text({ 'name' => "${name}_dd", 'id' => "${id}_dd", 'size' => '2', 'class' => 'text', - 'maxlength' => '2', 'value' => $dd, - 'disabled' => $disabled, %extra_opts }); - $ret .= html_text({ 'name' => "${name}_yyyy", 'id' => "${id}_yyyy", 'size' => '4', 'class' => 'text', - 'maxlength' => '4', 'value' => $yyyy, - 'disabled' => $disabled, %extra_opts }); + $ret .= html_select({ 'name' => "${name}_mm", + 'id' => "${id}_mm", + 'selected' => $mm, + 'class' => 'select', + 'title' => 'month', + 'disabled' => $disabled, %extra_opts, + }, + map { $_, LJ::Lang::month_long_ml($_) } (1..12)); + $ret .= html_text({ 'name' => "${name}_dd", + 'id' => "${id}_dd", + 'size' => '2', + 'class' => 'text', + 'maxlength' => '2', + 'value' => $dd, + 'title' => 'day', + 'disabled' => $disabled, %extra_opts, + }); + $ret .= html_text({ 'name' => "${name}_yyyy", + 'id' => "${id}_yyyy", + 'size' => '4', + 'class' => 'text', + 'maxlength' => '4', + 'value' => $yyyy, + 'title' => 'year', + 'disabled' => $disabled, %extra_opts, + }); unless ($opts->{'notime'}) { $ret .= ' '; - $ret .= html_text({ 'name' => "${name}_hh", 'id' => "${id}_hh", 'size' => '2', - 'maxlength' => '2', 'value' => $hh, - 'disabled' => $disabled }) . ':'; - $ret .= html_text({ 'name' => "${name}_nn", 'id' => "${id}_nn", 'size' => '2', - 'maxlength' => '2', 'value' => $nn, - 'disabled' => $disabled }); + $ret .= html_text({ 'name' => "${name}_hh", + 'id' => "${id}_hh", + 'size' => '2', + 'maxlength' => '2', + 'value' => $hh, + 'title' => 'hour', + 'disabled' => $disabled, + }) . ':'; + $ret .= html_text({ 'name' => "${name}_nn", + 'id' => "${id}_nn", + 'size' => '2', + 'maxlength' => '2', + 'value' => $nn, + 'title' => 'minutes', + 'disabled' => $disabled, + }); if ($opts->{'seconds'}) { $ret .= ':'; - $ret .= html_text({ 'name' => "${name}_ss", 'id' => "${id}_ss", 'size' => '2', - 'maxlength' => '2', 'value' => $ss, - 'disabled' => $disabled }); + $ret .= html_text({ 'name' => "${name}_ss", + 'id' => "${id}_ss", + 'size' => '2', + 'maxlength' => '2', + 'value' => $ss, + 'title' => 'seconds', + 'disabled' => $disabled, + }); } } diff -r 759db912e36f -r f7a7aa9b6525 htdocs/js/widgets/createaccount.js --- a/htdocs/js/widgets/createaccount.js Wed Jan 06 02:14:11 2010 +0000 +++ b/htdocs/js/widgets/createaccount.js Thu Jan 07 05:27:39 2010 +0000 @@ -18,15 +18,13 @@ CreateAccount.init = function () { DOM.addEventListener($('create_bday_dd'), "focus", CreateAccount.eventShowTip.bindEventListener("create_bday_mm")); DOM.addEventListener($('create_bday_yyyy'), "focus", CreateAccount.eventShowTip.bindEventListener("create_bday_mm")); - if (CreateAccount.alt_layout) { - DOM.addEventListener($('create_user'), "blur", CreateAccount.eventHideTip.bindEventListener("create_user")); - DOM.addEventListener($('create_email'), "blur", CreateAccount.eventHideTip.bindEventListener("create_email")); - DOM.addEventListener($('create_password1'), "blur", CreateAccount.eventHideTip.bindEventListener("create_password1")); - DOM.addEventListener($('create_password2'), "blur", CreateAccount.eventHideTip.bindEventListener("create_password1")); - DOM.addEventListener($('create_bday_mm'), "blur", CreateAccount.eventHideTip.bindEventListener("create_bday_mm")); - DOM.addEventListener($('create_bday_dd'), "blur", CreateAccount.eventHideTip.bindEventListener("create_bday_mm")); - DOM.addEventListener($('create_bday_yyyy'), "blur", CreateAccount.eventHideTip.bindEventListener("create_bday_mm")); - } + DOM.addEventListener($('create_user'), "blur", CreateAccount.eventHideTip.bindEventListener("create_user")); + DOM.addEventListener($('create_email'), "blur", CreateAccount.eventHideTip.bindEventListener("create_email")); + DOM.addEventListener($('create_password1'), "blur", CreateAccount.eventHideTip.bindEventListener("create_password1")); + DOM.addEventListener($('create_password2'), "blur", CreateAccount.eventHideTip.bindEventListener("create_password1")); + DOM.addEventListener($('create_bday_mm'), "blur", CreateAccount.eventHideTip.bindEventListener("create_bday_mm")); + DOM.addEventListener($('create_bday_dd'), "blur", CreateAccount.eventHideTip.bindEventListener("create_bday_mm")); + DOM.addEventListener($('create_bday_yyyy'), "blur", CreateAccount.eventHideTip.bindEventListener("create_bday_mm")); if (!$('username_check')) return; if (!$('username_error')) return; @@ -50,51 +48,46 @@ CreateAccount.showTip = function (id) { if (!id) return; var drop, arrowdrop, text; - if (CreateAccount.alt_layout) { - CreateAccount.bubbleid = id.replace(/create/, 'bubble'); - if ($(CreateAccount.bubbleid)) { - $(CreateAccount.bubbleid).style.visibility = "visible"; - } - } else { - if (id == "create_bday_mm") { - text = CreateAccount.birthdate; - drop = 40; - arrowdrop = 53; - } else if (id == "create_email") { - text = CreateAccount.email; - drop = 10; - arrowdrop = 13; - } else if (id == "create_password1") { - text = CreateAccount.password; - drop = 20; - arrowdrop = 24; - } else if (id == "create_user") { - text = CreateAccount.username; - drop = 0; - arrowdrop = 3; - } - var box = $('tips_box'), box_arr = $('tips_box_arrow'); - if (box && box_arr) { - box.innerHTML = text; + // Create the location for the tooltip + if (id == "create_bday_mm") { + text = CreateAccount.birthdate; + drop = 40; + arrowdrop = 53; + } else if (id == "create_email") { + text = CreateAccount.email; + drop = 10; + arrowdrop = 13; + } else if (id == "create_password1") { + text = CreateAccount.password; + drop = 20; + arrowdrop = 24; + } else if (id == "create_user") { + text = CreateAccount.username; + drop = 0; + arrowdrop = 3; + } - box.style.top = drop + "%"; - box.style.display = "block"; + var box = $('tips_box'), box_arr = $('tips_box_arrow'); + if (box && box_arr) { + box.innerHTML = text; - box_arr.style.top = arrowdrop + "%"; - box_arr.style.display = "block"; - } + box.style.top = drop + "%"; + box.style.display = "block"; + box.style.visibility = "visible"; + + box_arr.style.top = arrowdrop + "%"; + box_arr.style.display = "block"; } } CreateAccount.hideTip = function (id) { if (!id) return; - if (CreateAccount.alt_layout) { - if ($(CreateAccount.bubbleid)) { - $(CreateAccount.bubbleid).style.visibility = "hidden"; - } - } + // Set the tip to the empty string instead of just relying + // on CSS to maximize accessibility + $('tips_box').style.visibility = "hidden"; + $('tips_box').innerHTML = ""; } CreateAccount.checkUsername = function () { @@ -110,11 +103,13 @@ CreateAccount.checkUsername = function ( $('username_error_inner').innerHTML = data.error; $('username_check').style.display = "none"; $('username_error').style.display = "inline"; + $('create_user').setAttribute("aria-invalid", "true"); } else { if ($('username_error_main')) $('username_error_main').style.display = "none"; $('username_error').style.display = "none"; $('username_check').style.display = "inline"; + $('create_user').setAttribute("aria-invalid", "false"); } CreateAccount.showTip(CreateAccount.id); // recalc }, diff -r 759db912e36f -r f7a7aa9b6525 htdocs/stc/widgets/createaccount.css --- a/htdocs/stc/widgets/createaccount.css Wed Jan 06 02:14:11 2010 +0000 +++ b/htdocs/stc/widgets/createaccount.css Thu Jan 07 05:27:39 2010 +0000 @@ -1,3 +1,26 @@ +/* Relative container allows a parent container to something which + needs absolute positioning around it */ +.relative-container { + position: relative; +} + +.error-list { + background-color: #fcf6db; + border: 1px solid #ffdfc0; + padding: 1em; + padding-left: 3em; + margin-top: 3em; +} + +div.error-list ol, div.error-list li { + list-style-type: decimal; +} + +.errors-present { + font-weight: bold; + color: #f00; +} + .appwidget-createaccount .field-name { text-align: right; width: 125px; @@ -54,9 +77,7 @@ span.appwidget-createaccount #username_e font-weight: bold; } -.create-form span.formitemFlag { - display: block; - width: 330px; +li.formitemFlag { font-weight: bold; color: #f00; } --------------------------------------------------------------------------------