HTMLForm: Use <button> and allow differing label and value
authorBrad Jorsch <bjorsch@wikimedia.org>
Tue, 15 Dec 2015 22:12:34 +0000 (17:12 -0500)
committerBrad Jorsch <bjorsch@wikimedia.org>
Fri, 18 Dec 2015 17:41:55 +0000 (12:41 -0500)
Submit buttons, whether via HTMLForm::addButton() or HTMLSubmitField,
are difficult to use effectively when the submitted value has to match
the internationalized display label. HTML5 (and HTML4 for that matter)
already has <button> that does what we need, we just need to actually
use it.

But for IE6 and IE7, we can't use <button> because the browser doesn't
handle them correctly. Sniff those browsers to avoid their bugginess
(which the OOUI version should have been doing already for IE6, but
wasn't).

Bug: T121584
Change-Id: I7e15331efb162275c4116bcae61f19d6b884cbe3

includes/htmlform/HTMLButtonField.php
includes/htmlform/HTMLForm.php
includes/htmlform/OOUIHTMLForm.php
includes/specials/SpecialChangePassword.php
includes/specials/SpecialUpload.php

index 1d45e5f..b4849e5 100644 (file)
@@ -5,10 +5,24 @@
  * click handling code in JavaScript. Use a HTMLSubmitField if you merely
  * wish to add a submit button to a form.
  *
+ * Additional recognized configuration parameters include:
+ * - flags: OOUI flags for the button, see OOUI\\FlaggedElement
+ * - buttonlabel-message: Message to use for the button display text, instead
+ *   of the value from 'default'. Overrides 'buttonlabel' and 'buttonlabel-raw'.
+ * - buttonlabel: Text to display for the button display text, instead
+ *   of the value from 'default'. Overrides 'buttonlabel-raw'.
+ * - buttonlabel-raw: HTMLto display for the button display text, instead
+ *   of the value from 'default'.
+ *
+ * Note that the buttonlabel parameters are not supported on IE6 and IE7 due to
+ * bugs in those browsers. If detected, they will be served buttons using the
+ * value of 'default' as the button label.
+ *
  * @since 1.22
  */
 class HTMLButtonField extends HTMLFormField {
        protected $buttonType = 'button';
+       protected $buttonLabel = null;
 
        /** @var array $mFlags Flags to add to OOUI Button widget */
        protected $mFlags = array();
@@ -18,6 +32,30 @@ class HTMLButtonField extends HTMLFormField {
                if ( isset( $info['flags'] ) ) {
                        $this->mFlags = $info['flags'];
                }
+
+               # Generate the label from a message, if possible
+               if ( isset( $info['buttonlabel-message'] ) ) {
+                       $msgInfo = $info['buttonlabel-message'];
+
+                       if ( is_array( $msgInfo ) ) {
+                               $msg = array_shift( $msgInfo );
+                       } else {
+                               $msg = $msgInfo;
+                               $msgInfo = array();
+                       }
+
+                       $this->buttonLabel = $this->msg( $msg, $msgInfo )->parse();
+               } elseif ( isset( $info['buttonlabel'] ) ) {
+                       if ( $info['buttonlabel'] === '&#160;' ) {
+                               // Apparently some things set &nbsp directly and in an odd format
+                               $this->buttonLabel = '&#160;';
+                       } else {
+                               $this->buttonLabel = htmlspecialchars( $info['buttonlabel'] );
+                       }
+               } elseif ( isset( $info['buttonlabel-raw'] ) ) {
+                       $this->buttonLabel = $info['buttonlabel-raw'];
+               }
+
                parent::__construct( $info );
        }
 
@@ -37,9 +75,16 @@ class HTMLButtonField extends HTMLFormField {
                $attr = array(
                        'class' => 'mw-htmlform-submit ' . $this->mClass . $flags,
                        'id' => $this->mID,
+                       'type' => $this->buttonType,
+                       'name' => $this->mName,
+                       'value' => $value,
                ) + $this->getAttributes( array( 'disabled', 'tabindex' ) );
 
-               return Html::input( $this->mName, $value, $this->buttonType, $attr );
+               if ( $this->isBadIE() ) {
+                       return Html::element( 'input', $attr );
+               } else {
+                       return Html::rawElement( 'button', $attr, $this->buttonLabel ?: htmlspecialchars( $value ) );
+               }
        }
 
        /**
@@ -51,11 +96,14 @@ class HTMLButtonField extends HTMLFormField {
                return new OOUI\ButtonInputWidget( array(
                        'name' => $this->mName,
                        'value' => $value,
-                       'label' => $value,
+                       'label' => !$this->isBadIE() && $this->buttonLabel
+                               ? new OOUI\HtmlSnippet( $this->buttonLabel )
+                               : $value,
                        'type' => $this->buttonType,
                        'classes' => array( 'mw-htmlform-submit', $this->mClass ),
                        'id' => $this->mID,
                        'flags' => $this->mFlags,
+                       'useInputTag' => $this->isBadIE(),
                ) + $this->getAttributes( array( 'disabled', 'tabindex' ), array( 'tabindex' => 'tabIndex' ) ) );
        }
 
@@ -74,4 +122,15 @@ class HTMLButtonField extends HTMLFormField {
        public function validate( $value, $alldata ) {
                return true;
        }
+
+       /**
+        * IE<8 has bugs with <button>, so we'll need to avoid them.
+        * @return bool Whether the request is from a bad version of IE
+        */
+       private function isBadIE() {
+               $request = $this->mParent
+                       ? $this->mParent->getRequest()
+                       : RequestContext::getMain()->getRequest();
+               return preg_match( '/MSIE [1-7]\./i', $request->getHeader( 'User-Agent' ) );
+       }
 }
index b0d90af..05fb44e 100644 (file)
@@ -855,15 +855,53 @@ class HTMLForm extends ContextSource {
        /**
         * Add a button to the form
         *
-        * @param string $name Field name.
-        * @param string $value Field value
-        * @param string $id DOM id for the button (default: null)
-        * @param array $attribs
-        *
+        * @since 1.27 takes an array as shown. Earlier versions accepted
+        *  'name', 'value', 'id', and 'attribs' as separate parameters in that
+        *  order.
+        * @note Custom labels ('label', 'label-message', 'label-raw') are not
+        *  supported for IE6 and IE7 due to bugs in those browsers. If detected,
+        *  they will be served buttons using 'value' as the button label.
+        * @param array $data Data to define the button:
+        *  - name: (string) Button name.
+        *  - value: (string) Button value.
+        *  - label-message: (string, optional) Button label message key to use
+        *    instead of 'value'. Overrides 'label' and 'label-raw'.
+        *  - label: (string, optional) Button label text to use instead of
+        *    'value'. Overrides 'label-raw'.
+        *  - label-raw: (string, optional) Button label HTML to use instead of
+        *    'value'.
+        *  - id: (string, optional) DOM id for the button.
+        *  - attribs: (array, optional) Additional HTML attributes.
+        *  - flags: (string|string[], optional) OOUI flags.
         * @return HTMLForm $this for chaining calls (since 1.20)
         */
-       public function addButton( $name, $value, $id = null, $attribs = null ) {
-               $this->mButtons[] = compact( 'name', 'value', 'id', 'attribs' );
+       public function addButton( $data ) {
+               if ( !is_array( $data ) ) {
+                       $args = func_get_args();
+                       if ( count( $args ) < 2 || count( $args ) > 4 ) {
+                               throw new InvalidArgumentException(
+                                       'Incorrect number of arguments for deprecated calling style'
+                               );
+                       }
+                       $data = array(
+                               'name' => $args[0],
+                               'value' => $args[1],
+                               'id' => isset( $args[2] ) ? $args[2] : null,
+                               'attribs' => isset( $args[3] ) ? $args[3] : null,
+                       );
+               } else {
+                       if ( !isset( $data['name'] ) ) {
+                               throw new InvalidArgumentException( 'A name is required' );
+                       }
+                       if ( !isset( $data['value'] ) ) {
+                               throw new InvalidArgumentException( 'A value is required' );
+                       }
+               }
+               $this->mButtons[] = $data + array(
+                       'id' => null,
+                       'attribs' => null,
+                       'flags' => null,
+               );
 
                return $this;
        }
@@ -1041,6 +1079,9 @@ class HTMLForm extends ContextSource {
                        ) . "\n";
                }
 
+               // IE<8 has bugs with <button>, so we'll need to avoid them.
+               $isBadIE = preg_match( '/MSIE [1-7]\./i', $this->getRequest()->getHeader( 'User-Agent' ) );
+
                foreach ( $this->mButtons as $button ) {
                        $attrs = array(
                                'type' => 'submit',
@@ -1048,6 +1089,16 @@ class HTMLForm extends ContextSource {
                                'value' => $button['value']
                        );
 
+                       if ( isset( $button['label-message'] ) ) {
+                               $label = $this->msg( $button['label-message'] )->parse();
+                       } elseif ( isset( $button['label'] ) ) {
+                               $label = htmlspecialchars( $button['label'] );
+                       } elseif ( isset( $button['label-raw'] ) ) {
+                               $label = $button['label-raw'];
+                       } else {
+                               $label = htmlspecialchars( $button['value'] );
+                       }
+
                        if ( $button['attribs'] ) {
                                $attrs += $button['attribs'];
                        }
@@ -1061,7 +1112,11 @@ class HTMLForm extends ContextSource {
                                $attrs['class'][] = 'mw-ui-button';
                        }
 
-                       $buttons .= Html::element( 'input', $attrs ) . "\n";
+                       if ( $isBadIE ) {
+                               $buttons .= Html::element( 'input', $attrs ) . "\n";
+                       } else {
+                               $buttons .= Html::rawElement( 'button', $attrs, $label ) . "\n";
+                       }
                }
 
                $html = Html::rawElement( 'span',
index c11449a..811eaf4 100644 (file)
@@ -50,6 +50,9 @@ class OOUIHTMLForm extends HTMLForm {
        function getButtons() {
                $buttons = '';
 
+               // IE<8 has bugs with <button>, so we'll need to avoid them.
+               $isBadIE = preg_match( '/MSIE [1-7]\./i', $this->getRequest()->getHeader( 'User-Agent' ) );
+
                if ( $this->mShowSubmit ) {
                        $attribs = array( 'infusable' => true );
 
@@ -70,6 +73,7 @@ class OOUIHTMLForm extends HTMLForm {
                        $attribs['label'] = $this->getSubmitText();
                        $attribs['value'] = $this->getSubmitText();
                        $attribs['flags'] = $this->mSubmitFlags;
+                       $attribs['useInputTag'] = $isBadIE;
 
                        $buttons .= new OOUI\ButtonInputWidget( $attribs );
                }
@@ -78,6 +82,7 @@ class OOUIHTMLForm extends HTMLForm {
                        $buttons .= new OOUI\ButtonInputWidget( array(
                                'type' => 'reset',
                                'label' => $this->msg( 'htmlform-reset' )->text(),
+                               'useInputTag' => $isBadIE,
                        ) );
                }
 
@@ -92,13 +97,27 @@ class OOUIHTMLForm extends HTMLForm {
                                $attrs['id'] = $button['id'];
                        }
 
+                       if ( $isBadIE ) {
+                               $label = $button['value'];
+                       } elseif ( isset( $button['label-message'] ) ) {
+                               $label = new OOUI\HtmlSnippet( $this->msg( $button['label-message'] )->parse() );
+                       } elseif ( isset( $button['label'] ) ) {
+                               $label = $button['label'];
+                       } elseif ( isset( $button['label-raw'] ) ) {
+                               $label = new OOUI\HtmlSnippet( $button['label-raw'] );
+                       } else {
+                               $label = $button['value'];
+                       }
+
                        $attrs['classes'] = isset( $attrs['class'] ) ? (array)$attrs['class'] : array();
 
                        $buttons .= new OOUI\ButtonInputWidget( array(
                                'type' => 'submit',
                                'name' => $button['name'],
                                'value' => $button['value'],
-                               'label' => $button['value'],
+                               'label' => $label,
+                               'flags' => $button['flags'],
+                               'useInputTag' => $isBadIE,
                        ) + $attrs );
                }
 
index 91ac4e0..371ad19 100644 (file)
@@ -152,7 +152,10 @@ class SpecialChangePassword extends FormSpecialPage {
                                ? 'resetpass-submit-loggedin'
                                : 'resetpass_submit'
                );
-               $form->addButton( 'wpCancel', $this->msg( 'resetpass-submit-cancel' )->text() );
+               $form->addButton( array(
+                       'name' => 'wpCancel',
+                       'value' => $this->msg( 'resetpass-submit-cancel' )->text()
+               ) );
                $form->setHeaderText( $this->msg( 'resetpass_text' )->parseAsBlock() );
                if ( $this->mPreTextMessage instanceof Message ) {
                        $form->addPreText( $this->mPreTextMessage->parseAsBlock() );
index c8c4642..b4470f5 100644 (file)
@@ -405,8 +405,14 @@ class SpecialUpload extends SpecialPage {
 
                $form = $this->getUploadForm( $warningHtml, $sessionKey, /* $hideIgnoreWarning */ true );
                $form->setSubmitText( $this->msg( 'upload-tryagain' )->text() );
-               $form->addButton( 'wpUploadIgnoreWarning', $this->msg( 'ignorewarning' )->text() );
-               $form->addButton( 'wpCancelUpload', $this->msg( 'reuploaddesc' )->text() );
+               $form->addButton( array(
+                       'name' => 'wpUploadIgnoreWarning',
+                       'value' => $this->msg( 'ignorewarning' )->text()
+               ) );
+               $form->addButton( array(
+                       'name' => 'wpCancelUpload',
+                       'value' => $this->msg( 'reuploaddesc' )->text()
+               ) );
 
                $this->showUploadForm( $form );