molily: Script nachladen falls benötgt

Hallo,

ich habe ein Script, das wahlweise JSON-Serialisierung benötigt.
Wenn der Browser kein natives JSON unterstützt, dann soll das Script nachgeladen werden, ansonsten am besten nicht - weil es in dem Fall überflüssig ist und gar nicht erst vom Server übertragen und ausgeführt werden muss.

Jetzt finde ich partout keine sinnvolle Möglichkeit, das Script automatisch einzubinden, wenn nötig. Schließlich muss es sofort zur Verfügung stehen - ich kenne aber keine synchrone Möglichkeit, um ein Script nachzuladen (außer synchrones, blockendes XMLHttpRequest - bäh, damit friere ich ja den Browser ein!).

Ausgangslage:

<script src=json.js>
<script src=meinscript.js>
<script>
meinScript.tuWas(); // tuWas braucht JSON
</script>

D.h. JSON einfach immer einbinden, aber das will ich nun vermeiden.

Folgendes ginge und garantiert, dass JSON immer verfügbar ist:

<script>
if (!window.JSON) {
   document.write("<script src=json.js></script>);
}
</script>
<script src=meinscript.js>
<script>
meinScript.tuWas(); // tuWas braucht JSON
</script>

Nun will ich aber niemanden raten, in jedes Dokument diesen document.write-Aufruf reinzuschreiben. Nicht weil ich document.write böse finde (den XHTML-Fall halte ich für nicht relevant), sondern weil hier Inline-Script verwendet werden muss.

Bisher habe ich json.js dynamisch beim Initialisieren von meinscript.js geladen. Da wird ein script-Element erzeugt und in den head eingehängt. Das funktioniert wunderbar in 95% der Fälle - aber eben nicht zuverlässig. Manchmal ist das Script noch nicht geladen, window.JSON existiert noch nicht, aber meinScript.tuWas() wird schon ausgeführt.

Okay, also könnte ich das document.write einfach direkt in mein Script schreiben. Funktioniert auch prächtig, allerdings ist JSON nicht in jedem Fall der Scriptverwendung nötig. meinScript.tuWas() braucht nicht notwendig JSON, es soll aber die Möglichkeit geben, das Script so umzuschalten, dass es dann doch JSON braucht. Argh!

Wenn ich in diesem Fall nicht JSON von Anfang an laden will, komme ich doch in die Falle, das Script fallweise nachladen zu müssen. Das ginge ja auch mit document.write:

meinScript = {
   tuWas : function () {
      if (jsonNötig) {
         document.write("<script..>")
      }
   }
};
var rückgabe = meinScript.tuWas(); // tuWas braucht möglicherweise JSON

Das geht solange, wie ich tuWas während des Ladens der Seite aufrufe - aber üblicherweise arbeitet man ereignisorientiert, also funktioniert document.write nicht mehr und ist sogar schädlich. Ich wüsste auch nicht, wie ich abfragen kann, ob das Dokument schon fertig geladen ist (ohne den load-Handler zu überwachen) und ob ich document.write gebrauchen kann. Selbst wenn, gäbe es keine gute Alternative. createElement("script") funktioniert auch nicht, weil es asynchron läuft. Ich müsste mit einem load-Callbacks arbeiten. Das will ich aber nicht, das wäre sehr kompliziert für den Anwender des Scripts, sondern tuWas soll direkt den Wert zurückgeben, anstatt einen Callback aufzurufen.

Wie würdet ihr das machen?
1. JSON einfach immer mit <script> laden
2. JSON wahlweise mit document.write bei der Initialisierung laden, auch wenn's später nicht verwendet wird
3. JSON wahlweise asynchron mit createElement bei der Initialisierung laden, auch wenn's später nicht verwendet wird
4. JSON laden, wenn es nötig ist (nur wie?)
... noch was anderes / besseres, was mir nicht einfällt

JSON ist jetzt nicht so ein riesiges Script und eigentlich ist es auch in fast allen Anwendungsfällen nötig, insofern ist Möglichkeit 2 erst einmal okay, aber zufrieden bin ich nicht. (Wer XHTML verwendet, muss halt auf die intelligente Einbindung verzichten.)

Mathias

  1. Synchrone Ausführung von JavaScript geht nur mit eval()... und das ist evil()...

    Warum muss es unbedingt synchron sein? 100ms Unterschied merkt man ohnehin nur mit der Stoppuhr - und die JSON-Daten kommen doch wahrscheinlich auch zeitverzögert vom Server.

    Gruß, LX

    --
    RFC 1925, Satz 6: Es ist einfacher, ein Problem zu verschieben (...), als es zu lösen.
    1. Warum muss es unbedingt synchron sein?

      Weil es sonst einen JavaScript-Fehler gibt beim Aufruf von JSON.stringify/.parse.

      100ms Unterschied merkt man ohnehin nur mit der Stoppuhr

      Es geht nicht um Performance, sondern der Unterschied macht aus, ob das Script überhaupt oder gar nicht funktioniert.

      und die JSON-Daten kommen doch wahrscheinlich auch zeitverzögert vom Server.

      Ich übertrage die JSON-Daten nicht zum Server. JSON-Serialisierung und -Parsing wird clientseitig verwendet.
      Daten sind clientseitig mit JSON kodiert gespeichert, und wenn mein Script aufgerufen wird, muss es diesen String parsen, die Daten ändern und wieder mit JSON serialisieren.

      Mathias

      1. »» Warum muss es unbedingt synchron sein?

        Weil es sonst einen JavaScript-Fehler gibt beim Aufruf von JSON.stringify/.parse.

        Sofern Du die Funktion, die den Aufruf von JSON.stringify/.parse enthält, erst dann asynchron ausführst, wenn dieses Objekt vorhanden ist, nicht. Ein entsprechendes Interval, welches sich selbst eliminiert und die entsprechende Funktion aufruft, ist schnell geschrieben.

        Gruß, LX

        --
        RFC 1925, Satz 3: Mit ausreichendem Schub fliegen Schweine ganz wunderbar. (...)
        1. Hohoh.

          Sofern Du die Funktion, die den Aufruf von JSON.stringify/.parse enthält, erst dann asynchron ausführst, wenn dieses Objekt vorhanden ist, nicht. Ein entsprechendes Interval, welches sich selbst eliminiert und die entsprechende Funktion aufruft, ist schnell geschrieben.

          Das passt wie zu molilys Anforderung?

          Ich müsste mit einem load-Callbacks arbeiten. Das will ich aber nicht, das wäre sehr kompliziert für den Anwender des Scripts, sondern tuWas soll direkt den Wert zurückgeben, anstatt einen Callback aufzurufen.

          Grüße

          1. Das passt wie zu molilys Anforderung?

            Gar nicht. Ich stelle seine Anforderung ja gerade in Frage. Welche Funktion, die das JSON-Objekt benutzt, muss unbedingt und unter allen Umständen synchron aufgerufen werden?

            Gruß, LX

            --
            RFC 1925, Satz 5: Es ist immer möglich, einzelne voneinander getrennte Probleme zu einem einzigen komplexen, innerlich abhängigen Lösungsansatz zu verschmelzen.
  2. Hoi.

    ....
    Wie würdet ihr das machen?

    Ich würde _ein_ "meinscript.js" verwenden, welches die json lib bereits beinhaltet.

    Um hierbei nicht im Chaos zu versumpfen(json.js wird vermutlich in zig Projekten verwendet), braucht es noch etwas:

    http://code.google.com/p/minify/

    So wirst Du im Endeffekt vermutlich ein kleineres "Gesamt.js" erhalten, als "meinscript.js" bislang alleine verbraucht. Außerdem: einen Request gespart.

    Grüße

    1. Die Minifizierung hat nicht nur Vorteile: das Debugging wird beispielsweise sehr erschwert und andere minifizierte Scripte können in Konflikt mit dem Script geraten, solange kein klarer Namespace definiert ist.

      Außerdem kostet ein Script ja nicht nur Ladezeit, sondern auch Zeit zum Parsen, Interpretieren etc.

      Gruß, LX

      --
      RFC 1925, Satz 6: Es ist einfacher, ein Problem zu verschieben (...), als es zu lösen.
      1. Hoi.

        Die Minifizierung hat nicht nur Vorteile: das Debugging wird beispielsweise sehr erschwert

        Ist mir bislang nur seeehr selten passiert. Selbst wenn, ist die Minifizierung selbst doch kurzfristig deaktivierbar?

        und andere minifizierte Scripte können in Konflikt mit dem Script geraten, solange kein klarer Namespace definiert ist.

        Inwiefern ist das eine Frage der Minifizierung?

        Außerdem kostet ein Script ja nicht nur Ladezeit, sondern auch Zeit zum Parsen, Interpretieren etc.

        Nur während der Entwicklung, klaro. Live geht die hieraus erzeugte, statische Ressource.

        Grüße

        1. Hallo,

          »» (...) das Debugging wird beispielsweise sehr erschwert
          Ist mir bislang nur seeehr selten passiert. Selbst wenn, ist die Minifizierung selbst doch kurzfristig deaktivierbar?

          Das ist sie schon, allerdings ist ein minifiziertes Script immer ein anderes als das ursprüngliche Script. Wer es mit der Qualitätssicherung genau nimmt, könnte auf den Gedanken kommen, dass es auch Fehler bei der Minifizierung geben könnte.

          »» und andere minifizierte Scripte können in Konflikt mit dem Script geraten, solange kein klarer Namespace definiert ist.
          Inwiefern ist das eine Frage der Minifizierung?

          Weil die Minifizierung üblicherweise Variablennamen wie _1, _2, _a... einsetzt, die sehr wahrscheinlich auch in anderen minifizierten Scripten vorkommen.

          »» Außerdem kostet ein Script ja nicht nur Ladezeit, sondern auch Zeit zum Parsen, Interpretieren etc.
          Nur während der Entwicklung, klaro. Live geht die hieraus erzeugte, statische Ressource.

          Dann hast Du wahrscheinlich JavaScript deaktiviert. Ansonsten wird Dein Browser das Script nicht nur laden, sondern wahrscheinlich auch entsprechend bearbeiten.

          Gruß, LX

          --
          RFC 1925, Satz 6a: Es ist immer möglich, einen weiteren Umweg einzufügen.
          1. Hi.

            Wer es mit der Qualitätssicherung genau nimmt, könnte auf den Gedanken kommen, dass es auch Fehler bei der Minifizierung geben könnte.

            Punkt für Dich, die Möglichkeit besteht. Aber nicht in dem von Dir vermuteten Umfang. Siehe nächster Punkt.

            Inwiefern ist das eine Frage der Minifizierung?

            Weil die Minifizierung üblicherweise Variablennamen wie _1, _2, _a... einsetzt, die sehr wahrscheinlich auch in anderen minifizierten Scripten vorkommen.

            Nope. Das genannte Teil tut das nicht(zumindest bei mir). Eine definierte Liste von Dateien zu einer zusammenfassen, Kommentare, Linefeeds & Co wech, aber keine Manipulation von Variablennamen.

            Außerdem kostet ein Script ja nicht nur Ladezeit, sondern auch Zeit zum Parsen, Interpretieren etc.

            Nur während der Entwicklung, klaro. Live geht die hieraus erzeugte, statische Ressource.

            Dann hast Du wahrscheinlich JavaScript deaktiviert. Ansonsten wird Dein Browser das Script nicht nur laden, sondern wahrscheinlich auch entsprechend bearbeiten.

            Jo iss klar... Deine Ausführungen ergaben für mich nur bzgl. des minifizierenden Servers Sinn. Bzgl. des Clients verstehe ich den Einwand nicht.

            Grüße

            1. »» [Die Minifizierung kann Scripte maßgeblich verändern] Weil die Minifizierung üblicherweise Variablennamen wie _1, _2, _a... einsetzt, die sehr wahrscheinlich auch in anderen minifizierten Scripten vorkommen.

              Nope. Das genannte Teil tut das nicht(zumindest bei mir). Eine definierte Liste von Dateien zu einer zusammenfassen, Kommentare, Linefeeds & Co wech, aber keine Manipulation von Variablennamen.

              Das kann man kaum als Minifizierung bezeichnen. Einen entsprechenden Kommentar-Cleaner habe ich auch schon gebaut, allerdings entfernt der nicht mal 20% einer typischen JS-Datei.

              »» Außerdem kostet ein Script ja nicht nur Ladezeit, sondern auch Zeit zum Parsen, Interpretieren etc.
              »»

              (...)

              Jo iss klar... Deine Ausführungen ergaben für mich nur bzgl. des minifizierenden Servers Sinn. Bzgl. des Clients verstehe ich den Einwand nicht.

              Du hast vorgeschlagen, das dynamisch zugeladene Script in jedem Fall einzubinden. Wenn ich die Reaktion des Clients auf die zusätzlichen Scriptzeilen bewerte, hat dieser auch mehr oder weniger geringfügige Aufwände, diese zu bearbeiten. Muss ich Dir erst ein Bild zeichnen?

              --
              RFC 1925, Satz 1: Es muss funktionieren.
              1. Du hast vorgeschlagen, das dynamisch zugeladene Script in jedem Fall einzubinden. Wenn ich die Reaktion des Clients auf die zusätzlichen Scriptzeilen bewerte, hat dieser auch mehr oder weniger geringfügige Aufwände, diese zu bearbeiten. Muss ich Dir erst ein Bild zeichnen?

                Jetzt habe ich Dich verstanden, verzeih meine beschränkte Auffassungsgabe. Molily hatte übrigens explizit den geringen Codeumfang seiner json Bibliothek erwähnt.

                Grüße

          2. Weil die Minifizierung üblicherweise Variablennamen wie _1, _2, _a... einsetzt, die sehr wahrscheinlich auch in anderen minifizierten Scripten vorkommen.

            Öffentliche Interfaces werden doch nicht geändert, sondern nur lokale Variablennamen, was die Funktionsfähigkeit nicht beeinträchtigt und per se auch keine Konflikte mit anderen Scripten bringen kann. Die Kürzung von lokalen Variablen ist m.W. überhaupt kein Problem.

            Mathias

    2. Ich würde _ein_ "meinscript.js" verwenden, welches die json lib bereits beinhaltet.

      Naja, das stimmt im Prinzip. Der Clou des Scriptes ist aber, dass es standardmäß JSON anstatt eigene Serialisierungsmethoden verwendet. Das zielt natürlich auf eine Zukunft ab, in der alle Browser nach und nach natives JSON anbieten und das Zusatzscript überflüssig wird. Wenn ich sie beide von Anfang an bündle, dann habe ich diese Perspektive gleich aufgegeben.

      Mathias

      1. Wenn ich sie beide von Anfang an bündle, dann habe ich diese Perspektive gleich aufgegeben.

        Warum? Sofern Du nicht aus dem Stand eine völlig bugfreie und mit allen denkbaren Funktionen versehene Geschichte gebaut hast, wirst Du Dein JS naturgemäß noch oft "anpacken" müssen.

        Ob Du dann zum Zeitpunkt X sagst "Raus mit meiner json lib und der internen Fallback Unterscheidung", bleibt Dir doch frei?

        Grüße

        1. wirst Du Dein JS naturgemäß noch oft "anpacken" müssen.

          Natürlich werde ich noch weitere Versionen veröffentlichen, aber das ändert an dem Grundproblem nichts. Dann habe ich es nicht jetzt, sondern in einem Jahr.

          Ob Du dann zum Zeitpunkt X sagst "Raus mit meiner json lib und der internen Fallback Unterscheidung", bleibt Dir doch frei?

          Der Zeitpunkt wird sicher nicht in naher Zukunft eintreten, denn Browser, die kein natives JSON können, werden noch lange verbreitet sein, daher wird mich das fallweise Nachladen der json2.js auch noch lange beschäftigen.

          Mathias

  3. Hallo molily,

    was spricht denn gegen einen synchronen httpRequest und anschließendem eval? Ob du beim Aufrufen der Seite auf die Scripte wartest oder erst, wenn sie benötigt werden, ist doch eigentlich egal. Und das Nachladen dauert ja auch nicht viele Sekunden.

    Gruß, Jürgen

    1. was spricht denn gegen einen synchronen httpRequest und anschließendem eval?

      Das Script soll halt veröffentlicht werden, die Anwender sollen es in verschiedenen Umgebungen einsetzen können und es soll robust laufen. Wenn ich da irgendwo den Browser einfriere, kann das unschöne Effekte haben. Ich würde mir so ein Script nur ungerne einbauen.

      Ob du beim Aufrufen der Seite auf die Scripte wartest

      Das mache ja nicht ich, sondern der Browser beim Laden der script-Elemente - ich denke mir, dass das zuverlässiger funktioniert, als wenn ich im Script mit XMLHttpRequest herumhampel und einiges von Hand mache. XMLHttpRequest braucht ggf. ActiveX, unterliegt der Cross-Domain-Policy, funktioniert offline nicht so wirklich... Und nicht zuletzt müsste ich auch noch eine browserübergreifende Ajax-Funktion in mein Script schreiben, bei dem es eigentlich um etwas ganz anderes geht.

      Und das Nachladen dauert ja auch nicht viele Sekunden.

      Wer weiß. Wenn ein Server mal gerade überlastet ist, hängt der ganze Browser.

      Mathias

      1. Hallo Mathias

        wenn das synchrone und das asynchrone Nachladen per http-Request keine Optionen sind, kannst du das Script nur Nachladen, indem du das Script-Tag mit src-Attribut per DOM-Methoden erzeugst. Allerdings wird das Script dann asynchron abgearbeitet. Du musst also irgendwie warten, bis die Methoden und Variablen zur Verfügung stehen. Dieses könnte per setInterval geschehen (s. Antwort von nam). Du könntest aber auch am Ende des Scripts eine Funktion aufrufen, in der es weiter geht. Dieser Aufruf muss aber in der JS-Datei vorhanden sein.

        Gruß, Jürgen

        1. Du musst also irgendwie warten, bis die Methoden und Variablen zur Verfügung stehen. Dieses könnte per setInterval geschehen (s. Antwort von nam). Du könntest aber auch am Ende des Scripts eine Funktion aufrufen, in der es weiter geht.

          Klar, ich könnte mit einem load-Handler arbeiten, was sehr robust funktioniert. createElement(script).onload = handler.

          Das würde jedoch die API von meinem Script auf den Kopf stellen und verkomplizieren. Wie gesagt gehts um das Speichern von Daten, und wenn man sie auslesen will, sollte man schreiben können:

          var daten = meinScript.get("key");
          // mach direkt was mit den Daten

          Das ist ziemlich geradlinig und intuitiv. Wenn ich mit load-Handlern arbeite, muss ich die API wie folgt ändern:

          meinScript.get("key", function (daten) {
             // mach asynchron was mit den Daten
          });

          Gut, das ist jetzt nichts ungewöhnliches und für Profis ohnehin vertraut. Große modulare Scripte, die Komponenten automatisch nachladen, arbeiten notwendigerweise entweder so (YUI Loader) oder synchron (dojo.require). Allerdings werden bereits einfache Aufgaben durch diese Schreibweise sehr komplex, man muss sich über Closures und Binding Gedanken machen, um mit Callbacks vernünftig arbeiten zu können. Für große Frameworks mag das okay sein, aber so hoch will ich bewusst nicht hinaus, es soll möglichst einfach zu nutzen sein.

          Mathias

          1. Hallo molily,

            Das würde jedoch die API von meinem Script auf den Kopf stellen und verkomplizieren. ...

            und aus genau diesem Grund habe ich mich entschieden, Scripte bei Bedarf synchron nachzuladen. Ich habe das bisher mit Scripten im 10kB-Bereich getestet und nicht feststellen können, dass der Browser da merklich hängt. Wie groß sind denn deine Scripte, dass du dir Sorgen um das Einfrieren des Browsers machst?

            Hier mal ein Beispiel:

            Script sofort geladen: http://www.j-berkemeier.de/Ritzelrechner.html

            Script bei Bedarf nachgeladen http://www.j-berkemeier.de/Ritzelrechner-dyn_DnD.html (Testversion)

            Zur Bedienung:

            Klick auf "Tabellen und Grafik berechnen"
            Klick auf "Grafik zum Vergleichen kopieren"

            die dann erscheinende Grafik kann per Drag and Drop verschoben werden. Das Script dazu (ca. 2 kB) wird dynamisch nachgeladen.

            Gruß, Jürgen

  4. Hallo molily

    Wie auch LX schon empfohlen hat, würde ich das auch mit einem Callback machen. Du kannst das ja so kapseln, dass es der Benutzer des Scripts gar nicht merkt. Ungefähr so:

    meinScript.loadScript = function(cb) {
      appendScriptToDOM(); //Bindet Script ein
      var interval = window.setInterval(function () {
        if (JSON) { //Vorhandensein des Objekts testen
          window.clearInterval(interval);
          callback();
        }
      }, 100); //interval Zeit eventuell anpassen
    }

    meinScript.tuWas() {
      function process() {
        //tutdas
        //unddas
      }
      if (!JSON) {
        meinScript.loadScript(process);
      } else {
        process();
      }
    }

    Ein Hack, aber es sollte gehen.
    Gruss,
    nam

    1. Du kannst das ja so kapseln, dass es der Benutzer des Scripts gar nicht merkt.

      meinScript.tuWas() {
        function process() {
          //tutdas
          //unddas
        }
        if (!JSON) {
          meinScript.loadScript(process);
        } else {
          process();
        }
      }

      Das ist schon richtig, das Problem ist halt (entschuldige, falls es nicht gleich klar war), dass tuWas() eigentlich ein gibMir() ist, soll also einen Wert zurückgeben und das ist sein Hauptzweck. Wenn ich da intern Callbacks einführe, müsste ich das ganze Script nach außen hin ändern, siehe https://forum.selfhtml.org/?t=184698&m=1224911.

      Mathias

  5. Hallo Mathias,

    ich vermute ja, es geht um ssw, oder?

    1. JSON wahlweise mit document.write bei der Initialisierung laden, auch wenn's später nicht verwendet wird
    2. JSON wahlweise asynchron mit createElement bei der Initialisierung laden, auch wenn's später nicht verwendet wird

    Ich würde (3) wählen, um auf document.write verzichten zu können, es aber so synchron gestalten, dass ... sagen wir ... implementation.setup() oder eine ähnliche beendende Funktion als load-Handler ausgeführt wird. Ich hätte kein schlechtes Gewissen zu kommunizieren, dass das Skript erst letztendlich erst wirklich in document.onload zur Verfügung steht.

    Mich irritiert Dein „auch wenn's später nicht verwendet wird“. Derzeit hast Du doch nur JSON als Serialisierer, alle Deine Möglichkeiten zum Speichern verwenden JSON und ein anderes Text-Format für Datenstrukturen steht nicht auf dem Programm. Hältst Du es wirklich für nötig so extrem flexibel zu sein, nur weil was irgendwann in der Zukunft sein könnte? Mal abgesehen davon, dass Deine Implementierungen mittels des aufsetzenden implementation-Objektes durchaus ihre Bedürfnisse festlegen und damit .detect() bzw. .setup() mitteilen könnten, welche dann darauf eingehen könnten.

    OT: Kleines Nitpicking, nimm's mir bitte nicht übel, dass ich das hier anbringe. Ich hätte das Initialisieren des Skriptes durchaus etwas imperativer gestaltet mit einem öffentlichen Wrapper um eine imperativ auszuwählende Storage-Implementierung anstatt eines dafür nötigen größeren Init-Objektes. OOP ist doch letztendlich nur dafür da, gemeinsame Operationen nach außen hin zu kapseln und zu organisieren. Hier hast Du das teilweise schon mit dem anonymen Function-Scope samt eventueller Closure erreichst; weiteres wirkt mehr wie ein OOP-Selbstzweck. Zumindest beim Nachvollziehen des Codes ist das hier eher hinderlich, dafür gibt es zuviele Stellen, an denen der Programmfluss innerhalb der Initialisierung springt. Oder übersehe ich was wirklich entscheidendes, was Du schon erkannt hast?

    Tim

    1. (...) Ich hätte kein schlechtes Gewissen zu kommunizieren, dass das Skript erst letztendlich erst wirklich in document.onload zur Verfügung steht.

      Ja, das wäre möglich. Ein schlechtes Gewissen hätte ich dennoch. Ich weiß nicht, ob ein erzeugtes script-Element den load-Event verzögert. Ich fürchte nicht, also wäre es noch nicht einmal sicher, zu sagen, dass spätestens beim window.load-Event die Verwendung des Scriptes möglich ist.
      Bei heutigen Anwendungen will eigentlich niemand auf window.onload warten, so ziemlich jedes moderne Script verwendet sinnigerweise DOM-ready-Events. Wenn ich auf window.onload abzielen würde, wären die Anwendungsmöglichkeiten sehr begrenzt. Gerade bei dem Script soll es möglich sein, z.B. Benutzereinstellungen wie Schriftgröße und Alternativstylesheet zu speichern. Die will ich so schnell wie möglich auslesen und setzen, nicht erst window.onload abwarten.

      Mich irritiert Dein „auch wenn's später nicht verwendet wird“.

      Der Zusatz war Unsinn, sehe ich gerade.
      Das Script muss natürlich nur nachgeladen werden, wenn der Serializer JSON ist und dieser nicht nativ unterstützt wird.

      Derzeit hast Du doch nur JSON als Serialisierer, alle Deine Möglichkeiten zum Speichern verwenden JSON

      Ja, nachdem ich auf den Trichter gekommen bin, dass sessionStorage auch nur Strings speichert. ;)

      und ein anderes Text-Format für Datenstrukturen steht nicht auf dem Programm.

      JSON ist nicht der Weisheit letzter Schluss, z.B. kann es keine Funktionsobjekte serialisieren, was im Prinzip in allen Browsern funktioniert. Andere Scripte mit demselben Zweck benutzen nicht JSON, sondern reizen ECMAScript Object-Literale aus und können dadurch viel mehr übertragen. Daür haben sie z.T. schlechte oder lückenhafte Serialisierungsroutinen und können nicht Gebrauch von nativen Browserfunktionen machen.

      Ich hätte das Initialisieren des Skriptes durchaus etwas imperativer gestaltet mit einem öffentlichen Wrapper um eine imperativ auszuwählende Storage-Implementierung anstatt eines dafür nötigen größeren Init-Objektes.

      Du meinst, man sollte ssw.init() aufrufen müssen, anstatt dass es sich automatisch initialisiert und die Implementierung mit forceImplementation gesetzt/geändert werden kann?

      Mathias

      1. Hallo Mathias,

        Gerade bei dem Script soll es möglich sein, z.B. Benutzereinstellungen wie Schriftgröße und Alternativstylesheet zu speichern. Die will ich so schnell wie möglich auslesen und setzen, nicht erst window.onload abwarten.

        Dann würde ich wie Dirk meine Skrupel über Bord werfen, JSON.js direkt eingebettet mitliefern, einfach unter dem Aspekt, dass die zusätzlich übermittelten paar Bytes zeitlich sehr viel weniger wiegen als das danach abfolgende neue Anfordern einer neuen Ressource.

        Du meinst, man sollte ssw.init() aufrufen müssen, anstatt dass es sich automatisch initialisiert

        Nein gar nicht, mir ging's viel mehr im die Lese-Struktur des Programmtextes. Ich hätte instinktiv mit so einer (sehr vereinfachten) Grundstruktur angefangen:

        ~~~javascript (function () {

        /* JSON und sonstige Helferlein */

        function publicInterface (implementation) {
              this.implementation = implementation;
          }
          publicInterface.prototype = {
              get : function () {
                  return unserialized(this.implementation.get());
              },
              set : function (value) {
                  return this.implementation.set(serialized(value));
              },
              /* ... */
          };

        var backends = [
              { name : "storage",
                isPossible : function () { /* ... / },
                get  : function () { /
        ... / },
                set  : function (serializedString) { /
        ... / },
                /
        ... /
              },
              { name : "window.name",
                isPossible  : function () { /
        ... / },
                /
        ... /
              },
              /
        ... */
          ];

        var choosenBackend = backends.filter(function (backend) { return backend.isPossible(); })[0];

        window["ssw"] = new publicInterface(choosenBackend);
          delete backends;

        })()

          
        ... einfach weil der Ablauf (Definieren -> Entscheiden -> Verknüpfen, -> Installieren) sehr viel linearer ist und der Unterschied zwischen öffentlichem Objekt und Backend genauer rauskommt. Vielleicht ist das auch nur ein Bauchgefühl; ich argumentiere hier ja auch mehr aus der Warte des den Code Lesendens.  
          
          
        
        > und die Implementierung mit forceImplementation gesetzt/geändert werden kann?  
          
        force() hatte ich tatsächlich übersehen. ;)  
          
        Ja, dann ist es schon sinnvoller eine besser objekt-orientierte Verwaltung der Backends zu haben anstatt in der Initialisierung alles in Stein zu meißeln. Ich würde dann in meinem obigen Krempel wieder im Konstruktur des öffentlichen Objektes das Housekeeping und die Entscheidungslogik ansiedeln – und wäre dann wieder bei etwas, was Deiner Variante ähnelt.  
          
          
        Tim
        
        1. function publicInterface (implementation) {
                this.implementation = implementation;
            }
            publicInterface.prototype = {
                get : function () {
                    return unserialized(this.implementation.get());
                },
                set : function (value) {
                    return this.implementation.set(serialized(value));
                },
                /* ... */
            };

          Das ähnelt im Grunde dem Public-Teil des »Implementation Template« mixins.serialized, von dem derzeit alle Implementerungen Gebrauch machen. Nur dass ich die Funktionsweise der Implementierungen an der Stelle festschreiben würde.

          Bisher gibt es Implementations, die mixen Implementation Templates und darüber Serializer ein. Ich sammle also alle nötige Logik in einem Objekt und letztlich werden einige Methoden dieses über Referenzen zusammengeklaubten Objektes an das Public Interface kopiert (»veröffentlicht«), das ganze Objekt in der Eigenschaft implementation sowie die Methode forceImplementation verfügbar gemacht:

          /* Set up the public interface object */  
          var publicInterface = {  
          	/* Provide active (auto-detected) implementation */  
          	implementation : imp,  
          	/* Provide method to force another implementation */  
          	forceImplementation : helper.bind(this.force, this)  
          };  
            
          /* Provide core methods from the implementation */  
          helper.forEach(["get", "add", "remove", "clear"], function (methodName) {  
          	/* Copy the bound function to the public interface */  
          	publicInterface[methodName] = helper.bind(imp[methodName], imp);  
          });
          

          Dort in implementation.setup() könnte ich natürlich noch eine Abstraktionsebene einführen, indem ich das publicInterface von einem Konstruktor bauen lasse, der Wrappermethoden für get, add, remove und clear bietet und die intern auf this.implementation umleitet.

          Das erscheint mir aber keine Vereinfachung zu sein, stattdessen bleibe ich dort beim Herumreferenzieren) von Methoden sowie deren Binding, wie ich es auch im Rest des Scripts anstelle von Klassenhierarchien tue.

          Beim gegenwärtigen Aufbau müsste es z.B. recht einfach möglich sein, die vorhandene DOM-Storage-Implementierung zu verwenden, aber einfach einen anderen Serializer hineinzumixen.

          implementation.add(implementation.get("domstorage), {
             name : "domstorage with my own serializer",
             serializer : {
                init : ...,
                serialize : ...,
                unserializer : ...
             }
          });
          )

          ... einfach weil der Ablauf (Definieren -> Entscheiden -> Verknüpfen, -> Installieren) sehr viel linearer ist und der Unterschied zwischen öffentlichem Objekt und Backend genauer rauskommt.

          Ja, das stimmt schon.

          Mathias

  6. Moin.

    Ich sehe keine Möglichkeit, dein Problem in JavaScript zu lösen - hier macht sich das Fehlen von Multi-Threading bemerkbar.

    Entweder, du schreibt deine API so um, dass callbacks verwendet werden, oder du nimmst in Kauf, dass bestimmte Funktionalität zeitweilig nicht zur Verfügung steht.

    Eine Funktion, die JSON benötigt, enthält dann z.B. folgenden Schnipsel:

      
    if(!window.JSON) {  
        if(!window.JSON_requested) {  
            window.JSON_requested = true;  
            loadScript('json.js'); // asynchronous  
        }  
        throw new Error('sry, no JSON yet'); // or: return ERR_VALUE;  
    }  
    
    

    Der Programmierer ist dann dafür verantworlich, auf diese Situation angemessen zu reagieren.

    Christoph