Implement NamespaceInputWidget
authorBartosz Dziewoński <matma.rex@gmail.com>
Tue, 14 Jul 2015 20:30:06 +0000 (22:30 +0200)
committerBartosz Dziewoński <matma.rex@gmail.com>
Fri, 17 Jul 2015 13:33:59 +0000 (13:33 +0000)
* Add PHP version of NamespaceInputWidget, co-authored by Florian,
  which consists of a DropdownInputWidget offering a choice of
  namespaces and two CheckboxInputWidgets allowing to also match
  associated namespace (talk/content) or to invert the choice.
* Add an incomplete JS version of NamespaceInputWidget, which is only
  really functional when infused from the PHP version (it can't
  generate the dropdown by itself, for example). Implement some JS to
  improve the experience of selecting the "all namespaces" option in
  the dropdown (by disabling the checkboxes when this happens).
* Split off a 'mediawiki.widgets.styles' module, which has the basic
  styles for PHP widgets which are to be loaded in the head. Make
  OutputPage::enableOOUI() also add this module (which should stay
  reasonably small).
* Use the new widget in HTMLForm's HTMLSelectNamespace field. It can
  be seen in action on Special:LinkSearch, for example.

Co-Authored-By: Florian <florian.schmidt.welzow@t-online.de>
Co-Authored-By: Bartosz Dziewoński <matma.rex@gmail.com>
Change-Id: I5cbfa9d0f6a8641148ce476b7dbe65e9096b4485

autoload.php
includes/OutputPage.php
includes/htmlform/HTMLSelectNamespace.php
includes/widget/AUTHORS.txt
includes/widget/NamespaceInputWidget.php [new file with mode: 0644]
resources/Resources.php
resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.base.css [new file with mode: 0644]
resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js [new file with mode: 0644]

index 2c9dd8f..fd75c8a 100644 (file)
@@ -754,6 +754,7 @@ $wgAutoloadLocalClasses = array(
        'MediaWiki\\Logger\\Monolog\\WikiProcessor' => __DIR__ . '/includes/debug/logger/monolog/WikiProcessor.php',
        'MediaWiki\\Logger\\NullSpi' => __DIR__ . '/includes/debug/logger/NullSpi.php',
        'MediaWiki\\Logger\\Spi' => __DIR__ . '/includes/debug/logger/Spi.php',
+       'MediaWiki\\Widget\\NamespaceInputWidget' => __DIR__ . '/includes/widget/NamespaceInputWidget.php',
        'MediaWiki\\Widget\\TitleInputWidget' => __DIR__ . '/includes/widget/TitleInputWidget.php',
        'MemCachedClientforWiki' => __DIR__ . '/includes/objectcache/MemcachedClient.php',
        'MemcLockManager' => __DIR__ . '/includes/filebackend/lockmanager/MemcLockManager.php',
index 30ee19c..e832b82 100644 (file)
@@ -3959,6 +3959,7 @@ class OutputPage extends ContextSource {
                        'oojs-ui.styles.icons',
                        'oojs-ui.styles.indicators',
                        'oojs-ui.styles.textures',
+                       'mediawiki.widgets.styles',
                ) );
        }
 }
index dfab6cf..d6d564e 100644 (file)
@@ -22,21 +22,14 @@ class HTMLSelectNamespace extends HTMLFormField {
        }
 
        public function getInputOOUI( $value ) {
-               $namespaceOptions = Html::namespaceSelectorOptions( array( 'all' => $this->mAllValue ) );
-
-               $options = array();
-               foreach( $namespaceOptions as $id => $name ) {
-                       $options[] = array(
-                               'data' => (string)$id,
-                               'label' => $name,
-                       );
-               };
-
-               return new OOUI\DropdownInputWidget( array(
-                       'options' => $options,
-                       'value' => $value,
-                       'name' => $this->mName,
+               return new MediaWiki\Widget\NamespaceInputWidget( array(
+                       'valueNamespace' => $value,
+                       'nameNamespace' => $this->mName,
                        'id' => $this->mID,
+                       'includeAllValue' => $this->mAllValue,
+                       // Disable additional checkboxes
+                       'nameInvert' => null,
+                       'nameAssociated' => null,
                ) );
        }
 }
index 10064b2..a0d7703 100644 (file)
@@ -3,6 +3,7 @@ Authors (alphabetically)
 Alex Monk <krenair@wikimedia.org>
 Bartosz Dziewoński <bdziewonski@wikimedia.org>
 Ed Sanders <esanders@wikimedia.org>
+Florian Schmidt <florian.schmidt.welzow@t-online.de>
 James D. Forrester <jforrester@wikimedia.org>
 Roan Kattouw <roan@wikimedia.org>
 Sucheta Ghoshal <sghoshal@wikimedia.org>
diff --git a/includes/widget/NamespaceInputWidget.php b/includes/widget/NamespaceInputWidget.php
new file mode 100644 (file)
index 0000000..69427c5
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+/**
+ * MediaWiki Widgets – NamespaceInputWidget class.
+ *
+ * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+namespace MediaWiki\Widget;
+
+/**
+ * Namespace input widget. Displays a dropdown box with the choice of available namespaces, plus two
+ * checkboxes to include associated namespace or to invert selection.
+ */
+class NamespaceInputWidget extends \OOUI\Widget {
+
+       protected $namespace = null;
+       protected $associated = null;
+       protected $invert = null;
+       protected $allValue = null;
+
+       /**
+        * @param array $config Configuration options
+        * @param string $config['nameNamespace'] HTML input name for the namespace dropdown box (default:
+        *     'namespace')
+        * @param string $config['nameInvert'] HTML input name for the "invert selection" checkbox. If
+        *     null, the checkbox will not be generated. (default: 'invert')
+        * @param string $config['nameAssociated'] HTML input name for the "include associated namespace"
+        *     checkbox. If null, the checkbox will not be generated. (default: 'associated')
+        * @param string $config['includeAllValue'] If specified, add a "all namespaces" option to the
+        *     namespace dropdown, and use this as the input value for it
+        * @param int|string $config['valueNamespace'] Input value of the namespace dropdown box. May be a
+        *     string only if 'includeAllValue' is set.
+        * @param boolean $config['valueInvert'] Input value of the "invert selection" checkbox (default:
+        *     false)
+        * @param boolean $config['valueAssociated'] Input value of the "include associated namespace"
+        *     checkbox (default: false)
+        * @param string $config['labelInvert'] Text of label to use for "invert selection" checkbox
+        * @param string $config['labelAssociated'] Text of label to use for "include associated
+        *     namespace" checkbox
+        */
+       public function __construct( array $config = array() ) {
+               // Configuration initialization
+
+               $config = array_merge(
+                       array(
+                               'nameNamespace' => 'namespace',
+                               'nameInvert' => 'invert',
+                               'nameAssociated' => 'associated',
+                               // Choose first available: either main namespace or the "all namespaces" option
+                               'valueNamespace' => null,
+                               'valueInvert' => false,
+                               'valueAssociated' => false,
+                       ),
+                       $config
+               );
+
+               // Parent constructor
+               parent::__construct( $config );
+
+               // Properties
+               $this->allValue = isset( $config['includeAllValue'] ) ? $config['includeAllValue'] : null;
+               $this->namespace = new \OOUI\DropdownInputWidget( array(
+                       'name' => $config['nameNamespace'],
+                       'value' => $config['valueNamespace'],
+                       'options' => $this->getNamespaceDropdownOptions( $config ),
+               ) );
+               if ( $config['nameAssociated'] !== null ) {
+                       // FIXME Should use a LabelWidget? But they don't work like HTML <label>s yet
+                       $this->associated = new \OOUI\FieldLayout(
+                               new \OOUI\CheckboxInputWidget( array(
+                                       'name' => $config['nameAssociated'],
+                                       'selected' => $config['valueAssociated'],
+                                       'value' => '1',
+                               ) ),
+                               array(
+                                       'align' => 'inline',
+                                       'label' => $config['labelAssociated'],
+                               )
+                       );
+               }
+               if ( $config['nameInvert'] !== null ) {
+                       $this->invert = new \OOUI\FieldLayout(
+                               new \OOUI\CheckboxInputWidget( array(
+                                       'name' => $config['nameInvert'],
+                                       'selected' => $config['valueInvert'],
+                                       'value' => '1',
+                               ) ),
+                               array(
+                                       'align' => 'inline',
+                                       'label' => $config['labelInvert'],
+                               )
+                       );
+               }
+
+               // Initialization
+               $this
+                       ->addClasses( array( 'mw-widget-namespaceInputWidget' ) )
+                       ->appendContent( $this->namespace, $this->associated, $this->invert );
+       }
+
+       protected function getNamespaceDropdownOptions( array $config ) {
+               $namespaceOptionsParams = isset( $config['includeAllValue'] ) ?
+                       array( 'all' => $config['includeAllValue'] ) : array();
+               $namespaceOptions = \Html::namespaceSelectorOptions( $namespaceOptionsParams );
+
+               $options = array();
+               foreach( $namespaceOptions as $id => $name ) {
+                       $options[] = array(
+                               'data' => (string)$id,
+                               'label' => $name,
+                       );
+               }
+
+               return $options;
+       }
+
+       protected function getJavaScriptClassName() {
+               return 'mw.widgets.NamespaceInputWidget';
+       }
+
+       public function getConfig( &$config ) {
+               $config['namespace'] = $this->namespace;
+               $config['associated'] = $this->associated;
+               $config['invert'] = $this->invert;
+               $config['allValue'] = $this->allValue;
+               return parent::getConfig( $config );
+       }
+}
index 3217503..0c37ae8 100644 (file)
@@ -1746,13 +1746,17 @@ return array(
        'mediawiki.widgets' => array(
                'scripts' => array(
                        'resources/src/mediawiki.widgets/mw.widgets.js',
+                       'resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js',
                        'resources/src/mediawiki.widgets/mw.widgets.TitleInputWidget.js',
                        'resources/src/mediawiki.widgets/mw.widgets.TitleOptionWidget.js',
                ),
                'skinStyles' => array(
-                       'default' => 'resources/src/mediawiki.widgets/mw.widgets.TitleInputWidget.css',
+                       'default' => array(
+                               'resources/src/mediawiki.widgets/mw.widgets.TitleInputWidget.css',
+                       ),
                ),
                'dependencies' => array(
+                       'mediawiki.widgets.styles',
                        'jquery.autoEllipsis',
                        'mediawiki.Title',
                        'mediawiki.api',
@@ -1764,6 +1768,15 @@ return array(
                ),
                'targets' => array( 'desktop', 'mobile' ),
        ),
+       'mediawiki.widgets.styles' => array(
+               'skinStyles' => array(
+                       'default' => array(
+                               'resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.base.css',
+                       ),
+               ),
+               'position' => 'top',
+               'targets' => array( 'desktop', 'mobile' ),
+       ),
 
        /* es5-shim */
        'es5-shim' => array(
diff --git a/resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.base.css b/resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.base.css
new file mode 100644 (file)
index 0000000..3ce92ef
--- /dev/null
@@ -0,0 +1,26 @@
+/*!
+ * MediaWiki Widgets - base NamespaceInputWidget styles.
+ *
+ * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+.mw-widget-namespaceInputWidget .oo-ui-dropdownInputWidget,
+.mw-widget-namespaceInputWidget .oo-ui-fieldLayout {
+       display: inline-block;
+       margin-right: 1em;
+}
+
+/* FIXME FieldLayout is not supposed to be used the way we use it here */
+.mw-widget-namespaceInputWidget .oo-ui-fieldLayout {
+       vertical-align: middle;
+       margin-bottom: 0;
+}
+
+.mw-widget-namespaceInputWidget .oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+       padding-left: 0.5em;
+}
+
+.mw-widget-namespaceInputWidget .oo-ui-dropdownInputWidget {
+       width: 20em;
+}
diff --git a/resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js b/resources/src/mediawiki.widgets/mw.widgets.NamespaceInputWidget.js
new file mode 100644 (file)
index 0000000..0558c32
--- /dev/null
@@ -0,0 +1,68 @@
+/*!
+ * MediaWiki Widgets - NamespaceInputWidget class.
+ *
+ * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+( function ( $, mw ) {
+
+       /**
+        * Creates a mw.widgets.NamespaceInputWidget object.
+        *
+        * This is not a complete implementation and is not intended for public usage. It only exists
+        * because of HTMLForm shenanigans.
+        *
+        * @class
+        * @private
+        * @extends OO.ui.Widget
+        *
+        * @constructor
+        * @param {Object} [config] Configuration options
+        * @cfg {OO.ui.DropdownInputWidget} namespace Widget to include
+        * @cfg {OO.ui.CheckboxInputWidget|null} invert Widget to include
+        * @cfg {OO.ui.CheckboxInputWidget|null} associated Widget to include
+        * @cfg {string|null} allValue Value for "all namespaces" option, if any
+        */
+       mw.widgets.NamespaceInputWidget = function MwWidgetsNamespaceInputWidget( config ) {
+               // Parent constructor
+               OO.ui.Widget.call( this, config );
+
+               // Properties
+               this.namespace = config.namespace;
+               this.invert = config.invert;
+               this.associated = config.associated;
+               this.allValue = config.allValue;
+
+               // Events
+               config.namespace.connect( this, { change: 'updateCheckboxesState' } );
+
+               // Initialization
+               this.$element
+                       .addClass( 'mw-widget-namespaceInputWidget' )
+                       .append(
+                               config.namespace.$element,
+                               config.invert ? config.invert.$element : '',
+                               config.associated ? config.associated.$element : ''
+                       );
+               this.updateCheckboxesState();
+       };
+
+       /* Inheritance */
+
+       OO.inheritClass( mw.widgets.NamespaceInputWidget, OO.ui.Widget );
+
+       /* Methods */
+
+       /**
+        * Update the disabled state of checkboxes when the value of namespace dropdown changes.
+        */
+       mw.widgets.NamespaceInputWidget.prototype.updateCheckboxesState = function () {
+               if ( this.invert ) {
+                       this.invert.getField().setDisabled( this.namespace.getValue() === this.allValue );
+               }
+               if ( this.associated ) {
+                       this.associated.getField().setDisabled( this.namespace.getValue() === this.allValue );
+               }
+       };
+
+}( jQuery, mediaWiki ) );