RCFilters UI: Rework conflicts to be objects in filter or group context
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / dm / mw.rcfilters.dm.FilterGroup.js
1 ( function ( mw ) {
2 /**
3 * View model for a filter group
4 *
5 * @mixins OO.EventEmitter
6 * @mixins OO.EmitterList
7 *
8 * @constructor
9 * @param {string} name Group name
10 * @param {Object} [config] Configuration options
11 * @cfg {string} [type='send_unselected_if_any'] Group type
12 * @cfg {string} [title] Group title
13 * @cfg {string} [separator='|'] Value separator for 'string_options' groups
14 * @cfg {boolean} [active] Group is active
15 * @cfg {boolean} [fullCoverage] This filters in this group collectively cover all results
16 * @cfg {Object} [conflicts] Defines the conflicts for this filter group
17 */
18 mw.rcfilters.dm.FilterGroup = function MwRcfiltersDmFilterGroup( name, config ) {
19 config = config || {};
20
21 // Mixin constructor
22 OO.EventEmitter.call( this );
23 OO.EmitterList.call( this );
24
25 this.name = name;
26 this.type = config.type || 'send_unselected_if_any';
27 this.title = config.title;
28 this.separator = config.separator || '|';
29
30 this.active = !!config.active;
31 this.fullCoverage = !!config.fullCoverage;
32
33 this.conflicts = config.conflicts || {};
34
35 this.aggregate( { update: 'filterItemUpdate' } );
36 this.connect( this, { filterItemUpdate: 'onFilterItemUpdate' } );
37 };
38
39 /* Initialization */
40 OO.initClass( mw.rcfilters.dm.FilterGroup );
41 OO.mixinClass( mw.rcfilters.dm.FilterGroup, OO.EventEmitter );
42 OO.mixinClass( mw.rcfilters.dm.FilterGroup, OO.EmitterList );
43
44 /* Events */
45
46 /**
47 * @event update
48 *
49 * Group state has been updated
50 */
51
52 /* Methods */
53
54 /**
55 * Respond to filterItem update event
56 *
57 * @fires update
58 */
59 mw.rcfilters.dm.FilterGroup.prototype.onFilterItemUpdate = function () {
60 // Update state
61 var active = this.areAnySelected();
62
63 if ( this.active !== active ) {
64 this.active = active;
65 this.emit( 'update' );
66 }
67 };
68
69 /**
70 * Get group active state
71 *
72 * @return {boolean} Active state
73 */
74 mw.rcfilters.dm.FilterGroup.prototype.isActive = function () {
75 return this.active;
76 };
77
78 /**
79 * Get group name
80 *
81 * @return {string} Group name
82 */
83 mw.rcfilters.dm.FilterGroup.prototype.getName = function () {
84 return this.name;
85 };
86
87 /**
88 * Get the conflicts associated with the entire group.
89 * Conflict object is set up by filter name keys and conflict
90 * definition. For example:
91 * [
92 * {
93 * filterName: {
94 * filter: filterName,
95 * group: group1
96 * }
97 * },
98 * {
99 * filterName2: {
100 * filter: filterName2,
101 * group: group2
102 * }
103 * }
104 * ]
105 * @return {Object} Conflict definition
106 */
107 mw.rcfilters.dm.FilterGroup.prototype.getConflicts = function () {
108 return this.conflicts;
109 };
110
111 /**
112 * Set conflicts for this group. See #getConflicts for the expected
113 * structure of the definition.
114 *
115 * @param {Object} conflicts Conflicts for this group
116 */
117 mw.rcfilters.dm.FilterGroup.prototype.setConflicts = function ( conflicts ) {
118 this.conflicts = conflicts;
119 };
120
121 /**
122 * Check whether this item has a potential conflict with the given item
123 *
124 * This checks whether the given item is in the list of conflicts of
125 * the current item, but makes no judgment about whether the conflict
126 * is currently at play (either one of the items may not be selected)
127 *
128 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item
129 * @return {boolean} This item has a conflict with the given item
130 */
131 mw.rcfilters.dm.FilterGroup.prototype.existsInConflicts = function ( filterItem ) {
132 return Object.prototype.hasOwnProperty.call( this.getConflicts(), filterItem.getName() );
133 };
134
135 /**
136 * Check whether there are any items selected
137 *
138 * @return {boolean} Any items in the group are selected
139 */
140 mw.rcfilters.dm.FilterGroup.prototype.areAnySelected = function () {
141 return this.getItems().some( function ( filterItem ) {
142 return filterItem.isSelected();
143 } );
144 };
145
146 /**
147 * Check whether all items selected
148 *
149 * @return {boolean} All items are selected
150 */
151 mw.rcfilters.dm.FilterGroup.prototype.areAllSelected = function () {
152 return this.getItems().every( function ( filterItem ) {
153 return filterItem.isSelected();
154 } );
155 };
156
157 /**
158 * Get all selected items in this group
159 *
160 * @param {mw.rcfilters.dm.FilterItem} [excludeItem] Item to exclude from the list
161 * @return {mw.rcfilters.dm.FilterItem[]} Selected items
162 */
163 mw.rcfilters.dm.FilterGroup.prototype.getSelectedItems = function ( excludeItem ) {
164 var excludeName = ( excludeItem && excludeItem.getName() ) || '';
165
166 return this.getItems().filter( function ( item ) {
167 return item.getName() !== excludeName && item.isSelected();
168 } );
169 };
170
171 /**
172 * Check whether all selected items are in conflict with the given item
173 *
174 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item to test
175 * @return {boolean} All selected items are in conflict with this item
176 */
177 mw.rcfilters.dm.FilterGroup.prototype.areAllSelectedInConflictWith = function ( filterItem ) {
178 var selectedItems = this.getSelectedItems( filterItem );
179
180 return selectedItems.length > 0 &&
181 (
182 // The group as a whole is in conflict with this item
183 this.existsInConflicts( filterItem ) ||
184 // All selected items are in conflict individually
185 selectedItems.every( function ( selectedFilter ) {
186 return selectedFilter.existsInConflicts( filterItem );
187 } )
188 );
189 };
190
191 /**
192 * Check whether any of the selected items are in conflict with the given item
193 *
194 * @param {mw.rcfilters.dm.FilterItem} filterItem Filter item to test
195 * @return {boolean} Any of the selected items are in conflict with this item
196 */
197 mw.rcfilters.dm.FilterGroup.prototype.areAnySelectedInConflictWith = function ( filterItem ) {
198 var selectedItems = this.getSelectedItems( filterItem );
199
200 return selectedItems.length > 0 && (
201 // The group as a whole is in conflict with this item
202 this.existsInConflicts( filterItem ) ||
203 // Any selected items are in conflict individually
204 selectedItems.some( function ( selectedFilter ) {
205 return selectedFilter.existsInConflicts( filterItem );
206 } )
207 );
208 };
209
210 /**
211 * Get group type
212 *
213 * @return {string} Group type
214 */
215 mw.rcfilters.dm.FilterGroup.prototype.getType = function () {
216 return this.type;
217 };
218
219 /**
220 * Get group's title
221 *
222 * @return {string} Title
223 */
224 mw.rcfilters.dm.FilterGroup.prototype.getTitle = function () {
225 return this.title;
226 };
227
228 /**
229 * Get group's values separator
230 *
231 * @return {string} Values separator
232 */
233 mw.rcfilters.dm.FilterGroup.prototype.getSeparator = function () {
234 return this.separator;
235 };
236
237 /**
238 * Check whether the group is defined as full coverage
239 *
240 * @return {boolean} Group is full coverage
241 */
242 mw.rcfilters.dm.FilterGroup.prototype.isFullCoverage = function () {
243 return this.fullCoverage;
244 };
245 }( mediaWiki ) );