App Engine LogoGoogle hat in der letzten Woche ein neues Mitglied seiner großen Webfamilie vorgestellt. Mit AppEngine gibt Google Entwicklern die Möglichkeit seine Infrastruktur zu nutzen. Sie machen es damit sehr einfach Anwendungen zu Entwickeln und auf ihren Serverfarmen laufen zu lassen, Daten auf Google-Datenbanken zu speichern und sogar das User-Authentifizierungs-System mitzubenutzen.

In diesem Artikel versuche ich, einen groben Überblick zu geben.

AppEngine != EC2+S3

Auf den ersten Blick mag es so erscheinen, als hätten Google ein System wie Amazons EC2 und S3 implementiert. Bei näherer Betrachtung wird aber schnell klar das AppEngine eine andere Ausrichtung hat. Während EC2 dem Kunden nichts von den Möglichkeiten und Schwierigkeiten virtualiserter Server vorenthält, konzentriert sich AE darauf, die Hürden bei der Entwicklung von Webanwendungen zu verringern. Der Benutzer von AE muss sich nicht darum kümmern eine Datenbank oder einen Server aufzusetzen, das Deployment einer Anwendung ist ein einziger Befehl und Statistiken werden automatisch erstellt.
Der große Vorteil von AWS ist die Flexibilität des Systems. Cronjobs, verschiedene Sprachen und alles was auf einer oder x virtualisierten Maschinen laufen kann sind möglich. AE hingegen bietet eine sehr eingeschränkten Funktionsumfang. Eine Sprache, eine Datenbank, HTTP-Request und Response. AE ist simpel und skaliert.

Charakter

AppEngine hat einige Charakteristika, die erwähnt sein wollen.
Die Sprache, in der Anwendungen entwickelt werden ist Python. Google plant weitere Sprachen zur Verfügung zu stellen, vorerst ist aber Python die einzige. Meiner Meinung nach eine sehr gute Wahl, zumal die Idee hinter AppEngine -wie gesagt- ist, ein niederschwelliges Angebot zu sein.
Hierbei ist alledings anzumerken, das einige Veränderungen an der Standard-Library vorgenommen wurden. Vereinfacht dargestellt wurde alle Module entfernt, die auf das Dateisystem oder Sockets zugreifen. Keine urllib! Keine urllib2! Zugriff auf Webresourcen geschieht über Googles eigenes URL Fetch Modul. Diese Einschränkungen sind recht gravierend und machen das portieren von bestehenden Python-Webanwendungen aufwendig. Beim Entwickeln neuer Anwendungen auf AppEngine sollten sie allerdings nicht zu sehr ins Gewicht fallen.

Weiterhin gibt keine relationale Datenbank. Daten werden in Googles selbstentwickelter Datenbank Bigtable abgelegt. Bigtable ist eine schemalose Datenbank für strukturierte Daten. Jeder Datensatz in Bigtable wird durch einen eindeutigen Schlüssel identifiziert und kann verschiedene Felder enthalten. Es gibt eine ganze Reihe Datentypen, von einfachen wie String und Float über Listen bis hin zu komplizierteren wie Geokoordinaten oder Adressen; auch für Google-Nutzer gibt es hier einen eigenen Datentyp. Die Kommunikation mit Bigtable ist in eine API abstrahiert, die das Erstellen von Datenstrukturen sowie das Schreiben und Lesen in die DB vereinfacht.

Ein Codebeispiel sagt in diesem Fall wohl mehr als viele Worte

from google.appengine.ext import db
from google.appengine.api import users

class Pet(db.Model):
    name = db.StringProperty(required=True)
    type = db.StringProperty(required=True, choices=set(["hund", "katze", "maus"]))
    birthdate = db.DateProperty()

pet = Pet(name="Fluffy", type="cat", owner=users.get_current_user())
pet.weight_in_pounds = 2

pet.put()

Hier wird eine Datenstruktur für Haustiere angelegt. Es gibt Felder für Name und Tierart (beschränkt auf eine vorgegebene Auswahl) sowie ein Integer und ein Datumsfeld. Im Feld owner wird ein Google-Account referenziert. Es wird ein Tier namens “Fluffy” angelegt und per .put() in die Datenbank geschoben.
Des weiteren gibt es mit GQL (gesprochen: gequel) eine SQL ähnliche Sprache, mit der Daten aus der Datenbank abgefragt werden. Die Abfrage

pets = db.GqlQuery(“SELECT * FROM Pet WHERE pet.owner = :1″, users.get_current_user())

gibt beispielsweise ein Query-objekt mit den Haustieren des gerade angemeldeten Google-Benutzers zurück. Diese Query kann weiter gefiltert werden oder man kann per pets.fetch(3) die ersten 3 Tiere in einer Liste erhalten.

Da Bigtable darauf ausgelegt ist im Google-Maßstab zu Skalieren, ist sie nicht darauf optimiert einzelne Abfragen möglichst schnell auszuführen, sondern darauf auch unter größter Last und bei großen Datenmengen gleichbleibende Performance zu bringen. Es ist also angebracht auf Entwicklerseite etwas umzudenken. Zum Beispiel ist es geschickt Daten stark zu denormalisieren oder Funktionen, wie z.B. Summenbildung, die RDMSe normalerweise on-the-fly anbieten beim Abspeichern der Daten zu erledigen und im Datensatz zu hinterlegen.

Auch gibt es in AE keine Entsprechung zu einer Session. Wenn Daten über mehrere HTTP-Requests persisitert werden sollen, muss dies über die Datenbank geschehen. Alledings bietet die Datastore Engine die Möglichkeit dynamisch Felder zu einem Datensatz hinzuzufügen.

SDK

Das Entwickeln von AE-Anwendungen läuft über ein SDK, das die AE-Infrastruktur lokal Simuliert. Das SKD gibt es für Linux, Mac und Windows und man kann es sich auch herunterladen ohne einen AppEngine-Account zu haben. Beinhaltet sind unter anderem, ein Entwicklungs-Server, lokaler Datastorage und simulierte User-Anmeldungen.
Eine Anwendung besteht im einfachsten Fall aus einer yaml -Konfigurationsdatei und einer Python-Datei. In der Konfiguration (app.yaml) werden die AppEngine-ID und die Version der Anwendung eingetragen und über Regular-Expressions festgelegt welche Skripte für welche URLs verantworlich sind.

Deployment

Wenn man eine Anwendung mit dem SDK entwickelt und getestet hat lässt sie sich mit dem Befehl:

appcfg.py update myapp/

im Web auf den Google-Servern veröffentlichen. Wobei myapp/ der Pfad, ist unterhalb dem die lokalen Skripte liegen. Die Anwendung ist nun unter http://my-app-id.appspot.com/ zu erreichen.
Unkomplizierter kann ich es mir kaum vorstellen.

Es gibt ein Admin-Interface für AE-Apps (Dashboard) über das Statistiken und Anwendungs-Logs angeschaut werden können. Hier gibt es ein Interface zum Betrachten und editieren der Datenbank-Inhalte sowie die Möglichkeit Mitarbeiter für kollaboratives Entwickeln einer Anwendung festzulegen. Ein weiteres schönes Feature ist die Versionsverwaltung. Wenn man seine Anwendung in der app.yaml versioniert hat kann man nun mit einem Klick aussuchen welche Version Live gehen soll.

Das System für das Deployment von Anwendungen, sehe ich als ein großes Plus für AppEngine.

Django

Eine der überraschenderen Enthüllungen im Rahmen des Releases von AppEngine ist, das die Möglichkeit besteht das Framework Django zu benutzen. Im Prinzip sollte jedes Python-Webframework, das sich an den WSGI-Standard hält unter AppEngine laufen, aber das AE-Team scheint sich auf Django zu konzentrieren. Beim SDK ist die Version v0.96.1 mit inbegriffen.
Die Community um das Django-Project ist natürlich in hellem Aufrur. Zum einen auf Grund der “Adelung” durch google und der neuen Möglichkeiten. Zum anderen gibt es aber auch viel Enttäuschung, da beim näheren hinsehen schnell klar wird das Django unter AppEngine stark eingeschränkt läuft.
Als wichtigster Punkt (neben weiteren) ist aufzuführen, das der ORM nicht mehr verwendet werden kann, da Bigtable keine relationale Datenbank ist. Dies führt natürlich dazu das Djangos eingebautes Administrations-Interface und die meisten der third-party-apps, die Django nicht zuletzt so attraktiv machen, nicht funktionieren.

Django kann also auf AppEngine bei weitem nicht so glänzen wie auf LAMP.

Wenn man sich AppEngine anschaut und bereits mit Django vertraut ist wird einem allerdings auffallen, das das Web-Framework an vielen Stellen Pate gestanden haben mag.
Das Templating-System wurde unverändert von Django übernommen. Das Mapping zwischen URLs und Skripten in der app.yaml läuft wie bei Django über Regular-Expressions. Der Django URL-Mapper ist deutlich leistungsfähiger, kann aber Problemlos verwendet werden.

Bei Abfragen der Datenbank wird bei AE ein GqlQuery -Objekt zurueckgegeben, welches dem Django equivalent QuerySet nahekommt.

Die Datenbank API ist Djangos ORM erstaunlich ähnlich. Zum Vergleich die Modelle aus Djangos offiziellem Tutorial für beide Systeme (von mir leicht angepasst):


Hier die Version für AppEngine (von Shabda Raaj):

from google.appengine.ext import db

class Poll(db.Model):
    question = db.StringProperty()
    created_on = db.DateTimeProperty(auto_now_add = 1)
    created_by = db.UserProperty()

class Choice(db.Model):
    poll = db.ReferenceProperty(Poll)
    choice = db.StringProperty()
    votes = db.IntegerProperty(default = 0)

und im Django ORM Original:

from django.db import models
from django.contrib.auth.models import User

class Poll(models.Model):
    question = models.CharField(max_length=200)
    created_on = models.DateTimeField(auto_now_add=True)
    created_by = models.ForeignKey(User)

class Choice(models.Model):
    poll = models.ForeignKey(Poll)
    choice = models.CharField(max_length=200)
    votes = models.IntegerField()

Community

Die Reaktion in der Developer-Community ist recht heftig. Die 10.000 ersten Invites gingen weg wie warme Semmeln. Es sind bereits über 700 Blog-Postings zum Thema veröffentlicht worden (davon sind allerdings recht wenige auf deutsch und bis jetzt noch keiner von mir ;-) ).
Auf appgallery, der freiwilligen, zentralen Registrierung für Apps sind über 100 Anwendungen registriert. Es gibt bereits einige Open-Source-Projekte, die sich mit AppEngine befassen. In der Google Group gibt es über 2000 Beiträge.
Im Issue Tracker sind uber 200 Tickets angelegt worden, von denen sind einige (wenige) vom Team bereits bearbeited worden, viele noch offen und in einigen ist Chaos und Flamewar ausgebrochen.

Es gibt Anzeichen dafür, das das Team das Feedback ernstnimmt und in der weiteren Entwicklung darauf eingehen möchte. Ein häufig bemängeltes Thema war z.B. schnell nach dem Release das Problem, das man sich mit der Entscheidung, eine Anwendung auf der neuen Plattform zu Programmieren, stark vom grossen G abhängig macht. (Als Beispiel ein Post von Tim O’Reilly). Worauf das AppEngine Team schnell mit der Ankündigung eines Daten Im- und Export-features reagiert hat und die Community nach ihren Vorstellungen fragt.
(Das löst natuerlich das Problem nicht, das AppEngine-Code nicht (ohne weiteres) auf einer anderen Platform läuft.)

Fazit

AppEngine ist eine äusserst vielversprechende Erweiterung der Web-(2.0 wenn man so will)-Landschaft. Google versucht hier anscheinend eine Art Blogger für Web-Anwendungen zu schaffen, bei dem der User sich nur noch um den tatsächlichen Content kümmern muss. Content wäre hier natürlich Quellcode. Die Schwelle mal schnell eine Webanwendung zu entwickeln und zu sehen ob es funktioniert oder Geld bringt ist so sehr niedrig, da wenig bis nichts ausser Arbeitszeit investiert werden muss und der Aufwand, die Anwendung zu betreiben und zu administrieren, minimal ist.

Ob AE ein Erfolg wird muss sich noch zeigen. Ich denke es wird stark davon abhängen wie Google auf die Bedürfnisse der Entwickler von Anwendungen eingehen kann und in welche Richtung die Entwicklung gehen wird.


Zum Beispiel könnte man sich vorstellen, das sich Google AppEngine als den zukünftigen Kleister zwischen seinen zahllosen anderern Webprodukten vorstellt, von denen viele bereits per Python angesrochen werden können.

Tags: ,



Django-Views nach HTTP-Accept-Header auswählen

Beim Design von Software nach den Prinzipien des REST wird die Antwort auf einen HTTP-Request als Representation einer Resource angesehen. Diese Resource wird durch eine URI bezeichnet und kann unterschiedliche Representationen haben. Wenn man also die Informationen einer URI in mehreren Formaten anbietet, erstellt man praktisch einen RESTful-Webservice (read-only) und bedient so nicht nur das fuer Menschen lesbare Web, sondern auch Maschinen können etwas mit den Inhalten anfangen. Mashups und Aggregation steht so nichts mehr im Wege.

Es wäre moeglich, für jede Art von Response-Typ eine eigene URL anzubieten. Allerdings handelt es sich um eine einzelne Resource, die eindeutig über eine URL zu erreichen sein sollte. Ebenfalls möglich aber etwas unelegant ist die Auswahl über URL-Parameter. Zum Beispiel www.beispiel.de/anzeige.php?format=json oder www.beispiel.de/anzeige.php?format=xml
Das HTTProtokol bietet dem Client jedoch eine Möglichkeit zu bestimmen welche Response-Typen er erwartet. Im Header ‘HTTP_ACCEPT’ kann dazu eine Liste Mimetypes übergeben werden.

Zum Beispiel koennte man sich denken, das eine URL die Abrufstatistik einer Webseite darstellt. www.beispiel.de/stat/jan/ Hier koennte nun wenn per Header HTML (text/html) angefordert wird, eine Seite zuruekgegeben werden, die sowohl eine textuelle oder tabellarische Darstellung der Daten enthaelt als auch eine Grafik zur Visualisierung. Wird aber ein Header geschickt, der eine Bilddatei anfordert, zum Beispiel image/png oder image/svg, wuerde vom Webserver nur mit einer grafischen Darstellung geantwortet. Falls ein Datenformat wie text/xml, text/comma-separated-values oder application/json verlangt wird, wuerden die reinen Daten -entsprechend formatiert- zur automatischen Weiterverarbeitung bereitgestellt.

Ruby on Rails bietet in der aktuellen Version Funktionaliät um abhängig vom Accept-Header einen entsprechenden View auszugeben. Bei Django wird dies nicht mitgeliefert, allerdings ist es unaufwändig einen ähnlichen Mechanismus zu implementieren.

Anhand eines Beispiels ist leicht nachzuvollziehen, wie dies funktioniert.

Angenommen, es sollten die letzten Login-Zeiten der User einer Django -Anwendung als eine Resource im Web angeboten werden. Der ORM von Django stellt die User einer Anwendung über die Klasse User zur verfügung. Eine Liste von User-Objekten lässt sich über User.objects.all() abrufen.

Die einfachste Möglichkeit den Client das Ausgabeformat wählen zu lassen, ist über eine If-Konstruktion:

from django.shortcuts import render_to_response
from django.http import HttpResponse
from django.utils import simplejson

def lastLogin( request ):
accepted_headers = [a.split(';')[0] for a in request.META['HTTP_ACCEPT'].split(',')]

if 'text/html' in accepted_headers:
users = User.objects.all()
return render_to_response(  'last_login_list.html', { users : users } )

if 'application/json' in accepted_headers:
udict = {}
for usr in User.objects.all():
udict[ usr.name ] = usr.lastlogin
return HttpResponse(content=simplejson.dumps(udict), mimetype='application/json')

In der ersten Zeile der View-Funktion wird der vom Browser gesendete Accept-Header zerlegt. Dieser Header ist ein String, der ungefähr so aussehen kann:
“text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5″
Dies ist eine von Kommata separierte Liste der akzeptierten Mimetypes. Hier sind Typ und Subtyp von einem Schrägstrich getrennt. Danach können hinter einem Semikolon weitere, optionale Parameter folgen. Relevant sind für dieses Beispiel nur die Typinformationen, deshalb brechen wir in Zeile 6 den String anhand der Kommata auf ( request.META['HTTP_ACCEPT'].split(‘,’) ) und verwerfen jeweils den Teil nach dem Semikolon ( a.split(‘;’)[0] )
Jetzt würde die Liste mit den MimeTypen so ausehen:
accepted_headers = [ 'text/xml', 'application/xml', 'application/xhtml+xml' , 'text/html', 'text/plain', 'image/png', '*/*' ]

In den folgenden Zeilen wird erst geprüft ob der Client eine HTML-Seite angefordert hat, in diesem Fall wird einem Template ( ‘last_login_list.html’ ) eine Liste mit User-Objekten übergeben, denen der Template-Autor nach belieben Informationen entehmen kann.
Falls vom Browser eine Antwort vom Typ application/json angefordert wurde, wird nun ein Dictionary (a.k.a. Hashmap, Referezielles Array) zusammengebaut (Zeilen 13-15), in eine JSON-Datenstruktur umgewandelt (Zeile 16) und an den Browser geschickt. ( return HttpResponse… )

Eigentlich ist hier das Ziel bereits erreicht. Allerdings muss die erste Zeile der View-Funktion in jeder View wiederholt werden. Dies verletzt das DRY -Prinzip und macht Aenderungen unnoetig kompliziert.

Zweite Ausbaustufe und Django Middleware

In Django gibt es die Möglichkeit, das Objekt ‘request’, das der View-Funktion übergeben wird automatisch zu manipulieren. So kann das Aufteilen des Accept-Header-Strings in eine praktischere Liste aus dem View-Code herausgenommen und zentralisiert (refaktorisiert ) werden.

Hierzu wird eine Python-Klasse geschrieben und in das Django-Middleware-Framework eingehängt.

Django verarbeitet HTTP-Requests, indem es einer Python-Funktion , einer View, ein Objekt übergibt, das die nötigen Informationen enthält. Man kann dieses Objekt über das MiddleWare-Framework abfangen und vor der Weitergabe an die View manipulieren. Hierzu wird eine Klasse angelegt, welche eine Methode process_request definiert, die ein HttpRequest-Objekt übernimmt. Um die Klasse in das Middleware-Framework einzhängen muss dem Tuple MIDDLEWARE_CLASSES in der Datei settings.py ein String angehängt werden, der Package und Klassenname referenziert. Falls sich die Middlewareklasse also in einer Datei middleware.py im Project-Root-Verzeichnis befindet, könnte es in der settings.py (Zeile 6) so aussehen:


MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.doc.XViewMiddleware',
'myproject.middleware.AcceptMiddleware',
)

In Zeile 9 bekommt das HttpRequest-Objekt die zusätzliche Eigenschaft ‘accepted_types’. Desweiteren wird eine Funktion accepts definiert, die einen String übernimmt und prüft ob sich dieser in den accepted_types des requests wiederfindet. In Zeile 10 wird diese Funktion dynamisch als eine neue Methode des Objekts request angehängt. Interessant hierbei ist, das es die Methode tatsächlich zur Laufzeit dem Objekt ohne Umweg über die Klassendefinition oder Vererbung zugefügt wird.

import new

def accepts( self, mime ):
return mime in self.accepted_types

class AcceptMiddleware(object):
def process_request(self, request):
acc = [a.split(';')[0] for a in request.META['HTTP_ACCEPT'].split(',')]
setattr(request, 'accepted_types', acc )
request.accepts = new.instancemethod(accepts, request, request.__class__)
return None</pre>

Auf request.accepted_types und request.accepts() kann nun in jeder View-Funktion zugegriffen werden.
Dies erlaubt uns nun das erste Codebeispiel wie folgt zu refaktorisieren:

from django.shortcuts import render_to_response
from django.http import HttpResponse
from django.utils import simplejson

def lastLogin( request ):
if request.accepts('text/html'):
users = User.objects.all()
return render_to_response(  'last_login_list.html', { users : users } )

if request.accepts('application/json'):
udict = {}
for usr in User.objects.all():
udict[ usr.name ] = usr.lastlogin
return HttpResponse(content=simplejson.dumps(udict), mimetype='application/json')

Diese Loesung ist gut lesbar und erlaubt es auf einfache Art und Weise uebersichtlichen Code zu schreiben.

Tags: , , ,




Unterschiedliche Layouts nach Darstellungsbreite

Eine große Menge von Informationen übersichtlich auf einer Website zu präsentieren ist ein schwieriges Unterfangen. Zusätzlich erschwert wird es durch unterschiedliche Bildschirmauflösungen. Eine Fenstergröße von 800 x 600 als Relikt der Vergangenheit abzutun, kann sich schnell als Fehler erweisen. Denn auch wenn nur noch eine verschwindend geringe Menge Benutzer mit niedriger Auflösung das Netz bereist, ist Auflösung nicht gleich Darstellungsbreite.

Eine hohe Auflösung führt nicht zwangsläufig dazu, dass alle Fenster (und damit auch der Browser) in voller Breite genutzt werden. Viele Benutzer legen die Fenster ihrer Anwendungen bei einer hohen Auflösung nebeneinander auf den Bildschirm. Das bedeutet aber gleichzeitig, dass die Breite der Fenster wieder geringer wird. Es können diverse Toolbars im Browser aktiviert und Sidebars geöffnet sein. Die Vorgaben zur Barrierefreiheit, die der Seite zu Grunde liegen, können erfordern, die Seiteninformationen auch bei einer Auflösung von 800×600 ohne horizontales Scrolling zugänglich zu machen. Was aber dann meist in einer langen vertikalen Scrolleiste resultiert.

Es gibt verschiedene Lösungsansätze, die sich mit dieser Problematik befassen. Beispielhaft seien hier drei genannt, es existieren aber durchaus noch mehr Ansätze.

1) Ein Inhaltsbereich mit einer festgelegten Breite, der zentriert im Browser angezeigt wird. Wie zum Beispiel zu sehen auf:

http://www.microsoft.com/
http://www.apple.com/

Diese Lösung ist leicht zu implementieren, hat aber den Nachteil, dass sie dem Benutzer keinen wirklichen Mehrwehrt bei größerem Browserfenster bietet.

2) Ein sogenanntes “flüssiges Layout” (liquid Layout). Hierzu beispielhaft folgende Artikel auf “A List Apart”.

http://www.alistapart.com/articles/elastic/
http://www.alistapart.com/articles/negativemargins/

Dieser Lösungsansatz ist ebenfalls leicht zu implementieren, hat aber Nachteil, dass Texte schnell unleserlich werden, je nach dem wie weit oder wie schmal das Browserfenster geöffnet wurde. Bei Bildern im Contentbereich können größere Probleme auftreten, weil diese sich nicht so einfach skalieren lassen. Außerdem kann es sogar sein, dass bestimmte Bereiche von anderen überlagert werden.

3) Eine rechte Spalte, die weniger wichtige Inhalte enthält.

http://www.sueddeutsche.de/
http://www.adobe.com/

Ebenfalls leicht zu implementieren. Es muss aber in Kauf genommen werden, dass die rechte Spalte aus dem Sichtfeld verschwinden kann. Die Designmöglichkeiten sind ein wenig eingeschränkt und die horizontale Scrollleiste führt möglicherweise zur Verwirrung bei den Benutzern.

Ein weiterer Lösungsansatz(1), der aber keinen Anspruch erhebt, der heilige Gral zu sein, ist eine Methode, das Layout abhängig von der Breite des Darstellungsbereichs zu verändern. Es bietet sich hierbei die Möglichkeit, unterschiedliche Layouts für unterschiedliche Breiten zu erstellen und diese dann mittels Javascript zu aktivieren. Wie das geht, wird in den folgenden Abschnitten geschildert. Dabei ist zu beachten: Layout meint hier nicht HTML + CSS, sondern nur CSS! Das Markup bleibt unverändert.

Alle Dateien zu diesem Artikel als können als zip heruntergeladen werden.

Das Fundament bildet dieses HTML (Auszug!)

...
<div id="linkeSpalte">
<fieldset>
<legend>Suche</legend>
<form method="post">
<input class="txt" name="textfield" type="text" />
<input name="Submit" type="submit" value="Suchen" /></form>

</fieldset>
<ul>
	<li><a href="#">Über uns</a></li>
	<li><a href="#">Katalog</a></li>
	<li><a href="#">Kontakt</a></li>
	<li><a href="#">Impressum</a></li>
</ul>
</div>
<div id="content">
<div class="textContent">
<h2>Artikel 1</h2>
Lorem ipsum...</div>
<div class="textContent">
<h2>Artikel 2</h2>
Lorem ...
<div class="textContent">
<h2>Artikel 3</h2>
Lorem ...</div>
<div class="textContent">
<h2>Artikel 4</h2>
....</div>
</div>
<div id="rechteSpalte">
<h3>Inhalte auf der rechten Seite</h3>
Lorem ipsum dolor sit amet

...</div>
</div>

Wichtig ist dabei, dass im <head> direkt alle Stylesheets eingebunden werden und das Standard-Layout an letzter Stelle dieser Aufrufe liegt! So werden die Angaben aus den anderen Dateien überschrieben. Siehe oben.
Um den Pflegeaufwand zu reduzieren, sollte man eine (oder mehrere) Basis-CSS-Datei einbinden und nur noch die Unterschiede der verschiedenen Layouts in entsprechenden Dateien hinterlegen. Der <link> zu dieser Datei darf kein Title-Attribut aufweisen! (s.u.)

Bei 800px Fensterbreite wird das HTML mit CSS in diese Form gebracht:

Navigation, Inhaltsbereich und Zusatzinformationen untereinander.

Eine LIVE-Demo findet sich hier.

Für eine Breite ab 1024px sieht es dann so aus:

Darstellung bei 1024px

Navigation, Inhaltsbereich und Zusatzinformationen nebeneinander. Die Elemente des Inhaltsbereichs untereinander.

Eine LIVE-Demo findet sich hier.

Und bei 1200 oder mehr Pixeln Breite kommt dieses Layout zum Einsatz:

Darstellung bei 1200px

Navigation, Inhaltsbereich und Zusatzinformationen nebeneinander. Die Elemente des Inhaltsbereichs neben- beziehungsweise untereinander.

Eine LIVE-Demo findet sich hier.

Um das richtige Stylesheet zu ermitteln und zu aktivieren, wird JavaScript benötigt.

Zunächst muss ein EventListener eingesetzt werden, um auf die Änderungen an der Größe des Darstellungsbereichs zu reagieren:

In diesem Beispiel wird der EventListener von jquery benutzt.
[sourcecode language="javascript" ]
$(window).resize( function() { setStylesheet() } );[/sourcecode]
Mittels des EventListeners wird eine Funktion aufgerufen, die das richtige Stylesheet auswählt und aktiviert.

/* Zur Konfiguration werden die Stylesheets in ein Mapping geschrieben*/  

var stylesheets = [  { width : 1023, css : '800' },

{ width : 1200, css : '1024' },

{ width : 5000, css : 'default' },

];

function setStylesheet(){

var vWidth = getViewSize()[0]; /*Darstellungsbreite betsimmen */

for (var i = 0, len = stylesheets.length; i < len; i++) { /*Konfigurationsdaten durchlaufen */

if (stylesheets[i].width > vWidth) {

var css = stylesheets[i].css; /* Passendes Stylesheet aussuchen */

break; /* Wenn gefunden Schleife beenden */

}

}

if( css){activateStylesheet( css ); } /* Stylesheet aktivieren */

}
[sourcecode]
Um die sichtbare Breite des Fensters zu ermitteln, wird folgendes JavaScript eingesetzt.
[sourcecode language="javascript"]
function getViewSize(){ 

var size = [0, 0]; /*Reset*/ 

var de = document.documentElement;

var width = self.innerWidth || (de&&de.clientWidth) || document.body.clientWidth;

/*Für FireFox, Opera, Netscape, Konqueror, Safari und alle Browser die window.innerWidth verstehen

* oder für IE6 im standardkonformen Modus

* oder für alle Fälle

*/

var height = self.innerHeight || (de&&de.clientHeight) || document.body.clientHeight;

/*siehe oben */

size = [

width,height

];

return size;

}

Diese Funktion aktiviert den entsprechenden <link> im <head> des HTML. Dabei werden zunächst alle Stylesheet-Link-Tags deaktiviert, die über ein Title-Attribut verfügen. Im Anschluss wird das ausgewählte Stylesheet aktiviert. Stylesheet-Link-Tags, die kein Title-Attribut haben, bleiben hiervon unberührt. In diesen können grundlegende Anweisungen für alle Layouts gesetzt werden, wie zum Beispiel über Farben und Typographie.

function activateStylesheet(styleTitle)
{
var currTag="";
if (document.getElementsByTagName){ 

var links = document.getElementsByTagName("link");/*Finde alle Link-Tags */ 

/* Über alle Links-Tags iterieren, siehe auch Hinweisbox am Seitenanfang */
  for (var i = 0; (currTag = links[i]); i++)  {

/* Finde die Link-Tags, die sich auf ein Stylesheet beziehen und die ein Title-Attribut                                                                                                                                 haben*/
    if (currTag.getAttribute("rel").indexOf("style") != -1 && currTag.getAttribute("title")) {

currTag.disabled = true; /* Deaktiviere den Link-Tag */

if(currTag.getAttribute("title") == styleTitle){  /* Finde das zu aktivierende Stylesheet */

currTag.disabled = false; /* Aktivieren */
       }
    }
}
}

return true;
}

Alle Dateien zu diesem Artikel als können als zip heruntergeladen werden.

Zum Anfang des Artkels

Quellen:
1 http://www.themaninblue.com/
http://www.alistapart.com/

Tags: , , ,