Eine Typo3 Website mit TypoScript und jQuery mobilfähig machen

Wie man eine existierende Typo3 Website mit Flash Inhalten und festem fluidtemplate Layout nur per TypoScript und jQuery mobilfähig macht.

Grundanforderungen

Aufgabe war es, eine existierende Typo3 Seite mobilfähig zu machen und dabei die Desktopversion unverändert zu lassen. Das vorhandene Template war mit fluid realisiert, das Layout auf eine feste Breite plus Gradient zum Füllen des Hintergrundes angelegt. Zugegebenermaßen ist es manchmal einfacher, etwas von Grund auf neu zu programmieren, als vorhandenen Code zu verstehen und anpassen zu wollen. Bei einem reinen HTML Template und dazugehörigem Typoscript ist dies noch vertretbar. Beim vorliegenden Projekt kam das Template als eigene Extension Fluid daher.

Änderungen am typoscript

Generieren des mobilen dropdown Menüs

Das vorhandene Template für die Desktop Version der Seite generiert im Typoscript ein Menü mit zwei Ebenen. Die erste Ebene ist auf jeder Seite sichtbar, während bei Auswahl eines der Menüpunkte des Hauptmenüs eine zweite Ebene (Submenü) in der darunterliegenden Zeile hinzukommt. Die tiefere Navigation geschieht dann über Promoboxen mit Produkten auf den jeweiligen Seiten der zweiten Ebene.

Das Typoscript für die Desktop Navigation sieht wie folgt aus:

lib.menu = COA
lib.menu {
  10 = HMENU
  10 {
    entryLevel = 0
    1 = TMENU
    1{
      wrap = <div id="mainnav"><ul class="menu"> | </ul></div>
      NO = 1
      NO.allWrap = <li>|</li>
      ACT < .NO
      ACT.ATagParams = class="active"
    }
  }
  20 < .10
  20 {
    entryLevel = 1
    1.wrap = <div id="subnav"><ul class="menu"> | </ul></div>
  }
}

 

Für die Mobilansicht ist also ein zweistufiges Menü erforderlich, was allerdings nicht jeweils neu generiert werden soll, sondern komplett, nur eben nicht vollständig sichtbar und ausgeklappt auf jeder Seite gleich ist. Da das Menü nach dem Navigieren und auswählen eines Links eh wieder einklappen soll, wurde die Steuerung der angezeigten Navigationselemente aus dem Typoscript rausgenommen und über JavaScript bzw. jQuery im Browser nach vollständiger Übertragung des HTML Contents verlagert.

Damit brauchte man im Typoscript möglichst wenig am ursprünglichen Template zu verändern. Nach der Generierung des Haupt- und Submenüs für die Desktop Version wurde für mobil einfach noch die Struktur eines fertiges Dropdown-Menüs generiert und über CSS unsichtbar gemacht.

Das Menü besteht im Prinzip aus einem Verzeichnisbaum mit zwei Ebenen in einer Standard HTML Listen Struktur mit <ul>und <li>Elementen,  die dann per CSS gestylet werden. Das Ausklappen der 2. Verzeichnis-Ebene wird, wie bereits erwähnt, über einen onclick JS Event und Aufrufen einer entsprechenden Funktion gelöst, wobei die ID des geklickten Elements mit übergeben wird. Hierfür mussten im Typoscript noch die IDs der einzelnen Menüpunkte durchnummeriert werden, wofür eine Variable registriert und hochgezählt wurde. Das fertige Typoscript für das Dropdown-Menü sieht dann wie folgt aus:

lib.dropdownmenu = HMENU
  lib.dropdownmenu {
  ### Erste Ebene ###
    1 = TMENU
    1 {
      wrap = <ul class="mobilmenu">|</ul>
      expAll = 1

      NO {
        wrapItemAndSub = <li class="mobilmenuli">|</li>

        before.cObject= LOAD_REGISTER
        before.cObject {
          mainMenuNumber {
            cObject=TEXT
            cObject{
              value={register:mainMenuNumber}+1
              insertData=1
            }
            prioriCalc=intval 
          }

        }   
      }      

      ACT < .NO
      ACT = 1
      ACT.ATagParams = class="active"
    }

    ### Zweite Ebene ###

    2 = TMENU
    2 {
      stdWrap.wrap = <ul id="nav-{register:mainMenuNumber}" class="mobilsubmenu" >|</ul><span onclick="toggleSubMenu('nav-{register:mainMenuNumber}'); return false" class="drop-down-toggle"><span class="drop-down-arrow"></span></span>
      stdWrap.insertData = 1
      NO.allWrap = <li class="mobilsubmenuli">|</li>
    }    

}

Ersetzen des Flash Contents durch einen Slider

Abfrage des User Agent Strings

Um zu checken, ob es sich beim aufrufenden Client um ein Mobilgerät handelt, wird der vom jeweiligen verwendeten Browser verwendete User Agent String analysiert.

Im Typoscript wird eine if / else Abfrage schematisch wie folgt eingefügt

[Ausdruck = true]
 ...
 [else]

Im konkreten Beispiel wird die im typoscript useragent genannte Variable darauf hin untersucht, ob sie einen vorgegebenen String enthält, also etwa für den Begriff „iPhone“:

[useragent=*iPhone*]
 ...
 [else]

Bei der if Abfrage können logische Operatoren wie && (AND) oder || (OR) verwendet werden um verschiedene Bedingungen zu verknüpfen, also z.B.

[useragent=*iPhone*]||[useragent=*android*]
 ...
 [else]

Eine vollständige Liste von verfügbaren User Agent Strings findet man z.B. auf http://www.useragentstring.com/. Darüber hinaus gibt es eigene Dienste wie WURFL, die sich per API in Projekte in den gängigsten Programmiersprachen einbinden lassen. Die dynamische Abfrage der umfangreichen, stets aktuellen Datenbank erübrigt ein Anpassen der abgefragten User Agent Strings, ist aber ungleich komplexer bei der Implementierung.

Um die aktuell gängigsten Mobilen Endgeräte abzudecken, kann man zunächst von Produkten der Firma Apple (iPhone & iPad) sowie Android basierten Geräten diverser Hersteller ausgehen. Dies allein würde wohl mindestens 80% der eintreffenden Request von Mobilen Endgeräten erfassen. Nimmt man noch eine Auswahl von Kindle, BlackBerry, Windows Phone, etc. hinzu, dürfte man auf absehbare Zeit nahezu alle relevanten Anfragen bearbeiten können:

[useragent=*iPhone*]||[useragent=*iPad*]||[useragent=*PlayBook*]||[useragent=*BlackBerry*]||[useragent=*Kindle*]||[useragent=*Kindle Fire*]||[useragent=*Windows Phone*]||[useragent=*WebKit*]||[useragent=*IE Mobile*]||[useragent=*Opera Mobile*]||[useragent=*Opera Mini*]||[useragent=*Nokia N9*]||[useragent=*HTC*]||[useragent=*LG*]||[useragent=*iPod*]||[useragent=*Android*]||[useragent=*Windows Phone OS 7*]

Bei der Abfrage des User Agents geht es im dargestellten Beispiel gar nicht um die Darstellung der Website, sondern um den ausgelieferten Content.

Nachtrag 08.11.2017 – Da die Domain http://www.useragentstring.com/ offline zu sein scheint, kann man eine vergleichbare Liste an user agent strings finden unter:

https://udger.com/resources/ua-list

https://deviceatlas.com/blog/list-of-user-agent-strings

https://gist.github.com/enginnr/ed572cf5c324ad04ff2e

Wenn man nur einmal probeweise den user agent string des eigenen devices auslesen will, kann man auch einen Service wie etwa https://www.whoishostingthis.com/tools/user-agent/ nutzen.

Flash problematisch auf mobilen Geräten

Viele Mobilgeräte haben Probleme mit dem Darstellen von Flash (Shockwave) Elementen. Oft wird noch nicht einmal ein Platzhalter anstelle des nicht darstellbaren Flash Contents angezeigt, sondern das Element fehlt einfach. Dies kann natürlich das Layout durcheinanderbringen. Im dargestellten Fall bestand die Startseite (Home) der Website im wesentlichen aus Flash, worüber Produkte der Firma dargestellt und auch per deep link direkt anklickbar waren. Fehlt nun dieses Element, ist das User Experience natürlich ungleich schlechter und dürfte häufig zum Verlassen der Seite geführt haben, wenn Sie z.B. über ein Smartphone aufgerufen wurde.

Definition des Flash Contents in Typoscript

Im Typoscript wurde das Flash Element als Library Object (lib) definiert, in dessen value ein <div> Element samt über javascript erstelltes SWFObject (Shockwave) von fester Breite geschrieben wurde.

#lib.flashContent.20 = TEMPLATE
#lib.flashContent.20 {

lib.flashContent = TEMPLATE
lib.flashContent {

  template = TEXT
  template.value (

  <div id="flashcontent"></div>
  <script type="text/javascript">
  var so = new SWFObject("preview.swf", "preview", "845px", "453px", "9", "#111111");

 so.addVariable("XMLDataPath", "###XML_DATA###"); 
 so.addVariable("BackgroundPath", "background/bg.png"); 
 so.addVariable("BorderPath02", "graphics/TopBorder01.png"); 
 so.addVariable("BorderPath01", "graphics/BottomBorder01.png"); 
 so.addVariable("URLButtonPath", "graphics/Downloadback01.png"); 
 so.addVariable("FullscreenPath01", "graphics/FullscreenInactive01.png"); 
 so.addVariable("FullscreenPath02", "graphics/FullscreenActive01.png"); 
 so.addVariable("PreloaderPath01", "graphics/Loadingback01.png"); 
 so.addVariable("PreloaderPath02", "graphics/Preloaderback01.png"); 

 so.addParam("allowfullscreen", "true");

 so.addParam("quality", "HIGH"); 

  so.write("flashcontent");
  </script>
  )

  marks.XML_DATA = TEXT
  marks.XML_DATA {
    value = index.php?type=1&id=2 
  }
}

Einbinden des JS Slider

Da Flash für die Mobilversion nun gänzlich ungeeignet war, sollte die Flash Animation mit den Produkten der Firma durch einen Slider der gleichen Größe im Desktop Layout ersetzt werden, der sich aber beliebig skalieren lässt bis zur Fullwidth Darstellung auf einem kleinen Display mit etwa 320 px Breite.

Aus der Vielzahl von verfügbaren jQuery Slidern wurde hier die Camera Slideshow gewählt, da der Slider mit relativ geringem Aufwand und einem Set von Bildern, die passenderweise die wesentlichen Produktgruppen aus der Flash Animation darstellen, einzubauen war. Je nach Anforderungen kann man hier evtl. besser passende Slider finden, was HTML Text & Button Einbindung angeht, bzw. Effekte, Animationen und CSS3 Transistions.

Verzeichnisstruktur

Im Prinzip kommt der Slider samt JS, CSS und Images in einen Folder an beliebiger Stelle, hier wurde er im entsprechenden typo3conf/ext/ Folder des existierenden Desktop Layout Templates integriert.

Typoscript

Um das embedded SWFObject im typoscript zu ersetzen, wurde einfach das gleiche Library Object für ein – über den User Agent String erkanntes – mobiles Endgerät überschrieben, wobei das HTML Element direkt aus einer HTML Datei geholt wird


[useragent=*iPhone*]||[useragent=*iPad*]||[useragent=*PlayBook*]||[useragent=*BlackBerry*]||[useragent=*Kindle*]||[useragent=*Kindle Fire*]||[useragent=*Windows Phone*]||[useragent=*WebKit*]||[useragent=*IE Mobile*]||[useragent=*Opera Mobile*]||[useragent=*Opera Mini*]||[useragent=*Nokia N9*]||[useragent=*HTC*]||[useragent=*LG*]||[useragent=*iPod*]||[useragent=*Android*]||[useragent=*Windows Phone OS 7*]
lib.flashContent >
lib.flashContent = FILE
lib.flashContent.file = typo3conf/ext/websiteTemplate/Resources/Private/Elements/camera.html

[else]
lib.flashContent = TEMPLATE
lib.flashContent {

  template = TEXT
  template.value (

  <div id="flashcontent"></div>
  <script type="text/javascript">
  var so = new SWFObject("preview.swf", "preview", "845px", "453px", "9", "#111111");

 so.addVariable("XMLDataPath", "###XML_DATA###"); 
 so.addVariable("BackgroundPath", "background/bg.png"); 
 so.addVariable("BorderPath02", "graphics/TopBorder01.png"); 
 so.addVariable("BorderPath01", "graphics/BottomBorder01.png"); 
 so.addVariable("URLButtonPath", "graphics/Downloadback01.png"); 
 so.addVariable("FullscreenPath01", "graphics/FullscreenInactive01.png"); 
 so.addVariable("FullscreenPath02", "graphics/FullscreenActive01.png"); 
 so.addVariable("PreloaderPath01", "graphics/Loadingback01.png"); 
 so.addVariable("PreloaderPath02", "graphics/Preloaderback01.png"); 

 so.addParam("allowfullscreen", "true");

 so.addParam("quality", "HIGH"); 

  so.write("flashcontent");
  </script>
  )

  marks.XML_DATA = TEXT
  marks.XML_DATA {
    value = index.php?type=1&id=2 
  }
}
HTML & JavaScript Resources

In der HTML Datei findet sich der fertige code für den Slider, bestehend aus etwas HTML mit einem <div> Element für den Slider, der das <div>Element des Flash Content ersetzt, plus dem dazugehörigen JavaScript für den Slider und die verlinkten benötigten JS Sourcen und ein Stylesheet.

View Port

Absolut essentiell für die korrekte Darstellung der Seite auf einem mobilen Endgerät ist die Angabe des Viewport Meta Tags.

Dies wurde im TypoScript setup an recht beliebiger Stelle durch das Einfügen der Zeile

page.meta.viewport = width=device-width, initial-scale=1.0, maximum-scale=1

erreicht. Der Viewport Meta Tag sorgt für die korrekte Skalierung der Seite auf die zur verfügung stehende Bildschirmbreite des mobile devices. Ohne diesen Tag können nämlich selbst responsive Templates auf Desktopbreite gerendert und dann als Miniaturansicht runterskaliert ausgegeben werden.

Das Layout mit jQuery responsive machen

Im Prinzip wird die Seite erstmal ganz normal vom Browser geladen, wobei (wie oben beschrieben) schon über den User Agent String ermittelt wurde, ob Typo3 den Flash-Inhalt (für die Anzeige auf einem Desktop PC) oder die mobile Version mit dem JS Slider ausliefern soll.

$( document ).ready()

Der Borwser parst die Seite zunächst wie immer und erstellt aus dem HTML das DOM (Document Object Model). Ist er damit fertig, kommt jQuery ins Spiel. Der Browser meldet, dass das geladene Document „fertig“ ist und jQuery fängt diesen Event ab und kann nun innerhalb von $(document) .ready() JavaScript Code ausführen. Dies hätte man natürlich genauso gut mit JavaScript und entsprechnden Event Handler wie load machen können.

Sobald das Document geladen ist und das DOM erstellt wurde, wird zunächst eine Kopie des aktuellen Zustands gespeichert, um später ohne Neuladen diesen Zustand ggf. wiederherstellen zu können. Dies geschieht in einer JavaScript function storeContentDiv(). Man hätte hier auch die ganze Seite speichern können, es genügt hier aber der Seiteninhalt ohne Header, Footer und Navigation.

Als zweites wird eine JavaScript function ausgeführt, die die Darstellung der Seite im Browser an die verfügbare Breite anpasst. Diese Funktion heist hier setToScreenSize().

jQuery(document).ready(function($) {
   storeContentDiv();
   setToScreenSize();
 });

$( window ).resize()

So lässt sich per jQuery feststellen, ob die Fenstergröße bzw. Darstellungsgröße der Seite sich geändert hat. In diesem Fall kann wiederum JavaScript code ausgeführt werden. Dieser Fall kann z.B. auftreten, wenn auf einem Smartphone oder Tablet die Seite im Hochformat oder Querformat angezeigt wird, bzw. die Orientierung geändert wird. Und natürlich, wenn man im Desktop-Browser die Fenstergröße ändert.

In der dargestellten Lösung wird in diesem Fall wieder die JavaScript function setToScreenSize() ausgeführt.

$( window ).resize(function() {
  setToScreenSize(); 
});

storeContentDiv()

Die function speichert im Prinzip nur einen Node in eine zuvor definierte Variable und kommt im Doppelpack mit einer function, die den Inhalt wiederherstellt, restoreContentDiv()

var contentDiv = null;

 function storeContentDiv(){
   contentDiv = $('#content').html();
 }

 function restoreContentDiv(){
   $('#content').html(contentDiv);
 }

setToScreenSize()

Diese function macht im wesentlichen lediglich eine Unterscheidung anhand mehrerer zuvor definierter Breakpoints (hier exemplarisch), wie die Seite über CSS darzustellen ist.

Es gibt weiterhin eine function resizeImages() , die die auf der Seite dargestellten Bilder (fixe Abmessungen) skaliert und auf die Breite des mobilen Endgerätes anpasst.

Die dargestellten CSS Änderungen sind nur exemplarisch und lassen sich beliebig auf alle benötigten CSS Änderungen im DOM ausdehnen. Um passende Werte für einzelne Elemente zu ermitteln, eignen sich die gängigen Developer Tools aktueller Browser vorzüglich, die meist unter [F12] zu finden sind.

var breakpoint1 = 400;
var breakpoint2 = 600;
var breakpoint3 = 900;
function setToScreenSize(){
  if( ($(window).width() < breakpoint2) || ($(document).width() < breakpoint2) ) {
    resizeImages();
    $('#mainnav').css("display", "none");
    $('#subnav').css("display", "none");
    $('#mobil').css("display", "none");
    $('#footer').css("max-width", "100%");
    $('#logo').css("max-width", "100%");
    ...
  }
  else if( (($(window).width() >= breakpoint2)&&($(window).width() < breakpoint3)) || (($(document).width() >= breakpoint2)&&($(document).width() < breakpoint3)) ){
    restoreContentDiv();
    $('#mainnav').css("display", "block");
    $('#subnav').css("display", "block");
    $('#mobil').css("display", "none");
    $('#container').css("max-width", breakpoint2);
    $('#mainnav').css("max-width", breakpoint2);
    $('#subnav').css("max-width", breakpoint2);
    $('#footer').css("max-width", breakpoint2);
    $('#logo').css("max-width", breakpoint2);
    ...
  }
  else{
    storeContentDiv();
    $('#mainnav').css("display", "block");
    $('#subnav').css("display", "block");
    $('#mobil').css("display", "none");
    $('#container').css("max-width", breakpoint3);
    $('#mainnav').css("max-width", breakpoint3);
    $('#subnav').css("max-width", breakpoint3);
    $('#footer').css("max-width", breakpoint3);
    $('#logo').css("max-width", breakpoint3);
    ...
 }

Mit jQuery lässt sich das CSS des DOMs prima auslesen bzw. neu schreiben.

Drop-Down-Menu für mobile Navigation togglen

Um das Menu für die mobile Navigation (Smartphones) auszuklappen, bedient man sich am einfachst der OnClick Events, die im TypoScript in die Navigations-Struktur geschrieben wurden. Die entsprechenden Funktionen schalten im Grunde hauptsächlich die Sichtbarkeit einzelner HTML Knoten per CSS.

  $('.nav-show').click(function(){
    if( $('.mobil'.css("display") == "none") ){
      $('.mobil').css("display", "block");
    }
    else{
      $('.mobil').css("display", "none");
    }
  });

  function toggleMenu(){
    if( $('#mobil').css("display") == "none" ){
      $('#mobil').css("display", "block");
    }
    else{
      $('#mobil').css("display", "none");
    }
  }
  function toggleSubMenu(menuItem){
    if( $('#'+menuItem).css("display") == "none" ){
      $('#'+menuItem).css("display", "block");
    }
    else{
      $('#'+menuItem).css("display", "none");
    }
  }

resizeImages()

Diese function liest eigentlich nur die aktuellen Abmessungen aus dem CSS des DOM und schreibt einige CSS Parameter für die dargestellten Bilder per jQuery neu

function resizeImages(){
  $('#content img').each(function(){
    var newImageWidth = $('#content').width();
    var oldImageWidth = $(this).width();
    var oldImageHeight = $(this).height();
    var newImageHeight = (oldImageHeight/oldImageWidth)*newImageWidth;
    $(this).css("width", newImageWidth);
    $(this).css("height", newImageHeight);
    $(this).css("margin-bottom","10px");
  });
}