2 /* eslint no-underscore-dangle: "off" */
4 * URI Processor for RCFilters
6 * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters view model
8 mw
.rcfilters
.UriProcessor
= function MwRcfiltersController( filtersModel
) {
9 this.emptyParameterState
= {};
10 this.filtersModel
= filtersModel
;
13 this._buildEmptyParameterState();
17 OO
.initClass( mw
.rcfilters
.UriProcessor
);
22 * Replace the url history through replaceState
24 * @param {mw.Uri} newUri New URI to replace
26 mw
.rcfilters
.UriProcessor
.static.replaceState = function ( newUri
) {
27 window
.history
.replaceState(
35 * Push the url to history through pushState
37 * @param {mw.Uri} newUri New URI to push
39 mw
.rcfilters
.UriProcessor
.static.pushState = function ( newUri
) {
40 window
.history
.pushState(
50 * Get the version that this URL query is tagged with.
52 * @param {Object} [uriQuery] URI query
53 * @return {number} URL version
55 mw
.rcfilters
.UriProcessor
.prototype.getVersion = function ( uriQuery
) {
56 uriQuery
= uriQuery
|| new mw
.Uri().query
;
58 return Number( uriQuery
.urlversion
|| 1 );
62 * Update the filters model based on the URI query
63 * This happens on initialization, and from this moment on,
64 * we consider the system synchronized, and the model serves
65 * as the source of truth for the URL.
67 * This methods should only be called once on initialiation.
68 * After initialization, the model updates the URL, not the
71 * @param {Object} [uriQuery] URI query
73 mw
.rcfilters
.UriProcessor
.prototype.updateModelBasedOnQuery = function ( uriQuery
) {
76 uriQuery
= uriQuery
|| new mw
.Uri().query
;
78 // For arbitrary numeric single_option values, check the uri and see if it's beyond the limit
79 $.each( this.filtersModel
.getFilterGroups(), function ( groupName
, groupModel
) {
81 groupModel
.getType() === 'single_option' &&
82 groupModel
.isAllowArbitrary()
85 groupModel
.getMaxValue() !== null &&
86 uriQuery
[ groupName
] > groupModel
.getMaxValue()
88 // Change the value to the actual max value
89 uriQuery
[ groupName
] = String( groupModel
.getMaxValue() );
91 groupModel
.getMinValue() !== null &&
92 uriQuery
[ groupName
] < groupModel
.getMinValue()
94 // Change the value to the actual min value
95 uriQuery
[ groupName
] = String( groupModel
.getMinValue() );
101 parameters
= this._getNormalizedQueryParams( uriQuery
);
103 // Update filter states
104 this.filtersModel
.toggleFiltersSelected(
105 this.filtersModel
.getFiltersFromParameters(
110 this.filtersModel
.toggleInvertedNamespaces( !!Number( parameters
.invert
) );
112 // Update highlight state
113 this.filtersModel
.getItems().forEach( function ( filterItem
) {
114 var color
= parameters
[ filterItem
.getName() + '_color' ];
116 filterItem
.setHighlightColor( color
);
118 filterItem
.clearHighlightColor();
121 this.filtersModel
.toggleHighlight( !!Number( parameters
.highlight
) );
123 // Check all filter interactions
124 this.filtersModel
.reassessFilterInteractions();
128 * Get parameters representing the current state of the model
130 * @return {Object} Uri query parameters
132 mw
.rcfilters
.UriProcessor
.prototype.getUriParametersFromModel = function () {
136 this.filtersModel
.getParametersFromFilters(),
137 this.filtersModel
.getHighlightParameters(),
139 highlight
: String( Number( this.filtersModel
.isHighlightEnabled() ) ),
140 invert
: String( Number( this.filtersModel
.areNamespacesInverted() ) )
146 * Build the full parameter representation based on given query parameters
149 * @param {Object} uriQuery Given URI query
150 * @return {Object} Full parameter state representing the URI query
152 mw
.rcfilters
.UriProcessor
.prototype._expandModelParameters = function ( uriQuery
) {
153 var filterRepresentation
= this.filtersModel
.getFiltersFromParameters( uriQuery
);
155 return $.extend( true,
158 this.filtersModel
.getParametersFromFilters( filterRepresentation
),
159 this.filtersModel
.extractHighlightValues( uriQuery
),
161 highlight
: String( Number( uriQuery
.highlight
) ),
162 invert
: String( Number( uriQuery
.invert
) )
168 * Compare two URI queries to decide whether they are different
169 * enough to represent a new state.
171 * @param {Object} currentUriQuery Current Uri query
172 * @param {Object} updatedUriQuery Updated Uri query
173 * @return {boolean} This is a new state
175 mw
.rcfilters
.UriProcessor
.prototype.isNewState = function ( currentUriQuery
, updatedUriQuery
) {
176 var currentParamState
, updatedParamState
,
177 notEquivalent = function ( obj1
, obj2
) {
178 var keys
= Object
.keys( obj1
).concat( Object
.keys( obj2
) );
179 return keys
.some( function ( key
) {
180 return obj1
[ key
] != obj2
[ key
]; // eslint-disable-line eqeqeq
184 // Compare states instead of parameters
185 // This will allow us to always have a proper check of whether
186 // the requested new url is one to change or not, regardless of
187 // actual parameter visibility/representation in the URL
188 currentParamState
= this._expandModelParameters( currentUriQuery
);
189 updatedParamState
= this._expandModelParameters( updatedUriQuery
);
191 return notEquivalent( currentParamState
, updatedParamState
);
195 * Check whether the given query has parameters that are
196 * recognized as parameters we should load the system with
198 * @param {mw.Uri} [uriQuery] Given URI query
199 * @return {boolean} Query contains valid recognized parameters
201 mw
.rcfilters
.UriProcessor
.prototype.doesQueryContainRecognizedParams = function ( uriQuery
) {
203 validParameterNames
= Object
.keys( this._getEmptyParameterState() )
204 .filter( function ( param
) {
205 // Remove 'highlight' parameter from this check;
206 // if it's the only parameter in the URL we still
207 // want to consider the URL 'empty' for defaults to load
208 return param
!== 'highlight';
211 uriQuery
= uriQuery
|| new mw
.Uri().query
;
213 anyValidInUrl
= Object
.keys( uriQuery
).some( function ( parameter
) {
214 return validParameterNames
.indexOf( parameter
) > -1;
217 // URL version 2 is allowed to be empty or within nonrecognized params
218 return anyValidInUrl
|| this.getVersion( uriQuery
) === 2;
222 * Remove all parameters that have the same value as the base state
223 * This method expects uri queries of the urlversion=2 format
226 * @param {Object} uriQuery Current uri query
227 * @return {Object} Minimized query
229 mw
.rcfilters
.UriProcessor
.prototype.minimizeQuery = function ( uriQuery
) {
230 var baseParams
= this._getEmptyParameterState(),
231 uriResult
= $.extend( true, {}, uriQuery
);
233 $.each( uriResult
, function ( paramName
, paramValue
) {
235 baseParams
[ paramName
] !== undefined &&
236 baseParams
[ paramName
] === paramValue
238 // Remove parameter from query
239 delete uriResult
[ paramName
];
247 * Get the adjusted URI params based on the url version
248 * If the urlversion is not 2, the parameters are merged with
249 * the model's defaults.
252 * @param {Object} uriQuery Current URI query
253 * @return {Object} Normalized parameters
255 mw
.rcfilters
.UriProcessor
.prototype._getNormalizedQueryParams = function ( uriQuery
) {
256 // Check whether we are dealing with urlversion=2
257 // If we are, we do not merge the initial request with
258 // defaults. Not having urlversion=2 means we need to
259 // reproduce the server-side request and merge the
260 // requested parameters (or starting state) with the
262 // Any subsequent change of the URL through the RCFilters
263 // system will receive 'urlversion=2'
264 var hiddenParamDefaults
= {},
265 base
= this.getVersion( uriQuery
) === 2 ?
267 this.filtersModel
.getDefaultParams();
269 // Go over the model and get all hidden parameters' defaults
270 // These defaults should be applied regardless of the urlversion
271 // but be overridden by the URL params if they exist
272 $.each( this.filtersModel
.getFilterGroups(), function ( groupName
, groupModel
) {
273 if ( groupModel
.isHidden() ) {
274 $.extend( true, hiddenParamDefaults
, groupModel
.getDefaultParams() );
278 return this.minimizeQuery(
279 $.extend( true, {}, hiddenParamDefaults
, base
, uriQuery
, { urlversion
: '2' } )
284 * Get the representation of an empty parameter state
287 * @return {Object} Empty parameter state
289 mw
.rcfilters
.UriProcessor
.prototype._getEmptyParameterState = function () {
290 // Override empty parameter state with the sticky parameter values
291 return $.extend( true, {}, this.emptyParameterState
, this.filtersModel
.getStickyParams() );
295 * Build an empty representation of the parameters, where all parameters
296 * are either set to '0' or '' depending on their type.
297 * This must run during initialization, before highlights are set.
301 mw
.rcfilters
.UriProcessor
.prototype._buildEmptyParameterState = function () {
302 var emptyParams
= this.filtersModel
.getParametersFromFilters( {} ),
303 emptyHighlights
= this.filtersModel
.getHighlightParameters();
305 this.emptyParameterState
= $.extend(
310 { highlight
: '0', invert
: '0' }
313 }( mediaWiki
, jQuery
) );