pl: XHR und mehrere Progressbars

Moin,

siehe Thema, hier meine Praxis und da hänge ich die ID direkt an das xhr-Objekt bzw. xhr.upload-objekt. Die ID über den Request zu schleifen ist ja nicht möglich, weil an der Stelle wo sie gebraucht wird (Upload Progress Monitor) die Response noch gar nicht verfügbar ist. Auf der verlinkten Seite versehe ich u.a. auch ein canvas-Objekt mit eigenen Attributen.

Ich kann mich erinnern, dass vor Jahren der IE mal rumgezickt hat, beim Versuch einem ActiveX-Objekt eigene Attribute zu verpassen. Mal abgesehen davon dass o.g. Anwendung aus anderen Gründen nur mit dem FireFox funktioniert, wie sind Eure Erfahrungen mit der Vergabe eigener Attribute an JS-Legacy-Objekte?

MfG

  1. Mal abgesehen davon dass o.g. Anwendung aus anderen Gründen nur mit dem FireFox funktioniert, wie sind Eure Erfahrungen mit der Vergabe eigener Attribute an JS-Legacy-Objekte?

    Fremde Objekte zu verändern ist schlechter Stil, ich lasse es ganz sein. Abgesehen davon, kannst du mit Object.isSealed(foo) überprüfen, ob du foo mit eigenen Eigenschaften erweitern darfst.

    1. Mal abgesehen davon dass o.g. Anwendung aus anderen Gründen nur mit dem FireFox funktioniert, wie sind Eure Erfahrungen mit der Vergabe eigener Attribute an JS-Legacy-Objekte?

      Fremde Objekte zu verändern ist schlechter Stil, ich lasse es ganz sein. Abgesehen davon, kannst du mit Object.isSealed(foo) überprüfen, ob du foo mit eigenen Eigenschaften erweitern darfst.

      Danke für den Hinweis. Wie ordnest Du denn eine Progressbar zu, ohne dem XHR-Object eine id zu verpassen?

      1. Danke für den Hinweis. Wie ordnest Du denn eine Progressbar zu, ohne dem XHR-Object eine id zu verpassen?

        Ich trenne Datenbeschaffung und Ausgabe vollständig voneinander und lasse die Zustände automatisch von einem Zustands-Container synchronisieren, d.h. in der Praxis von Redux oder dem Elm-Laufzeitsystem. Die Fragestellung ergibt in einer solchen Architektur keinen Sinn. Um dir trotzdem eine praktische Antwort zu geben, gehen wir davon aus, dass dein XHR-Objekt und das Progress-Element über Variablen zur Verfügung stehen.

        const request = new XMLHttpRequest();
        const progress = document.querySelector('progress');
        

        Dann könntest du die Zuordnungen zum Beispiel in einer Map verwalten:

        const map = new Map([[request,progress]]);
        
      2. Tach!

        Wie ordnest Du denn eine Progressbar zu, ohne dem XHR-Object eine id zu verpassen?

        Closures. Ich nähme auch keine ID, sondern eine Referenz auf das Objekt selbst. Also die Referenz auf das XHR und die Referenz auf die Progressbar in eine Closure stecken, und wenn der Callback aufgerufen wird, kann man von da aus auf die Variablen im Scope der Closure zugreifen.

        dedlfix.

        1. Tach!

          Wie ordnest Du denn eine Progressbar zu, ohne dem XHR-Object eine id zu verpassen?

          Closures. Ich nähme auch keine ID, sondern eine Referenz auf das Objekt selbst. Also die Referenz auf das XHR und die Referenz auf die Progressbar in eine Closure stecken, und wenn der Callback aufgerufen wird, kann man von da aus auf die Variablen im Scope der Closure zugreifen.

          Hast Du mal ein Beispiel dafür wie das für beliebig viele Progressbars auszusehen hat? Also wie im EventListener die Zuordnung progressbar <=> xhrObject erfolgt ohne eine Eigenschaft id zu haben. Ählich liegt für mich das Problem hier

           canvas.toBlob(function(blob) {} );
          

          innerhalb der Funktion, die bereits selbst in einen EventListener geschachtelt ist, brauche ich auch eine Reihe von Angaben, aber die Funktion hat nur ein Argument. Idee?

          1. Tach!

            Closures. Ich nähme auch keine ID, sondern eine Referenz auf das Objekt selbst. Also die Referenz auf das XHR und die Referenz auf die Progressbar in eine Closure stecken, und wenn der Callback aufgerufen wird, kann man von da aus auf die Variablen im Scope der Closure zugreifen.

            Hast Du mal ein Beispiel dafür wie das für beliebig viele Progressbars auszusehen hat? Also wie im EventListener die Zuordnung progressbar <=> xhrObject erfolgt ohne eine Eigenschaft id zu haben.

            Die erfolgt nicht im EventListener sondern in der Closure, in der auch der EventListener steckt.

            function transfer() {
               var xhr = ...;
               var progress = ...;
            
               xhr.onsomething = function() {
                 progress.foo = 'bar';
               };
            }
            

            Das ergibt ein ähnliches Bild wie bei klassischer (auch im Sinne von klassenbasierter) OOP. Die Funktion transfer entspricht der Klasse, die Variablen darin den Eigenschaften. Jeder Aufruf der Funktion erzeugt sozusagen eine Instanz mit eigenem Scope. Sehr vereinfacht gesagt.

            Ählich liegt für mich das Problem hier

             canvas.toBlob(function(blob) {} );
            

            innerhalb der Funktion, die bereits selbst in einen EventListener geschachtelt ist, brauche ich auch eine Reihe von Angaben, aber die Funktion hat nur ein Argument. Idee?

            Eine Closure drumherumbauen und das Zeug dort ablegen.

            dedlfix.

            1. Eine Closure drumherumbauen und das Zeug dort ablegen.

              Nicht drumherum sondern innendrinnen in der Funktion xhr.upload.onprogress = ... brauche ich die id von ganz oben:

              function upload(id){
                  // vor dem Upload wird das Bild scaliert
                  var height = $('#'+id+'height').text();
                  var width = $('#'+id+'width').text();
                  var img = new Image();
                  var bloburl = URL.createObjectURL(FILES[id]);
                  img.src = bloburl;
                  img.id = id;
                  img.width = width;
                  img.height = height;
                  img.onload = function(e){
                          var canvas = document.createElement('canvas');
                          canvas.id = e.target.id;
                          var ctx = canvas.getContext("2d");
                          canvas.width  = e.target.width;
                          canvas.height = e.target.height;
                          canvas.id = e.target.id;
                          ctx.drawImage(e.target, 0, 0, width, height);
                          canvas.toBlob(function(blob) {
                              if( document.getElementById(canvas.id+'saveas').checked == true ){
                                  saveAs(blob, $('#'+canvas.id+'name').text());
                                  return;
                              }
                              $('#'+canvas.id+'state').text('Pending');
                              var param = {
                                  upload: [1],
                                  name: [$('#'+id+'name').text()],
                                  descr: [$('#'+id+'descr').text()]
                              }
                              var xhr = new XMLHttpRequest();
                              xhr.open('PUT','%URL%?'+query4request(param));
                              xhr.upload.id = canvas.id;
                              xhr.id = canvas.id;
                              xhr.setRequestHeader('Content-Type','application/body+query');
                              xhr.upload.onprogress = function(e) {
                                  if (e.lengthComputable) {
                                      document.getElementById(e.target.id+'progress').value = (e.loaded / e.total) * 100;
                                  }
                              };
                              xhr.onload = function(e){
                                  if(e.target.status != 200){
                                      var name = $('#'+id+'name').text();
                                      return errmsg(xr("Problem mit Datei '@datei@', Fehler: @msg@", {msg:e.target.response,datei:name}));
                                  }
                                  $('#'+e.target.id+'state').text('Done');
                                  fixname(e.target.id, 'state');
                              };
                              xhr.send(blob);
                          },'image/jpeg', 0.6);
              
                  };
              }
              

              Jedesmal wenn upload(id) aufgerufen wird, kann die id eine andere sein. Falls der Aufruf in einer Schleife erfolgt, ist id mit Sicherheit eine Andere als die id deren Upload-Prozess noch läuft. Deswegen reicht mein Code die id über Objekteigenschaften durch bis zum jeweiligen EventHandler.

              1. Tach!

                Eine Closure drumherumbauen und das Zeug dort ablegen.

                Nicht drumherum sondern innendrinnen in der Funktion xhr.upload.onprogress = ... brauche ich die id von ganz oben:

                Jene Stack-Overflow-Frage zeigt, wie es geht. (Ob ich mit meiner vorigen Antwort richtig lag, bin ich grad im Zweifel.

                Jedesmal wenn upload(id) aufgerufen wird, kann die id eine andere sein.

                Ja, Closure bauen.

                Falls der Aufruf in einer Schleife erfolgt, ist id mit Sicherheit eine Andere als die id deren Upload-Prozess noch läuft. Deswegen reicht mein Code die id über Objekteigenschaften durch bis zum jeweiligen EventHandler.

                Die ID muss nicht bis direkt reingereicht werden, Closures sind ausreichend und ein üblicher Weg.

                dedlfix.

                1. Tach!

                  Jene Stack-Overflow-Frage zeigt, wie es geht. (Ob ich mit meiner vorigen Antwort richtig lag, bin ich grad im Zweifel.

                  Ich lag dann wohl doch richtig, wenn ich 1UPs Antwort als Bestätigung nehme, und die Closure ist bereits vorhanden. Muss also keine extra um den Eventhandler-Aufruf drumherumgestrickt werden.

                  Die verlinkte Variante braucht man nur, wenn man eine Schleife hat und damit immer im selben Scope bleibt. Im vorliegenden Fall erzeugt aber der Aufruf von upload() jeweils einen neuen Scope.

                  dedlfix.

              2. Jedesmal wenn upload(id) aufgerufen wird, kann die id eine andere sein. Falls der Aufruf in einer Schleife erfolgt, ist id mit Sicherheit eine Andere als die id deren Upload-Prozess noch läuft. Deswegen reicht mein Code die id über Objekteigenschaften durch bis zum jeweiligen EventHandler.

                Brauchst du nicht, beim Aufruf von update wird der Parameter id an einen Wert gebunden. Die Variable liegt automatisch auch im lexikalischen Geltungsbereich aller verschachtelten Funktions-Definitionen. In Code ausgedrückt:

                function update(id) {
                   // ...
                   img.onload = function(e){
                      // ...
                      canvas.toBlob(function(blob) {
                         // ...
                         xhr.upload.onprogress = function(e) {
                            console.log(id); // 42
                         }
                      }
                   }
                }
                update(42);
                
                1. Jedesmal wenn upload(id) aufgerufen wird, kann die id eine andere sein. Falls der Aufruf in einer Schleife erfolgt, ist id mit Sicherheit eine Andere als die id deren Upload-Prozess noch läuft. Deswegen reicht mein Code die id über Objekteigenschaften durch bis zum jeweiligen EventHandler.

                  Brauchst du nicht, beim Aufruf von update wird der Parameter id an einen Wert gebunden. Die Variable liegt automatisch auch im lexikalischen Geltungsbereich aller verschachtelten Funktions-Definitionen. In Code ausgedrückt:

                  Ach sooo.... Oooh Mann, Danke Dir und dedlfix ;)

                  Aber das Map Object ist auch ganz interessant, komm' ich dauf zurück!

                  Viele Grüße!

                  Ein Fall... für lange Winterabende ;)

              3. Ich mache mal ein einfaches Beispiel für Closures: Wenn Du irgendwo schreibst:

                function test(a)
                   var f = function(x) { return a+x; }
                   return f;
                }
                var f1=test(1), f2=test(2), f3=test(4);
                

                dann steht in f1, f2 und f3 jedesmal eine Funktion, die sich als function(x) { return a+x; } präsentiert, also gleich aussieht. Was man nicht sieht, ist der Laufzeitkontext. Der wird in dem Moment an die Funktion gehängt, wo function... an irgendetwas zugewiesen wird. Dieser Kontext enthält die Variablen a und f, und a hat jedesmal einen anderen Wert. Deswegen addiert f1 eine 1, f2 eine 2 und f3 eine 4.

                Übertragen auf dein Szenario heißt das: Eigentlich musst Du gar nichts durchreichen, damit es funktioniert. Die ID ist Teil des Aufrufkontextes, der an deine Eventhandler gebunden ist, und darum in jeder Handlerinstanz unterschiedlich. Ganz genau formuliert hängen an deinen Eventhandlern sogar drei Aufrufkontexte: der des canvas.toBlob-Handlers, der des img.onload Handlers und der von update(). Die ID ist ein Parameter für update und darum Teil des Laufzeitkontextes der update() Funktion.

                Was Probleme bereiten könnte, ist dein erzwungenes Zuweisen der e.target.id (sprich: der img.id) an die diversen id Attribute anderer Elemente. Die ID muss im DOM eindeutig sein und das könnte deshalb zur Verwirrung des Browsers führen. Arbeite nur mit der id Variablen, das müsste passen.

                Rolf

                1. ...ich bin zu langsam. Bis ich meine Antwort fertig habe, ist schon ein ganzer Threadbaum gewachsen :)

                  Rolf

                2. Was Probleme bereiten könnte, ist dein erzwungenes Zuweisen der e.target.id (sprich: der img.id) an die diversen id Attribute anderer Elemente. Die ID muss im DOM eindeutig sein und das könnte deshalb zur Verwirrung des Browsers führen. Arbeite nur mit der id Variablen, das müsste passen.

                  Naja, meine Objekte mit gleicher id hänge ich ja gar nicht rein ins DOM. Aber egal, es gibt Einiges zu verbessern. Z.B. werde ich das Tabellen-Template demnächst erst rendern, wenn das ThumbImg.onload feuert. Dann kommt die Zeile gleich in der richtigen Höhe/Breite.

                  MfG

      3. Ich würde sagen, dass die Lösung funktionale Programmierung und Closures lautet.

        Was triggert bei Dir denn eine Progress-Änderung? Doch sicher das Progress-Event, dem du einen Handler zuordnest. Wenn Du diese Zuordnung innerhalb einer Funktion durchführst (die sonst NICHTS tut), kannst Du eine Closure bilden, die deinem Handler Zusatzinformationen bereitstellt. Wenn Du Zusatzinformationen für mehrere Handler gemeinsam brauchst, kannst Du sie alle in dieser Funktion registrieren und hast einen geteilten Datenpool nur für dieses XHR Objekt, ohne das Objekt anzufassen.

        Also z.B. sowas:

        function addProgressHandler(xhr, progrControl)
        {
           xhr.addEventListener('progress', function(event) {
              // verwende progrControl variable um das DOM Element zu referenzieren, das den Progress zeigt
           });
        }
        

        Wenn Du die Funktion nicht in addProgressHandler einkapseln, sondern anderswoher holen willst, dann kannst Du sie natürlich auch in die Registrarfunktion hineingeben. Wichtig ist eben nur, dass eine Closure gebildet wird, die pro Registrierung einen kleinen Datenspeicher für die Handlerfunktion bildet.

        function addProgressHandler(xhr, progrControl, progressHandler)
        {
           xhr.addEventListener('progress', function(event) {
              progressHandler(event, progrControl);
           });
        }
        

        Gruß
        M.