Subversion Repositories Sites.obs-saisons.fr

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
40 aurelien 1
/*
2
 * jQuery UI Autocomplete 1.8.5
3
 *
4
 * Copyright 2010, 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/Autocomplete
9
 *
10
 * Depends:
11
 *	jquery.ui.core.js
12
 *	jquery.ui.widget.js
13
 *	jquery.ui.position.js
14
 */
15
(function( $, undefined ) {
16
 
17
$.widget( "ui.autocomplete", {
18
	options: {
19
		appendTo: "body",
20
		delay: 300,
21
		minLength: 1,
22
		position: {
23
			my: "left top",
24
			at: "left bottom",
25
			collision: "none"
26
		},
27
		source: null
28
	},
29
	_create: function() {
30
		var self = this,
31
			doc = this.element[ 0 ].ownerDocument;
32
		this.element
33
			.addClass( "ui-autocomplete-input" )
34
			.attr( "autocomplete", "off" )
35
			// TODO verify these actually work as intended
36
			.attr({
37
				role: "textbox",
38
				"aria-autocomplete": "list",
39
				"aria-haspopup": "true"
40
			})
41
			.bind( "keydown.autocomplete", function( event ) {
42
				if ( self.options.disabled ) {
43
					return;
44
				}
45
 
46
				var keyCode = $.ui.keyCode;
47
				switch( event.keyCode ) {
48
				case keyCode.PAGE_UP:
49
					self._move( "previousPage", event );
50
					break;
51
				case keyCode.PAGE_DOWN:
52
					self._move( "nextPage", event );
53
					break;
54
				case keyCode.UP:
55
					self._move( "previous", event );
56
					// prevent moving cursor to beginning of text field in some browsers
57
					event.preventDefault();
58
					break;
59
				case keyCode.DOWN:
60
					self._move( "next", event );
61
					// prevent moving cursor to end of text field in some browsers
62
					event.preventDefault();
63
					break;
64
				case keyCode.ENTER:
65
				case keyCode.NUMPAD_ENTER:
66
					// when menu is open or has focus
67
					if ( self.menu.element.is( ":visible" ) ) {
68
						event.preventDefault();
69
					}
70
					//passthrough - ENTER and TAB both select the current element
71
				case keyCode.TAB:
72
					if ( !self.menu.active ) {
73
						return;
74
					}
75
					self.menu.select( event );
76
					break;
77
				case keyCode.ESCAPE:
78
					self.element.val( self.term );
79
					self.close( event );
80
					break;
81
				default:
82
					// keypress is triggered before the input value is changed
83
					clearTimeout( self.searching );
84
					self.searching = setTimeout(function() {
85
						// only search if the value has changed
86
						if ( self.term != self.element.val() ) {
87
							self.selectedItem = null;
88
							self.search( null, event );
89
						}
90
					}, self.options.delay );
91
					break;
92
				}
93
			})
94
			.bind( "focus.autocomplete", function() {
95
				if ( self.options.disabled ) {
96
					return;
97
				}
98
 
99
				self.selectedItem = null;
100
				self.previous = self.element.val();
101
			})
102
			.bind( "blur.autocomplete", function( event ) {
103
				if ( self.options.disabled ) {
104
					return;
105
				}
106
 
107
				clearTimeout( self.searching );
108
				// clicks on the menu (or a button to trigger a search) will cause a blur event
109
				self.closing = setTimeout(function() {
110
					self.close( event );
111
					self._change( event );
112
				}, 150 );
113
			});
114
		this._initSource();
115
		this.response = function() {
116
			return self._response.apply( self, arguments );
117
		};
118
		this.menu = $( "<ul></ul>" )
119
			.addClass( "ui-autocomplete" )
120
			.appendTo( $( this.options.appendTo || "body", doc )[0] )
121
			// prevent the close-on-blur in case of a "slow" click on the menu (long mousedown)
122
			.mousedown(function( event ) {
123
				// clicking on the scrollbar causes focus to shift to the body
124
				// but we can't detect a mouseup or a click immediately afterward
125
				// so we have to track the next mousedown and close the menu if
126
				// the user clicks somewhere outside of the autocomplete
127
				var menuElement = self.menu.element[ 0 ];
128
				if ( event.target === menuElement ) {
129
					setTimeout(function() {
130
						$( document ).one( 'mousedown', function( event ) {
131
							if ( event.target !== self.element[ 0 ] &&
132
								event.target !== menuElement &&
133
								!$.ui.contains( menuElement, event.target ) ) {
134
								self.close();
135
							}
136
						});
137
					}, 1 );
138
				}
139
 
140
				// use another timeout to make sure the blur-event-handler on the input was already triggered
141
				setTimeout(function() {
142
					clearTimeout( self.closing );
143
				}, 13);
144
			})
145
			.menu({
146
				focus: function( event, ui ) {
147
					var item = ui.item.data( "item.autocomplete" );
148
					if ( false !== self._trigger( "focus", null, { item: item } ) ) {
149
						// use value to match what will end up in the input, if it was a key event
150
						if ( /^key/.test(event.originalEvent.type) ) {
151
							self.element.val( item.value );
152
						}
153
					}
154
				},
155
				selected: function( event, ui ) {
156
					var item = ui.item.data( "item.autocomplete" ),
157
						previous = self.previous;
158
 
159
					// only trigger when focus was lost (click on menu)
160
					if ( self.element[0] !== doc.activeElement ) {
161
						self.element.focus();
162
						self.previous = previous;
163
					}
164
 
165
					if ( false !== self._trigger( "select", event, { item: item } ) ) {
166
						self.term = item.value;
167
						self.element.val( item.value );
168
					}
169
 
170
					self.close( event );
171
					self.selectedItem = item;
172
				},
173
				blur: function( event, ui ) {
174
					// don't set the value of the text field if it's already correct
175
					// this prevents moving the cursor unnecessarily
176
					if ( self.menu.element.is(":visible") &&
177
						( self.element.val() !== self.term ) ) {
178
						self.element.val( self.term );
179
					}
180
				}
181
			})
182
			.zIndex( this.element.zIndex() + 1 )
183
			// workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781
184
			.css({ top: 0, left: 0 })
185
			.hide()
186
			.data( "menu" );
187
		if ( $.fn.bgiframe ) {
188
			 this.menu.element.bgiframe();
189
		}
190
	},
191
 
192
	destroy: function() {
193
		this.element
194
			.removeClass( "ui-autocomplete-input" )
195
			.removeAttr( "autocomplete" )
196
			.removeAttr( "role" )
197
			.removeAttr( "aria-autocomplete" )
198
			.removeAttr( "aria-haspopup" );
199
		this.menu.element.remove();
200
		$.Widget.prototype.destroy.call( this );
201
	},
202
 
203
	_setOption: function( key, value ) {
204
		$.Widget.prototype._setOption.apply( this, arguments );
205
		if ( key === "source" ) {
206
			this._initSource();
207
		}
208
		if ( key === "appendTo" ) {
209
			this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] )
210
		}
211
	},
212
 
213
	_initSource: function() {
214
		var self = this,
215
			array,
216
			url;
217
		if ( $.isArray(this.options.source) ) {
218
			array = this.options.source;
219
			this.source = function( request, response ) {
220
				response( $.ui.autocomplete.filter(array, request.term) );
221
			};
222
		} else if ( typeof this.options.source === "string" ) {
223
			url = this.options.source;
224
			this.source = function( request, response ) {
225
				if (self.xhr) {
226
					self.xhr.abort();
227
				}
228
				self.xhr = $.getJSON( url, request, function( data, status, xhr ) {
229
					if ( xhr === self.xhr ) {
230
						response( data );
231
					}
232
					self.xhr = null;
233
				});
234
			};
235
		} else {
236
			this.source = this.options.source;
237
		}
238
	},
239
 
240
	search: function( value, event ) {
241
		value = value != null ? value : this.element.val();
242
 
243
		// always save the actual value, not the one passed as an argument
244
		this.term = this.element.val();
245
 
246
		if ( value.length < this.options.minLength ) {
247
			return this.close( event );
248
		}
249
 
250
		clearTimeout( this.closing );
251
		if ( this._trigger("search") === false ) {
252
			return;
253
		}
254
 
255
		return this._search( value );
256
	},
257
 
258
	_search: function( value ) {
259
		this.element.addClass( "ui-autocomplete-loading" );
260
 
261
		this.source( { term: value }, this.response );
262
	},
263
 
264
	_response: function( content ) {
265
		if ( content.length ) {
266
			content = this._normalize( content );
267
			this._suggest( content );
268
			this._trigger( "open" );
269
		} else {
270
			this.close();
271
		}
272
		this.element.removeClass( "ui-autocomplete-loading" );
273
	},
274
 
275
	close: function( event ) {
276
		clearTimeout( this.closing );
277
		if ( this.menu.element.is(":visible") ) {
278
			this._trigger( "close", event );
279
			this.menu.element.hide();
280
			this.menu.deactivate();
281
		}
282
	},
283
 
284
	_change: function( event ) {
285
		if ( this.previous !== this.element.val() ) {
286
			this._trigger( "change", event, { item: this.selectedItem } );
287
		}
288
	},
289
 
290
	_normalize: function( items ) {
291
		// assume all items have the right format when the first item is complete
292
		if ( items.length && items[0].label && items[0].value ) {
293
			return items;
294
		}
295
		return $.map( items, function(item) {
296
			if ( typeof item === "string" ) {
297
				return {
298
					label: item,
299
					value: item
300
				};
301
			}
302
			return $.extend({
303
				label: item.label || item.value,
304
				value: item.value || item.label
305
			}, item );
306
		});
307
	},
308
 
309
	_suggest: function( items ) {
310
		var ul = this.menu.element
311
				.empty()
312
				.zIndex( this.element.zIndex() + 1 ),
313
			menuWidth,
314
			textWidth;
315
		this._renderMenu( ul, items );
316
		// TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
317
		this.menu.deactivate();
318
		this.menu.refresh();
319
		this.menu.element.show().position( $.extend({
320
			of: this.element
321
		}, this.options.position ));
322
 
323
		menuWidth = ul.width( "" ).outerWidth();
324
		textWidth = this.element.outerWidth();
325
		ul.outerWidth( Math.max( menuWidth, textWidth ) );
326
	},
327
 
328
	_renderMenu: function( ul, items ) {
329
		var self = this;
330
		$.each( items, function( index, item ) {
331
			self._renderItem( ul, item );
332
		});
333
	},
334
 
335
	_renderItem: function( ul, item) {
336
		return $( "<li></li>" )
337
			.data( "item.autocomplete", item )
338
			.append( $( "<a></a>" ).text( item.label ) )
339
			.appendTo( ul );
340
	},
341
 
342
	_move: function( direction, event ) {
343
		if ( !this.menu.element.is(":visible") ) {
344
			this.search( null, event );
345
			return;
346
		}
347
		if ( this.menu.first() && /^previous/.test(direction) ||
348
				this.menu.last() && /^next/.test(direction) ) {
349
			this.element.val( this.term );
350
			this.menu.deactivate();
351
			return;
352
		}
353
		this.menu[ direction ]( event );
354
	},
355
 
356
	widget: function() {
357
		return this.menu.element;
358
	}
359
});
360
 
361
$.extend( $.ui.autocomplete, {
362
	escapeRegex: function( value ) {
363
		return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
364
	},
365
	filter: function(array, term) {
366
		var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
367
		return $.grep( array, function(value) {
368
			return matcher.test( value.label || value.value || value );
369
		});
370
	}
371
});
372
 
373
}( jQuery ));
374
 
375
/*
376
 * jQuery UI Menu (not officially released)
377
 *
378
 * This widget isn't yet finished and the API is subject to change. We plan to finish
379
 * it for the next release. You're welcome to give it a try anyway and give us feedback,
380
 * as long as you're okay with migrating your code later on. We can help with that, too.
381
 *
382
 * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
383
 * Dual licensed under the MIT or GPL Version 2 licenses.
384
 * http://jquery.org/license
385
 *
386
 * http://docs.jquery.com/UI/Menu
387
 *
388
 * Depends:
389
 *	jquery.ui.core.js
390
 *  jquery.ui.widget.js
391
 */
392
(function($) {
393
 
394
$.widget("ui.menu", {
395
	_create: function() {
396
		var self = this;
397
		this.element
398
			.addClass("ui-menu ui-widget ui-widget-content ui-corner-all")
399
			.attr({
400
				role: "listbox",
401
				"aria-activedescendant": "ui-active-menuitem"
402
			})
403
			.click(function( event ) {
404
				if ( !$( event.target ).closest( ".ui-menu-item a" ).length ) {
405
					return;
406
				}
407
				// temporary
408
				event.preventDefault();
409
				self.select( event );
410
			});
411
		this.refresh();
412
	},
413
 
414
	refresh: function() {
415
		var self = this;
416
 
417
		// don't refresh list items that are already adapted
418
		var items = this.element.children("li:not(.ui-menu-item):has(a)")
419
			.addClass("ui-menu-item")
420
			.attr("role", "menuitem");
421
 
422
		items.children("a")
423
			.addClass("ui-corner-all")
424
			.attr("tabindex", -1)
425
			// mouseenter doesn't work with event delegation
426
			.mouseenter(function( event ) {
427
				self.activate( event, $(this).parent() );
428
			})
429
			.mouseleave(function() {
430
				self.deactivate();
431
			});
432
	},
433
 
434
	activate: function( event, item ) {
435
		this.deactivate();
436
		if (this.hasScroll()) {
437
			var offset = item.offset().top - this.element.offset().top,
438
				scroll = this.element.attr("scrollTop"),
439
				elementHeight = this.element.height();
440
			if (offset < 0) {
441
				this.element.attr("scrollTop", scroll + offset);
442
			} else if (offset >= elementHeight) {
443
				this.element.attr("scrollTop", scroll + offset - elementHeight + item.height());
444
			}
445
		}
446
		this.active = item.eq(0)
447
			.children("a")
448
				.addClass("ui-state-hover")
449
				.attr("id", "ui-active-menuitem")
450
			.end();
451
		this._trigger("focus", event, { item: item });
452
	},
453
 
454
	deactivate: function() {
455
		if (!this.active) { return; }
456
 
457
		this.active.children("a")
458
			.removeClass("ui-state-hover")
459
			.removeAttr("id");
460
		this._trigger("blur");
461
		this.active = null;
462
	},
463
 
464
	next: function(event) {
465
		this.move("next", ".ui-menu-item:first", event);
466
	},
467
 
468
	previous: function(event) {
469
		this.move("prev", ".ui-menu-item:last", event);
470
	},
471
 
472
	first: function() {
473
		return this.active && !this.active.prevAll(".ui-menu-item").length;
474
	},
475
 
476
	last: function() {
477
		return this.active && !this.active.nextAll(".ui-menu-item").length;
478
	},
479
 
480
	move: function(direction, edge, event) {
481
		if (!this.active) {
482
			this.activate(event, this.element.children(edge));
483
			return;
484
		}
485
		var next = this.active[direction + "All"](".ui-menu-item").eq(0);
486
		if (next.length) {
487
			this.activate(event, next);
488
		} else {
489
			this.activate(event, this.element.children(edge));
490
		}
491
	},
492
 
493
	// TODO merge with previousPage
494
	nextPage: function(event) {
495
		if (this.hasScroll()) {
496
			// TODO merge with no-scroll-else
497
			if (!this.active || this.last()) {
498
				this.activate(event, this.element.children(":first"));
499
				return;
500
			}
501
			var base = this.active.offset().top,
502
				height = this.element.height(),
503
				result = this.element.children("li").filter(function() {
504
					var close = $(this).offset().top - base - height + $(this).height();
505
					// TODO improve approximation
506
					return close < 10 && close > -10;
507
				});
508
 
509
			// TODO try to catch this earlier when scrollTop indicates the last page anyway
510
			if (!result.length) {
511
				result = this.element.children(":last");
512
			}
513
			this.activate(event, result);
514
		} else {
515
			this.activate(event, this.element.children(!this.active || this.last() ? ":first" : ":last"));
516
		}
517
	},
518
 
519
	// TODO merge with nextPage
520
	previousPage: function(event) {
521
		if (this.hasScroll()) {
522
			// TODO merge with no-scroll-else
523
			if (!this.active || this.first()) {
524
				this.activate(event, this.element.children(":last"));
525
				return;
526
			}
527
 
528
			var base = this.active.offset().top,
529
				height = this.element.height();
530
				result = this.element.children("li").filter(function() {
531
					var close = $(this).offset().top - base + height - $(this).height();
532
					// TODO improve approximation
533
					return close < 10 && close > -10;
534
				});
535
 
536
			// TODO try to catch this earlier when scrollTop indicates the last page anyway
537
			if (!result.length) {
538
				result = this.element.children(":first");
539
			}
540
			this.activate(event, result);
541
		} else {
542
			this.activate(event, this.element.children(!this.active || this.first() ? ":last" : ":first"));
543
		}
544
	},
545
 
546
	hasScroll: function() {
547
		return this.element.height() < this.element.attr("scrollHeight");
548
	},
549
 
550
	select: function( event ) {
551
		this._trigger("selected", event, { item: this.active });
552
	}
553
});
554
 
555
}(jQuery));