' ).addClass( 'mw-rcfilters-ui-menuSelectWidget-body' );
this.footers = [];
// Parent
mw.rcfilters.ui.MenuSelectWidget.parent.call( this, $.extend( config, {
$autoCloseIgnore: this.$overlay,
width: 650,
// Our filtering is done through the model
filterFromInput: false
} ) );
this.setGroupElement(
$( '
' )
.addClass( 'mw-rcfilters-ui-menuSelectWidget-group' )
);
this.setClippableElement( this.$body );
this.setClippableContainer( this.$element );
header = new mw.rcfilters.ui.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 )
);
// 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: $( '
' )
.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( mw.rcfilters.ui.MenuSelectWidget, OO.ui.MenuSelectWidget );
/* Events */
/* Methods */
mw.rcfilters.ui.MenuSelectWidget.prototype.onModelSearchChange = function () {
this.updateView();
};
/**
* @inheritdoc
*/
mw.rcfilters.ui.MenuSelectWidget.prototype.toggle = function ( show ) {
this.lazyMenuCreation();
mw.rcfilters.ui.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
*/
mw.rcfilters.ui.MenuSelectWidget.prototype.lazyMenuCreation = function () {
var widget = this,
items = [],
viewGroupCount = {},
groups = this.model.getFilterGroups();
if ( this.menuInitialized ) {
return;
}
this.menuInitialized = true;
// Count groups per view
$.each( groups, function ( groupName, groupModel ) {
if ( !groupModel.isHidden() ) {
viewGroupCount[ groupModel.getView() ] = viewGroupCount[ groupModel.getView() ] || 0;
viewGroupCount[ groupModel.getView() ]++;
}
} );
$.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 mw.rcfilters.ui.FilterMenuSectionOptionWidget(
widget.controller,
groupModel,
{
$overlay: widget.$overlay
}
)
);
}
// Add items
widget.model.getGroupFilters( groupName ).forEach( function ( filterItem ) {
currentItems.push(
new mw.rcfilters.ui.FilterMenuOptionWidget(
widget.controller,
widget.model,
widget.model.getInvertModel(),
filterItem,
{
$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
*/
mw.rcfilters.ui.MenuSelectWidget.prototype.onModelInitialize = function () {
this.menuInitialized = false;
};
/**
* Update view
*/
mw.rcfilters.ui.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();
}
this.postProcessItems();
this.clip();
};
/**
* Go over the available footers and decide which should be visible
* for this view
*
* @param {string} [currentView] Current view
*/
mw.rcfilters.ui.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.
*/
mw.rcfilters.ui.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;
}
}
if ( !itemWasSelected ) {
this.selectItem( null );
}
}
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
*/
mw.rcfilters.ui.MenuSelectWidget.prototype.getItemFromModel = function ( model ) {
this.lazyMenuCreation();
return this.views[ model.getGroupModel().getView() ].filter( function ( item ) {
return item.getName() === model.getName();
} )[ 0 ];
};
/**
* @inheritdoc
*/
mw.rcfilters.ui.MenuSelectWidget.prototype.onKeyDown = function ( e ) {
var nextItem,
currentItem = this.findHighlightedItem() || this.findSelectedItem();
// Call parent
mw.rcfilters.ui.MenuSelectWidget.parent.prototype.onKeyDown.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
*/
mw.rcfilters.ui.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
*/
mw.rcfilters.ui.MenuSelectWidget.prototype.setUserSelecting = function ( isSelecting ) {
this.userSelecting = !!isSelecting;
};
}( mediaWiki ) );