From: jenkins-bot Date: Tue, 28 Feb 2017 17:49:52 +0000 (+0000) Subject: Merge "RCFilters UI: Scroll to filter when selected" X-Git-Tag: 1.31.0-rc.0~3955 X-Git-Url: http://git.cyclocoop.org/?a=commitdiff_plain;h=6f69987af18b6f256ca31957d4244b42235a8b37;hp=f17a70e253c24f5ceee072de10b7ef0b5b5e228a;p=lhc%2Fweb%2Fwiklou.git Merge "RCFilters UI: Scroll to filter when selected" --- diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterItemWidget.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterItemWidget.less index 94da3ac51a..21953da441 100644 --- a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterItemWidget.less +++ b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterItemWidget.less @@ -16,6 +16,10 @@ } } + &-selected { + background-color: #eaf3ff; // Accent90 AAA + } + &-label { &-title { font-weight: bold; diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.CapsuleItemWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.CapsuleItemWidget.js index cf03932eba..fc05649e3f 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.CapsuleItemWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.CapsuleItemWidget.js @@ -90,6 +90,14 @@ e.stopPropagation(); }; + /** + * Emit a click event when the capsule is clicked so we can aggregate this + * in the parent (the capsule) + */ + mw.rcfilters.ui.CapsuleItemWidget.prototype.onClick = function () { + this.emit( 'click' ); + }; + /** * Override the event listening to the item close button click */ diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterCapsuleMultiselectWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterCapsuleMultiselectWidget.js index 2bd2f0e194..8e0b2592fc 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterCapsuleMultiselectWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterCapsuleMultiselectWidget.js @@ -11,8 +11,6 @@ * @param {OO.ui.InputWidget} filterInput A filter input that focuses the capsule widget * @param {Object} config Configuration object * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups - * @cfg {number} [topScrollOffset=10] When scrolling the entire widget to the top, leave this - * much space (in pixels) above the widget. */ mw.rcfilters.ui.FilterCapsuleMultiselectWidget = function MwRcfiltersUiFilterCapsuleMultiselectWidget( controller, model, filterInput, config ) { var title = new OO.ui.LabelWidget( { @@ -32,8 +30,7 @@ this.controller = controller; this.model = model; this.filterInput = filterInput; - - this.topScrollOffset = config.topScrollOffset || 10; + this.isSelecting = false; this.resetButton = new OO.ui.ButtonWidget( { icon: 'trash', @@ -54,7 +51,7 @@ itemUpdate: 'onModelItemUpdate', highlightChange: 'onModelHighlightChange' } ); - this.popup.connect( this, { toggle: 'onPopupToggle' } ); + this.aggregate( { click: 'capsuleItemClick' } ); // Add the filterInput as trigger this.filterInput.$input @@ -167,28 +164,6 @@ } }; - /** - * Respond to popup toggle event - * - * @param {boolean} isVisible Popup is visible - */ - mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.onPopupToggle = function ( isVisible ) { - if ( isVisible ) { - this.scrollToTop(); - } - }; - - /** - * Scroll the capsule to the top of the screen - */ - mw.rcfilters.ui.FilterCapsuleMultiselectWidget.prototype.scrollToTop = function () { - var container = OO.ui.Element.static.getClosestScrollableContainer( this.$element[ 0 ], 'y' ); - - $( container ).animate( { - scrollTop: this.$element.offset().top - this.topScrollOffset - } ); - }; - /** * Reevaluate the restore state for the widget between setting to defaults and clearing all filters */ diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterGroupWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterGroupWidget.js index f858ab0fd6..a750c44f8c 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterGroupWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterGroupWidget.js @@ -19,6 +19,7 @@ this.controller = controller; this.model = model; + this.filters = {}; // Mixin constructors OO.ui.mixin.GroupWidget.call( this, config ); @@ -36,6 +37,7 @@ this.$element .addClass( 'mw-rcfilters-ui-filterGroupWidget' ) + .addClass( 'mw-rcfilters-ui-filterGroupWidget-name-' + this.model.getName() ) .append( this.$label, this.$group @@ -59,12 +61,28 @@ ); }; + /** + * Get an item widget from its filter name + * + * @param {string} filterName Filter name + * @return {mw.rcfilters.ui.FilterItemWidget} Item widget + */ + mw.rcfilters.ui.FilterGroupWidget.prototype.getItemWidget = function ( filterName ) { + return this.filters[ filterName ]; + }; + + /** + * Populate data from the model + */ mw.rcfilters.ui.FilterGroupWidget.prototype.populateFromModel = function () { var widget = this; + this.clearItems(); + this.filters = {}; + this.addItems( this.model.getItems().map( function ( filterItem ) { - return new mw.rcfilters.ui.FilterItemWidget( + var groupWidget = new mw.rcfilters.ui.FilterItemWidget( widget.controller, filterItem, { @@ -73,6 +91,10 @@ $overlay: widget.$overlay } ); + + widget.filters[ filterItem.getName() ] = groupWidget; + + return groupWidget; } ) ); }; diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemWidget.js index 63db2b042a..eda4fe7a4c 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemWidget.js @@ -21,6 +21,7 @@ this.controller = controller; this.model = model; + this.selected = false; this.checkboxWidget = new mw.rcfilters.ui.CheckboxInputWidget( { value: this.model.getName(), @@ -112,6 +113,21 @@ this.setCurrentMuteState(); }; + /** + * Set selected state on this widget + * + * @param {boolean} [isSelected] Widget is selected + */ + mw.rcfilters.ui.FilterItemWidget.prototype.toggleSelected = function ( isSelected ) { + isSelected = isSelected !== undefined ? isSelected : !this.selected; + + if ( this.selected !== isSelected ) { + this.selected = isSelected; + + this.$element.toggleClass( 'mw-rcfilters-ui-filterItemWidget-selected', this.selected ); + } + }; + /** * Set the current mute state for this item */ diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js index 9fa58f3e28..bb213fd5ec 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js @@ -50,11 +50,16 @@ // Events this.model.connect( this, { - initialize: 'onModelInitialize' + initialize: 'onModelInitialize', + itemUpdate: 'onModelItemUpdate' } ); this.textInput.connect( this, { change: 'onTextInputChange' } ); + 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 ); @@ -65,12 +70,43 @@ OO.inheritClass( mw.rcfilters.ui.FilterWrapperWidget, OO.ui.Widget ); OO.mixinClass( mw.rcfilters.ui.FilterWrapperWidget, OO.ui.mixin.PendingElement ); + /** + * Respond to capsule item click and make the popup scroll down to the requested item + * + * @param {mw.rcfilters.ui.CapsuleItemWidget} item Clicked item + */ + 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.scrollToTop( filterWidget.$element ); + }; + + /** + * Respond to popup toggle event. Reset selection in the list when the popup is closed. + * + * @param {boolean} isVisible Popup is visible + */ + mw.rcfilters.ui.FilterWrapperWidget.prototype.onCapsulePopupToggle = function ( isVisible ) { + if ( !isVisible ) { + this.filterPopup.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(); @@ -93,4 +129,30 @@ } } ); }; + + /** + * 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 () { + this.filterPopup.resetSelection(); + }; + + /** + * Scroll the element to top within its container + * + * @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.scrollToTop = function ( $element, marginFromTop ) { + var container = OO.ui.Element.static.getClosestScrollableContainer( $element[ 0 ], 'y' ), + pos = OO.ui.Element.static.getRelativePosition( $element, $( container ) ); + + // Scroll to item + $( container ).animate( { + scrollTop: $( container ).scrollTop() + pos.top + ( marginFromTop || 0 ) + } ); + }; }( mediaWiki ) ); diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FiltersListWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FiltersListWidget.js index ae9ee71efd..38679d75e3 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FiltersListWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FiltersListWidget.js @@ -26,6 +26,8 @@ this.controller = controller; this.model = model; this.$overlay = config.$overlay || this.$element; + this.groups = {}; + this.selected = null; this.highlightButton = new OO.ui.ButtonWidget( { label: mw.message( 'rcfilters-highlightbutton-title' ).text(), @@ -74,16 +76,20 @@ // Reset this.clearItems(); + this.groups = {}; this.addItems( Object.keys( this.model.getFilterGroups() ).map( function ( groupName ) { - return new mw.rcfilters.ui.FilterGroupWidget( + var groupWidget = new mw.rcfilters.ui.FilterGroupWidget( widget.controller, widget.model.getGroup( groupName ), { $overlay: widget.$overlay } ); + + widget.groups[ groupName ] = groupWidget; + return groupWidget; } ) ); }; @@ -99,6 +105,59 @@ this.controller.toggleHighlight(); }; + /** + * Find the filter item widget that corresponds to the item name + * + * @param {string} itemName Filter name + * @return {mw.rcfilters.ui.FilterItemWidget} Filter widget + */ + mw.rcfilters.ui.FiltersListWidget.prototype.getItemWidget = function ( itemName ) { + var filterItem = this.model.getItemByName( itemName ), + // Find the group + groupWidget = this.groups[ filterItem.getGroupName() ]; + + // Find the item inside the group + return groupWidget.getItemWidget( itemName ); + }; + + /** + * Mark an item widget as selected + * + * @param {string} itemName Filter name + */ + mw.rcfilters.ui.FiltersListWidget.prototype.select = function ( itemName ) { + var filterWidget; + + if ( this.selected !== itemName ) { + // Unselect previous + if ( this.selected ) { + filterWidget = this.getItemWidget( this.selected ); + filterWidget.toggleSelected( false ); + } + + // Select new one + this.selected = itemName; + if ( this.selected ) { + filterWidget = this.getItemWidget( this.selected ); + filterWidget.toggleSelected( true ); + } + } + }; + + /** + * Reset selection and remove selected states from all items + */ + mw.rcfilters.ui.FiltersListWidget.prototype.resetSelection = function () { + if ( this.selected !== null ) { + this.selected = null; + this.getItems().forEach( function ( groupWidget ) { + groupWidget.getItems().forEach( function ( filterItemWidget ) { + filterItemWidget.toggleSelected( false ); + } ); + } ); + } + }; + /** * Switch between showing the 'no results' message for filtering results or the result list. *