' )
.addClass( 'mw-rcfilters-ui-cell' )
.addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-views-select' )
.append( this.viewsSelectWidget.$element )
)
)
);
// Event
this.viewsSelectWidget.connect( this, { choose: 'onViewsSelectWidgetChoose' } );
rcFiltersRow.append(
$( '
' )
.addClass( 'mw-rcfilters-ui-cell' )
.addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-cell-reset' )
.append( this.resetButton.$element )
);
// Build the content
$contentWrapper.append(
title.$element,
this.savedQueryTitle.$element,
$( '
' )
.addClass( 'mw-rcfilters-ui-table' )
.append(
rcFiltersRow
)
);
// Initialize
this.$handle.append( $contentWrapper );
this.emptyFilterMessage.toggle( this.isEmpty() );
this.savedQueryTitle.toggle( false );
this.$element
.addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget' );
this.reevaluateResetRestoreState();
};
/* Initialization */
OO.inheritClass( mw.rcfilters.ui.FilterTagMultiselectWidget, OO.ui.MenuTagMultiselectWidget );
/* Methods */
/**
* Respond to view select widget choose event
*
* @param {OO.ui.ButtonOptionWidget} buttonOptionWidget Chosen widget
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onViewsSelectWidgetChoose = function ( buttonOptionWidget ) {
this.controller.switchView( buttonOptionWidget.getData() );
this.viewsSelectWidget.selectItem( null );
this.focus();
};
/**
* Respond to input change event
*
* @param {string} value Value of the input
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onInputChange = function ( value ) {
var view;
value = value.trim();
view = this.model.getViewByTrigger( value.substr( 0, 1 ) );
this.controller.switchView( view );
};
/**
* Respond to query button click
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onSaveQueryButtonClick = function () {
this.getMenu().toggle( false );
};
/**
* Respond to save query item change. Mainly this is done to update the label in case
* a query item has been edited
*
* @param {mw.rcfilters.dm.SavedQueryItemModel} item Saved query item
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onSavedQueriesItemUpdate = function ( item ) {
if ( this.matchingQuery === item ) {
// This means we just edited the item that is currently matched
this.savedQueryTitle.setLabel( item.getLabel() );
}
};
/**
* Respond to menu toggle
*
* @param {boolean} isVisible Menu is visible
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onMenuToggle = function ( isVisible ) {
// Parent
mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.onMenuToggle.call( this );
if ( isVisible ) {
mw.hook( 'RcFilters.popup.open' ).fire();
if ( !this.getMenu().getSelectedItem() ) {
// If there are no selected items, scroll menu to top
// This has to be in a setTimeout so the menu has time
// to be positioned and fixed
setTimeout( function () { this.getMenu().scrollToTop(); }.bind( this ), 0 );
}
} else {
// Clear selection
this.selectTag( null );
// Clear input if the only thing in the input is the prefix
if (
this.input.getValue().trim() === this.model.getViewTrigger( this.model.getCurrentView() )
) {
// Clear the input
this.input.setValue( '' );
}
// Log filter grouping
this.controller.trackFilterGroupings( 'filtermenu' );
}
this.input.setIcon( isVisible ? 'search' : 'menu' );
};
/**
* @inheritdoc
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onInputFocus = function () {
// Parent
mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.onInputFocus.call( this );
// Scroll to top
this.scrollToTop( this.$element );
};
/**
* @inheritdoc
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.doInputEscape = function () {
// Parent
mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.doInputEscape.call( this );
// Blur the input
this.input.$input.blur();
};
/**
* @inheritdoc
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onChangeTags = function () {
// Parent method
mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.onChangeTags.call( this );
this.emptyFilterMessage.toggle( this.isEmpty() );
};
/**
* Respond to model initialize event
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onModelInitialize = function () {
this.setSavedQueryVisibility();
};
/**
* Respond to model update event
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onModelUpdate = function () {
this.updateElementsForView();
};
/**
* Update the elements in the widget to the current view
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.updateElementsForView = function () {
var view = this.model.getCurrentView(),
inputValue = this.input.getValue().trim(),
inputView = this.model.getViewByTrigger( inputValue.substr( 0, 1 ) );
if ( inputView !== 'default' ) {
// We have a prefix already, remove it
inputValue = inputValue.substr( 1 );
}
if ( inputView !== view ) {
// Add the correct prefix
inputValue = this.model.getViewTrigger( view ) + inputValue;
}
// Update input
this.input.setValue( inputValue );
};
/**
* Set the visibility of the saved query button
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.setSavedQueryVisibility = function () {
if ( this.areSavedQueriesEnabled ) {
this.matchingQuery = this.controller.findQueryMatchingCurrentState();
this.savedQueryTitle.setLabel(
this.matchingQuery ? this.matchingQuery.getLabel() : ''
);
this.savedQueryTitle.toggle( !!this.matchingQuery );
this.saveQueryButton.toggle(
!this.isEmpty() &&
!this.matchingQuery
);
if ( this.matchingQuery ) {
this.emphasize();
}
}
};
/**
* Respond to model itemUpdate event
*
* @param {mw.rcfilters.dm.FilterItem} item Filter item model
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onModelItemUpdate = function ( item ) {
if ( item.getGroupModel().isHidden() ) {
return;
}
if (
item.isSelected() ||
(
this.model.isHighlightEnabled() &&
item.isHighlightSupported() &&
item.getHighlightColor()
)
) {
this.addTag( item.getName(), item.getLabel() );
} else {
this.removeTagByData( item.getName() );
}
this.setSavedQueryVisibility();
// Re-evaluate reset state
this.reevaluateResetRestoreState();
};
/**
* @inheritdoc
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.isAllowedData = function ( data ) {
return (
this.model.getItemByName( data ) &&
!this.isDuplicateData( data )
);
};
/**
* @inheritdoc
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onMenuChoose = function ( item ) {
this.controller.toggleFilterSelect( item.model.getName() );
// Select the tag if it exists, or reset selection otherwise
this.selectTag( this.getItemFromData( item.model.getName() ) );
this.focus();
};
/**
* Respond to highlightChange event
*
* @param {boolean} isHighlightEnabled Highlight is enabled
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onModelHighlightChange = function ( isHighlightEnabled ) {
var highlightedItems = this.model.getHighlightedItems();
if ( isHighlightEnabled ) {
// Add capsule widgets
highlightedItems.forEach( function ( filterItem ) {
this.addTag( filterItem.getName(), filterItem.getLabel() );
}.bind( this ) );
} else {
// Remove capsule widgets if they're not selected
highlightedItems.forEach( function ( filterItem ) {
if ( !filterItem.isSelected() ) {
this.removeTagByData( filterItem.getName() );
}
}.bind( this ) );
}
};
/**
* @inheritdoc
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onTagSelect = function ( tagItem ) {
var widget = this,
menuOption = this.menu.getItemFromModel( tagItem.getModel() ),
oldInputValue = this.input.getValue().trim();
this.menu.setUserSelecting( true );
// Reset input
this.input.setValue( '' );
// Switch view
this.controller.switchView( tagItem.getView() );
// Parent method
mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.onTagSelect.call( this, tagItem );
this.menu.selectItem( menuOption );
this.selectTag( tagItem );
// Scroll to the item
if ( this.model.removeViewTriggers( oldInputValue ) ) {
// We're binding a 'once' to the itemVisibilityChange event
// so this happens when the menu is ready after the items
// are visible again, in case this is done right after the
// user filtered the results
this.getMenu().once(
'itemVisibilityChange',
function () {
widget.scrollToTop( menuOption.$element );
widget.menu.setUserSelecting( false );
}
);
} else {
this.scrollToTop( menuOption.$element );
this.menu.setUserSelecting( false );
}
};
/**
* Select a tag by reference. This is what OO.ui.SelectWidget is doing.
* If no items are given, reset selection from all.
*
* @param {mw.rcfilters.ui.FilterTagItemWidget} [item] Tag to select,
* omit to deselect all
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.selectTag = function ( item ) {
var i, len, selected;
for ( i = 0, len = this.items.length; i < len; i++ ) {
selected = this.items[ i ] === item;
if ( this.items[ i ].isSelected() !== selected ) {
this.items[ i ].toggleSelected( selected );
}
}
};
/**
* @inheritdoc
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onTagRemove = function ( tagItem ) {
// Parent method
mw.rcfilters.ui.FilterTagMultiselectWidget.parent.prototype.onTagRemove.call( this, tagItem );
this.controller.clearFilter( tagItem.getName() );
tagItem.destroy();
};
/**
* Respond to click event on the reset button
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.onResetButtonClick = function () {
if ( this.model.areCurrentFiltersEmpty() ) {
// Reset to default filters
this.controller.resetToDefaults();
} else {
// Reset to have no filters
this.controller.emptyFilters();
}
};
/**
* Reevaluate the restore state for the widget between setting to defaults and clearing all filters
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.reevaluateResetRestoreState = function () {
var defaultsAreEmpty = this.model.areDefaultFiltersEmpty(),
currFiltersAreEmpty = this.model.areCurrentFiltersEmpty(),
hideResetButton = currFiltersAreEmpty && defaultsAreEmpty;
this.resetButton.setIcon(
currFiltersAreEmpty ? 'history' : 'trash'
);
this.resetButton.setLabel(
currFiltersAreEmpty ? mw.msg( 'rcfilters-restore-default-filters' ) : ''
);
this.resetButton.setTitle(
currFiltersAreEmpty ? null : mw.msg( 'rcfilters-clear-all-filters' )
);
this.resetButton.toggle( !hideResetButton );
this.emptyFilterMessage.toggle( currFiltersAreEmpty );
};
/**
* @inheritdoc
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.createMenuWidget = function ( menuConfig ) {
return new mw.rcfilters.ui.MenuSelectWidget(
this.controller,
this.model,
$.extend( {
filterFromInput: true
}, menuConfig )
);
};
/**
* @inheritdoc
*/
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.createTagItemWidget = function ( data ) {
var filterItem = this.model.getItemByName( data );
if ( filterItem ) {
return new mw.rcfilters.ui.FilterTagItemWidget(
this.controller,
filterItem,
{
$overlay: this.$overlay
}
);
}
};
mw.rcfilters.ui.FilterTagMultiselectWidget.prototype.emphasize = function () {
if (
!this.$handle.hasClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-animate' )
) {
this.$handle
.addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-emphasize' )
.addClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-animate' );
setTimeout( function () {
this.$handle
.removeClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-emphasize' );
setTimeout( function () {
this.$handle
.removeClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-animate' );
}.bind( this ), 1000 );
}.bind( this ), 500 );
}
};
/**
* 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.FilterTagMultiselectWidget.prototype.scrollToTop = function ( $element, marginFromTop ) {
var container = OO.ui.Element.static.getClosestScrollableContainer( $element[ 0 ], 'y' ),
pos = OO.ui.Element.static.getRelativePosition( $element, $( container ) ),
containerScrollTop = $( container ).is( 'body, html' ) ? 0 : $( container ).scrollTop();
// Scroll to item
$( container ).animate( {
scrollTop: containerScrollTop + pos.top - ( marginFromTop || 0 )
} );
};
}( mediaWiki ) );