Archiv für die Kategorie ‘Prototype + Scriptaculous’

Javascript Pitfalls und mehr

Freitag, 15. Mai 2009

Es gibt beim schreiben von javascript so einiges zu beachten, wenn man zuerst für den Firefox entwickelt und anschließend sein javascript im IE bewundern möchte.

Zum Beispiel sollten Kommata vorsichtig gesetzt werden…

var Utils = {
 
	bindFocusSetter: function() {
		$$("input", "textarea", "select").each(
		    function(input) {
		    	this.bindFocusElement(input);
		    }.bind(this)
		);
	},
 
	bindFocusElement: function(input) {
		...
	},
 
	disableDefaultDrag: function(event) {
		...
	},
 }

Während der Firefox dies akzeptiert und die functions problemlos ausführt..
..schaut es im IE anders aus, das Komma hinter der letzten function läßt den IE stutzig werden.

Auch sollte man sich bei der Verwendung von Prototype einiges von Prototype selbst abschaun, z.B. das “prototypifizieren” von Elementen, wie im folgenden Code Block dargestellt.

/**
 * Fuer die Validierung von Inputfeldern.
 */
var Validate = {
	validate: function(regEx,field,message){
		var feld = $(field);
		if(feld){
			var feldlabel;
			if(feld.previous(0)){
...

So ist man mit “var feld = $(field);” auf der sicheren Seite ein Element an der Hand zu haben, auf dem gefahrlos Prototype Funktionen aufgerufen werden können.

Das hereinreichen von Strings per el in eine javascript Funktion kann auch daneben gehen, wenn z.B. Zeilenumbrüche vorhanden sind.

<tr class="active'"
    onclick="showZusatzInfo('${xyItem.inhalt}');"
    style="cursor: pointer;">

Ein Zeilenumbruch “\n” führt hier zu einem Fehler, beim auslösen des onclick Event-Handlers. Abhilfe schafft hier eine einfache JSTL-Function.
Der modifizierte onclick Event-Handler…

<tr class="active''}"
    onclick="showZusatzInfo('${xyItem.inhalt}');"
    style="cursor: pointer;">

Die simple Java-Klasse…

public class Functions {
 
    private Functions() {
    }
 
    public static String unwrap(final String input) {
        String result = null;
 
        if (input != null) {
            result = input.replaceAll("\\r\\n", "\\\\n");
            result = result.replaceAll("\\n\\r", "\\\\n");
            result = result.replaceAll("\\r", "\\\\n");
            result = result.replaceAll("\\n", "\\\\n");
        }
 
        return result;
    }
}

Und noch der TLD-Eintrag…

<function>
    <name>unwrap</name>
    <function-class>de.standard.Functions</function-class>
    <function-signature>
        java.lang.String unwrap(java.lang.String)
    </function-signature>
</function>

Bis zum nächsten Blog-Eintrag…
Sven Seiller


Sven Seiler


Scriptaculous Autocompleter Scroll Bugs

Dienstag, 09. Dezember 2008

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);
}
});

Christian Schätzlein