3 * View model for the filters selection and display
5 * @mixins OO.EventEmitter
6 * @mixins OO.EmitterList
10 mw.rcfilters.dm.FiltersViewModel = function MwRcfiltersDmFiltersViewModel() {
12 OO.EventEmitter.call( this );
13 OO.EmitterList.call( this );
18 this.aggregate( { update: 'itemUpdate' } );
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 );
31 * Filter list is initialized
36 * @param {mw.rcfilters.dm.FilterItem} item Filter item updated
38 * Filter item has changed
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 ) {
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, {
69 label: data.filters[ i ].label,
70 description: data.filters[ i ].description,
71 selected: data.filters[ i ].selected
74 model.groups[ group ].filters.push( filterItem );
75 items.push( filterItem );
79 this.addItems( items );
80 this.emit( 'initialize' );
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(); } );
93 * Get the object that defines groups and their filter items.
94 * The structure of this response:
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 () {
110 * Get the current state of the filters
112 * @return {Object} Filters current state
114 mw.rcfilters.dm.FiltersViewModel.prototype.getState = function () {
116 items = this.getItems(),
119 for ( i = 0; i < items.length; i++ ) {
120 result[ items[ i ].getName() ] = items[ i ].isSelected();
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,
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
144 anySelected = filterItems.some( function ( filterItem ) {
145 return filterItem.isSelected();
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' ) {
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';
164 result[ group ] = values.join( data.separator );
173 * Sanitize value group of a string_option groups type
174 * Remove duplicates and make sure to only use valid
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 ) {
183 validNames = this.groups[ groupName ].filters.map( function ( filterItem ) {
184 return filterItem.getName();
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
195 // Get rid of any dupe and invalid parameter, only output
197 // Example: param=valid1,valid2,invalid1,valid1
198 // Result: param=valid1,valid2
199 valueArray.forEach( function ( value ) {
201 validNames.indexOf( value ) > -1 &&
202 result.indexOf( value ) === -1
204 result.push( value );
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 ) {
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
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 };
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
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
285 // Otherwise, the filter is selected only if it appears in the parameter values
286 paramValues.indexOf( filterItem.getName() ) > -1;
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();
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 ) {
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 ] );
342 }( mediaWiki, jQuery ) );