5 * @mixins OO.EventEmitter
8 * @param {string} param Filter param name
9 * @param {mw.rcfilters.dm.FilterGroup} groupModel Filter group model
10 * @param {Object} config Configuration object
11 * @cfg {string} [group] The group this item belongs to
12 * @cfg {string} [label] The label for the filter
13 * @cfg {string} [description] The description of the filter
14 * @cfg {boolean} [active=true] The filter is active and affecting the result
15 * @cfg {string[]} [excludes=[]] A list of filter names this filter, if
16 * selected, makes inactive.
17 * @cfg {boolean} [selected] The item is selected
18 * @cfg {string[]} [subset] Defining the names of filters that are a subset of this filter
19 * @cfg {Object} [conflicts] Defines the conflicts for this filter
20 * @cfg {string} [cssClass] The class identifying the results that match this filter
22 mw
.rcfilters
.dm
.FilterItem
= function MwRcfiltersDmFilterItem( param
, groupModel
, config
) {
23 config
= config
|| {};
26 OO
.EventEmitter
.call( this );
29 this.groupModel
= groupModel
;
30 this.name
= this.groupModel
.getNamePrefix() + param
;
32 this.label
= config
.label
|| this.name
;
33 this.description
= config
.description
;
34 this.selected
= !!config
.selected
;
36 // Interaction definitions
37 this.subset
= config
.subset
|| [];
38 this.conflicts
= config
.conflicts
|| {};
42 this.included
= false;
43 this.conflicted
= false;
44 this.fullyCovered
= false;
47 this.cssClass
= config
.cssClass
;
48 this.highlightColor
= null;
49 this.highlightEnabled
= false;
54 OO
.initClass( mw
.rcfilters
.dm
.FilterItem
);
55 OO
.mixinClass( mw
.rcfilters
.dm
.FilterItem
, OO
.EventEmitter
);
62 * The state of this filter has changed
68 * Return the representation of the state of this item.
70 * @return {Object} State of the object
72 mw
.rcfilters
.dm
.FilterItem
.prototype.getState = function () {
74 selected
: this.isSelected(),
75 included
: this.isIncluded(),
76 conflicted
: this.isConflicted(),
77 fullyCovered
: this.isFullyCovered()
82 * Get the name of this filter
84 * @return {string} Filter name
86 mw
.rcfilters
.dm
.FilterItem
.prototype.getName = function () {
91 * Get the param name or value of this filter
93 * @return {string} Filter param name
95 mw
.rcfilters
.dm
.FilterItem
.prototype.getParamName = function () {
100 * Get the message for the display area for the currently active conflict
102 * @return {string} Conflict result message key
104 mw
.rcfilters
.dm
.FilterItem
.prototype.getCurrentConflictResultMessage = function () {
107 // First look in filter's own conflicts
108 details
= this.getConflictDetails( this.getOwnConflicts(), 'globalDescription' );
109 if ( !details
.message
) {
110 // Fall back onto conflicts in the group
111 details
= this.getConflictDetails( this.getGroupModel().getConflicts(), 'globalDescription' );
114 return details
.message
;
118 * Get the details of the active conflict on this filter
120 * @param {Object} conflicts Conflicts to examine
121 * @param {string} [key='contextDescription'] Message key
122 * @return {Object} Object with conflict message and conflict items
123 * @return {string} return.message Conflict message
124 * @return {string[]} return.names Conflicting item labels
126 mw
.rcfilters
.dm
.FilterItem
.prototype.getConflictDetails = function ( conflicts
, key
) {
128 conflictMessage
= '',
131 key
= key
|| 'contextDescription';
133 $.each( conflicts
, function ( filterName
, conflict
) {
134 if ( !conflict
.item
.isSelected() ) {
138 if ( !conflictMessage
) {
139 conflictMessage
= conflict
[ key
];
140 group
= conflict
.group
;
143 if ( group
=== conflict
.group
) {
144 itemLabels
.push( mw
.msg( 'quotation-marks', conflict
.item
.getLabel() ) );
149 message
: conflictMessage
,
156 * Get the message representing the state of this model.
158 * @return {string} State message
160 mw
.rcfilters
.dm
.FilterItem
.prototype.getStateMessage = function () {
161 var messageKey
, details
, superset
,
164 if ( this.isSelected() ) {
165 if ( this.isConflicted() ) {
166 // First look in filter's own conflicts
167 details
= this.getConflictDetails( this.getOwnConflicts() );
168 if ( !details
.message
) {
169 // Fall back onto conflicts in the group
170 details
= this.getConflictDetails( this.getGroupModel().getConflicts() );
173 messageKey
= details
.message
;
174 affectingItems
= details
.names
;
175 } else if ( this.isIncluded() && !this.isHighlighted() ) {
176 // We only show the 'no effect' full-coverage message
177 // if the item is also not highlighted. See T161273
178 superset
= this.getSuperset();
179 // For this message we need to collect the affecting superset
180 affectingItems
= this.getGroupModel().getSelectedItems( this )
181 .filter( function ( item
) {
182 return superset
.indexOf( item
.getName() ) !== -1;
184 .map( function ( item
) {
185 return mw
.msg( 'quotation-marks', item
.getLabel() );
188 messageKey
= 'rcfilters-state-message-subset';
189 } else if ( this.isFullyCovered() && !this.isHighlighted() ) {
190 affectingItems
= this.getGroupModel().getSelectedItems( this )
191 .map( function ( item
) {
192 return mw
.msg( 'quotation-marks', item
.getLabel() );
195 messageKey
= 'rcfilters-state-message-fullcoverage';
203 mw
.language
.listToText( affectingItems
),
204 affectingItems
.length
208 // Display description
209 return this.getDescription();
213 * Get the model of the group this filter belongs to
215 * @return {mw.rcfilters.dm.FilterGroup} Filter group model
217 mw
.rcfilters
.dm
.FilterItem
.prototype.getGroupModel = function () {
218 return this.groupModel
;
222 * Get the group name this filter belongs to
224 * @return {string} Filter group name
226 mw
.rcfilters
.dm
.FilterItem
.prototype.getGroupName = function () {
227 return this.groupModel
.getName();
231 * Get the label of this filter
233 * @return {string} Filter label
235 mw
.rcfilters
.dm
.FilterItem
.prototype.getLabel = function () {
240 * Get the description of this filter
242 * @return {string} Filter description
244 mw
.rcfilters
.dm
.FilterItem
.prototype.getDescription = function () {
245 return this.description
;
249 * Get the default value of this filter
251 * @return {boolean} Filter default
253 mw
.rcfilters
.dm
.FilterItem
.prototype.getDefault = function () {
259 * This is a list of filter names that are defined to be included
260 * when this filter is selected.
262 * @return {string[]} Filter subset
264 mw
.rcfilters
.dm
.FilterItem
.prototype.getSubset = function () {
269 * Get filter superset
270 * This is a generated list of filters that define this filter
271 * to be included when either of them is selected.
273 * @return {string[]} Filter superset
275 mw
.rcfilters
.dm
.FilterItem
.prototype.getSuperset = function () {
276 return this.superset
;
280 * Get the selected state of this filter
282 * @return {boolean} Filter is selected
284 mw
.rcfilters
.dm
.FilterItem
.prototype.isSelected = function () {
285 return this.selected
;
289 * Check whether the filter is currently in a conflict state
291 * @return {boolean} Filter is in conflict state
293 mw
.rcfilters
.dm
.FilterItem
.prototype.isConflicted = function () {
294 return this.conflicted
;
298 * Check whether the filter is currently in an already included subset
300 * @return {boolean} Filter is in an already-included subset
302 mw
.rcfilters
.dm
.FilterItem
.prototype.isIncluded = function () {
303 return this.included
;
307 * Check whether the filter is currently fully covered
309 * @return {boolean} Filter is in fully-covered state
311 mw
.rcfilters
.dm
.FilterItem
.prototype.isFullyCovered = function () {
312 return this.fullyCovered
;
316 * Get all conflicts associated with this filter or its group
318 * Conflict object is set up by filter name keys and conflict
319 * definition. For example:
322 * filter: filterName,
328 * filter: filterName2,
335 * @return {Object} Filter conflicts
337 mw
.rcfilters
.dm
.FilterItem
.prototype.getConflicts = function () {
338 return $.extend( {}, this.conflicts
, this.getGroupModel().getConflicts() );
342 * Get the conflicts associated with this filter
344 * @return {Object} Filter conflicts
346 mw
.rcfilters
.dm
.FilterItem
.prototype.getOwnConflicts = function () {
347 return this.conflicts
;
351 * Set conflicts for this filter. See #getConflicts for the expected
352 * structure of the definition.
354 * @param {Object} conflicts Conflicts for this filter
356 mw
.rcfilters
.dm
.FilterItem
.prototype.setConflicts = function ( conflicts
) {
357 this.conflicts
= conflicts
|| {};
361 * Set filter superset
363 * @param {string[]} superset Filter superset
365 mw
.rcfilters
.dm
.FilterItem
.prototype.setSuperset = function ( superset
) {
366 this.superset
= superset
|| [];
372 * @param {string[]} subset Filter subset
374 mw
.rcfilters
.dm
.FilterItem
.prototype.setSubset = function ( subset
) {
375 this.subset
= subset
|| [];
379 * Check whether a filter exists in the subset list for this filter
381 * @param {string} filterName Filter name
382 * @return {boolean} Filter name is in the subset list
384 mw
.rcfilters
.dm
.FilterItem
.prototype.existsInSubset = function ( filterName
) {
385 return this.subset
.indexOf( filterName
) > -1;
389 * Check whether this item has a potential conflict with the given item
391 * This checks whether the given item is in the list of conflicts of
392 * the current item, but makes no judgment about whether the conflict
393 * is currently at play (either one of the items may not be selected)
395 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item
396 * @return {boolean} This item has a conflict with the given item
398 mw
.rcfilters
.dm
.FilterItem
.prototype.existsInConflicts = function ( filterItem
) {
399 return Object
.prototype.hasOwnProperty
.call( this.getConflicts(), filterItem
.getName() );
403 * Set the state of this filter as being conflicted
404 * (This means any filters in its conflicts are selected)
406 * @param {boolean} [conflicted] Filter is in conflict state
409 mw
.rcfilters
.dm
.FilterItem
.prototype.toggleConflicted = function ( conflicted
) {
410 conflicted
= conflicted
=== undefined ? !this.conflicted
: conflicted
;
412 if ( this.conflicted
!== conflicted
) {
413 this.conflicted
= conflicted
;
414 this.emit( 'update' );
419 * Set the state of this filter as being already included
420 * (This means any filters in its superset are selected)
422 * @param {boolean} [included] Filter is included as part of a subset
425 mw
.rcfilters
.dm
.FilterItem
.prototype.toggleIncluded = function ( included
) {
426 included
= included
=== undefined ? !this.included
: included
;
428 if ( this.included
!== included
) {
429 this.included
= included
;
430 this.emit( 'update' );
435 * Toggle the selected state of the item
437 * @param {boolean} [isSelected] Filter is selected
440 mw
.rcfilters
.dm
.FilterItem
.prototype.toggleSelected = function ( isSelected
) {
441 isSelected
= isSelected
=== undefined ? !this.selected
: isSelected
;
443 if ( this.selected
!== isSelected
) {
444 this.selected
= isSelected
;
445 this.emit( 'update' );
450 * Toggle the fully covered state of the item
452 * @param {boolean} [isFullyCovered] Filter is fully covered
455 mw
.rcfilters
.dm
.FilterItem
.prototype.toggleFullyCovered = function ( isFullyCovered
) {
456 isFullyCovered
= isFullyCovered
=== undefined ? !this.fullycovered
: isFullyCovered
;
458 if ( this.fullyCovered
!== isFullyCovered
) {
459 this.fullyCovered
= isFullyCovered
;
460 this.emit( 'update' );
465 * Set the highlight color
467 * @param {string|null} highlightColor
469 mw
.rcfilters
.dm
.FilterItem
.prototype.setHighlightColor = function ( highlightColor
) {
470 if ( this.highlightColor
!== highlightColor
) {
471 this.highlightColor
= highlightColor
;
472 this.emit( 'update' );
477 * Clear the highlight color
479 mw
.rcfilters
.dm
.FilterItem
.prototype.clearHighlightColor = function () {
480 this.setHighlightColor( null );
484 * Get the highlight color, or null if none is configured
486 * @return {string|null}
488 mw
.rcfilters
.dm
.FilterItem
.prototype.getHighlightColor = function () {
489 return this.highlightColor
;
493 * Get the CSS class that matches changes that fit this filter
494 * or null if none is configured
496 * @return {string|null}
498 mw
.rcfilters
.dm
.FilterItem
.prototype.getCssClass = function () {
499 return this.cssClass
;
503 * Toggle the highlight feature on and off for this filter.
504 * It only works if highlight is supported for this filter.
506 * @param {boolean} enable Highlight should be enabled
508 mw
.rcfilters
.dm
.FilterItem
.prototype.toggleHighlight = function ( enable
) {
509 enable
= enable
=== undefined ? !this.highlightEnabled
: enable
;
511 if ( !this.isHighlightSupported() ) {
515 if ( enable
=== this.highlightEnabled
) {
519 this.highlightEnabled
= enable
;
520 this.emit( 'update' );
524 * Check if the highlight feature is currently enabled for this filter
528 mw
.rcfilters
.dm
.FilterItem
.prototype.isHighlightEnabled = function () {
529 return !!this.highlightEnabled
;
533 * Check if the highlight feature is supported for this filter
537 mw
.rcfilters
.dm
.FilterItem
.prototype.isHighlightSupported = function () {
538 return !!this.getCssClass();
542 * Check if the filter is currently highlighted
546 mw
.rcfilters
.dm
.FilterItem
.prototype.isHighlighted = function () {
547 return this.isHighlightEnabled() && !!this.getHighlightColor();