MediaWiki:Gadget-switch-infobox.js

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
  1 // <nowiki>
  2 /* switch infobox code for infoboxes
  3  * contains switching code for both:
  4  * * originalInfoboxes:
  5  *		older infobox switching, such as [[Template:Infobox Bonuses]]
  6  *		which works my generating complete infoboxes for each version
  7  * * moduleInfoboxes:
  8  *		newer switching, as implemented by [[Module:Infobox]]
  9  *		which generates one infobox and a resources pool for switching
 10  * * synced switches
 11  *		as generated by [[Module:Synced switch]] and its template
 12  * 
 13  * The script also facilitates synchronising infoboxes, so that if a button of one is pressed
 14  *	and another switchfobox on the same page also has that button, it will 'press' itself
 15  * This only activates if there are matching version parameters in the infoboxes (i.e. the button text is the same)
 16  * - thus it works best if the version parameters are all identical
 17  * 
 18  * TODO: OOUI? (probably not, its a little clunky and large for this. It'd need so much styling it isn't worthwhile)
 19  */
 20 $(function () {
 21 	if (!($('.switch-infobox').length || $('.infobox-buttons').length)) {
 22 		return;
 23 	}
 24 
 25 	var SWITCH_REF_REGEX = /^\$(\d+)/,
 26 		CAN_LOCAL_STORAGE = true;
 27 	function getGenderFromLS() {
 28 		if (CAN_LOCAL_STORAGE) {
 29 			var x = window.localStorage.getItem('gender-render');
 30 			if (['m', 'f'].indexOf(x) > -1) {
 31 				return x;
 32 			}
 33 		}
 34 		return 'm';
 35 	}
 36 	/**
 37 	 * Switch infobox psuedo-interface
 38 	 * 
 39 	 * Switch infoboxes are given several similar functions so that they can be called similarly
 40 	 * This is essentially like an interface or class structure, except I'm too lazy to implement that
 41 	 * 
 42 	 * 		switchfo.beginSwitchEvent(event)
 43 	 * 			the reactionary event to buttons being clicked/selects being selected/etc
 44 	 * 			tells SwitchEventManager to switch all the boxes
 45 	 * 			should extract an index and anchor from the currentTarget and pass that to the SwitchEventManager.trigger function
 46 	 * 			event		the jQuery event fired from $.click/$.change/etc
 47 	 * 
 48 	 * 		switchfo.switch(index, anchor)
 49 	 * 			do all the actual switching of the infobox to the infobox specified by the anchor and index
 50 	 * 			prefer using the anchor if there is a conflict
 51 	 * 
 52 	 * 		switchfo.defaultVer()
 53 	 * 			called during script init
 54 	 * 			returns either an anchor for the default version, if manually specified, or false if there is no default specified
 55 	 * 			the page will automatically switch to the default version, or to version 1, when loaded.
 56 	 * 
 57 	 */
 58 	/** 
 59 	 * Switch Infoboxes based on [[Module:Infobox]]
 60 	 * 
 61 	 * - the preferred way to do switch infoboxes
 62 	 * - generates one table and a resources table, swaps resources into the table as required
 63 	 * - with enough buttons, becomes a dropdown <select>
 64 	 * 
 65 	 * parameters
 66 	 *	  $box	jQuery object representing the infobox itself (.infobox-switch)
 67 	 *	  index   index of this infobox, from $.each
 68 	 */
 69 	function SwitchInfobox($box, index) {
 70 		var self = this;
 71 		this.index = index;
 72 		this.$infobox = $box;
 73 		this.$resources = self.$infobox.next();
 74 		this.$buttons = self.$infobox.find('div.infobox-buttons');
 75 		this.isSelect = self.$buttons.hasClass('infobox-buttons-select');
 76 		this.$select = null;
 77 		this.originalClasses = {};
 78 
 79 		/* click/change event - triggers switch event manager */
 80 		this.beginSwitchEvent = function(e) {
 81 			var $tgt = $(e.currentTarget);
 82 			mw.log('beginSwitchEvent triggered in module infobox, id '+self.index);
 83 			if (self.isSelect) {
 84 				window.switchEventManager.trigger($tgt.val(), $tgt.find(' > option[data-switch-index='+$tgt.val()+']').attr('data-switch-anchor'));
 85 			} else {
 86 				window.switchEventManager.trigger($tgt.attr('data-switch-index'), $tgt.attr('data-switch-anchor'), self.$infobox);
 87 			}
 88 		};
 89 
 90 		/* switch event, triggered by manager */
 91 		this.switchInfobox = function(index, text) {
 92 			if (text === '@init@') {
 93 				text = self.$buttons.find('[data-switch-index="1"]').attr('data-switch-anchor');
 94 			}
 95 			var ind, txt, $thisButton = self.$buttons.find('[data-switch-anchor="'+text+'"]');
 96 			mw.log('switching module infobox, id '+self.index);
 97 			// prefer text
 98 			if ($thisButton.length) {
 99 				txt = text;
100 				ind = $thisButton.attr('data-switch-index');
101 			} 
102 			if (ind === undefined) {
103 				return;
104 				/*ind = index;
105 				$thisButton = self.$buttons.find('[data-switch-index="'+ind+'"]');
106 				if ($thisButton.length) {
107 					txt = $thisButton.attr('data-switch-anchor');
108 				}*/
109 			}
110 			if (txt === undefined) {
111 				return;
112 			}
113 			if (self.isSelect) {
114 				self.$select.val(ind);
115 			} else {
116 				self.$buttons.find('span.button').removeClass('button-selected');
117 				$thisButton.addClass('button-selected');
118 			}
119 			
120 			self.$infobox.find('[data-attr-param][data-attr-param!=""]').each(function(i,e) {
121 				var $e = $(e),
122 					param = $e.attr('data-attr-param'),
123 					$switches = self.$resources.find('span[data-attr-param="'+param+'"]'),
124 					m,
125 					$val,
126 					$classTgt;
127 				
128 				// check if we found some switch data
129 				if (!$switches.length) return;
130 
131 				// find value
132 				$val = $switches.find('span[data-attr-index="'+ind+'"]');
133 				if (!$val.length) {
134 					// didn't find it, use default value
135 					$val = $switches.find('span[data-attr-index="0"]');
136 					if (!$val.length) return;
137 				}
138 				// switch references support - $2 -> use the value for index 2
139 				m = SWITCH_REF_REGEX.exec($val.html());
140 				if (m) { // m is null if no matches
141 					$val = $switches.find('span[data-attr-index="'+m[1]+'"]'); // m is [ entire match, capture ]
142 					if (!$val.length) {
143 						$val = $switches.find('span[data-attr-index="0"]'); // fallback again
144 						if (!$val.length) return;
145 					}
146 				}
147 				$val = $val.clone(true,true);
148 				$e.empty().append($val.contents());
149 
150 				// class switching
151 				// find the thing we're switching classes for
152 				if ($e.is('td, th')) {
153 					$classTgt = $e.parent('tr');
154 				} else {
155 					$classTgt = $e;
156 				}
157 
158 				// reset classes
159 				if (self.originalClasses.hasOwnProperty(param)) {
160 					$classTgt.attr('class', self.originalClasses[param]);
161 				} else {
162 					$classTgt.removeAttr('class');
163 				}
164 
165 				// change classes if needed
166 				if ($val.attr('data-addclass') !== undefined) {
167 					$classTgt.addClass($val.attr('data-addclass'));
168 				}
169 			});
170 			// trigger complete event for inter-script functions
171 			self.$buttons.trigger('switchinfoboxComplete', {txt:txt, num:ind});
172 			//re-initialise quantity boxes, if any
173 			if (window.rswiki && typeof(rswiki.initQtyBox) == 'function') {
174 				rswiki.initQtyBox(self.$infobox)
175 			}
176 			console.log(this);
177 		};
178 		
179 		/* default version, return the anchor of the switchable if it exists */
180 		this.defaultVer = function () {
181 			var defver = self.$buttons.attr('data-default-version');
182 			if (defver !== undefined) {
183 				return { idx: defver, txt: self.$buttons.find('[data-switch-index="'+defver+'"]').attr('data-switch-anchor') };
184 			}
185 			return false;
186 		};
187 		
188 		this.isParentOf = function ($triggerer) {
189 			return self.$infobox.find($triggerer).length > 0;
190 		};
191 
192 		/* init */
193 		mw.log('setting up module infobox, id '+self.index);
194 		// setup original classes
195 		this.$infobox.find('[data-attr-param][data-attr-param!=""]').each(function(i,e){
196 			var $e = $(e), $classElem = $e, clas;
197 			if ($e.is('td, th')) {
198 				$classElem = $e.parent('tr');
199 			}
200 			clas = $classElem.attr('class');
201 			if (typeof clas === 'string') {
202 				self.originalClasses[$e.attr('data-attr-param')] = clas;
203 			}
204 		});
205 
206 		// setup select/buttons and events
207 		if (self.isSelect) {
208 			self.$select = $('<select>')
209 				.attr({
210 					id: 'infobox-select-' + self.index,
211 					name: 'infobox-select-' + self.index,
212 				});
213 			self.$buttons.find('span.button').each(function(i, e){
214 				var $e = $(e);
215 				self.$select.append(
216 					$('<option>').attr({
217 						value: $e.attr('data-switch-index'),
218 						'data-switch-index': $e.attr('data-switch-index'),
219 						'data-switch-anchor': $e.attr('data-switch-anchor')
220 					}).text($e.text())
221 				);
222 			});
223 			self.$buttons.empty().append(self.$select);
224 			self.$select.change(self.beginSwitchEvent);
225 		} else {
226 			self.$buttons
227 				.attr({
228 					id: 'infobox-buttons-'+self.index
229 				})
230 				.find('span').each(function(i,e) {
231 					$(e).click(self.beginSwitchEvent);
232 				});
233 		}
234 
235 		self.$buttons.css('display', 'block');
236 		self.switchInfobox(1, '@init@');
237 
238 		window.switchEventManager.addSwitchInfobox(this);
239 		if (this.$infobox.find('.infobox-bonuses-image.render-m').length === 1 && this.$infobox.find('.infobox-bonuses-image.render-f').length === 1) {
240 			this.genderswitch = new GenderRenderSwitcher(this.$infobox, this.index);
241 		}
242 	}
243 	
244 	/**
245 	 * Special support for gender render switching in infobox bonuses (& synced switch)
246 	 * Currently specifically only supports male & female
247 	 * potential TODO: generalise?
248 	 * 
249 	 * parameters
250 	 *	  $box	jQuery object representing the infobox itself (.infobox-switch)
251 	 */
252 	function GenderRenderSwitcher($box, index) {
253 		var self = this;
254 		this.$box = $box;
255 		this.index = index;
256 		this.$buttons = $('<div>').addClass('infobox-buttons').css('display', 'block');
257 		this.button = {
258 			m: $('<span>').addClass('button').attr('data-gender-render', 'm').text('Male'),
259 			f: $('<span>').addClass('button').attr('data-gender-render', 'f').text('Female')
260 		};
261 		this.$td = $('<td>');
262 		this.$td_inner = $('<div class="gender-render-inner">');
263 		this.visible_gender = '';
264 		
265 		// from interface, we can just get the SyncedSwitches to switch
266 		this.beginSwitchEvent = function(event){
267 			var $e = $(event.currentTarget);
268 			var gen = $e.attr('data-gender-render');
269 			mw.log('beginSwitchEvent for genderswitcher '+self.index+' - switching to '+gen);
270 			window.switchEventManager.triggerGenderRenderSwitch(gen);
271 			if (CAN_LOCAL_STORAGE) {
272 				window.localStorage.setItem('gender-render', gen);
273 			}
274 		};
275 		// do the actual switching
276 		this.genderSwitch = function(gender) {
277 			mw.log('switching gender for genderswitcher for '+self.index+' to '+gender);
278 			self.$buttons.find('.button-selected').removeClass('button-selected');
279 			self.button[gender].addClass('button-selected');
280 
281 			var x = self.$box.find('.infobox-bonuses-image.render-'+gender+'');
282 			self.$td_inner.empty().append(x.find('>*').clone());
283 			self.visible_gender = gender;
284 		};
285 		this.refreshImage = function(index,anchor) {
286 			// for when a main infobox switch happens
287 			// this is a post-switch function so the new images are in the original cells
288 			// we just gotta clone them into the visible cell again
289 			self.genderSwitch(self.visible_gender);
290 			mw.log('refreshed image for genderswitcher '+self.index);
291 		};
292 		
293 		
294 		// other 'interface' methods just so stuff doesn't break, just in case
295 		this.switchInfobox = function(ind,anchor){/* do nothing */};
296 		this.defaultVer = function(){ return false; };
297 
298 		mw.log('Initialising genderswitcher for '+self.index);
299 		var $c_m = this.$box.find('.infobox-bonuses-image.render-m'), $c_f=this.$box.find('.infobox-bonuses-image.render-f');
300 		this.$td.addClass('gender-render').attr({
301 			'style': $c_m.attr('style'),
302 			'rowspan': $c_m.attr('rowspan')
303 		}).append(this.$td_inner);
304 		$c_m.parent().append(this.$td);
305 		this.$buttons.append(this.button.m, this.button.f);
306 		this.$td.append(this.$buttons);
307 		this.$buttons.find('span.button').on('click', this.beginSwitchEvent);
308 
309 		$c_m.addClass('gender-render-hidden').attr('data-gender-render', 'm');
310 		$c_f.addClass('gender-render-hidden').attr('data-gender-render', 'f');
311 		window.switchEventManager.addGenderRenderSwitch(self);
312 		window.switchEventManager.addPostSwitchEvent(this.refreshImage);
313 		this.genderSwitch(getGenderFromLS());
314 	}
315 
316 	/**
317 	 * Legacy switch infoboxes, as generated by [[Template:Switch infobox]]
318 	 * 
319 	 * 
320 	 * parameters
321 	 *	  $box	jQuery object representing the infobox itself (.switch-infobox)
322 	 *	  index   index of this infobox, from $.each
323 	 */
324 	function LegacySwitchInfobox($box, index) {
325 		var self = this;
326 		this.$parent = $box;
327 		this.index = index;
328 		this.$originalButtons = self.$parent.find('.switch-infobox-triggers');
329 		this.$items = self.$parent.find('.item');
330 
331 		/* click/change event - triggers switch event manager */
332 		this.beginSwitchEvent = function(e) {
333 			var $tgt = $(e.currentTarget);
334 			mw.log('beginSwitchEvent triggered in legacy infobox, id '+self.index);
335 			window.switchEventManager.trigger($tgt.attr('data-id'), $tgt.attr('data-anchor'), self.$parent);
336 		};
337 
338 		/* click/change event - triggers switch event manager */
339 		this.switchInfobox = function(index, text){
340 			if (text === '@init@') {
341 				text = self.$buttons.find('[data-switch-index="1"]').attr('data-switch-anchor');
342 			}
343 			var ind, txt, $thisButton = self.$buttons.find('[data-anchor="'+text+'"]').first();
344 			mw.log('switching legacy infobox, id '+self.index);
345 			if ($thisButton.length) {
346 				txt = text;
347 				ind = $thisButton.attr('data-id');
348 			} else {
349 				return;
350 				/*ind = index;
351 				$thisButton = self.$buttons.find('[data-id="'+ind+'"]');
352 				if ($thisButton.length) {
353 					txt = $thisButton.attr('data-anchor');
354 				}*/
355 			}
356 			if (txt === undefined) {
357 				return;
358 			}
359 			self.$buttons.find('.trigger').removeClass('button-selected');
360 			self.$buttons.find('.trigger[data-id="'+ind+'"]').addClass('button-selected');
361 			
362 			self.$items.filter('.showing').removeClass('showing');
363 			self.$items.filter('[data-id="'+ind+'"]').addClass('showing');
364 		};
365 		
366 		/* default version - not supported by legacy, always false */
367 		this.defaultVer = function () { return false; };
368 		
369 		this.isParentOf = function ($triggerer) {
370 			return self.$parent.find($triggerer).length > 0;
371 		};
372 
373 		/* init */
374 		mw.log('setting up legacy infobox, id '+self.index);
375 		// add anchor text
376 		self.$originalButtons.find('span.trigger.button').each(function(i,e){
377 			var $e = $(e);
378 			$e.attr('data-anchor', '#'+$e.text().replace(' ', '_'));
379 		});
380 
381 		// append triggers to every item
382 		// if contents has a infobox, add to a caption of that
383 		// else just put at top
384 		self.$items.each(function(i,e){
385 			var $item = $(e);
386 			if ($item.find('table.infobox').length > 0) {
387 				if ($item.find('table.infobox caption').length < 1) {
388 					$item.find('table.infobox').prepend('<caption>');
389 				}
390 				$item.find('table.infobox caption').first().prepend(self.$originalButtons.clone());
391 			} else {
392 				$item.prepend(self.$originalButtons.clone());
393 			}
394 		});
395 		// remove buttons from current location
396 		self.$originalButtons.remove();
397 
398 		// update selection
399 		this.$buttons = self.$parent.find('.switch-infobox-triggers');
400 		self.$buttons.find('.trigger').each(function (i,e) {
401 			$(e).click(self.beginSwitchEvent);
402 		});
403 		self.switchInfobox(1, '@init@');
404 		
405 		window.switchEventManager.addSwitchInfobox(this);
406 		self.$parent.removeClass('loading').find('span.loading-button').remove();
407 	}
408 
409 	/**
410 	 * Synced switches, as generated by [[Template:Synced switch]]
411 	 * 
412 	 * 
413 	 * parameters
414 	 *	  $box	jQuery object representing the synced switch itself (.rsw-synced-switch)
415 	 *	  index   index of this infobox, from $.each
416 	 */
417 	function SyncedSwitch($box, index) {
418 		var self = this;
419 		this.index = index;
420 		this.$syncedswitch = $box;
421 		this.attachedLabels = false;
422 
423 		/* filling in interface - synced switch has no buttons to press so cannot trigger an event by itself */
424 		this.beginSwitchEvent = function (){};
425 
426 		this.switchInfobox = function(index, text){
427 			mw.log('switching synced switch, id '+self.index);
428 			if (text === '@init@') {
429 				text = self.$syncedswitch.find('[data-item="1"]').attr('data-item-text');
430 			}
431 			var $toShow = self.$syncedswitch.find('[data-item-text="'+text+'"]');
432 			if (!(self.attachedLabels && $toShow.length)) {
433 				return;
434 				//$toShow = self.$syncedswitch.find('[data-item="'+index+'"]');
435 			}
436 			if (!$toShow.length) {
437 				// show default instead
438 				self.$syncedswitch.find('.rsw-synced-switch-item').removeClass('showing');
439 				self.$syncedswitch.find('[data-item="0"]').addClass('showing');
440 			} else {
441 				self.$syncedswitch.find('.rsw-synced-switch-item').removeClass('showing');
442 				$toShow.addClass('showing');
443 			}
444 		};
445 
446 		this.genderSwitch = function(gender){
447 			var $gens = self.$syncedswitch.find('.render-m, .render-f');
448 			var srch = '.render-'+gender;
449 			if ($gens.length) {
450 				$gens.each(function(i,e){
451 					var $e = $(e);
452 					if ($e.is(srch)) {
453 						$e.removeClass('gender-render-hidden').addClass('gender-render-showing');
454 					} else {
455 						$e.removeClass('gender-render-showing').addClass('gender-render-hidden');
456 					}
457 				});
458 			}
459 		};
460 		
461 		/* default version - not supported by synced switches, always false */
462 		this.defaultVer = function () { return false; };
463 		
464 		this.isParentOf = function ($triggerer) {
465 			return self.$syncedswitch.find($triggerer).length > 0;
466 		};
467 		
468 		/* init */
469 		mw.log('setting up synced switch, id '+self.index);
470 		// attempt to apply some button text from a SwitchInfobox
471 		if ($('.infobox.infobox-switch').length) {
472 			self.attachedLabels = true;
473 			var $linkedButtonTextInfobox = $('.infobox.infobox-switch').first();
474 			self.$syncedswitch.find('.rsw-synced-switch-item').each(function(i,e){
475 				var $e = $(e);
476 				if ($e.attr('data-item-text') === undefined) {
477 					$e.attr('data-item-text', $linkedButtonTextInfobox.find('[data-switch-index="'+i+'"]').attr('data-switch-anchor'));
478 				}
479 			});
480 		}
481 		self.switchInfobox(1, '@init@');
482 		window.switchEventManager.addSwitchInfobox(this);
483 		if (self.$syncedswitch.find('.render-m, .render-f').length) {
484 			window.switchEventManager.addGenderRenderSwitch(self);
485 			this.genderSwitch(getGenderFromLS());
486 		}
487 	}
488 
489 	/**
490 	 * Event manager
491 	 * Observer pattern
492 	 * Globally available as window.switchEventManager
493 	 * 
494 	 * Methods
495 	 *	  addSwitchInfobox(l)
496 	 *		  adds switch infobox (of any type) to the list of switch infoboxes listening to trigger events
497 	 *		  l	   switch infobox
498 	 * 
499 	 * 		addPreSwitchEvent(f)
500 	 * 			adds the function to a list of functions that runs when the switch event is triggered but before any other action is taken
501 	 * 			the function is passed the index and anchor (in that order) that was passed to the trigger function
502 	 * 			returning the boolean true from the function will cancel the switch event
503 	 * 			trying to add a non-function is a noop
504 	 * 			e		function to run
505 	 * 
506 	 * 		addPostSwitchEvent(f)
507 	 * 			adds the function to a list of functions that runs when the switch event is completed, after all of the switching is completed (including the hash change)
508 	 * 			the function is passed the index and anchor (in that order) that was passed to the trigger function
509 	 * 			the return value is ignored
510 	 * 			trying to add a non-function is a noop
511 	 * 			e		function to run
512 	 * 
513 	 *	  trigger(i, a)
514 	 *		  triggers the switch event on all listeners
515 	 *		  will prefer switching to the anchor if available
516 	 *		  i	   index to switch to
517 	 *		  a	   anchor to switch to
518 	 * 
519 	 * 		makeSwitchInfobox($box)
520 	 * 			creates the correct object for the passed switch infobox, based on the classes of the infobox
521 	 * 			is a noop if it does not match any of the selectors
522 	 * 			infobox is given an index based on the internal counter for the switch
523 	 * 			$box		jQuery object for the switch infobox (the jQuery object passed to the above functions, see above for selectors checked)
524 	 * 
525 	 * 		addIndex(i)
526 	 * 			updates the internal counter by adding i to it
527 	 * 			if i is not a number or is negative, is a noop
528 	 * 			used for manually setting up infoboxes (init) or creating a new type to plugin
529 	 * 			i	number to add
530 	 */
531 
532 	function SwitchEventManager() {
533 		var self = this, switchInfoboxes = [], genderRenderSwitchers = [], preSwitchEvents = [], postSwitchEvents = [], index = 0;
534 		
535 		// actual switch infoboxes to change
536 		this.addSwitchInfobox = function(l) {
537 			switchInfoboxes.push(l);
538 		};
539 
540 		this.addGenderRenderSwitch = function(gs) {
541 			genderRenderSwitchers.push(gs);
542 		};
543 		
544 		// things to do when switch button is clicked but before any switching
545 		this.addPreSwitchEvent = function(e) {
546 			if (typeof(e) === 'function') {
547 				preSwitchEvents.push(e);
548 			}
549 		};
550 		this.addPostSwitchEvent = function(e) {
551 			if (typeof(e) === 'function') {
552 				postSwitchEvents.push(e);
553 			}
554 		};
555 
556 		this.trigger = function(index, anchor, $triggerer) {
557 			mw.log('Triggering switch event for index '+index+'; text '+anchor);
558 			// using a real for loop so we can use return to exit the trigger function
559 			for (var i=0; i < preSwitchEvents.length; i++){
560 				var ret = preSwitchEvents[i](index,anchor);
561 				if (typeof(ret) === 'boolean') {
562 					if (ret) {
563 						mw.log('switching was cancelled');
564 						return;
565 					}
566 				}
567 			}
568 
569 			// close all tooltips on the page
570 			$('.js-tooltip-wrapper').trigger('js-tooltip-close');
571 
572 			// trigger switching on listeners
573 			switchInfoboxes.forEach(function (e) {
574 				if (!e.isParentOf($triggerer)) {
575 					e.switchInfobox(index, anchor);
576 				}
577 			});
578 
579 			// update hash
580 			if (typeof anchor === 'string') {
581 				var _anchor = anchor;
582 				if (_anchor === '@init@') {
583 					_anchor = '';
584 				}
585 				
586 				if (window.history && window.history.replaceState) {
587 					if (window.location.hash !== '') {
588 						window.history.replaceState({}, '', window.location.href.replace(window.location.hash, _anchor));
589 					} else {
590 						window.history.replaceState({}, '', window.location.href + _anchor);
591 					}
592 				} else {
593 					// replaceState not supported, I guess we just change the hash normally?
594 					window.location.hash = _anchor;
595 				}
596 			}
597 
598 			postSwitchEvents.forEach(function(e){
599 				e(index, anchor);
600 			});
601 		};
602 
603 		this.triggerGenderRenderSwitch = function(gender){
604 			mw.log(genderRenderSwitchers);
605 			for (var i = 0; i<genderRenderSwitchers.length; i++) {
606 				genderRenderSwitchers[i].genderSwitch(gender);
607 			}
608 		};
609 		
610 		/* attempts to detect what type of switch infobox this is and applies the relevant type */
611 		// mostly for external access
612 		this.makeSwitchInfobox = function($e) {
613 			if ($e.is('.infobox-switch')) {
614 				return new SwitchInfobox($e, index++);
615 			}
616 			if ($e.hasClass('switch-infobox')) {
617 				return new LegacySwitchInfobox($e, index++);
618 			}
619 			if ($e.hasClass('rsw-synced-switch')) {
620 				return new SyncedSwitch($e, index++);
621 			}
622 		};
623 		this.addIndex = function(i) {
624 			if (typeof(i) === 'number') {
625 				 i += Math.max(Math.floor(i), 0);
626 			}
627 		};
628 		this.applyDefaultVersion = function() {
629 			if (window.location.hash !== '') {
630 				self.trigger(1, window.location.hash);
631 				return;
632 			} else {
633 			// real for loop so we can return out of the function
634 				for (var i = 0; i<switchInfoboxes.length; i++) {
635 					var defver = switchInfoboxes[i].defaultVer();
636 					if (typeof(defver) === 'object') {
637 						self.trigger(defver.idx, defver.txt);
638 						return;
639 					}
640 				}
641 			}
642 			self.trigger(1, '@init@');
643 		};
644 	}
645 
646 	function init() {
647 		// mirror rsw-util
648 		try {
649 			localStorage.setItem('test', 'test');
650 			localStorage.removeItem('test');
651 			CAN_LOCAL_STORAGE = true;
652 		} catch (e) {
653 			CAN_LOCAL_STORAGE = false;
654 		}
655 		var index = 0;
656 		window.switchEventManager = new SwitchEventManager();
657 		$('.infobox-switch').each(function(i,e){
658 			return new SwitchInfobox($(e), index++);
659 		});
660 		$('.switch-infobox').each(function(i,e){
661 			return new LegacySwitchInfobox($(e), index++);
662 		});
663 		$('.rsw-synced-switch').each(function(i,e){
664 			return new SyncedSwitch($(e), index++);
665 		});
666 		window.switchEventManager.addIndex(index);
667 				// reinitialize any kartographer map frames added due to a switch
668 		if ($('.infobox-switch .mw-kartographer-map').length
669          || $('.infobox-switch-resources .mw-kartographer-map').length
670          || $('.switch-infobox .mw-kartographer-map').length
671          || $('.rsw-synced-switch .mw-kartographer-map').length) {
672 			window.switchEventManager.addPostSwitchEvent(function() {
673 				mw.hook('wikipage.content').fire($('a.mw-kartographer-map').parent());
674 			});
675 		}
676 		
677 		window.switchEventManager.applyDefaultVersion();
678 	}
679 
680 	$(init);
681 })
682 // </nowiki>