Der Interaktionsdesigner – PHP, jQuery und CSS

12. Dezember 2008 (10:10 Uhr)

TYPO3 Seiten dynamisch nachladen mit jQuery

Jetzt kommt ein herrlicher Ajaxeffekt: Beim Klick auf einen Menüpunkt wird der Inhalt sanft ausgeblendet, die aufzurufende Seite wird asynchron vom Server geladen und dem User sanft wieder eingeblendet.

Herrlich, oder? Das ist alles kein Problem mit TYPO3 und jQuery. Undzwar mit Hausmitteln, ohne ein einziges Plugin. Eine Demo gibt es auf meiner privaten Seite unter Paul-Lunow.de, die ausführliche Anleitung hier im Blog.

TYPO3 vorbereiten

In dem Beitrag zum automatischen Infofenster für TYPO3 bin ich schon auf die Technik eingegangen: Wir erstellen einen neuen Seitentyp mit Typoscript um nur die Inhalte bei einem Aufruf zu bekommen.

nurinhalt = PAGE
nurinhalt {
  typeNum = 101
}

Die neue Seite heißt nurinhalt und bekommt die Nummer 101 zugewiesen. Man achte allerdings darauf keine doppelten Nummern zu vergeben!

nurinhalt.10 < page.10

Mit dieser Anweisung wird der Inhalt von der normalen Seite in das neue Objekt kopiert.

nurinhalt.10.template.file = fileadmin/template/index-nurinhalt.html

Jetzt weisen wir der Seite ein neues Template zu. Hier kann man noch ein bisschen rumspielen und einige Extrasachen machen wenn man möchte, ist ein ganz normales Template.
In meiner Version besteht es nur aus einer Zeile:

###INHALT###

Mehr als den Inhalt will ich nicht haben.

Nach dem das Typoscript gespeichert, das neue Template erstellt und der Cache gelöscht ist, sollte man die Ausgabe einmal testen: www.meine-typo3.de/praesenz/index.php?type=101. Funktioniert? Gut.

Allerdings gibts noch eine Kleinigkeit: Die Seite kommt im kompletten HTML Gerüst daher. Zum Glück können wir das unterbinden:

nurinhalt.config {
   #entfernt diverse header angaben
   disableAllHeaderCode = 1 

   #standard doctype deaktivieren
   disableCharsetHeader = 1

   #entfern die HTML Kommentare
   disablePrefixComment = 1
}

JavaScript vorbereiten

Zurück zum "richtigen" Frontend. jQuery muss eingebunden werden und ich empfehle eine Datei Namens actions.js, für unser zu erstellendes Script. Das geschieht wieder im Standardseitentyp (bei mir stets page).

$(document).ready(function() {
	alert("Hallo!");
});

Seite neuladen und wenn wir mit einem (fröhlichen) Hallo! begrüßt werden, hats funktioniert und wir ersetzen das Alert durch unser Script.

Im ersten Schritt muss der Klick auf ein Menüpunkt abgefangen werden und verhindert das die neue Seite geladen wird:

$("#menu a").click(function() {
  url = $(this).attr("href");
  alert("Geklickt auf "+url);
  return false;
});

Drei Dinge passieren bei einem Klick auf einen Link im Objekt, welches die ID menu hat:

Der Variable url wird das Attribut href aus dem Link zugewiesen, zur späteren Verwendung (z.B. index.php?id=123).

Mit alert wird überprüft ob es funktioniert hat.

return false; verhindert das der Browser die Seite lädt. Funktionierts?

Gut! Dann laden wir den Inhalt vom Server. Dafür wird eine neue Funktion erstellt, welche eine URL erwartet und diese lädt.

$(document).ready(function() {
	//Zeug von oben...
});

function getContent(url) {
	//Hier kommt der Ajaxaufruf
}

Die neue Funktion getContent() ersetzt die Alertanweisung aus dem vorherigen Schritt. Der vollständige Aufruf sieht dann so aus:

$(document).ready(function() {
  $("#menu a").click(function() {
    url = $(this).attr("href");
    getContent(url);
    return false;
  });
});

Dann mal Ajax. Ist genauso einfach:

$.ajax({
	//erweitere aufzurufenden Link
	url: url+"&type=101",
	dataType: "html",
	//wenn es geklappt hat
	success: function(html) {
		//Inhalt reinschreiben und anzeigen
		$("#content")
			.css("display", "none")
			.html(html)
			.fadeIn();
	}
});

Das sieht doch schon wieder etwas professioneller aus. Der übergebenen url wird ein &type=101 angehängt um aus TYPO3 nur den Inhalt herauszuholen. Mit der Eigenschaft dataType zeigen wir an, dass eine HTML Seite als Rückgabewert erwartet wird.

Wenn das geklappt hat wird das Element mit der ID content ausgewählt. Dann folgt die geniale Verkettung von Funktionen:
- Das Element wird versteckt (würde auch mit hide() funktionieren)
- Das Element kriegt einen neuen Inhalt (den gerade geladenen)
- Und wird eingeblendet!

Fertig! Was für ein Erlebnis für den Benutzer. Und ohne JavaScript bleibt die Seite wie gewohnt nutzbar.

Ähmmm... nicht so ganz? Dann auf zur Fehlersuche: Der wichtigste Ansatzpunkt ist natürlich Firebug. Man aktiviert die Konsole und kann sich alle asynchronen Datenbewegungen ansehen. Vielleicht wird eine nicht vorhandene URL aufgerufen, oder der Server versteht die Anfrage nicht. Dann muss man seine Ajaxabfrage verbessern. Mehr Informationen dazu liefert die offizielle jQuery Seite (und noch dutzend andere).

Verbesserung 1: Sanftes Ausblenden

Damit der alte Inhalt nicht einfach verschwindet, benutzen wir eine Callback Funktion von jQuery: ajaxStart.

$("#content").ajaxStart(function() {
	$(this).fadeOut(function() {
		$(this).addClass("loading");
	});
});

Diese Funktion wird aufgerufen wenn eine Ajaxabfrage gestartet wird, blendet den Inhalt aus und fügt die Klasse loading hinzu was aber eigentlich nichts bringt, denn das Element ist ja ausgeblendet (aber für Vollständigkeit).

Verbesserung 2: Bookmarks und Vor- und Zurückknöpfe

Beim dynamischen nachladen hat man das Problem das sich die Adresszeile nicht ändert, der Browser also nicht mitkriegt das eine neue Seite geladen wurde. Außerdem kann man keinen Link auf eine bestimmte Seite setzen.

Das müssen wir ändern! Dafür benutzen wir Anker, die sind von der URL durch eine Raute getrennt. Mit dem JavaScript Objekt location.hash können wir die schreiben und lesen.

Als erstes schreiben: In der Success Funktion vom Ajaxaufruf fügen wir eine Zeile hinzu.

location.hash = url;

Damit kriegen wir dann folgende Adresse, nach dem ein neuer Inhalt geladen wurde:

www.meine-typo3.de/praesenz/index.php?id=123#index.php?id=321

Und diese Angabe müssen wir beim laden der Seite auch wieder abfragen:

if(location.hash) {
	x = location.hash;
	getContent(x.slice(1));
}

Ist ein Anker vorhanden, wird er der Variable x zugewiesen. Danach rufen wir unsere schon vorbereitete Funktion getContent auf und der Inhalt wird geladen!

Mit der Funktion slice(1) wird das erste Zeichen herausgeschnitten (bzw. alles andere), denn die Raute würde unser Script verwirren.

Verbesserung 3: Verwendung von RealURL

Ja, auch das ist möglich und macht die Seite noch viel schöner. Der erste Schritt dafür ist den neuen PageType über RealURL ansprechbar zu machen. Ich denke da an

www.meine-typo3.de/praesenz/start/ajax.html

Mit der Version 2.5 ist es (endlich) ein Kinderspiel: Die Datei localconf.php wird um ein paar Zeilen erweitert.

$TYPO3_CONF_VARS['EXTCONF']['realurl']['_DEFAULT'] = array(
	'pagePath' => array (
		'type' => 'user',
		'userFunc' => 'EXT:realurl/class.tx_realurl_advanced.php:&tx_realurl_advanced->main',
		'spaceCharacter' => '-',
		'expireDays' => 3,
		'rootpage_id' => '1',
	),
	'fileName' => array (
		'defaultToHTMLsuffixOnPrev' => 1,
		'index' => array(
			'ajax.html' => array(
				'keyValues' => array ('type' => 101)
			),
		),
	),
);

Anschließend sind noch ein paar Dinge im JavaScript zu verändern. Weil ich langsam genug vom Schreiben habe, kommt jetzt das komplette Script mit einigen erklärenden Kommentaren.

$(document).ready(function() {
	//Auf den ggf. übermittelten Anker reagieren
	if(location.hash) {
		x = location.hash;
		getContent(x.slice(1)+".html");
	}

	//Links per Ajax nachladen
	//$("a:not([href^='http://'])").click(function() {
	$("#menu a").click(function() {
		url = $(this).attr("href");
		getContent(url);
		$(this).blur();
		return false;
	});

	//Informieren das etwas passiert
	$("#content").ajaxStart(function() {
		$(this).fadeOut(function() {
			$(this)
				.text("")
				.addClass("loading")
				.fadeIn();
		});
	});

});

/*
 *	Holt den Inhalt vom Server
 */
function getContent(url) {
	$.ajax({
	//erweitere aufzurufenden Link
	url: url.replace(/\.html/, "/ajax.html"),
	//wenn es geklappt hat
	success: function(html) {
		//Inhalt reinschreiben und anzeigen
		$("#content")
			.removeClass("loading")
			.css("display", "none")
			.html(html)
			.fadeIn();
		//Adresszeile aktualisieren
		location.hash = url.replace(/\.html/, "");
	}
	});
}

Fazit

jQuery und TYPO3 bieten mal wieder alles an was man sich wünschen kann. Eine funktionierende Demo befindet sich auf meiner Seite.
Über Kommentare und Verbesserungsvorschläge würde ich mich sehr freuen.

Bis dahin frohes Programmieren!

Christoph 22. Juni 2009 (14:21 Uhr)

Schöne Lösung! Leider komme ich nicht auf die richtige Idee das ganze mit verschiedenen Templates zu realisieren. Jemand ne Idee!?

Paul 22. Juni 2009 (15:37 Uhr)

Wozu brauchst du denn verschiedene Templates? Du könntest einen Container um die Ausgabe wrappen und eine individuelle Klasse mitgeben – anschließend hast du mit CSS die Möglichkeit jeden Inhalt individuell darzustellen. Oder habe ich was falsch verstanden?

Christoph 26. Juni 2009 (07:58 Uhr)

ich baue momentan eine Präsenz mit verschieden gestlateten Inhalten. An diese “quick and dirty” Methode habe ich schon gedacht. Wenn irgendwie möglich hätte ich das lieber schöner gelöst. Ich würde dir gerne das Projekt zeigen, kann aber die URL nicht veröffentlichen.

Christoph 10. Juli 2009 (12:27 Uhr)

Eigentlich klappt nun alles wunderbar. Nur im IE bekomme ich kein ergebnis. das Problem scheint an dem jQuery .html() Tag zu liegen. komisch nur, dass auf http://www.paul-lunow.de alles auch im IE funktioniert :-(

Paul 10. Juli 2009 (14:23 Uhr)

Hast du überprüft ob die Seite aus validem HTML besteht? Häufig ist auch ein unvollständiger oder falscher Doctype schuld, wenn ganz normale Sachen nicht funktionieren.
Oder du deaktivierst den Effekt im IE und setzt ihnen ein Bild vor ala “Das ist ein schlechter Browser”.

Christoph 12. Juli 2009 (14:14 Uhr)

Doctype stimmt mit Paul-Lunow.de überein und alle seiten sind zu 100% Valide! wenn ich die html Variable vor dem Befehl .html() ausgebe erscheint auch immer der richtige Inhalt, mit dem richtigen HTML und allem drum und dran. es wird nur nicht eingebunden. Ich verzweifle! Safari funktioniert, Mozilla funktioniert, Chrome auch. Einzig und allein der IE mach Mucken :-(

Ich weiss nicht weiter…

Hillllllllllllllllllllllllllllllllllllffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee!!

Klaus 31. August 2009 (17:03 Uhr)

Eine wirklich klasse Idee und super beschrieben. Danke!
Ginge so etwas eigentlich auch mit TemplaVoila?

Klaus 1. September 2009 (10:48 Uhr)

Ich antworte mal kurz mir selber und ja es geht. Habe es einfach mal ausprobiert und es klappt. Typo3 ist schon ziemlich genial. Man muss in seiner TV DS nur oldStyleColumnNumber richtig vergeben und dann über styles.content.getXXX darauf zugreifen im nurinhalt-Template. Danke für die wundervolle Idee.

Robert Wildling 12. Oktober 2009 (14:41 Uhr)

Wow, Paul! Super! Einfach super! Danke, dass du das mit uns teilst! Deine Seite kommt jetzt in meine Hauptbookmarks!

asche 30. November 2009 (14:00 Uhr)

Hallo…
@Klaus
könnstest du etwas genauer erklären, wie Du das mit TV gemacht/gelöst hast?
Habe “oldStyleColumnNumber” im DS des Html Webseitentemplates sowie im DS in meinem “fce_nurinhalt.html” auf “0″ gesetzt.
Wie greife ich nun mit “styles.content.get” darauf zu?
Falls Du ein TS Snippet dazu entbehren kannst, gerne an solarkaine@web.de.
Vielen Dank

Paul 4. Dezember 2009 (10:47 Uhr)

Hi! Vielen Dank für das ganze Lob, das ist ja schön. War offensichtlich ne Weile nicht mehr hier :)
Wie das mit TV läuft kann ich leider nicht beantworten da ich damit noch nicht länger gearbeitet habe.

Aber dem IE sollte man es schon beibringen können. Bist du weiter gekommen @Christoph?

Bernhard 28. Dezember 2009 (22:32 Uhr)

Klasse, genau danach habe ich gerade gesucht! :-)

Was mir nur aufgefallen ist, dass die URL nicht automatisch mit angepasst wird, das ist doch soweit ich weiß auch möglich. Dann wäre es perfekt.
Man könnte vielleicht auch ein Ladesymbol einblenden solange der neue Inhalt noch nicht da ist.

Nadine 2. April 2010 (20:48 Uhr)

Hallo Paul,
erst mal: DANKE danke danke danke für das Tutorial… ich habe eine Seite in Bearbeitung, die mit dem ganzen hier läuft. Nur leider geht es im IE nicht. Anstatt den Link abzufangen, wird der Nutzer auf die Seite umgeleitet, die der Link ansteuert… warum habe ich leider noch nicht raus gefunden…

Christoph 28. Juni 2010 (07:24 Uhr)

Ja. Ich bin weiter gekommen, allerdings bin ich mir nicht mehr sicher wo der Fehler lag (Ich glaube es war irgend ein Sonderzeichen im Weg). Das Ergebnis ist unter http://www.brd-dkt.de zu sehen.

Vielen Dank – auch für die vielen Anderen Lösungsansätze.

Fred 14. Juli 2010 (05:15 Uhr)

Hallo TYPO3 ler,

könnt Ihr mir bitte erklären wie das mit TemplaVoila funktioniert. Bei mir wird immer die ganze Seite in den Content geladen. Den ansatz von Klaus habe ich versucht aber net wirklich verstanden ;-)

Vielen Dank

Einen Kommentar schreiben

(wird nicht veröffentlicht)

(wird veröffentlicht!)