LX: tinyJS-Toolkit beta

Hallo, Forum!

Ich entwickele seit ein paar Monaten hauptsächlich mit jQuery und nur noch in manchen Fällen mit reinem, unverfälschtem JS. Nicht, dass mir jQuery nicht gefallen würde, es hat viele schöne Ansätze, aber ich befürchte, dass ich mental verweichliche, wenn ich mir von einem so umfassenden Toolkit die Arbeit abnehmen lasse und so langsam mein Script-Fu schwäche.

Die logische Konsequenz bestand darin, ein eigenes kleines Toolkit zu schreiben, um mir selbst zu beweisen, dass ich noch dazu in der Lage bin, ohne ein anderes Toolkit die wesentliche Funktionalität, die ich regelmäßig in jQuery nutze, abzubilden, als da wären: eine Selektor-Engine mit ein paar praktischen Methoden, DOM-Events, AJAX/JSONp und ein paar Hilfsfunktionen.

Da der Versuch, schneller zu sein als jQuery, qooxdoo etc., ohnehin zum Scheitern verurteilt gewesen wäre, habe ich mir, um wenigstens ein wenig praktischen Nutzen zu haben, stattdessen das Ziel gesetzt, das Toolkit so klein (aber gleichzeitig mit Plugins erweiterbar) wie möglich zu gestalten - außer an den wenigen Stellen, an denen die Minifizierung zu sehr zu Lasten von Les- oder Debugbarkeit geht. Das erste Ziel war, nach Minifizierung und gzip auf unter 5kb zu kommen - das habe ich tatsächlich erreicht. Natürlich ist an vielen Stellen noch Optimierungspotential - das ich in späteren Versionen auch teilweise zu nutzen gedenke, aber primär ging es mir darum, etwas Funktionierendes zu haben.

Aber lange Rede, kurzer Sinn: eine erste Beta-Version des Toolkits findet sich auf tinyjs.sourceforge.net. Bitte zerreisst es nach bestem Gewissen in der Luft und schreibt mir Verbesserungsvorschläge. Ich überlege noch, die Sourcen nach github auszulagern. Auch wenn ihr zur Versionskontrolle Vorschläge habt, nehme ich diese gerne entgegen.

Gruß, LX

--
RFC 1925, Satz 6a: Es ist immer möglich, einen weiteren Umweg einzufügen.
RFC 1925, Satz 11a: Siehe Regel 6a
  1. Hallo LX,

    prinzipielles:

    Ich verstehe nicht, warum man heutzutage für alles ein framework benötigt. Programmierung sollte sich am tatsächlichen Gebrauch orientieren. Da kann es zwar hilfreich sein, einige bewährte Funktionen/Objekte zu haben, die man immer wiederverwendet, jedoch in alle Dokumente immer eine Ganze Werkstatt einzurichten, um ein oder zwei Schraubenschlüssel davon zu gebrauchen, ist die Eierlegende Wollmilchsau.

    Zu Deinem Script:

    if (!document.readyState) {...}  
    // Event trigger  
    ready=window.setInterval(function() {...}, 45);
    

    Da Du eh nur eine Fallunterscheidung zwischen "loading" und "complete" machst, also gar nicht die volle Bandbreite von MS nutzt, frage ich mich, welchen Nutzen Du hier in der Eigenschaft readyState tatsächlich vermutest, denn man nicht ohne simulierten Eventhandler (böse™) durch nutzen von window.onload hätte - zumal diese Eigenschaft Dein fallback ist?

    t.j=function(u, c, t) {  
        // ...  
        s.src=u+(c?'?c='+c:'');
    

    Es ist mir nicht ganz klar, warum man nicht einen URL als solchen URL belässt. Mittels t.p() lässt sich daraus ganz wunderbar einen query string basteln. Hinzu kommt, dass Du hier mit "c=" ganz selbstverständlich einen Parameter vorgibst. Der Programmieren wird hierbei in zweierlei Hinsicht eingeschränkt. Einmal /muss/ er so GET["c"] im Serverscript berücksichtigen, zum anderen muss er, wenn er neben "c" noch weitere Parameter übergeben will, im Javascript darauf achten, dass er dem Funktionsargument "c" kein gewohnheitsbedingtes Fragezeichen voranstellt.

    };

    t.a=function(o) {  
        // ...  
        if (!async) { return x['response'+o.type||'Text']; }
    

    Hier muss m. M. n. o.async abgefragt werden. async ist nicht definiert.

        // set events  
        t.i(ev, function(_, e) { if (o.hasOwnProperty(e)) { x[e]=o[e]; } });
    

    Mir ist nicht wirklich klar geworden, was genau das bezweckt. Weder das Interface eines XMLHttpRequestObjekts noch Mozillas und Microsofts Implementierungen kennen die erstmal willkürlich erscheinenden Eigneschaften onloadstart, onprogress, onabort, onerror, onload und onloadend. Wenn es nur darum geht, weitere Methoden/eigenschaften zum Requestobjekt hinzuzufügen, verstehe ich nicht, warum man dafür nicht seine Eigenen Namen verwenden kann.

    };  
    })();
    

    Gruß aus Berlin!
    eddi

    1. Hallo, eddi!

      Zu Deiner prinzipiellen Anmerkung:

      Frameworks sind nun mal die Hämmer und Schraubenzieher im Werkzeugkasten des JavaScript-Entwicklers. Dass man natürlich seine Hände immer noch gebrauchen muss, wird kein Handwerker bestreiten, aber man nagelt eben nicht ständig die Nägel mit der bloßen Hand in die Wände - aber es ist durchaus beeindruckend, wenn man dazu in der Lage ist.

      Außerdem habe ich genau aus dem Grund, dass ich keine eierlegende Wollmilchsau möchte, das Toolkit aufs Wesentliche reduziert.

      Zu ReadyState: nur in Browsern, in denen readyState nicht standardmäßig vom DOM gesetzt wird, muss ich manuell nachsteuern. Der Vorteil im Gegensatz zu window.onload besteht darin, dass ich nicht auf das Laden von Bildern und CSS warten muss, sondern sofort loslegen kann.

      Bei t.j wird c deshalb als Callback-Variable übergeben, weil meine bisherigen Implementierungen genau das gemacht haben. Ich werde in einem künftigen Release die Möglichkeit einbauen, unbenannte Funktionen zu übergeben, die dann in einer hinreichend zufälligen Variable hinterlegt und nach ihrer Ausführung wieder entfernt werden.

      Was t.a betrifft, hast Du tatsächlich einen Fehler gefunden, den ich gerade behoben habe, Danke sehr! Die Zeile, die Du nicht verstehst, ermöglicht es, innerhalb des AJAX-Objekts Event-Callbacks für den jeweiligen Request zu definieren (außer onreadystatechange, was natürlich in async gehandhabt wird). Die entsprechenden Events sind zwar nicht offizieller Standard, werden aber in fast allen modernen Browsern unterstützt. An dieser Stelle kann man durchaus eine graceful degradation in Betracht ziehen. Als Zielgruppe für das Toolkit sind durchaus Entwickler gemeint, die wissen, was sie tun.

      Gruß, LX

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

        Zu Deiner prinzipiellen Anmerkung: …

        Naja, ist eben Geschmackssache.

        Der Vorteil im Gegensatz zu window.onload besteht darin, dass ich nicht auf das Laden von Bildern und CSS warten muss, sondern sofort loslegen kann.

        Ja, okay.

        Bei t.j wird c deshalb als Callback-Variable übergeben, weil meine bisherigen Implementierungen genau das gemacht haben. Ich werde in einem künftigen Release die Möglichkeit einbauen, unbenannte Funktionen zu übergeben, die dann in einer hinreichend zufälligen Variable hinterlegt und nach ihrer Ausführung wieder entfernt werden.

        Daraus werde ich nicht wirklich schlau. Was hat ein Parameter in der URL mit einem callback zu tun?

        Was t.a betrifft,… Als Zielgruppe für das Toolkit sind durchaus Entwickler gemeint, die wissen, was sie tun.

        Und da denke ich, dass Du das Definieren eigener Methoden unnötig in den separaten code auslagerst, wo sich t.a doch so schön anbietet.

        Gruß aus Berlin!
        eddi

        1. Hallo, eddi

          Bei t.j wird c deshalb als Callback-Variable ...
          Daraus werde ich nicht wirklich schlau. Was hat ein Parameter in der URL mit einem callback zu tun?

          Nur so viel, als dass der Parameter den Namen der Callback-Funktion übergeben soll - im Moment ist das gleichermaßen Konvention wie Abkürzung. Wie schon gesagt, plane ich, die Fähigkeit zu integrieren, unbenannte Funktionen vorübergehend zufällig zu benahmen, um sie als Callback zu übergeben (ähnlich wie bei jQuery).

          Was t.a betrifft,… Als Zielgruppe für das Toolkit sind durchaus Entwickler gemeint, die wissen, was sie tun.
          Und da denke ich, dass Du das Definieren eigener Methoden unnötig in den separaten code auslagerst, wo sich t.a doch so schön anbietet.

          Man könnte natürlich alternativ auf das Belegen der Event-Methoden verzichten und stattdessen das XHR-Objekt zurückgeben. Ist es das, was Du meinst?

          Gruß, LX

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

            Nur so viel, als dass der Parameter den Namen der Callback-Funktion übergeben soll - im Moment ist das gleichermaßen Konvention wie Abkürzung. Wie schon gesagt, plane ich, die Fähigkeit zu integrieren, unbenannte Funktionen vorübergehend zufällig zu benahmen, um sie als Callback zu übergeben (ähnlich wie bei jQuery).

            Manchmal nutze ein Serverscript, was anhand des query strings die Komponenten für das nachzuladenden Javascripte zusammensucht. Wenn Du also so willst, aus der Werkstatt die tatsächlich benötigten Werkzeuge herausrückt. Vielleicht ließe sich das Ganze mit einem wrapper ja kombinieren:

            t.j =function(u,t){  
                var s=document.createElement('script');  
              
                s.type='text/javascript';  
                s.src=u+(c?'?c='+c:'');  
                document.body.appendChild(s);  
              
                if (t) { window.setTimeout(function() { document.body.removeChild(s); }, t); }  
            }  
            t.js=function(u, c, t) {  
                u=u+(c?'?c='+c:'');  
                t.j(u,t);  
            };
            

            Was t.a betrifft,… Als Zielgruppe für das Toolkit sind durchaus Entwickler gemeint, die wissen, was sie tun.
            Und da denke ich, dass Du das Definieren eigener Methoden unnötig in den separaten code auslagerst, wo sich t.a doch so schön anbietet.

            Man könnte natürlich alternativ auf das Belegen der Event-Methoden verzichten und stattdessen das XHR-Objekt zurückgeben. Ist es das, was Du meinst?

            Dass Requestobjekt verfügbar zu machen, wäre eine Variante. Die Lösung die Du jetzt aber hast, eigene Methoden zu definieren, ist so schön, dass ich eben die Einschränkung nicht verstehe. Bei updates der vordefinierten Eigenschaften/Methoden wärst Du einerseits ohne weitere Anpassungen kompatibel. Nicht dass das hier als wirkliches Argument hinreicht, weil der Code wirklich schön einfach ist. Andererseits könnte man eben so eigenen Schnickschnack wirklich gelungen einfach integrieren.

            Gruß aus Berlin!
            eddi

            1. // in Berichtigung:  
              t.j =function(u,t){  
                  var s=document.createElement('script');  
                
                  s.type='text/javascript';  
                  s.src=u;  
                  document.body.appendChild(s);  
                
                  if (t) { window.setTimeout(function() { document.body.removeChild(s); }, t); }  
              }  
              t.js=function(u, c, t) {  
                  u=u+(c?'?c='+c:'');  
                  t.j(u,t);  
              };
              
              1. Das Script, was Du da schreibst, kann nicht mehr, benötigt aber mehr Zeilen. Meine Idee ging eher so in diese Richtung (Achtung, nur ein Prototyp, dieser Code ist nicht getestet):

                // u=URL, c=callback (name/func), p=GET-parameter-Name oder "c", t=timeout  
                t.j=function(u,c,p,t){  
                    var f=typeof c==='function';  
                    if (f) { t.j[(c='fn'+Math.random()*1000000+(new Date()*1))]=function(c){ return c; }(c); }  
                    var s=document.createElement('script');  
                    s.type='text/javascript';  
                    s.src=u+(c?(/\?/.test(u)?'&':'?')+(p||'c')+'='+c));  
                    document.body.appendChild(s);  
                    if (t) { window.setTimeout(function() { document.body.removeChild(s); }, t); }  
                    if (f) { window.setTimeout(function() { delete t.j[c]; }, (t*1)||0+100)); }  
                };
                

                Dann könnte man damit auch beliebige Parameter-Namen verwenden und auch unbenannte Funktionen übergeben, die einen vorübergehenden festen Namen bekommen, der nach dem Ausführen wieder gelöscht wird (ggf. muss man noch mit dem Timing ein wenig spielen).

                Gruß, LX

                --
                RFC 1925, Satz 6a: Es ist immer möglich, einen weiteren Umweg einzufügen.
                RFC 1925, Satz 11a: Siehe Regel 6a
                1. s.src=u+(c?(/\?/.test(u)?'&':'?')+(p||'c')+'='+c));

                  Das löst erstmal meine eigentlichen Bedenken wegen den potenziell vorhandenen Parametern in Wohlgefallen auf. Zusätzlich eine weiteres Argument p einzufügen, halte ich nicht für notwendig. u ist URL. Den hat man gefälligst vorher zusammenzubasteln. ;)

                  Gruß aus Berlin!
                  eddi

                  1. Hi, eddi

                    u ist URL. Den hat man gefälligst vorher zusammenzubasteln. ;)

                    Die Frage ist, an welcher Stelle man die Funktionalität präzisieren soll: im Code oder in der Dokumentation? Da es bei diesem Toolkit darum geht, Code zu sparen, gebe ich Dir durchaus Recht.

                    Gruß, LX

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

                      u ist URL. Den hat man gefälligst vorher zusammenzubasteln. ;)

                      Die Frage ist, an welcher Stelle man die Funktionalität präzisieren soll: im Code oder in der Dokumentation? Da es bei diesem Toolkit darum geht, Code zu sparen, gebe ich Dir durchaus Recht.

                      Mal abgesehen davon, dass sich Funktionalität nicht in eine Dokumentation verfrachten lässt, ist die Frage nach dem Wo schon der Knackpunkt. „A Quarter Century of Unix” lässt sich ein Stück weit ja auch hier als Grundlage nutzen. Dabei würde ich jede Methode als eigenständiges Programm betrachten.

                      t.j() soll ein JavaScript nachladen. (Punkt)

                      Gruß aus Berlin!
                      eddi

                      1. Das mag der Unix-Philosophie entsprechen, aber mein Toolkit versucht, so wenig Methoden wie möglich mit so viel sinnvoller Funktionalität wie möglich zu füllen, vom Ansatz her also ähnlich wie jQuery.

                        Mal abgesehen davon: JSONp ist schließlich auch nur ein Sonderfall von "lade ein JS nach" - also kann man diesen ruhig in der gleichen Methode behandeln.

                        Gruß, LX

                        --
                        RFC 1925, Satz 6a: Es ist immer möglich, einen weiteren Umweg einzufügen.
                        RFC 1925, Satz 11a: Siehe Regel 6a
            2. Dass Requestobjekt verfügbar zu machen, wäre eine Variante. Die Lösung die Du jetzt aber hast, eigene Methoden zu definieren, ist so schön, dass ich eben die Einschränkung nicht verstehe. Bei updates der vordefinierten Eigenschaften/Methoden wärst Du einerseits ohne weitere Anpassungen kompatibel. Nicht dass das hier als wirkliches Argument hinreicht, weil der Code wirklich schön einfach ist. Andererseits könnte man eben so eigenen Schnickschnack wirklich gelungen einfach integrieren.

              Meinst Du, ich sollte lieber eine auf die bereits verwendeten Instanzen gefilterte Kopie des Objekts direkt in das Request-Objekt einhängen?, also statt diesem Array und dessen Iteration lieber folgendes zu tun (Vorsicht, kein getesteter Code, nur mal in der Theorie):

              t.a=function(o) {  
                  // create XMLHttpRequest or leave  
                  var x=(window.XMLHttpRequest?new XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP"));  
                  if (!x) { return false; }  
                  // Open Request  
                  x.open(o.method||'GET', o.url, !!o.async, o.user, o.pass);  
                  // Send data  
                  x.send(o.data?(typeof o.data==='object'?t.p(o.data):o.data):null);  
                  // if not async, return result  
                  if (!o.async) { return x['response'+o.type||'Text']; }  
                  // Add everything not currently used to the Request Object  
                  t.x(x, t.f(o, function(k, v) { return /(url|method|data|type|async|user|pass)/.test(k)?u:v; }));  
                  // if async, set result handler function  
                  x.onreadystatechange=function(e) { if (x.readyState===4) { o.async(x['response'+o.type||'Text']); } }  
                  // return request object for further fun  
                  return x;  
              };
              

              Diese Lösung ist sogar ein paar Zeichen kleiner und hat daher durchaus das Potential, in einer baldigen Version integriert zu werden.

              Gruß, LX

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

                Meinst Du, ich sollte lieber eine auf die bereits verwendeten Instanzen gefilterte Kopie des Objekts direkt in das Request-Objekt einhängen?, also statt diesem Array und dessen Iteration lieber folgendes zu tun (Vorsicht, kein getesteter Code, nur mal in der Theorie):

                Pff, ähm, öh, das ist mir jetzt zu hoch ^^

                Gruß aus Berlin!
                eddi

                1. Hi, eddi!

                  Der Code macht eigentlich fast das gleiche, aber anstatt nur die Events in das Objekt zu kopieren, schmeißt es alles, was noch nicht verwendet wurde (was am Namen der Instanz per RegExp erkannt wird), in das XHR-Objekt, welches am Ende zurückgegeben wird.

                  Wahrscheinlich reicht es auch einfach, das Objekt am Ende zu übergeben. Wie gesagt, eine solche Funktionalität wäre in einer künftigen Version durchaus denkbar, im Moment bin ich bei beta1, die nach Beseitigung der Bugs zu rc1 und schließlich zu 0.0.1final wird. Danach denke ich über neue Features nach.

                  Gruß, LX

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

                    Wahrscheinlich reicht es auch einfach, das Objekt am Ende zu übergeben.

                    Der Meinung bin ich auch. Das sollte dann aber auch in beiden Fällen (synchron, asynchron) passieren.

                    Gruß aus Berlin!
                    eddi

                    1. Der Meinung bin ich auch. Das sollte dann aber auch in beiden Fällen (synchron, asynchron) passieren.

                      Das würde den Code natürlich weiter vereinfachen, was ich für eine gute Sache (TM) halte ;-) Ich werde das sogar noch für das 0.0.1final-Release (an dem ich derzeit arbeite) ernsthaft in Erwägung ziehen.

                      Gruß, LX

                      --
                      RFC 1925, Satz 6a: Es ist immer möglich, einen weiteren Umweg einzufügen.
                      RFC 1925, Satz 11a: Siehe Regel 6a
  2. Zunächst einmal vielen Dank für das Feedback (nicht alles davon ging über dieses Forum)!

    Inzwischen ist die zweite Beta-Version mit leichten Änderungen beim Networking auf der Welt und das Cheat Sheet hat als zusätzliche Fähigkeiten noch einen Selektor- und Event-Test mitbekommen.

    Gruß, LX

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