protofunc()

mwheelIntent: Das gebrauchstaugliche mouswheel-Event

Tags: Usability, deutsch, javascript, jquery

Eine Möglichkeit die Usability von Javascript Widgets zu erhöhen, ist es eine reichhaltige Interaktionsmöglichkeit zu bieten. D.h. beispielsweise, daß ein Carousel nicht nur durch einen Click auf die Vorwärts-/Rückwärts-Schalter, sondern beispielsweise auch durch Tastatur oder eben das Mausrad bedient werden kann.

Javascript Widgets, die typischwerweise mit dem Mausrad bedient werden können, sind Karten wie Googlemaps, Yahoo Maps, Scrollbar-Ersetzungen sowie Laufbänder/Carousels. Mit dem Hinzufügen einer einfachen Mausradbehandlung zu diesen Widgets verursacht man jedoch in der Regel gleichzeitig ein größeres Usability-Problem. Denn diese Widgets sind in der Regel innerhalb eines Dokuments angelegt, welches ebenfalls mit dem Mausrad bedient werden kann. Möchte der User beispielsweise das Dokument mit dem Mausrad scrollen und kommt dabei eher zufällig auf ein Widget, welches ebenfalls per Mausrad gesteuert werden kann, fängt gerade dieses Widget die Mausradeingabe ab. Dies dürfte vom User nicht gewüsncht sein und ihn mehr verärgern als daß es ihn freut, daß er das Widget auch mit dem Mausrad bedienen kann.

Die Lösung für dieses Problem ist relativ einfach (und ist alleine deshalb bereits genial) und wird beispielsweise in einigen nativen Appliaktionen wie beispielsweise Firefox genutzt, um zu entscheiden, welches Widget gerade mit dem Mausrad bedient werden soll.

Hat der User nämlich in einem bestimmten “Mausradinteraktionsbereich” angefangen das Mausrad zu bedienen, so erhält dieser Bereich das Mausrad-Event exklusiv, solange der User sich a) über diesen Bereich bewegt und b) nicht die Maus bewegt, selbst wenn er dabei über andere interaktive Bereiche scrollt.

Ich habe mir erlaubt für eine solche Behandlung des Mausradevents ein jQuery-Plugin mit mwheelIntent-Demo zu schreiben. Die Nutzung ist relativ einfach:

$('div.widget').bind('mwheelIntent', function(e, d){
	//die mausrad implementierung
}):

Wie wurde dies implementiert

Das Grundgerüst

Für alle die es interessiert ein neues tolles custom-Event für jQuery zu schreiben, hier eine kurze Anleitung am Beispiel des mwheelIntent-Codes.

Als 1. definieren wir einige Variablen, welche wir noch benötigen werden. Besondere Beachtung sollte der Variable mwheelI geschenkt werden. Unter der Eigenschaft pos speichern wir mit jeder Mausradbewegung ab, wo sich das Mausrad gerade innerhalb des Viewports befindet, um zu entscheiden, ob der User die Maus zwischenzeitlich bewegt hat. Anfangs setzen wir diese mit jeweils -260 für x und y Koordinate weit aus dem Viewport, so daß anfangs immer von einer Mausbwegung ausgegangen wird.

Die Variable minDif gibt an, um wieviel Pixel der User die Maus mindestens bewegt haben muß, um eine Änderung des “Mausradinteraktionsbereichs” zu bewirken. Das eigentliche Kernstück unseres Events befindet sich unter $.event.special.mwheelIntent.

Die Methode setup wird aufgerufen, sobald der 1. mwheelIntent-Handler an einem DOM-Objekt gebindet wird, teardown, wenn der letzte mwheelIntent-Handler wieder entfernt wird. Die Methode handler wird letztendlich durch unser Event-System selbst aufgerufen und stößt das Aufrufen der eigentlich hinzugefügten Handler als eine Art proxy-Handler an. Dieser Methodenname könnte – meines Wissens nach – auch anders heißen, es ist jedoch Konvention ihn so zu nennen.

(function($){

//einige variablen
var mwheelI = {
			pos: [-260, -260]
		},
	minDif 	= 3,
	doc 	= document,
	root 	= doc.documentElement,
	body 	= doc.body,
	longDelay, shortDelay
;

// das eigentliche event grundgerüst
$.event.special.mwheelIntent = {
	setup: function(){

    },
	teardown: function(){

    },
    handler: function(e, d){

    }
};

// shortcuts  .bind('mwheelIntent', fn) -> .mwheelIntent()
$.fn.extend({
	mwheelIntent: function(fn) {
		return fn ? this.bind("mwheelIntent", fn) : this.trigger("mwheelIntent");
	},

	unmwheelIntent: function(fn) {
		return this.unbind("mwheelIntent", fn);
	}
});

//initialisierung des scrollbaren bereichs
$(function(){
	// falls body anfangs undefined gewesen sein sollte
	body = doc.body;
	// falls der User das cross-browser-mousewheel-Plugin nicht eingebunden hat
	if(!$.fn.mousewheel){
		setTimeout(function(){
			throw('Please include the mousewheel plugin before the mwheelIntent-plugin');
		}, 0);
	}
	//document als immer scrollbaren bereich berücksichtigen
	$(doc).bind('mwheelIntent.mwheelIntentDefault', function(){});
});
})(jQuery);

jQuery 1.4 wird zudem die beiden neuen Event-Hooks add und remove einführen, welche mit jedem hinzugefügten bzw. jedem entfernten Handler aufgerufen werden. (Mehr zu den neuen jQuery 1.4 Event hooks). Allerdings brauchen wir diese nicht.

Die Mausradbehandlung

Kommen wir nun zu der eigentlichen Implementierung. Unsere setup-Methode fügt letztendlich einen Eventlistener für das normale Mausradevent hinzu, welche unsere Handler Methode aufruft, in dem die Berechnung stattfindet, ob das Mausradevent durchgelassen werden soll oder nicht. Außerdem wird beim Verlassen der Maus aus dem Bereich die Funktion unsetPos aufgerufen, welche unsere eventuell gesetzten Werte auf den Anfangswert zurücksetzt. Bei den Objekten document, html und body wird dieser Listener nicht hinzugefügt, da er hier keinen Sinn machen würde und aufgrund der Tatsache, daß mouseleave ein auf mouseout aufbauendes Special-Event ist, einiges an Performance Kosten würde. Die teardown entfernt beide Listener wieder.

Die handler-Methode verfügt nun über den bereits erwähnten simplen, aber wirkungsvollen Code. Jedesmal wenn der User das Mausrad bewegt, wird die Mausposition abgespeichert und mit der letzten verglichen. Ist das DOM-Objekt mit dem letzten -durch das Mausrad bedienten – Objekt identisch oder hat der User seitdem die Maus weiter bewegt als in minDif definiert, wird das Event durchgelassen und die gebindeten handler mit der Methode $.event.handle.call(this, e, d); aufgerufen.


function unsetPos(){
	if(this === mwheelI.elem){
		mwheelI.pos = [-260, -260];
		mwheelI.elem = false;
		minDif = 3;
	}
}

$.event.special.mwheelIntent = {
	setup: function(){
		var jElm = $(this).bind('mousewheel', $.event.special.mwheelIntent.handler);
		if( this !== doc && this !== root && this !== body ){
			jElm.bind('mouseleave', unsetPos);
		}
		jElm = null;
        return true;
    },
	teardown: function(){
        $(this)
			.unbind('mousewheel', $.event.special.mwheelIntent.handler)
			.unbind('mouseleave', unsetPos)
		;
        return true;
    },
    handler: function(e, d){
		var pos = [e.clientX, e.clientY];
		if( this === mwheelI.elem || Math.abs(mwheelI.pos[0] - pos[0]) > minDif || Math.abs(mwheelI.pos[1] - pos[1]) > minDif ){
            mwheelI.elem = this;
			mwheelI.pos = pos;
			e = $.extend({}, e, {type: 'mwheelIntent'});
            return $.event.handle.call(this, e, d);
		}
    }
};

Verfeinerung der Mausradbehandlung

Dem aufmerksamen Leser wird aufgefallen sein, daß ich die Variable minDif ebenfalls auf 3 zurücksetze, obwohl ich sie gar nicht geändert haben. Außerdem habe ich 2 Variablen (longDelay, shortDelay) deklariert, die noch gar nicht genutzt werden. Dies hat mit einer kleineren Verfeinerung unseres Script zu tun. Hat der User gerade erst das Mausrad gedreht, soll er größere Mausbewegungen machen können, um eine ungewollte Verschiebung der Maus eben durch die Betätigung des Mausrads abzufangen. Dieser Code ist in der handler-Methode untergebracht und sieht wie folgt aus:

handler: function(e, d){
	var pos = [e.clientX, e.clientY];
	if( this === mwheelI.elem || Math.abs(mwheelI.pos[0] - pos[0]) > minDif || Math.abs(mwheelI.pos[1] - pos[1]) > minDif ){
		mwheelI.elem = this;
		mwheelI.pos = pos;
		minDif = 250;

		clearTimeout(shortDelay);
		shortDelay = setTimeout(function(){
			minDif = 10;
		}, 200);
		clearTimeout(longDelay);
		longDelay = setTimeout(function(){
			minDif = 3;
		}, 1500);
		e = $.extend({}, e, {type: 'mwheelIntent'});
		return $.event.handle.call(this, e, d);
	}
}

Im Ergebnis kann der User also 200ms nachdem er das Mausrad bedient hat die Maus um 250 Pixel bewegen, ohne daß dies eine Änderung des durch das Mausrad bedienbaren Bereichs zur Folge hat. Außerdem ist der Bereich für 1.5 Sekunden von 3 auf 10 Pixel erhöht.

Fazit

Die Berücksichtigung von Mausradeingaben kann die Userexpierence und die Bedienbarkeit von Widgets steigern, aber auch gleichzeitig große Usability-Problem verursachen, wenn die Intention des Users nicht berücksichtigt wird. Mit ein bißchen JS kann die wahrscheinliche Intention des Nutzers berechnet werden. (zur mwheelIntent-Projekt-Seite)

Written December 29, 2009 by
protofunc

jQuery: live-Methode / Braucht jQuery 1.4 eine neue API für Event Delegation?

Tags: deutsch, javascript, jquery

Jedes jQuery-Major-Release hat wesentliche Neuerungen/Verbesserungen gebracht. Bei jQuery 1.3 war es wohl die Einführung von Sizzle, die Umstellung von Browser-Sniffing auf Feature-Detection und die Einführung der live-Methode für Event Delegation.

Letztendlich habe ich die live-Methode in jQuery 1.3 so gut wie nie genutzt, da ich es für meine Usecases für ineffizient gehalten habe, das document-Objekt mit Eventlistener zu zuknallen. Eine präzisere Möglichkeit Event Delegation in jQuery 1.3 zu nutzen, war für mich immer die Nutzung der closest-Methode:

$('#nav').bind('click', function(e){
	// Der zweite Parameter wird erst in jQuery 1.4 eingeführt!
	var anchor = $(e.target).closest('a', this);
	if(!anchor[0]){return;)
	// mach was mit anchor
});

Das Problem mit der live-Methode

Mit jQuery 1.4 wird Event Delegation und die Möglichkeiten der live-Methode stark erweitert. Zum einen werden Events unterstützt, die nicht bubbeln und zum anderen soll der Entwickler bestimmen können, an welches DOM-Objekt der Eventlistener hinzugefügt wird. Dies dürfte in jedem Fall dazu führen, daß die Methode häufiger eingesetzt wird. Gleichzeitig haben einige Leute in der Vergangenheit gefordert, die live-Mwthode entweder abzuändern oder zumindest eine neue Methode einzuführen. Dies geschah mit besonderer Rücksicht auf Performance und wurde – meiner Meinung nach – zurecht abgelehnt.

Das Problem ist jedoch, daß die live-Methode schwer zu erklären/dokumentieren ist, leicht zu Fehlern führen kann und den Entwickler dazu zwingt bereits beim Instantiieren eines jQuery-Objekts über die spätere Art der Event Delegation nachzudenken. Diese Grundproblematik läßt sich bereits bei jQuery 1.3 ausmachen und wird bei jQuery 1.4 noch komplizierter (aber nicht unbedingt schlimmer).

Problem bei jQuery 1.3

Laut jQuery-Dokumentation kann live u.a. nicht verwendet werden, wenn man beim Instanzieren den Context-Parameter nutzt bzw. wenn man Traversing-Methoden zwischenschaltet. Eine ältere jQuery Dokumentation hat hiervon ausdrücklich die Methode find als nicht funktionierende Traversing Methode ausgenommen. Letztendlich stimmt diese Behauptung in der Dokumentation nicht ganz. Eine Erklärung wann dies funktionieren kann und wann nicht, wäre jedoch einfach zu kompliziert. Hier eine kurze Erklärung

// funktioniert in 1.3 nicht,
$('li', $('#nav')[0]).live('click'....
// ... da
// 1. der Eventlistener dem document-Objekt hinzugefügt wird
// 2. jQuery ausschließlich den 'li', aber nicht den '#nav' Selektor kennt

// funktioniert in 1.3,
$('li', $('#nav')).live('click'....
$('#nav').find('li').live('click'....
// ... da
// jQuery beide Selektor-Bestandteile kennt und diese zu '#nav li' zusammenfassen kann

// funktioniert in 1.3 nicht,
$('li, a', $('#nav')).live('click'....
// ... da jQuery beide Selektor-Bestandteile zu '#nav li, a' zusammenfaßt

Eskalierung des Problems mit jQuery 1.4 (nightly-Stand)

jQuery 1.4 ist noch nicht draußen, aber der derzeitige Stand kann über github eingesehen werden. Zudem existiert eine öffentliche Alpha, welche ich zum Testen herangezogen habe.

Mit dem derzeitigen Stand von jQuery 1.4a1 wird die oben beschriebene Ungenauigkeit zu einem großen Problem, denn die Verwendung des context-Parameters soll nicht nur ermöglicht werden, sondern wird zu einem elementaren Feature, da nun der context-Parameter bestimmen soll, an welchem Element der Eventhanlder gebunden werden soll. Das Problem hierbei ist jedoch eben die Tatsache, daß es eine Reihe von Bedingungen auftreten müssen, damit das ganze funktioniert.

//Event wird in der Regel an nav-Element gebunden und funktioniert in der Regel wie gewünscht
$('li', $('#nav')[0]).live('click'....
// aber dann nicht, wenn $('#nav')[0] === undefined ist

//Event wird an document-body gebunden (Obwohl ein context-Parameter angegeben wurde, funktioniert aber ansonsten wie gewünscht.)
$('li', $('#nav')).live('click'....

//Funktioniert wie bei jQuery 1.3 nicht wie gewünscht
$('li, a', $('#nav')).live('click'....

Die Dokumentation müßte hier einerseits auf die Unterscheidung hinweisen, was alleine für sich bereits ziemlich verwirrend sein könnte und andererseits darauf, daß die 1. Zeile fehleranfällig ist. Kommt nämlich auf der gesamten Seite kein #nav-Element vor, ist $(‘#nav’)[0] undefined, was dazu führt, daß erstens der Eventhandler zum document.body hinzugefügt und zweitens der Eventhandler bei jedem Click auf/in irgend ein li aufgerufen wird und damit nicht so funktioniert wie gewünscht.

Das gesamte Problem wird zusätzlich verschärft, wenn man einen Eventhandler mehreren Elementen hinzufügen möchte.

//Event wird an das document.body gebunden und funktioniert wie gewünscht
$('div.teaser', $('div.teaser-wrapper')).live('click'...

//Schreibweise wird von jQuery nicht unterstützt, Event wird an document.body gebunden und jeder Click auf/in ein div.teaser löst Event aus
$('div.teaser', $('div.teaser-wrapper').get()).live('click'...

Funktionierender Code, der das neue Event Delegation-Feature von jQuery 1.4 nutzt und dabei sowohl klar, verständlich als auch robust ist, könnte beispielsweise wie folgt aussehen:

$('#nav').each(function(){
	$('a', this).live('click' ...
});
//oder
$('div.teaser-wrapper').each(function(){
	$('div.teaser', this).live('click' ...
});

Letztendlich sind wir damit ziemlich nah an dem dran, was wir bereits in jQuery 1.3 schreiben können, um Event Delegation mit anderem context als document bzw. document.body zu nutzen, nämlich:

$('#nav').bind('click', function(e){
	// Der zweite Parameter wird erst in jQuery 1.4 eingeführt!
	var anchor = $(e.target).closest('a', this);
	if(!anchor[0]){return;)
	// mach was mit anchor
});

Es ist ziemlich “jQuery-unlike” mehrere Zeilen-Code zu schreiben, um nur eine “einfache” Sache zu erreichen, aber genau das würde bei einer Beibehaltung der derzeitigen API passieren. Ganz abgesehen davon, daß die Dokumentation um weitere kompliziertere Erklärungen nicht umhin kommen würde.

Eine einfache Implementierung einer neuen jQuery-Event Delegation API könnte hierbei beispielsweise wie folgt aussehen.

(function($){
	var dummy = $([]);

	$.each({addLive: 'live', removeLive: 'die'}, function(name, jMethod){
		$.fn[name] = function(sel){
			var args = (this[0]) ? Array.prototype.slice.call(arguments, 1) : [];
			return this.each(function(){
				dummy.selector = sel;
				dummy.context = this;
				$.fn[jMethod].apply(dummy, args);
			});
		};
	});
})(jQuery);

Die Nutzung würde hierbei wie folgt aussehen:

function fn(){
	alert('F');
}
//bind live:
$('div.teaser-wrapper').addLive('div.teaser', 'click', fn);
// unbind live/die
$('div.teaser-wrapper').removeLive('div.teaser', 'click', fn);

Fazit:

jQuery 1.4 benötigt nicht unbedingt eine neue Methode für Event-Delegation. Die Verwendung des context-Parameters wird nun grundsätzlich ermöglicht und damit mögliche Fehler einer flaschen Benutzung minimiert. “Power User”, die jedoch Wert darauf legen, daß sie die volle Kontrolle darüber haben, welchem Element der Event-Listener genau hinzugefügt wird, würden sich jedoch stark über eine anders funktionierende Methode freuen.

Eine solche Methode wäre dann nicht nur einfacher zu erklären, sondern würde ebenfalls die Forderung nach einer performanteren (ohne Unnötiges Selektieren von Elementen, die man nicht braucht) berücksichtigen.

Written December 8, 2009 by
protofunc

Der große Screenreader Focus-Test

Tags: accessibility, deutsch, javascript

Das Versetzen des Fokus, ist ein recht effizientes Mittel, um dynamische Änderungen im HTML Screenreader-Nutzern bekannt zu machen oder Probleme der Linearisierung beim Scripting zu umgehen (beispielsweise beim Anzeigen eines Dialogs bzw. einer Lightbox). Die Möglichkeit praktisch jedes beliebige Element zu fokusieren ist wohl einer der wichtigsten Bausteine für Wai-Aria. Das Schöne: Grundsätzlich sollte diese Technik auch in nicht aria-fähigen Browser-/Screenreader-Kombinationen möglich sein.

Gleichzeitig gibt es jedoch Unterschiede zwischen Screenreadern in der Unterstützung der focus-Methode. Der generelle Support dieser Methode ist bereits beim wohl wichtigsten Screenreader Jaws je nach Version unterschiedlich. Hier zeigt sich, daß Jaws 10 einen großen Sprung nach vorn gemacht hat und praktisch gar keine Probleme macht, währenddem Jaws 8 bzw. Jaws 9 noch einige Probleme haben. Möchte man diese älteren Versionen unterstützen, sollte man wissen, was geht und was nicht.

Zum Testaufbau

Getestet wurde mit den Jaws Versionen 8, 9 und 10 sowie mit den Browsern IE8, IE7 sowie Firefox 3.5. Zusätzlich wurde im IE8 mit Cobra 8.1 und Webformator 2.4c sowie mit NVDA 0.6p3.2 im Firefox 3.5 getestet. Abschließend wurde nochmals mit der aktuellen Beta-Version von NVDA (NVDA 2009.1beta1) getestet. Im wesentlichen hat sich herausgestellt, daß sich IE7 nicht von IE8 und Jaws 8 nicht von Jaws 9 unterscheiden, so daß die Testergbenisse zusammengefaßt werden.

  • Der Fokus sollte aufgrund eines Klicks auf einen Link, als Kontrollelement, verschoben werden
  • Jaws sollte die ganze Zeit über im normalen Modus arbeiten (Virtueller Cursor Modus war an, kein EINFÜGEN + Z)
  • Eine zusätzliche Verwendung von Aria-Attributen, welche den Screenreader in den Applikationsmodus versetzen könnte, war verboten
  • Der Buffer von Jaws wurde nicht manuell upgedatet (kein Einfügen + ESC)

Der Test galt nur als bestanden, wenn der Screenreader den Inhalt des fokusierten Bereich vorgelesen hat und sowohl der virtuelle Cursor als auch der “physikalische” Fokus richtig verschoben war. Der User also sowohl mit den Pfeiltasten als auch mit der Tab-Taste vom neu fokusierten Element aus arbeiten kann.

Welche Elemente können fokusiert werden

Normalerweise können – laut HTML 4 Spezifikation – nur Interaktionselemente, welche nicht versteckt oder deaktiviert sind, fokusiert werden (also Links, Buttons, Eingabefelder etc.). Mit dem tabindex-Wert -1 können diese für den User unfkousierbar gemacht werden, währenddem sie weiterhin durch Javascript fokusiert werden können. Alle getestet Screenreader-/Browser-Kombinationen konnten ohne Probleme diese Elemente im Test fokusieren.

Mit Hilfe des tabindex-Attributs sollen ebenfalls alle anderen Elemente fokusierbar gemacht werden können. Hat das tabindex-Attribut den Wert 0 kann dieses Element sowohl durch den Nutzer als auch durch Javascript fokusiert werden. In allen getesteten Screenreadern konnten solche Elemente durch den User fokusiert werden. In Jaws 8/9 mit Internet Explorer konnten allerdings Elemente, die normalerweise nicht foksuierbar sind, nicht mit der Javascript-Methode focus fokusiert werden (Beispiel: span[tabindex=0], span[tabindex=-1]). Als Ausnahme dieser Regel stellten sich Überschriften heraus. Befand sich das zu fokusierende Element in einer Überschrfit bzw. war es eine Überschrift, konnte dieses auch in Jaws 8/9 fokusiert werden.

Cobra 8.1 laß anchor-Elemente ohne href-Attribut manchmal als Links vor, was irritierend sein dürfte. Befand sich direkt über dem fokusierten Element eine Überschrift und das fokusierte Element war selbst keine Überschrift, wurde diese zusätzlich in Jaws als auch in Cobra vorgelesen.

Regel: Fokusiere nur Elemente, die Interaktionselemente sind oder Überschriften mit tabindex bzw. Elemente mit tabindex, die in Überschriften plaziert wurden. Mit Rücksicht auf Cobra sollten keine anchor-Elemente ohne href-Attribut fokusiert werden.

Nachfolgend der Testcase für das einfache Fokusieren.

Fokusieren von vor kurzem noch versteckten bzw. gerade erst erschaffenen Elementen

Um zwischen den Screenreadern Waffengleichheit herzustellen, wurde der Webformator so eingestellt, daß er nur wirklich sichtbare Elemente anzeigt.

Das Fokusieren von gerade noch versteckten Elementen bzw. Elementen, die erst aufgrund einer User-Aktion ins DOM eingefügt werden (beispielsweise Click -> Ajax -> innerHTML -> focus), gehört wohl zu den problematischsten aller Möglichkeiten. Im wesentlichen haben sich aber alle Screenreader, außer Jaws 8/9, recht gut geschlagen.

Damit Screenreader solche Elemente fokusieren können, müssen sie ihre Ausgabe aktualisieren, um das zu fokusierende Element zu finden. Grundsätzlich sollte die focus-Methode hierzu mit der setTimeout-Methode verknüpft werden. Dies ist weniger ein Workaround, bei dem abgewartet wird, bis die Ausgabe durch den Screenreader aktualisiert wird, sondern ein technisches Erfordernis, welches mit der Arbeitsweise von Events im Browser zu tun hat, welche u.a. Jaws 10 zum Updaten seines Buffers heranzieht. Der Testcase umfaßte hierbei mehrere Delays zum Testen (0ms, 180ms, 400ms). Letztendlich gab es hierbei jedoch in der Regel keine Unterschiede zwischen den delays. Wichtig war nur das die focus-Methode, welche im selben Thread arbeitet wie das Sichtbarmachen/Einfügen selbst, durch die setTimeout-Methode “gequeued” wird.

Jaws 10 und Cobra haben hierbei sämtliche Tests ohne Probleme absolviert.

NVDA sowie Jaws 8/9 hatten dagegen gewisse Probleme. Befand sich das zu fokusierende Elemente über dem aktuell fokusierten Kontrollelement, wurde durch Jaws 8/9 sowie NVDA 0.6p3.2 der Fokus zwar verschoben, jedoch der fokusierte Text nie gesprochen.

Die aktuelle Beta-Version von NVDA sprach zwar alle fokusierten Texte, also auch die, welche sich vor dem Kontrollelement befanden, hatte jedoch bei allen Testfällen das Problem, daß sowohl Tab-Taste als auch Pfeiltasten, nach der Fokusierung immer noch vom Kontrolelement aus starteten. Ein also Weiterarbeiten vom fokusierten Bereich aus nicht möglich war. Hierbei handelt es sich um einen Regressionsbug, der nicht in der 0.6p3.2-Version auftritt. Ich hoffe, daß dieser Bug bis zur Final behoben sein wird.

Jaws 8/9 hatte noch das zusätzliche Problem dahingehend, daß der fokusierte Text auch wenn er nach dem Kontrollelement kam, manchmal nicht gesprochen und/oder der Cursor manchmal nicht richtig verschoben wurde. Dies hatte letztendlich mit zwei Dingen zu tun:

Zum einen ist es von der Bedienung des Screenreaders abhängig, ob er seine Ausgabe rechtzeitig updatet. Wurde der virtuelle Cursor, beispielsweise mit den Pfeiltasten, über den Link gebracht und dann mit der Enter-Taste der Link ausgelöst, klappte das Fokusieren in der Regel. Wurde dagegen der “richtige” Fokus mit der Tab-Taste auf das Kontrollelement versetzt und dann mit der Enter-Taste ausgelöst, wurde die Ausgabe in der Regel nicht richtig upgedatet und es kam mit allen Delays zu Problemen.

Zum anderen Griff im Widerspruch zu dem oben gesagten ein Delay von 0 bzw. 50 nicht immer (aber in etwa 90% aller Fälle). Sobald das Delay jedoch über 100ms betrug und das Kontrollelement mit dem virtuellen Cursor ausgewählt wurde, wurden auf allen bisher getesteten Rechnern mit unterschiedlicher CPU (1 Single Core sowie 2 Core DUO), unterschiedlicher CPU Auslastung (normal bis 100%) sowie unterschiedlicher Sprachgeschwindigkeit die fokusierten Texte immer gesprochen und richtig versetzt.

Regel: Will man ältere Jaws-Versionen unterstützen (und das sollte man), sollte man das Fokusieren von gerade noch versteckten bzw. gerade erst erstellten Elementen bleiben lassen und andere Techniken nutzen.

Nachfolgend der Testcase für das Fokusieren von gerade sichtbar gemachten Elementen.

Inhalt Umschreiben und dann Fokusieren

Als eine wirkungsvolle Alternative für das Fokusieren von gerade sichtbar gemachten Elementen, bietet sich das Umtexten von bereits vorhandenen sichtbaren Elementen und das nachfolgende fokusieren dieser an. Dirk Ginader hatte diese Technik in seinem barrierefreien Tab-Beispiel gezeigt. Diese Technik bietet sich aber nicht nur für Tabs an, sondern auch für viele andere Dinge an.

Die Testfälle absolvierten letztendlich (fast) alle Screenreader. Lediglich wieder Jaws 8 und Jaws 9 hatten mit einem unwahrscheinlichen Testfall Schwierigkeiten. Befand sich innerhalb des umgeschriebenen Elements kein initialer Text verhielt sich Jaws 8/9 ähnlich begriffsstutzig wie bei gerade erst sichtbar gemachten Elementen. Da Jaws 8 und 9 allerdings gleichzeitig nur Interaktionselemente sowie Überschrfiten fokusieren können, dürfte man sowieso selten mit einem solchen Element leeren Inhalts arbeiten wollen.

Regel: Elemente, die zum Umschreiben und Fokusieren gedacht sind, sollten nicht mit leerem Inhalt “starten”.

Nachfolgend der Testcase für das Fokusieren von gerade “umgeschribenen” Elementen.

Fokusieren von gerade sichtbar gemachten/eingefügten Elementen Teil 2

Um dennoch die Möglichkeit zu haben gerade erst erstellte/sichtbar gemachte Elemente zu fokusieren, sind Workarounds denkbar. Ich habe hierzu einen Testcase mit abgewandelter setFocus-Methode zur Verfügung gestellt, welcher nach ersten Tests recht zuverlässig in Jaws 8/9 funktioniert und in anderen Screenreadern keine Probleme auslöst.

Regel: Wenn man Inhalte nicht umschreiben kann, dann – und nur dann – das modifizierte setFocus-Script ausprobieren.

Written October 11, 2009 by
protofunc

Meinung zu HTML5: Semantik noch Flop, progressive enhancement Top

Tags: HTML 5, deutsch, javascript

Ich bin ein großer Freund von HTML5. Allerdings kann ich nicht nachvollziehen, in welcher Form einige – bereits heute – HTML5 nutzen wollen bzw. zu nutzen empfehlen. Die tollen HTML5-Elemente (nav, aside, header, footer..), die wir mit dreckigen Tricks nutzen könnten, bringen nichts. Kein Browser, kein Screenreader und auch keine ernstzunehmende Suchmaschine unterstützen die Semantik der neuen Tags, gleichzeitig bereiten Sie in allen Versionen des Internet Explorer sowie im Firefox 2 erhebliche Probleme und machen einiges kaputt.

Auf der anderen Seite gibt es die schönen WAI-Aria-Landmarks. Sie sind für eine zusätzliche Semantik definitiv der bessere Weg, weil sie im Gegensatz zu den oben genannten HTML 5 Element einfach funktionieren, helfen und nichts kaputt machen.

Andere Elemente wie figure und legend können teilweise durch aria-labelledby/aria-describedby substituiert werden.

Dies ist kein Widerspruch zu HTML 5. Im Gegenteil der HTML 5 Validator validiert inzwischen nicht nur Teile von Wai-Aria, sondern eben auch die Aria-Landmarks.

Neben den problemlos verwendbaren Aria-Attributen, gibt es in HTML5 viele schöne neue Sachen zu entdecken.

  • Man kann sich endlich den doctype merken.
  • Das canvas-Element wird von allen modernen Browsern unterstützt und kann mit excanvas auch im Internet Explorer zum Laufen gebracht werden (allerdings mit deutlich geringerer Performance).
  • Attribute wie autocomplete sind endlich Bestandteil von HTML 5. Ohne dieses Attribut sind mit Javascript erstellte Auto-Suggest-Listen für Eingabefelder unmöglich.
  • Es gibt neue, befreiende Verschachtelungsregeln.
  • Best practices wie beispielsweise document.documentElement.className += ‘js-on’; erzeugen endlich validen Code.

Vor allem jedoch bietet HTML 5 – insbesondere bei Formularelementen – semantische Extras, die Javascript-Entwickler, gleichgültig von der derzeitigen Unterstützung im Browser, dringend benötigen, um eleganten, unobtrusiven Code schreiben zu können.

Wie zusätzliche semantische HTML5-Attribute beim Aufbau von Javascript helfen

Man stelle sich vor, man möchte einen Slider mit numerischen Werten realisieren. Dieser Slider soll jeweils einen vom Backend vorgegeben Startwert, Mindestwert und Höchstwert kennen. Als Javascript-Fallback soll ein einfaches Texteingabefeld dienen.

Während HTML 4 sowie XHTML 1.0 lediglich ein Attribut für den Startwert bieten, hilft uns HTML 5 ebenso beim Mindest- und Höchstwert unseres unobtrusiv zu erstellenden Sliders.

<input type=“number“ value=“3“ min=“1“ max=“10“ />
<!-- oder in Schritten -->
<input type=“number“ value=“3“ min=“1“ max=“10“ step="1" />
<!-- oder gleich als 'slider' -->
<input type=“range“ value=“3“ min=“1“ max=“10“ />

HTML 5 und progressive enhacement

HTML 5 bietet einige interessante Features, welche bereits heute mit Hilfe von Javascript implementiert werden können.

Das oben beschriebene canvas-Element ist ein Beispiel hierfür, ein weiteres sind die zahlreichen Erweiterungen für Formularelemente, welche in Teilen bereits von Browsern implementiert wurden.

Die HTML 5 – Attribute liefern hierbei die semantische Grundlage für die Aktivierung und Konfiguierung des Javascripts.

Vor der Implementierung mit Javascript sollte man grundsätzlich testen, ob das Feature nicht bereits Teil des Browsers ist. Da die neuen HTML-Attribute in der Regel mit einem DOM-Interface korrelieren, ist dieser Test kinderleicht. Hier einige Beispiele mit jQuery:

(function($){

	var form = $('<form><fieldset><textarea /></fieldset></form>');

	$.extend($.support, {
		//wird die Constrain Validation unterstützt?
		checkValidity: !!(form[0].checkValidity),
		// wird das maxlength-Attribut am textarea-Element unterstützt?
		textareaMaxlength: !!( $('textarea', form)[0].maxLength !== undefined || $('textarea', form)[0].maxlength !== undefined)
	});

})(jQuery);

Eine Beispielimplementierung für das placeholder-Attribut

Das placeholder-Attribut bietet ein Formularfeature, welches Frontend-Entwickler tagtäglich bereits heute mit Javascript in Webseiten nutzen; nämlich das Entfernen von vorbelegten Werten in Eingabefeldern. Hierbei ist das Standardverhalten wie folgt definiert: Der Placeholder stellt einen kurzen Hinweistext dar, welcher dem User als vorläufiger Wert im Eingabefeld angezeigt wird, solange das Feld keinen Wert hat und nicht fokusiert ist. Das placeholder-Attribut ist kein Ersatz für ein assoziertes label-Element!

<label for="birthday">Ihr Geburtstag</label>
<input type="text" placeholder="TT.MM.JJJJ" name="birthday" id="birthday" />

In HTML4/XHTML 1 ist die Unterscheidung ob das Eingabefeld einen Wert, welcher sich wie ein Placeholder verhalten soll oder nicht, nicht gelöst. Die derzeit eleganteste Javascript-Technik verwendet hierzu einen Vergleich zwischen der value- und der defaultValue-Eigenschaft. Diese Form der Implementierung kommt jedoch an Ihre Grenzen, wenn der User beispielsweise ein Suchformular absendet und der Suchbegriff aus Usability-Gründen wiederholt wird, um es dem User zu gestatten, ohne Neueingabe den Suchbegriff zu erweitern, verkürzen oder sonst wie zu modifizieren. Da in diesem Beispiel ausnahmsweise der defaultValue nicht die Aufgabe eines Placeholders besitzt, fehlt dem Javascript die entscheidende Information sich korrekt zu verhalten. Dieses Problem kann letztlich nur durch Hinzufügen dieser Extrainformation gelöst werden. Und die Standardisierung dieser Extrainformation stellt gerade eben das placeholder-Attribut dar.

Wie könnte nun eine placeholder-Implementierung aussehen?

Als erstes erweitern wir unseren oben genannten HTML5-Implementierungstest:

var form = $('<form><fieldset><input type=“text“ /><textarea /></fieldset></form>');

$.extend($.support, {
	checkValidity: !!(form[0].checkValidity),
	textareaMaxlength: !!( $('textarea', form)[0].maxLength !== undefined || $('textarea', form)[0].maxlength !== undefined),
	placeHolder: !!($('input', form)[0].placeholder !== undefined || $('input', form)[0].placeHolder !== undefined)
});

Wird das placeholder-Attribut nicht unterstützt, kann mit der Implementierung durch Javascript fortgefahren werden, was wie folgt aussehen könnte.

if(!$.support.placeHolder){

	function resetInput(){
		if(this.value == this.getAttribute('placeholder')){
			this.value = '';
		}
	}

	function fillInput(){
		if(!this.value){
			this.value = this.getAttribute('placeholder');
		}
	}
	$(function(){
		$('input[placeholder]')
			.each(function(){
				var placeHolder = this.getAttribute('placeholder').replace(/\n|\r|\f|\t/g, '');
				this.setAttribute('placeholder', placeHolder);
				if(!this.value){
					this.value = placeHolder;
				}

			})
			.bind('blur', fillInput)
			.bind('focus', resetInput)
		;
	});
}

Das schöne, das Script kann bereits heute für ein besseres placeholder/input-rest-Script eingesetzt werden. (Ich kenne keines mit einem besseren Verhalten). Bei Browsern (zum Beispiel Safari 4), welche dieses Attribut bereits unterstützen, funktioniert das ganze dann auch ohne Javascript. Die Benutzung ist sehr einfach. Konfiguration und Start des Scripts geschehen komplett automatisch.

<label for="birthday">Ihr Geburtstag</label>
<input type="text" id="birthday" placeholder="TT.MM.JJJJ" name="birthday" />

Hier das gesamte Script (Eine verbesserte Variante des Placeholder-Scripts):

(function($){

var form = $('<form><fieldset><input type=“text“ /><textarea /></fieldset></form>');

$.extend($.support, {
	checkValidity: !!(form[0].checkValidity),
	textareaMaxlength: !!( $('textarea', form)[0].maxLength !== undefined || $('textarea', form)[0].maxlength !== undefined),
	placeHolder: !!($('input', form)[0].placeholder !== undefined || $('input', form)[0].placeHolder !== undefined)
});

if(!$.support.placeHolder){
	$(function(){
		$('input[placeholder]')
			.each(function(){
				var placeHolder = this.getAttribute('placeholder').replace(/\n|\r|\f|\t/g, '');
				this.setAttribute('placeholder', placeHolder);
				if(!this.value){
					this.value = placeHolder;
				}

			})
			.bind('blur', function fillInput(){
				if(!this.value){
					this.value = this.getAttribute('placeholder');
				}
			})
			.bind('focus', function resetInput(){
				if(this.value == this.getAttribute('placeholder')){
					this.value = '';
				}
			})
		;
	});
}
})(jQuery);

Fazit

HTML 5 hat deutlich mehr zu bieten als nicht funktionierende HTML-Elemente. Diese sind oftmals nicht mehr als semantischer „Zuckerguß“. HTML 5 kann jetzt schon vielmehr: Es ermöglicht wirklich elegantes und unobtrusives Javascript.

Written August 16, 2009 by
protofunc

Wie man Tabs wirklich wirklich zugänglich macht

Tags: accessibility, deutsch, javascript

Vor etwa einem halben Jahr veröffentlichte Dirk Ginader eine zugängliche Tab-Lösung. Dieser Artikel versucht auf einem ähnlichem Grundscript durch Hinzufügen von WAI-Aria- Rollen und Zuständen die Zugänglichkeit zu erhöhen. Daneben deckt der Artikel einen Firefox/Jaws-Bug inklusive eines brauchbaren Workarounds auf.

Warum ich nicht die Aria Tab-Semantik nutze

Einigen wird nachfolgend (negativ) auffallen, daß ich eben nicht die Tabsematik anwende, welche durch das Aria Best Practices Dokument vorgeschlagen wird, sondern eine neue zusammenstelle. Dies begründet sich aus folgenden Punkten:

1. Abwärtskompatibilität: Tabben vs. erweiterte Tastaturnutzung

Aria im allgemeinen und insbesondere das Tab-Widget folgen der One-Tab-Stop-Technik”. Tatsächlich ist diese Technik sehr cool, um die Usability in aria-fähigen Screenreader-/Browserkombinationen drastisch zu erhöhen. Sie kann jedoch in manchen Situationen die Zugänglichkeit für nicht aria-fähige Systeme reduzieren.

Dies könnte beim Tab-Widget der Fall sein. Nur der aktive Tab soll nämlich in der Tabreihenfolge sein; alle inaktiven Tabs dagegen ausserhalb der, durch den Nutzer, erreichbaren Tabreihenfolge. Das Umschalten zwischen den Tabs erfolgt durch besondere Tastaturkürzel, die gescriptet werden müssen. Während das Herausnehmen von inaktiven Tabs aus der Tabreihenfolge in aria-unfähigen Systemen funktionieren kann, funktioniert das Scripten von Tastatureingaben im Normalmodus der meisten Screenreader ohne Aria in der Regel eben nicht.

2. Abwärtskompatibilität: Fehlende Rückmeldung

Im normalen Aria-Tabwidget findet die Rückmeldung über das Switchen von Tabs ausschließlich über Aria statt. Eine Fokusverlagerung, die in aria-unfähigen Screenreader/Browserkombinationen für eine solche Rückmeldung sorgen könnte, ist dagegen nicht vorgehsehen.

3. Konflikt mit Tastaturnutzung zwischen Webseite und Browser

Das Tabwidget sollte desweiteren eine Tastaturkombination verwenden (Ctrl+PageUp/Ctrl+PageDown), welche mit der des Browsers in Konflikt geraten kann.

4. Wir machen doch nicht nur Tabwidgets

Eine extrem häufige Aufgabe ist es einen Schalter zu platzieren, welcher die Sichtbarkeit eines anderen Elements steuert. Hierbei handelt es sich dann aber noch nicht um ein Tabwidget. Aber eben auch für diese Schalter wollen wir eine zugängliche Technik entwickeln. Ein Tabwidget ist aus dieser Sicht dann nichts anderes als die Zusammenfassung solcher Buttons mit ihren Containern zu einer Gruppe.

Auch wenn mittelfristig – durch steigende Aria-Unterstützung – das hier gezeigte Aria-Markup für Tabs weniger sinnvoll erscheinen wird, so bleibt es dennoch für Accordions oder Schalter, welche irgendwelche anderen Elemente in ihrer Sichtbarkeit toggeln, sinnvoll.

Das ariafizierte Markup

<div>
	<ul>
		<li><a role="button" aria-expanded="true" tabindex="-1" id="tab-1" aria-controls="panel-1" href="#">Tab 1</a></li>
		<li><a role="button" aria-expanded="false" tabindex="0" id="tab-2" aria-controls="panel-2" href="#">Tab 2</a></li>
	</ul>
	<div role="group" aria-expanded="true" aria-labelledby="tab-1" "id="panel-1">
		<p tabindex="-1">Text für Panel 1. Startet manchmal halt ohne Überschrift (schade ist aber so)</p>
	</div>
	<div role="group" aria-expanded="false" aria-labelledby="tab-2" "id="panel-2">
		<h3 tabindex="-1">Überschrift für Panel 2</h3>
		<p>Weiterer Text für Panel 2</p>
	</div>
</div>

Mit dem tabindex-Attribut kontrollieren wir die Tabreihenfolge. Wie sich zeigt drehen wir die Logik des W3C zum tabwidget um. Alle Tabs sind in der Tabreihenfolge nur eben das aktive Tab-Element nicht. Wir geben dem Tab die Rolle Button und die Eigenschaft expanded. Die expanded-Eigenschaft an Button-Elementen wird derzeit nur von Firefox 3.5 an Screenreader weitergeleitet. Die Eigenschaft controls baut eine logische Beziehung zwischen Button und Panel auf, wird jedoch noch von keinem Screenreader richtig unterstüzt (schadet wahrscheinlich nicht). Das Fokusieren des 2. Tab würde sich durch obige Auszeichnung wie folgt anhören: Tab 2 [Schalter reduziert].

Das Tabpanel bekommt die Rolle ‘group’ sowie die Eigenschaften expanded und labelledby. Die labelledby-Eigenschaft setzt eine logische Beziehung von dem group-Element zum Button. Anders als Controls wird sie gut unterstüzt. Auch die Eigenschaft expanded ist am group-Element besser unterstützt als am button-Element. Sobald der User auf Tab 2 klickt, fokusieren wir mit einem kurzen Delay das 1. Kindelement des Tabpanels, was sich ungefähr wie folgt anhören würde: [Tab 2 offen] Überschrift für Panel 2 [Überschrift Ebene 3].

Da wir die Ariafizierung mit Javascript vornehmen, hier 2 Scripte die uns dabei helfen:

ID-Attribute erstellen/ermitteln und labelledby-Eigenschaft setzen

(function($){

	var uId = new Date().getTime();
	$.fn.getID = function(){
		var id = '';
		if(this[0]){
			var elem = $(this[0]);
			id = elem.attr('id');
			if(!id){
				id = 'ID-' + (uId++);
				elem.attr({'id': id});
			}
		}
		return id;
	};

	$.each({
		labelWith: 'aria-labelledby',
		describeWith: 'aria-describedby',
		ownsThis: 'aria-owns',
		controlsThis: 'aria-controls'
	}, function(name, prop){
		$.fn[name] = function(elem){
			return this.attr(prop, $(elem).getID());
		};
	});
})(jQuery);

Das jQuery-Plugin getID gibt den Wert des id-Attributs eines Elements zurück. Ist keine id vorhanden wird eine eindeutige id erstellt. Daneben werden hierauf aufbauend die Methoden labelWith, describeWith, ownsThis und controlsThis erstellt.

Benutzung:

// Gibt den ID-Wert von panel zurück
$(panel).getID();

// gib dem Panel die Eigenschaft labelledby mit dem id-Wert von button
$(panel).labelWith(button);

Element fokusieren

Ein Script, welches ein beliebiges Element nach einem kurzen Delay fokusiert, könnte dagegen wie folgt aussehen:

(function($){

	function addTabindex(jElm){
		if(!jElm.is('a[href], area, input, button, select, textarea')){
			jElm.css({outline: 'none'}).attr({tabindex: '-1'});
		}
		return jElm;
	}
	var focusTimer;
    $.fn.setFocus = function(time){
		if(!this[0]){return this;}
		var jElm = $(this[0]);
		clearTimeout(focusTimer);
		focusTimer = setTimeout(function(){
			try{
				addTabindex(jElm)[0].focus();
			} catch(e){}
		}, time || 0);
		return this;
	};

})(jQuery);

Benutzung des Focus-Scripts:

// 1. sichtbares Kindelement von panel nach 0 ms fokusieren
$('> :visible:first', panel).setFocus();

Sollte man das Panel oder ein anderes wrapper-Element des zu fokusierenden Elements animieren, empfiehlt es sich mit der Fokusierung bis zum Ende der animation zu warten, da beim Internet Explorer der Fokus bei manchen Animation ab und zu “verspringt”. Eine Verzögerung von weniger als 500ms (Die Standarddaauer einer jQuery-Animation beträgt 400ms) beim Fokusieren wird nicht als störend empfunden.

Wenn das ganze funktionieren würde…

… hätte ich diesen Blogeintrag nie geschrieben. Es ist etwa einen Monat her, da wies mich Timo Wirth daraufhin, daß das Script in einem seiner Projekte nicht funktioniert. Das Problem war, daß sobald das HTML eben mit Aria erweitert wurde, insbesondere die Aria-Rolle Button vergeben wurde, Jaws mit Firefox den Buffer nicht mehr beim Sichtbarkeitswechsel updaten wollte. Da aber die vorbereiteten Beispiele funktionierten, stellte sich die Frage, welcher andere Faktor an dem Scheitern verantworlich ist. Es begann die Suche nach der Nadel im Heuhaufen.

Das Problem ist kurz gesagt dermaßen banal und es ist eine Schande, wie lange die Ursachenforschung gedauert hat:

Das Element, dessen Sichtbarkeit beeinflußt wird, darf unter bestimmten Bedingungen (beispielsweise Kontrollelement hat die Rolle Button) nicht die CSS-Eigenschaft overflow: hidden haben. Um sicherzustellen, daß das Panel diese CSS-Eigenschaft weder durch CSS noch durch eine Javascript-Animation erhält, bekommt es daher von mir eine CSS-Klasse. Ein dynamisch erzeugtes Stylesheet sorgt dafür, dass diese Klasse ein overvlow: visible erhält:

$(function(){
	var style = document.createElement('style'),
		styleS
	;
	style.setAttribute('type', 'text/css');
	style = $(style).prependTo('head');

	styleS = document.styleSheets[0];

	function add(sel, prop){
		if (styleS.cssRules || styleS.rules) {
			if (styleS.insertRule) {
				styleS.insertRule(sel +' {'+ prop +';}', styleS.cssRules.length);
			} else if (styleS.addRule) {
				styleS.addRule(sel, prop);
			}
		}
	}

	add('.a11y-visibility-change', 'overflow:visible !important');

	$.cssRule = {
		add: add
	};
});
//über die Klasse reden wir noch :-)
$(panel).addClass('a11y-visibility-change');

Ein div sie zu knechten

Nun stellt die CSS-Eigenschaft overflow: hidden eine recht wichtige Eigenschaft dar. Einerseits wird sie gerne genommen, um floats einzuschließen andererseits ist sie essentieller Bestandteil einiger JavaScript-Animationen, beispielsweise (slideUp, slideDown etc.). Unser Stylesheet-Problem läßt sich recht einfach lösen, sowohl Vorfahren- als auch Nachfahrenelemente dürfen wieder die CSS-Eigenschaft hidden aufweisen. Bei Animationen wie slideDown/slideUp kann das Elternelement mit einer Höhen-Animation und entsprechendem overflow: hidden verwendet werden. Zu beachten ist allerdings, daß in unserem Fall das group-Element selbst die display: none/block Eigenschaft bekommen sollte und eben nicht das Elternelement. Eine Alternative zu slideUp/slideDown könnten daher folgende Plugins bieten:

$.fn.slideParentDown = function(opts){
	opts = $.extend({}, $.fn.slideParentDown.defaults, opts);
	var fn = opts.complete;

	return this.each(function(){
		var jElm 		= $(this),
			parent		= jElm.parent().css({overflow: 'hidden', height: '0px'}),
			outerHeight = jElm.css({display: 'block'}).outerHeight({margin: true})
		;
		parent.animate({
			height: outerHeight
		}, $.extend({}, opts, {complete: function(){
			parent.css({height: '', overflow: ''});
			fn.apply(this, arguments);
		}}));
	});
};
$.fn.slideParentDown.defaults = {
	duration: 400,
	complete: function(){}
};
$.fn.slideParentUp = function(opts){
	opts = $.extend({}, $.fn.slideParentDown.defaults, opts);
	var fn = opts.complete;
	return this.each(function(){
		var jElm 		= $(this),
			parent		= jElm.parent().css({overflow: 'hidden', height: jElm.outerHeight({margin: true})})
		;
		parent.animate({
			height: '0px'
		}, $.extend({}, opts, {complete: function(){
			jElm.css({display: 'none'});
			parent.css({height: '', overflow: '', display: ''});
			fn.apply(this, arguments);
		}}));
	});
};
$.fn.slideParentDown.defaults = {
	duration: 400,
	complete: function(){}
};

Kurz gesagt, wenn man die overflow: hidden Eigenschaft braucht, nimmt man ein zusätzliches div-Element und alles wird gut.

Fazit

Testen, testen, testen!!!

Das Ergebnis ist mit Sicherheit noch nicht des Weisheits letzter Schluß, aber hört sich schon ganz nett an. Für Leute die dies einmal testen möchten hier nun abschließend eine kleine Demonstration des zusammengebauten Tab-Scripts.

Die vorgenannte Demo wurde bisher mit folgenden Browser/Screenreader-Kombinationen erfolgreich getestet:

IE8: Jaws 10, Cobra (Webformator 2.4) | Firefox. 3.5: Jaws 10, NVDA 0.6p2 | IE 7: Jaws 9/Jaws 8

Update 06.10.2009: Das Script enthielt zuvor einen kleinen Fehler, so daß es nicht im IE7 mit Jaws 8/9 funktionierten konnte. Dieser Bug ist nun gefixt.

Written August 2, 2009 by
protofunc

leichtgewichtiges, barrierearmes, generisches Hover-Plugin für jQuery

Tags: accessibility, deutsch, javascript, jquery, tutorial

Die hover-Methode von jQuery gehört wohl zu einer der beliebtesten Methoden von jQuery. Leider besitzt sie das Problem nicht barrierefrei zu sein, da die jeweils zugrunde liegenden Events (mouseenter/mouseleave) (eingabe-)geräteabhängig sind.

Die mit dem mouseenter/mouseleave häufig (aber nicht immer) korrespondierenden Events focus/blur werden dagegen selten eingesetzt. Andererseits gibt es einige fertige Scripte, die sich des Problems für bestimmte Teilbereiche angenommen haben. Sie setzen allerdings mehr oder weniger feste HTML-Strukturen voraus (In der Regel Flyout Menue Scripte mit verschachtelten Listen und darin befindlichen Links).

Ein Script, welches dieses – eigentlich einfache Problem – etwas generischer versucht zu lösen, ist mir nicht bekannt. Ich habe versucht dies vor einiger Zeit zu lösen. Hier eine Demo dieses Lösungsansatzes. Bei der Implementierung stößt man in der Regel auf mehrere Probleme:

  1. Das “Mausinteraktionselement” kann muß aber nicht immer das “Tastaturinteraktionselement”
  2. In einem “Mausinteraktionselement können mehrere “Tastaturinteraktionselemente” vorkommen
  3. Die Art der “Tastaturinteraktionselemente” ist nicht bekannt (jedes Element kann tastaturbenutzbar gemacht werden
  4. Wurde die Interaktion bereits durch die Maus getriggert, darf sie nicht nochmal durch ein focus getriggert werden und umgekehrt.

Kern der Lösung stellen hierbei die focusin/focusout Events dar, welche bubbelnde focus/blur Events darstellen und von Microsoft erfunden wurden. In standardkonformen Browsern läßt sich dieses Event dadurch nachstellen, daß man gefeuerte Events bereits in der Capturing-Phase abfängt.

Jörn Zaefferer hat für sein simples delegate-Plugin eine entsprechende focusin/focusout-Implementierung geschrieben, welche er auch in seinem bekannten Validation-Plugin nutzt. Jörn´s focusin/focusout Code sieht wie folgt aus:

/*
*	focusin/focusout von  jörn zaefferer´s delegate plugin
*	 Copyright (c) 2007 Jörn Zaefferer
*/
$.each({
	focus: 'focusin',
	blur: 'focusout'
}, function( original, fix ){
	$.event.special[fix] = {
		setup:function() {
			if ( $.browser.msie ) return false;
			this.addEventListener( original, $.event.special[fix].handler, true );
		},
		teardown:function() {
			if ( $.browser.msie ) return false;
			this.removeEventListener( original,
			$.event.special[fix].handler, true );
		},
		handler: function(e) {
			arguments[0] = $.event.fix(e);
			arguments[0].type = fix;
			return $.event.handle.apply(this, arguments);
		}
	};
});

Unser barrierearmes hover-Plugin soll identisch zum hover-Plugin aufgerufen werden können. Als kleinen Bonus kann man als dritten Parameter Optionen angeben:

$('#nav').find('li').inOut(enterHandler, leaveHandler);
//oder mit anderen Optionen
$('#nav').find('li').inOut(enterHandler, leaveHandler, {bothOut: true});

Hierauf aufbauend nun die Grundkonstruktion unseres jQuery-Plugins:

(function($){
	var handler = {},
		uID = new Date().getTime()
	;

	$.fn.inOut = function(enter, out, opts){

	};

	$.fn.inOut.defaults = {
		mouseDelay: 0,
		keyDelay: 1,
		bothOut: false,
		useEventTypes: 'both' // both || mouse || focus
	};
})(jQuery);

Wir definieren als erstes ein handler-Objekt hier werden wir unsere handler-Methoden einfügen, die dann entscheiden, ob und wann die übergebenen Callback-Funktionen aufgerufen werden. Danach erstellen wir eine ID. Die ID werden wir später benötigen, da wir stark mit der data-Methode von jQuery arbeiten, um die einzelnen Zustände an den Elementen zu speichern. Für den seltenen Fall, daß man das Plugin mehrfach auf das selbe DOM-Element anwenden will, werden wir diese ID mit jedem Durchlauf des Plugins verändern. Hiernach kommt das eigentliche Plugin-Grundgerüst sowie die Default-Einstellungen.

Mit mouseDelay kann entschieden werden nach welchem Delay die jeweilige mouseenter/mouseleave Funktion auferufen werden sollen. Wir können dies sehr gut für benutzerfreundliche Flyout-Menues verwenden. Mit keyDelay können wir einen entsprechenden Delay für Tastaturuser einbauen. Allerdings macht dies hier weniger Sinn. Mit dem booleschen Wert bothOut können wir bestimmen, ob die leave-Methode nur dann aufgerufen werden soll, wenn weder das jeweilige Interaktionselement fokusiert noch die Maus drüber liegt. Mit der Option ‘useEventTypes’ können wir entscheiden, ob hover tatsächlich mit focus/blur gleichgesetzt werden soll (both). Anderenfalls können wir das Handling nur auf focus/blur (focus) oder nur auf mouseenter/mouseleave (mouse) beschränken.

Nun zum Inhalt unseres jQuery Plugins:

//hochzählen unserer ID
uID++;
//Defaults mit Optionen auffüllen
opts = $.extend({}, $.fn.inOut.defaults, opts);
//Option 'useEventTypes' verarbeiten
var inEvents = 'mouseenter focusin',
	outEvents = 'mouseleave focusout';
if(opts.useEventTypes === 'mouse'){
	inEvents = 'mouseenter';
	outEvents = 'mouseleave';
} else if(opts.useEventTypes === 'focus'){
	inEvents = 'focusin';
	outEvents = 'focusout';
}
//Anzahl der enter Events auf 0 setzen
//Eventhandler mit Optionen, der ID und eigentlicher Callback-Funktion binden
return this
	.data('inEvents'+ uID, 0)
	.bind(inEvents, [enter, opts, uID], handler.enter)
	.bind(outEvents, [out, opts, uID], handler.out);

Nun müssen wir noch die enter-/out-Methoden definieren. Als 1. die enter-Methode (wenn die Maus drüberwandert oder ein Element fokusiert wird):

handler.enter = function(e){
// Extrahieren der callback-Funktion, der Einstellungen und der ID aus der data-Array
	var fn = e.data[0],
		o = e.data[1],
		ID = e.data[2],
//Zwischenspeichern des Kontextes (zeigt auf DOM-Element)
		elem = this,
//Hochzählen unseres 'inEvents'
		inEvents = $.data(elem, 'inEvents'+ ID) + 1
	;
//Möglichen timer clearen, um ausführen eines "delayten" out-Callbacks zu verhindern
	clearTimeout($.data(this, 'inOutTimer'+ ID));
//setzen unserer aktuellen inEvents
	$.data(elem, 'inEvents'+ ID, inEvents);
//starten unseres Timeouts und speichern unserer timeout-Referenz
	$.data(elem, 'inOutTimer'+ ID, setTimeout(function(){
//enterCallback nur ausführen, wenn sie noch nicht ausgeführt wurde
		if(!$.data(elem, 'inOutState'+ ID)){
//setzen unserers inOutStates
			$.data(elem, 'inOutState'+ ID, true);
//modifizieren unseres Events
			e.type = 'in';
//eigentliche Callback-Funktion ausführen:
			fn.call(elem, e);
		}
//länge des Delay ermitteln
	}, /focus/.test(e.type) ? o.keyDelay : o.mouseDelay));
};

Nun müssen wir noch die leave-Methode erstellen, welche wie folgt aussieht:

handler.out = function (e){
	var fn = e.data[0],
		o = e.data[1],
		ID = e.data[2],
		elem = this,
		//sicherstellen das inEvents-Wert mind. 0 beträgt
		inEvents = Math.max($.data(elem, 'inEvents'+ ID) - 1, 0)
	;

	clearTimeout($.data(this, 'inOutTimer'+ ID));
	$.data(elem, 'inEvents'+ ID, inEvents);
	$.data(elem, 'inOutTimer'+ ID, setTimeout(function(){
		if($.data(elem, 'inOutState'+ ID) &&
			//Verarbeiten der bothOut-Option
				(!o.bothOut || !inEvents)){
			$.data(elem, 'inOutState'+ ID, false);
			e.type = 'out';
			fn.call(elem, e);
		}
	}, /focus/.test(e.type) ? o.keyDelay : o.mouseDelay));
};

Wie sich zeigt sind die enter-/leave-Methoden praktischen identisch. Die Unterschiede im Überblick: Dort wo die enter-Methode einen Wert hochzählt (inEvents), zählt die leave-Methode eins runter und dort wo die enter-Methode den Wert auf true setzt, setzt die leave-Methode den Wert false (inOutState). Daneben wird ausschließlich in der leave-Methode die Option bothOut verarbeitet.

Aus diesen geringen Unterschieden läßt sich eine kleine Funktion bauen, welche uns beide Handler-Methoden dynamisch erstellt.

$.each({enter: [1, 'in', true], out: [-1, 'out', false]}, function(handle, params){
	handler[handle] = function(e){
		var fn = e.data[0],
			o = e.data[1],
			ID = e.data[2],
			elem = this,
			inEvents = Math.max($.data(elem, 'inEvents'+ ID) + params[0], 0)
		;

		clearTimeout($.data(this, 'inOutTimer'+ ID));
		$.data(elem, 'inEvents'+ ID, inEvents);
		$.data(elem, 'inOutTimer'+ ID, setTimeout(function(){
			if((params[2] != $.data(elem, 'inOutState'+ ID)) &&
					(params[2] || !o.bothOut || !inEvents)){
				$.data(elem, 'inOutState'+ ID, params[2]);
				e.type = params[1];
				fn.call(elem, e);
			}
		}, /focus/.test(e.type) ? o.keyDelay : o.mouseDelay));
	};
});

Abschließend nochmals das gesamte Script sowie die Demo:

(function($){
	var handler = {},
		uID = new Date().getTime()
	;
	$.each({enter: [1, 'in', true], out: [-1, 'out', false]}, function(handle, params){
		handler[handle] = function(e){
			var fn = e.data[0],
				o = e.data[1],
				ID = e.data[2],
				elem = this,
				inEvents = Math.max($.data(elem, 'inEvents'+ ID) + params[0], 0)
			;

			clearTimeout($.data(this, 'inOutTimer'+ ID));
			$.data(elem, 'inEvents'+ ID, inEvents);
			$.data(elem, 'inOutTimer'+ ID, setTimeout(function(){
				if((params[2] != $.data(elem, 'inOutState'+ ID)) &&
						(params[2] || !o.bothOut || !inEvents)){
					$.data(elem, 'inOutState'+ ID, params[2]);
					e.type = params[1];
					fn.call(elem, e);
				}
			}, /focus/.test(e.type) ? o.keyDelay : o.mouseDelay));
		};
	});

	$.fn.inOut = function(enter, out, opts){
		uID++;
		opts = $.extend({}, $.fn.inOut.defaults, opts);

		var inEvents = 'mouseenter focusin',
			outEvents = 'mouseleave focusout'
		;
		if(opts.useEventTypes === 'mouse'){
			inEvents = 'mouseenter';
			outEvents = 'mouseleave';
		} else if(opts.useEventTypes === 'focus'){
			inEvents = 'focusin';
			outEvents = 'focusout';
		}

		return this
				.data('inEvents'+ uID, 0)
				.bind(inEvents, [enter, opts, uID], handler.enter)
				.bind(outEvents, [out, opts, uID], handler.out);

	};

	$.fn.inOut.defaults = {
		mouseDelay: 0,
		keyDelay: 1,
		bothOut: false,
		useEventTypes: 'both' // both || mouse || focus
	};

})(jQuery);

Angesichts der Tatsache, daß dieses Script nicht nur die meisten 08/15 Menue-Scripte überflüßig macht, sondern eine wirklich universell einsetzbare Lösung für unser hover-Problem darstellt, ist es wirklich klein und leichtgewichtig geraten :-) .

Update: Hier findet ihr eine aktuelle Version des inOut/enterLeave-Scripts

Written March 29, 2009 by
protofunc

Animation-Ajax-Queue erstellen

Tags: ajax, deutsch, javascript, jquery, tutorial, video

Eine häufigere Aufgabe ist es bei Ajax Requests den alten Content durch eine Animation zu verstecken, dann den Content auszutauschen und diesen dann durch eine weitere Animation anzuzeigen.

Hierbei stellt sich das Problem, daß die genaue Dauer der Ajax-Response unbekannt ist. Ist die Response deutlich kürzer als die Verstecken-Animation, kann der Inhalt nicht ausgetauscht werden, da der User dann während dieser Animation plötzlich bereits den neuen Content sieht.

Um dieses Problem zu lösen, eignet sich jQuery´s queue Methode sehr gut, welche bereits genutzt wird, um Animationen in eine Warteschlange zu stellen. Um diesen Animationsqueue zu nutzen, muß der Ajax-Callback sich zu diesem Queue hinzufügen. Wird die Animation noch ausgeführt, wird dessen Ende abgewartet ansonsten wird die Callback-Funktion sofort ausgeführt.

Eine kleine Funktion, die aus einer Callback-Funktion eine sich in die Effekt-Warteschlange “anstellende” Funktion macht, könnte wie folgt aussehen:

$.createQueueCallback = function(jElm, fn, type){
	type = type ||
		'fx';
	return function(){
		var that = this,
			args = arguments;

		jElm = $(jElm);
		jElm.queue(type, function(){
			fn.apply(that, args);
			jElm.dequeue(type);
		});
	};
};

Dieser Methode wird als 1. Parameter ein jQuery-DOM-Object, ein DOM-Objekt selbst oder ein CSS-Selektor, als 2. Parameter die Callback-Funktion und als 3. optionaler Parameter der Name der Warteschlange übergeben. Die Benutzung könnte so aussehen:

function requestContent(){
	var live = $('#live')
		.hide(500);

	$.ajax({
		url: 'htmlsnippet.txt',
		success: $.createQueueCallback(live, function(data){
			live
				.html(data)
				.show(200);
		}),
		error: function(){
			//immer errors handeln
		}
	});
	return false;
}

Folgend findet ihr eine Ajax-FX-Queue-Demo als Download.

Written March 15, 2009 by
protofunc

IE8, excanvas und dynamisch kreierte Canvas-Elemente

Tags: deutsch, javascript

Wer die offizielle ExplorerCanvas Version einsetzt, sollte bald gezwungen sein, diese upzudaten, da diese Version aus dem Jahr 2007 nicht den Windows Internet Explorer 8 im Standard-Modus unterstützt. Abhilfe schafft die aktuelle Revision 44 aus dem excanvas subversion repository.

Leider unterstützt diese Version wiederum nicht das dynamische Erstellen von canvas-Elementen. Abhilfe verschafft die Methode fixElement_, welche leider zugunsten des createElement-Workarounds gestrichen wurde. Eine Erweiterung der aktuellen excanvas-Revision, um wieder dynamisch kreierte canvas-Elemente im IE fixen zu können, könnte wie folgt aussehen:

//alte fixElement_-Methode aus Rev 26
G_vmlCanvasManager.fixElement_ = function (el) {
   // in IE before version 5.5 we would need to add HTML: to the tag name
   // but we do not care about IE before version 6
   var outerHTML = el.outerHTML;

   var newEl = el.ownerDocument.createElement(outerHTML);
   // if the tag is still open IE has created the children as siblings and
   // it has also created a tag with the name "/FOO"
   if (outerHTML.slice(-2) != "/>") {
	 var tagName = "/" + el.tagName;
	 var ns;
	 // remove content
	 while ((ns = el.nextSibling) && ns.tagName != tagName) {
	   ns.removeNode();
	 }
	 // remove the incorrect closing tag
	 if (ns) {
	   ns.removeNode();
	 }
   }
   el.parentNode.replaceChild(newEl, el);
   return newEl;
 };
//Verknüpfung zwischen fixElement_- und initElement-Methode
G_vmlCanvasManager.fixDynamicElement = function(el){
	return G_vmlCanvasManager.initElement(G_vmlCanvasManager.fixElement_(el));
};

Hiermit kann dann einfach das canvas-Element mit der Methode fixDynamicElement gefixt werden:

//...
canvasElement = G_vmlCanvasManager.fixDynamicElement(canvasElement);
//...

Ein Update lohnt in jedem Fall. Die Revision scheint nicht nur recht stabil zu sein, sondern enthält viele gute Bugfixes.

Hinweis: Inzwischen gibt es ein offizielles Release (3.) auf Grundlage der Revision 48. Das Projekt wird nun bei Google Code weiter vorangetrieben. Der oben genannte Fix für das dynamische Hinzufügen von Canvas-Elementen scheint aber weiterhin nötig zu sein :-( .

Written February 12, 2009 by
protofunc

jQuery´s step-Methode erweitern: synchrone Width-Animation

Tags: deutsch, javascript, jquery, tutorial

jQuery bietet viele Möglichkeiten der Erweiterung. Neben der relativ gut bekannten Möglichkeit das jQuery Prototype Objekt mit weiteren Methoden zu versorgen ($.fn ist ein alias für jQuery.prototype). Gibt es APIs für die Erweiterung von Events, Selektoren, easing, Ajax und der step-Methode. Letztere spielt -wie das easing – eine zentrale Rolle bei Animationen. Die bekannteste step-Erweiterung ist wohl das Color-Animation-Plugin, welches es erlaubt auch Farbwerte zu animieren.

Wir wollen eine sehr simple step-Erweiterung schreiben. Bei diesem Tutorial geht es weniger darum eine tolle Custom-Animation zu schreiben, sondern zu lernen, wie man schnell und einfach eine solche Erstellen könnte. Möchte man 2 oder mehrere unterschiedliche Elemente mit unterschiedlichen Werten synchron animieren, kann man in der Regel einfach diese Animationen gleichzeitig starten. Hierbei kommt in der Regel nichts 100%ig synchrones heraus, jedoch fällt dies dem User nicht auf.

In manchen Fällen benötigt man jedoch eine wirklich 100%ige Synchronisierung von Effekten. In diesen Fällen kann eine Erweiterung der step-Methode der mögliche Weg zum Ziel sein. Die step-Methode wird – wie der Name bereits aussagt – bei einer Animation ständig aufgerufen. Nachfolgendend findet ihr eine Demo.

Unsere Step-Methode werden wir syncWidth nennen, so dass sie bei einer Animierung der Eigenschaft syncWidth jedes mal aufgerufen wird:

$('div.box:first').animate({syncWidth: 400});

Damit unsere Erweiterung weiß, welche Elemente synchronisiert werden und wieviel Platz für diese Elemente zur Verfügung stehen soll, geben wir in den Optionen zwei weitere Eigenschaften an:

$('div.box:first')
	.animate({syncWidth: 400}, {syncElements: 'div.box', fullWidth: 800});

Das Grundgerüst unseres Step-Plugins sieht wie folgt aus:

$.fx.step.syncWidth = function(fx){
	console.log(fx);
};

Als 1. Parameter übergibt jQuery uns das fx-Objekt der jeweiligen Animation. Mit Firebug können wir die in diesem Objekt zur Verfügung stehenden Informationen anschauen. Der gesamte weitere Code findet innerhalb dieses Grundgerüst statt.

Am Anfang kümmern wir uns um die initialen Berechnungen, die nur einmal ganz am Anfang durchgeführt werden müssen. Damit wir auf diese Berechnungen auch bei den späteren Aufrufen zurückgreifen können, erweitern wir einfach das fx-Objekt.

//fx.state ist nur beim 1. Aufruf falsy
if (!fx.state) {
	var o = fx.options;
	//Startwerte der Animation berechnen
	fx.start = $(fx.elem).width();
	//Zugriff auf wichtige Eigenschaft verkürzen
	fx.fullWidth = o.fullWidth;

	//Array für die Startwerte der synchronisierten Elemente
	fx.syncStart = [];
	fx.syncElements = $(o.syncElements)
	// neues jQuery-Objekt mit allen synchronisieten Elementen, aber ohne das fx.elem, erstellen
		.map(function(i, elem){
			if(elem !== fx.elem){
				return elem;
			}
		})
		//Array mit Startwerten füllen
		.each(function(i){
			fx.syncStart.push($(this).width());
		});
	//Endwert für synchronisierte Elemente berechnen
	fx.syncEnd = (fx.fullWidth - fx.end) / fx.syncElements.length;
}

Nun kümmern wir uns letztlich darum die einzelnen CSS-Änderungen zu berechnen und bei den jeweiligen Elementen zu setzen.

//syncedWidth enthält die Gesamtbreite aller synchronisierten Elemente
var syncedWidth = 0;
fx.syncElements
	.each(function(i){
		//Berechnung der aktuellen Breite des synchronisierten Elements
		//fx.pos gibt die Position in der Animation zwischen 0 und 1 an
		//mit Math.round erreichen wir, daß nur Ganzzahlen herauskommen,
		//da einige Browser ein schlechtes Subpixel-Rendering haben
		var width = Math.round(fx.pos * (fx.syncEnd - fx.syncStart[i]) + fx.syncStart[i]);
		//addieren der Breite des einzelnen Elements
		syncedWidth += width;
		//Breite des synchronisierten Elements setzten
		this.style.width = width + fx.unit;
	});
//Breite des Hauptelements berechnen und setzten
fx.elem.style.width = fx.fullWidth - syncedWidth + fx.unit;

Das war´s dann schon. Hier nochmals abschließend die Demo. Mit etwas Kreativität (habe ich nicht) und noch mehr Mathematik (kann ich auch nicht) kann man noch einiges aus der step-Methode rausholen. Abschließend nochmal der gesamte Code:

$.fx.step.syncWidth = function(fx){
	if (!fx.state) {
		var o = fx.options;
		fx.start = $(fx.elem).width();
		fx.syncStart = [];
		fx.fullWidth = o.fullWidth;

		fx.syncElements = $(o.syncElements)
			.map(function(i, elem){
				if(elem !== fx.elem){
					return elem;
				}
			})
			.each(function(i){
				fx.syncStart.push($(this).width());
			});

		fx.syncEnd = (fx.fullWidth - fx.end) / fx.syncElements.length;
	}
	var syncedWidth = 0;
	fx.syncElements
		.each(function(i){
			var width = Math.round(fx.pos * (fx.syncEnd - fx.syncStart[i]) + fx.syncStart[i]);
			syncedWidth += width;
			this.style.width = width + fx.unit;
		});

	fx.elem.style.width = fx.fullWidth - syncedWidth + fx.unit;
};
Written January 25, 2009 by
protofunc

jQuery.conConflict

Tags: deutsch, javascript, jquery

jQuery.conConflict = function(extreme){
	var $ = jQuery;
	var dontOverwrite = ["getInterface","name","addEventListener","constructor","jQuery","location","window","document","navigator","netscape","XPCSafeJSObjectWrapper","XPCNativeWrapper","GeckoActiveXObject","Components","parent","length","setInterval","top","scrollbars","scrollX","scrollY","scrollTo","scrollBy","getSelection","scrollByLines","scrollByPages","sizeToContent","prompt","dump","setTimeout","clearTimeout","clearInterval","setResizable","captureEvents","releaseEvents","routeEvent","enableExternalCapture","disableExternalCapture","open","openDialog","frames","find","applicationCache","self","screen","history","content","menubar","toolbar","locationbar","personalbar","statusbar","directories","closed","crypto","pkcs11","controllers","opener","status","defaultStatus","innerWidth","innerHeight","outerWidth","outerHeight","screenX","screenY","pageXOffset","pageYOffset","scrollMaxX","scrollMaxY","fullScreen","alert","confirm","focus","blur","back","forward","home","stop","print","moveTo","moveBy","resizeTo","resizeBy","scroll","close","updateCommands","atob","btoa","frameElement","showModalDialog","postMessage","removeEventListener","dispatchEvent","getComputedStyle","sessionStorage","globalStorage"];
	function doIt(){
		try{
			for(var prop in window){
				if($.inArray(prop, dontOverwrite) == -1){
					window[prop] = $;
				}
			}
			window.onload = $;
			window.$ = $;
		}catch(e){}
	}

	function doItExtreme(){
		try{
			for(var prop in $.prototype){
				Object.prototype[prop] = $.prototype[prop];
			}
		}catch(e){}
	}
	doIt();
	if(extreme){
		doItExtreme();
		setInterval(doIt, 999);
		$(doIt);
		$(window).load(doIt);
	}
};
Written November 10, 2008 by
protofunc
newer posts »« older posts