Freitag, 26. August 2011

Navigation Manager in RedDot

Der Navigation Manager in RedDot erstellt dynamisch eine Navigation. Änderungen in der Navigationsstrukur wirken sich direkt auf die Projektstruktur und die Seiten aus. Das bedeutet, wenn man neue Seiten im Navigation Manager anlegt, wird auf allen Seiten die Navigation angepasst.

Folgende Schritte müssen durchgeführt werden, um eine Navigation mit dem Navigation Manager zu erstellen:
  1. Navigation Manager aktivieren
  2. Mastertemplate mit einem Listenelement erstellen
  3. Content-Klassen für die Navigationsebenen erstellen
  4. Navigationsbereiche erstellen und die Content-Klassen zuordnen
  5. Navigationsbereiche im Mastertemplate angeben
  6. Navigationsstruktur aufbauen
1. Navigation Manager aktivieren
Damit der Navigation Manager genutzt werden kann, muss er in den allgemeinen Einstellungen des Projektes aktiviert werden.

2. Mastertemplate mit einem Listenelement erstellen
Nur an einem Mastertemplate können mit dem Navigation Manager Seiten angehängt werden. Eine Seite, die eine Masterpage als Template hat, wird automatisch in die Navigationsstruktur aufgenommen. Dazu erstellt man eine neue Content-Klasse. Diese kann zum Beispiel das Grundgerüst für alle Seiten sein. In dieser Content-Klasse muss ein Listenelement angelegt werden. Diese Liste dient als direktes Strukturelement zur Verknüpfung von darauf folgenden Ebenen. Die Einstellung für das Listenelement ist „Nur Datei- und Pfadnamen angeben". Dieses Element muss nicht im Template verwendet werden. In den Masterpage Einstellungen wird definiert, dass diese Content-Klasse als Masterpage verwendet wird. Weiterhin gibt man an welches Listenelement als Navigationsstruktur dient.

3. Contentklassen für die Navigationsebenen erstellen
Es muss für jede Navigationsebene ein Template erstellt werden. Wenn es einen „selektiert“ und „nicht selektiert“ Zustand gibt, benötigt jeder Zustand ein eigenes Template. Für eine Navigation mit zwei Ebenen ohne unterschiedliche Zustände, benötigt man also zwei Templates.
Der Navigation Manager bietet Render Tags. Die Render Tags sind Code für die Navigationsfunktionalität. Sie ermöglichen den Zugriff auf Objekte im Management Server, wie Navigationsindex, Inhalte aus den Seiten oder Navigationsbereiche. Komplexere Render Tags Funktionen werden in einem <reddot:cms /> Block zusammengefasst.

Ebene Eins
<ul>
<li>
<a href=" <%!! Context:CurrentIndex.GetUrl() !!%>">
<%!! Context:CurrentIndex.Headline !!%>
</a>
<reddot:cms>
<if>
<query valuea="Context:CurrentIndex.HasChildren()" operator="=="
valueb="Bool:True">
<htmltext>
<ul>
<navigation:nextlevel>
</ul>
</htmltext>
</query>
</if>
</reddot:cms>
</li>
</ul>

Context:CurrentIndex.GetUrl = setzt den Link der aktuellen Seite Context:CurrentIndex.Page.Headline = liest den Seitennamen der aktuellen Seite aus und ergibt den Linktext .
Die Abfrage ist die Grundlage um Seiten aus der zweiten Navigationsebene einzufügen. Wenn die aktuelle Seite (CurrentIndex) Kindelemente besitzt, wird eine Liste für das nächste Level generiert.
<if> <qeury> = Abfrage; alle Werte werden in Strings angegeben, selbst der Vergleichsoperator; es werden valuea und valueb vergleichen.
<htmltext /> = Es kann HTML Code im <reddot:cms /> Block geschrieben werden
<navigation:nextlevel> = fügt nächstes Navigationstemplate ein

Ebene Zwei
<li>
<a href=" <%!! Context:CurrentIndex.GetUrl() !!%>">
<%!! Context:CurrentIndex.Headline !!%>
</a>
</li>

Es wird nur das Listenelement der aktuellen Seite generiert. Das umgebende Listenelement (<ul />) wird im Navigationstemplate Ebene Eins erstellt.

4. Navigationsbereiche erstellen und die Content-Klassen zuordnen
Es müssen die erstellten Navigationtemplates Navigationsbereichen zugeteilt werden. Unter "Navigation ansehen - Projekt - Navigationsbereiche" werden Navigationsbereiche erstellt und die jeweilige Content-Klasse zugeordnet.Im linken Dialogfenster werden die erstellten Navigationsbereiche angezeigt, rechts die Content-Klassen. In diesem Beispiel muss ein Bereich für die Hauptnavigation erstellt werden. Diesem Bereich werden die Navigationtemplates Ebene Eins und Ebene Zwei zugeordnet.In den Einstellungen der Zuordnung gibt man an für welche Ebene das jeweilige Template wirken soll.

5. Navigationsbereiche im Mastertemplate angeben
Die Zusammenführung des Mastertemplates und der Navigationtemplates erfolgt über die Navigationsbereiche. Im Mastertemplate werden über Render Tags die Navigationsbereiche eingefügt. Diese basieren auf den Navigationtemplates, so dass sich das Bild eines kompletten HTML-Gerüstes ergibt.

<%!! Navigation:OutputArea(Str:Hauptnavigation) !!%>

6. Navigationsstruktur aufbauen
Unter "Navigation einsehen - Projekt - Navigationsstruktur" erstellt man die Navigationsstruktur. Das Dialogfenster hat zwei Seiten:
Links: navigationsrelevante Seiten
Rechts: Hilfsbaum, der entweder alle freien Seiten, oder bestehende Navigation anzeigt

Jedes Projekt kann nur eine Hauptseite haben, eine zweite Seite landet unter den freien Seiten. Weitere Seiten können an der Hauptseite erstellt werden.

Es gibt zwei Unterschiede beim Löschen von Seiten:
Seite löschen - Seite wird aus Navigation und Projekt gelöscht
Seitenindex löschen - Seite wird aus Navigation gelöscht, bleibt im Projekt bestehen

Donnerstag, 25. August 2011

JavaScript richtig integrieren

Eine Webapplikation ohne den Einsatz von JavaScript, ist mittlerweile kaum noch vorstellbar. JavaScript wird unter anderem für das Nachladen und Ersetzen von DOM-Elementen, sowie für Event-Verarbeitung und Animationen genutzt.

Es gibt unterschiedliche Möglichkeiten JavaScript zu integrieren, wobei sich einige im Bezug auf die resultierende Performance der Website besser eignen als andere. Dieser Artikel fasst einige Empfehlungen aus dem Buch High Performance Web Sites von Steve Souders zusammen und soll darüber hinaus noch Tipps für die JavaScript-Entwicklung geben.

Externe JavaScript-Dateien

Für die Einbindung von JavaScript gibt es zwei Wege. Zum einen direkt in der HTML-Datei innerhalb eines Script-Blocks und zum anderen in einer externen JavaScript-Datei referenziert durch ein Script-Tag. Da bei der Lösung mit externer Datei ein zusätzlicher HTTP-Request durchgeführt werden muss, lädt die Website mit integriertem Script-Block beim ersten Aufruf schneller. Der große Nachteil an Script-Blöcken ist jedoch, dass diese bei jedem Request mit übertragen werden müssen. Externe JavaScript-Dateien können hingegen vom Browser gecached werden.

Eine minifizierte JavaScript-Datei

Zusätzlich sollte beachtet werden, dass nur eine JavaScript-Datei eingebunden wird, da jede Datei durch einen eigenen Request geladen werden muss. Das Skript sollte außerdem minifiziert vorliegen, um die zu übertragenen Bytes zu minimieren. Gute Ergebnisse können beispielsweise mit UglifyJS, Google Closure Compiler oder YUI Compressor erreicht werden.

JavaScript ans Ende und erst nach dem Rendern ausführen

Die Browser rendern eine Seite von oben nach unten und laden dabei alle benötigten externen Ressourcen wie Stylesheets, Bilder und JavaScripts. Da nur eine begrenzte Anzahl an Requests parallel durchgeführt werden kann (RFC2616 empfiehlt zwei parallele Requests pro Domain, moderne Browser unterstützen jedoch mehr), sollten Scripts erst am Ende der Seite eingebettet werden, damit das initiale Rendern von sichtbaren Elementen nicht gebremst wird.

Außerdem werden JavaScripts direkt nach dem Laden evaluiert, wodurch der Rendervorgang pausiert wird. Daher empfiehlt es sich, die Ausführung des JavaScripts an ein Browser-Event zu binden, welches erst nach dem vollständigen Rendern ausgelöst wird. Im folgenden Beispiel vereinheitlicht jQuery die Events der unterschiedlichen Browser und ruft den Event-Handler auf, nachdem die Seite vollständig geladen wurde:

$(document).ready(function() {

// do stuff

});


In diesem Abschnitt wird ein eine anonyme Funktion als Event-Handler an das Ready-Event des HTML-Dokuments gebunden. Innerhalb dieser Funktion sollten alle weiteren JavaScript Funktionen gekapselt werden.

Entwicklungs- und Produktionsmodus

Eine einzelne, und obendrein noch minifizierte JavaScript-Datei, ist selbstverständlich durch keinen Entwickler wartbar und im Fehlerfall kann die Zeilennummer in der Browserkonsole keinen Aufschluss über die Fehlerquelle geben. Daher wird ein Mechanismus benötigt, welcher dafür Sorge trägt, dass im Produktionsmodus die minifizierte und im Entwicklungsmodus die normale JavaScript-Datei eingebunden werden. Die Zusammenfassung der einzelnen JavaScript-Dateien und die anschließende Minifizierung erfolgen dabei in einem Postprozess während des Deployments.

Je nach Servertechnologie und Template-Engine, könnte dies wie folgt aussehen: Eine Variable, deren Wert in der Serverkonfiguration oder durch eine Umgebungsvariable definiert wird, bestimmt, ob die basic.js oder die basic-min.js eingebunden wird.

<% if env === "development" %>

<script type="text/javascript"

src="/extensions/js/basic.js"></script>

<% else %>

<script type="text/javascript"

src="/extensions/js/basic-min.js"></script>

<% end %>


Dadurch wird im Entwicklungsmodus die nicht-minifizierte basic.js eingebunden. Somit hat der Entwickler bessere Möglichkeiten für das Debuggen des JavaScripts.

Aufteilung in mehrere Module

In einem Projekt, in dem viel JavaScript eingesetzt wird, wird eine Datei sehr schnell unübersichtlich und unwartbar. Erschwerend kommt hinzu, dass oftmals mehrere Personen an den Entwicklungsarbeiten beteiligt sind, wodurch Konflikte entstehen können.

Aus diesem Grund ist es sinnvoll, das gesamte JavaScript in mehrere Dateien zu modularisieren. Templateänderungen führen bei einigen Systemen dazu, dass HTML-Seiten neu publiziert werden müssen. Daher sollte das Hinzufügen von neuen JavaScript-Modulen nicht durch ergänzen von Script-Tags im Template erfolgen. Vielmehr sollte es möglich sein, dass Anpassungen zentral in einer JavaScript-Datei vorgenommen werden können.

Dies gelingt, indem die einzelnen Script-Tags für die Module durch ein JavaScript (basic.js) in die Seite geschrieben werden:

(function() {

var files = [

"/extensions/js/file1.js",

"/extensions/js/file2.js"

];


(function(scripts) {

for (var i = 0; i <scripts.length; ++i) {

scripts[i] = "<script type=\"text/javascript\"" +

"src=\"" + scripts[i] + "\"></script>";

}

document.write(scripts.join("\n"));

})(files);

})();


Dieses JavaScript iteriert über eine Liste der angegebenen URIs der JavaScript-Dateien und erstellt für jede ein Script-Tag. Diese werden anschließend gemeinsam in das HTML-Dokument geschrieben, bevor der Browser das Rendern des Dokuments beendet hat. Soll ein Modul hinzugefügt oder entfernt werden, muss lediglich die Liste files angepasst werden.

Ausliefern der JavaScript-Dateien

Einige Websites liefern JavaScript-Dateien wie andere statische Ressourcen (z. B. Bilder) mit einem Content Management System aus. Dies ist jedoch mit Sicherheit nicht die beste Möglichkeit, da JavaScript keinen Seiteninhalt, sondern vielmehr einen Bestandteil der Software darstellt. Außerdem müsste für jede Anpassung des JavaScripts jeweils eine Publikation angestoßen werden. Stattdessen sollte JavaScript direkt von einem Webserver ausgeliefert werden. nginx und andere Webserver bieten für diesen Fall den Einsatz eines Rewrites an, wodurch ein Pfadmuster gesondert behandelt werden kann. Anfragen, welche mit http://meineseite.de/extensions/js/ beginnen werden abgefangen und können mit Ressourcen unterhalb von /var/www/meineseite.de/extensions/js beantwortet werden. Alle anderen Requests werden wie gewohnt vom verwendeten System beantwortet.

server {

server_name meineseite.de;

listen 80;


location / {

rewrite /extensions/js/(.*)

/var/www/meineseite.de/extensions/js/$1;

}

}


Kleiner Aufwand für große Auswirkungen

Die in diesem Artikel beschriebenen Tipps können zum größten Teil mit wenig Mühe umgesetzt werden. Dennoch können diese erheblich auf die Performance Ihrer Website einzahlen. Weiterhin kann dadurch auch der Komfort für die Entwicklungsarbeiten erhöht werden.

Freitag, 12. August 2011

RubyGems selber bauen

RubyGems
ist das offizielle Software-Paketsystem für die Programmiersprache Ruby.

Ein RubyGem kann nicht nur Bibliotheken, sondern auch (Kommandozeilen-) Programme, Rails-Plugins etc. pp. enthalten. RubyGems erlaubt dem Benutzer die gleichzeitige Installation eines Gems in verschiedenen Versionen.

Ein Gem-Autor kann für sein Gem Abhängigkeiten zu anderen Gems definieren, die von RubyGems bei der Installation automatisch aufgelöst werden.

Ein RubyGem selber zu bauen und veröffentlichen ist gar nicht schwer. In diesem Artikel werde ich zeigen, wie man ein RubyGem von Grund auf selbst baut und sowohl im Quellcode als auch als Paket veröffentlicht.

Im Inneren eines Gems

Ein Gem besteht aus mindestens zwei Teilen

  1. dem eigentlichen Programmcode und
  2. einer Gemspec-Datei

Zweckmäßigerweise beinhaltet ein Gem darüber hinaus noch:

  • Tests
  • Dokumentation
  • Automatisierung für die Tests und das Bauen des Pakets

Hier ein Beispiel für die Datei-Struktur eines Gems aus dem RubyGems
Guide
:

$ tree freewill
freewill/
|-- bin/
| `-- freewill
|-- lib/
| `-- freewill.rb
|-- test/
| `-- test_freewill.rb
|-- README
|-- Rakefile
`-- freewill.gemspec
Das Gem-Kommando

Die RubyGems-Installation bring das Kommandozeilen-Programm gem mit. Mit diesem Programm werden Gems verwaltet (installiert, deinstalliert, aufgelistet usw.). Das gem-Programm dient auch dazu Gems aus dem Quellcode zu bauen:

   $ gem build hola.gemspec

Successfully built RubyGem
Name: hola
Version: 0.0.0
File: hola-0.0.0.gem

Mit dem Gem-Kommando build wird die Gem-Datei erzeugt. Hierfür liest das Gem-Programm die erforderlichen Angaben aus der Gemspec-Datei.

In dem Beispiel finden wir das fertige Gem in der Datei hola-0.0.0.gem. Mit

   gem install hola-0.0.0.gem

kann man das Gem lokal installieren.

RubyGems können auf im Netz veröffentlicht und mit dem Gem-Kommando
von remote installiert werden. Näheres dazu den Abschnitt RubyGems
Dot Org
unten.


Die Gemspec-Datei

Das Herz eines Gems ist die Gemspec-Datei.

Hier wieder ein Beispiel aus dem RubyGems Guide:

Gem::Specification.new do |s|
s.name = 'hola'
s.version = '0.0.0'
s.date = '2010-04-28'
s.summary = "Hola!"
s.description = "A simple hello world gem"
s.authors = ["Nick Quaranto"]
s.email = 'nick@quaran.to'
s.files = ["lib/hola.rb"]
s.homepage = 'http://rubygems.org/gems/hola'
end
Die Gemspec-Datei enthält allgemeine Informationen über das Gem und beschreibt dessen Inhalt. Eine vollständige Liste aller Attribute findet man hier.
Die meisten Attribute sind optional. Erforderlich sind date, name, summary und version.

Optional aber dennoch wichtig ist das Attribut files. Es entscheidet, welche Dateien ins Gem kommen -- und welche nicht. Das ist deshalb wichtig, weil wir sonst Dateien mit ins Gem packen (zum Beispiel Repository-Dateien .git, .svn etc.), die da nicht hineingehören. Wer seinen Gem-Quellcode in git verwaltet, kann einfach

    s.files = `git ls-files`.split($/)

schreiben. Das packt dann alle Dateien ins Gem, die im Repository gehalten werden.

Komplexere Gems basieren möglicherweise auf anderen Gems. Um bei der Installation des Gem automatisch dafür zu sorgen, dass Abhängigkeiten aufgelöst und benötigte Gems gleich mit installiert werden, muss man nur mit z.B.

   s.add_dependency('log4r', '>= 1.0.5')

entsprechende Abhängigkeiten definieren.

RubyGems Dot Org

Wir haben ein RubyGem gebaut, die Gemspec-Datei geschrieben und können nun das Gem bauen und lokal installieren. Jetzt möchten wir das Gem veröffentlichen, so dass jeder im Netz es mit

    $ gem list -r

finden und mit

    $ gem install 

installieren kann.

Alles was man dafür braucht ist ein kostenloses Konto auf rubygems.org. Dann kann man dort sein Gem veröffentlichen:

$ gem push <gem-datei>

Don't have an account yet? Create one at http://rubygems.org/sign_up
Email: ...
Password:
Pushing gem to https://rubygems.org...
Successfully registered gem: ...