Teil von SELFHTML aktuell Teil von Artikel Teil von JavaScript

Übergroße Tabellen
in koordiniert scrollenden Frames

nach unten Gernot Back
nach unten Das Problem
nach unten Das Frameset
nach unten Das JavaScript
nach unten Die Tabelle

Gernot Back

E-Mail: E-Mail gernotback@arcor.de
Homepage-URL: deutschsprachige Seite http://www.akadaf.de/

Bei Fragen zu diesem Beitrag bitte den Autor des Beitrags kontaktieren!

nach obennach unten

Das Problem: Übergroße Tabellen

Wer kennt das nicht? Beim Lesen von Informationen aus Tabellen kann man leicht die Übersicht verlieren, egal, ob es sich dabei um einen Busfahrplan, einen betrieblichen Urlaubsplaner oder eine Übersicht von Funktionsträgern eines Sportvereins handelt. Zellenbegrenzungslinien und zeilenweise abwechselnde Hintergrundfarben sind ja schon hilfreich, aber das reicht nicht immer aus.

Zur Orientierung in einer übergroßen Tabelle fährt man trotzdem gerne mal mit dem Finger über Zeilen und Spalten, wenn es sich dabei um eine Printversion handelt. Aber was, wenn es sich um eine Online-Version-handelt? Wer hat schon gerne Fingerabdrücke auf dem Bildschirm?

In den meisten Tabellenkalkulationsprogrammen ist das Bildschirmproblem wunderbar gelöst: Man wählt zum Beispiel einfach die erste Zelle aus, die horizontal wie vertikal scrollen darf und klickt dann im Menü auf "fixieren". Schon wird alles, was horizontal links oder vertikal oben vor dieser Zelle steht, als Überschrift erkannt und scrollt nur in einer Richtung; die horizontalen Tabellenüberschriften scrollen nur horizontal und die vertikalen Tabellenüberschriften scrollen nur vertikal mit. Auf diese Weise weiß man im eigentlichen Informationbereich immer, worum es geht.

Exportiert man jedoch solch eine Datei als Webseite, so geht dieser Effekt verloren: Beim Scrollen nach unten und rechts geraten die Überschriften oben und links aus dem Blickfeld.

Wie leicht eine tabellarische Übersicht den Anzeigebereich eines Bildschirms sprengen kann, wird anhand des folgenden Screenshots deutlich. Dargestellt ist eine Übersicht der Vorstandsmitglieder einer Freiwilligen Feuerwehr aus den Jahren 1952 bis 2003.

Screenshot

Obwohl der Screenshot mit einer üppigen Bildschirmauflösung von 1280 * 1024 Bildpunkten im Vollbildmodus aufgenommen wurde, liegt das letzte verzeichnete Jahr 2003 bereits außerhalb des dargestellten Bereichs. Schon bei der nächstkleineren, aber immer noch luxuriösen Bildschirmauflösung von 1152 * 864 Pixeln, die auf dem Screenshot mit dem äußersten roten Rahmen markiert ist, würden bei Betrachtung der letzten Jahre die Namen der Vorstandsmitglieder vollkommen aus dem Blickfeld geraten.

In der Senkrechten ist die Tabelle auch bei größtmöglicher Auflösung noch nicht einmal zur Hälfte sichtbar. Scrollt man nach unten, so sieht man die Jahreszahlen nicht mehr.

Im Folgenden soll eine Lösung für dieses Problem beschrieben werden: Dabei wird die übergroße Tabelle jeweils ausschnittweise in vier verschiedenen Frames eines Framesets eingeblendet, deren Scrolling je nachdem, ob es sich dabei um einen Überschriftenframe handelt oder nicht, über JavaScript in bestimmten Richtungen koordiniert bzw. verhindert wird.

Andere Lösungen, die hier nicht beschrieben werden, kämen zwar vielleicht ohne Frames, aber wohl kaum ohne JavaScript aus. Der hier verfolgte Ansatz, zunächst einen Link auf die Tabelle als solche zu setzen, um dann mit JavaScript auf die koordinierte Variante weiterzuleiten, wäre aber übertragbar.

Da übergroße Tabellen meist nicht nur von ihren Ausmaßen her sehr groß sind, wird in diesem Artikel auch ein Lösungsansatz zur Verringerung des Datenumfangs mittels CSS vorgestellt, der allerdings nicht unbedingt auf jede Tabelle übertragbar sein dürfte.

nach obennach unten

Das Frameset

Unsere Tabelle soll über Kreuz in vier Hauptbereiche unterteilt werden:

  1. einen großen Bereich unten rechts, in dem man nach Herzenslust waagerecht wie senkrecht scrollen kann,
  2. zwei schmale Bereiche oben rechts und unten links, die als Überschriften jeweils koordiniert mit dem großen Bereich unten rechts nur horizontal bzw. vertikal scrollen,
  3. einen ganz kleinen Bereich oben links, der immer fest stehen bleibt und nur ein Logo oder die Legenden-Titel der Seite enthält.

Um Besuchern unserer Seite, die JavaScript in ihrem Browser deaktiviert haben, zumindest die komplett statische Variante nicht vorzuenthalten, geben wir als Link grundsätzlich zunächst die Datei tabelle.html selbst an. Ein kleines Skript direkt am Anfang der Tabellen-Datei sorgt aber im Regelfall dafür, dass sofort auf das Frameset weitergeleitet wird, in dem dieselbe Tabelle in unterschiedlichen Ausschnitten und mit dem oben beschriebenen unterschiedlichen Scrollverhalten gleich viermal angezeigt wird. Netter Nebeneffekt dieser Technik: Auch Suchmaschinen, die ansonsten mit Framesets ihre Schwierigkeiten haben, erhalten erst einmal ihr Futter.

Popup-Seite Anzeigebeispiel: So sieht's aus
(Link führt auf tabelle.html, wird aber weitergeleitet auf frameset.html.)

Das Weiterleitungs-Skipt sieht folgendermaßen aus:

Beispiel:

<script  type="text/javascript">
<!--
if (document.layers) {
 window.location.replace("leer.html");
} else {
 if(self == parent) {
  window.location.replace("frameset.html");
 }
}
//-->
</script>

Erläuterung:

Da es im vorliegendem Fall erforderlich war, die Tabelle durch CSS-Angaben zu verschlanken, die von älteren Netscape-Browsern nicht interpretiert werden, müssen wir uns leider von den wenigen verbliebenen Nutzern dieser Browser gleich am Anfang verabschieden. Das muss aber bei anderen übergroßen Tabellen, die mit weniger Datenvolumen auskommen, nicht unbedingt so sein. Allerdings müssten im Folgenden alle JavaScript-Methoden entsprechend an die Gegebenheiten dieser älteren Netscape-Browser angepasst werden. Anstelle der Methode document.getElementById() müsste dann z.B. das bereichsübergreifende Seite layers-Objekt verwendet werden. Allerding ist es im Netscape 4 nicht möglich, bereichsübergreifende Seite Frames pixelgenau anzugeben, daher dürfte es sowieso schwierig werden, diesen Ansatz auf Netscape 4 zu übertragen.

Falls es sich bei dem verwendeten Browser nicht um Netscape 4.x handelt, wird auf die Datei frameset.html weitergeleitet. Auf diese Weise finden auch Suchmaschinen, die Weiterleitungen ignorieren, auf jeden Fall den eigentlichen Inhalt in tabelle.html.

Die Weiterleitung erfolgt nur dann, wenn die Datei tabelle.html nicht bereits in ein Frameset oder speziell in unsere Datei frameset.html eingebunden ist. Dies wird über die Bedingung if(self == parent) sichergestellt. Diese Einschränkung ist zwingend erforderlich, da sich ansonsten die geladene Datei tabelle.html über Rückleitung auf frameset.html wie eine russische Matroschka-Puppe in einer Endlosschleife ständig selbst neu gebären würde.

Im hier präsentierten Fall können wir in aller Regel davon ausgehen, dass bei auch noch so hoher Bildschirmauflösung die Tabelle weder vertikal noch horizontal voll darstellbar ist. Es muss also höchstwahrscheinlich gescrollt werden, um alle Informationen zu sehen.

Zum Ausgleich der Breite bzw. der Höhe der Scrollbalken im Frame unten rechts ist es daher erforderlich, zusätzlich zu den oben bereits erwähnten vier Frames zwei kleine leere Frames einzufügen.

Screenshot

Popup-Seite Anzeigebeispiel: So sieht's aus

Der zugehörige Quellcode sieht wie folgt aus:

Beispiel:

<html>
<head>
<title>Dynamisches Frameset</title>
<script type="text/javascript">
<!--
siehe Das JavaScript
//-->
</script>
</head>
<frameset  onLoad="init()" onResize="balken()" framespacing="0" frameborder="0" cols="178,*">
   <frameset id="links" rows="44,*,13">
     <frame marginheight="0" marginwidth="10" scrolling="no" name="obLi" src="tabelle.html">
     <frame marginheight="0" marginwidth="10" scrolling="no" name="untLi" src="tabelle.html">
     <frame scrolling="no" src="leer.html">
   </frameset>
   <frameset rows="44,*">
     <frameset id="oben" cols="*,13">
        <frame marginheight="0" marginwidth="10" scrolling="no" name="obRe" src="tabelle.html">
        <frame scrolling="no" src="leer.html">
     </frameset>
     <frame marginheight="0" marginwidth="10" scrolling="auto" name="untRe" src="tabelle.html">
   </frameset>
</frameset>
</html>

Erläuterung:

Vorliegender Code beschreibt ein über eine Hierarchie von zwei Stufen ineinander verschachteltes Frameset, ein sogenanntes "nested frameset". Bei allen Frames werden die Scrollbalken mit scrolling="no" unterdrückt, außer beim letzten.

Die Angaben zu marginheight und marginwidth in den Frame-Elementen sind erforderlich, um die ansonsten in diesem Punkt unterschiedlichen Default-Werte verschiedener Browser zu harmonisieren. Die Werte können aber ganz nach Geschmack gewählt werden und müssen dann nur mit dem Versatz, der nach unten später in der Javascript-Initialisierungsfunktion init() eingestellt wird, in Einklang gebracht werden.

Die Attribute frameborder und framespacing im obersten Frameset-Element sind kein HTML-Standard. Alternativ könnte man das Attribut frameborder="0" in jedem einzelnen Frame angeben. Wer jedoch auch in Gecko-basierten Browsern wie Mozilla keine Lücken in seinem Frameset haben möchte, darf auf das standardwidrige Attribut framespacing="0" im obersten Frameset-Element nicht verzichten.

Vgl. hierzu: bereichsübergreifende Seite Rahmendicke bzw. unsichtbare Fensterrahmen
sowie englischsprachige Seite DTD Fragment for Frames

Alle Frames, in denen Inhalte angezeigt werden sollen, erhalten einen Namen entsprechend ihrer Position im Frameset, also "obLi" für den Frame oben links, "obRe" für den Frame oben rechts und so weiter. In alle vier Inhaltsframes wird die Datei src="tabelle.html" geladen.

An dieser Stelle sei Detlef G. aus dem bereichsübergreifendes Kapitel SELFHTML-Forum ganz herzlich gedankt, dessen Unterstützung bei der Programmierung entscheidend zur Umsetzung der vorliegenden Version ohne Searchstrings beitrugen. Damit muss die Datei tabelle.html nur noch einmal und nicht mehr viermal in den Browser-Cache geladen werden, was die Ladezeit und den Speicherbedarf entsprechend verringert.

nach obennach unten

Das JavaScript

Leider kann der folgende JavaScript-Code nicht über eine externe JS-Datei in die Datei frameset.html eingebunden werden, da es ansonsten zu Fehlermeldungen kommt, wenn onLoad die Initialisierungsfunktion init() aufgerufen wird, diese aber noch nicht geladen ist. Er wird daher direkt im Head-Bereich des Framesets definiert.

Beispiel:

function init () {
  untLi.document.getElementById('cont').style.top = "-44px";
  obRe.document.getElementById('cont').style.left = "-167px";
  untRe.document.getElementById('cont').style.left = "-167px";
  untRe.document.getElementById('cont').style.top = "-44px";
  balken();
  scrollen();
  for(i = 0; i < frames.length; i++) {
      frames[i].document.onkeydown=MyFocus;
  }
  MyFocus();
}

var aktFrame = "untRe";

function MyFocus(){
  aktFrame = "untRe";
  untRe.focus();
}

function balken () {
  if(document.all) {
    var breite = untRe.document.body.clientWidth;
    var hoehe = untRe.document.body.clientHeight;
    breite = breite + ',*';
    hoehe = '44,' + hoehe + ',*';
    document.all.oben.setAttribute('cols', breite ,'false');
    document.all.links.setAttribute('rows', hoehe ,'false');
  } else {
    var breite = 0;
    while(untRe.outerWidth<obRe.outerWidth) {
      breite++;
      br = '*,' + breite;
      document.getElementById('oben').setAttribute('cols', br ,'false');
    }
    var hoehe = 0;
    while(untRe.outerHeight<untLi.outerHeight) {
      hoehe++;
      ho = '44,*,' + hoehe;
      document.getElementById('links').setAttribute('rows', ho ,'false');
    }
  }
}

function hor(MyFrame) {
  if(document.all)
    return MyFrame.document.body.scrollLeft;
  else
    return MyFrame.pageXOffset;
}

function ver(MyFrame) {
  if(document.all)
    return MyFrame.document.body.scrollTop;
  else
    return MyFrame.pageYOffset;
}

var sc; // Deklaration nur bei Mozilla-Workaround erforderlich (optional)

function scrollen () {

  switch (aktFrame) {

   case "untRe":
     obRe.scrollTo(hor(untRe), 0);
     untLi.scrollTo(0, ver(untRe));
     break;

   case "untLi":
     untRe.scrollTo(hor(untRe), ver(untLi));
     obRe.scrollTo(hor(untRe), 0);
     untLi.scrollTo(0, ver(untLi));
     break;

   case "obRe":
     untRe.scrollTo(hor(obRe), ver(untRe));
     untLi.scrollTo(0, ver(untRe));
     obRe.scrollTo(hor(obRe), 0);
     break;

   case "obLi":
     untRe.scrollTo(hor(obRe), ver(untLi));
     obLi.scrollTo(0, 0);
     break;

   default:
     obRe.scrollTo(hor(untRe), 0);
     untLi.scrollTo(0, ver(untRe));
     aktFrame="untRe";
  }
  // Beginn Mozilla Workaround (optional)
  if(!document.all&&!document.layers) {
    sc = window.setTimeout("scrollen()", 83);
  }
  // Ende Mozilla Workaround
}

Erläuterung:

Die Initialisierungsfunktion init ()

Sobald das hierarchisch oberste Frameset-Element in der Datei frameset.html volltändig, d.h. mit allen Kind- und Kindeskind-Elementen (Frames) vollständig geladen ist, wird die Initialisierungsfunktion init() aufgerufen. (<body onLoad="init()">)

Die Funktion init() bestimmt dann , um wie viele Pixel jeder einzelne der Frameset-Vierlinge von tabelle.html nach oben und/oder nach links ins negative "Off", also in einen nicht angezeigten Bereich versetzt wird.

Dies wird dadurch ermöglicht, dass die gesamte Tabelle sich in tabelle.html innerhalb eines absolut positionierten DIV-Containers befindet, der über das Attribut ID="cont" mit der Methode document.getElementById('cont') anzusprechen ist.

Handelt es sich zum Beispiel um den Frame unten rechts, der in der Datei frameset.html als tabelle.html eingebunden ist, so wird die Tabelle in unserem Beispiel um 167 Pixel nach links und um 44 Pixel nach oben verschoben wird. Diese Werte entsprechen dort der Breite der ersten Tabellenspalte, die ja bereits im Frame unten links angezeigt wird und der Höhe der ersten beiden Tabellenzeilen, die bereits im Frame oben rechts angezeigt wird.

Die Werte für die Verschiebung des Anzeigebereichs wurden durch Interpolation über "Trial and Error" ermittelt. Diese ließen sich natürlich auch über document.getElementById('ersteSpalte').offsetWidth und document.getElementById('ersteZeile').offsetHeight berechnen. Hierauf wurde aber in unserem Beispiel der Einfachheit halber verzichtet.

Entsprechendes gilt übrigens auch für die Bestimmung der Attribute cols, rows und marginwidth, die sich auf diese Weise auch über Variablen im Query-String mit document.write() schreiben bzw. über setAttribute() nachträglich setzen ließen. Letzteres passiert ja auch in der Funktion balken(). Im vorliegenden Fall wurde aber auch aufgrund der Probleme, die ältere Browser (so z.B. auch noch Opera 7.11) dabei haben, die "Topfschlagen-Methode" mit "warm-kälter-wärmer-heiß" gewählt, - solange, bis es passte.

Die Anpassung der Scrollbalken-Imitationsframes mit der Funktion balken()

Die vorliegende Riesen-Tabelle mit dem Feuerwehrvorstand soll nur als Beispiel dienen. Im Einzelfall kann es vorkommen, dass eine Tabelle je nach Bildschirmauflösung in der Breite und/oder Höhe doch einmal ganz auf den Bldschirm passt und dann auch keine Scrollbalken erscheinen.

Dann muss dafür Sorge getragen werden, dass die kleinen schmalen Scrollbalken-Imitationsframes entsprechend verschwinden, damit die Überschriften in den Frames unten links bzw. oben rechts diesen Platz mitbenutzen können. Auch kann die Scrollbalkenbreite je nach Browser und/oder dort gewähltem Erscheinungsbild ("Theme") um einige Pixel von den üblichen 13 abweichen.

Die Funktion balken() wird erstmalig über die Initialisierungsfunktion init() aufgerufen. Die Funktion balken() unterscheidet zunächst zwei Typen von Browsern; solche, die das document.all-Objekt verstehen, wie z.B. den Microsoft Internet-Explorer und Opera und die übrigen, bei denen dies nicht der Fall ist, wie z.B. Gecko-basierte Browser wie Mozilla.

Bei erstgenannten Browsertypen werden innerhalb der Funktion zwei Variablen breite und hoehe angelegt und mit den Werten document.body.clientWidth bzw. document.body.clientHeight belegt.

Diese numerischen Werte spiegeln die innere Breite bzw. Höhe wider, die das Body-Element im Dokument einnimmt. Bei dem fraglichen Dokument handelt es sich um den Frame "untRe" (unten rechts). Es wird dessen innere Breite bzw. Höhe in Pixeln gemessen, also abzüglich etwaiger Fenster-Außenrahmen und Scrollleisten.

Exakt diese Maße sollen nun auf die jeweiligen Überschriftenframes "untLi" (unten links) und "obRe" (oben rechts) übertragen werden. Die Breite und Höhe dieser Frames ist in den Frameset-Elementen in der Datei frameset.html nach oben mit Default-Werten in den Attributen cols bzw. rows vorbelegt, wobei zunächst von einer Scrollbalkenbreite (oder -höhe) von 13 Pixeln ausgegangen und dem Überschriftenframe mit * der restliche zur Verfügung stehende Platz zugewiesen wird:

<frameset id="oben" cols="*,13"> und
<frameset id="links" rows="44,*,13">

Um die Werte in den Überschriftenframes an die tatsächlichen Verhältnisse im Hauptanzeigeframe "untRe" anpassen zu können, wurden die entsprechenden Framesets mit einem id-Attribut versehen: In mit id versehenen Elementen lassen sich über die Methode setAttribute() die den sonstigen Attributen zugewiesenen Werte nachträglich ändern. Als Übergabewerte erwartet die Methode setAttribute() drei durch Komma getrennte Zeichenketten:

  1. die Bezeichnung des neu zu setzenden Attributs
  2. den neuen Wert (auch numerische als Zeichenkette!)
  3. eine Boole'sche Aussage darüber, ob bei der Erkennung der Attributsbezeichnung die Groß-undKleinschreibung relevant sein soll oder nicht (also 'true' oder 'false', ebenfalls als Zeichenkette!)

Vgl. hierzu: bereichsübergreifende Seite all.setAttribute()

Da die Attribute cols bzw. rows ohnehin nicht nur die Breite bzw. Höhe eines Frames als Wert enthalten, sondern durch Komma getrennt auch diejenigen von Nachbarframes, werden die bisher numerischen Werte der Variablen breite und hoehe davor und/oder dahinter um entsprechende Zeichenketten ergänzt, wodurch die Variable nun insgesamt eine Zeichenkette enthält. Die Variable kann nun also der Methode setAttribute() als zweiter Wert übergeben werden.

Während zuvor dem Scrollleisten-Ergänzungsframe im Frameset der feste Wert von 13 Pixeln zugewiesen wurde, und dem jeweiligen Überschriftenframe der Rest des zur Verfügung stehenden Platzes, tauschen beide Frames nun ihre Rollen:

breite = breite + ',*';
hoehe = '44,' + hoehe + ',*';

Es wird zunächst das jeweilige Überschriftenframeset mit der id="oben" bzw. id="links" angesprochen. Dem Überschriftenframe wird dabei über die Methode setAttribute() die im Hauptanzeigeframe "untRe" gemessene Breite oder Höhe als fester Pixelwert zugewiesen und dem Scrollbalkenergänzungsframe über * der Rest des zur Verfügung stehenden Platzes. Bei fehlenden Scrollleisten im Frame quot;untRe" beträgt dieser Platz0 Pixel.

document.all.oben.setAttribute('cols', breite ,'false');
document.all.links.setAttribute('rows', hoehe ,'false');

Für die übrigen Browser (diejenigen, die das document.all Objekt nicht verstehen) musste ein anderer Ansatz gewählt werden, da viele der Gecko-basierten Browser wie Mozilla auch die Werte document.body.clientWidth und document.body.clientHeight häufig nicht korrekt ermitteln.

Als Alternative bieten sich hier die Eigenschaften innerWidth, outerWidth, innerHeight und outerHeight des window-Objektes an. Allerdings entsprechen diese Werte, wenn sie sich auf Frames beziehen, nicht unbedingt genau den in den cols- und rows-Attributen eingestellten Werten eines Frameset-Elements, offenbar je nachdem, ob die darin enthaltenen Frames an einen Fenster(-außen-)rahmen grenzen oder nicht.

Deshalb ist hier der Ansatz folgender: Bei der Beanspruchung des mit * zugewiesenen Platzes wird auf den "Rollentausch" zwischen den Frames verzichtet: Die Breite oder Höhe der Scrollbalken-Ergänzungsframes wird über die Variablen breite bzw. hoehe zunächst auf 0 gesetzt. Der jeweilige Überschriftenframe behält den Rest, erhält also zunächst den gesamten in den Framesets mit den Attributen id="oben" bzw. id="links" zur Verfügung stehenden Platz.

Dann wird überprüft, ob die im Hauptanzeigeframe "untRe" gemessene innere Weite innerWidth genauso groß wie der entsprechende Wert im Überschriftenframe "obRe" ist. Wenn es im Frame unten rechts keinen vertikalen Scrollbalken gibt, passiert nichts und der Ergänzungsframe bleibt ebenfalls auf einer Breite von 0 stehen. Solange aber die im Frame unten rechts gemessene innere Weite kleiner als die im oberen Überschriftenframe gemessene Weite ist, wird die Breite des Ergänzungsframes in einer while-Schleife über die Methode setAttribute() um jeweils einen Pixel erhöht.

Entsprechend wird beim Abgleich der inneren Höhen innerHeight zwischen den Frames "untRe" (unten rechts) und "untLi" (unten links) verfahren:

var hoehe = 0;
while(innerHeight<parent.frames['untLi'].innerHeight) {
  hoehe++;
  ho = '44,*,' + hoehe;
  document.getElementById('links').setAttribute('rows', ho ,'false');
}

Wird die Fenstergröße geändert, so ändern sich auch die Verhältnisse im Frameset: Scrollbalken erscheinen oder verschwinden im unteren rechten Frame. Für diejenigen Browser, die das document.all-Objekt verstehen, muss aufgrund des Rollentauschs bei der Beanspruchung des mit * zugewiesenen Platzes auch der feste Pixelwert für die Überschriftenframes neu ermittelt werden. Der Abgleich muss daher bei jeder Fenstergrößenänderung neu erfolgen. Dies geschieht über den Event-Handler onResize im obersten Frameset der Daei frameset.html, der die Funktion balken() ggf. neu aufruft: onResize="balken()"

Die Ermittlung der Scrollposition über hor() und ver()

Den Funktionen hor() und ver() wird als Übergabeparameter ein Frame als Objekt übergeben. Dafür liefern sie als Rückgabewert mit der je nach Browser unterschiedlichen Microsoft- bzw. Netscape-Syntax jeweils den horizontalen bzw. vertikalen Offset-Wert zurück, der sich durch Scrollen im entsprechnenden Frame ergeben hat. Der Browser Opera versteht beide Syntaxen, sowohl die Schreibweise document.body.scrollLeft und document.body.scrollTop einerseits als auch window.pageXOffset und window.pageYOffset andererseits. Die einfachste Art, den Möglichkeiten der unterschiedlichen Browsertypen gerecht zu werden, ist daher, eine Fallunterscheidung anhand des document.all-Objektes zu treffen, über das Opera und der Microsoft Internet-Explorer verfügen, Gecko-basierte Browser wie Mozilla hingegen nicht.

Der Offset-Wert beschreibt nichts anderes als die Anzahl der Pixel, um die die Seite nach links bzw. oben ins nicht mehr sichtbare Off versetzt wird. Der Versatz, den wir je nach Frame mit der Initialisierungsfunktion init() durch Verschieben des absolut positionierten Div-Containers bewirkt haben, bleibt dabei unberücksichtigt, da er nicht durch Scrollen zustande gekommen ist: Alle Frames starten daher beim Laden sowohl horizontal als auch vertikal mit einem Wert von 0 und behalten den zusätzlichen Div-Container-Offset auch während des späteren Scrollens mit der Offset-Koordinierungsfunktion scrollen() bei.

Vgl. hierzu: bereichsübergreifende Seite window.pageXOffset

Die Offset-Koordinierungsfunktion scrollen()

Der Aufruf der Scroll-Koordinierungsfunktion scrollen() erfolgt immer dann, wenn der Nutzer in einem der Vierlings-Frames scrollt, die mit der Datei tabelle.html gefüllt sind. Dies wird in tabelle.html durch den im <body>-Tag definierten Eventhandler onScroll="parent.scrollen()" bewirkt.

Die Scrollleisten sind in der Datei frameset.html in allen Frames ausgeblendet, außer im Frame unten rechts mit dem Attribut name="untRe".

Ein Scrollen mittels Cursor-Tasten bzw. Mausrad, wird aber durch diese Maßnahme in den übrigen Frames nicht in allen Browsern unterbunden. Wenn diese den Fokus erhalten, d.h. wenn man z.B. mit der Maus in die betreffenden Frames hineinklickt, kann auf diese Weisen dann u.U. auch dort gescrollt werden.

Deshalb muss für jeden Frame mit Inhalt (und nicht nur für den Frame unten rechts) eine Entscheidung getroffen werden, wie er sich im Falle des Scrollens verhalten soll. Dabei werden die Geschwister-Frames ihren Namen entsprechend als Objekte angesprochen. Welches der zurzeit aktive Frame ist, wird über die globale Variable aktFrame als switch (also Schalter) entschieden, die jeweils eine entsprechende Zeichenkette enthält.

Der Mozilla-Workaround

So weit, so gut, aber die Sache hat einen Haken: In Gecko-basierten Browsern wie Mozilla, Netscape 6, Netscape 7 oder Firefox sowie im Safari ist das onScroll-Event zwar implementiert, bezieht sich dort aber nur auf das Scrollen mit dem Balkenschieber. Scrollt man hingegen mit den Cursor- oder Bildlauftasten oder mit dem Mausrad, so feuert der Eventhandler dort nicht, die Koordinierunsfunktion scrollen() wird nicht aufgerufen und man kannn dann die Frames unabhängig voneinander verschieben.

Es handelt sich um einen seit Anfang 2003 bei Mozilla.org bekannten Fehler und dieser scheint auch erst in einer späteren Version aufgetaucht zu sein.

Vgl. hierzu: englischsprachige Seite Mozilla Bug #189308

Um Nutzern von Gecko-basierten Browsern wie Mozilla den Anblick des bei Maus- und Tastatur-Scrollen nicht koordinierten Framesets zu ersparen, wird die Funktion scrollen() bei den entsprechenden Browsern nicht nur durch das Event onScroll aufgerufen, sondern auch einmal nach dem Laden des Frames aus der Initialisierungsfunktion init() heraus. Dabei bleiben natürlich zunächst alle Frames auf ihrer Ursprungsposition (0, 0) stehen.

Am Ende der Funktion scrollen() ruft sich diese, falls es sich um einen der problematischen Browser handelt (d.h. er weder das document.all noch das document.layers-Unterobjekt versteht), über sc = window.setTimeout("scrollen()", 83); nach 83 Millisekunden erneut selbst auf. Vorsichtshalber wird das Timeout an eine Variable gebunden, die zuvor global definiert wurde, damit nicht ständig neue Intervalle eröffnet werden, die die Rechnerleistung sonst im Verlauf der Sitzung erschöpfen könnten.

Die Funktion scrollen() stellt so auch ohne einen Aufruf durch den Eventhandler onScroll in Abständen von 83 Millisekunden sicher, dass die Scrollpositionen in den einzelnen Frames koordiniert werden. Das Intervall von 83 Millisekunden entspricht dabei einer Bildrate von 12 pro Sekunde, die man von Flashfilmen gewohnt ist.

Fokussierung der Frames über Eventhandler

Um Scrollen mittels Bildlauf- und Cursortasten unabhängig davon zu ermöglichen, welcher der Frames gerade den Fokus hat, wird dieser immer auf den Frame unten rechts gesetzt, sobald eine Taste gedrückt wird, denn nur dort ist Scrolling ja grundsätzlich möglich. Dies geschieht dadurch, dass in der Funktion init() ein entsprechender Evendhandler onkeydown für jeden einzelnen Frame der Datei frameset.html gesetzt wird. In der Funktion myFocus() wird dann sowohl die Variable aktFrame, die die Zeichenkette zur Identifizierung des zurzeit aktiven Frames enthält, als auch der Fokus bei Tastendruck auf den Frame unten rechts gesetzt.

Bei Loslassen der Taste wird die Variable aktFrame aber sofort wieder auf den Frame gesetzt, über dem sich der Mauszeiger gerade befindet. Dies ist erforderlich, um ein Scrollen über das Scrollrad oder ein Markieren mit der Maus zu gewährleisten und wird in der Datei tabelle.html über den Eventhandler onmouseover="parent.aktFrame=window.name;" bewirkt, der dort im <body>-Tag definiert ist.

Quirksmode für den IE

Der Internet Explorer interpretiert das Beispiel leider nur im Quirksmode richtig, im Standards Mode versagen sowohl der IE6 als auch der IE7. Das Vorhandensein der XML-Deklaration reicht beim IE6 bereits aus, damit er in den Quirksmode geht, für den IE7 muss man vor das DOCTYPE noch ein zusätzliches Kommentar hinzufügen. Es ist natürlich nicht ideal, dass der Internet Explorer das Beispiel nicht auch im Standards Mode korrekt darstellen kann, allerdings ist derzeit keine andere Möglichkeit bekannt.

nach obennach unten

Die Tabelle

Übergroße Tabellen sind meist nicht nur von ihren Ausmaßen auf dem Bildschirm her übergroß, sondern auch gemessen an ihrem Datenvolumen, besonders dann, wenn sie nicht mit einem HTML-Editor erstellt, sondern aus einem Tabellenkalkulationsprogramm exportiert wurden. Die hier präsentierte, ursprünglich aus dem Microsoft-Office-Programm Excel heraus generierte HTML-Datei hatte eine Größe von 289 Kilobyte.

Da die Datei bei ihrer Darstellung in einem dynamischen Frameset - wenn auch nur teilweise - gleich in vier Frames eingeblendet wird, die dann auch noch im Scrolling koordiniert werden müssen, ist es nur allzu verständlich, dass manche Rechner mit geringer Leistung hier bei manchen Browsern schlapp machen.

Komprimierungsmaßnahmen sind daher unumgänglich. Einige HTML-Editoren, so etwa auch Macromedia Dreamweaver, sind in der Lage, einen Großteil des aus Office-Programmen exportierten Datenschrotts vollautomatisch zu entfernen. Das vorliegende Beispiel wurde jedoch "von Hand" gesäubert. Dabei wurde die Anzahl der CSS-Klassen von ursprünglich 44 auf 11 reduziert und die Lines of Code (LOC) von 6047 auf 630.

Letzteres gelang vor allem dadurch, dass anstelle von jeweils 53 einzelnen Tabellenzellen pro Zeile durch Zusammenfassung mittels Column-Span-Attribut colspan - abgesehen von der ersten Tabellenzeile - nun nur noch höchstens vier Zellen pro Zeile definiert werden. Die für die Übersichtlichkeit hilfreichen Zellenbegrenzungslinien wurden dabei in den zusammengefassten Zellen mit einem teiltransparenten GIF als Hintergrundkachel imitiert.

Popup-Seite Anzeigebeispiel: So sieht's aus
(Link führt auf tabelle.html, wird aber weitergeleitet auf frameset.html, deshalb zur Anzeige des Quellcodes eines Frames bitte Kontextmenü über rechte Maustaste benutzen)

Beispiel:

<html>
<head>
<title>Feuerwehr-Vorstand</title>
<script  type="text/javascript">
<!-- // siehe Weiterleitungsfunktion -->
</script>
<link href="tabelle.css" rel="stylesheet" type="text/css"></link>
</head>
<body onScroll="parent.scrollen()" onmouseover="parent.aktFrame=window.name;">
<div id="cont">
<table border="0" cellpadding="0" cellspacing="0">
 <tr>
  <td class="titel">Position Jahr</td>
  <td class="zahl">52</td>
  <td class="zahl">53</td>
  <td class="zahl">54</td>

  ...

  <td class="zahl">01</td>
  <td class="zahl">02</td>
  <td class="zahl">03</td>
 </tr>
 <tr>
  <td colspan="53" class="frei">&nbsp;</td>
 </tr>
 <tr>
  <td class="oben">Erster Vorsitzender</td>
  <td colspan="52" class="obRe">&nbsp;</td>
 </tr>
 <tr>
  <td class="links">Fritz Weitzel</td>
  <td colspan="3" class="ja">&nbsp;</td>
  <td colspan="49" class="rechts nein">&nbsp;</td>
 </tr>
 <tr>
  <td class="links">Georg Zimmermann</td>
  <td colspan="2" class="nein">&nbsp;</td>
  <td colspan="12" class="ja">&nbsp;</td>
  <td colspan="38" class="rechts nein">&nbsp;</td>
 </tr>

 ...

 <tr>
  <td class="unten">Kurt Schmidberger</td>
  <td colspan="47" class="zahl">&nbsp;</td>
  <td colspan="5" class="untRe ja">&nbsp;</td>
 </tr>
 <tr>
  <td colspan="53" class="frei">&nbsp;</td>
 </tr>

 ...

</table>
</div>
</body>
</html>

Erläuterung:

Einzig die erste Tabellenzeile besteht aus 53 einzelnen Zellen. Der ersten Zelle der ersten Zeile wurde die CSS-Klasse "titel" zugewiesen, in der u.a. ein Wortabstand word-spacing von 60px und ein Schrift-Gewicht font-weight von 700 definiert ist.

Die 52 folgenden Tabellenzellen, die die Jahreszahlen von (19)52 bis (20)03 enthalten, gehören der CSS-Klasse "zahl" an. In dieser Klasse wird eine zentrierte Schriftausrichtung über text-align:center sichergestellt.

Die zweite Zeile der Tabelle, die wie weitere Zeilen zum Absetzen der thematischen Blöcke leer bleibt, gehört der gleichnamigen CSS-Klasse "leer" an, in der nur der untere Rahmen definiert ist. Die Leere Abstandszelle ersteckt sich mit einem colspan von 53 über die gesamte Tabelle. Ganz leer darf allerdings auch eine "leere" Zelle nicht bleiben. Da manche Browser ansonsten auch die zugewiesenen Stile wie Rahmen und Hintergrund nicht anzeigen, ist es erforderlich, in jede "leere" Tabellenzelle zumindest ein nicht-umbrechendes Leerzeichen &nbsp; zu setzen.

Bei den die eigentliche Information übermittelnden Zellen handelt es sich paradoxerweise ebenfalls um Zellen, die nichts weiter als ein nicht-umbrechendes Leerzeichen &nbsp; enthalten. Der Zeitraum, in dem eine Person Funktionsträger war, sowie derjenige davor und/oder danach, wird über eine entsprechende Spaltenspannweite (column-span) mit dem Attribut colspan definiert. Je nachdem, ob es sich um eine Amtszeit handelte oder nicht, gehört die Zelle entweder der CSS-Klasse "ja" oder "nein" an. Für beide Klassen ist zur Imitation der Zellenbegrenzungslinien ein teiltransparentes GIF-Image als Hintergrundkachel über background-image:url(zelle.gif); definiert. Zusätzlich wird bei der Klasse "ja" die graue Hintergrundfarbe über background:silver; definiert, die durch die transparenten Bereiche des GIFs durchscheint und so die eigentliche Information über die Amtszeit eines Funktionsträgers übermittelt.

Befindet sich eine Zelle mit Information über Amts- und Nicht-Amtszeiten gleichzeitig am rechten oder unteren Rand eines Tabellenblocks, so muss ihr gleichzeitig ein Stil zur Definition des Rahmens zugewiesen werden. Solchen Zellen wird eine doppelte Klassenzugehörigkeit zugewiesen, die Klasse ja oder nein und eine weitere, wie z.B. rechts, um den Außenrahmen zu definieren. Die doppelte Klassenzuweisung erfolgt innerhalb von Anführungsstrichen und getrennt durch ein Leerzeichen z.B. über class="rechts nein".

Vgl. hierzu: deutschsprachige Seite Tipps und Tricks: Elementen mehrere Klassen zuweisen

Auszugsweise werden im Folgenden aus der Datei tabelle.css einige Klassen exemplarisch erläutert:

Klasse Erläuterung
#cont {
  position:absolute;
}
Über ID-Selektor zugewiesene CSS-Eigenschaft des die gesamte Tabelle umschließenden DIV-Containers. Die absolute Positionierung ist erforderlich, um diesen je nach Frame um die Breite der ersten Spalte bzw. die Höhe der ersten Zeilen versetzen zu können.
body {
  background: #FFFFFF;
}
Zuweisung einer Hintergrundfarbe, die sich von derjenigen unterscheidet, die in der informationstragenden Klasse .ja definiert ist. (Farbzuweisung kann auch an table erfolgen, ist aber wichtig, weil im Browser ansonsten möglicherweise dieselbe Farbe als Defaultwert gewählt wird wie die in der Klasse .ja definierte.
table {
  border-color:#000000;
  width:1px;
  height:1px;
}
Zuweisung einer Rahmenfarbe, wird auf die Rahmenfarbe der Tabellenzellen td vererbt, wenn dort nichts anderes definiert wird. Definition kann aber auch unter td erfolgen. Um die Ausdehnung der Tabelle sowohl in ihrer Breite, als auch in ihrer Höhe immer auf das Minimum zu begrenzen, wird ihr eine Breite und Höhe von einem Pixel zugewiesen, die sie mit ihrem Inhalt freilich immer sprengt. Dies ist erforderlich, um sicherzustellen, dass die Zellenimitationskacheln auch dann an die tatsächlichen Zellenbegrenzungen anschließen, wenn das Fenster einmal breiter oder höher als die Tabelle selbst ist. Im Linux-Browser Konqueror kann diese Maßnahme allerdings kontraproduktiv wirken, da die Anweisung white-space:nowrap; für TabellenZellen dann nicht umgesetzt wird.
td {
  height:21px;
  padding:2px;
  color:#000000;
  font-size:10.0pt;
  font-family:Arial, sans-serif;
  white-space:nowrap;
}
Da alle Tabellenzellen bezüglich Zeilenhöhe, Innenabstand, Schriftfarbe, -größe und -art ein einheitliches Aussehen haben sollen, werden ihnen mit dem Typ-Selektor entsprechende Werte zugewiesen. Bei Größenangaben kommen nur absolute Werte wie Punkt pt und px in Frage, nicht aber relative Werten wie em und en, da ansonsten bei nutzerseitiger Schriftgrößenänderungen die Verhältnisse nicht mehr stimmen und das Gesamtbild der Tabelle zerstört wird. Da bei der gewählten Höhendefinition der Zelle nicht mehr als jeweils eine Zeile hineinpasst, muss mit white-space:nowrap auch der Zeilenumbruch verhindert und stattdessen ein Ausdehnen der Zelle in die Breite erzwungen werden.
.titel {
  font-weight:700;
  word-spacing:60px;
  border-bottom:2px solid;
  border-right:1px solid;
}
Die Legenden-Überschriften der ersten Spalte und der ersten Zeile, von denen diese durch Trennlinien unten bzw. rechts abgegrenzt sind, sollen recht fett und in einem gehörigen Wortabstand erscheinen.
.zahl {
  width:21px;
  text-align:center;
  border-right:1px solid;
  border-bottom:2px solid;
  background-image:url(zelle.gif)
}
Da die Jahreszahlen in der ersten Zeile die Breite der darunter stehenden, "leeren" nur über ihre Hintergrundfarbe Information tragenden Zellen vorgeben, werden sie einheitlich auf eine ausreichende Breite von 21 Pixeln gesetzt. Die Textausrichtung der Jahreszahlen wird in der Zelle zentriert. Nach rechts werden die Zellen dieser Klasse von der nächsten mit einem Rahmen abgegrenzt, ebenso nach unten von Leerzeilen. Da die Klasse auch in den jeweils letzten Zeilen eines Blocks als Unterkante verwendet wird, wo Zellen zwar keine Jahreszahlen enthalten, aber mit dem colspan-Attribut zusammengefasst werden, wird dieser Klasse zusätzlich die Hintergrundkachel background-image:url(zelle.gif) zugewiesen.
Die Hintergrundkachel hat eine Breite von 19 Pixeln und eine Höhe von 21 Pixeln. Damit entspricht sie der Breite der Klasse .zahl inklusive rechtem Rahmen und der Höhe, die für Tabellenzellen td definiert ist.
.untRe {
  border-right:2px solid;
  border-bottom:2px solid;
  background:silver;
  background-image:url(zelle.gif);
}
Die Tabellenzelle, die sich innerhalb eines jeden Tabellenblocks unten rechts befindet, erhält als Außenbegrenzung einen doppelt so dicken Rahmen wie er als Innenbegrenzung zu einer rechten Nachbarzelle, etwa in der Klasse .zahl definiert ist. Da dieser Zellentyp immer den letzten Amtsträger markiert, werden zusätzlich die CSS-Eigenschaften der Klasse ".ja" zugewiesen. Auf die Erläuterung der übrigen Zellen mit Außen- und/oder Innenrahmendefinition kann hier verzichtet werden.
.ja {
  background:silver;
  background-image:url(zelle.gif);
}
Durch die Hintergrundfarbe, die sich vom Dokument und der Tabelle abhebt, signalisiert die Zugehörigkeit einer Tabellenzelle zu dieser Klasse: "Ja", die Person, die als Funktionsträger in der ersten Geschwisterzelle dieser Zeile genannt wird, war in dem unter der entsprechenden Jahreszahl genannten Zeitraum im Amt. Durch die Zuweisung einer Zeilenspannweite über das colspan-Attribut werden mehrjährige Amtszeiten definiert. Dadurch wird eine beträchtliche Datenmenge und gleichzeitig Rechnerbelastung eingespart. Die durch das Zusammenfassen nicht mehr über Rahmen-Definitionen darstellbaren Zellenbegrenzungslinien werden durch die teiltransparente Hintergrundkachel imitiert. Gleiches gilt für Zellen, die der Klasse .nein angehören und sich von der Klasse .ja nur durch das Fehlen der Hintergrundfarbe unterscheiden, wodurch sie Nicht-Amtszeiten signalisieren.

Doch, ein CSS noch als Schmankerl: Das ist aber inline definiert, und zwar im <body>-Tag der Datei leer.html:

style="background:scrollbar", damit wird sichergestellt, dass die kleinen schmalen Frames mit den Array-Nummern 2 und 4 die gleiche Hintergrundfarbe tragen wie die Scrollbars, deren Verlängerung sie imitieren sollen.

Im Browser Opera funktioniert die farbliche Übereinstimmung aber nur, wenn man diesen als Benuzer unter Datei -> Einstellungen -> Dekoration auf "windows_skin" stellt. Bei Gecko-basierten Browsern wie Mozilla stimmt es nur, wenn man unter Anzeigen -> Theme anwenden "Classic" wählt.

Teil von SELFHTML aktuell Teil von Artikel Teil von JavaScript

© 2007 E-Mail redaktion@selfhtml.org