Tooltips.. die kleinen Text-Popups die einem helfen sich zu orientieren. Immer wieder gerne gesehen und wurde auch schon zig-mal programmiert.
Ich hab mir schon so einige fertige Scripts angesehen, aber die meisten machen einfach viel zu viel und nutzen teilweise sogar selber Prototype, nur leider nicht effizient genug.
Meistens werden die DIVs beim Aufruf der Seite generiert und irgendwo unterhalb des BODYs "versteckt". Bei sehr vielen Tooltips/Links kann das den Seitenaufbau extrem verlangsamen. Andere wiederum berechnen gott-weiss-was und haben einen mehrseitigen Umfang. Da oft gleich auch eine Klasse geschrieben wird muss man meist eine weitere Scriptdatei laden, was nochmals den Aufbau verzögert.
Ich schreibe daher meine Tooltips immer selber und bin mittlerweile runter auf 10 Zeilen. Könnte sogar in einer passen, aber man will ja nicht den Überblick verlieren.
-
$$('a[title]').invoke('observe', 'mouseover', function(evt){
-
var element = evt.findElement('a'), text = element.getAttribute('title');
-
var tooltip = new Element('div', {'class':'tooltip'}).update(text);
-
element.removeAttribute('title'); element.setAttribute('_title', text);
-
element.insert({'after': tooltip.setStyle({'left':element.positionedOffset().first()+'px'})});
-
}).invoke('observe', 'mouseout', function(evt){
-
var element = evt.findElement('a'), text = element.getAttribute('_title');
-
element.removeAttribute('_title'); element.setAttribute('title', text);
-
if (element.next().hasClassName('tooltip')) element.next().remove();
-
});
- Es wird OnMouseOver ein DIV kreiert mit dem Inhalt des TITLE Attributs eines Links. OnMouseOut wird das DIV einfach wieder entfernt.
- Damit das Browser Tooltip nicht zusätzlich angezeitg wird, verschiebe ich bis zum OnMouseOut den Inhalt des TITLE Attributs kurz in ein Fake-Attribut namens _TITLE.
- Der Selector
a[title]wählt nur Anker die auch wirklich ein TITLE Attribut haben. Somit brauchen wir auch nicht mehr zu kontrollieren ob eines vorhanden ist. - Vom Javascript aus wird nur LEFT gesetzt, TOP braucht man nicht zwingend.
- Am Ende von OnMouseOut prüfe ich ob das nächste Element wirklich die tooltip Klasse hat. Das ist nur eine reine Vorsichtsmaßnahme. Wer dennoch Probleme hat könnte etwas radikaler einfach alle DIVs mit dieser Klasse löschen via getElementsByClassName (ist am schnellsten).
Das nachfolgende CSS kümmert sich um den z-Index und den Abstand zum Cursor.
div.tooltip {
color: #000;
background-color: #FAFAFA;
position: absolute;
z-index: 9999;
padding: 5px;
margin: 5px;
margin-left: 15px; /* Abstand zum Zeiger */
}
So, und das war's. Wenn man nun irgendwo auf der Seite ein <a ... title="mein tooltip"> platziert hat man einen Link mit Tooltip. Zero config, wenn man so will.
Man kann natürlich auch ohne Probleme auf andere Elemente erweitern. Beispiel: $$('a[title], img[title]')...
Falls jemand Verbesserungsvorschläge hat: Immer her damit!
Script wurde in IE, FF und Safari getestet.
Justin Palmer hat ein Snippet für Prototype gepostet welches das Date Object um strftime() erweitert.
-
Object.extend(Date.prototype, {
-
strftime: function(format) {
-
var day = this.getDay(), month = this.getMonth();
-
var hours = this.getHours(), minutes = this.getMinutes();
-
function pad(num) { return num.toPaddedString(2); };
-
-
return format.gsub(/\%([aAbBcdHImMpSwyY])/, function(part) {
-
switch(part[1]) {
-
case 'a': return $w("Sun Mon Tue Wed Thu Fri Sat")[day]; break;
-
case 'A': return $w("Sunday Monday Tuesday Wednesday Thursday Friday Saturday")[day]; break;
-
case 'b': return $w("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec")[month]; break;
-
case 'B': return $w("January February March April May June July August September October November December")[month]; break;
-
case 'c': return this.toString(); break;
-
case 'd': return pad(this.getDate()); break;
-
case 'H': return pad(hours); break;
-
case 'I': return pad((hours + 12) % 12); break;
-
case 'm': return pad(month + 1); break;
-
case 'M': return pad(minutes); break;
-
case 'p': return hours> 12 ? 'PM' : 'AM'; break;
-
case 'S': return pad(this.getSeconds()); break;
-
case 'w': return day; break;
-
case 'y': return pad(this.getFullYear() % 100); break;
-
case 'Y': return this.getFullYear().toString(); break;
-
}
-
}.bind(this));
-
}
-
});
[via Justin Palmer]
Schön gelöst. Weitere ähnliche Schnippsel von ihm gibt es im GitHub.
John-David Dalton hat mal wieder das Protoculous Paket aktualisiert. Endlich ist das neue Prototype 1.6 um 60% kleiner. Diesmal hat er auch ein kleineres Protoculous (nur mit effects.js) erstellt, welches in der Packer Variante ohne GZIP nur 64k wiegt (mit GZIP nur 28k).
Natürlich gibt es auch wieder das normale Protoculous (Prototype 1.6 mit Scriptaculous 1.8) als Packer (92k) und Shrinkvars Version (152k).
Nun kann man wohl getrost umsatteln. Und wenn die "deprecated" Sektion aus Prototype rausfliegt wird es wohl noch schmaler.
Happy coding!
In letzter Zeit ist es ja recht still hier im Blog. Das hat einen guten Grund: Meine Arbeit bei Stylished.com. Heute möchte ich euch die hübsche Ajax Anwendung zeigen, welche ich die letzten Tage entwickelt habe.
Stylished ist eine soziales Netzwerk für Models, Fotografen und Modelagenturen. In der angeführten Anwendung geht es darum, dass Models in einem virtuellen Contest, eins gegen eins, antreten und sich somit Punkte verdienen.
Die Teilnahme funktioniert automatisch und die entstehenden Wertungen fliessen in eine Rangliste mit den Kategorien Newcomer, Professionell und Semi-Professionell.
Von der technischen Seite ist die Anwendung komplett in Javascript geschrieben und wird liebevoll mit JSON gefüttert. Als Ajax-Bibliothek wurde natürlich das von mir bevorzugte Protoype und Script.aculo.us Paket gewählt.
Zum Contest: http://www.stylished.com/contests/
Viel Spass damit!
Den Benutzer nicht gleich umzuleiten wenn er sicht registrieren möchte ist chique, modern und freundlich. Das geht aber nur mit Javascript, oder doppelten Code, bzw. zwei Formularen. Aber wenn der Benutzer kein Javascript hat, geht es entweder überhaupt nicht, oder wir müssen die Struktur so ändern, dass wir andere Links setzen.
Hier mal ein Vorschlag wie man das geschickt umgehen kann.
-
var AppRules = {
-
'a.swaplink:click': function(element) {
-
new Effect.toggle(element.getAttribute('href').split('#')[1], 'appear', {duration:0.4});
-
// brich den inhalt von href auseinander und verwende den hinteren teil.
-
}
-
}
-
-
FastInit.addOnLoad(function(){
-
EventSelectors.start(AppRules);
-
});
Unsere Links müssten dann so aussehen:
-
<a href="signup.php#signup" class="swaplink">Registrieren</a>
Unser Formular müsste dann eine versteckte Sektion beinhalten, welche die zur Registrierung zusätzlich notwendigen Felder beinhalten.
-
<div id="signup" style="display:none">
-
... wähle ein passwort, etc..
-
</div>
Wenn Javascript aktiviert ist erhält der "swaplink" unser Event, welches "href" auseinanderbricht und nur das verwendet was hinter der Raute steht um ein Element mit der daraus resultierenden ID zu 'togglen'.
Wenn Javascript nicht aktiviert ist wird der Benutzer ganz einfach auf die "signup.php" umgeleitet. Zusätzlich hätte man auch die Sprungmarke auf der signup.php um es dem Benutzer noch angenehmer zu machen.
So einfach kann das sein! :)
Verwendete Bibliotheken: prototype.js, event-selectors.js, fastinit.js, effects.js
So wurde bisher in Prototype.js eine Klasse erstellt:
-
var Message = Class.create();
-
Message.prototype = {
-
initialize(title, message) {
-
this.title = title;
-
this.message = message;
-
},
-
format: function() {
-
return this.title + "\n" + this.message;
-
},
-
show: function() {
-
alert(this.format());
-
}
-
}
Und so haben wir diese dann erweitert, komplett mit "Variable.prototype" und "Object.extend", aber ohne wirklichen Bezug zur abgeleiteten Klasse.
-
var ErrorMessage = Class.create();
-
ErrorMessage.prototype = Object.extend(new Message(), { // überschreibe format()
-
format: function() {
-
return 'ERROR: ' + this.title + "\n" + this.message;
-
}
-
});
In Prototype 1.6 ist dass nun alles ein wenig einfacher. Anstatt noch mal mit "Variable.prototype = {...}" zu beginnen, übergeben wir den "Hash" direkt als Parameter von Class.create().
-
var Message = Class.create({
-
initialize(title, message) {
-
this.title = title;
-
this.message = message;
-
},
-
format: function() {
-
return this.title + "\n" + this.message;
-
},
-
show: function() {
-
alert(this.format());
-
}
-
});
Das Erweitern ist jetzt ähnlich PHP und liest sich fast ebenso natürlich wie "Klasse extends AndereKlasse". Wir nehmen auch hier direkt den Namen der Variable (bzw. Klassenname) anstatt "new Variable()". Hinzu kommt dass "Object.extend" komplett wegfällt. Man beachte das $super, welches die Kopie der originalen Methode ist.
-
var ErrorMessage= Class.create(Message, {
-
format: function($super) {
-
return 'ERROR: ' + $super(); // vergleichbar mit parent::format(); in PHP
-
}
-
});
Die vorherige Klasse hatte kein Parameter in der Funktion, jedoch wird $super nun definiert. Warum?
Das ist nur der Fall bei Unterklassen und ist optional. Sollte die ursprünglich Methode bereits Paremeter erwarten, sind diese dann einfach als zweiter Parameter zu definieren. Die neue Methode lässt sich dann genau wie das Original verwenden.
Hier mal ein Beispiel:
-
var notice = new Message('Nur zur Info', 'Dies ist das normale Format');
-
notice.show(); // wirft alert('Nur zur Info ...');
-
-
var error = new ErrorMessage('Es trat ein Fehler auf', 'Deshalb ein anderes Format');
-
error.show(); // wirft alert('ERROR: Es trat ein ...');
Ich habe eine Testseite angelegt und auch ein paar weitere Beispiele angefangen. Vor allem ist addMethods interessant, da es die hinzugefügten Methoden anschliessend an alle Instanzen vererbt. So könnte man zum Beispiel durch eine erfüllte Bedingung allen Klassen "auf Knopfdruck" neue Möglichkeiten einräumen (mutieren).
Naja.. schaut euch einfach mal die Tests an und ansonsten wie gehabt:
Happy coding!
Auf Ajaxian entdeckte ich heute die sprinkle.js. Ein Skript welches das dynamische laden von Inhalten via eines Pseudoattributes "src" ermöglichen soll. Abgesehen davon dass dies nicht valide ist, da es kein "src"-Attribut für ein DIV gibt, waren die Kommentare im Gegenzug dazu doch recht erfrischend. :-)
Hier ein Beispiel wie sprinkle.js funktionieren soll:
-
<script src="sprinkle.js"></script>
-
<div src="info.html"></div>
-
<!-- info.html wird via ajax in das div geladen, on body load -->
Die sprinkle.js ist rund 300 Zeilen lang und enthält eine Menge Schönheitsfehler und ist eigentlich ein gutes Beispiel wie man es nicht machen sollte. Wem aber diese Lösung gefällt, und bereits eine Bibliothek wie Prototype oder jQuery verwendet, sollte sich mal die folgenden Beispiele ansehen.
jQuery - www.jquery.com
-
$('textarea[src], div[src], span[src]').each(function() {
-
$(this).load(this.src);
-
});
Prototype -www.prototypejs.org
-
$$('textarea[src], div[src], span[src]').each(function(el){
-
new Ajax.Updater(el, el.src||'');
-
});
Somit wäre das mit der sprinkle.js wohl dann auch geklärt ;)
Ich las grad wieder auf Ajaxian darüber und auch ich selbst steh dem oft gespalten gegenüber. Ich leuge nicht ein starker Vertreter von Prototpye.js zu sein, und wahrscheinlich werden folgende Gedanken davon geprägt sein, aber ich finde ich muss mal schreiben was ich an Prototype so wirklich mag und was ich bei jQuery vermisse.
Das Frameworks einen neuen Schreibstil mit sich bringen ist bekannt. Von den ganzen Codeschnipseln und endlosen Experimenten kann ich sagen dass Prototype einfach eleganter ist, bzw. es liest sich einfach schöner.
Ich würde zum Beispiel niemals auf die Idee kommen einen Ajax-Chat oder ein komplexes Interface mit jQuery umzusetzen, denn dort würde man (einfach um den Überblick nicht zu verlieren) Klassen und Objekte erstellen um die Anwendung in handliche Einheiten zu zerlegen. Dazu ist jQuery meiner Meinung nach einfach nicht Gedacht. Prototype wartet hierzu mit einem ganzen Arsenal von Utilities auf.
Bei jQuery gibt es viele Nebenprodukte wie $.each, die vielleicht wie eine Alternative zu Prototype erscheinen, aber auf mich eher wirken als wäre den Entwicklern später eingefallen das sie noch was vergessen haben. Aber deshalb sind es eben zwei paar Schuhe.
In jQuery dreht sich alles um Elemente (eher Sammlungen von Elementen). Es wird immer davon ausgegangen dass man mit HTML arbeitet, was meiner Meinung nach ein riesiger Nachteil von jQuery ist. Zur DOM-Manipulation bestens geeeignet (und man kann dort wirklich mächtige Ein-Zeiler schreiben), aber wenn man mit fremden Daten arbeitet kommt man schnell ins Schleudern. Dann ist plötzlich jQuery nicht mehr so toll.
Prototype hat seine stärken bei der Verarbeiten von Daten. Arrays, Hashes und Strings. Das liegt daran, weil Prototype eine ganz andere Route verfolgt. Es erweitert die vorhandnen Javascript Objekte und würzt es mit einem einfachen Modell zum Erstellen von Klassen. Es hilft einem also nicht nur schnell an irgendwelche Elemente zu kommen, sonder greift einem auch in anderen Bereichen unter die Arme.
Demnach kann man jQuery und Prototype auch gar nicht wirklich gegenüber stellen.
Bei der Frage nach Prototype oder jQuery antworte ich eigentlich deshalb meist: Wollen wir ein wenig HTML manipulieren, oder wollen wir eine Ajax-Anwendung schreiben? Letzteres wäre dann Prototype.
Man muss nur mal einen Blick in Dokumentation beider schauen. Bei Prototype gibt es für alles etwas. Bei jQuery stellt der DOM Abschrnitt alles andere in den Schatten.
Prototype kann einfach mehr und lässt einen nicht irgendwann im Dunkeln, weshalb Prototype mein Favorit für jede Anforderung ist.
Vor einiger Zeit habe ich mal mein eigenes Ajax.JSONRequest() geschrieben, weil ich kein Fan davon bin 100 mal das gleiche zu schreiben. Damit das alles klappt habe ich mich nach dem richtigen Content-Type umgesehen und den Prozess darauf aufgebaut. Der korrekte Typ ist application/json und falls gesetzt, evaluieren den responseText automatisch.
Die neue Prototype 1.6 Version nutzt ein ähnliches Prinzip und liefert uns nun ein transport.responseJSON. Vorbei sind die Zeiten von transport.responseText.evalJSON(). Ich frage mich warum nicht gleich so, wo doch text/xml schon seit längerem dafür gesorgt hat das wir responseXML haben. Naja.. super Schritt vorwärts.
-
getResponseJSON: function() {
-
var options = this.request.options;
-
try {
-
if (options.evalJSON == 'force' || (options.evalJSON &&
-
(this.getHeader('Content-type') || '').include('application/json')))
-
return this.transport.responseText.evalJSON(options.sanitizeJSON);
-
return null;
-
} catch (e) {
-
this.request.dispatchException(e);
-
}
-
}
Es gibt auch nocht ein paar neue Optionen für Ajax.Request.
- sanitizeJSON (bool) - Prüft automatisch auf böse Inhalte und blockiert die Ausführung falls etwas gefunden wird. Ist standardmässig "true", aber ob das so gut ist, weiss ich nicht. In der ersten Revision gab es Probleme je nach Anwendungsgebiet.
- evalJSON (bool) - Damit kann man das neue Verhalten der automatischen evaluierung beeinflussen. Standard ist "true" und gut.
- evalJS (bool) - Gleiches Prinzip, anderer Inhalt. Wenn der Server text/javascript liefert wird der responseText direkt ausgeführt.
Angeblich soll diese Version 1.6.0 RC_0 recht stabil sein, aber ich warte lieber und werde nach und nach die Api Doku aktualisieren.
Geoffrey Grosenbach und Justin Palmer vom Prototype Core-Team haben sich die letzten Wochen über zusammengesetzt und den zweiten Teil der Prototype Video Serie zusammengestellt. Das Video geht 90 Minuten und zeigt euch wie man mit Ajax und Json seiner Applikation den letzten Schliff verpasst, und worauf man achten muss bevor man Ajax verwendet. Wie auch im ersten Teil wird empfohlen das Tutorial mit Firefox samt Firebug zu verfolgen. Beispiel Rails Projekte die im Video verwendet werden, liegen dem Download bei.
Ihr könnt das Video (quicktime und ipod format vorhanden) für 9 Dollar (rund 6 Euro) via PayPal oder Google Checkout bei peepcode.com kaufen.

