Start using some HTML 5 form features
authorAryeh Gregor <simetrical@users.mediawiki.org>
Fri, 7 Aug 2009 03:32:20 +0000 (03:32 +0000)
committerAryeh Gregor <simetrical@users.mediawiki.org>
Fri, 7 Aug 2009 03:32:20 +0000 (03:32 +0000)
autofocus attribute added in some places; this looks like it's respected
by both recent Opera and recent WebKit.  Its function is
self-explanatory.  :)  I used this in a few obvious places like
Special:UserLogin and Special:ResetPass to focus the first field in the
form.  Could be used in other places too: Special:Search, etc.

required attribute added in some places.  This is only supported in
recent Opera at the moment.  Also self-explanatory: it won't allow form
submission if the field is empty.

For stuff using HTMLForm (i.e., Special:Preferences), validation will be
done for integers and floats.  Browsers that support this (recent Opera)
will not allow non-integers to be submitted for integer fields, will not
allow non-floating-point values to be submitted for float fields, and
will enforce any min/max values specified.  Opera also gives little up
and down arrows to allow the user to increment/decrement the value in
addition to letting them edit the field as text.

For HTMLForm and account creation, the email input type is used for
e-mails.  This enforces a sane set of values for e-mails (alphanumerics
plus some ASCII punctuation, with an @ in it).  Again, this is supported
only by recent Opera (yay Opera!).  Note that this is actually more
restrictive than what we currently check for on the server side; it
might be sane to tighten up our server-side checks to forbid e-mail
addresses that HTML 5 forbids.

In all cases, the extra features aren't added if $wgHtml5 is false, and
will be ignored by non-supporting browsers.

The major room for further improvement here is use of the pattern
attribute.  We can have the client refuse to submit the form unless it
matches a regex!  The HTML 5 spec says that if a title attribute is
provided, it should be a message that explains what the valid values
are and browsers should provide it to the user if the regex doesn't
match, so it's not a usability problem.  I didn't bother adding that
anywhere at this point because it would require adding new messages, but
it should be easy to do.  Note of course that HTMLForm should be updated
to verify that pattern matches on the server side as well -- this way we
have a clean, unified way of ensuring that our client and server checks
are the same.

RELEASE-NOTES
includes/HTMLForm.php
includes/Preferences.php
includes/specials/SpecialResetpass.php
includes/templates/Userlogin.php

index f7a3700..d4fb8c0 100644 (file)
@@ -71,7 +71,6 @@ this. Was used when mwEmbed was going to be an extension.
   interface will not appear in Special:AllMessages.
 * $wgRegisterInternalExternals can be used to record external links pointing 
   to same server
-* $wgHtml5 outputs an HTML 5 doctype instead of XHTML 1.0 Transitional.
 * $wgSpecialVersionExtended shows the extended version information besides 
   PHP and database version.
 * $wgSecondaryGoNamespaces allows an arry of namespaces to be checked when the
@@ -188,6 +187,13 @@ this. Was used when mwEmbed was going to be an extension.
 * wgMainPageTitle variable now available to JavaScript code to identify the main
   page link, so it doesn't have to be extracted from the link URLs.
 * (bug 16836) Display preview of signature in user preferences and describe its use
+* The default output format is now HTML 5 instead of XHTML 1.0 Transitional.
+  This can be disabled by setting $wgHtml5 = false;.  Specific features enabled
+  if HTML 5 is used:
+** New HTML 5 input attributes allow JavaScript-free input validation in some
+   cutting-edge browsers.  E.g., some inputs will be autofocused, users will
+   not be allowed to submit forms with certain types of invalid values (like
+   numbers outside the permitted ranges), etc.
 
 === Bug fixes in 1.16 ===
 
index 619a069..30f85be 100644 (file)
@@ -23,6 +23,10 @@ class HTMLForm {
                'float' => 'HTMLFloatField',
                'info' => 'HTMLInfoField',
                'selectorother' => 'HTMLSelectOrOtherField',
+               # HTMLTextField will output the correct type="" attribute automagically.
+               # There are about four zillion other HTML 5 input types, like url, but
+               # we don't use those at the moment, so no point in adding all of them.
+               'email' => 'HTMLTextField',
        );
 
        function __construct( $descriptor, $messagePrefix ) {
@@ -512,22 +516,53 @@ abstract class HTMLFormField {
 }
 
 class HTMLTextField extends HTMLFormField {
-
        function getSize() {
                return isset( $this->mParams['size'] ) ? $this->mParams['size'] : 45;
        }
 
        function getInputHTML( $value ) {
+               global $wgHtml5;
                $attribs = array( 'id' => $this->mID );
 
                if ( isset( $this->mParams['maxlength'] ) ) {
                        $attribs['maxlength'] = $this->mParams['maxlength'];
                }
                
-               if( !empty( $this->mParams['disabled'] ) ) {
+               if ( !empty( $this->mParams['disabled'] ) ) {
                        $attribs['disabled'] = 'disabled';
                }
 
+               if ( $wgHtml5 ) {
+                       # TODO: Enforce pattern, step, required, readonly on the server
+                       # side as well
+                       foreach ( array( 'min', 'max', 'pattern', 'title', 'step',
+                       'placeholder' ) as $param ) {
+                               if ( isset( $this->mParams[$param] ) ) {
+                                       $attribs[$param] = $this->mParams[$param];
+                               }
+                       }
+                       foreach ( array( 'required', 'autofocus', 'multiple', 'readonly' )
+                       as $param ) {
+                               if ( isset( $this->mParams[$param] ) ) {
+                                       $attribs[$param] = '';
+                               }
+                       }
+                       if ( isset( $this->mParams['type'] ) ) {
+                               switch ( $this->mParams['type'] ) {
+                               case 'email':
+                                       $attribs['type'] = 'email';
+                                       break;
+                               case 'int':
+                                       $attribs['type'] = 'number';
+                                       break;
+                               case 'float':
+                                       $attribs['type'] = 'number';
+                                       $attribs['step'] = 'any';
+                                       break;
+                               }
+                       }
+               }
+
                return Xml::input(
                        $this->mName,
                        $this->getSize(),
@@ -535,7 +570,6 @@ class HTMLTextField extends HTMLFormField {
                        $attribs
                );
        }
-
 }
 
 class HTMLFloatField extends HTMLTextField {
index 19e1c80..308a9c2 100644 (file)
@@ -334,7 +334,7 @@ class Preferences {
        
                        $defaultPreferences['emailaddress'] =
                                        array(
-                                               'type' => $wgAuth->allowPropChange( 'emailaddress' ) ? 'text' : 'info',
+                                               'type' => $wgAuth->allowPropChange( 'emailaddress' ) ? 'email' : 'info',
                                                'default' => $user->getEmail(),
                                                'section' => 'personal/email',
                                                'label-message' => 'youremail',
index dd8b9dd..67e68b3 100644 (file)
@@ -128,14 +128,24 @@ class SpecialResetpass extends SpecialPage {
        }
 
        function pretty( $fields ) {
+               global $wgHtml5;
                $out = '';
                foreach( $fields as $list ) {
                        list( $name, $label, $type, $value ) = $list;
                        if( $type == 'text' ) {
                                $field = htmlspecialchars( $value );
                        } else {
+                               $attribs = array( 'id' => $name, 'type' => $type );
+                               if ( $wgHtml5 ) {
+                                       # All three fields are required, and we should focus the
+                                       # first (wpPassword)
+                                       $attribs['required'] = '';
+                                       if ( $name == 'wpPassword' ) {
+                                               $attribs['autofocus'] = '';
+                                       }
+                               }
                                $field = Xml::input( $name, 20, $value,
-                                       array( 'id' => $name, 'type' => $type ) );
+                                       $attribs );
                        }
                        $out .= '<tr>';
                        $out .= "<td class='mw-label'>";
index 1caa7ea..b853c8a 100644 (file)
@@ -12,6 +12,8 @@ if( !defined( 'MEDIAWIKI' ) ) die( -1 );
  */
 class UserloginTemplate extends QuickTemplate {
        function execute() {
+               global $wgHtml5;
+
                if( $this->data['message'] ) {
 ?>
        <div class="<?php $this->text('messagetype') ?>box">
@@ -37,7 +39,11 @@ class UserloginTemplate extends QuickTemplate {
                        <td class="mw-input">
                                <input type='text' class='loginText' name="wpName" id="wpName1"
                                        tabindex="1"
-                                       value="<?php $this->text('name') ?>" size='20' />
+                                       value="<?php $this->text('name'); ?>" size='20'<?php
+if ( $wgHtml5 ) {
+       echo ' required="" autofocus=""';
+}
+?>" />
                        </td>
                </tr>
                <tr>
@@ -109,6 +115,8 @@ class UsercreateTemplate extends QuickTemplate {
        }
        
        function execute() {
+               global $wgHtml5, $wgMinimalPasswordLength;
+
                if( $this->data['message'] ) {
 ?>
        <div class="<?php $this->text('messagetype') ?>box">
@@ -132,7 +140,11 @@ class UsercreateTemplate extends QuickTemplate {
                        <td class="mw-input">
                                <input type='text' class='loginText' name="wpName" id="wpName2"
                                        tabindex="1"
-                                       value="<?php $this->text('name') ?>" size='20' />
+                                       value="<?php $this->text('name') ?>" size='20'<?php
+if ( $wgHtml5 ) {
+       echo ' required=""';
+}
+?> />
                        </td>
                </tr>
                <tr>
@@ -140,7 +152,11 @@ class UsercreateTemplate extends QuickTemplate {
                        <td class="mw-input">
                                <input type='password' class='loginPassword' name="wpPassword" id="wpPassword2"
                                        tabindex="2"
-                                       value="" size='20' />
+                                       value="" size='20'<?php
+if ( $wgHtml5 && $wgMinimalPasswordLength > 0 ) {
+       echo ' required=""';
+}
+?> />
                        </td>
                </tr>
        <?php if( $this->data['usedomain'] ) {
@@ -165,14 +181,18 @@ class UsercreateTemplate extends QuickTemplate {
                                <input type='password' class='loginPassword' name="wpRetype" id="wpRetype"
                                        tabindex="4"
                                        value=""
-                                       size='20' />
+                                       size='20'<?php
+if ( $wgHtml5 && $wgMinimalPasswordLength > 0 ) {
+       echo ' required=""';
+}
+?> />
                        </td>
                </tr>
                <tr>
                        <?php if( $this->data['useemail'] ) { ?>
                                <td class="mw-label"><label for='wpEmail'><?php $this->msg('youremail') ?></label></td>
                                <td class="mw-input">
-                                       <input type='text' class='loginText' name="wpEmail" id="wpEmail"
+                                       <input type='<?php echo $wgHtml5 ? 'email' : 'text' ?>' class='loginText' name="wpEmail" id="wpEmail"
                                                tabindex="5"
                                                value="<?php $this->text('email') ?>" size='20' />
                                        <div class="prefsectiontip">