Merge "mw.rcfilters.ui.SaveFiltersPopupButtonWidget: Remove pointless option"
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / dm / mw.rcfilters.dm.FilterItem.js
1 ( function ( mw ) {
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 $.each( conflicts, function ( filterName, conflict ) {
98 if ( !conflict.item.isSelected() ) {
99 return;
100 }
101
102 if ( !conflictMessage ) {
103 conflictMessage = conflict[ key ];
104 group = conflict.group;
105 }
106
107 if ( group === conflict.group ) {
108 itemLabels.push( mw.msg( 'quotation-marks', conflict.item.getLabel() ) );
109 }
110 } );
111
112 return {
113 message: conflictMessage,
114 names: itemLabels
115 };
116
117 };
118
119 /**
120 * @inheritdoc
121 */
122 mw.rcfilters.dm.FilterItem.prototype.getStateMessage = function () {
123 var messageKey, details, superset,
124 affectingItems = [];
125
126 if ( this.isSelected() ) {
127 if ( this.isConflicted() ) {
128 // First look in filter's own conflicts
129 details = this.getConflictDetails( this.getOwnConflicts() );
130 if ( !details.message ) {
131 // Fall back onto conflicts in the group
132 details = this.getConflictDetails( this.getGroupModel().getConflicts() );
133 }
134
135 messageKey = details.message;
136 affectingItems = details.names;
137 } else if ( this.isIncluded() && !this.isHighlighted() ) {
138 // We only show the 'no effect' full-coverage message
139 // if the item is also not highlighted. See T161273
140 superset = this.getSuperset();
141 // For this message we need to collect the affecting superset
142 affectingItems = this.getGroupModel().getSelectedItems( this )
143 .filter( function ( item ) {
144 return superset.indexOf( item.getName() ) !== -1;
145 } )
146 .map( function ( item ) {
147 return mw.msg( 'quotation-marks', item.getLabel() );
148 } );
149
150 messageKey = 'rcfilters-state-message-subset';
151 } else if ( this.isFullyCovered() && !this.isHighlighted() ) {
152 affectingItems = this.getGroupModel().getSelectedItems( this )
153 .map( function ( item ) {
154 return mw.msg( 'quotation-marks', item.getLabel() );
155 } );
156
157 messageKey = 'rcfilters-state-message-fullcoverage';
158 }
159 }
160
161 if ( messageKey ) {
162 // Build message
163 return mw.msg(
164 messageKey,
165 mw.language.listToText( affectingItems ),
166 affectingItems.length
167 );
168 }
169
170 // Display description
171 return this.getDescription();
172 };
173
174 /**
175 * Get the model of the group this filter belongs to
176 *
177 * @return {mw.rcfilters.dm.FilterGroup} Filter group model
178 */
179 mw.rcfilters.dm.FilterItem.prototype.getGroupModel = function () {
180 return this.groupModel;
181 };
182
183 /**
184 * Get the group name this filter belongs to
185 *
186 * @return {string} Filter group name
187 */
188 mw.rcfilters.dm.FilterItem.prototype.getGroupName = function () {
189 return this.groupModel.getName();
190 };
191
192 /**
193 * Get filter subset
194 * This is a list of filter names that are defined to be included
195 * when this filter is selected.
196 *
197 * @return {string[]} Filter subset
198 */
199 mw.rcfilters.dm.FilterItem.prototype.getSubset = function () {
200 return this.subset;
201 };
202
203 /**
204 * Get filter superset
205 * This is a generated list of filters that define this filter
206 * to be included when either of them is selected.
207 *
208 * @return {string[]} Filter superset
209 */
210 mw.rcfilters.dm.FilterItem.prototype.getSuperset = function () {
211 return this.superset;
212 };
213
214 /**
215 * Check whether the filter is currently in a conflict state
216 *
217 * @return {boolean} Filter is in conflict state
218 */
219 mw.rcfilters.dm.FilterItem.prototype.isConflicted = function () {
220 return this.conflicted;
221 };
222
223 /**
224 * Check whether the filter is currently in an already included subset
225 *
226 * @return {boolean} Filter is in an already-included subset
227 */
228 mw.rcfilters.dm.FilterItem.prototype.isIncluded = function () {
229 return this.included;
230 };
231
232 /**
233 * Check whether the filter is currently fully covered
234 *
235 * @return {boolean} Filter is in fully-covered state
236 */
237 mw.rcfilters.dm.FilterItem.prototype.isFullyCovered = function () {
238 return this.fullyCovered;
239 };
240
241 /**
242 * Get all conflicts associated with this filter or its group
243 *
244 * Conflict object is set up by filter name keys and conflict
245 * definition. For example:
246 * {
247 * filterName: {
248 * filter: filterName,
249 * group: group1,
250 * label: itemLabel,
251 * item: itemModel
252 * }
253 * filterName2: {
254 * filter: filterName2,
255 * group: group2
256 * label: itemLabel2,
257 * item: itemModel2
258 * }
259 * }
260 *
261 * @return {Object} Filter conflicts
262 */
263 mw.rcfilters.dm.FilterItem.prototype.getConflicts = function () {
264 return $.extend( {}, this.conflicts, this.getGroupModel().getConflicts() );
265 };
266
267 /**
268 * Get the conflicts associated with this filter
269 *
270 * @return {Object} Filter conflicts
271 */
272 mw.rcfilters.dm.FilterItem.prototype.getOwnConflicts = function () {
273 return this.conflicts;
274 };
275
276 /**
277 * Set conflicts for this filter. See #getConflicts for the expected
278 * structure of the definition.
279 *
280 * @param {Object} conflicts Conflicts for this filter
281 */
282 mw.rcfilters.dm.FilterItem.prototype.setConflicts = function ( conflicts ) {
283 this.conflicts = conflicts || {};
284 };
285
286 /**
287 * Set filter superset
288 *
289 * @param {string[]} superset Filter superset
290 */
291 mw.rcfilters.dm.FilterItem.prototype.setSuperset = function ( superset ) {
292 this.superset = superset || [];
293 };
294
295 /**
296 * Set filter subset
297 *
298 * @param {string[]} subset Filter subset
299 */
300 mw.rcfilters.dm.FilterItem.prototype.setSubset = function ( subset ) {
301 this.subset = subset || [];
302 };
303
304 /**
305 * Check whether a filter exists in the subset list for this filter
306 *
307 * @param {string} filterName Filter name
308 * @return {boolean} Filter name is in the subset list
309 */
310 mw.rcfilters.dm.FilterItem.prototype.existsInSubset = function ( filterName ) {
311 return this.subset.indexOf( filterName ) > -1;
312 };
313
314 /**
315 * Check whether this item has a potential conflict with the given item
316 *
317 * This checks whether the given item is in the list of conflicts of
318 * the current item, but makes no judgment about whether the conflict
319 * is currently at play (either one of the items may not be selected)
320 *
321 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item
322 * @return {boolean} This item has a conflict with the given item
323 */
324 mw.rcfilters.dm.FilterItem.prototype.existsInConflicts = function ( filterItem ) {
325 return Object.prototype.hasOwnProperty.call( this.getConflicts(), filterItem.getName() );
326 };
327
328 /**
329 * Set the state of this filter as being conflicted
330 * (This means any filters in its conflicts are selected)
331 *
332 * @param {boolean} [conflicted] Filter is in conflict state
333 * @fires update
334 */
335 mw.rcfilters.dm.FilterItem.prototype.toggleConflicted = function ( conflicted ) {
336 conflicted = conflicted === undefined ? !this.conflicted : conflicted;
337
338 if ( this.conflicted !== conflicted ) {
339 this.conflicted = conflicted;
340 this.emit( 'update' );
341 }
342 };
343
344 /**
345 * Set the state of this filter as being already included
346 * (This means any filters in its superset are selected)
347 *
348 * @param {boolean} [included] Filter is included as part of a subset
349 * @fires update
350 */
351 mw.rcfilters.dm.FilterItem.prototype.toggleIncluded = function ( included ) {
352 included = included === undefined ? !this.included : included;
353
354 if ( this.included !== included ) {
355 this.included = included;
356 this.emit( 'update' );
357 }
358 };
359
360 /**
361 * Toggle the fully covered state of the item
362 *
363 * @param {boolean} [isFullyCovered] Filter is fully covered
364 * @fires update
365 */
366 mw.rcfilters.dm.FilterItem.prototype.toggleFullyCovered = function ( isFullyCovered ) {
367 isFullyCovered = isFullyCovered === undefined ? !this.fullycovered : isFullyCovered;
368
369 if ( this.fullyCovered !== isFullyCovered ) {
370 this.fullyCovered = isFullyCovered;
371 this.emit( 'update' );
372 }
373 };
374
375 /**
376 * Toggle the visibility of this item
377 *
378 * @param {boolean} [isVisible] Item is visible
379 */
380 mw.rcfilters.dm.FilterItem.prototype.toggleVisible = function ( isVisible ) {
381 isVisible = isVisible === undefined ? !this.visible : !!isVisible;
382
383 if ( this.visible !== isVisible ) {
384 this.visible = isVisible;
385 this.emit( 'update' );
386 }
387 };
388
389 /**
390 * Check whether the item is visible
391 *
392 * @return {boolean} Item is visible
393 */
394 mw.rcfilters.dm.FilterItem.prototype.isVisible = function () {
395 return this.visible;
396 };
397
398 }( mediaWiki ) );