mark: A photo of Mark kneeling on top of the Taal Volcano in the Philippines. It was a long hike. (Default)
Mark Smith ([staff profile] mark) wrote in [site community profile] changelog2010-01-07 05:27 am

[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 [personal profile] jadelennox.

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'>&nbsp;</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'>&nbsp;</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'>&nbsp;</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'>&nbsp;</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'>&nbsp;</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'>&nbsp;</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;
 }
--------------------------------------------------------------------------------

Post a comment in response:

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org