RCFilters UI: Rework conflicts to be objects in filter or group context
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / dm / mw.rcfilters.dm.FilterItem.js
1 ( function ( mw ) {
2 /**
3 * Filter item model
4 *
5 * @mixins OO.EventEmitter
6 *
7 * @constructor
8 * @param {string} name Filter 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
21 */
22 mw.rcfilters.dm.FilterItem = function MwRcfiltersDmFilterItem( name, groupModel, config ) {
23 config = config || {};
24
25 // Mixin constructor
26 OO.EventEmitter.call( this );
27
28 this.name = name;
29 this.groupModel = groupModel;
30
31 this.label = config.label || this.name;
32 this.description = config.description;
33 this.selected = !!config.selected;
34
35 // Interaction definitions
36 this.subset = config.subset || [];
37 this.conflicts = config.conflicts || {};
38 this.superset = [];
39
40 // Interaction states
41 this.included = false;
42 this.conflicted = false;
43 this.fullyCovered = false;
44
45 // Highlight
46 this.cssClass = config.cssClass;
47 this.highlightColor = null;
48 this.highlightEnabled = false;
49 };
50
51 /* Initialization */
52
53 OO.initClass( mw.rcfilters.dm.FilterItem );
54 OO.mixinClass( mw.rcfilters.dm.FilterItem, OO.EventEmitter );
55
56 /* Events */
57
58 /**
59 * @event update
60 *
61 * The state of this filter has changed
62 */
63
64 /* Methods */
65
66 /**
67 * Return the representation of the state of this item.
68 *
69 * @return {Object} State of the object
70 */
71 mw.rcfilters.dm.FilterItem.prototype.getState = function () {
72 return {
73 selected: this.isSelected(),
74 included: this.isIncluded(),
75 conflicted: this.isConflicted(),
76 fullyCovered: this.isFullyCovered()
77 };
78 };
79
80 /**
81 * Get the name of this filter
82 *
83 * @return {string} Filter name
84 */
85 mw.rcfilters.dm.FilterItem.prototype.getName = function () {
86 return this.name;
87 };
88
89 /**
90 * Get the model of the group this filter belongs to
91 *
92 * @return {mw.rcfilters.dm.FilterGroup} Filter group model
93 */
94 mw.rcfilters.dm.FilterItem.prototype.getGroupModel = function () {
95 return this.groupModel;
96 };
97
98 /**
99 * Get the group name this filter belongs to
100 *
101 * @return {string} Filter group name
102 */
103 mw.rcfilters.dm.FilterItem.prototype.getGroupName = function () {
104 return this.groupModel.getName();
105 };
106
107 /**
108 * Get the label of this filter
109 *
110 * @return {string} Filter label
111 */
112 mw.rcfilters.dm.FilterItem.prototype.getLabel = function () {
113 return this.label;
114 };
115
116 /**
117 * Get the description of this filter
118 *
119 * @return {string} Filter description
120 */
121 mw.rcfilters.dm.FilterItem.prototype.getDescription = function () {
122 return this.description;
123 };
124
125 /**
126 * Get the default value of this filter
127 *
128 * @return {boolean} Filter default
129 */
130 mw.rcfilters.dm.FilterItem.prototype.getDefault = function () {
131 return this.default;
132 };
133
134 /**
135 * Get filter subset
136 * This is a list of filter names that are defined to be included
137 * when this filter is selected.
138 *
139 * @return {string[]} Filter subset
140 */
141 mw.rcfilters.dm.FilterItem.prototype.getSubset = function () {
142 return this.subset;
143 };
144
145 /**
146 * Get filter superset
147 * This is a generated list of filters that define this filter
148 * to be included when either of them is selected.
149 *
150 * @return {string[]} Filter superset
151 */
152 mw.rcfilters.dm.FilterItem.prototype.getSuperset = function () {
153 return this.superset;
154 };
155
156 /**
157 * Get the selected state of this filter
158 *
159 * @return {boolean} Filter is selected
160 */
161 mw.rcfilters.dm.FilterItem.prototype.isSelected = function () {
162 return this.selected;
163 };
164
165 /**
166 * Check whether the filter is currently in a conflict state
167 *
168 * @return {boolean} Filter is in conflict state
169 */
170 mw.rcfilters.dm.FilterItem.prototype.isConflicted = function () {
171 return this.conflicted;
172 };
173
174 /**
175 * Check whether the filter is currently in an already included subset
176 *
177 * @return {boolean} Filter is in an already-included subset
178 */
179 mw.rcfilters.dm.FilterItem.prototype.isIncluded = function () {
180 return this.included;
181 };
182
183 /**
184 * Check whether the filter is currently fully covered
185 *
186 * @return {boolean} Filter is in fully-covered state
187 */
188 mw.rcfilters.dm.FilterItem.prototype.isFullyCovered = function () {
189 return this.fullyCovered;
190 };
191
192 /**
193 * Get filter conflicts
194 *
195 * Conflict object is set up by filter name keys and conflict
196 * definition. For example:
197 * {
198 * filterName: {
199 * filter: filterName,
200 * group: group1
201 * }
202 * filterName2: {
203 * filter: filterName2,
204 * group: group2
205 * }
206 * }
207 *
208 * @return {Object} Filter conflicts
209 */
210 mw.rcfilters.dm.FilterItem.prototype.getConflicts = function () {
211 return $.extend( {}, this.conflicts, this.getGroupModel().getConflicts() );
212 };
213
214 /**
215 * Set conflicts for this filter. See #getConflicts for the expected
216 * structure of the definition.
217 *
218 * @param {Object} conflicts Conflicts for this filter
219 */
220 mw.rcfilters.dm.FilterItem.prototype.setConflicts = function ( conflicts ) {
221 this.conflicts = conflicts || {};
222 };
223
224 /**
225 * Set filter superset
226 *
227 * @param {string[]} superset Filter superset
228 */
229 mw.rcfilters.dm.FilterItem.prototype.setSuperset = function ( superset ) {
230 this.superset = superset || [];
231 };
232
233 /**
234 * Check whether a filter exists in the subset list for this filter
235 *
236 * @param {string} filterName Filter name
237 * @return {boolean} Filter name is in the subset list
238 */
239 mw.rcfilters.dm.FilterItem.prototype.existsInSubset = function ( filterName ) {
240 return this.subset.indexOf( filterName ) > -1;
241 };
242
243 /**
244 * Check whether this item has a potential conflict with the given item
245 *
246 * This checks whether the given item is in the list of conflicts of
247 * the current item, but makes no judgment about whether the conflict
248 * is currently at play (either one of the items may not be selected)
249 *
250 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item
251 * @return {boolean} This item has a conflict with the given item
252 */
253 mw.rcfilters.dm.FilterItem.prototype.existsInConflicts = function ( filterItem ) {
254 return Object.prototype.hasOwnProperty.call( this.getConflicts(), filterItem.getName() );
255 };
256
257 /**
258 * Set the state of this filter as being conflicted
259 * (This means any filters in its conflicts are selected)
260 *
261 * @param {boolean} [conflicted] Filter is in conflict state
262 * @fires update
263 */
264 mw.rcfilters.dm.FilterItem.prototype.toggleConflicted = function ( conflicted ) {
265 conflicted = conflicted === undefined ? !this.conflicted : conflicted;
266
267 if ( this.conflicted !== conflicted ) {
268 this.conflicted = conflicted;
269 this.emit( 'update' );
270 }
271 };
272
273 /**
274 * Set the state of this filter as being already included
275 * (This means any filters in its superset are selected)
276 *
277 * @param {boolean} [included] Filter is included as part of a subset
278 * @fires update
279 */
280 mw.rcfilters.dm.FilterItem.prototype.toggleIncluded = function ( included ) {
281 included = included === undefined ? !this.included : included;
282
283 if ( this.included !== included ) {
284 this.included = included;
285 this.emit( 'update' );
286 }
287 };
288
289 /**
290 * Toggle the selected state of the item
291 *
292 * @param {boolean} [isSelected] Filter is selected
293 * @fires update
294 */
295 mw.rcfilters.dm.FilterItem.prototype.toggleSelected = function ( isSelected ) {
296 isSelected = isSelected === undefined ? !this.selected : isSelected;
297
298 if ( this.selected !== isSelected ) {
299 this.selected = isSelected;
300 this.emit( 'update' );
301 }
302 };
303
304 /**
305 * Toggle the fully covered state of the item
306 *
307 * @param {boolean} [isFullyCovered] Filter is fully covered
308 * @fires update
309 */
310 mw.rcfilters.dm.FilterItem.prototype.toggleFullyCovered = function ( isFullyCovered ) {
311 isFullyCovered = isFullyCovered === undefined ? !this.fullycovered : isFullyCovered;
312
313 if ( this.fullyCovered !== isFullyCovered ) {
314 this.fullyCovered = isFullyCovered;
315 this.emit( 'update' );
316 }
317 };
318
319 /**
320 * Set the highlight color
321 *
322 * @param {string|null} highlightColor
323 */
324 mw.rcfilters.dm.FilterItem.prototype.setHighlightColor = function ( highlightColor ) {
325 if ( this.highlightColor !== highlightColor ) {
326 this.highlightColor = highlightColor;
327 this.emit( 'update' );
328 }
329 };
330
331 /**
332 * Clear the highlight color
333 */
334 mw.rcfilters.dm.FilterItem.prototype.clearHighlightColor = function () {
335 this.setHighlightColor( null );
336 };
337
338 /**
339 * Get the highlight color, or null if none is configured
340 *
341 * @return {string|null}
342 */
343 mw.rcfilters.dm.FilterItem.prototype.getHighlightColor = function () {
344 return this.highlightColor;
345 };
346
347 /**
348 * Get the CSS class that matches changes that fit this filter
349 * or null if none is configured
350 *
351 * @return {string|null}
352 */
353 mw.rcfilters.dm.FilterItem.prototype.getCssClass = function () {
354 return this.cssClass;
355 };
356
357 /**
358 * Toggle the highlight feature on and off for this filter.
359 * It only works if highlight is supported for this filter.
360 *
361 * @param {boolean} enable Highlight should be enabled
362 */
363 mw.rcfilters.dm.FilterItem.prototype.toggleHighlight = function ( enable ) {
364 enable = enable === undefined ? !this.highlightEnabled : enable;
365
366 if ( !this.isHighlightSupported() ) {
367 return;
368 }
369
370 if ( enable === this.highlightEnabled ) {
371 return;
372 }
373
374 this.highlightEnabled = enable;
375 this.emit( 'update' );
376 };
377
378 /**
379 * Check if the highlight feature is currently enabled for this filter
380 *
381 * @return {boolean}
382 */
383 mw.rcfilters.dm.FilterItem.prototype.isHighlightEnabled = function () {
384 return !!this.highlightEnabled;
385 };
386
387 /**
388 * Check if the highlight feature is supported for this filter
389 *
390 * @return {boolean}
391 */
392 mw.rcfilters.dm.FilterItem.prototype.isHighlightSupported = function () {
393 return !!this.getCssClass();
394 };
395
396 /**
397 * Check if the filter is currently highlighted
398 *
399 * @return {boolean}
400 */
401 mw.rcfilters.dm.FilterItem.prototype.isHighlighted = function () {
402 return this.isHighlightEnabled() && !!this.getHighlightColor();
403 };
404 }( mediaWiki ) );