Project

General

Profile

1
/*!
2
 * jQuery UI Accordion 1.8.20
3
 *
4
 * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
5
 * Dual licensed under the MIT or GPL Version 2 licenses.
6
 * http://jquery.org/license
7
 *
8
 * http://docs.jquery.com/UI/Accordion
9
 *
10
 * Depends:
11
 *	jquery.ui.core.js
12
 *	jquery.ui.widget.js
13
 */
14
(function( $, undefined ) {
15

    
16
$.widget( "ui.accordion", {
17
	options: {
18
		active: 0,
19
		animated: "slide",
20
		autoHeight: true,
21
		clearStyle: false,
22
		collapsible: false,
23
		event: "click",
24
		fillSpace: false,
25
		header: "> li > :first-child,> :not(li):even",
26
		icons: {
27
			header: "ui-icon-triangle-1-e",
28
			headerSelected: "ui-icon-triangle-1-s"
29
		},
30
		navigation: false,
31
		navigationFilter: function() {
32
			return this.href.toLowerCase() === location.href.toLowerCase();
33
		}
34
	},
35

    
36
	_create: function() {
37
		var self = this,
38
			options = self.options;
39

    
40
		self.running = 0;
41

    
42
		self.element
43
			.addClass( "ui-accordion ui-widget ui-helper-reset" )
44
			// in lack of child-selectors in CSS
45
			// we need to mark top-LIs in a UL-accordion for some IE-fix
46
			.children( "li" )
47
				.addClass( "ui-accordion-li-fix" );
48

    
49
		self.headers = self.element.find( options.header )
50
			.addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" )
51
			.bind( "mouseenter.accordion", function() {
52
				if ( options.disabled ) {
53
					return;
54
				}
55
				$( this ).addClass( "ui-state-hover" );
56
			})
57
			.bind( "mouseleave.accordion", function() {
58
				if ( options.disabled ) {
59
					return;
60
				}
61
				$( this ).removeClass( "ui-state-hover" );
62
			})
63
			.bind( "focus.accordion", function() {
64
				if ( options.disabled ) {
65
					return;
66
				}
67
				$( this ).addClass( "ui-state-focus" );
68
			})
69
			.bind( "blur.accordion", function() {
70
				if ( options.disabled ) {
71
					return;
72
				}
73
				$( this ).removeClass( "ui-state-focus" );
74
			});
75

    
76
		self.headers.next()
77
			.addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" );
78

    
79
		if ( options.navigation ) {
80
			var current = self.element.find( "a" ).filter( options.navigationFilter ).eq( 0 );
81
			if ( current.length ) {
82
				var header = current.closest( ".ui-accordion-header" );
83
				if ( header.length ) {
84
					// anchor within header
85
					self.active = header;
86
				} else {
87
					// anchor within content
88
					self.active = current.closest( ".ui-accordion-content" ).prev();
89
				}
90
			}
91
		}
92

    
93
		self.active = self._findActive( self.active || options.active )
94
			.addClass( "ui-state-default ui-state-active" )
95
			.toggleClass( "ui-corner-all" )
96
			.toggleClass( "ui-corner-top" );
97
		self.active.next().addClass( "ui-accordion-content-active" );
98

    
99
		self._createIcons();
100
		self.resize();
101
		
102
		// ARIA
103
		self.element.attr( "role", "tablist" );
104

    
105
		self.headers
106
			.attr( "role", "tab" )
107
			.bind( "keydown.accordion", function( event ) {
108
				return self._keydown( event );
109
			})
110
			.next()
111
				.attr( "role", "tabpanel" );
112

    
113
		self.headers
114
			.not( self.active || "" )
115
			.attr({
116
				"aria-expanded": "false",
117
				"aria-selected": "false",
118
				tabIndex: -1
119
			})
120
			.next()
121
				.hide();
122

    
123
		// make sure at least one header is in the tab order
124
		if ( !self.active.length ) {
125
			self.headers.eq( 0 ).attr( "tabIndex", 0 );
126
		} else {
127
			self.active
128
				.attr({
129
					"aria-expanded": "true",
130
					"aria-selected": "true",
131
					tabIndex: 0
132
				});
133
		}
134

    
135
		// only need links in tab order for Safari
136
		if ( !$.browser.safari ) {
137
			self.headers.find( "a" ).attr( "tabIndex", -1 );
138
		}
139

    
140
		if ( options.event ) {
141
			self.headers.bind( options.event.split(" ").join(".accordion ") + ".accordion", function(event) {
142
				self._clickHandler.call( self, event, this );
143
				event.preventDefault();
144
			});
145
		}
146
	},
147

    
148
	_createIcons: function() {
149
		var options = this.options;
150
		if ( options.icons ) {
151
			$( "<span></span>" )
152
				.addClass( "ui-icon " + options.icons.header )
153
				.prependTo( this.headers );
154
			this.active.children( ".ui-icon" )
155
				.toggleClass(options.icons.header)
156
				.toggleClass(options.icons.headerSelected);
157
			this.element.addClass( "ui-accordion-icons" );
158
		}
159
	},
160

    
161
	_destroyIcons: function() {
162
		this.headers.children( ".ui-icon" ).remove();
163
		this.element.removeClass( "ui-accordion-icons" );
164
	},
165

    
166
	destroy: function() {
167
		var options = this.options;
168

    
169
		this.element
170
			.removeClass( "ui-accordion ui-widget ui-helper-reset" )
171
			.removeAttr( "role" );
172

    
173
		this.headers
174
			.unbind( ".accordion" )
175
			.removeClass( "ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
176
			.removeAttr( "role" )
177
			.removeAttr( "aria-expanded" )
178
			.removeAttr( "aria-selected" )
179
			.removeAttr( "tabIndex" );
180

    
181
		this.headers.find( "a" ).removeAttr( "tabIndex" );
182
		this._destroyIcons();
183
		var contents = this.headers.next()
184
			.css( "display", "" )
185
			.removeAttr( "role" )
186
			.removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled" );
187
		if ( options.autoHeight || options.fillHeight ) {
188
			contents.css( "height", "" );
189
		}
190

    
191
		return $.Widget.prototype.destroy.call( this );
192
	},
193

    
194
	_setOption: function( key, value ) {
195
		$.Widget.prototype._setOption.apply( this, arguments );
196
			
197
		if ( key == "active" ) {
198
			this.activate( value );
199
		}
200
		if ( key == "icons" ) {
201
			this._destroyIcons();
202
			if ( value ) {
203
				this._createIcons();
204
			}
205
		}
206
		// #5332 - opacity doesn't cascade to positioned elements in IE
207
		// so we need to add the disabled class to the headers and panels
208
		if ( key == "disabled" ) {
209
			this.headers.add(this.headers.next())
210
				[ value ? "addClass" : "removeClass" ](
211
					"ui-accordion-disabled ui-state-disabled" );
212
		}
213
	},
214

    
215
	_keydown: function( event ) {
216
		if ( this.options.disabled || event.altKey || event.ctrlKey ) {
217
			return;
218
		}
219

    
220
		var keyCode = $.ui.keyCode,
221
			length = this.headers.length,
222
			currentIndex = this.headers.index( event.target ),
223
			toFocus = false;
224

    
225
		switch ( event.keyCode ) {
226
			case keyCode.RIGHT:
227
			case keyCode.DOWN:
228
				toFocus = this.headers[ ( currentIndex + 1 ) % length ];
229
				break;
230
			case keyCode.LEFT:
231
			case keyCode.UP:
232
				toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
233
				break;
234
			case keyCode.SPACE:
235
			case keyCode.ENTER:
236
				this._clickHandler( { target: event.target }, event.target );
237
				event.preventDefault();
238
		}
239

    
240
		if ( toFocus ) {
241
			$( event.target ).attr( "tabIndex", -1 );
242
			$( toFocus ).attr( "tabIndex", 0 );
243
			toFocus.focus();
244
			return false;
245
		}
246

    
247
		return true;
248
	},
249

    
250
	resize: function() {
251
		var options = this.options,
252
			maxHeight;
253

    
254
		if ( options.fillSpace ) {
255
			if ( $.browser.msie ) {
256
				var defOverflow = this.element.parent().css( "overflow" );
257
				this.element.parent().css( "overflow", "hidden");
258
			}
259
			maxHeight = this.element.parent().height();
260
			if ($.browser.msie) {
261
				this.element.parent().css( "overflow", defOverflow );
262
			}
263

    
264
			this.headers.each(function() {
265
				maxHeight -= $( this ).outerHeight( true );
266
			});
267

    
268
			this.headers.next()
269
				.each(function() {
270
					$( this ).height( Math.max( 0, maxHeight -
271
						$( this ).innerHeight() + $( this ).height() ) );
272
				})
273
				.css( "overflow", "auto" );
274
		} else if ( options.autoHeight ) {
275
			maxHeight = 0;
276
			this.headers.next()
277
				.each(function() {
278
					maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
279
				})
280
				.height( maxHeight );
281
		}
282

    
283
		return this;
284
	},
285

    
286
	activate: function( index ) {
287
		// TODO this gets called on init, changing the option without an explicit call for that
288
		this.options.active = index;
289
		// call clickHandler with custom event
290
		var active = this._findActive( index )[ 0 ];
291
		this._clickHandler( { target: active }, active );
292

    
293
		return this;
294
	},
295

    
296
	_findActive: function( selector ) {
297
		return selector
298
			? typeof selector === "number"
299
				? this.headers.filter( ":eq(" + selector + ")" )
300
				: this.headers.not( this.headers.not( selector ) )
301
			: selector === false
302
				? $( [] )
303
				: this.headers.filter( ":eq(0)" );
304
	},
305

    
306
	// TODO isn't event.target enough? why the separate target argument?
307
	_clickHandler: function( event, target ) {
308
		var options = this.options;
309
		if ( options.disabled ) {
310
			return;
311
		}
312

    
313
		// called only when using activate(false) to close all parts programmatically
314
		if ( !event.target ) {
315
			if ( !options.collapsible ) {
316
				return;
317
			}
318
			this.active
319
				.removeClass( "ui-state-active ui-corner-top" )
320
				.addClass( "ui-state-default ui-corner-all" )
321
				.children( ".ui-icon" )
322
					.removeClass( options.icons.headerSelected )
323
					.addClass( options.icons.header );
324
			this.active.next().addClass( "ui-accordion-content-active" );
325
			var toHide = this.active.next(),
326
				data = {
327
					options: options,
328
					newHeader: $( [] ),
329
					oldHeader: options.active,
330
					newContent: $( [] ),
331
					oldContent: toHide
332
				},
333
				toShow = ( this.active = $( [] ) );
334
			this._toggle( toShow, toHide, data );
335
			return;
336
		}
337

    
338
		// get the click target
339
		var clicked = $( event.currentTarget || target ),
340
			clickedIsActive = clicked[0] === this.active[0];
341

    
342
		// TODO the option is changed, is that correct?
343
		// TODO if it is correct, shouldn't that happen after determining that the click is valid?
344
		options.active = options.collapsible && clickedIsActive ?
345
			false :
346
			this.headers.index( clicked );
347

    
348
		// if animations are still active, or the active header is the target, ignore click
349
		if ( this.running || ( !options.collapsible && clickedIsActive ) ) {
350
			return;
351
		}
352

    
353
		// find elements to show and hide
354
		var active = this.active,
355
			toShow = clicked.next(),
356
			toHide = this.active.next(),
357
			data = {
358
				options: options,
359
				newHeader: clickedIsActive && options.collapsible ? $([]) : clicked,
360
				oldHeader: this.active,
361
				newContent: clickedIsActive && options.collapsible ? $([]) : toShow,
362
				oldContent: toHide
363
			},
364
			down = this.headers.index( this.active[0] ) > this.headers.index( clicked[0] );
365

    
366
		// when the call to ._toggle() comes after the class changes
367
		// it causes a very odd bug in IE 8 (see #6720)
368
		this.active = clickedIsActive ? $([]) : clicked;
369
		this._toggle( toShow, toHide, data, clickedIsActive, down );
370

    
371
		// switch classes
372
		active
373
			.removeClass( "ui-state-active ui-corner-top" )
374
			.addClass( "ui-state-default ui-corner-all" )
375
			.children( ".ui-icon" )
376
				.removeClass( options.icons.headerSelected )
377
				.addClass( options.icons.header );
378
		if ( !clickedIsActive ) {
379
			clicked
380
				.removeClass( "ui-state-default ui-corner-all" )
381
				.addClass( "ui-state-active ui-corner-top" )
382
				.children( ".ui-icon" )
383
					.removeClass( options.icons.header )
384
					.addClass( options.icons.headerSelected );
385
			clicked
386
				.next()
387
				.addClass( "ui-accordion-content-active" );
388
		}
389

    
390
		return;
391
	},
392

    
393
	_toggle: function( toShow, toHide, data, clickedIsActive, down ) {
394
		var self = this,
395
			options = self.options;
396

    
397
		self.toShow = toShow;
398
		self.toHide = toHide;
399
		self.data = data;
400

    
401
		var complete = function() {
402
			if ( !self ) {
403
				return;
404
			}
405
			return self._completed.apply( self, arguments );
406
		};
407

    
408
		// trigger changestart event
409
		self._trigger( "changestart", null, self.data );
410

    
411
		// count elements to animate
412
		self.running = toHide.size() === 0 ? toShow.size() : toHide.size();
413

    
414
		if ( options.animated ) {
415
			var animOptions = {};
416

    
417
			if ( options.collapsible && clickedIsActive ) {
418
				animOptions = {
419
					toShow: $( [] ),
420
					toHide: toHide,
421
					complete: complete,
422
					down: down,
423
					autoHeight: options.autoHeight || options.fillSpace
424
				};
425
			} else {
426
				animOptions = {
427
					toShow: toShow,
428
					toHide: toHide,
429
					complete: complete,
430
					down: down,
431
					autoHeight: options.autoHeight || options.fillSpace
432
				};
433
			}
434

    
435
			if ( !options.proxied ) {
436
				options.proxied = options.animated;
437
			}
438

    
439
			if ( !options.proxiedDuration ) {
440
				options.proxiedDuration = options.duration;
441
			}
442

    
443
			options.animated = $.isFunction( options.proxied ) ?
444
				options.proxied( animOptions ) :
445
				options.proxied;
446

    
447
			options.duration = $.isFunction( options.proxiedDuration ) ?
448
				options.proxiedDuration( animOptions ) :
449
				options.proxiedDuration;
450

    
451
			var animations = $.ui.accordion.animations,
452
				duration = options.duration,
453
				easing = options.animated;
454

    
455
			if ( easing && !animations[ easing ] && !$.easing[ easing ] ) {
456
				easing = "slide";
457
			}
458
			if ( !animations[ easing ] ) {
459
				animations[ easing ] = function( options ) {
460
					this.slide( options, {
461
						easing: easing,
462
						duration: duration || 700
463
					});
464
				};
465
			}
466

    
467
			animations[ easing ]( animOptions );
468
		} else {
469
			if ( options.collapsible && clickedIsActive ) {
470
				toShow.toggle();
471
			} else {
472
				toHide.hide();
473
				toShow.show();
474
			}
475

    
476
			complete( true );
477
		}
478

    
479
		// TODO assert that the blur and focus triggers are really necessary, remove otherwise
480
		toHide.prev()
481
			.attr({
482
				"aria-expanded": "false",
483
				"aria-selected": "false",
484
				tabIndex: -1
485
			})
486
			.blur();
487
		toShow.prev()
488
			.attr({
489
				"aria-expanded": "true",
490
				"aria-selected": "true",
491
				tabIndex: 0
492
			})
493
			.focus();
494
	},
495

    
496
	_completed: function( cancel ) {
497
		this.running = cancel ? 0 : --this.running;
498
		if ( this.running ) {
499
			return;
500
		}
501

    
502
		if ( this.options.clearStyle ) {
503
			this.toShow.add( this.toHide ).css({
504
				height: "",
505
				overflow: ""
506
			});
507
		}
508

    
509
		// other classes are removed before the animation; this one needs to stay until completed
510
		this.toHide.removeClass( "ui-accordion-content-active" );
511
		// Work around for rendering bug in IE (#5421)
512
		if ( this.toHide.length ) {
513
			this.toHide.parent()[0].className = this.toHide.parent()[0].className;
514
		}
515

    
516
		this._trigger( "change", null, this.data );
517
	}
518
});
519

    
520
$.extend( $.ui.accordion, {
521
	version: "1.8.20",
522
	animations: {
523
		slide: function( options, additions ) {
524
			options = $.extend({
525
				easing: "swing",
526
				duration: 300
527
			}, options, additions );
528
			if ( !options.toHide.size() ) {
529
				options.toShow.animate({
530
					height: "show",
531
					paddingTop: "show",
532
					paddingBottom: "show"
533
				}, options );
534
				return;
535
			}
536
			if ( !options.toShow.size() ) {
537
				options.toHide.animate({
538
					height: "hide",
539
					paddingTop: "hide",
540
					paddingBottom: "hide"
541
				}, options );
542
				return;
543
			}
544
			var overflow = options.toShow.css( "overflow" ),
545
				percentDone = 0,
546
				showProps = {},
547
				hideProps = {},
548
				fxAttrs = [ "height", "paddingTop", "paddingBottom" ],
549
				originalWidth;
550
			// fix width before calculating height of hidden element
551
			var s = options.toShow;
552
			originalWidth = s[0].style.width;
553
			s.width( s.parent().width()
554
				- parseFloat( s.css( "paddingLeft" ) )
555
				- parseFloat( s.css( "paddingRight" ) )
556
				- ( parseFloat( s.css( "borderLeftWidth" ) ) || 0 )
557
				- ( parseFloat( s.css( "borderRightWidth" ) ) || 0 ) );
558

    
559
			$.each( fxAttrs, function( i, prop ) {
560
				hideProps[ prop ] = "hide";
561

    
562
				var parts = ( "" + $.css( options.toShow[0], prop ) ).match( /^([\d+-.]+)(.*)$/ );
563
				showProps[ prop ] = {
564
					value: parts[ 1 ],
565
					unit: parts[ 2 ] || "px"
566
				};
567
			});
568
			options.toShow.css({ height: 0, overflow: "hidden" }).show();
569
			options.toHide
570
				.filter( ":hidden" )
571
					.each( options.complete )
572
				.end()
573
				.filter( ":visible" )
574
				.animate( hideProps, {
575
				step: function( now, settings ) {
576
					// only calculate the percent when animating height
577
					// IE gets very inconsistent results when animating elements
578
					// with small values, which is common for padding
579
					if ( settings.prop == "height" ) {
580
						percentDone = ( settings.end - settings.start === 0 ) ? 0 :
581
							( settings.now - settings.start ) / ( settings.end - settings.start );
582
					}
583

    
584
					options.toShow[ 0 ].style[ settings.prop ] =
585
						( percentDone * showProps[ settings.prop ].value )
586
						+ showProps[ settings.prop ].unit;
587
				},
588
				duration: options.duration,
589
				easing: options.easing,
590
				complete: function() {
591
					if ( !options.autoHeight ) {
592
						options.toShow.css( "height", "" );
593
					}
594
					options.toShow.css({
595
						width: originalWidth,
596
						overflow: overflow
597
					});
598
					options.complete();
599
				}
600
			});
601
		},
602
		bounceslide: function( options ) {
603
			this.slide( options, {
604
				easing: options.down ? "easeOutBounce" : "swing",
605
				duration: options.down ? 1000 : 200
606
			});
607
		}
608
	}
609
});
610

    
611
})( jQuery );
(16-16/32)