Category Archives: Javascript

VZnet @ IPC 2011 Spring

We are happy to announce that we will take part at this years International PHP Conference (May 29 – June 1 in Berlin) with the following talk:

Mashing up JavaScript – Advanced Techniques for modern Web Apps

Bastian Hofmann,VZnet Netzwerke Ltd.

2011-05-31 | 02:15 PM – 03:15 PM

Nowadays many modern web applications are solely relying on JavaScript to render their frontend. But if you want to create mashups, load data from many different places or include external widgets into your site, you are quickly running into boundaries because of browser and security restrictions. In this presentation I will talk about techniques helping you with such problems.

For more information, tickets etc see http://phpconference.com/2011spring

Traversieren/Modifizieren von TextNodes mit JQuery

In einer Webanwendung gibt es manchmal die Situation, dass man in bestehendem HTML-Code bestimmte Textinhalte modifizieren muss, unter Verwendung von zusätzlichem Markup. Z.B. Links klickbar machen, Highlighting von Suchergebnissen, etc. Folgendes Sample zeigt, wie man mit JQuery die Textnodes iterieren und dabei modifizieren kann:

// newElement enthält das zu modifizierende HTML. Dabei soll "Mein" durch "<b>Mein</b>" ersetzt
// werden, aber nur in Texten, nicht an sonstigen Stellen im HTML-Code
var newElement = $(['<ul><li>Erste <a href="http://www.Mein.de/">',
    'URL anders?</a> Zeile</li>',
    '<li>Meine zweite Zeile</li><li>Dein und Mein</li>',
    '<li>Nicht Mein sondern Main</li></ul>'].join(''));
  
// Diese Zeile selektiert/filtert die TextNodes in newElement:
var textnodes = $('*', newElement).contents().filter(function(){ return this.nodeType == 3 ; });
  
// Durchlaufen der TextNodes:
$.each(textnodes, function () {
  // mit this.nodeValue erhält man den Text-Inhalt des Text-Elements im DOM
  if (this.nodeValue.indexOf('Mein')>=0) {
    //Ersetzen des TextNodes durch ein HTML-Konstrukt
    $(this).replaceWith(this.nodeValue.replace('Mein', '<b>Mein</b>'));
  }
});
  
// Anzeige des Ergebnis
console.log(newElement.html());

Label inside – Beschriftung in Formularfeldern

(Dies ist eine leicht gekürzte Fassung des Artikels Label inside – Beschriftung in Formularfeldern.)

Auf unseren Plattformen verwenden wir an mehreren Stellen Eingabefelder, bei denen ihre zugehörige Beschriftung im Eingabefeld selbst angezeigt wird. Dies ist platzsparend und dem Nutzer können zusätzliche Informationen gegeben werden.

Die technische Umsetzung, die Beschriftung in den Wert des @value-Attribut des input-Elements bzw. den Inhalt des textarea-Elements zu setzen und per JavaScript aus- und einzublenden, ist jedoch problematisch:

  • Beschriftung und wirkliche Nutzereingabe im Feld müssen unterschieden werden; der Textstil (Farbe) muss mit JavaScript geändert werden. Sollen auch vorbelegte Felder verwendet werden, muss die Unterscheidung auch im Markup (HTML) erfolgen.
  • Wenn das JavaScript zu spät ausgeführt wird, gibt der Nutzer schon etwas ins Feld ein, bevor dessen Beschriftung gelöscht wurde. Dadurch wird der Beschriftungstext unerwünscht zu einem Teil der Eingabe.

Lösung mit label

Markup: Besser realisiert man die Beschriftung mit dem dafür vorgesehenen HTML-Element label. Dieses wird zusammen mit dem Eingabefeld in einem Containerelement der Klasse “labelinside” gekapselt. Auch vorbelegte Felder sind möglich:

<p>
  <span class="labelinside">
    <label for="foo">Label inside</label>
    <input id="foo"/>
  </span>
  <span class="labelinside">
    <label for="baz">Label inside</label>
    <input id="baz" value="vorbelegt"/>
  </span>
</p>
<div class="labelinside">
  <label for="bar">Label inside</label>
  <textarea id="bar"></textarea>
</div>
<div class="labelinside">
  <label for="quz">Label inside</label>
  <textarea id="quz">vorbelegt</textarea>
</div>

Stylesheet: Damit die Beschriftung im Eingabefeld erscheint, wird das label-Element per absoluter Positionierung aus dem Fluss genommen und im Containerelement oben links positioniert. Dazu muss das Containerelement selbst auch positioniert sein:

.labelinside
{
  margin: 3px 0;
  position: relative;
}

.labelinside label
{
  color: silver;
  cursor: text;
  display: none;
  font-size: 0.8em;
  left: 0;
  line-height: 0.8em;
  padding: 6px 3px;
  position: absolute;
  top: 0;
}

.labelinside input,
.labelinside textarea
{
  margin: 0;
}

JavaScript: Wir verwenden auf unseren Plattformen das jQuery-Framework. Damit lassen sich recht einfach alle input- und textarea-Elemente selektieren, die Kindelemente eines Containerelements der Klasse “labelinside” sind. Beim Seitenaufruf sollen die Beschriftungen aller dieser Eingabefelder eingeblendet werden, die nicht vorausgefüllt sind; die anderen bleiben ausgeblendet. Beim Fokussieren eines Eingabefeldes wird dessen Beschriftung ausgeblendet; beim Verlassen wird dessen Beschriftung eingeblendet, wenn kein Eingabewert im Feld steht.

Außerdem wird die display-Eigenschaft von inzeiligen Containerelementen auf “inline-block” geändert, damit die Beschriftung auch bei diesen passt; dies jedoch nicht für Internet Explorer < 8 (Abfrage per conditional compilation und document.documentMode).

var $inputControl = $(".labelinside>input, .labelinside>textarea");

$inputControl.each(function (index, domElement)
{
  /*@cc_on if (document.documentMode && document.documentMode >= 8) @*/
  if ($(this).parent().css("display") == "inline")
    $(this).parent().css("display", "inline-block");

  if (!$(this).val())
    $(this).parent().children("label").show();
});

$inputControl.bind("focus", function(event)
{
  $(this).parent().children("label").hide();
});

$inputControl.bind("blur", function(event)
{
  if (!$(this).val())
    $(this).parent().children("label").show();
});

Und so sieht’s aus: Beipiel 1

Automatisch fokussiertes Eingabefeld

Besondere Beachtung erfordert das beim Seitenaufruf automatisch fokussierte Eingabefeld (HTML5: @autofocus), denn für dieses ist das initiale Ausblenden der Beschriftung nicht ratsam, wenn der Nutzer dadurch keine Information bekommt, was er in dieses Feld eintragen sollte. Auf unseren Plattformen betrifft dies bspw. das Login-Feld.

Hier wird die Beschriftung anfangs eingeblendet. Im Internet Explorer ist dabei eine kurze Verzögerung notwendig (per conditional compilation eingebunden, damit die Ausführung in anderen Browsern nicht verzögert wird). Die Beschriftung wird ausgeblendet, sobald Text eingegeben wird oder wenn mit der Maus in das Eingabefeld geklickt wird.

var $autofocus = $inputControl.filter("[autofocus]");  // oder Selektion per ID

$autofocus.focus();  // setzt den Fokus auch in Browsern, die @autofocus noch nicht interpretieren

if (!$autofocus.val())
  /*@cc_on setTimeout(function () { @*/
  $autofocus.parent().children("label").show();
  /*@cc_on }, 0); @*/

$autofocus.bind("click", function(event)
{
  $(this).parent().children("label").hide();
});

$autofocus.bind("keyup", function(event)
{
  $(this).parent().children("label").hide();
});

Und so sieht’s aus: Beipiel 2

Vorteile dieser Lösung

Bei dieser Lösung genügen wenige Codezeilen für alle Eingabefelder auf einer Webseite. Für neu hinzukommende Eingabefelder ist kein zusätzliches JavaScript erforderlich.

Präsentationsschicht (CSS) und Verhaltensschicht (JavaScript) sind sauber voneinander getrennt. Beschriftung und Nutzereingaben sind sauber voneinander getrennt; eine ungewollte Übernahme des Beschriftungstextes als Eingabe kann nicht erfolgen. Zur Unterscheidung von Beschriftung und Nutzereingaben in den Feldern sind keine Flags oder gar Stringvergleiche des value mit dem (für jedes Eingabefeld anderen) Beschriftungstext erforderlich. Der Stil für den Beschriftungstext steht im Stylesheet; mit JavaScript muss lediglich die Sichtbarkeit geändert werden, nicht jedoch die Textfarbe o.a. Dadurch ist die Lösung nicht nur einfach, sondern auch schnell.

OpenSocial session at Berlin GTUG

At the next Berlin Google Technology User Group Meeting we will do a session on writing OpenSocial Gadgets for VZ-Netzwerke. We will cover the OpenSocial basics, People and AppData Services, the OpenSocial REST Api as well as the VZ-Gadget-Sandbox and deployment process.

Berlin GTUG

The meeting will take place at Zanox GAP Campus (Stralauer Alle 2, 10245 Berlin) on Tuesday, 9/Feb/2010, 18:30.

For the full agenda and registration see http://www.amiando.com/Berlin-GTUG-201002.html

Slides

Memcache Feeds

Buschfunk, die Möglichkeit die Statusnachrichten (“Ist gerade…”) deiner Freunde auf unseren VZ’s anzuzeigen, ist nun/nur der Beginn der VZ Feeds. Letzlich ist der Buschfunk nur die Zusammenführung aller Statusnachrichten deiner Freunde auf der Startseite.

Nach dem Launch der ersten Version des Buschfunks, gab es bereits wenige Minuten später einen riesigen Impakt auf unserer Serverfarm. Dies führte dazu, dass wir bereits nach einem Tag die Statusnachrichtendatenbank auf eigene Server umziehen mussten. Ihr seht hier die absolute Anzahl von Statusaktualisierungen pro Minute (getrennt nach studiVZ/meinVZ = blau/flacher Graph und schuelerVZ = rot/steiler Graph):

zugriffe_mb2

Man kann Feeds, also Mitteilungen über Statusaktualisierungen eines Freundes, unterschiedlich implementieren und stellt sich dabei einigen Herausforderungen, gerade wenn es nicht nur um die optimale Speicherung, sondern auch um performante Zugriffe und logisches Zusammenführen von ähnlichen Feedeinträgen geht.

Wir haben uns für erste Tests in Richtung Social-Feeds für eine reine Memcache-Implementierung entschieden. Man hat den Vorteil, dass man die ohnehin nur momentan interessanten Posts von Usern nicht in der Datenbank vorhalten muss und Memcacheoperationen dazu noch um einiges schneller sind. Für einen Feed baut man sich im einfachsten Fall eine Queue pro User mit sortierten Einträgen auf, die man als Entity im Memcache ablegt. Da man nicht unendlich viele Einträge vorhalten muss und ein Memcacheobjekt per default eh nur 1 MB pro Eintrag groß sein darf, limitiert man die Queue auf eine feste Anzahl und wirft bei einem neuen Eintrag einfach alte Einträge weg.

Ok, genug der Einleitung, kommen wir zu ein paar Codeschnippseln. Am besten baut man sich ein Interface, welches sich um das Handling von Feedeinträgen kümmert:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface FeedEntry {
 
//referenziertes Memcacheobject, also eigentlicher Inhalt
public function getFeedEntryReference(...);
 
//ist der Eintrag sichtbar -> Privacy
public function isFeedEntryHidden(...);
 
//initialer Aufbau
public function initializeFeedEntry(...);
 
[...]
 
}

Jetzt haben wir einen beliebig erweiterbaren Feed Typen und können mit Implementierungen, wie zum Beispiel Statusänderungen von Nutzerprofilen, Microblogeinträgen etc, beginnen.

Was man jetzt noch braucht, ist das eigentliche Aufbauen der Queue, also Füllen des Feeds mit Feed Typen. Man sollte sich überlegen, woraus ein Eintrag innerhalb der Queue aussehen soll. Es sollte die id des Users, einen Zeitstempel, sowie den eigentlichen Inhalt enthalten. Wir haben uns dafür entschieden, den Inhalt des Eintrages nur als Referenz in der Queue zu halten, damit bei Änderungen nicht jedes Queueobjekt, sondern lediglich das referenzierte Memcacheobjekt geändert werden muss. Ausserdem will man die Daten nicht doppelt im Speicher halten.

Beispielhaft könnte eine vereinfachte Queue folgendermaßen aussehen:

1
2
3
4
5
$queue = array(
0 => array ('timestamp' => time(), 'userId' => 123456789, 'contentId' => 1000, 'type' => TYPE_MICROBLOG),
1 => array ('timestamp' => time(), 'userId' => 1234567910, 'contentId' => 1001, 'type'  => TYPE_PHOTOCOMMENT)
[...]
);

Mit Hilfe des Typs und dessen konkreter Implementierung eines Feed Entries kann nun der eigentliche Inhalt aus einem weiteren Memcacheobjekt oder aus der Datenbank geholt werden. Sortiert ist die Queue bereits nach dem Einfügen eines neuen Entries. Wird nun der Content hinter einer solchen Referenz gelöscht, so braucht man die Queues der User, dessen Feeds beeinflusst werden, nicht updaten, da der Feed beim Einlesen automatisch merkt, dass die Referenz nicht mehr gültig ist und diese “überspringt”.

Für’s Aufbauen der Queue noch ein Tipp: Verwendet lieber mehrmaliges array_reverse im Zusammenhang mit array_push, anstatt ein array_shift! Das ist um Welten schneller, wenn man ein Element vorn ranhängt bzw. hinten anfügt.

(Quelle: http://www.ingo-schramm.de/blog/archives/9-PHP-array_shift-does-not-scale.html)

Da der Memcache, wenn der ihm zugewiesene Speicher vollläuft, wenig frequentierte Bereiche freigibt, muss man sich zwangsweise überlegen, wie man mit Datenverlusten innerhalb der Queue umgeht. Dazu könnte man zyklische Backups der Queueeinträge oder ein initiales Befüllen der Feedeinträge implementieren (im Interface bereits vorgesehen). Die eigentlichen Daten (bsp. das konkrete Statusupdate) bleiben natürlich erhalten und liegen in der Datenbank persistent vor, es geht hierbei nur um die Referenzen auf diese Einträge.