this.defaultParams = {};
this.defaultFiltersEmpty = null;
this.highlightEnabled = false;
- this.invertedNamespaces = false;
this.parameterMap = {};
this.views = {};
* Highlight feature has been toggled enabled or disabled
*/
- /**
- * @event invertChange
- * @param {boolean} isInverted Namespace selected is inverted
- *
- * Namespace selection is inverted or straight forward
- */
-
/* Methods */
/**
* Propagate the change to namespace filter items.
*
* @param {boolean} enable Inverted property is enabled
- * @fires invertChange
*/
mw.rcfilters.dm.FiltersViewModel.prototype.toggleInvertedNamespaces = function ( enable ) {
- enable = enable === undefined ? !this.invertedNamespaces : enable;
-
- if ( this.invertedNamespaces !== enable ) {
- this.invertedNamespaces = enable;
-
- this.getFiltersByView( 'namespaces' ).forEach( function ( filterItem ) {
- filterItem.toggleInverted( this.invertedNamespaces );
- }.bind( this ) );
-
- this.emit( 'invertChange', this.invertedNamespaces );
- }
+ this.toggleFilterSelected( this.getInvertModel().getName(), enable );
};
/**
- * Check if the namespaces selection is set to be inverted
- * @return {boolean}
+ * Get the model object that represents the 'invert' filter
+ *
+ * @return {mw.rcfilters.dm.FilterItem}
*/
- mw.rcfilters.dm.FiltersViewModel.prototype.areNamespacesInverted = function () {
- return !!this.invertedNamespaces;
+ mw.rcfilters.dm.FiltersViewModel.prototype.getInvertModel = function () {
+ return this.getGroup( 'invertGroup' ).getItemByParamName( 'invert' );
};
/**
* with 'default' and 'inverted' as keys.
* @cfg {boolean} [active=true] The filter is active and affecting the result
* @cfg {boolean} [selected] The item is selected
- * @cfg {boolean} [inverted] The item is inverted, meaning the search is excluding
- * this parameter.
* @cfg {string} [namePrefix='item_'] A prefix to add to the param name to act as a unique
* identifier
* @cfg {string} [cssClass] The class identifying the results that match this filter
this.description = config.description || '';
this.selected = !!config.selected;
- this.inverted = !!config.inverted;
this.identifiers = config.identifiers || [];
// Highlight
*/
mw.rcfilters.dm.ItemModel.prototype.getState = function () {
return {
- selected: this.isSelected(),
- inverted: this.isInverted()
+ selected: this.isSelected()
};
};
/**
* Get a prefixed label
*
+ * @param {boolean} inverted This item should be considered inverted
* @return {string} Prefixed label
*/
- mw.rcfilters.dm.ItemModel.prototype.getPrefixedLabel = function () {
+ mw.rcfilters.dm.ItemModel.prototype.getPrefixedLabel = function ( inverted ) {
if ( this.labelPrefixKey ) {
if ( typeof this.labelPrefixKey === 'string' ) {
return mw.message( this.labelPrefixKey, this.getLabel() ).parse();
this.labelPrefixKey[
// Only use inverted-prefix if the item is selected
// Highlight-only an inverted item makes no sense
- this.isInverted() && this.isSelected() ?
+ inverted && this.isSelected() ?
'inverted' : 'default'
],
this.getLabel()
}
};
- /**
- * Get the inverted state of this item
- *
- * @return {boolean} Item is inverted
- */
- mw.rcfilters.dm.ItemModel.prototype.isInverted = function () {
- return this.inverted;
- };
-
- /**
- * Toggle the inverted state of the item
- *
- * @param {boolean} [isInverted] Item is inverted
- * @fires update
- */
- mw.rcfilters.dm.ItemModel.prototype.toggleInverted = function ( isInverted ) {
- isInverted = isInverted === undefined ? !this.inverted : isInverted;
-
- if ( this.inverted !== isInverted ) {
- this.inverted = isInverted;
- this.emit( 'update' );
- }
- };
-
/**
* Set the highlight color
*
newData.highlights[ highlightedFilterName + '_color' ] = data.highlights[ highlightedFilterName ];
} );
- // Add highlight and invert toggles to params
+ // Add highlight
newData.params.highlight = String( Number( highlightEnabled || 0 ) );
- newData.params.invert = String( Number( data.invert || 0 ) );
return newData;
};
} );
this.baseParamState = {
- params: $.extend( true, { invert: '0', highlight: '0' }, allParams ),
+ params: $.extend( true, { highlight: '0' }, allParams ),
highlights: highlightedItems
};
}
separator: ';',
fullCoverage: true,
filters: items
+ },
+ {
+ name: 'invertGroup',
+ type: 'boolean',
+ hidden: true,
+ filters: [ {
+ name: 'invert',
+ 'default': '0'
+ } ]
} ]
};
}
params: $.extend(
true,
{
- invert: String( Number( this.filtersModel.areNamespacesInverted() ) ),
highlight: String( Number( this.filtersModel.isHighlightEnabled() ) )
},
this.filtersModel.getParametersFromFilters( selectedState )
)
);
- // Update namespace inverted property
- this.filtersModel.toggleInvertedNamespaces( !!Number( data.params.invert ) );
-
// Update highlight state
this.filtersModel.toggleHighlight( !!Number( data.params.highlight ) );
this.filtersModel.getItems().forEach( function ( filterItem ) {
params: $.extend(
true,
{
- highlight: String( Number( this.filtersModel.isHighlightEnabled() ) ),
- invert: String( Number( this.filtersModel.areNamespacesInverted() ) )
+ highlight: String( Number( this.filtersModel.isHighlightEnabled() ) )
},
this.filtersModel.getParametersFromFilters( selectedState )
),
return $.extend( true, {},
this.filtersModel.getParametersFromFilters( savedFilters ),
data.highlights,
- { highlight: data.params.highlight, invert: data.params.invert }
+ { highlight: data.params.highlight }
);
}
return this.filtersModel.getDefaultParams();
)
);
- this.filtersModel.toggleInvertedNamespaces( !!Number( parameters.invert ) );
-
// Update highlight state
this.filtersModel.getItems().forEach( function ( filterItem ) {
var color = parameters[ filterItem.getName() + '_color' ];
this.filtersModel.getParametersFromFilters(),
this.filtersModel.getHighlightParameters(),
{
- highlight: String( Number( this.filtersModel.isHighlightEnabled() ) ),
- invert: String( Number( this.filtersModel.areNamespacesInverted() ) )
+ highlight: String( Number( this.filtersModel.isHighlightEnabled() ) )
}
);
};
this.filtersModel.getParametersFromFilters( filterRepresentation ),
this.filtersModel.extractHighlightValues( uriQuery ),
{
- highlight: String( Number( uriQuery.highlight ) ),
- invert: String( Number( uriQuery.invert ) )
+ highlight: String( Number( uriQuery.highlight ) )
}
);
};
{},
emptyParams,
emptyHighlights,
- { highlight: '0', invert: '0' }
+ { highlight: '0' }
);
};
}( mediaWiki, jQuery ) );
classes: [ 'mw-rcfilters-ui-filterMenuHeaderWidget-invertNamespacesButton' ]
} );
this.invertNamespacesButton.toggle( this.model.getCurrentView() === 'namespaces' );
- this.updateInvertButton( this.model.areNamespacesInverted() );
// Events
this.backButton.connect( this, { click: 'onBackButtonClick' } );
.connect( this, { click: 'onInvertNamespacesButtonClick' } );
this.model.connect( this, {
highlightChange: 'onModelHighlightChange',
- invertChange: 'onModelInvertChange',
- update: 'onModelUpdate'
+ update: 'onModelUpdate',
+ initialize: 'onModelInitialize'
} );
// Initialize
/* Methods */
+ /**
+ * Respond to model initialization event
+ *
+ * Note: need to wait for initialization before getting the invertModel
+ * and registering its update event. Creating all the models before the UI
+ * would help with that.
+ */
+ mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.onModelInitialize = function () {
+ this.invertModel = this.model.getInvertModel();
+ this.updateInvertButton();
+ this.invertModel.connect( this, { update: 'updateInvertButton' } );
+ };
+
/**
* Respond to model update event
*/
this.highlightButton.setActive( highlightEnabled );
};
- /**
- * Respond to model invert change event
- *
- * @param {boolean} isInverted Namespaces selection is inverted
- */
- mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.onModelInvertChange = function ( isInverted ) {
- this.updateInvertButton( isInverted );
- };
-
/**
* Update the state of the invert button
- *
- * @param {boolean} isInverted Namespaces selection is inverted
*/
- mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.updateInvertButton = function ( isInverted ) {
- this.invertNamespacesButton.setActive( isInverted );
+ mw.rcfilters.ui.FilterMenuHeaderWidget.prototype.updateInvertButton = function () {
+ this.invertNamespacesButton.setActive( this.invertModel.isSelected() );
this.invertNamespacesButton.setLabel(
- isInverted ?
+ this.invertModel.isSelected() ?
mw.msg( 'rcfilters-exclude-button-on' ) :
mw.msg( 'rcfilters-exclude-button-off' )
);
*
* @constructor
* @param {mw.rcfilters.Controller} controller RCFilters controller
+ * @param {mw.rcfilters.dm.FilterItem} invertModel
* @param {mw.rcfilters.dm.FilterItem} model Filter item model
* @param {Object} config Configuration object
*/
- mw.rcfilters.ui.FilterMenuOptionWidget = function MwRcfiltersUiFilterMenuOptionWidget( controller, model, config ) {
+ mw.rcfilters.ui.FilterMenuOptionWidget = function MwRcfiltersUiFilterMenuOptionWidget( controller, invertModel, model, config ) {
config = config || {};
this.controller = controller;
+ this.invertModel = invertModel;
this.model = model;
// Parent
- mw.rcfilters.ui.FilterMenuOptionWidget.parent.call( this, controller, model, config );
+ mw.rcfilters.ui.FilterMenuOptionWidget.parent.call( this, controller, this.invertModel, model, config );
// Event
this.model.getGroupModel().connect( this, { update: 'onGroupModelUpdate' } );
mw.rcfilters.ui.FilterMenuOptionWidget.prototype.setCurrentMuteState = function () {
if (
this.model.getGroupModel().getView() === 'namespaces' &&
- this.model.isInverted()
+ this.invertModel.isSelected()
) {
// This is an inverted behavior than the other rules, specifically
// for inverted namespaces
*
* @constructor
* @param {mw.rcfilters.Controller} controller
+ * @param {mw.rcfilters.dm.FilterItem} invertModel
* @param {mw.rcfilters.dm.FilterItem} model Item model
* @param {Object} config Configuration object
*/
- mw.rcfilters.ui.FilterTagItemWidget = function MwRcfiltersUiFilterTagItemWidget( controller, model, config ) {
+ mw.rcfilters.ui.FilterTagItemWidget = function MwRcfiltersUiFilterTagItemWidget( controller, invertModel, model, config ) {
config = config || {};
- mw.rcfilters.ui.FilterTagItemWidget.parent.call( this, controller, model, config );
+ mw.rcfilters.ui.FilterTagItemWidget.parent.call( this, controller, invertModel, model, config );
this.$element
.addClass( 'mw-rcfilters-ui-filterTagItemWidget' );
* @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() );
+ if ( !item.getGroupModel().isHidden() ) {
+ if (
+ item.isSelected() ||
+ (
+ this.model.isHighlightEnabled() &&
+ item.isHighlightSupported() &&
+ item.getHighlightColor()
+ )
+ ) {
+ this.addTag( item.getName(), item.getLabel() );
+ } else {
+ this.removeTagByData( item.getName() );
+ }
}
this.setSavedQueryVisibility();
if ( filterItem ) {
return new mw.rcfilters.ui.FilterTagItemWidget(
this.controller,
+ this.model.getInvertModel(),
filterItem,
{
$overlay: this.$overlay
*
* @constructor
* @param {mw.rcfilters.Controller} controller RCFilters controller
+ * @param {mw.rcfilters.dm.ItemModel} invertModel
* @param {mw.rcfilters.dm.ItemModel} model Item model
* @param {Object} config Configuration object
*/
- mw.rcfilters.ui.ItemMenuOptionWidget = function MwRcfiltersUiItemMenuOptionWidget( controller, model, config ) {
+ mw.rcfilters.ui.ItemMenuOptionWidget = function MwRcfiltersUiItemMenuOptionWidget( controller, invertModel, model, config ) {
var layout,
classes = [],
$label = $( '<div>' )
config = config || {};
this.controller = controller;
+ this.invertModel = invertModel;
this.model = model;
// Parent
this.excludeLabel = new OO.ui.LabelWidget( {
label: mw.msg( 'rcfilters-filter-excluded' )
} );
- this.excludeLabel.toggle( this.model.isSelected() && this.model.isInverted() );
+ this.excludeLabel.toggle( this.model.isSelected() && this.invertModel.isSelected() );
layout = new OO.ui.FieldLayout( this.checkboxWidget, {
label: $label,
} );
// Events
+ this.invertModel.connect( this, { update: 'onModelUpdate' } );
this.model.connect( this, { update: 'onModelUpdate' } );
// HACK: Prevent defaults on 'click' for the label so it
// doesn't steal the focus away from the input. This means
this.checkboxWidget.setSelected( this.model.isSelected() );
this.highlightButton.toggle( this.model.isHighlightEnabled() );
- this.excludeLabel.toggle( this.model.isSelected() && this.model.isInverted() );
+ this.excludeLabel.toggle( this.model.isSelected() && this.invertModel.isSelected() );
};
/**
currentItems.push(
new mw.rcfilters.ui.FilterMenuOptionWidget(
widget.controller,
+ widget.model.getInvertModel(),
filterItem,
{
$overlay: widget.$overlay
*
* @constructor
* @param {mw.rcfilters.Controller} controller
+ * @param {mw.rcfilters.dm.FilterItem} invertModel
* @param {mw.rcfilters.dm.FilterItem} model Item model
* @param {Object} config Configuration object
* @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups
*/
- mw.rcfilters.ui.TagItemWidget = function MwRcfiltersUiTagItemWidget( controller, model, config ) {
+ mw.rcfilters.ui.TagItemWidget = function MwRcfiltersUiTagItemWidget( controller, invertModel, model, config ) {
// Configuration initialization
config = config || {};
this.controller = controller;
+ this.invertModel = invertModel;
this.model = model;
this.selected = false;
mw.rcfilters.ui.TagItemWidget.parent.call( this, $.extend( {
- data: this.model.getName(),
- label: $( '<div>' ).html( this.model.getPrefixedLabel() ).contents()
+ data: this.model.getName()
}, config ) );
this.$overlay = config.$overlay || this.$element;
this.closeButton.setTitle( mw.msg( 'rcfilters-tag-remove', this.model.getLabel() ) );
// Events
- this.model.connect( this, { update: 'onModelUpdate' } );
+ this.invertModel.connect( this, { update: 'updateUiBasedOnState' } );
+ this.model.connect( this, { update: 'updateUiBasedOnState' } );
// Initialization
this.$overlay.append( this.popup.$element );
.on( 'mouseenter', this.onMouseEnter.bind( this ) )
.on( 'mouseleave', this.onMouseLeave.bind( this ) );
- this.setCurrentMuteState();
- this.setHighlightColor();
+ this.updateUiBasedOnState();
};
/* Initialization */
/**
* Respond to model update event
*/
- mw.rcfilters.ui.TagItemWidget.prototype.onModelUpdate = function () {
+ mw.rcfilters.ui.TagItemWidget.prototype.updateUiBasedOnState = function () {
this.setCurrentMuteState();
// Update label if needed
- this.setLabel( $( '<div>' ).html( this.model.getPrefixedLabel() ).contents() );
+ this.setLabel( $( '<div>' ).html( this.model.getPrefixedLabel( this.invertModel.isSelected() ) ).contents() );
this.setHighlightColor();
};
filter4: '0',
group3: '',
highlight: '0',
- invert: '0',
group1__filter1_color: null,
group1__filter2_color: null,
group2__filter3_color: null,
} ),
'Highlight parameters in Uri query set highlight state in the model'
);
-
- uriProcessor.updateModelBasedOnQuery( { invert: '1', urlversion: '2' } );
- assert.deepEqual(
- uriProcessor.getUriParametersFromModel(),
- $.extend( true, {}, baseParams, {
- invert: '1'
- } ),
- 'Invert parameter in Uri query set invert state in the model'
- );
} );
QUnit.test( 'isNewState', function ( assert ) {
highlight: true,
filter1: 'c5',
group3option1: 'c1'
- },
- invert: true
+ }
}
}
}
// Group type string_options
group2: 'filter4',
// Note - Group3 is sticky, so it won't show in output
- // Invert/highlight toggles
- invert: '1',
+ // highlight toggle
highlight: '1'
},
highlights: {
group2: 'filter5',
filter1: '0',
filter2: '0',
- highlight: '1',
- invert: '0'
+ highlight: '1'
},
highlights: {
filter1_color: 'c5',