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