Ich habe die letzten 3 Tage mit jQuery rumgespielt, sogar daran gedacht bereits aktive Projekte auf jQuery umzustellen, aber dann fand ich zugleich den wahren Nutzen von addMethods heraus. Auf der Suche nach Code-Beispielen für jQuery stiess ich auf einen kritischen Artikel auf Encytemedia. Es wurde erklärt dass der Promo-Artikel im offiziellen jQuery Blog - welcher jQuery's Stärken gegenüber Prototype aufzeigen soll - nicht einfach für Gegeben hingenommen werden sollte. Viele Funktionen von jQuery können auf in Prototype erreicht werden, mit ähnlich simplen Syntax.
Mit Element.addMethods bietet Prototype eine einfache Schnittstelle um DOM Elementen neue Tricks beizubringen. Syntax wie $('element').update('hallo') ist praktisch, schnell, direkt und verständlich. Mit addMethods kann ich meine eigenen Element-bezognen Methoden definieren. Ich halte viele meiner neuen Methoden für sinnvolle Erweiterungen und möchte sie hier mit euch teilen. Vielleicht seht ihr das genauso und möchtet sie adoptieren, oder hat sogar eigene bereits entwickelt. Ich beginne mal mit einem Beispiel:
-
<div id="menu">
-
<ul>
-
<li class="page" id="news">News</li>
-
<li class="page" id="infos">Infos</li>
-
<li class="page" id="about">About</li>
-
</ul>
-
</div>
-
$('menu').findClass('page').invoke('click', function(){
-
$('main').updateAjax('/ajax/element/'+this.id, {
-
onLoading: function(){ $('loading').show(); },
-
onComplete: function(){ $('loading').hide(); }
-
});
-
});
Das Javascript sucht im #menu nach Elementen mit der Klasse 'page' und startet einen EventObserver für Mausklicks, und aktualisiert bei einem Klick auf ein gefundenes Element den Inhalt von #main via Ajax. Wer aber Prototype kennt wird festellen, das hier drei Methoden verwendet werden die nicht in Prototype von Haus aus existieren: findClass, click und updateAjax.
- findClass als kurzform zu Element.getElementsByClassName(className)
- click ist die elementbezogene Version von Event.observe(element, 'click', callback)
- updateAjax ist die elementbezogene Version von Ajax.Updater(element, url, options)
Wie man sieht sind das alles Funktionen die man häufig verwendet und sich immer auf ein einzelnes Element beziehen. Genau hier war mein Ansatz diese dem Element Objekt hinzuzufügen. Das ganze funktioniert so:
-
Element.addMethods({
-
findClass: function(element, argument) { return element.getElementsByClassName(argument); },
-
updateAjax: function(element, url, options) { new Ajax.Updater(element, url, options); return element; },
-
click: function(element, callback) { Event.observe(element, 'click', callback); return element; }
-
});
Ich könnte natürlich auch die $$ Funktion verwenden, aber dadurch das ich das direkte (nächstliegende) Elternelement #menu zuerst ansteuer und dann die optimierte Funktion getElementsByClassName verwende bin ich um längen schneller. Wer es kürzer mag, und wenn Performance nicht wirklich eine Rolle spielt kann natürlich auch so verfahren:
-
$$('.page').invoke('click', function(){ $('main').updateAjax('/ajax/element/'+this.id, {etc..} ); })
Ich habe auch noch ein paar für Script.aculo.us eingebaut und für die Template Klasse, die ich ja schoneinmal beschrieben habe.
-
Element.addMethods({
-
template: function(element, template, data) { element.update(new Template(template).evaluate(data)); return element; },
-
toggle: function(element, type, options, callback) { new Effect.toggle(element, type || 'blind', options); return element; },
-
morph: function(element, options) { new Effect.Morph(element, options); return element; },
-
morphStyle: function(element, argument) { new Effect.Morph(element, {style:argument}); return element; },
-
highlight: function(element, options) { new Effect.Highlight(element, Object.extend({queue:{scope:element.id, position:'end'}}, options || {})); return element; },
-
fadeIn: function(element, options, callback) { new Effect.Appear(element, Object.extend({queue:{scope:element.id, position:'end'}, duration:0.5, afterFinish:callback}, options || {})); return element;},
-
fadeOut: function(element, options, callback) { new Effect.Fade(element, Object.extend({queue:{scope:element.id, position:'end'}, duration:0.5, afterFinish:callback}, options || {})); return element; }
-
});
Vor allem template ist praktisch wenn man mit JSON arbeitet. Hier mal ein Beispiel:
-
new Ajax.Request('/json/users/listing', {onComplete:function(xhr){
-
$('userlist').template('<li>#{username}</li>', eval(xhr.responseText))
-
}
-
});
Natürlich häufige Effekte wie Effect.Appear(element) können so gekürzt werden, da auch sie eigentlich Singeltons sind und immer sich auf ein Element beziehen. Ich habe versucht die chainability (verkettungen), was jQuery so elegant macht, zu simulieren indem ich mit Effect.queues per default arbeite, aber hatte nur bedingt Erfolg. Das automatische scoping mit element.id funktioniert zwar, aber verhindert nicht die direkte Ausführung des zweiten Aufrufs in der Kette.
Beispiel: $('message').fadeIn().fadeOut(); geht nicht, obwohl der gleiche Scope verwendet wird. Das liegt wohl daran das die Position des Vorgängers fadeIn mit fadeOut sofort überschrieben wird und irgendwie beide Effekte synchron laufen. Ich habe versucht das mit dem afterEffect callback zu kompenisieren, aber noch recht unschön, wie ich finde:
-
$('message').fadeIn({}, function(fx){
-
fx.element.fadeOut();
-
});
Code wie $('header').update('blubb').highlight().morphStyle('height:30px'); klappt aber ohne Probleme.
Ich experimentiere hier noch selber. :)
Da nun die Version 1.5.0 finale von Prototype draussen ist, lohnte es sich für mich dessen neue und komplette Dokumentation nun auch in eine CHM zu verbannen.
Ich habe seit gestern Abend bis heute Nachmittag daran gesessen und jeden einzelnen Schnipsel der Doku in mein eigenes Format kopiert und formatiert. Das war eine heiden Arbeit, aber glaubt mir wenn ich sage das es sich gelohnt hat!
Endlich eine saubere und vollständige Doku als CHM.
Ich werde mich bemühen diese immer aktuell zu halten und hoffe das euch meine Version gefällt.
Prototype 1.5 ist nun endlich final. Als wäre das nicht Grund zur Freude genug, gibt es auch gleich eine neue eigene Webseite mit massig Features obendrauf! Das neue Zuhause von Prototype Javascript Frameworks heisst nun http://prototypejs.org/.
Neben den endlich kompletten und überarbeiteten API Docs gibt es nun auch eine offizielle Tips und Tutorials Ecke, sowie natürlich einen Weblog.
Ich begrüsse diesen Schritt und habe mich eigentlich schon immer gefragt warum prototype sich nicht eine eigene Seite spendiert. Nun, das wäre also erledigt. Die Mailingliste bleibt weiter rubyonrails-spinoffs. Einen IRC Channel gibt es auch, aber das is ja nix neues.
Version 1.5.0 Download und die neue Seite findet ihr hier:
http://prototypejs.org/
Ich habe gerade nach einer Referenz für die Template Klasse von Prototype gesucht, aber konnte nichts finden. Letztendlich wurde ich in Japan fündig und daher bin ich der Meinung das ich dass ganze mal für Deutschland schreiben sollte.
Ich persönlich bin schon seit längerem weg von der Ausgabe ganzer HTML Schnipsel via AJAX und bastel die DOM Fragmente in Javascript nach. Was nun letztendlich schneller ist sei dahingestellt. JSON ist meiner Meinung nach das schlankeste Format und somit auch schnell transportiert. Bei Seiten mit viel Traffic zählt jedes Byte. Da kann der Client ruhig ein bisschen Traffic sparen indem er einfaches HTML in seinem Browser generiert. :)
Gestern habe ich ja über den X-JSON Header und Prototype geschrieben und erklärt wie man recht einfach zum Ergebnis kommt. Allerdings muss ich nun sagen das die Sache einen Haken hat, und ich finde das Prototype hier einen blöden Weg beschritten hat.
Damit Prototype ein transport als JSON evaluiert muss X-JSON im HTTP-Header stehen. Das Problem: Ein HTTP-Header ist kein unendlicher Datenspeicher und mag auch nicht jede Art von Daten. Es kann daher vorkommen das einfach eine leere Seite geliefert werd (eben durch einen defekten header).
In meinem Beispiel von gestern habe ich zusätzlich den Content-Type geändert. Und genau hier sollte Prototype - wie auch bei text/javascript - ansetzen um die Daten gegebenfalls zu evaluieren. Somit spart man sich diesen "Header Hack".
Ich habe die Prototype JS ein wenig erweitert um genau das zu tun. Das ganze geht recht schnell und durch die Vorgabe von text/javascript auch leicht zu aktivieren. Code bezieht sich auf Prototype 1.5.0 RC1.
Nach der Zeile 787 in der prototype.js folgende Funktion hinzufügen:
-
evalJSONResponse: function() {
-
try {
-
return eval('(' + this.transport.responseText + ')');
-
} catch (e) {}
-
},
Und in der respondToReadyState Funktion nach Zeile 808 folgendes 'If':
-
if (this.header('Content-type') == 'text/x-json')
-
json = this.evalJSONResponse();
Wenn der Server nun als Content-Type "text/x-json" liefert, statt das übliche "text/html", wird der responseText des transports als JSON evaluiert. Viel besser als header('X-JSON:'.$data); womit nicht immer alles mit transportiert werden kann.
Angeblich definieren folgende Content-Types JSON, aber hier sollte man aufpassen:
JSON ( text/x-json, application/x-json )
Aus dem RFC:
7.4. The Application Content-Type
The "application" Content-Type is to be used for data which do not fit in any of the other categories, and particularly for data to be processed by mail-based uses of application programs. This is information which must be processed by an application before it is viewable or usable to a user.
Application sollte nur benutzt werden wenn es in keine andere Kategorie passt. Da es sich aber um lesbaren Text handelt, ist gemäß des RFCs text/x-json wohl die korrektere Definition.
Hoffe das hier spart einigen ein paar Kopfschmerzen ;-)
Ich bin endlich mal auf den JSON trip gekommen ;) Gibt es ja schon seit einiger Zeit, aber ich dachte mir immer .. "warum? xml is auch ok.."
Aber direkt in Javascript ein Daten-Objekt zu haben ist einfach göttlich. Vor allem in Verbindung mit Prototype 1.5.0 RC1. Bevor ich jetzt zum eigentlichen Thema des Posts komme möchte ich die Möglichkeit des Zusammenspiels von JSON, CakePHP und Prototype verdeutlichen.
Bei Cake gibt es ja die Model Funktionen findBy, findAll, read, etc... welche ein assoziatives Daten-Array aus der Datenbank liefert. Nun mit meinem JSON View wird das Array in JSON umgewandelt, die Daten als text/x-json geliefert und der notwendige X-JSON header für Prototype gesetzt. Super ist auch das jedes Modell ein eigenes Objekt mit Kindobjekten darstellt!
Um den View zu benutzen braucht man lediglich $this->view = "json"; in die Controller-Action zu schreiben und per $this->set die Daten bereitstellen. Super easy.. :-)
Mit Prototype 1.5.0 RC1 gibt es neue String Funktionen und die Template Klasse. Die Objekt Funktion .each( ) kommt hier zum Einsatz und macht es möglich mit nur wenigen Zeilen ein MySQL Result über AJAX in eine schicke HTML Liste zu verwandeln. JSON ist eben ein purer Datencontainer und genau das gibt uns die Freiheit und Geschwindigkeit die wir wollen.
Read the rest of this entry »
Quick n Dirty.. ;)
Diese Funktion fügt allen Bilderlinks innerhalb des angegebenen CSS-Selectors das berühmte ' rel="lightbox" ' hinzu. Es macht praktisch das was mein Wordpress Plugin auch tut, nur eben als Javascript und mit Hilfe der Prototype $$ Funktion. Also wenn zum Beispiel eure Beiträge im DIV mit der ID "posts" liegen müsste der Aufruf wie folgt aussehen
enlightMe('#posts')
Das wars dann auch schon.
Für Lightbox2 Support habe ich noch einen zweiten Parameter eingebaut um die Serien Funktion miteinzubringen.
Wenn ihr den zweiten Parameter mit eine String füllt wird rel="lightbox" entsprechend angepasst ;)
enlightMe('#slideshow', 'slider')
-
/**
-
* Add lightbox to given css selector
-
*
-
* @example enlightMe('#content')
-
* @access public
-
* @param string cssSelector CSS Class or ID with . or #
-
* @param mixed serie Optional param string. Default false. Name of the series.
-
* @return void
-
**/
-
function enlightMe(cssSelector, serie)
-
{
-
var links = $$(cssSelector+' a');
-
var imglink = new RegExp("jpg|gif|png","i");
-
for(var i=0; i <links.length; i++) {
-
var url = links[i].href;
-
if(imglink.test(url)) {
-
if(serie!=undefined){ links[i].setAttribute('rel','lightbox['+serie+']'); }
-
else { links[i].setAttribute('rel','lightbox'); }
-
}
-
}
-
lb = new Lightbox(); lb.initialize();
-
}
In diesem Artikel möcht ich mal all denen helfen die nicht wissen wie sie Foobar2000 0.9x mit dem G15 Keyboard von Logitech kombiniert bekommen. Ich musste lange suchen bis ich was brauchbares gefunden habe. Also als erstes sei gesagt das man für die Kontrolle des Players über die Media Keys kein Plugin für das G15 braucht. Der Foobar bietet dafür die Hotkey Einstellungen die sich ohne weiteres konfigurieren lassen. Wichtig ist hierbei das man die Hotkeys als "Global Hotkeys" definiert, denn ansonsten funktionieren diese nur wenn man die Applikation im Vordergrund hat.
Für die Anzeige braucht man ein Plugin für Foobar. Da gibt es zwar ein spezielles ('foo_g15') aber das ist nur für 0.8x und daher nicht für die neuste Version des Players geeignet. Man muss dem Media Display von Logitech ein wenig was vorflunkern: Es gibt für den Foobar2000 Player ein Plugin das allen Awendungen vorgibt ein Winamp Player zu sein. Sobald man das installiert hat meldet sich auch sofort das Media Display von Logitech und alles wird so angezeigt wie es sich gehört. .. Problem gelöst.
Zusammen mit den Global Hotkeys und der Anzeige hat man nun also das Equivalent eines richtigen G15 Plugins. Gewusst wie. :)
Die einzigst wahre Komprimierung
Auch wenn sich durch das verketten von Variablendefinitionen, das entfernen von Kommentaren und das einsparen von hübscher Formatierung die Dateigrösse bis zu 50% verringert, gibt es eine weitere Methode seine Skripte schneller unter das Volk zu bringen. GZIP hilft nicht nur bei HTML oder bei CSS. Gzip hilft auch bei Javascript und dank Apache sogar ohne sein Script in eine PHP-Datei packen zu müssen. Der wohlmöglich grösste Vorteil dieser Methode ist neben der Komprimierung auf nur 40% der Originaldateigrösse die Möglichkeit das Javascript zu cachen, was den weiteren Verlauf des Besuchs der Seite positiv beeinflusst.
Vorteile:
- Skripte wie z.b. die 50kb grosse prototype.js werden zu angenehmen 17kb häppchen
- Man kann Einfluss auf das Caching des Clients nehmen (Haltezeit, etc.)
- Folgeseiten laden schneller
Nachteile:
- Ältere Browser unterstützen Gzip nicht richtig, jedoch alle Browser der fünften Generation
Überlegungung vor der Implementierung:
- Ist mein Javascript dynamisch (generiert via php o.ä)?
- Benutze ich Skripte die Browser der 5ten Generation sowieso vorrausetzen?
- Ist Apache und PHP für diese Methode konfiguriert?
Erstellt eine PHP Datei mit dem namen "gzip-js.php". Fügt folgenden Code ein und ändert gegebenfalls das Caching oder was euch noch so in den Sinn kommt:
Dann legt diese Datei in den Ordner mit euren Javascripten. In dem gleichen Ordner erstellt eine ".htaccess" Datei mit folgendem Inhalt:
-
AddHandler application/x-httpd-php .js
-
php_value auto_prepend_file gzip-js.php
-
php_flag zlib.output_compression On
Diese Methode funktioniert nur wenn ihr PHP mit der ZLib kompiliert habt und auto_prepend erlaubt ist, bzw. Apache die Verwendung von .htaccess zulässt.
Code von John Cox (quelle)
Nicht alles aufeinmal..
Browser sind dumm - manche sogar dümmer. Also ziehen wir mal unsere Vorteile daraus. Javascript onload-events werden erst ausgeführt nachdem der DOM-Tree geladen ist und zusätzlich alle Bilder geladen wurden. Desweiteren sollte man berücksichtigen das Browser standardmässig nur 2 bis 4 HTTP Requests aufeinmal machen und jeweils gewartet wird bis diese fertig sind. Nun haben wir aber auf der anderen Seite unsere 10 Javascript-Dateien die wir dem Benutzer gerne liefern würden. Üblicherweise würde man diese nun alle im Head-Bereich mit dem Script-Tag der Reihe nach einfügen. Aber halt..
Diese Methode ist überflüssig und lahmt lediglich den Ladevorgang. Letzendlich verschwendet man zum Zeitpunkt an dem die Seite aufgerufen wird nur das limitierte Kontigent an HTTP Requests. Wie zuvor erwähnt führt der Browser die Skripte erst aus wenn der Rest der Seite beim Klienten vorliegt. Frei nach dem Prinzip "erst die Arbeit - dann das Vergnügen".
Um dieses Verhalten zu umseren Vorteil zu nutzen erstellen wir im folgendem ein Skript welches zuständig für das Laden weiterer Skripte ist. Das ganze wird erledigt mit einem Loop, einem Array und document.write(). Der Sinn ist es, dass man im Endeffekt nur noch ein kleines Script hat welches erst dann alle Bibliotheken runterlädt, wenn der Klient Javascript aktiviert hat (entlastung) UND wenn der Browser überhaupt in der Lage ist diese auszuführen. Zwei Fliegen mit einer Klappe :)
Und so sieht das ganze dann aus:
Erstellt eine Funktion welche mit document.write die Script-tags einfügt.
-
var meineSkripte = new Array('prototype', 'lightbox', 'behaviour');
-
-
function writeScriptTag(filename){
-
document.write('<script type="text/javascript" src="scripts/'+filename+'.js"></script>');
-
}
-
function loadScripts() {
-
for(var i=0;i<meineSkripte.length;i++){ writeScriptTag(meineSkripte[i]); }
-
}
-
window.onload = loadScripts();
Wenn eure Seite geladen wird lädt der Browser erstmal das ganze HTML samt Bilder und DANACH führ er erst die 'loadScripts' Funktion aus, welche die eigentlichen Skripte runterlädt.
Vorteile:
- Man kann fast zu 100% sicher sein das der DOM komplett verfügbar ist. Für eventuell geplante Manipulation etc..
- Die Seite wird schneller geladen, weil man den Browser nicht mit noch nutzlosen Scriptdownloads beschäftigt
- Javascripte werden nur geladen wenn der Benutzer auch Javascript aktiviert hat. (Spart Traffic und Nerven)
Nachteile:
- Mir sind keine bekannt. window.onload geht meines Wissens nach in allen Browsern.
Überlegungung vor der Implementierung:
- Warum man das nicht schon immer so gemacht hat ;)
Die etwas andere Richtung
Zuvor haben wir ja das verzögter Ausführen für nützlich befunden. Es mag jedoch Fälle geben in dem das nicht erwünscht ist. Allerdings muss ich gleich dabei sagen das meine Erfahrungen mit dieser Methode positiv sowie negativ waren. Es kommt eben auf das Anwendungsgebiet an. Dean Edwards hat sich ein wenig mit dem "onload"-Problem auseinandergesetzt und einen Weg gefunden wie er das Ausfühen von Javascript NACH dem laden des DOM und VOR dem laden enthaltener Bilder möglich ist.
Gerade auf Seiten mit grossen Bildern kann es hier zu argen Problemen kommen, da der Benutzer wichtige Funktionen oder Navigationselemente erst verwenden wenn der Ladevorgang abgeschlossen ist. Das kann unbeachtet ein Besucher-Killer sein.
Testet es einfach mal selbst mit dem Code von seinem Blog. Falls ihr Prototype einsetzt, kann ich die FastInit Klasse empfehlen. Sie ermöglicht auch mehrere Funktionen die vor dem Laden der Bilder gebraucht werden aneinander zu ketten.
Vorteile:
- Man kommt schneller an den DOM.
- Man kann Berechnungen auf dem Client erledigen die er später braucht, aber ohne die Bilder noch nicht einsetzen kann.
Nachteile:
- Internet Explorer nur via externer Datei, Safari und Opara nur über Umwege (Q)
Überlegungung vor der Implementierung:
- Was wird als erstes gebraucht (dhtml menüs, etc.)?
- Welche Browser haben die Besucher (zielgruppe)
- Kommt mein Skript damit klar so schnell zu sein?
Pierre Francois beschreibt die wichtigsten Zutaten für einen erfolgreichen Web2.0 Startup und was man für die Zukunft wirklich alles benötigt. AJAXSQLRUBYCSSXHTML etc.. In diesem 17 minütigen Video, aufgenommen im Barcamp Boston, wird auf lustige Art und Weise (und in Englisch) ein wenig Web2.0 durch den Kakao gezogen. Die Einblendungen auf der linken Seite sind die Folien vom Beamer zur besseren Lesbarkeit. Viel Spass :)

