Merge "Add deprecated annotation to Article::doEditContent()"
[mediawiki.git] / resources / src / mediawiki.rcfilters / dm / mw.rcfilters.dm.FiltersViewModel.js
blob3217d0d7d416fbd8e4164b93414a339cf7b3dda1
1 ( function ( mw, $ ) {
2 /**
3 * View model for the filters selection and display
5 * @mixins OO.EventEmitter
6 * @mixins OO.EmitterList
8 * @constructor
9 */
10 mw.rcfilters.dm.FiltersViewModel = function MwRcfiltersDmFiltersViewModel() {
11 // Mixin constructor
12 OO.EventEmitter.call( this );
13 OO.EmitterList.call( this );
15 this.groups = {};
17 // Events
18 this.aggregate( { update: 'itemUpdate' } );
21 /* Initialization */
22 OO.initClass( mw.rcfilters.dm.FiltersViewModel );
23 OO.mixinClass( mw.rcfilters.dm.FiltersViewModel, OO.EventEmitter );
24 OO.mixinClass( mw.rcfilters.dm.FiltersViewModel, OO.EmitterList );
26 /* Events */
28 /**
29 * @event initialize
31 * Filter list is initialized
34 /**
35 * @event itemUpdate
36 * @param {mw.rcfilters.dm.FilterItem} item Filter item updated
38 * Filter item has changed
41 /* Methods */
43 /**
44 * Set filters and preserve a group relationship based on
45 * the definition given by an object
47 * @param {Object} filters Filter group definition
49 mw.rcfilters.dm.FiltersViewModel.prototype.initializeFilters = function ( filters ) {
50 var i, filterItem,
51 model = this,
52 items = [];
54 // Reset
55 this.clearItems();
56 this.groups = {};
58 $.each( filters, function ( group, data ) {
59 model.groups[ group ] = model.groups[ group ] || {};
60 model.groups[ group ].filters = model.groups[ group ].filters || [];
62 model.groups[ group ].title = data.title;
63 model.groups[ group ].type = data.type;
64 model.groups[ group ].separator = data.separator || '|';
66 for ( i = 0; i < data.filters.length; i++ ) {
67 filterItem = new mw.rcfilters.dm.FilterItem( data.filters[ i ].name, {
68 group: group,
69 label: data.filters[ i ].label,
70 description: data.filters[ i ].description,
71 selected: data.filters[ i ].selected
72 } );
74 model.groups[ group ].filters.push( filterItem );
75 items.push( filterItem );
77 } );
79 this.addItems( items );
80 this.emit( 'initialize' );
83 /**
84 * Get the names of all available filters
86 * @return {string[]} An array of filter names
88 mw.rcfilters.dm.FiltersViewModel.prototype.getFilterNames = function () {
89 return this.getItems().map( function ( item ) { return item.getName(); } );
92 /**
93 * Get the object that defines groups and their filter items.
94 * The structure of this response:
95 * {
96 * groupName: {
97 * title: {string} Group title
98 * type: {string} Group type
99 * filters: {string[]} Filters in the group
103 * @return {Object} Filter groups
105 mw.rcfilters.dm.FiltersViewModel.prototype.getFilterGroups = function () {
106 return this.groups;
110 * Get the current state of the filters
112 * @return {Object} Filters current state
114 mw.rcfilters.dm.FiltersViewModel.prototype.getState = function () {
115 var i,
116 items = this.getItems(),
117 result = {};
119 for ( i = 0; i < items.length; i++ ) {
120 result[ items[ i ].getName() ] = items[ i ].isSelected();
123 return result;
127 * Analyze the groups and their filters and output an object representing
128 * the state of the parameters they represent.
130 * @return {Object} Parameter state object
132 mw.rcfilters.dm.FiltersViewModel.prototype.getParametersFromFilters = function () {
133 var i, filterItems, anySelected, values,
134 result = {},
135 groupItems = this.getFilterGroups();
137 $.each( groupItems, function ( group, data ) {
138 filterItems = data.filters;
140 if ( data.type === 'send_unselected_if_any' ) {
141 // First, check if any of the items are selected at all.
142 // If none is selected, we're treating it as if they are
143 // all false
144 anySelected = filterItems.some( function ( filterItem ) {
145 return filterItem.isSelected();
146 } );
148 // Go over the items and define the correct values
149 for ( i = 0; i < filterItems.length; i++ ) {
150 result[ filterItems[ i ].getName() ] = anySelected ?
151 Number( !filterItems[ i ].isSelected() ) : 0;
153 } else if ( data.type === 'string_options' ) {
154 values = [];
155 for ( i = 0; i < filterItems.length; i++ ) {
156 if ( filterItems[ i ].isSelected() ) {
157 values.push( filterItems[ i ].getName() );
161 if ( values.length === 0 || values.length === filterItems.length ) {
162 result[ group ] = 'all';
163 } else {
164 result[ group ] = values.join( data.separator );
167 } );
169 return result;
173 * Sanitize value group of a string_option groups type
174 * Remove duplicates and make sure to only use valid
175 * values.
177 * @param {string} groupName Group name
178 * @param {string[]} valueArray Array of values
179 * @return {string[]} Array of valid values
181 mw.rcfilters.dm.FiltersViewModel.prototype.sanitizeStringOptionGroup = function( groupName, valueArray ) {
182 var result = [],
183 validNames = this.groups[ groupName ].filters.map( function ( filterItem ) {
184 return filterItem.getName();
185 } );
187 if ( valueArray.indexOf( 'all' ) > -1 ) {
188 // If anywhere in the values there's 'all', we
189 // treat it as if only 'all' was selected.
190 // Example: param=valid1,valid2,all
191 // Result: param=all
192 return [ 'all' ];
195 // Get rid of any dupe and invalid parameter, only output
196 // valid ones
197 // Example: param=valid1,valid2,invalid1,valid1
198 // Result: param=valid1,valid2
199 valueArray.forEach( function ( value ) {
200 if (
201 validNames.indexOf( value ) > -1 &&
202 result.indexOf( value ) === -1
204 result.push( value );
206 } );
208 return result;
212 * This is the opposite of the #getParametersFromFilters method; this goes over
213 * the parameters and translates into a selected/unselected value in the filters.
215 * @param {Object} params Parameters query object
216 * @return {Object} Filter state object
218 mw.rcfilters.dm.FiltersViewModel.prototype.getFiltersFromParameters = function ( params ) {
219 var i, filterItem,
220 groupMap = {},
221 model = this,
222 base = this.getParametersFromFilters(),
223 // Start with current state
224 result = this.getState();
226 params = $.extend( {}, base, params );
228 $.each( params, function ( paramName, paramValue ) {
229 // Find the filter item
230 filterItem = model.getItemByName( paramName );
231 // Ignore if no filter item exists
232 if ( filterItem ) {
233 groupMap[ filterItem.getGroup() ] = groupMap[ filterItem.getGroup() ] || {};
235 // Mark the group if it has any items that are selected
236 groupMap[ filterItem.getGroup() ].hasSelected = (
237 groupMap[ filterItem.getGroup() ].hasSelected ||
238 !!Number( paramValue )
241 // Add the relevant filter into the group map
242 groupMap[ filterItem.getGroup() ].filters = groupMap[ filterItem.getGroup() ].filters || [];
243 groupMap[ filterItem.getGroup() ].filters.push( filterItem );
244 } else if ( model.groups.hasOwnProperty( paramName ) ) {
245 // This parameter represents a group (values are the filters)
246 // this is equivalent to checking if the group is 'string_options'
247 groupMap[ paramName ] = { filters: model.groups[ paramName ].filters };
249 } );
251 // Now that we know the groups' selection states, we need to go over
252 // the filters in the groups and mark their selected states appropriately
253 $.each( groupMap, function ( group, data ) {
254 var paramValues, filterItem,
255 allItemsInGroup = data.filters;
257 if ( model.groups[ group ].type === 'send_unselected_if_any' ) {
258 for ( i = 0; i < allItemsInGroup.length; i++ ) {
259 filterItem = allItemsInGroup[ i ];
261 result[ filterItem.getName() ] = data.hasSelected ?
262 // Flip the definition between the parameter
263 // state and the filter state
264 // This is what the 'toggleSelected' value of the filter is
265 !Number( params[ filterItem.getName() ] ) :
266 // Otherwise, there are no selected items in the
267 // group, which means the state is false
268 false;
270 } else if ( model.groups[ group ].type === 'string_options' ) {
271 paramValues = model.sanitizeStringOptionGroup( group, params[ group ].split( model.groups[ group ].separator ) );
273 for ( i = 0; i < allItemsInGroup.length; i++ ) {
274 filterItem = allItemsInGroup[ i ];
276 result[ filterItem.getName() ] = (
277 // If it is the word 'all'
278 paramValues.length === 1 && paramValues[ 0 ] === 'all' ||
279 // All values are written
280 paramValues.length === model.groups[ group ].filters.length
282 // All true (either because all values are written or the term 'all' is written)
283 // is the same as all filters set to false
284 false :
285 // Otherwise, the filter is selected only if it appears in the parameter values
286 paramValues.indexOf( filterItem.getName() ) > -1;
289 } );
290 return result;
294 * Get the item that matches the given name
296 * @param {string} name Filter name
297 * @return {mw.rcfilters.dm.FilterItem} Filter item
299 mw.rcfilters.dm.FiltersViewModel.prototype.getItemByName = function ( name ) {
300 return this.getItems().filter( function ( item ) {
301 return name === item.getName();
302 } )[ 0 ];
306 * Toggle selected state of items by their names
308 * @param {Object} filterDef Filter definitions
310 mw.rcfilters.dm.FiltersViewModel.prototype.updateFilters = function ( filterDef ) {
311 var name, filterItem;
313 for ( name in filterDef ) {
314 filterItem = this.getItemByName( name );
315 filterItem.toggleSelected( filterDef[ name ] );
320 * Find items whose labels match the given string
322 * @param {string} str Search string
323 * @return {Object} An object of items to show
324 * arranged by their group names
326 mw.rcfilters.dm.FiltersViewModel.prototype.findMatches = function ( str ) {
327 var i,
328 result = {},
329 items = this.getItems();
331 // Normalize so we can search strings regardless of case
332 str = str.toLowerCase();
333 for ( i = 0; i < items.length; i++ ) {
334 if ( items[ i ].getLabel().toLowerCase().indexOf( str ) > -1 ) {
335 result[ items[ i ].getGroup() ] = result[ items[ i ].getGroup() ] || [];
336 result[ items[ i ].getGroup() ].push( items[ i ] );
339 return result;
342 }( mediaWiki, jQuery ) );