Accessibility: Make the collapsible sidebar screen reader friendly
[lhc/web/wiklou.git] / skins / vector / collapsibleNav.js
1 /**
2 * Collapsible navigation for Vector
3 */
4 ( function ( mw, $ ) {
5 'use strict';
6 var map;
7
8 // Use the same function for all navigation headings - don't repeat
9 function toggle( $element ) {
10 var isCollapsed = $element.parent().is( '.collapsed' );
11
12 $.cookie(
13 'vector-nav-' + $element.parent().attr( 'id' ),
14 isCollapsed,
15 { 'expires': 30, 'path': '/' }
16 );
17
18 $element
19 .parent()
20 .toggleClass( 'expanded' )
21 .toggleClass( 'collapsed' )
22 .find( '.body' )
23 .slideToggle( 'fast' );
24 isCollapsed = !isCollapsed;
25
26 $element
27 .find( '> a' )
28 .attr( {
29 'aria-pressed': isCollapsed ? 'false' : 'true',
30 'aria-expanded': isCollapsed ? 'false' : 'true'
31 } );
32 }
33
34 /* Browser Support */
35
36 map = {
37 // Left-to-right languages
38 ltr: {
39 // Collapsible Nav is broken in Opera < 9.6 and Konqueror < 4
40 opera: [['>=', 9.6]],
41 konqueror: [['>=', 4.0]],
42 blackberry: false,
43 ipod: false,
44 iphone: false,
45 ps3: false
46 },
47 // Right-to-left languages
48 rtl: {
49 opera: [['>=', 9.6]],
50 konqueror: [['>=', 4.0]],
51 blackberry: false,
52 ipod: false,
53 iphone: false,
54 ps3: false
55 }
56 };
57 if ( !$.client.test( map ) ) {
58 return true;
59 }
60
61 $( function ( $ ) {
62 var $headings, tabIndex;
63
64 /* General Portal Modification */
65
66 // Always show the first portal
67 $( '#mw-panel > .portal:first' ).addClass( 'first persistent' );
68 // Apply a class to the entire panel to activate styles
69 $( '#mw-panel' ).addClass( 'collapsible-nav' );
70 // Use cookie data to restore preferences of what to show and hide
71 $( '#mw-panel > .portal:not(.persistent)' )
72 .each( function ( i ) {
73 var id = $(this).attr( 'id' ),
74 state = $.cookie( 'vector-nav-' + id );
75 $(this).find( 'ul:first' ).attr( 'id', id + '-list' );
76 // Add anchor tag to heading for better accessibility
77 $( this ).find( 'h3' ).wrapInner(
78 $( '<a>' )
79 .attr( {
80 href: '#',
81 'aria-haspopup': 'true',
82 'aria-controls': id + '-list',
83 role: 'button'
84 } )
85 .click( false )
86 );
87 // In the case that we are not showing the new version, let's show the languages by default
88 if (
89 state === 'true' ||
90 ( state === null && i < 1 ) ||
91 ( state === null && id === 'p-lang' )
92 ) {
93 $(this)
94 .addClass( 'expanded' )
95 .removeClass( 'collapsed' )
96 .find( '.body' )
97 .hide() // bug 34450
98 .show();
99 $(this).find( 'h3 > a' )
100 .attr( {
101 'aria-pressed': 'true',
102 'aria-expanded': 'true'
103 } );
104 } else {
105 $(this)
106 .addClass( 'collapsed' )
107 .removeClass( 'expanded' );
108 $(this).find( 'h3 > a' )
109 .attr( {
110 'aria-pressed': 'false',
111 'aria-expanded': 'false'
112 } );
113 }
114 // Re-save cookie
115 if ( state !== null ) {
116 $.cookie( 'vector-nav-' + $(this).attr( 'id' ), state, { 'expires': 30, 'path': '/' } );
117 }
118 } );
119
120 /* Tab Indexing */
121
122 $headings = $( '#mw-panel > .portal:not(.persistent) > h3' );
123
124 // Get the highest tab index
125 tabIndex = $( document ).lastTabIndex() + 1;
126
127 // Fix the search not having a tabindex
128 $( '#searchInput' ).attr( 'tabindex', tabIndex++ );
129
130 // Make it keyboard accessible
131 $headings.attr( 'tabindex', function () {
132 return tabIndex++;
133 });
134
135 // Toggle the selected menu's class and expand or collapse the menu
136 $( '#mw-panel' )
137 .delegate( '.portal:not(.persistent) > h3', 'keydown', function ( e ) {
138 // Make the space and enter keys act as a click
139 if ( e.which === 13 /* Enter */ || e.which === 32 /* Space */ ) {
140 toggle( $(this) );
141 }
142 } )
143 .delegate( '.portal:not(.persistent) > h3', 'mousedown', function ( e ) {
144 if ( e.which !== 3 ) { // Right mouse click
145 toggle( $(this) );
146 $(this).blur();
147 }
148 return false;
149 } );
150 });
151
152 }( mediaWiki, jQuery ) );