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
) );