Make 'groups' a data model in the FiltersViewModel
Transform the groups Object to a full data model that handles events, and connect the FilterGroupWidget to its model for responding to these events. Bug: T156533 Change-Id: Iebde3138e16bac7f62e8f557e5ce08f41a9535cb
This commit is contained in:
parent
728cd57b2d
commit
1ac69cd38d
6 changed files with 215 additions and 111 deletions
|
|
@ -1737,6 +1737,7 @@ return [
|
|||
'scripts' => [
|
||||
'resources/src/mediawiki.rcfilters/mw.rcfilters.js',
|
||||
'resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js',
|
||||
'resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js',
|
||||
'resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FiltersListWidget.js',
|
||||
'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterGroupWidget.js',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
( function ( mw ) {
|
||||
/**
|
||||
* View model for a filter group
|
||||
*
|
||||
* @mixins OO.EventEmitter
|
||||
* @mixins OO.EmitterList
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} [config] Configuration options
|
||||
* @cfg {string} [type='send_unselected_if_any'] Group type
|
||||
* @cfg {string} [title] Group title
|
||||
* @cfg {string} [separator='|'] Value separator for 'string_options' groups
|
||||
* @cfg {string} [exclusionType='default'] Group exclusion type
|
||||
* @cfg {boolean} [active] Group is active
|
||||
*/
|
||||
mw.rcfilters.dm.FilterGroup = function MwRcfiltersDmFilterGroup( config ) {
|
||||
config = config || {};
|
||||
|
||||
// Mixin constructor
|
||||
OO.EventEmitter.call( this );
|
||||
OO.EmitterList.call( this );
|
||||
|
||||
this.type = config.type || 'send_unselected_if_any';
|
||||
this.title = config.title;
|
||||
this.separator = config.separator || '|';
|
||||
this.exclusionType = config.exclusionType || 'default';
|
||||
this.active = !!config.active;
|
||||
};
|
||||
|
||||
/* Initialization */
|
||||
OO.initClass( mw.rcfilters.dm.FilterGroup );
|
||||
OO.mixinClass( mw.rcfilters.dm.FilterGroup, OO.EventEmitter );
|
||||
OO.mixinClass( mw.rcfilters.dm.FilterGroup, OO.EmitterList );
|
||||
|
||||
/* Events */
|
||||
|
||||
/**
|
||||
* @event update
|
||||
*
|
||||
* Group state has been updated
|
||||
*/
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Check the active status of the group and set it accordingly.
|
||||
*
|
||||
* @fires update
|
||||
*/
|
||||
mw.rcfilters.dm.FilterGroup.prototype.checkActive = function () {
|
||||
var active,
|
||||
count = 0;
|
||||
|
||||
// Recheck group activity
|
||||
this.getItems().forEach( function ( filterItem ) {
|
||||
count += Number( filterItem.isSelected() );
|
||||
} );
|
||||
|
||||
active = (
|
||||
count > 0 &&
|
||||
count < this.getItemCount()
|
||||
);
|
||||
|
||||
if ( this.active !== active ) {
|
||||
this.active = active;
|
||||
this.emit( 'update' );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get group active state
|
||||
*
|
||||
* @return {boolean} Active state
|
||||
*/
|
||||
mw.rcfilters.dm.FilterGroup.prototype.isActive = function () {
|
||||
return this.active;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get group type
|
||||
*
|
||||
* @return {string} Group type
|
||||
*/
|
||||
mw.rcfilters.dm.FilterGroup.prototype.getType = function () {
|
||||
return this.type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get group's title
|
||||
*
|
||||
* @return {string} Title
|
||||
*/
|
||||
mw.rcfilters.dm.FilterGroup.prototype.getTitle = function () {
|
||||
return this.title;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get group's values separator
|
||||
*
|
||||
* @return {string} Values separator
|
||||
*/
|
||||
mw.rcfilters.dm.FilterGroup.prototype.getSeparator = function () {
|
||||
return this.separator;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get group exclusion type
|
||||
*
|
||||
* @return {string} Exclusion type
|
||||
*/
|
||||
mw.rcfilters.dm.FilterGroup.prototype.getExclusionType = function () {
|
||||
return this.exclusionType;
|
||||
};
|
||||
}( mediaWiki ) );
|
||||
|
|
@ -54,6 +54,9 @@
|
|||
// Reapply the active state of filters
|
||||
this.reapplyActiveFilters( item );
|
||||
|
||||
// Recheck group activity state
|
||||
this.getGroup( item.getGroup() ).checkActive();
|
||||
|
||||
this.emit( 'itemUpdate', item );
|
||||
};
|
||||
|
||||
|
|
@ -67,8 +70,8 @@
|
|||
group = item.getGroup(),
|
||||
model = this;
|
||||
if (
|
||||
!this.groups[ group ].exclusionType ||
|
||||
this.groups[ group ].exclusionType === 'default'
|
||||
!this.getGroup( group ).getExclusionType() ||
|
||||
this.getGroup( group ).getExclusionType() === 'default'
|
||||
) {
|
||||
// Default behavior
|
||||
// If any parameter is selected, but:
|
||||
|
|
@ -76,16 +79,16 @@
|
|||
// - If the entire group is selected, all are inactive
|
||||
|
||||
// Check what's selected in the group
|
||||
selectedItemsCount = this.groups[ group ].filters.filter( function ( filterItem ) {
|
||||
selectedItemsCount = this.getGroupFilters( group ).filter( function ( filterItem ) {
|
||||
return filterItem.isSelected();
|
||||
} ).length;
|
||||
|
||||
this.groups[ group ].filters.forEach( function ( filterItem ) {
|
||||
this.getGroupFilters( group ).forEach( function ( filterItem ) {
|
||||
filterItem.toggleActive(
|
||||
selectedItemsCount > 0 ?
|
||||
// If some items are selected
|
||||
(
|
||||
selectedItemsCount === model.groups[ group ].filters.length ?
|
||||
selectedItemsCount === model.groups[ group ].getItemCount() ?
|
||||
// If **all** items are selected, they're all inactive
|
||||
false :
|
||||
// If not all are selected, then the selected are active
|
||||
|
|
@ -96,7 +99,7 @@
|
|||
true
|
||||
);
|
||||
} );
|
||||
} else if ( this.groups[ group ].exclusionType === 'explicit' ) {
|
||||
} else if ( this.getGroup( group ).getExclusionType() === 'explicit' ) {
|
||||
// Explicit behavior
|
||||
// - Go over the list of excluded filters to change their
|
||||
// active states accordingly
|
||||
|
|
@ -157,13 +160,14 @@
|
|||
this.excludedByMap = {};
|
||||
|
||||
$.each( filters, function ( group, data ) {
|
||||
model.groups[ group ] = model.groups[ group ] || {};
|
||||
model.groups[ group ].filters = model.groups[ group ].filters || [];
|
||||
|
||||
model.groups[ group ].title = data.title;
|
||||
model.groups[ group ].type = data.type;
|
||||
model.groups[ group ].separator = data.separator || '|';
|
||||
model.groups[ group ].exclusionType = data.exclusionType || 'default';
|
||||
if ( !model.groups[ group ] ) {
|
||||
model.groups[ group ] = new mw.rcfilters.dm.FilterGroup( {
|
||||
type: data.type,
|
||||
title: data.title,
|
||||
separator: data.separator,
|
||||
exclusionType: data.exclusionType
|
||||
} );
|
||||
}
|
||||
|
||||
selectedFilterNames = [];
|
||||
for ( i = 0; i < data.filters.length; i++ ) {
|
||||
|
|
@ -192,7 +196,7 @@
|
|||
selectedFilterNames.push( data.filters[ i ].name );
|
||||
}
|
||||
|
||||
model.groups[ group ].filters.push( filterItem );
|
||||
model.groups[ group ].addItems( filterItem );
|
||||
items.push( filterItem );
|
||||
}
|
||||
|
||||
|
|
@ -200,7 +204,7 @@
|
|||
// Store the default parameter group state
|
||||
// For this group, the parameter is group name and value is the names
|
||||
// of selected items
|
||||
model.defaultParams[ group ] = model.sanitizeStringOptionGroup( group, selectedFilterNames ).join( model.groups[ group ].separator );
|
||||
model.defaultParams[ group ] = model.sanitizeStringOptionGroup( group, selectedFilterNames ).join( model.groups[ group ].getSeparator() );
|
||||
}
|
||||
} );
|
||||
|
||||
|
|
@ -219,15 +223,7 @@
|
|||
};
|
||||
|
||||
/**
|
||||
* Get the object that defines groups and their filter items.
|
||||
* The structure of this response:
|
||||
* {
|
||||
* groupName: {
|
||||
* title: {string} Group title
|
||||
* type: {string} Group type
|
||||
* filters: {string[]} Filters in the group
|
||||
* }
|
||||
* }
|
||||
* Get the object that defines groups by their name.
|
||||
*
|
||||
* @return {Object} Filter groups
|
||||
*/
|
||||
|
|
@ -235,29 +231,6 @@
|
|||
return this.groups;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current state of the filters.
|
||||
*
|
||||
* Checks whether the filter group is active. This means at least one
|
||||
* filter is selected, but not all filters are selected.
|
||||
*
|
||||
* @param {string} groupName Group name
|
||||
* @return {boolean} Filter group is active
|
||||
*/
|
||||
mw.rcfilters.dm.FiltersViewModel.prototype.isFilterGroupActive = function ( groupName ) {
|
||||
var count = 0,
|
||||
filters = this.groups[ groupName ].filters;
|
||||
|
||||
filters.forEach( function ( filterItem ) {
|
||||
count += Number( filterItem.isSelected() );
|
||||
} );
|
||||
|
||||
return (
|
||||
count > 0 &&
|
||||
count < filters.length
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the representation of the parameters. These are the back-end
|
||||
* parameters representing the filters, but they represent the given
|
||||
|
|
@ -357,10 +330,10 @@
|
|||
result = {},
|
||||
groupItems = filterGroups || this.getFilterGroups();
|
||||
|
||||
$.each( groupItems, function ( group, data ) {
|
||||
filterItems = data.filters;
|
||||
$.each( groupItems, function ( group, model ) {
|
||||
filterItems = model.getItems();
|
||||
|
||||
if ( data.type === 'send_unselected_if_any' ) {
|
||||
if ( model.getType() === 'send_unselected_if_any' ) {
|
||||
// First, check if any of the items are selected at all.
|
||||
// If none is selected, we're treating it as if they are
|
||||
// all false
|
||||
|
|
@ -373,7 +346,7 @@
|
|||
result[ filterItems[ i ].getName() ] = anySelected ?
|
||||
Number( !filterItems[ i ].isSelected() ) : 0;
|
||||
}
|
||||
} else if ( data.type === 'string_options' ) {
|
||||
} else if ( model.getType() === 'string_options' ) {
|
||||
values = [];
|
||||
for ( i = 0; i < filterItems.length; i++ ) {
|
||||
if ( filterItems[ i ].isSelected() ) {
|
||||
|
|
@ -384,7 +357,7 @@
|
|||
if ( values.length === 0 || values.length === filterItems.length ) {
|
||||
result[ group ] = 'all';
|
||||
} else {
|
||||
result[ group ] = values.join( data.separator );
|
||||
result[ group ] = values.join( model.getSeparator() );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
|
@ -404,7 +377,7 @@
|
|||
*/
|
||||
mw.rcfilters.dm.FiltersViewModel.prototype.sanitizeStringOptionGroup = function( groupName, valueArray ) {
|
||||
var result = [],
|
||||
validNames = this.groups[ groupName ].filters.map( function ( filterItem ) {
|
||||
validNames = this.getGroupFilters( groupName ).map( function ( filterItem ) {
|
||||
return filterItem.getName();
|
||||
} );
|
||||
|
||||
|
|
@ -500,7 +473,7 @@
|
|||
} else if ( model.groups.hasOwnProperty( paramName ) ) {
|
||||
// This parameter represents a group (values are the filters)
|
||||
// this is equivalent to checking if the group is 'string_options'
|
||||
groupMap[ paramName ] = { filters: model.groups[ paramName ].filters };
|
||||
groupMap[ paramName ] = { filters: model.groups[ paramName ].getItems() };
|
||||
}
|
||||
} );
|
||||
|
||||
|
|
@ -510,7 +483,7 @@
|
|||
var paramValues, filterItem,
|
||||
allItemsInGroup = data.filters;
|
||||
|
||||
if ( model.groups[ group ].type === 'send_unselected_if_any' ) {
|
||||
if ( model.groups[ group ].getType() === 'send_unselected_if_any' ) {
|
||||
for ( i = 0; i < allItemsInGroup.length; i++ ) {
|
||||
filterItem = allItemsInGroup[ i ];
|
||||
|
||||
|
|
@ -523,8 +496,8 @@
|
|||
// group, which means the state is false
|
||||
false;
|
||||
}
|
||||
} else if ( model.groups[ group ].type === 'string_options' ) {
|
||||
paramValues = model.sanitizeStringOptionGroup( group, params[ group ].split( model.groups[ group ].separator ) );
|
||||
} else if ( model.groups[ group ].getType() === 'string_options' ) {
|
||||
paramValues = model.sanitizeStringOptionGroup( group, params[ group ].split( model.groups[ group ].getSeparator() ) );
|
||||
|
||||
for ( i = 0; i < allItemsInGroup.length; i++ ) {
|
||||
filterItem = allItemsInGroup[ i ];
|
||||
|
|
@ -533,7 +506,7 @@
|
|||
// If it is the word 'all'
|
||||
paramValues.length === 1 && paramValues[ 0 ] === 'all' ||
|
||||
// All values are written
|
||||
paramValues.length === model.groups[ group ].filters.length
|
||||
paramValues.length === model.groups[ group ].getItemCount()
|
||||
) ?
|
||||
// All true (either because all values are written or the term 'all' is written)
|
||||
// is the same as all filters set to false
|
||||
|
|
@ -587,6 +560,26 @@
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a group model from its name
|
||||
*
|
||||
* @param {string} groupName Group name
|
||||
* @return {mw.rcfilters.dm.FilterGroup} Group model
|
||||
*/
|
||||
mw.rcfilters.dm.FiltersViewModel.prototype.getGroup = function ( groupName ) {
|
||||
return this.groups[ groupName ];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all filters within a specified group by its name
|
||||
*
|
||||
* @param {string} groupName Group name
|
||||
* @return {mw.rcfilters.dm.FilterItem[]} Filters belonging to this group
|
||||
*/
|
||||
mw.rcfilters.dm.FiltersViewModel.prototype.getGroupFilters = function ( groupName ) {
|
||||
return ( this.getGroup( groupName ) && this.getGroup( groupName ).getItems() ) || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Find items whose labels match the given string
|
||||
*
|
||||
|
|
|
|||
|
|
@ -7,22 +7,31 @@
|
|||
* @mixins OO.ui.mixin.LabelElement
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} name Group name
|
||||
* @param {mw.rcfilters.Controller} controller Controller
|
||||
* @param {mw.rcfilters.dm.FilterGroup} model Filter group model
|
||||
* @param {Object} config Configuration object
|
||||
*/
|
||||
mw.rcfilters.ui.FilterGroupWidget = function MwRcfiltersUiFilterGroupWidget( name, config ) {
|
||||
mw.rcfilters.ui.FilterGroupWidget = function MwRcfiltersUiFilterGroupWidget( controller, model, config ) {
|
||||
config = config || {};
|
||||
|
||||
// Parent
|
||||
mw.rcfilters.ui.FilterGroupWidget.parent.call( this, config );
|
||||
|
||||
this.controller = controller;
|
||||
this.model = model;
|
||||
|
||||
// Mixin constructors
|
||||
OO.ui.mixin.GroupWidget.call( this, config );
|
||||
OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, {
|
||||
label: this.model.getTitle(),
|
||||
$label: $( '<div>' )
|
||||
.addClass( 'mw-rcfilters-ui-filterGroupWidget-title' )
|
||||
} ) );
|
||||
|
||||
this.name = name;
|
||||
// Populate
|
||||
this.populateFromModel();
|
||||
|
||||
this.model.connect( this, { update: 'onModelUpdate' } );
|
||||
|
||||
this.$element
|
||||
.addClass( 'mw-rcfilters-ui-filterGroupWidget' )
|
||||
|
|
@ -39,22 +48,39 @@
|
|||
OO.mixinClass( mw.rcfilters.ui.FilterGroupWidget, OO.ui.mixin.GroupWidget );
|
||||
OO.mixinClass( mw.rcfilters.ui.FilterGroupWidget, OO.ui.mixin.LabelElement );
|
||||
|
||||
/**
|
||||
* Respond to model update event
|
||||
*/
|
||||
mw.rcfilters.ui.FilterGroupWidget.prototype.onModelUpdate = function () {
|
||||
this.$element.toggleClass(
|
||||
'mw-rcfilters-ui-filterGroupWidget-active',
|
||||
this.model.isActive()
|
||||
);
|
||||
};
|
||||
|
||||
mw.rcfilters.ui.FilterGroupWidget.prototype.populateFromModel = function () {
|
||||
var widget = this;
|
||||
|
||||
this.addItems(
|
||||
this.model.getItems().map( function ( filterItem ) {
|
||||
return new mw.rcfilters.ui.FilterItemWidget(
|
||||
widget.controller,
|
||||
filterItem,
|
||||
{
|
||||
label: filterItem.getLabel(),
|
||||
description: filterItem.getDescription()
|
||||
}
|
||||
);
|
||||
} )
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the group name
|
||||
*
|
||||
* @return {string} Group name
|
||||
*/
|
||||
mw.rcfilters.ui.FilterGroupWidget.prototype.getName = function () {
|
||||
return this.name;
|
||||
return this.model.getName();
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the active state of this group
|
||||
*
|
||||
* @param {boolean} isActive The group is active
|
||||
*/
|
||||
mw.rcfilters.ui.FilterGroupWidget.prototype.toggleActiveState = function ( isActive ) {
|
||||
this.$element.toggleClass( 'mw-rcfilters-ui-filterGroupWidget-active', isActive );
|
||||
};
|
||||
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
|
|||
|
|
@ -132,20 +132,11 @@
|
|||
* @param {mw.rcfilters.dm.FilterItem} item Filter item that was updated
|
||||
*/
|
||||
mw.rcfilters.ui.FilterWrapperWidget.prototype.onModelItemUpdate = function ( item ) {
|
||||
var widget = this;
|
||||
|
||||
if ( item.isSelected() ) {
|
||||
this.addCapsuleItemFromName( item.getName() );
|
||||
} else {
|
||||
this.capsule.removeItemsFromData( [ item.getName() ] );
|
||||
}
|
||||
|
||||
// Toggle the active state of the group
|
||||
this.filterPopup.getItems().forEach( function ( groupWidget ) {
|
||||
if ( groupWidget.getName() === item.getGroup() ) {
|
||||
groupWidget.toggleActiveState( widget.model.isFilterGroupActive( groupWidget.getName() ) );
|
||||
}
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -60,40 +60,19 @@
|
|||
* Respond to initialize event from the model
|
||||
*/
|
||||
mw.rcfilters.ui.FiltersListWidget.prototype.onModelInitialize = function () {
|
||||
var i, group, groupWidget,
|
||||
itemWidgets = [],
|
||||
groupWidgets = [],
|
||||
groups = this.model.getFilterGroups();
|
||||
var widget = this;
|
||||
|
||||
// Reset
|
||||
this.clearItems();
|
||||
|
||||
for ( group in groups ) {
|
||||
groupWidget = new mw.rcfilters.ui.FilterGroupWidget( group, {
|
||||
label: groups[ group ].title
|
||||
} );
|
||||
groupWidgets.push( groupWidget );
|
||||
|
||||
itemWidgets = [];
|
||||
if ( groups[ group ].filters ) {
|
||||
for ( i = 0; i < groups[ group ].filters.length; i++ ) {
|
||||
itemWidgets.push(
|
||||
new mw.rcfilters.ui.FilterItemWidget(
|
||||
this.controller,
|
||||
groups[ group ].filters[ i ],
|
||||
{
|
||||
label: groups[ group ].filters[ i ].getLabel(),
|
||||
description: groups[ group ].filters[ i ].getDescription()
|
||||
}
|
||||
)
|
||||
this.addItems(
|
||||
Object.keys( this.model.getFilterGroups() ).map( function ( groupName ) {
|
||||
return new mw.rcfilters.ui.FilterGroupWidget(
|
||||
widget.controller,
|
||||
widget.model.getGroup( groupName )
|
||||
);
|
||||
} )
|
||||
);
|
||||
}
|
||||
|
||||
groupWidget.addItems( itemWidgets );
|
||||
}
|
||||
}
|
||||
|
||||
this.addItems( groupWidgets );
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue