Merge "Fix order of @var parameter in PHP"
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / ui / MenuSelectWidget.js
index 864d0cf..1e75020 100644 (file)
-( function () {
-       var FilterMenuHeaderWidget = require( './FilterMenuHeaderWidget.js' ),
-               HighlightPopupWidget = require( './HighlightPopupWidget.js' ),
-               FilterMenuSectionOptionWidget = require( './FilterMenuSectionOptionWidget.js' ),
-               FilterMenuOptionWidget = require( './FilterMenuOptionWidget.js' ),
-               MenuSelectWidget;
-
-       /**
-        * A floating menu widget for the filter list
-        *
-        * @class mw.rcfilters.ui.MenuSelectWidget
-        * @extends OO.ui.MenuSelectWidget
-        *
-        * @constructor
-        * @param {mw.rcfilters.Controller} controller Controller
-        * @param {mw.rcfilters.dm.FiltersViewModel} model View model
-        * @param {Object} [config] Configuration object
-        * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
-        * @cfg {Object[]} [footers] An array of objects defining the footers for
-        *  this menu, with a definition whether they appear per specific views.
-        *  The expected structure is:
-        *  [
-        *     {
-        *        name: {string} A unique name for the footer object
-        *        $element: {jQuery} A jQuery object for the content of the footer
-        *        views: {string[]} Optional. An array stating which views this footer is
-        *               active on. Use null or omit to display this on all views.
-        *     }
-        *  ]
-        */
-       MenuSelectWidget = function MwRcfiltersUiMenuSelectWidget( controller, model, config ) {
-               var header;
-
-               config = config || {};
-
-               this.controller = controller;
-               this.model = model;
-               this.currentView = '';
-               this.views = {};
-               this.userSelecting = false;
-
-               this.menuInitialized = false;
-               this.$overlay = config.$overlay || this.$element;
-               this.$body = $( '<div>' ).addClass( 'mw-rcfilters-ui-menuSelectWidget-body' );
-               this.footers = [];
-
-               // Parent
-               MenuSelectWidget.parent.call( this, $.extend( config, {
-                       $autoCloseIgnore: this.$overlay,
-                       width: 650,
-                       // Our filtering is done through the model
-                       filterFromInput: false
-               } ) );
-               this.setGroupElement(
-                       $( '<div>' )
-                               .addClass( 'mw-rcfilters-ui-menuSelectWidget-group' )
-               );
-               this.setClippableElement( this.$body );
-               this.setClippableContainer( this.$element );
-
-               header = new FilterMenuHeaderWidget(
-                       this.controller,
-                       this.model,
-                       {
-                               $overlay: this.$overlay
-                       }
+var FilterMenuHeaderWidget = require( './FilterMenuHeaderWidget.js' ),
+       HighlightPopupWidget = require( './HighlightPopupWidget.js' ),
+       FilterMenuSectionOptionWidget = require( './FilterMenuSectionOptionWidget.js' ),
+       FilterMenuOptionWidget = require( './FilterMenuOptionWidget.js' ),
+       MenuSelectWidget;
+
+/**
+ * A floating menu widget for the filter list
+ *
+ * @class mw.rcfilters.ui.MenuSelectWidget
+ * @extends OO.ui.MenuSelectWidget
+ *
+ * @constructor
+ * @param {mw.rcfilters.Controller} controller Controller
+ * @param {mw.rcfilters.dm.FiltersViewModel} model View model
+ * @param {Object} [config] Configuration object
+ * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
+ * @cfg {Object[]} [footers] An array of objects defining the footers for
+ *  this menu, with a definition whether they appear per specific views.
+ *  The expected structure is:
+ *  [
+ *     {
+ *        name: {string} A unique name for the footer object
+ *        $element: {jQuery} A jQuery object for the content of the footer
+ *        views: {string[]} Optional. An array stating which views this footer is
+ *               active on. Use null or omit to display this on all views.
+ *     }
+ *  ]
+ */
+MenuSelectWidget = function MwRcfiltersUiMenuSelectWidget( controller, model, config ) {
+       var header;
+
+       config = config || {};
+
+       this.controller = controller;
+       this.model = model;
+       this.currentView = '';
+       this.views = {};
+       this.userSelecting = false;
+
+       this.menuInitialized = false;
+       this.$overlay = config.$overlay || this.$element;
+       this.$body = $( '<div>' ).addClass( 'mw-rcfilters-ui-menuSelectWidget-body' );
+       this.footers = [];
+
+       // Parent
+       MenuSelectWidget.parent.call( this, $.extend( config, {
+               $autoCloseIgnore: this.$overlay,
+               width: 650,
+               // Our filtering is done through the model
+               filterFromInput: false
+       } ) );
+       this.setGroupElement(
+               $( '<div>' )
+                       .addClass( 'mw-rcfilters-ui-menuSelectWidget-group' )
+       );
+       this.setClippableElement( this.$body );
+       this.setClippableContainer( this.$element );
+
+       header = new FilterMenuHeaderWidget(
+               this.controller,
+               this.model,
+               {
+                       $overlay: this.$overlay
+               }
+       );
+
+       this.noResults = new OO.ui.LabelWidget( {
+               label: mw.msg( 'rcfilters-filterlist-noresults' ),
+               classes: [ 'mw-rcfilters-ui-menuSelectWidget-noresults' ]
+       } );
+
+       // Events
+       this.model.connect( this, {
+               initialize: 'onModelInitialize',
+               searchChange: 'onModelSearchChange'
+       } );
+
+       // Initialization
+       this.$element
+               .addClass( 'mw-rcfilters-ui-menuSelectWidget' )
+               .append( header.$element )
+               .append(
+                       this.$body
+                               .append( this.$group, this.noResults.$element )
                );
 
-               this.noResults = new OO.ui.LabelWidget( {
-                       label: mw.msg( 'rcfilters-filterlist-noresults' ),
-                       classes: [ 'mw-rcfilters-ui-menuSelectWidget-noresults' ]
-               } );
-
-               // Events
-               this.model.connect( this, {
-                       initialize: 'onModelInitialize',
-                       searchChange: 'onModelSearchChange'
-               } );
-
-               // Initialization
-               this.$element
-                       .addClass( 'mw-rcfilters-ui-menuSelectWidget' )
-                       .append( header.$element )
-                       .append(
-                               this.$body
-                                       .append( this.$group, this.noResults.$element )
-                       );
-
-               // Append all footers; we will control their visibility
-               // based on view
-               config.footers = config.footers || [];
-               config.footers.forEach( function ( footerData ) {
-                       var isSticky = footerData.sticky === undefined ? true : !!footerData.sticky,
-                               adjustedData = {
-                                       // Wrap the element with our own footer wrapper
-                                       $element: $( '<div>' )
-                                               .addClass( 'mw-rcfilters-ui-menuSelectWidget-footer' )
-                                               .addClass( 'mw-rcfilters-ui-menuSelectWidget-footer-' + footerData.name )
-                                               .append( footerData.$element ),
-                                       views: footerData.views
-                               };
-
-                       if ( !footerData.disabled ) {
-                               this.footers.push( adjustedData );
-
-                               if ( isSticky ) {
-                                       this.$element.append( adjustedData.$element );
-                               } else {
-                                       this.$body.append( adjustedData.$element );
-                               }
+       // Append all footers; we will control their visibility
+       // based on view
+       config.footers = config.footers || [];
+       config.footers.forEach( function ( footerData ) {
+               var isSticky = footerData.sticky === undefined ? true : !!footerData.sticky,
+                       adjustedData = {
+                               // Wrap the element with our own footer wrapper
+                               $element: $( '<div>' )
+                                       .addClass( 'mw-rcfilters-ui-menuSelectWidget-footer' )
+                                       .addClass( 'mw-rcfilters-ui-menuSelectWidget-footer-' + footerData.name )
+                                       .append( footerData.$element ),
+                               views: footerData.views
+                       };
+
+               if ( !footerData.disabled ) {
+                       this.footers.push( adjustedData );
+
+                       if ( isSticky ) {
+                               this.$element.append( adjustedData.$element );
+                       } else {
+                               this.$body.append( adjustedData.$element );
                        }
-               }.bind( this ) );
-
-               // Switch to the correct view
-               this.updateView();
-       };
-
-       /* Initialize */
-
-       OO.inheritClass( MenuSelectWidget, OO.ui.MenuSelectWidget );
-
-       /* Events */
-
-       /* Methods */
-       MenuSelectWidget.prototype.onModelSearchChange = function () {
-               this.updateView();
-       };
-
-       /**
-        * @inheritdoc
-        */
-       MenuSelectWidget.prototype.toggle = function ( show ) {
-               this.lazyMenuCreation();
-               MenuSelectWidget.parent.prototype.toggle.call( this, show );
-               // Always open this menu downwards. FilterTagMultiselectWidget scrolls it into view.
-               this.setVerticalPosition( 'below' );
-       };
-
-       /**
-        * lazy creation of the menu
-        */
-       MenuSelectWidget.prototype.lazyMenuCreation = function () {
-               var widget = this,
-                       items = [],
-                       viewGroupCount = {},
-                       groups = this.model.getFilterGroups();
-
-               if ( this.menuInitialized ) {
-                       return;
                }
-
-               this.menuInitialized = true;
-
-               // Create shared popup for highlight buttons
-               this.highlightPopup = new HighlightPopupWidget( this.controller );
-               this.$overlay.append( this.highlightPopup.$element );
-
-               // Count groups per view
-               // eslint-disable-next-line no-jquery/no-each-util
-               $.each( groups, function ( groupName, groupModel ) {
-                       if ( !groupModel.isHidden() ) {
-                               viewGroupCount[ groupModel.getView() ] = viewGroupCount[ groupModel.getView() ] || 0;
-                               viewGroupCount[ groupModel.getView() ]++;
-                       }
-               } );
-
-               // eslint-disable-next-line no-jquery/no-each-util
-               $.each( groups, function ( groupName, groupModel ) {
-                       var currentItems = [],
-                               view = groupModel.getView();
-
-                       if ( !groupModel.isHidden() ) {
-                               if ( viewGroupCount[ view ] > 1 ) {
-                                       // Only add a section header if there is more than
-                                       // one group
-                                       currentItems.push(
-                                               // Group section
-                                               new FilterMenuSectionOptionWidget(
-                                                       widget.controller,
-                                                       groupModel,
-                                                       {
-                                                               $overlay: widget.$overlay
-                                                       }
-                                               )
-                                       );
-                               }
-
-                               // Add items
-                               widget.model.getGroupFilters( groupName ).forEach( function ( filterItem ) {
-                                       currentItems.push(
-                                               new FilterMenuOptionWidget(
-                                                       widget.controller,
-                                                       widget.model,
-                                                       widget.model.getInvertModel(),
-                                                       filterItem,
-                                                       widget.highlightPopup,
-                                                       {
-                                                               $overlay: widget.$overlay
-                                                       }
-                                               )
-                                       );
-                               } );
-
-                               // Cache the items per view, so we can switch between them
-                               // without rebuilding the widgets each time
-                               widget.views[ view ] = widget.views[ view ] || [];
-                               widget.views[ view ] = widget.views[ view ].concat( currentItems );
-                               items = items.concat( currentItems );
-                       }
-               } );
-
-               this.addItems( items );
-               this.updateView();
-       };
-
-       /**
-        * Respond to model initialize event. Populate the menu from the model
-        */
-       MenuSelectWidget.prototype.onModelInitialize = function () {
-               this.menuInitialized = false;
-               // Set timeout for the menu to lazy build.
-               setTimeout( this.lazyMenuCreation.bind( this ) );
-       };
-
-       /**
-        * Update view
-        */
-       MenuSelectWidget.prototype.updateView = function () {
-               var viewName = this.model.getCurrentView();
-
-               if ( this.views[ viewName ] && this.currentView !== viewName ) {
-                       this.updateFooterVisibility( viewName );
-
-                       this.$element
-                               .data( 'view', viewName )
-                               .removeClass( 'mw-rcfilters-ui-menuSelectWidget-view-' + this.currentView )
-                               .addClass( 'mw-rcfilters-ui-menuSelectWidget-view-' + viewName );
-
-                       this.currentView = viewName;
-                       this.scrollToTop();
+       }.bind( this ) );
+
+       // Switch to the correct view
+       this.updateView();
+};
+
+/* Initialize */
+
+OO.inheritClass( MenuSelectWidget, OO.ui.MenuSelectWidget );
+
+/* Events */
+
+/* Methods */
+MenuSelectWidget.prototype.onModelSearchChange = function () {
+       this.updateView();
+};
+
+/**
+ * @inheritdoc
+ */
+MenuSelectWidget.prototype.toggle = function ( show ) {
+       this.lazyMenuCreation();
+       MenuSelectWidget.parent.prototype.toggle.call( this, show );
+       // Always open this menu downwards. FilterTagMultiselectWidget scrolls it into view.
+       this.setVerticalPosition( 'below' );
+};
+
+/**
+ * lazy creation of the menu
+ */
+MenuSelectWidget.prototype.lazyMenuCreation = function () {
+       var widget = this,
+               items = [],
+               viewGroupCount = {},
+               groups = this.model.getFilterGroups();
+
+       if ( this.menuInitialized ) {
+               return;
+       }
+
+       this.menuInitialized = true;
+
+       // Create shared popup for highlight buttons
+       this.highlightPopup = new HighlightPopupWidget( this.controller );
+       this.$overlay.append( this.highlightPopup.$element );
+
+       // Count groups per view
+       // eslint-disable-next-line no-jquery/no-each-util
+       $.each( groups, function ( groupName, groupModel ) {
+               if ( !groupModel.isHidden() ) {
+                       viewGroupCount[ groupModel.getView() ] = viewGroupCount[ groupModel.getView() ] || 0;
+                       viewGroupCount[ groupModel.getView() ]++;
                }
-
-               this.postProcessItems();
-               this.clip();
-       };
-
-       /**
-        * Go over the available footers and decide which should be visible
-        * for this view
-        *
-        * @param {string} [currentView] Current view
-        */
-       MenuSelectWidget.prototype.updateFooterVisibility = function ( currentView ) {
-               currentView = currentView || this.model.getCurrentView();
-
-               this.footers.forEach( function ( data ) {
-                       data.$element.toggle(
-                               // This footer should only be shown if it is configured
-                               // for all views or for this specific view
-                               !data.views || data.views.length === 0 || data.views.indexOf( currentView ) > -1
-                       );
-               } );
-       };
-
-       /**
-        * Post-process items after the visibility changed. Make sure
-        * that we always have an item selected, and that the no-results
-        * widget appears if the menu is empty.
-        */
-       MenuSelectWidget.prototype.postProcessItems = function () {
-               var i,
-                       itemWasSelected = false,
-                       items = this.getItems();
-
-               // If we are not already selecting an item, always make sure
-               // that the top item is selected
-               if ( !this.userSelecting ) {
-                       // Select the first item in the list
-                       for ( i = 0; i < items.length; i++ ) {
-                               if (
-                                       !( items[ i ] instanceof OO.ui.MenuSectionOptionWidget ) &&
-                                       items[ i ].isVisible()
-                               ) {
-                                       itemWasSelected = true;
-                                       this.selectItem( items[ i ] );
-                                       break;
-                               }
+       } );
+
+       // eslint-disable-next-line no-jquery/no-each-util
+       $.each( groups, function ( groupName, groupModel ) {
+               var currentItems = [],
+                       view = groupModel.getView();
+
+               if ( !groupModel.isHidden() ) {
+                       if ( viewGroupCount[ view ] > 1 ) {
+                               // Only add a section header if there is more than
+                               // one group
+                               currentItems.push(
+                                       // Group section
+                                       new FilterMenuSectionOptionWidget(
+                                               widget.controller,
+                                               groupModel,
+                                               {
+                                                       $overlay: widget.$overlay
+                                               }
+                                       )
+                               );
                        }
 
-                       if ( !itemWasSelected ) {
-                               this.selectItem( null );
-                       }
+                       // Add items
+                       widget.model.getGroupFilters( groupName ).forEach( function ( filterItem ) {
+                               currentItems.push(
+                                       new FilterMenuOptionWidget(
+                                               widget.controller,
+                                               widget.model,
+                                               widget.model.getInvertModel(),
+                                               filterItem,
+                                               widget.highlightPopup,
+                                               {
+                                                       $overlay: widget.$overlay
+                                               }
+                                       )
+                               );
+                       } );
+
+                       // Cache the items per view, so we can switch between them
+                       // without rebuilding the widgets each time
+                       widget.views[ view ] = widget.views[ view ] || [];
+                       widget.views[ view ] = widget.views[ view ].concat( currentItems );
+                       items = items.concat( currentItems );
                }
+       } );
+
+       this.addItems( items );
+       this.updateView();
+};
+
+/**
+ * Respond to model initialize event. Populate the menu from the model
+ */
+MenuSelectWidget.prototype.onModelInitialize = function () {
+       this.menuInitialized = false;
+       // Set timeout for the menu to lazy build.
+       setTimeout( this.lazyMenuCreation.bind( this ) );
+};
+
+/**
+ * Update view
+ */
+MenuSelectWidget.prototype.updateView = function () {
+       var viewName = this.model.getCurrentView();
+
+       if ( this.views[ viewName ] && this.currentView !== viewName ) {
+               this.updateFooterVisibility( viewName );
 
-               this.noResults.toggle( !this.getItems().some( function ( item ) {
-                       return item.isVisible();
-               } ) );
-       };
-
-       /**
-        * Get the option widget that matches the model given
-        *
-        * @param {mw.rcfilters.dm.ItemModel} model Item model
-        * @return {mw.rcfilters.ui.ItemMenuOptionWidget} Option widget
-        */
-       MenuSelectWidget.prototype.getItemFromModel = function ( model ) {
-               this.lazyMenuCreation();
-               return this.views[ model.getGroupModel().getView() ].filter( function ( item ) {
-                       return item.getName() === model.getName();
-               } )[ 0 ];
-       };
-
-       /**
-        * @inheritdoc
-        */
-       MenuSelectWidget.prototype.onDocumentKeyDown = function ( e ) {
-               var nextItem,
-                       currentItem = this.findHighlightedItem() || this.findSelectedItem();
-
-               // Call parent
-               MenuSelectWidget.parent.prototype.onDocumentKeyDown.call( this, e );
-
-               // We want to select the item on arrow movement
-               // rather than just highlight it, like the menu
-               // does by default
-               if ( !this.isDisabled() && this.isVisible() ) {
-                       switch ( e.keyCode ) {
-                               case OO.ui.Keys.UP:
-                               case OO.ui.Keys.LEFT:
-                                       // Get the next item
-                                       nextItem = this.findRelativeSelectableItem( currentItem, -1 );
-                                       break;
-                               case OO.ui.Keys.DOWN:
-                               case OO.ui.Keys.RIGHT:
-                                       // Get the next item
-                                       nextItem = this.findRelativeSelectableItem( currentItem, 1 );
-                                       break;
+               this.$element
+                       .data( 'view', viewName )
+                       .removeClass( 'mw-rcfilters-ui-menuSelectWidget-view-' + this.currentView )
+                       .addClass( 'mw-rcfilters-ui-menuSelectWidget-view-' + viewName );
+
+               this.currentView = viewName;
+               this.scrollToTop();
+       }
+
+       this.postProcessItems();
+       this.clip();
+};
+
+/**
+ * Go over the available footers and decide which should be visible
+ * for this view
+ *
+ * @param {string} [currentView] Current view
+ */
+MenuSelectWidget.prototype.updateFooterVisibility = function ( currentView ) {
+       currentView = currentView || this.model.getCurrentView();
+
+       this.footers.forEach( function ( data ) {
+               data.$element.toggle(
+                       // This footer should only be shown if it is configured
+                       // for all views or for this specific view
+                       !data.views || data.views.length === 0 || data.views.indexOf( currentView ) > -1
+               );
+       } );
+};
+
+/**
+ * Post-process items after the visibility changed. Make sure
+ * that we always have an item selected, and that the no-results
+ * widget appears if the menu is empty.
+ */
+MenuSelectWidget.prototype.postProcessItems = function () {
+       var i,
+               itemWasSelected = false,
+               items = this.getItems();
+
+       // If we are not already selecting an item, always make sure
+       // that the top item is selected
+       if ( !this.userSelecting ) {
+               // Select the first item in the list
+               for ( i = 0; i < items.length; i++ ) {
+                       if (
+                               !( items[ i ] instanceof OO.ui.MenuSectionOptionWidget ) &&
+                               items[ i ].isVisible()
+                       ) {
+                               itemWasSelected = true;
+                               this.selectItem( items[ i ] );
+                               break;
                        }
+               }
 
-                       nextItem = nextItem && nextItem.constructor.static.selectable ?
-                               nextItem : null;
-
-                       // Select the next item
-                       this.selectItem( nextItem );
+               if ( !itemWasSelected ) {
+                       this.selectItem( null );
                }
-       };
-
-       /**
-        * Scroll to the top of the menu
-        */
-       MenuSelectWidget.prototype.scrollToTop = function () {
-               this.$body.scrollTop( 0 );
-       };
-
-       /**
-        * Set whether the user is currently selecting an item.
-        * This is important when the user selects an item that is in between
-        * different views, and makes sure we do not re-select a different
-        * item (like the item on top) when this is happening.
-        *
-        * @param {boolean} isSelecting User is selecting
-        */
-       MenuSelectWidget.prototype.setUserSelecting = function ( isSelecting ) {
-               this.userSelecting = !!isSelecting;
-       };
-
-       module.exports = MenuSelectWidget;
-}() );
+       }
+
+       this.noResults.toggle( !this.getItems().some( function ( item ) {
+               return item.isVisible();
+       } ) );
+};
+
+/**
+ * Get the option widget that matches the model given
+ *
+ * @param {mw.rcfilters.dm.ItemModel} model Item model
+ * @return {mw.rcfilters.ui.ItemMenuOptionWidget} Option widget
+ */
+MenuSelectWidget.prototype.getItemFromModel = function ( model ) {
+       this.lazyMenuCreation();
+       return this.views[ model.getGroupModel().getView() ].filter( function ( item ) {
+               return item.getName() === model.getName();
+       } )[ 0 ];
+};
+
+/**
+ * @inheritdoc
+ */
+MenuSelectWidget.prototype.onDocumentKeyDown = function ( e ) {
+       var nextItem,
+               currentItem = this.findHighlightedItem() || this.findSelectedItem();
+
+       // Call parent
+       MenuSelectWidget.parent.prototype.onDocumentKeyDown.call( this, e );
+
+       // We want to select the item on arrow movement
+       // rather than just highlight it, like the menu
+       // does by default
+       if ( !this.isDisabled() && this.isVisible() ) {
+               switch ( e.keyCode ) {
+                       case OO.ui.Keys.UP:
+                       case OO.ui.Keys.LEFT:
+                               // Get the next item
+                               nextItem = this.findRelativeSelectableItem( currentItem, -1 );
+                               break;
+                       case OO.ui.Keys.DOWN:
+                       case OO.ui.Keys.RIGHT:
+                               // Get the next item
+                               nextItem = this.findRelativeSelectableItem( currentItem, 1 );
+                               break;
+               }
+
+               nextItem = nextItem && nextItem.constructor.static.selectable ?
+                       nextItem : null;
+
+               // Select the next item
+               this.selectItem( nextItem );
+       }
+};
+
+/**
+ * Scroll to the top of the menu
+ */
+MenuSelectWidget.prototype.scrollToTop = function () {
+       this.$body.scrollTop( 0 );
+};
+
+/**
+ * Set whether the user is currently selecting an item.
+ * This is important when the user selects an item that is in between
+ * different views, and makes sure we do not re-select a different
+ * item (like the item on top) when this is happening.
+ *
+ * @param {boolean} isSelecting User is selecting
+ */
+MenuSelectWidget.prototype.setUserSelecting = function ( isSelecting ) {
+       this.userSelecting = !!isSelecting;
+};
+
+module.exports = MenuSelectWidget;