Das ConventionCamp 2011 in Hannover
Am 8. November 2011 findet zum vierten Mal die Internet-(Un-)Konferenz ConventionCamp in Hannover statt und heute startet der Vorverkauf der Eintrittskarten. Die Vergangenheit hat gezeigt, dass ein Besuch der Veranstaltung durchaus lohnenswert ist, weshalb ich an dieser Stelle jedem empfehlen kann, sich eines der Early-Bird-Tickets zu sichern. Denjenigen, die nicht wissen, worum es sich beim ConventionCamp handelt, sei außerdem obiges Video ans Herz gelegt. In 180 Sekunden wird alles Wesentliche – begleitet von gefälliger 3D-Animation – erklärt.
(Disclaimer: Ich arbeite regelmäßig auf Honorarbasis für die w3design. GmbH, Veranstalter des ConventionCamps.)
Open Transport Tycoon Deluxe – Der Klassiker als Open-Source-Version
Ich weiß nicht, wie oft ich mich schon darüber beschwert habe, dass es den Klassiker “Transport Tycoon Deluxe” nicht mehr gibt und scheinbar vergleichbare Spiele heutzutage nicht mehr entwickelt habe. In meiner Jugend habe ich einen gewissen Teil meiner Zeit in dieses Spiel investiert und neben “Civilization”, “Sim City 2000” und “Die Siedler” ist es definitiv einer meiner Alltime-Favorites. Auch wenn ich sonst kein großer Gamer bin.
Was jedoch komplett an mir vorüber gegangen ist, ist die Tatsache, dass “Transport Tycoon Deluxe” seit 2004 als Open-Source-Projekt existiert und für Mac OS X, Linus und Windows erhältlich ist. Das ganze firmiert unter der Bezeichnung OpenTTD. Wenn das kein Grund zur Freude ist. Ich kann jedem, der sich für Simulationen und Strategiespiele interessiert und bisher nichts mit TTD zu tun hatte, nur empfehlen, das Spiel mal anzutesten.
Die Grafik ist schwer 90'er, aber das sollte in Zeiten wie diesen, wo Menschen in “Blockbuster”-Games mit ausgeprägter Pixelgrafik quantenphysikalische Experimente unter Verwendung digitaler Hühnern nachstellen, kein Problem sein, denke ich…
Link: OpenTTD dans les webs
Studie “Backboned”: AJAX-powered WordPress-Theme mit Backbone.js

Um mir gelegentlich etwas Zerstreuung vom Lernen zu geben, habe ich einen lange gehegten Plan in die Tat umgesetzt: Ein AJAX-betriebenes WordPress-Theme mit Backbone.js zu bauen. Und zwar keine auf “Graceful Degradation” setzende Kompromisslösung. Alle Inhalte werden asynchron geladen und sind per Hashbang URIs ansteuerbar. Ein Blick in den Quellcode offenbart, was ich meine: Ein JSON-Objekt mit allen grundlegenden Daten, eine Handvoll jQuery-Templates und das HTML-Grundgerüst. Das war es an statischem Content - die Darstellung des Inhalts geschieht über Backbone.js.
Damit Suchmaschinen nicht außen vor bleiben und man sich nicht die Mühe machen muss, einen “Headless Browser” à la HtmlUnit auf seinem Server zum laufen bringen zu müssen, werden grundsätzlich alle Inhalte als GET-Anfrage mit dem Parameter “_escaped_fragment_” abgehandelt - die Ausgabe variiert dann je nachdem zwischen statischem HTML oder einem nackten JSON-Objekt. So ist sichergestellt, dass die Inhalte trotzdem indiziert werden können. Die einzigen, die in die Röhre schauen, sind Besucher ohne JavaScript.
Um diesen Workaround zu realisieren, war jedoch ein hohes Maß an Improvisation vonnöten. Mit WordPress-Bordmitteln habe ich es nicht geschafft, das Frontend-seitige URL-Routing von Backbone.js server-seitig abzubilden und entsprechend zu bearbeiten. Ich habe deshalb auf ein simples MVC-Pattern zurück gegriffen und in das eigentliche Theme eine Art Child-Theme integriert. Das ist insgesamt kein Ansatz der mir - besonders in meiner Umsetzung - gefällt. Des weiteren muss man für einen störungsfreien Betrieb des Themes das URL-Rewriting in den WordPress-Einstellungen deaktivieren.
Darüber hinaus bleibt anzumerken, dass das Theme insgesamt eher rudimentär ist. Ich würde von einem Produktiveinsatz abraten. Allerdings bin ich grundsätzlich von der Idee des Themes überzeugt und freue mich natürlich, wenn jemand sich ebenfalls dafür begeistern kann und daran weiterarbeitet. Gerne auch in Kollaboration mit mir. Zu tun gibt es unter anderem noch:
- die grundsätzliche Verbesserung des PHP-Codes (sicherer machen, besser in das WordPress-Environment integrieren,…)
- den Funktionsumfang erhöhen (Neueste Kommentare, Tags, Suchfunktion, Sidebar-Widgets(?),…)
- das JavaScript straffen (Performance, geschmeidigere GUI-Abläufe,…)
…um ein paar Aspekte zu nennen.
Ansonsten freue ich mich wie immer über Anregungen und Verbesserungsvorschläge - gerade bei einem Vorhaben dieser Größenordnung hat man als Entwickler nicht wirklich einen umfassenden Überblick.
PS: Wahrscheinlich werde ich das Teil zeitnah bei GitHub reinladen. Muss mich da aber erst noch anschlauen.
Update: Okay, die Geschichte ist jetzt auch auf GitHub - https://github.com/herschel666/Backboned. Viel Spaß.
JavaScript Garden: Das geballte Wissen auf einer Seite
Kleinere Beiträge zum Thema "Worauf man beim JavaScript schreiben achten sollte" gibt es mittlerweile ja wie Sand am Meer im Interweb. Zusätzlich hat Douglas Crockford mit seinem Buch "JavaScript: The Good Parts" eine umfassende Sammlung geschaffen, hinsichtlich der Fallstricke und Annehmlichkeiten, die JavaScript bietet.
Wer allerdings den Kauf des Buches scheut und die Mühen, die das Finden der vielen kleinen, guten Beiträge mit sich bringt, vermeiden möchte, dem sei der JavaScript Garden ans Herz gelegt. Dort hat Ivo Wetzel eine ansehnliche und stetig wachsende Dokumentation der "most quirky parts of the JavaScript programming language" geschaffen.
Ich habe den Garten bisher lediglich kurz überflogen, daher gibt es die Empfehlung hier nur mit Vorbehalt. Ich bin aber zuversichtlich, dass da ein Quell an nützlichen Informationen angelegt wurde. Also guckt mal vorbei.
Gefunden bei bb-applications
Backbone.js-Tutorial: Die Merkliste

Backbone.js ist ein interessantes JavaScript-MVC, mit welchem ich mich seit etwa zwei Wochen beschäftige. Und nun möchte ich ein kleines Tutorial dazu präsentieren - wir bauen uns ein Merkliste. Das Konzept sieht wie folgt aus: Ständig laufen einem tolle Filme, Bücher und Spiele über den Weg, die man unbedingt noch sehen/lesen/spielen möchte, aber man merkt sie sich nie. Das ist der Punkt, wo die Merkliste ins Spiel kommt.
Bei der Programmierung habe ich mich stark an der Todo List Application orientiert, allerdings ist die Merkliste vom Funktionsumfang her schmaler, beinhaltet dafür aber einen Controller für das URL-Routing.
Aber nun zur Sache:
Als erstes benötigen wir den HTML-Teil.
HTML
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Backbone.js-Tutorial - Merkliste</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<ul id="nav"></ul>
<input type="text" placeholder="Gib einen Titel ein…" id="list_input" />
<ul id="list"></ul>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script src="http://ajax.cdnjs.com/ajax/libs/underscore.js/1.1.6/underscore-min.js"></script>
<script src="http://ajax.cdnjs.com/ajax/libs/backbone.js/0.3.3/backbone-min.js"></script>
<script src="js/backbone-localstorage.js"></script>
<script src="js/list.min.js"></script>
<script type="text/template" id="list-item-template">
<strong><%= title %></strong>
<span class="delete_item">x</span>
</script>
<script type="text/template" id="nav-template">
<a href="#/category/<%= title %>"><%= title %></a>
</script>
</body>
</html>
Für die Merklisten-App benötigen wir jQuery, Underscore.js, Backbone.js und die Backbone-Erweiterung Local-Storage, damit die Einträge im Browser gespeichert werden können. Des weiteren werden die Container für die Navigation und die Listeneinträge, sowie das Eingabefeld angelegt. Schlussendlich brauchen wir noch zwei Templates - eins für die Navigation, eins für die Liste.
Kommen wir nun zum JavaScript-Teil. Als erstes benötigen wir hier unser Model:
JavaScript
window.List = Backbone.Model.extend();
Als nächstes erstellen wir zwei Collections, eine für die Navigation, eine für die Listeneinträge:
JavaScript
window.NavCollection = Backbone.Collection.extend({
model : List
});
window.ListCollection = Backbone.Collection.extend({
model : List,
localStorage : new Store('List'),
getByCategory : function ( category )
{
return this.filter( function (item)
{
return item.get('category') == category;
});
}
});
In der Collection für die Listen-Einträge wird der Local Storage angemeldet und eine Funktion eingefügt, die es ermöglicht, die Einträge der Collection nach ihrer Kategorie zu filtern.
Kommen wir nun zum Controller der App:
JavaScript
window.ListController = Backbone.Controller.extend({
_navModel : new NavCollection([
{title : 'Filme'},
{title : 'Buecher'},
{title : 'Spiele'}
]),
_navViews : [],
_categoryModel : new ListCollection,
_inputView : null,
routes : {
'' : 'init',
'/category/:category' : 'getItems',
},
initialize : function ()
{
this._navModel.each( function ( item, i )
{
this._navViews[i] = new NavigationView({
model : item
});
}, this);
Backbone.history.start();
},
init : function ()
{
window.location.hash = '/category/Filme';
},
getItems : function ( category )
{
for ( view in this._navViews )
{
this._navViews[view]
.render()
.setClass();
};
if ( this._inputView == null )
{
this._inputView = new ListInputView({
model : this._categoryModel,
category : category
});
}
else
{
this._inputView.options.category = category;
this._inputView.model.trigger('refresh');
}
}
});
Als erstes werden - jeweils für die Navigation und die Liste - neue Instanzen der zugehörigen Collection erstellt und Platzhalter für die jeweiligen Views angemeldet. Danach werden die relevanten Pfade mit Funktionen verknüpft. In diesem Fall wird die Funktion init() ausgeführt, wenn kein Hash vorhanden ist, und die Funktion getItems(), wenn ein Kategorie-Hash vorhanden ist.
Als nächstes folgt die initialize-Funktion, welche als erstes beim Aufrufen des Controllers ausgeführt wird. Dabei wird das Navigation-Model mit den nötigen Einträgen versehen, danach für jeden Eintrag der Navigation-Collection eine View-Instanz erstellt und im _navView-Array gespeichert, sowie die Backbone.history-Funktion gestartet.
Als nächstes wird die init-Funktion definiert. Diese sorgt einfach nur dafür, dass die Kategorie "Filme" gewählt wird, indem der entsprechende Kategorie-Hash gesetzt wird. Die Kategorie ist hierbei willkürlich von mir gewählt.
Danach folgt die Definition der getItems-Funktion. In dieser werden als erstes die Views für die Navigationspunkte ge-rendert. Daraufhin folgt entweder die Initialisierung des Views für die Listeneinträge, oder - falls dies schon geschehen ist - das Überschreiben der aktuellen, dem View übergebenen Kategorie und das Neu-Aufbauen der Liste mit den entsprechenden Einträgen. Dabei kommt die Filter-Funktion der Listen-Collection zum Einsatz.
Nachdem nun Model, Collection und Controller vorhanden sind, geht es an die Views. Davon benötigen wir drei - einen für das Eingabefeld, einen für die Liste und einen für die Navigation. Fangen wir mit dem Eingabefeld an:
JavaScript
window.ListInputView = Backbone.View.extend({
el : $('#list_input'),
list : $('#list'),
events : {
'keypress' : 'createListItem'
},
initialize : function ()
{
_.bindAll(this, 'addListItem', 'addAllListItems');
this.model.bind('add', this.addListItem);
this.model.bind('refresh', this.addAllListItems);
this.model.fetch();
},
createListItem : function (e)
{
if ( e.keyCode == 13 )
{
this.model.create({
category : this.options.category,
title : this.el.val()
});
this.el.val('');
this.el.blur();
}
},
addListItem : function ( item )
{
var view = new ListItemView({model : item});
!view.model.length && this.list.append( view.render().el );
},
addAllListItems : function ()
{
this.list.empty();
_.each(this.model.getByCategory(this.options.category), function(item)
{
this.addListItem(item);
}, this);
}
});
Der ListInputView nimmt die Eingabe entgegen, erstellt eine neue Instanz des Listen-Eintrag-View und fügt füllt die Liste mit Einträgen. Die Funktion createListItem() erstellt einen neuen Eintrag in der Collection, wenn das Eingabefeld abgefeuert wurde. Über die Angabe this.model.bind('add', this.addListItem); in der initialize-Funktion wird gesorgt, dass daraufhin die Funktion addListItem() aufgerufen wird, die für den neuen Eintrag in der Collection eine Instant des Listen-Eintrag-View erstellt und diesem den Befehl render() mit auf den Weg gibt. Über die Funktion addAllListItems() wird einerseits sichergestellt, dass die Liste geleert wird, bevor neue Einträge nach einem Kategorie-Wechsel reingladen werden, und andererseits, bei Übergabe einer Collection mit mehreren einträgen, für jeden Eintrag die Funktion addListItem() ausgeführt wird.
Als nächstes kommen wir zum Listen-Eintrag-View:
JavaScript
window.ListItemView = Backbone.View.extend({
tagName : 'li',
className : 'list_item',
tmpl : _.template( $('#list-item-template').html() ),
events : {
'click .delete_item' : 'removeItem'
},
render : function ()
{
$(this.el).html( this.tmpl( this.model.toJSON() ));
return this;
},
removeItem : function ()
{
this.model.destroy();
$(this.el).fadeOut( function()
{
$(this).remove();
});
}
});
Hier wird das Element vom Standard (div) auf li gesetzt, die gewünschte CSS-Klasse gesetzt, das Template für den Eintrag angemeldet, ein Klick-Event mit der Funktion removeItem() verknüpft und anschließend die Funktionen render() und removeItem() definiert. Erstere fügt die Daten des Collection-Eintrags in das Template ein, letztere löscht den View und den dazugehörigen Collection-Eintrag.
Schlussendlich benötigen wir noch einen View für die Navigation:
JavaScript
window.NavigationView = Backbone.View.extend({
tagName : 'li',
tmpl : _.template( $('#nav-template').html() ),
hash : function ()
{
return window.location.hash.replace('#/category/', '');
},
render : function ()
{
$('#nav').append($(this.el).html( this.tmpl( this.model.toJSON() ) ));
return this;
},
setClass : function ()
{
var curHash = this.hash();
this.el.className = ( curHash == $(this.el).find('a').text() ) ? 'current' : '';
}
});
Neu und interessant ist hier die Funktion setClass(). Diese sorgt nach dem Rendern der Navigation, dass der aktuelle Reiter die Klasse "current" bekommt.
Um die Listen-App nun zum Laufen zu bringen, erstellen wir eine neue Instanz des Controllers:
JavaScript
var listApp = new ListController();
Der ganze Spaß wird in eine anonyme jQuery-Funktion geschrieben, damit die App gestartet wird, sobald das DOM geladen ist.
Das war es auch schon. Ich hoffe, meine Erklärungen sind einigermaßen nachvollziehbar. Falls nicht, nutzt auf jeden Fall die Kommentar-Funktion. Des weiteren würde ich mich natürlich über Anregungen und Verbesserungsvorschläge freuen, da ich ja doch ein Neuling in Sachen Backbone.js bin. Ansonsten noch der Hinweis, dass bei Gefallen natürlich gerne regen Gebrauch von den unten stehenden Social-Media-Buttons gemacht werden kann.
Vielen Dank!
Email Obfuscation: jQuery-PlugIn zur Verschleierung von Email-Adressen
Anmerkung: Die Überschrift ist genau genommen etwas irreführend, da das PlugIn die Email-Adresse vielmehr entschleiert. Das aber nur am Rande erwähnt.
Ein altes Problem, für welches es mittlerweile zugegebenermaßen schon viele Lösungen gibt, ist das Veröffentlichen von Email-Adressen auf Websites. Schreibt man die richtige Adresse in den Quelltext, macht man seinen Besuchern das Leben leichter. Aber man arbeitet auch den Email-Spammern in die Hände, die das Netz nach Adressen durchforsten.
Verschleiert man seine Email-Adresse, indem man bspw. das @-Zeichen durch "(at)" ersetzt, hat man das Problem nicht. Nur müssen Besucher die Zeichen erst umständlich ersetzen, wenn sie eine Email an die Adresse schreiben wollen.
Meine Lösung des Problems ist eine Kombination aus Verschleierung der Adresse im Quelltext und ein Re-Build der Adresse per Javascript mithilfe eines jQuery-Plugins:
Javascript
(function($){
$.fn.obfuscateEmail = function(at, point, addClass)
{
/* Regular Expressions fuer @ und . definieren
* falls die Argumente gesetzt sind
**/
var at = at && new RegExp(at, 'g') || false,
point = point && new RegExp(point, 'g') || false;
/* Plugin-Funktion ausfuehren, falls
* Regular Expressions fuer @ und . definiert
* sind.
* Ansonsten nur 'this' zurueck geben.
**/
return at && point && this.each(
function()
{
/* Wenn 'addClass' auf 'true' gesetzt ist,
* dem Element die Klasse 'js' anhaengen
**/
this.className = this.className + ( addClass && ' js' || '');
/* Verschleierte Adresse anhand der Leer-
* zeichen in Array aufsplitten
**/
var mailTo = this.innerHTML.split(' '),
i, mailToLen = mailTo.length,
address = [];
/* Anhand der uebergebene @- und .-Werte
* jedes Element des mailTo-Arrays pruefen
* und ggf. Zeichen ersetzen.
* mailTo-Elemente in address-Array speichern.
**/
for ( i=0; i<mailToLen; i++ )
{
address.push( point.test(mailTo[i]) ?
'.' :
at.test(mailTo[i]) ?
'@' :
mailTo[i] );
}
/* Inhalt des Elements mit mailto-Link
* und der unverschleieerten Adresse aus
* dem address-Array befuellen
**/
this.innerHTML = '<a href="mailto:' + address.join('') + '">' + address.join('') + '</a>';
}
) || this;
};
})(jQuery);
Angewendet wird das PlugIn folgendermaßen:
Javascript
$(document).ready( function() {
$('span.email').obfuscateEmail('(at)', '(punkt)', true);
});
Die ersten beiden Argumente der Funktion definieren, mit welchen Ausdrücken das @- und .-Zeichen der Email-Adressen verschleiert ist. Das dritte Argument ist ein Boolean-Wert und gibt an, ob den Adressen die Klasse 'js' angehängt werden soll, um sie unterschiedlich stylen zu können, je nachdem, ob der Besucher JavaScript aktiviert hat oder nicht. Das PlugIn arbeitet sich dann durch alle Adressen der Seite, hängt im Bedarfsfall die Klasse 'js' an und ersetzt die verschleierte Adresse durch die korrekte, inklusive mailto-Hyperlink zum einfachen Versenden im Email-Client bei Klicken der Adresse.
Die Adressen können folgendermaßen aussehen:
HTML
<span class="email">hansmustermann (at) domain (punkt) com</span>
<p class="email">foo <em>(punkt)</em> bar <em>(at)</em> domain <em>(punkt)</em> com</p>
Wichtig bei den Adressen ist, dass die einzelnen Elemente durch ein Leerzeichen getrennt sind, sonst funktioniert es nicht. Wie man in der zweiten Zeile sieht, können die verschleierten Symbole beliebig in HTML gefasst werden, um die Adresse für Besucher ohne JavaScript besser lesbar zu machen. Der Funktionalität des PlugIns tut dies kein Abbruch, solange die Leerzeichen vorhanden sind.
So einfach ist. Fragen, Anregungen, Verbesserungsvorschläge bitte in die Kommentare. Vielen Dank.
Ein paar Worte zur Causa “Nerdcore | Euroweb”
Dass René Walter seine Domain "Nerdcore.de" an Euroweb abgeben musste, ist nicht mehr neu und ich will hier auch niemanden mit dem hunderttausendsten Kommentar dazu langweilen, wie schlimm das alles ist. Als "Mann vom Fach" (klingt sehr hochtrabend, ich weiß
), fallen mir bei der Interimsseite unter www.nerdcore.de aber zwei Details auf, die mich schon etwas wundern.
Zum einen wird auf Kommentar-Pagination verzichtet. Das ist allerdings nicht so gravierend, könnte man doch der Annahme anhängen, die Euroweb Internet GmbH hätte angesichts der Häme, die sich dort in den über 1700 Kommentaren ergießt, gar kein gesteigertes Interesse daran, dass die - vom Markup mittlerweile vollkommen überfrachtete - Kommentarseite zügig lädt.
Eine andere Sache macht mich viel mehr stutzig: Gelangt man über eine Suchmaschine oder einen der im Netz verstreuten Links auf einen Nerdcore-Artikel, erscheint eine profane 404-Seite mit dem Hinweis, dass die aufgerufene Seite nicht existiert. Angesichts der Tatsache, dass die Causa "Nerdcore | Euroweb" in gewisser Weise schon pikant ist und zu nicht unerheblicher Kontroverse geführt hat, wäre mir an Stelle der Euroweb Internet GmbH daran gelegen gewesen, soweit wie möglich jeden Besucher auf www.nerdcore.de abzufangen und die momentane Situation hinlänglich zu kommunizieren.
Sei es, dass man eine entsprechende Erklärung auf der 404-Seite anzeigt oder die 404-Seite der Einfachheit halber gleich auf die Startseite umleitet. Könnte man jedenfalls machen. Man könnte genau genommen sehr vieles machen. Und vieles wiederum lassen. Aber das ist ein anderes Thema…
Wer übrigens nicht weiß, worum es bei der Causa "Nerdcore | Euroweb" genau geht, hat im folgenden ein paar Links mit weiterführenden Informationen:
Kabel Deutschland und die Postwurfsendung
Postwurfsendungen gehören zum festen Marketing-Kanon von Kabel Deutschland. Die dafür nötigen Adressen werden (u.a.?!) von der AZ Direct GmbH aus Gütersloh gekauft, die sich auf das Handeln mit Adressen verlegt hat. Grundsätzlich kann man sich darüber streiten, welchen betriebswirtschaftlichen Nutzen das Belästigen unbescholtener Bürger mit Briefwerbung hat, aber das würde den Rahmen sprengen.

Interessanter ist, dass meine Daten bei der AZ Direct GmbH vorliegen (scheinbar habe ich irgendwann im Netz ein Häkchen zu wenig weg gemacht…) und ich deshalb in den Genuss regelmäßiger Postwurfsendungen durch Kabel Deutschland gekommen bin. Kuriose Angelegenheit, dachte ich bei mir. Wenn sie sicherstellen wollen, dass ich niemals Kunde bei ihnen werden, können sie sich wohl damit rühmen, die nötigen Schritte unternommen zu haben.
Und um dem Terror Einhalt zu gebieten, habe ich mich an den Kundenservice gewandt und darum gebeten, meine Daten zu löschen. Schließlich haben sie ausreichend sichergestellt, dass ich keinen Vertrag mehr bei ihnen unterschreiben werden - jede weitere Postwurfsendung wäre somit verschwendetes Geld. Außerdem interessierte mich, was hinter der markigen Ansage im Kleingedruckten der Werbebriefe steckte:
Wir bieten volle Transparenz über die Verarbeitung ihrer Daten und ihr Recht auf Werbewiederspruch: …
Dazu noch Internet- und Email-Adresse, sowie Telefonnummer des Kundenservice, wo man den Werbe-Schwachsinn zu beiderseitigem Vorteil beenden kann. Obendrein der Hinweis, wo die Daten erworben wurden und dass alles rechtmäßig und im Sinne des Datenschutzes stattgefunden hat.
Die Antwort kam auch keine 12 Stunden später um 7 Uhr in der Frühe: Meine genauen Daten würden fehlen und…
Unser Vorschlag: Bitte schicken Sie uns Ihre Anfrage noch einmal mit diesen Daten. Dann kümmern wir uns selbstverständlich sofort darum. Vielen Dank!
"Shame on me!" dachte ich mir, ich muss den guten Leuten natürlich auch was geben, womit sie arbeiten können. Also hurtig - zwei Stunden später - eine Antwort verfasst und meine genauen Daten hingeschickt. Etwas mulmig war mir zwar schon dabei zumute, da sie jetzt wissen würden, dass die Werbebriefe auch wirklich ankommen, aber ich beruhigte mich damit, dass ich an ein Unternehmen mit Sitz in Deutschland schreibe und nicht an das RBS oder an einen drittklassigen Spammer mit Sitz auf Honululu (No Offense!).
Es kam dann auch nichts mehr zurück, weshalb ich davon ausging, dass sie meine Daten zwar gelöscht hätten, aber aufgrund meines Unwillens eingeschnappt seien und deshalb auf eine abschließende Email verzichten und mich mit Ungnade und Nichtbeachtung strafen würden. Sind ja schließlich auch nur Menschen dort bei Kabel Deutschland.
Doch hatte ich mich zu früh gefreut. Heute kam wieder der Standard-Werbebrief reingeflattert:
Kabel Deutschland, bla bla, alles doppelt so schnell und gratis für 22,90 Euro, bla blub, Gratis-Installation, sülz, kostenloser Anruf, fasel, Sparen und Susanne May. Kundenkommunikation!
Man kann sich vorstellen, dass ich einigermaßen überrascht war. Ich muss auch zugeben, dass ich diesen Artikel schreibe, bevor ich mich ein drittes mal an den Kundenservice wende und höflichst um das klein gedruckte Bisschen Transparenz bitte. Ich muss zu meiner Verteidigung allerdings sagen, dass ich nicht davon ausgehe, dass Kabel Deutschland es dieses mal schaffen wird, der Bitte um Löschung meiner Daten nachzukommen. Von daher habe ich keine Eile.
Nur ein paar offene Worte an Frau Susanne May möchte ich an dieser Stelle loswerden:
Erschütternd schlechte Arbeit, die sie und ihr Team da leisten. Hängen sie von mir aus dem Luftschloss nach, mit Brief-Werbe-Terror könnte man Kunden gewinnen, aber vergraulen sie doch nicht so fahrlässig Neukundschaft. Oder ist mir entgangen, dass Kabel Deutschland längst mit Abstand Marktführer ist und sie durch diese Flut an sinnlosen Briefen lediglich versuchen ihre Stelle zu rechtfertigen und so der Arbeitslosigkeit zu entgehen?
8Bit-Style-Navigation mit Fly-Out-Menus
Heute möchte ich kurz zeigen, wie man mit etwas HTML, CSS und ein paar kleinen GIF-Grafiken eine pixelige Seiten-Navigation im 8Bit-Stil baut. Außerdem benutzen wir etwas jQuery-Magic, um der Navigation noch Fly-Out-Menus zu spendieren.

Beginnen wir wie gewohnt mit dem HTML-Teil:
HTML
<div id="nav">
<ul>
<li class="top">
<a href="index.html">
<strong>Home</strong>
</a>
<div class="sub">
<div>
<ul>
<li>
<a href="#">Sub-Item 1</a>
</li>
<li>
<a href="#">Sub-Item 2</a>
</li>
<li>
<a href="#">Sub-Item 3</a>
</li>
<li>
<a href="#">Sub-Item 4</a>
</li>
<li>
<a href="#">Sub-Item 5</a>
</li>
</ul>
</div>
</div>
</li>
<li class="top">
<a href="#">
<strong>About</strong>
</a>
<div class="sub">
<div>
<ul>
<li>
<a href="#">One Sub-Item</a>
</li>
<li>
<a href="#">Another Sub-Item</a>
</li>
<li>
<a href="#">Still a Sub-Item</a>
</li>
</ul>
</div>
</div>
</li>
<li>
<a href="#">
<strong>Contact</strong>
</a>
</li>
</ul>
</div>
Wie gewohnt eine ungeordnete Liste für die Haupt-Navigation und jeweils eine für die Sub-Navigationen. Um die charakteristischen Ecken hinzubekommen, müssen zwei Elemente ineinander verschachtelt und gegeneinander verschoben werden. Das macht den Quelltext Tag-intensiver. In meinen Augen jedoch noch in einem vertretbaren Rahmen und weit entfernt von klassischer "Diveritis".
Als nächstes kommen wir zum CSS:
CSS
@font-face {
font-family: 'SilkscreenNormal';
src: url('slkscr-webfont.eot');
src: local('☺'), url('slkscr-webfont.woff') format('woff'), url('slkscr-webfont.ttf') format('truetype'), url('slkscr-webfont.svg#webfontUx1SMfhe') format('svg');
font-weight: normal;
font-style: normal;
}
ul {
list-style: none;
}
body {
background-color: #FFF;
color: #333;
font: normal 13px SilkscreenNormal, sans-serif;
}
#nav,
#nav > ul,
#nav > ul > li {
float: left;
display: inline;
}
#nav,
#nav > ul {
width: auto;
_width: 1%; /* IE6 Hack */
height: 32px;
}
#nav {
margin: 50px;
position: relative;
border-width: 2px 0;
border-style: solid;
border-color: #666;
}
#nav > ul {
position: relative;
left: -2px;
margin-right: -4px;
padding: 0 10px;
border-width: 0 2px;
border-style: solid;
border-color: #666;
background: url('data:image/gif;base64,R0lGODlhBAAEAIAAAP///73n/yH5BAAAAAAALAAAAAAEAAQAAAIGTACGqBkFADs=') 0 0 repeat;
*background: url('img/tile1.gif') 0 0 repeat; /* IE6 and IE7 can't handle data uris */
}
#nav > ul > li {
_width: 1%; /* IE6 Hack */
margin: 3px 5px;
position: relative;
}
#nav > ul > li > a {
display: block;
position: relative;
width: auto;
height: 22px;
border-width: 2px 0;
border-style: solid;
border-color: #999;
background-color: #FFF;
}
#nav > ul > li > a:link,
#nav > ul > li > a:visited {
color: #999;
text-decoration: none;
background: url('data:image/gif;base64,R0lGODlhBAAEAIAAAP/////fvSH5BAAAAAAALAAAAAAEAAQAAAIGTACGqBkFADs=') 0 0 repeat;
*background: url('img/tile2.gif') 0 0 repeat; /* IE6 and IE7 can't handle data uris */
}
#nav > ul > li > a:hover,
#nav > ul > li > a:focus,
#nav > ul > li > a:active {
color: #666;
background-image: url('data:image/gif;base64,R0lGODlhBAAEAIAAAP/////MmSH5BAAAAAAALAAAAAAEAAQAAAIGTACGqBkFADs=');
*background-image: url('img/tile3.gif'); /* IE6 and IE7 can't handle data uris */
}
#nav > ul > li > a strong {
display: block;
position: relative;
width: auto;
height: 22px;
padding: 0 10px;
line-height: 22px;
left: -2px;
margin-right: -4px;
border-width: 0 2px;
border-style: solid;
border-color: #999;
}
#nav > ul > li.top > a > strong {
padding-left: 21px;
background: url('data:image/gif;base64,R0lGODlhBgAEAIABAJmZmf///yH5BAEAAAEALAAAAAAGAAQAAAIHhI8WocuwCgA7') 5px center no-repeat;
*background: url('img/arrow.gif') 5px center no-repeat; /* IE6 and IE7 can't handle data uris */
}
#nav > ul > li > a:hover,
#nav > ul > li > a:hover strong,
#nav > ul > li > a:focus,
#nav > ul > li > a:focus strong {
border-color: #666;
}
#nav > ul > li.top > a:hover strong,
#nav > ul > li.top > a:focus strong {
background-image: url('data:image/gif;base64,R0lGODlhBgAEAIABAGZmZv///yH5BAEAAAEALAAAAAAGAAQAAAIHhI8WocuwCgA7');
*background-image: url('img/arrow_hv.gif'); /* IE6 and IE7 can't handle data uris */
}
#nav > ul > li > a:active {
top: 1px;
}
.sub {
position: absolute;
width: auto;
top: 24px;
left: 0;
padding-top: 9px;
display: none;
}
.sub div {
position: relative;
border-top: 2px solid #666;
border-bottom: 2px solid #666;
}
.sub div ul {
position: relative;
left: -2px;
margin-right: -4px;
border-left: 2px solid #666;
border-right: 2px solid #666;
background: url('data:image/gif;base64,R0lGODlhBAAEAIAAAP///+7u7iH5BAAAAAAALAAAAAAEAAQAAAIGTACGqBkFADs=') 0 0 repeat;
*background: url('img/tile4.gif') 0 0 repeat; /* IE6 and IE7 can't handle data uris */
}
.sub div ul li {
border-top: 2px solid #666;
}
.sub div ul li:first-child {
border-top: none;
}
.sub div ul li a {
display: block;
padding: 0 10px;
line-height: 22px;
font-size: 12px;
white-space: nowrap;
}
.sub div ul li a:link,
.sub div ul li a:visited {
color: #666;
}
.sub div ul li a:hover,´
.sub div ul li a:focus {
background: url('data:image/gif;base64,R0lGODlhBAAEAIAAAP///93d3SH5BAAAAAAALAAAAAAEAAQAAAIGTACGqBkFADs=') 0 0 repeat;
*background: url('img/tile5.gif') 0 0 repeat; /* IE6 and IE7 can't handle data uris */
}
.sub div ul li a:active {
background: url('data:image/gif;base64,R0lGODlhBAAEAIAAAP///8zMzCH5BAAAAAAALAAAAAAEAAQAAAIGTACGqBkFADs=') 0 0 repeat;
*background: url('img/tile6.gif') 0 0 repeat; /* IE6 and IE7 can't handle data uris */
}
Als erstes binde ich den Pixel-Font "SilkScreen von Jason Kottke ein. Danach folgen die Angaben für die Navigation.
Interessant ist dabei, dass selbst das äußere Element <div id="nav"> die Angabe float: left hat, damit es sich der Breite des Inhalts anpasst. Das kann zu Layout-Problemen führen, weshalb man im praktischen Einsatz darauf achten muss, das Element direkt unterhalb der Navigation mit einem clear: left zu versehen.
Des weiteren kann man sehen, wie die charakteristischen Ecken zustande kommen: Das äußere Element hat jeweils unten und oben eine zwei-Pixel-starke border und das innere jeweils links und rechts. Das innere Element wird dann per left: -2px und margin-righ: -4px um jeweils zwei Pixel nach links und rechts aus dem umgebenden Element hinaus gezogen. Schon ist der gewünschte Effekt da.
Ebenfalls erwähnenswert sind die Grafiken. Da diese nur 4x4 Pixel bzw. 4x6 Pixel groß sind, lohnt es sich, sie in Form von Data URIs einzubinden und so unnötige HTTP Requests zu sparen. Blöderweise können IE6 und IE7 damit nicht umgehen, weshalb die richtigen Grafiken ebenfalls eingebunden werden müssen. Diese werden dann mithilfe des Star-Hack den beiden Browsern zugewiesen.
Bilder in Data URIs umwandeln könnt ihr übrigens mit diesem Online-Tool.
Zum Schluss noch etwas jQuery um die Fly-Out-Menu-Funktionalität zu realisieren:
JavaScript
$(document).ready( function() {
$('#nav li.top').hover( function() {
$(this).find('div').stop(true, true).fadeIn('slow');
}, function() {
$(this).find('div').stop(true, true).fadeOut('slow');
});
});
Ich denke, das ist die spartanischste Lösung und bedarf keiner weiteren Erläuterung.
Das war es auch schon. Die Navigation ist beliebig per Copy&Paste erweiterbar, denkt nur daran, den li-Elementen, die eine Sub-Navigation beinhalten, die Klasse top zu verpassen, damit die Fly-Out-Menu-Funktionalität gewährleistet ist.
Ansonsten wünsche ich viel Spaß mit der Navigation. Bei Fragen bitte wie immer die Kommentar-Funktion nutzen. Und bei Gefallen fleißig via Twitter und Facebook verbreiten. Vielen Dank
Feine Fahrräder von Bertelli aus New York
Bertelli kommt aus New York City und hat sich auf das Herstellen von Fahrrädern verlegt. Er produziert ausschließlich Track Bikes und Fixed Gear, außerdem sind seine Räder Einzelstücke.
Gefunden bei Minimalissimo





