Mit ‘Prototype’ getaggte Artikel

JavaScript: Tastatureingaben mit prototype auswerten

Montag, 15. März 2010

Im folgenden Abschnitt möchte ich anhand des Beispiels einer Mehrfachauswahl von Eingabefeldern erläutern, wie einfach es ist, in JavaScript einen Tastendruck auszuwerten.
Ich verwende hier die prototype Bibliothek. Für alle, die prototype nicht kennen, ganz kurz:
$(’name’) entspricht der Methode getElementById(’name’)

var KeyEvent = {
   ctrl: false,
   keyCode: 0,
   element: null,
 
   /**
    * Am uebergebenen Element z.B. einem div Container oder document selbst
    * werden die Methoden fuer die Events keydown und keyup registriert.
    *
    * element: HTML-Element, an dem die Events registriert werden sollen
    * z.B. Tabelle oder Div.
    */
   init: function(element) {
      KeyEvent.element = element;
      // Taste gedrueckt
      Event.observe(KeyEvent.element, 'keydown', KeyEvent.registerDown);
      // Taste wieder losgelassen
      Event.observe(KeyEvent.element, 'keyup', KeyEvent.registerUp);
   },
 
   /**
    * Function, die auf den keydown event reagiert
    */	
   registerDown: function(event) {
      // falls die Taste gedrueckt bleibt, sollen keine weiteren 
      // keydown events verarbeitet werden
      KeyEvent.element.stopObserving('keydown');
      KeyEvent.keyCode = event.keyCode;
      if (event.ctrlKey) {
         KeyEvent.ctrl = true;
      }
   },
 
    /**
     * Function, die auf den keyup event reagiert
     */
   registerUp: function(event) {
      KeyEvent.ctrl = false;
      // wird die Taste losgelassen, starten wir den Observer fuer keydown wieder
      Event.observe(KeyEvent.element, 'keydown', KeyEvent.registerDown);
   }
};
 
/**
 * Wird beim onfocus des Eingabefeldes aufgerufen
 */
function selectField(element) {
   if (KeyEvent.ctrl) {
      // multiselect ... z.B. könnte man hier das Feld mit einer CSS class markieren.
   }
}

Im HTML könnte das dann wie folgt verwendet werden:

<body onload="KeyEvent.init($('content'))">
 
   <div id="content">
      <form ....>
         <input type="text" name="f1" onfocus="selectField(this)" />
         <input type="text" name="f2" onfocus="selectField(this)" />
         ...
      </form>
   </div>
</body>

Das obige Beispiel verwendet jetzt nur den speziellen event.ctrlKey. Eine denkbare Erweiterung wäre z.B. die Shift-Taste auzuwerten um die Felder von – bis zu markieren. Natürlich könnte man auch beliebige andere Tasten auswerten, dafür muss aber dann der numerische keyCode verwendet werden.

Mehr Infos zur Verwendung von Prototype finden sie unter http://prototypejs.org


Christof Aenderl


Single und Double-Clicks auf einem HTML-Element mit Prototype

Freitag, 24. Juli 2009

Möchte man auf einem HTML-Element (bzw. DOM-Element) einen einfachen und einen Doppel-Klick registrieren und
jeweils unterschiedliche Aktionen ausführen, steht man schnell vor einem Problem. Es gibt zwar ‘onclick’ und ‘ondblclick’ jedoch
löst ein Doppel-Klick immer auch einen einfachen Klick mit aus. Die Lösung besteht darin, nach einem einfachen Klick ein definierte Zeit zu warten und erst, wenn in dieser Zeit kein Doppel-Klick Event aufgetreten ist, die Aktion für den einfachen Klick wirklich auszuführen.
Tritt in dieser Zeit jedoch ein Doppel-Klick auf, wird die Aktion nicht durchgeführt und stattdessen die Aktion des Doppel-Klicks prozessiert. Mit Hilfe des beliebten Javascript Frameworks Prototype kann man dafür leicht eine generische Lösung entwickeln, die diese beiden Events für ein Element oder mehrere registriert:

 
var FunkyTools = {	
/* Registriert einen einfachen klick und einen Doppel-Klick auf den selben Elementen.
 * Stellt sicher, dass bei einem Doppel-Klick nicht auch der Event-Handler des einfachen
* Klicks getriggert wird (gilt nur für den Event-Handler der über diese Methode 
* für den einfachen Klick registriert wurde).  
* 
* Parameter:
* 
* element         = ein DOM-Element, ein String(id des Elementes) oder ein Array 
*                       mit DOM-Elementen bzw. Strings(id der Elemente). 
* singleClickFunc = der Even-Handler für einfache Klicks
* dblClickFunc    = der Even-Handler für doppelte Klicks         
* 
* */
registerClicks: function(element, singleClickFunc, dblClickFunc) {
       var elements = $(element);
 
	if (! Object.isArray(elements)) {
		elements = [element];
	}
 
	elements.each(function(element){
		var doubleClick = false;
 
		element.observe('click', function(event){
			doubleClick = false;
			singleClickFunc.wrap(function(proceed, event){
					if (doubleClick) {
						event.stop();
					} else {
						proceed(event);
					}
				}).delay(0.3, event);			
			});		
 
			element.observe('dblclick', dblClickFunc.wrap(function(proceed, event){
				doubleClick = true;
				proceed(event);
			}));				
		});			
	}
}

Christian Schätzlein


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