Prototype und Scriptaculous sind in Kombination eine der beliebtesten Javascript-Bibliotheken, die bei Web-Anwendungen zum Einsatz kommen, um auf dem Client auf einfache Weise und trotzdem Cross-Browser fähig u.a. Effekte darzustellen, mit XHTML Requests zu arbeiten oder DOM-Manipulationen vorzunehemen.
Doch vor allem die Scriptacoulous-Bibliothek ist an einigen Stellen noch nicht ausgereift. Ein Beispiel dafür ist der Autocompleter, der Vorschläge für ein Textfeld anhand des bereits eingegeben Textes in einer Liste anzeigt. Eigentlich sollte das Fenster, wenn sich die Liste am unteren Rand befindet (Dokument ist länger als der Viewport) und das nächste Element nicht mehr im sichtbaren Bereich liegt, nach unten scrollen.
Da Browser im (Quirks-Mode) von Prototype aber grundsätzlich nicht unterstützt werden, funktioniert die Berechnung, wann gescrollt werden muss in diesem Fall nicht. Darüberhinaus exisitieren weitere bekannte Probleme mit dem Scrollen unabhängig vom verwendeten Browser-Mode (siehe Ticket für das Autocompleter Scroll Problem). Die in diesem Zusammenhang existierenden Patches sind jedoch auf den Strict-Mode zugeschnitten, funktionieren nicht zuverlässig für alle Browser und bieten zudem keine Lösung für einen weiteren Bug. Dieser ist dabei eigentlich mehr eine Verkettung von unglücklichen Umständen: Er tritt auf, wenn sich das Textfeld am unteren Rand des Fensters befindet und der Anwender mit der Maus hinein klickt, um – nach einer Texteingabe – aus der erscheinenden Liste eine Eintrag zu selektieren (über die Pfeiltasten). Läuft alles korrekt, wird das Fenster nach unten gescrollt und der nächste Eintrag selektiert. Da sich die Maus aber weiterhin an der gleichen Position (relativ zum Fenster) befindet, löst sie einen Mouseover-Event über dem zuvor selektierten Eintrag aus. Das Resultat dieses Verhaltens ist, dass die Liste beim Selektieren scheinbar hin und her springt bzw. das Selektieren teilweise gänzlich unmöglich ist.
Um für ein konkretes Projekt, dass mit Browsern im Quirks-Modus arbeitet, diese Probleme zu beseitigen wurde folgender Patch entwickelt. Er wurde getestet für Protototype Version 1.6 und Scriptaculous 1.8.1 und liegt als Monkey-Patch vor. Für Browser im Strict-Modus muss lediglich die Berechnung der Höhe des Viewport in der Funktion scrollIntoViewDown entsprechend des Kommentares geändert werden.
Der Patch berechnet die Höhe des Viewports korrekt und scrolled dementsprechend nur dann nach oben oder nach unten. Etwaige ungewollte Maus-Events nach einem nötigen – durch den Autocompleter hervorgerufenen – Scrollen werden einfach ignoriert.
Object.extend(Autocompleter.Base.prototype, {
baseInitialize : function(element, update, options) {
this.superBaseInitialize(element, update, options);
this.ignoreHoverEvent = false;
},
superBaseInitialize :Autocompleter.Base.prototype.baseInitialize,
scrollIntoViewUp : function() {
var entry = this.getEntry(this.index);
if (entry.viewportOffset().top < 0) {
// only scroll if entry out of bounds
entry.scrollIntoView(true);
// ignore next mouse event to avoid marking of entry
// after scrolling if mouse is over list
this.ignoreHoverEvent = true;
// make sure following mouse events are not ignored if mouse is
// not over list
setTimeout(this.resetIgnoreHover, 500);
}
},
scrollIntoViewDown : function() {
var entry = this.getEntry(this.index);
// get viewport height (most browsers, ie + mozilla in quirks mode)
// (for strict mode use document.viewport.getHeight()
var windowHeight = window.innerHeight || document.body.clientHeight;
// get y value of selected entry and add entry height to get lower bounds
// y value
var currentHeight = entry.viewportOffset().top + entry.getHeight();
if (windowHeight - currentHeight < 0) {
// only scroll if entry out of bounds
this.getEntry(this.index).scrollIntoView(false);
// ignore next mouse event to avoid marking of entry
// after scrolling if mouse is over list
this.ignoreHoverEvent = true;
// make sure following mouse events are not ignored if mouse is
// not over list
setTimeout(this.resetIgnoreHover, 500);
}
},
markPrevious : function() {
if (this.index > 0) {
this.index--
// scroll up if list is stepped up
this.scrollIntoViewUp();
} else {
this.index = this.entryCount - 1;
// scroll down on jump to last entry
this.scrollIntoViewDown();
}
},
markNext : function() {
if (this.index < this.entryCount - 1) {
this.index++
// scroll down if list is stepped down
this.scrollIntoViewDown();
} else {
this.index = 0;
// scroll up on jump to first entry
this.scrollIntoViewUp();
}
},
resetIgnoreHover : function() {
this.ignoreHoverEvent = false;
},
onHover : function(event) {
if (!this.ignoreHoverEvent) {
var element = Event.findElement(event, 'LI');
if (this.index != element.autocompleteIndex) {
this.index = element.autocompleteIndex;
this.render();
}
} else {
this.ignoreHoverEvent = false;
}
Event.stop(event);
}
});
Tags: Autocompleter, Javascript, Prototype, Scriptaculous