Adding new interface for review filters to RecentChanges
[lhc/web/wiklou.git] / resources / src / mediawiki.rcfilters / dm / mw.rcfilters.dm.FiltersViewModel.js
1 ( function ( mw, $ ) {
2 /**
3 * View model for the filters selection and display
4 *
5 * @mixins OO.EventEmitter
6 * @mixins OO.EmitterList
7 *
8 * @constructor
9 */
10 mw.rcfilters.dm.FiltersViewModel = function MwRcfiltersDmFiltersViewModel() {
11 // Mixin constructor
12 OO.EventEmitter.call( this );
13 OO.EmitterList.call( this );
14
15 this.groups = {};
16
17 // Events
18 this.aggregate( { update: 'itemUpdate' } );
19 };
20
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 );
25
26 /* Events */
27
28 /**
29 * @event initialize
30 *
31 * Filter list is initialized
32 */
33
34 /**
35 * @event itemUpdate
36 * @param {mw.rcfilters.dm.FilterItem} item Filter item updated
37 *
38 * Filter item has changed
39 */
40
41 /* Methods */
42
43 /**
44 * Set filters and preserve a group relationship based on
45 * the definition given by an object
46 *
47 * @param {Object} filters Filter group definition
48 */
49 mw.rcfilters.dm.FiltersViewModel.prototype.initializeFilters = function ( filters ) {
50 var i, filterItem,
51 model = this,
52 items = [];
53
54 // Reset
55 this.clearItems();
56 this.groups = {};
57
58 $.each( filters, function ( group, data ) {
59 model.groups[ group ] = model.groups[ group ] || {};
60 model.groups[ group ].filters = model.groups[ group ].filters || [];
61
62 model.groups[ group ].title = data.title;
63 model.groups[ group ].type = data.type;
64
65 for ( i = 0; i < data.filters.length; i++ ) {
66 filterItem = new mw.rcfilters.dm.FilterItem( data.filters[ i ].name, {
67 group: group,
68 label: data.filters[ i ].label,
69 description: data.filters[ i ].description,
70 selected: data.filters[ i ].selected
71 } );
72
73 model.groups[ group ].filters.push( filterItem );
74 items.push( filterItem );
75 }
76 } );
77
78 this.addItems( items );
79 this.emit( 'initialize' );
80 };
81
82 /**
83 * Get the names of all available filters
84 *
85 * @return {string[]} An array of filter names
86 */
87 mw.rcfilters.dm.FiltersViewModel.prototype.getFilterNames = function () {
88 return this.getItems().map( function ( item ) { return item.getName(); } );
89 };
90
91 /**
92 * Get the object that defines groups and their filter items.
93 * The structure of this response:
94 * {
95 * groupName: {
96 * title: {string} Group title
97 * type: {string} Group type
98 * filters: {string[]} Filters in the group
99 * }
100 * }
101 *
102 * @return {Object} Filter groups
103 */
104 mw.rcfilters.dm.FiltersViewModel.prototype.getFilterGroups = function () {
105 return this.groups;
106 };
107
108 /**
109 * Get the current state of the filters
110 *
111 * @return {Object} Filters current state
112 */
113 mw.rcfilters.dm.FiltersViewModel.prototype.getState = function () {
114 var i,
115 items = this.getItems(),
116 result = {};
117
118 for ( i = 0; i < items.length; i++ ) {
119 result[ items[ i ].getName() ] = items[ i ].isSelected();
120 }
121
122 return result;
123 };
124
125 /**
126 * Analyze the groups and their filters and output an object representing
127 * the state of the parameters they represent.
128 *
129 * @return {Object} Parameter state object
130 */
131 mw.rcfilters.dm.FiltersViewModel.prototype.getParametersFromFilters = function () {
132 var i, filterItems, anySelected,
133 result = {},
134 groupItems = this.getFilterGroups();
135
136 $.each( groupItems, function ( group, data ) {
137 if ( data.type === 'send_unselected_if_any' ) {
138 filterItems = data.filters;
139
140 // First, check if any of the items are selected at all.
141 // If none is selected, we're treating it as if they are
142 // all false
143 anySelected = filterItems.some( function ( filterItem ) {
144 return filterItem.isSelected();
145 } );
146
147 // Go over the items and define the correct values
148 for ( i = 0; i < filterItems.length; i++ ) {
149 result[ filterItems[ i ].getName() ] = anySelected ?
150 Number( !filterItems[ i ].isSelected() ) : 0;
151 }
152 }
153 } );
154
155 return result;
156 };
157
158 /**
159 * This is the opposite of the #getParametersFromFilters method; this goes over
160 * the parameters and translates into a selected/unselected value in the filters.
161 *
162 * @param {Object} params Parameters query object
163 * @return {Object} Filter state object
164 */
165 mw.rcfilters.dm.FiltersViewModel.prototype.getFiltersFromParameters = function ( params ) {
166 var i, filterItem, allItemsInGroup,
167 groupMap = {},
168 model = this,
169 base = this.getParametersFromFilters(),
170 // Start with current state
171 result = this.getState();
172
173 params = $.extend( {}, base, params );
174
175 $.each( params, function ( paramName, paramValue ) {
176 // Find the filter item
177 filterItem = model.getItemByName( paramName );
178
179 // Ignore if no filter item exists
180 if ( filterItem ) {
181 groupMap[ filterItem.getGroup() ] = groupMap[ filterItem.getGroup() ] || {};
182
183 // Mark the group if it has any items that are selected
184 groupMap[ filterItem.getGroup() ].hasSelected = (
185 groupMap[ filterItem.getGroup() ].hasSelected ||
186 !!Number( paramValue )
187 );
188
189 // Add the relevant filter into the group map
190 groupMap[ filterItem.getGroup() ].filters = groupMap[ filterItem.getGroup() ].filters || [];
191 groupMap[ filterItem.getGroup() ].filters.push( filterItem );
192 }
193 } );
194
195 // Now that we know the groups' selection states, we need to go over
196 // the filters in the groups and mark their selected states appropriately
197 $.each( groupMap, function ( group, data ) {
198 if ( model.groups[ group ].type === 'send_unselected_if_any' ) {
199 allItemsInGroup = model.groups[ group ].filters;
200
201 for ( i = 0; i < allItemsInGroup.length; i++ ) {
202 filterItem = allItemsInGroup[ i ];
203
204 result[ filterItem.getName() ] = data.hasSelected ?
205 // Flip the definition between the parameter
206 // state and the filter state
207 // This is what the 'toggleSelected' value of the filter is
208 !Number( params[ filterItem.getName() ] ) :
209 // Otherwise, there are no selected items in the
210 // group, which means the state is false
211 false;
212 }
213 }
214 } );
215 return result;
216 };
217
218 /**
219 * Get the item that matches the given name
220 *
221 * @param {string} name Filter name
222 * @return {mw.rcfilters.dm.FilterItem} Filter item
223 */
224 mw.rcfilters.dm.FiltersViewModel.prototype.getItemByName = function ( name ) {
225 return this.getItems().filter( function ( item ) {
226 return name === item.getName();
227 } )[ 0 ];
228 };
229
230 /**
231 * Toggle selected state of items by their names
232 *
233 * @param {Object} filterDef Filter definitions
234 */
235 mw.rcfilters.dm.FiltersViewModel.prototype.updateFilters = function ( filterDef ) {
236 var name, filterItem;
237
238 for ( name in filterDef ) {
239 filterItem = this.getItemByName( name );
240 filterItem.toggleSelected( filterDef[ name ] );
241 }
242 };
243
244 /**
245 * Find items whose labels match the given string
246 *
247 * @param {string} str Search string
248 * @return {Object} An object of items to show
249 * arranged by their group names
250 */
251 mw.rcfilters.dm.FiltersViewModel.prototype.findMatches = function ( str ) {
252 var i,
253 result = {},
254 items = this.getItems();
255
256 // Normalize so we can search strings regardless of case
257 str = str.toLowerCase();
258 for ( i = 0; i < items.length; i++ ) {
259 if ( items[ i ].getLabel().toLowerCase().indexOf( str ) > -1 ) {
260 result[ items[ i ].getGroup() ] = result[ items[ i ].getGroup() ] || [];
261 result[ items[ i ].getGroup() ].push( items[ i ] );
262 }
263 }
264 return result;
265 };
266
267 }( mediaWiki, jQuery ) );