Tim Tepaße: Verschachtelte Daten fürs Sortieren vorbereiten

Beitrag lesen

-Ist es klug, den Linknamen & Klickzahl so zu speichern, wie ich
es momentan mache, oder würdest du raten, es in einem
anderen Format zu speichern?

Jain. Deine grundsätzliche Idee ist schon praktikabel. Ich würde es aber entweder sauberer (technischer Fachbegriff) oder aber gleich mit JSON machen.

Was heisst sauberer? Du verwendest in Deinem Cookie-String die Trennzeichen "=" und ";". Beides sind Zeichen, die in URLs vorkommen können. Bei einem split kann dann die URL statt des Cookie-Strings getrennt werden und einem hinterher die Daten durcheinander kommen. Die Lösung ist es, die Trennzeichen vorher in der URL zu maskieren, d.h. durch andere Strings zu ersetzen. Ehe Du Dir vor den Kopf schlägst ... da gibt es schon was Praktisches in Javascript mitgeliefert: encodeURIComponent().

Ich bastel hier mal einen Klicktracker, wie ich ihn unter diesem Aspekt halbwegs implementieren würde:

~~~javascript // Der Klicktracker ist eigentlich nur ein Objekt in dem Daten gespeichert
  // werden sollen, wie z.B. hinterher { "LinkA" : 4 }
  // Auch wenn er ein Singleton ist, bauen wir dennoch eine Konstruktur-
  // Funktion, die die Intialisierung übernimmt.

function Klicktracker () {

// Wir brauchen später einen Verweis auf das Objekt selber.
      var self = this;

// Hier werden die Daten gespeichert
      this.storage = {}

// Beim Start ruft der Klicktracker die Methode .restore() auf, die
      // den Klicktracker mit Werten befüllt
      this.restore();

// Dann wird ein Ereignis für das load-Event des Dokument registriert.
      // Bei der Auslösung dieses Ereignisses wird für alle Links des
      // Dokumentes eine Handler-Funktion registriert, die wiederum die
      // eigene Methode .klick() aufruft.

// Mehr zur Funktion addEvent() später
      addEvent(window, "load", function () {

// Erst wenn das Dokument geladen ist, kann man alle Links auf
          // einmal sammeln
          var links = document.getElementByTagName("a");

// Durchlaufen ...
          for (var i = 0, end = links.length; i < end; i++) {

var link = links[i];

// wieder: mehr zu AddEvent später
              addEvent(link, "click", function (e) {

// Ignorier diese beiden Zeilen erstmal
                  e = e || window.event;
                  var target = e.target || e.srcElement;

// Wenn geklickt wird, wird die eigene Methode .klick()
                  // mit dem geklickten Element als Argument aufgerufen.
                  self.klick(target);
              });
          }
      });
  }

// Bedient wird der Klicktracker über Methoden, die an den Prototypen
  // des Objekts kommen.

// Die Methode .klick() wird aufgerufen, wenn ein Link geklickt wird.
  // Argument ist das geklickte Element
  Klicktracker.prototype.klick = function (target) {

// target zeigt auf das Element, dass geklickt wurde, in diesem Fall
      // also das a-Element. Dadurch kriegen wir die Link-Adresse raus:
      var link = target.href;

// Wenn der Link schon im Klicktracker-Objekt vorhanden ist, addiere
      // eins dazu, ansonsten setze den Eintrag im Objekt neu an und zwar
      // auf eins.
      if (link in this) {
          this.storage[link]++;
      } else {
          this.storage[link] = 1;
      }

// Da wir die Seite gleich verlassen werden, speicher den Zustand des
      // Klicktracker-Objektes
      this.safe();
  }

// Die Methode .safe() wandelt das Storage-Objekt in einen geeigneten
  // String um und speichert diesen.
  Klicktracker.prototype.safe = function () {

// Erstmal überprüfen, on es was zu speichern gibt. Wenn nicht, dann
      // halt nicht. isEmptyObject() ist wieder so eine Helferfunktion.
      if (isEmptyObject(this.storage)) {
          return;
      }

// Jetzt wird wieder massiv die Daten umgewandelt. Erstmal ein map vom
      // Storage-Objekt auf ein Array
      var arrayOfEncodedStrings = [];
      for (var link in this.storage) {

// Sicherheitsabfrage
          if (this.storage.hasOwnProperty(link)) {

// der Link wird maskiert.
              link = encodeURIComponent(link);

// Die Klickrate wird in einen String umgewandelt.
              var count = encodeURIComponent(this.storage[link].toString());

// ... und das ganze wird gespeichert mit "=" als Trennzeichen.
              arrayOfEncodedStrings.push(link + "=" count);
          }
      }

// Das Array von Strings wird jetzt in einen String mit dem Trennzeichen
      // "+" umgewandelt. Arrays haben dafür die Methode .join
      var cookieString = arrayOfEncodedString.join("+");

// Uuund speichern
      document.cookie = cookieString;
  }

// Die Methode .restore() wird beim Laden aufgerufen und befüllt das interne
  // Storage-Objekt mit den Daten aus dem Cookie-String
  Klicktracker.prototype.restore = function () {
      var cookieString = documentCookie;
      var arrayOfStrings = cookieString.split("+");

for (var i = 0, end = arrayOfStrings.length; i < end; i++) {
          var currentString = arrayOfStrings[i];
          var currentArray = currentString.split("=");

// wieder brav decoden
          var link  = decodeURIComponent(currentArray[0]);
          var count = decodeURIComponent(currentArray[1]);

// und umwandeln
          count = Number(count);

// Und speichern
          this.storage[link] = count;
      }
  }

// Und nun den Klicktracker starten und um den globalen Namensraum nicht
  // zu verschmutzen als Eigenschaft direkt an der Konstrukturfunktion
  // speichern.
  Klicktracker.tracker = new Klicktracker();

  
Ok, das ist sehr viel, aber beachte, dass das zu pädagogischen Zwecken sehr durchkommentiert ist und ausführlicher ist, als ich das in der Praxis schreiben würde.  
  
  
JSON ist ein String-Format, mit dem man verschachtelte Javascript-Datenstrukturen in einen String und wieder heraus bekommen kann. Es war lange ein Quasi-Standard; inzwischen gibt es in modernen Browsern schon Methoden dafür. Wenn die nicht vorhanden sind, bindet man die [quasi-offizielle Bibliothek](https://github.com/douglascrockford/JSON-js/blob/master/json2.js) mit ein. Bei Vorhandensein des JSON-Objektes kann man dann die safe- und restore-Methoden so schreiben:  
  
  ~~~javascript
Klicktracker.prototype.safe = function () {  
      if (isEmptyObject(this.storage)) {  
          document.cookie = "";  
      } else {  
          document.cookie = JSON.stringify(this.storage);  
      }  
  }  
  
  Klicktracker.prototype.restore = function () {  
      if (!document.cookie) {  
          this.storage = {};  
      } else {  
          this.storage = JSON.parse(document.cookie);  
      }  
  }

Was netter ist. Vorausgesetzt man hat die JSON-Funktionen zur Verfügung.

Apropos Voraussetzungen. Ich benutze da oben Helfer-Funktionen. isEmptyObject ist so definiert:

~~~javascript function isEmptyObject (obj) {
      for (var temp in obj) {
          return false;
      }
      return true;
  }

  
[addEvent()](http://molily.de/js/event-handling-fortgeschritten.html#addevent-helfer) ist eine sehr verbreitete Helferfunktion, die das unterschiedliche Event-Handling älterer Browserversionen vereinheitlicht und es dennoch schafft, dass man verschiedene Event-Handler an ein Element packen kann. Best practice, sozusagen. Molilys [Javascript-Einführung](http://molily.de/js/) ist zum Thema Event-Handling sowieso lesenswert und dringst Du bis Abschnitt 16.4 vor, erkennst Du auch, warum in der Konstruktor-Funktion der Umweg mit dem self nötig war bzw. dass ich bindAsEventListener hätte nehmen sollen, wäre es denn vorhanden und müsse man sich das nicht selbst doch noch definieren.  
  
Du merkst hier ein unterschwelliges Thema: Javascript wird kürzer und verständlicher, wenn man andere Funktionen hat, die z.B. mittels einer Bibliothek eingebunden werden. jQuery z.B. hat unter anderem Unterstützung für Event-Handling und anderes praktisches. JSON braucht aus absurden Gründen (ernsthaft: jQuery hat $.parseJSON aber kein Stringify?) wieder obige Library. Hier mal ein kompletter auf jQuery aufbauender Klicktracker:  
  
  ~~~javascript
jQuery(function ($) {  
      var storage;  
  
     // Restore  
     if (!document.cookie) {  
         storage = {};  
     } else {  
         storage = JSON.parse(document.cookie);  
     }  
  
     $("a").click(function () {  
  
         // Adding a click  
         var link = this.href;  
         if (link in storage) {  
             storage[link]++;  
         } else {  
             storage[link] = 1;  
         }  
  
         // Saving the Cookie  
         document.cookie = JSON.stringify(storage)  
  })

-Wie ich an deinem Beispiel sehe, ist das Extrahieren & Sortieren ja
momentan einigermassen umfangreich, und man könnte
das Cookie-Abspeichern vielleicht etwas besser machen.

Ach. Geht. Für die Zukunft sind mit der Weiterentwicklung von HTML (5) bessere Speichermöglichkeiten (Dom Storage, IndexedDB ...) geplant, aber die alten Browser mal wieder. Es gibt auch noch andere Hacks, wie z.B. das Speichern in window.name, aber das ist ja nur auf die Session des Browsers begrenzt. Wenn das jedoch Dein Fall ist, dann guck Dir mal molilys Session Storage Wrapper (Dokumentation) an, da wird aus unterschiedlichen Möglichkeiten die beste ausgewählt und vieles automatisiert.