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