Merge "Special:Preferences: Construct fake tabs to avoid FOUC"
[lhc/web/wiklou.git] / resources / src / mediawiki.special.preferences.ooui / tabs.js
1 /*!
2 * JavaScript for Special:Preferences: Tab navigation.
3 */
4 ( function ( mw, $ ) {
5 $( function () {
6 var $preferences, tabs, wrapper, previousTab;
7
8 $preferences = $( '#preferences' );
9
10 // Make sure the accessibility tip is selectable so that screen reader users take notice,
11 // but hide it per default to reduce interface clutter. Also make sure it becomes visible
12 // when selected. Similar to jquery.mw-jump
13 $( '<div>' ).addClass( 'mw-navigation-hint' )
14 .text( mw.msg( 'prefs-tabs-navigation-hint' ) )
15 .attr( 'tabIndex', 0 )
16 .on( 'focus blur', function ( e ) {
17 if ( e.type === 'blur' || e.type === 'focusout' ) {
18 $( this ).css( 'height', '0' );
19 } else {
20 $( this ).css( 'height', 'auto' );
21 }
22 } ).prependTo( '#mw-content-text' );
23
24 tabs = new OO.ui.IndexLayout( {
25 expanded: false,
26 // Do not remove focus from the tabs menu after choosing a tab
27 autoFocus: false
28 } );
29
30 mw.config.get( 'wgPreferencesTabs' ).forEach( function ( tabConfig ) {
31 var panel, $panelContents;
32
33 panel = new OO.ui.TabPanelLayout( tabConfig.name, {
34 expanded: false,
35 label: tabConfig.label
36 } );
37 $panelContents = $( '#mw-prefsection-' + tabConfig.name );
38
39 // Hide the unnecessary PHP PanelLayouts
40 // (Do not use .remove(), as that would remove event handlers for everything inside them)
41 $panelContents.parent().detach();
42
43 panel.$element.append( $panelContents );
44 tabs.addTabPanels( [ panel ] );
45
46 // Remove duplicate labels
47 // (This must be after .addTabPanels(), otherwise the tab item doesn't exist yet)
48 $panelContents.children( 'legend' ).remove();
49 $panelContents.attr( 'aria-labelledby', panel.getTabItem().getElementId() );
50 } );
51
52 wrapper = new OO.ui.PanelLayout( {
53 expanded: false,
54 padded: false,
55 framed: true
56 } );
57 wrapper.$element.append( tabs.$element );
58 $preferences.prepend( wrapper.$element );
59 $( '.mw-prefs-faketabs' ).remove();
60
61 function updateHash( panel ) {
62 var scrollTop, active;
63 // Handle hash manually to prevent jumping,
64 // therefore save and restore scrollTop to prevent jumping.
65 scrollTop = $( window ).scrollTop();
66 // Changing the hash apparently causes keyboard focus to be lost?
67 // Save and restore it. This makes no sense though.
68 active = document.activeElement;
69 location.hash = '#mw-prefsection-' + panel.getName();
70 if ( active ) {
71 active.focus();
72 }
73 $( window ).scrollTop( scrollTop );
74 }
75
76 tabs.on( 'set', updateHash );
77
78 /**
79 * @ignore
80 * @param {string} name the name of a tab without the prefix ("mw-prefsection-")
81 * @param {string} [mode] A hash will be set according to the current
82 * open section. Set mode 'noHash' to supress this.
83 */
84 function switchPrefTab( name, mode ) {
85 if ( mode === 'noHash' ) {
86 tabs.off( 'set', updateHash );
87 }
88 tabs.setTabPanel( name );
89 if ( mode === 'noHash' ) {
90 tabs.on( 'set', updateHash );
91 }
92 }
93
94 // Jump to correct section as indicated by the hash.
95 // This function is called onload and onhashchange.
96 function detectHash() {
97 var hash = location.hash,
98 matchedElement, parentSection;
99 if ( hash.match( /^#mw-prefsection-[\w]+$/ ) ) {
100 mw.storage.session.remove( 'mwpreferences-prevTab' );
101 switchPrefTab( hash.replace( '#mw-prefsection-', '' ) );
102 } else if ( hash.match( /^#mw-[\w-]+$/ ) ) {
103 matchedElement = document.getElementById( hash.slice( 1 ) );
104 parentSection = $( matchedElement ).parent().closest( '[id^="mw-prefsection-"]' );
105 if ( parentSection.length ) {
106 mw.storage.session.remove( 'mwpreferences-prevTab' );
107 // Switch to proper tab and scroll to selected item.
108 switchPrefTab( parentSection.attr( 'id' ).replace( 'mw-prefsection-', '' ), 'noHash' );
109 matchedElement.scrollIntoView();
110 }
111 }
112 }
113
114 $( window ).on( 'hashchange', function () {
115 var hash = location.hash;
116 if ( hash.match( /^#mw-[\w-]+/ ) ) {
117 detectHash();
118 } else if ( hash === '' ) {
119 switchPrefTab( 'personal', 'noHash' );
120 }
121 } )
122 // Run the function immediately to select the proper tab on startup.
123 .trigger( 'hashchange' );
124
125 // Restore the active tab after saving the preferences
126 previousTab = mw.storage.session.get( 'mwpreferences-prevTab' );
127 if ( previousTab ) {
128 switchPrefTab( previousTab, 'noHash' );
129 // Deleting the key, the tab states should be reset until we press Save
130 mw.storage.session.remove( 'mwpreferences-prevTab' );
131 }
132
133 $( '#mw-prefs-form' ).on( 'submit', function () {
134 var value = tabs.getCurrentTabPanelName();
135 mw.storage.session.set( 'mwpreferences-prevTab', value );
136 } );
137
138 } );
139 }( mediaWiki, jQuery ) );