Merge "RCFilters UI: Add 'select' state and styles to capsule items"
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / ui / mw.rcfilters.ui.FilterWrapperWidget.js
index 788ab3c..02ddb54 100644 (file)
@@ -8,10 +8,12 @@
         * @constructor
         * @param {mw.rcfilters.Controller} controller Controller
         * @param {mw.rcfilters.dm.FiltersViewModel} model View model
-        * @param {Object} config Configuration object
+        * @param {Object} [config] Configuration object
         * @cfg {Object} [filters] A definition of the filter groups in this list
+        * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
         */
        mw.rcfilters.ui.FilterWrapperWidget = function MwRcfiltersUiFilterWrapperWidget( controller, model, config ) {
+               var $footer = $( '<div>' );
                config = config || {};
 
                // Parent
 
                this.controller = controller;
                this.model = model;
-               this.filtersInCapsule = [];
+               this.$overlay = config.$overlay || this.$element;
 
                this.filterPopup = new mw.rcfilters.ui.FiltersListWidget(
                        this.controller,
                        this.model,
                        {
-                               label: mw.msg( 'rcfilters-filterlist-title' )
+                               label: mw.msg( 'rcfilters-filterlist-title' ),
+                               $overlay: this.$overlay
                        }
                );
 
+               $footer.append(
+                       new OO.ui.ButtonWidget( {
+                               framed: false,
+                               icon: 'feedback',
+                               flags: [ 'progressive' ],
+                               label: mw.msg( 'rcfilters-filterlist-feedbacklink' ),
+                               href: 'https://www.mediawiki.org/wiki/Help_talk:Edit_Review_Improvements/RC_filters'
+                       } ).$element
+               );
+
                this.textInput = new OO.ui.TextInputWidget( {
                        classes: [ 'mw-rcfilters-ui-filterWrapperWidget-search' ],
                        icon: 'search',
                } );
 
                this.capsule = new mw.rcfilters.ui.FilterCapsuleMultiselectWidget( controller, this.model, this.textInput, {
+                       $overlay: this.$overlay,
                        popup: {
                                $content: this.filterPopup.$element,
-                               classes: [ 'mw-rcfilters-ui-filterWrapperWidget-popup' ]
+                               $footer: $footer,
+                               classes: [ 'mw-rcfilters-ui-filterWrapperWidget-popup' ],
+                               width: 650
                        }
                } );
 
                this.textInput.connect( this, {
                        change: 'onTextInputChange'
                } );
-               this.capsule.connect( this, {
-                       remove: 'onCapsuleRemoveItem'
-               } );
+               this.capsule.connect( this, { capsuleItemClick: 'onCapsuleItemClick' } );
+               this.capsule.popup.connect( this, { toggle: 'onCapsulePopupToggle' } );
 
+               // Initialize
                this.$element
                        .addClass( 'mw-rcfilters-ui-filterWrapperWidget' )
                        .append( this.capsule.$element, this.textInput.$element );
        OO.mixinClass( mw.rcfilters.ui.FilterWrapperWidget, OO.ui.mixin.PendingElement );
 
        /**
-        * Respond to text input change
+        * Respond to capsule item click and make the popup scroll down to the requested item
         *
-        * @param {string} newValue Current value
+        * @param {mw.rcfilters.ui.CapsuleItemWidget} item Clicked item
         */
-       mw.rcfilters.ui.FilterWrapperWidget.prototype.onTextInputChange = function ( newValue ) {
-               // Filter the results
-               this.filterPopup.filter( this.model.findMatches( newValue ) );
+       mw.rcfilters.ui.FilterWrapperWidget.prototype.onCapsuleItemClick = function ( item ) {
+               var filterName = item.getData(),
+                       // Find the item in the popup
+                       filterWidget = this.filterPopup.getItemWidget( filterName );
+
+               // Highlight item
+               this.filterPopup.select( filterName );
+               this.capsule.select( item );
+
+               this.scrollToTop( filterWidget.$element );
        };
 
        /**
-        * Respond to an event where an item is removed from the capsule.
-        * This is the case where a user actively removes a filter box from the capsule widget.
+        * Respond to popup toggle event. Reset selection in the list when the popup is closed.
         *
-        * @param {string[]} filterNames An array of filter names that were removed
+        * @param {boolean} isVisible Popup is visible
         */
-       mw.rcfilters.ui.FilterWrapperWidget.prototype.onCapsuleRemoveItem = function ( filterNames ) {
-               var filterItem,
-                       widget = this;
-
-               filterNames.forEach( function ( filterName ) {
-                       // Go over filters
-                       filterItem = widget.model.getItemByName( filterName );
-                       filterItem.toggleSelected( false );
-               } );
+       mw.rcfilters.ui.FilterWrapperWidget.prototype.onCapsulePopupToggle = function ( isVisible ) {
+               if ( !isVisible ) {
+                       this.filterPopup.resetSelection();
+                       this.capsule.resetSelection();
+               } else {
+                       this.scrollToTop( this.capsule.$element, 10 );
+               }
+       };
+
+       /**
+        * Respond to text input change
+        *
+        * @param {string} newValue Current value
+        */
+       mw.rcfilters.ui.FilterWrapperWidget.prototype.onTextInputChange = function ( newValue ) {
+               this.filterPopup.resetSelection();
+
+               // Filter the results
+               this.filterPopup.filter( this.model.findMatches( newValue ) );
+               this.capsule.popup.clip();
        };
 
        /**
         * from.
         */
        mw.rcfilters.ui.FilterWrapperWidget.prototype.onModelInitialize = function () {
-               var items,
-                       wrapper = this,
-                       filters = this.model.getItems();
-
-               // Reset
-               this.capsule.getMenu().clearItems();
-
-               // Insert hidden options for the capsule to get its item data from
-               items = filters.map( function ( filterItem ) {
-                       return new OO.ui.MenuOptionWidget( {
-                               data: filterItem.getName(),
-                               label: filterItem.getLabel()
-                       } );
-               } );
-
-               this.capsule.getMenu().addItems( items );
+               var wrapper = this;
 
                // Add defaults to capsule. We have to do this
                // after we added to the capsule menu, since that's
                // how the capsule multiselect widget knows which
                // object to add
-               filters.forEach( function ( filterItem ) {
+               this.model.getItems().forEach( function ( filterItem ) {
                        if ( filterItem.isSelected() ) {
-                               wrapper.addCapsuleItemFromName( filterItem.getName() );
+                               wrapper.capsule.addItemByName( filterItem.getName() );
                        }
                } );
        };
 
        /**
-        * Respond to model item update
-        *
-        * @param {mw.rcfilters.dm.FilterItem} item Filter item that was updated
+        * Respond to item update and reset the selection. This will make it so that
+        * any actual interaction with the system resets the selection state of any item.
         */
-       mw.rcfilters.ui.FilterWrapperWidget.prototype.onModelItemUpdate = function ( item ) {
-               if ( item.isSelected() ) {
-                       this.addCapsuleItemFromName( item.getName() );
-               } else {
-                       this.capsule.removeItemsFromData( [ item.getName() ] );
-               }
+       mw.rcfilters.ui.FilterWrapperWidget.prototype.onModelItemUpdate = function () {
+               this.filterPopup.resetSelection();
        };
 
        /**
-        * Add a capsule item by its filter name
+        * Scroll the element to top within its container
         *
-        * @param {string} itemName Filter name
+        * @private
+        * @param {jQuery} $element Element to position
+        * @param {number} [marginFromTop] When scrolling the entire widget to the top, leave this
+        *  much space (in pixels) above the widget.
         */
-       mw.rcfilters.ui.FilterWrapperWidget.prototype.addCapsuleItemFromName = function ( itemName ) {
-               var item = this.model.getItemByName( itemName );
-
-               this.capsule.addItemsFromData( [ itemName ] );
+       mw.rcfilters.ui.FilterWrapperWidget.prototype.scrollToTop = function ( $element, marginFromTop ) {
+               var container = OO.ui.Element.static.getClosestScrollableContainer( $element[ 0 ], 'y' ),
+                       pos = OO.ui.Element.static.getRelativePosition( $element, $( container ) );
 
-               // Deal with active/inactive capsule filter items
-               this.capsule.getItemFromData( itemName ).$element
-                       .toggleClass( 'mw-rcfilters-ui-filterCapsuleMultiselectWidget-item-inactive', !item.isActive() );
+               // Scroll to item
+               $( container ).animate( {
+                       scrollTop: $( container ).scrollTop() + pos.top + ( marginFromTop || 0 )
+               } );
        };
 }( mediaWiki ) );