From: Moriel Schottlender Date: Tue, 31 Jan 2017 01:08:42 +0000 (-0800) Subject: Make 'groups' a data model in the FiltersViewModel X-Git-Tag: 1.31.0-rc.0~4150^2 X-Git-Url: http://git.heureux-cyclage.org/?a=commitdiff_plain;h=1ac69cd38d417f7a0bcf93e42a0fb18db410a2fd;p=lhc%2Fweb%2Fwiklou.git 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 --- diff --git a/resources/Resources.php b/resources/Resources.php index f14787f815..94d7d99914 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -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', diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js new file mode 100644 index 0000000000..bc911f4aa7 --- /dev/null +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.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 ) ); diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js index 3f7fa53322..d1b7925c02 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js @@ -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 * 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 27232585d4..37182d6c46 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterGroupWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterGroupWidget.js @@ -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: $( '
' ) .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' ) @@ -40,21 +49,38 @@ OO.mixinClass( mw.rcfilters.ui.FilterGroupWidget, OO.ui.mixin.LabelElement ); /** - * Get the group name - * - * @return {string} Group name + * Respond to model update event */ - mw.rcfilters.ui.FilterGroupWidget.prototype.getName = function () { - return this.name; + 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() + } + ); + } ) + ); }; /** - * Toggle the active state of this group + * Get the group name * - * @param {boolean} isActive The group is active + * @return {string} Group name */ - mw.rcfilters.ui.FilterGroupWidget.prototype.toggleActiveState = function ( isActive ) { - this.$element.toggleClass( 'mw-rcfilters-ui-filterGroupWidget-active', isActive ); + mw.rcfilters.ui.FilterGroupWidget.prototype.getName = function () { + return this.model.getName(); }; - }( mediaWiki, jQuery ) ); 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 34cc240779..788ab3c2a2 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js @@ -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() ) ); - } - } ); }; /** 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 f5ec1fca6b..4ef3461269 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FiltersListWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FiltersListWidget.js @@ -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() - } - ) - ); - } - - groupWidget.addItems( itemWidgets ); - } - } - - this.addItems( groupWidgets ); + this.addItems( + Object.keys( this.model.getFilterGroups() ).map( function ( groupName ) { + return new mw.rcfilters.ui.FilterGroupWidget( + widget.controller, + widget.model.getGroup( groupName ) + ); + } ) + ); }; /**