build: Use eslint-config-wikimedia v0.9.0 and make pass
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / dm / mw.rcfilters.dm.FilterItem.js
1 ( function () {
2 /**
3 * Filter item model
4 *
5 * @extends mw.rcfilters.dm.ItemModel
6 *
7 * @constructor
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[]} [excludes=[]] A list of filter names this filter, if
12 * selected, makes inactive.
13 * @cfg {string[]} [subset] Defining the names of filters that are a subset of this filter
14 * @cfg {Object} [conflicts] Defines the conflicts for this filter
15 * @cfg {boolean} [visible=true] The visibility of the group
16 */
17 mw.rcfilters.dm.FilterItem = function MwRcfiltersDmFilterItem( param, groupModel, config ) {
18 config = config || {};
19
20 this.groupModel = groupModel;
21
22 // Parent
23 mw.rcfilters.dm.FilterItem.parent.call( this, param, $.extend( {
24 namePrefix: this.groupModel.getNamePrefix()
25 }, config ) );
26 // Mixin constructor
27 OO.EventEmitter.call( this );
28
29 // Interaction definitions
30 this.subset = config.subset || [];
31 this.conflicts = config.conflicts || {};
32 this.superset = [];
33 this.visible = config.visible === undefined ? true : !!config.visible;
34
35 // Interaction states
36 this.included = false;
37 this.conflicted = false;
38 this.fullyCovered = false;
39 };
40
41 /* Initialization */
42
43 OO.inheritClass( mw.rcfilters.dm.FilterItem, mw.rcfilters.dm.ItemModel );
44
45 /* Methods */
46
47 /**
48 * Return the representation of the state of this item.
49 *
50 * @return {Object} State of the object
51 */
52 mw.rcfilters.dm.FilterItem.prototype.getState = function () {
53 return {
54 selected: this.isSelected(),
55 included: this.isIncluded(),
56 conflicted: this.isConflicted(),
57 fullyCovered: this.isFullyCovered()
58 };
59 };
60
61 /**
62 * Get the message for the display area for the currently active conflict
63 *
64 * @private
65 * @return {string} Conflict result message key
66 */
67 mw.rcfilters.dm.FilterItem.prototype.getCurrentConflictResultMessage = function () {
68 var details = {};
69
70 // First look in filter's own conflicts
71 details = this.getConflictDetails( this.getOwnConflicts(), 'globalDescription' );
72 if ( !details.message ) {
73 // Fall back onto conflicts in the group
74 details = this.getConflictDetails( this.getGroupModel().getConflicts(), 'globalDescription' );
75 }
76
77 return details.message;
78 };
79
80 /**
81 * Get the details of the active conflict on this filter
82 *
83 * @private
84 * @param {Object} conflicts Conflicts to examine
85 * @param {string} [key='contextDescription'] Message key
86 * @return {Object} Object with conflict message and conflict items
87 * @return {string} return.message Conflict message
88 * @return {string[]} return.names Conflicting item labels
89 */
90 mw.rcfilters.dm.FilterItem.prototype.getConflictDetails = function ( conflicts, key ) {
91 var group,
92 conflictMessage = '',
93 itemLabels = [];
94
95 key = key || 'contextDescription';
96
97 // eslint-disable-next-line jquery/no-each-util
98 $.each( conflicts, function ( filterName, conflict ) {
99 if ( !conflict.item.isSelected() ) {
100 return;
101 }
102
103 if ( !conflictMessage ) {
104 conflictMessage = conflict[ key ];
105 group = conflict.group;
106 }
107
108 if ( group === conflict.group ) {
109 itemLabels.push( mw.msg( 'quotation-marks', conflict.item.getLabel() ) );
110 }
111 } );
112
113 return {
114 message: conflictMessage,
115 names: itemLabels
116 };
117
118 };
119
120 /**
121 * @inheritdoc
122 */
123 mw.rcfilters.dm.FilterItem.prototype.getStateMessage = function () {
124 var messageKey, details, superset,
125 affectingItems = [];
126
127 if ( this.isSelected() ) {
128 if ( this.isConflicted() ) {
129 // First look in filter's own conflicts
130 details = this.getConflictDetails( this.getOwnConflicts() );
131 if ( !details.message ) {
132 // Fall back onto conflicts in the group
133 details = this.getConflictDetails( this.getGroupModel().getConflicts() );
134 }
135
136 messageKey = details.message;
137 affectingItems = details.names;
138 } else if ( this.isIncluded() && !this.isHighlighted() ) {
139 // We only show the 'no effect' full-coverage message
140 // if the item is also not highlighted. See T161273
141 superset = this.getSuperset();
142 // For this message we need to collect the affecting superset
143 affectingItems = this.getGroupModel().findSelectedItems( this )
144 .filter( function ( item ) {
145 return superset.indexOf( item.getName() ) !== -1;
146 } )
147 .map( function ( item ) {
148 return mw.msg( 'quotation-marks', item.getLabel() );
149 } );
150
151 messageKey = 'rcfilters-state-message-subset';
152 } else if ( this.isFullyCovered() && !this.isHighlighted() ) {
153 affectingItems = this.getGroupModel().findSelectedItems( this )
154 .map( function ( item ) {
155 return mw.msg( 'quotation-marks', item.getLabel() );
156 } );
157
158 messageKey = 'rcfilters-state-message-fullcoverage';
159 }
160 }
161
162 if ( messageKey ) {
163 // Build message
164 return mw.msg(
165 messageKey,
166 mw.language.listToText( affectingItems ),
167 affectingItems.length
168 );
169 }
170
171 // Display description
172 return this.getDescription();
173 };
174
175 /**
176 * Get the model of the group this filter belongs to
177 *
178 * @return {mw.rcfilters.dm.FilterGroup} Filter group model
179 */
180 mw.rcfilters.dm.FilterItem.prototype.getGroupModel = function () {
181 return this.groupModel;
182 };
183
184 /**
185 * Get the group name this filter belongs to
186 *
187 * @return {string} Filter group name
188 */
189 mw.rcfilters.dm.FilterItem.prototype.getGroupName = function () {
190 return this.groupModel.getName();
191 };
192
193 /**
194 * Get filter subset
195 * This is a list of filter names that are defined to be included
196 * when this filter is selected.
197 *
198 * @return {string[]} Filter subset
199 */
200 mw.rcfilters.dm.FilterItem.prototype.getSubset = function () {
201 return this.subset;
202 };
203
204 /**
205 * Get filter superset
206 * This is a generated list of filters that define this filter
207 * to be included when either of them is selected.
208 *
209 * @return {string[]} Filter superset
210 */
211 mw.rcfilters.dm.FilterItem.prototype.getSuperset = function () {
212 return this.superset;
213 };
214
215 /**
216 * Check whether the filter is currently in a conflict state
217 *
218 * @return {boolean} Filter is in conflict state
219 */
220 mw.rcfilters.dm.FilterItem.prototype.isConflicted = function () {
221 return this.conflicted;
222 };
223
224 /**
225 * Check whether the filter is currently in an already included subset
226 *
227 * @return {boolean} Filter is in an already-included subset
228 */
229 mw.rcfilters.dm.FilterItem.prototype.isIncluded = function () {
230 return this.included;
231 };
232
233 /**
234 * Check whether the filter is currently fully covered
235 *
236 * @return {boolean} Filter is in fully-covered state
237 */
238 mw.rcfilters.dm.FilterItem.prototype.isFullyCovered = function () {
239 return this.fullyCovered;
240 };
241
242 /**
243 * Get all conflicts associated with this filter or its group
244 *
245 * Conflict object is set up by filter name keys and conflict
246 * definition. For example:
247 *
248 * {
249 * filterName: {
250 * filter: filterName,
251 * group: group1,
252 * label: itemLabel,
253 * item: itemModel
254 * }
255 * filterName2: {
256 * filter: filterName2,
257 * group: group2
258 * label: itemLabel2,
259 * item: itemModel2
260 * }
261 * }
262 *
263 * @return {Object} Filter conflicts
264 */
265 mw.rcfilters.dm.FilterItem.prototype.getConflicts = function () {
266 return $.extend( {}, this.conflicts, this.getGroupModel().getConflicts() );
267 };
268
269 /**
270 * Get the conflicts associated with this filter
271 *
272 * @return {Object} Filter conflicts
273 */
274 mw.rcfilters.dm.FilterItem.prototype.getOwnConflicts = function () {
275 return this.conflicts;
276 };
277
278 /**
279 * Set conflicts for this filter. See #getConflicts for the expected
280 * structure of the definition.
281 *
282 * @param {Object} conflicts Conflicts for this filter
283 */
284 mw.rcfilters.dm.FilterItem.prototype.setConflicts = function ( conflicts ) {
285 this.conflicts = conflicts || {};
286 };
287
288 /**
289 * Set filter superset
290 *
291 * @param {string[]} superset Filter superset
292 */
293 mw.rcfilters.dm.FilterItem.prototype.setSuperset = function ( superset ) {
294 this.superset = superset || [];
295 };
296
297 /**
298 * Set filter subset
299 *
300 * @param {string[]} subset Filter subset
301 */
302 mw.rcfilters.dm.FilterItem.prototype.setSubset = function ( subset ) {
303 this.subset = subset || [];
304 };
305
306 /**
307 * Check whether a filter exists in the subset list for this filter
308 *
309 * @param {string} filterName Filter name
310 * @return {boolean} Filter name is in the subset list
311 */
312 mw.rcfilters.dm.FilterItem.prototype.existsInSubset = function ( filterName ) {
313 return this.subset.indexOf( filterName ) > -1;
314 };
315
316 /**
317 * Check whether this item has a potential conflict with the given item
318 *
319 * This checks whether the given item is in the list of conflicts of
320 * the current item, but makes no judgment about whether the conflict
321 * is currently at play (either one of the items may not be selected)
322 *
323 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item
324 * @return {boolean} This item has a conflict with the given item
325 */
326 mw.rcfilters.dm.FilterItem.prototype.existsInConflicts = function ( filterItem ) {
327 return Object.prototype.hasOwnProperty.call( this.getConflicts(), filterItem.getName() );
328 };
329
330 /**
331 * Set the state of this filter as being conflicted
332 * (This means any filters in its conflicts are selected)
333 *
334 * @param {boolean} [conflicted] Filter is in conflict state
335 * @fires update
336 */
337 mw.rcfilters.dm.FilterItem.prototype.toggleConflicted = function ( conflicted ) {
338 conflicted = conflicted === undefined ? !this.conflicted : conflicted;
339
340 if ( this.conflicted !== conflicted ) {
341 this.conflicted = conflicted;
342 this.emit( 'update' );
343 }
344 };
345
346 /**
347 * Set the state of this filter as being already included
348 * (This means any filters in its superset are selected)
349 *
350 * @param {boolean} [included] Filter is included as part of a subset
351 * @fires update
352 */
353 mw.rcfilters.dm.FilterItem.prototype.toggleIncluded = function ( included ) {
354 included = included === undefined ? !this.included : included;
355
356 if ( this.included !== included ) {
357 this.included = included;
358 this.emit( 'update' );
359 }
360 };
361
362 /**
363 * Toggle the fully covered state of the item
364 *
365 * @param {boolean} [isFullyCovered] Filter is fully covered
366 * @fires update
367 */
368 mw.rcfilters.dm.FilterItem.prototype.toggleFullyCovered = function ( isFullyCovered ) {
369 isFullyCovered = isFullyCovered === undefined ? !this.fullycovered : isFullyCovered;
370
371 if ( this.fullyCovered !== isFullyCovered ) {
372 this.fullyCovered = isFullyCovered;
373 this.emit( 'update' );
374 }
375 };
376
377 /**
378 * Toggle the visibility of this item
379 *
380 * @param {boolean} [isVisible] Item is visible
381 */
382 mw.rcfilters.dm.FilterItem.prototype.toggleVisible = function ( isVisible ) {
383 isVisible = isVisible === undefined ? !this.visible : !!isVisible;
384
385 if ( this.visible !== isVisible ) {
386 this.visible = isVisible;
387 this.emit( 'update' );
388 }
389 };
390
391 /**
392 * Check whether the item is visible
393 *
394 * @return {boolean} Item is visible
395 */
396 mw.rcfilters.dm.FilterItem.prototype.isVisible = function () {
397 return this.visible;
398 };
399
400 }() );