bubble: drag&drop element wird nicht gespeichert

Hi,
für den Lerneffekt erfinne ich grade das Rad neu, um genau zu sein eine minimale GUI.

Nun sitz ich grade beim Verschieben der "Fenster" fest.

Ich habe eine "Klasse" GUIenvironment:

function GUIEnvironment(rootElement)  
{  
	this.root = rootElement;  
	this.elements = Array();  
   //dragging vars  
	this.dragObj = null;  
	this.mouse = new Point(0,0);  
	this.offset = new Point(0,0);  
   //browser related  
	this.ie = document.all && !window.opera;  
}  
  
GUIEnvironment.prototype.doDrag = function(e)  
{  
	if(this.ie)  
		this.mouse = new Point(window.event.clientX, window.event.clientY);  
	else  
		this.mouse = new Point(e.pageX, e.pageY);  
  
	document.getElementById("log").value += "\n do_drag "+(this.dragObj?"set":"null");  
	if(this.dragObj)  
	{  
		this.dragObj.style.left = (this.mouse.x - this.offset.x)+"px";  
		this.dragObj.style.top = (this.mouse.y - this.offset.y)+"px";  
	}  
}  
  
GUIEnvironment.prototype.startDrag = function(obj)  
{  
	this.dragObj = obj;  
	document.getElementById("log").value += "\n start_drag "+(this.dragObj?"set":"null");  
	this.offset = new Point(this.mouse.x - obj.offsetLeft, this.mouse.y - obj.offsetTop);  
}  
  
GUIEnvironment.prototype.stopDrag = function(event)  
{  
	document.getElementById("log").value += "\n stop_drag "+(this.dragObj?"set":"null");  
	this.dragObj = null;  
}

Das Element mit der ID log ist ein einfaches <textarea>, verschwindet, später dann komplett aus dem Code.

Beim Initialisieren wird unteranderem folgendes gemacht:

		document.onmousemove = GUIenv.doDrag;  
		document.onmouseup = GUIenv.stopDrag;

Im onmousedown Event des Elements, das verschoben werden soll rufe ich dann window.GUIenv.startDrag(this); auf.

Wenn ich nun die Maus auf das Element bewege kommt halt, wie es sein soll, "do_drag null" im log,
wenn ich klicke und die Maustaste gedrückt lasse erscheint auch noch "start_drag set",
wenn ich jetzt aber die Maus bewege um das Element zu verschieben kommt wieder "do_drag null" (this.dragobj hat jetzt einen falschen Wert :-/),
wenn ich die Maustaste wieder los lasse kommt auch wieder fälschlicher weise "stop_drag null".

Das sind allerdings auch alle Funktionen die dragObj lesen/schreiben =/

Ich wind partout den Fehler nicht :(

MfG
bubble

  1. Puh, hab mich jetzt dadurch gewühlt. Ein paar Kommentare mehr hätten es wesentlich leichter gemacht.

    Zunächst mal ein paar Basics:

      
    this.elements = Array();  
    
    

    Wozu soll elements gut sein? Es taucht später nicht mehr auf. Ich nehme mal an, du wolltest elements auf eine Array-Instanz zeigen lassen. Dazu müsstest du aber mittels dem Keyword new den Konstruktor aufrufen. Noch besser ist allerdings es einfach mittels dem Array-Operator "[]" zu initialisieren. Ich könnte mir vorstellen, dass der Fehler, warum dein Code nicht funtkioniert genauso der gleiche ist. "GUIenv" scheint deine GUIEnvironment-Instanz zu sein. Wie wird diese initialisiert? Den Code hast du uns leider vorenthalten.

    Das Element mit der ID log ist ein einfaches <textarea>, verschwindet, später dann komplett aus dem Code.

    Besser du logst mit
    console.log("dein text");

    Beim Initialisieren wird unteranderem folgendes gemacht:

    	document.onmousemove = GUIenv.doDrag;  
    
      document.onmouseup = GUIenv.stopDrag;
    
      
    Das ist schade. Du hast einen objektorientierten Ansatz gewählt, kannst trotzdem nicht mehrere Instanzen gleichzeitig laufen lassen, weil du die veralteten Funktionszeiger "onmouseup" und co. verwendest. Besser du verwendest  
    ~~~javascript
      
    document.addEventListener("mousedown", function(e){  
       // do something  
    })
    

    Grüße
    -1UnitedPower

    1. Puh, hab mich jetzt dadurch gewühlt. Ein paar Kommentare mehr hätten es wesentlich leichter gemacht.

      Zunächst mal ein paar Basics:

      this.elements = Array();

      
      > Wozu soll elements gut sein? Es taucht später nicht mehr auf. Ich nehme mal an, du wolltest elements auf eine Array-Instanz zeigen lassen. Dazu müsstest du aber mittels dem Keyword new den Konstruktor aufrufen. Noch besser ist allerdings es einfach mittels dem Array-Operator "[]" zu initialisieren. Ich könnte mir vorstellen, dass der Fehler, warum dein Code nicht funtkioniert genauso der gleiche ist. "GUIenv" scheint deine GUIEnvironment-Instanz zu sein. Wie wird diese initialisiert? Den Code hast du uns leider vorenthalten.  
        
      elements wird in anderen Code-Segmenten die nichts mit dem drag&drop zu tun haben, verwendet. Hab ich nur vergessen raus zu nehmen,  
      Das "new" hab ich auch vergessen >.<  
      Wenn ich mit "[]" initialisieren will, dann einfach "this.elements[];" ?  
        
      Hier mal die Initialisierung (das ist das "Ende" der js-Datei):  
        
      ~~~javascript
      function GUIInit()  
      {  
      	body = document.getElementsByTagName("body")[0];  
      	if(body)  
      	{  
      		window.GUIenv = new GUIEnvironment(body);  
      		document.onmousemove = GUIenv.doDrag;  
      		document.onmouseup = GUIenv.stopDrag;  
      	}  
      	else  
      		window.setTimeout("GUIInit()", 100);  
      }  
        
      GUIInit();  
      
      

      Dazu fällt mir noch ein, wenn ich an setTimeout() direkt GUIInit() übergebe - also nicht als string - meckert Firefox immer rum a la "too much recursion" [dazu werd ich mich zu einem anderen Zeitpunkt noch mal genau belesen]

      Point hatte ich auch vergessen sollte aber eigentlich nicht der "böse Fehler" sein:

      function Point(x,y)  
      {  
      	this.x = (x?x:0);  
      	this.y = (y?y:0);  
      }
      

      Seitens Browser kommen keine Fehler-/Warnmeldungen

      MfG
      bubble

      Hier mal der komplette Code (der ist allerdings mit Vorsicht zugenießen):
      http://pastebin.com/usEQkGb2

      1. Wenn ich mit "[]" initialisieren will, dann einfach "this.elements[];" ?

        this.elements = [];

        Hier mal die Initialisierung (das ist das "Ende" der js-Datei):

        function GUIInit()

        {
        body = document.getElementsByTagName("body")[0];
        if(body)
        {
        window.GUIenv = new GUIEnvironment(body);
        document.onmousemove = GUIenv.doDrag;
        document.onmouseup = GUIenv.stopDrag;
        }
        else
        window.setTimeout("GUIInit()", 100);
        }

        GUIInit();

        
        >   
        > Dazu fällt mir noch ein, wenn ich an setTimeout() direkt GUIInit() übergebe - also nicht als string - meckert Firefox immer rum a la "too much recursion" [dazu werd ich mich zu einem anderen Zeitpunkt noch mal genau belesen]  
          
        ~~~javascript
          
        window.setTimeout(GUIInit(), 100); //Führt zu einem Fehler, falls GuiInit nicht zufällig eine Funktion zurückgibt  
          
        window.setTimeout(GUIInit, 100); // Müsste funktionieren  
          
        // Beides ist allerdings hässlich, besser du machst folgendes  
          
        document.addEventListener("DOMContentLoaded",GUIInit); // dann kannst du sicher sein, dass dein DOM-Baum fertig geladen hat  
        
        

        Das könnte widerum mit deinem Problem zu tun haben. Durch setTimeout("GUIInit()",100) wird GUIInit in einem anderen Scope, wenn nicht sogar in einem anderen Scope aufgerufen. setTimeout arbeitet hier ähnlich wie "eval()".

        Als nächstes hierzu:

        window.GUIenv = new GUIEnvironment(body);

        Warum machst du GUIenv zu einer Eigenschaft von window? Ich sehe keinen Grund dafür und würde GUIenv besser zu deinem Programm-Scope hinzufügen.

        Apropros Scope du solltest dein Programm in einen closure packen um keine unnötigen globalen Variable zu erzeugen. In etwa so:

          
        (function(){  
          // dein Programmcode  
        }());  
        
        

        Falls das alles immernoch nicht hilft, ich bin noch ne Weile wach.

        Grüße
        -1UP

        1. window.setTimeout(GUIInit(), 100); //Führt zu einem Fehler, falls GuiInit nicht zufällig eine Funktion zurückgibt

          window.setTimeout(GUIInit, 100); // Müsste funktionieren

          Funktioniert auch einwandfrei :) Ein wirklich dummer Fehler meinerseits >.<

          // Beides ist allerdings hässlich, besser du machst folgendes

          document.addEventListener("DOMContentLoaded",GUIInit); // dann kannst du sicher sein, dass dein DOM-Baum fertig geladen hat

            
          Später soll die GUIInit noch komplexer werden, um zb. dynamisches Nachladen zu ermöglichen, mit dem EventListener unter JS hab ich mich generell noch nicht wirklich beschäftigt, deswegen kam mir diese (noch) unschöne Lösung gelegen.  
            
          
          > Das könnte widerum mit deinem Problem zu tun haben. Durch setTimeout("GUIInit()",100) wird GUIInit in einem anderen Scope, wenn nicht sogar in einem anderen Scope aufgerufen. setTimeout arbeitet hier ähnlich wie "eval()".  
          >   
          > Als nächstes hierzu:  
          > >`window.GUIenv = new GUIEnvironment(body);`{:.language-javascript}  
          >   
          > Warum machst du GUIenv zu einer Eigenschaft von window? Ich sehe keinen Grund dafür und würde GUIenv besser zu deinem Programm-Scope hinzufügen.  
            
          Damit ich zum einem weiß wie ich darauf zugreifen kann (ich war mir nie recht sicher wann eine Instanz/Variable global ist oder nicht, da window ja global sein sollte, kann ich dann auch darauf zugreifen), zum anderem um eventuelle spätere Spieleren mit Sachen wie Greasemonkey zu vereinfachen/ermöglichen  
            
          
          > Apropros Scope du solltest dein Programm in einen closure packen um keine unnötigen globalen Variable zu erzeugen. In etwa so:  
          >   
          > ~~~javascript
            
          
          > (function(){  
          >   // dein Programmcode  
          > }());  
          > 
          
          

          Falls das alles immernoch nicht hilft, ich bin noch ne Weile wach.

          Ja, hier wieder mein Problem, dass ich noch nicht recht weiß, wann eine Variable global wird.
          Wenn ich mir meinen bisherigen Code angucke, werden eigentlich nur die Funktionen global

          Am Beispiel der GUIInit:

          function GUIInit()  
          {  
          	body = document.getElementsByTagName("body")[0];  
          	if(body)  
          	{  
          		window.GUIenv = new GUIEnvironment(body);  
          		document.onmousemove = GUIenv.doDrag;  
          		document.onmouseup = GUIenv.stopDrag;  
            
            
          		win = new GUIWindow("testWin","Test window",0);  
          		win.create();  
          	}  
          	else  
          		window.setTimeout(GUIInit, 100);  
          }  
          
          

          body und win sollte nicht global sein, da sie im Gültigkeitsbereich der Funktion deklariert werden. Im Gegenzug sollte GUIenv zur Eigentschaft der des global window-Objektes werden.

          Wenn ich jetzt den kompletten Code der JS-Datei in ein Scope packe,

            
          (function(){  
            // um genau zu sein, hier  
          }());
          

          hätte ich zwar weiterhin Zugriff auf GUIenv und deren Eigenschaften/Kindelementen (über window), aber der Konstruktor GUIwindow wäre zB. über ein späteres <script>-Segment der HTML-Datei nicht mehr bekannt, oder doch? Und wenn ja, warum?

          Das einzige was mir einfallen würde, wo ein Scope Sinn ergibt wäre somit die Funktion GUIinit, damit diese nicht doppelt aufgerufen wird, was wiederum mit dem setTimeout(GUIinit, 100) Probleme ergeben dürfte.

          Der eigentliche Fehler, dass this.dragObj kein gültiges Element ist besteht immernoch. (Um nicht komplett vom Thema abzuscheifen >.<)

          MfG
          bubble

          1. Also Javascript hat ausschließlich function-scope. Jede variable die nicht innerhalb einer Funktion erzeugt wurde ist folglich global. Das selbe gilt für Funktionen.

            Wenn du nämlich schreibst:
            function foo(){}
            Wird daraus intern:
            var foo = function foo(){}

            [Quelle und außerdem sehr empfehlenswerter Vortrag: http://www.youtube.com/watch?v=hQVTIJBZook]

            Du solltest unter allen Umständen versuchen globale Variablen zu vermeiden, falls ein andere Skript Zugriff auch deine Objecte braucht, dann schau dir dazu am besten mal die Lösung von jQuery an, die haben quasi eine Referenzvariable an ihren Closure übergeben, der alle öffentlichen Attribute zu Teil gemacht werden.

            Es fällt mir leider schwierig beim eigentlichen Thema zu bleiben, da ich viele dieser Kleinigkeiten schon lange nicht mehr benutzt habe und ich deren exakte Arbeitsweisen nicht mehr im Kopf habe. Dennoch will ichs probieren.
            Ein nächster Schritt beim Debuggen wäre den genauen Typ von "dragObj" zu ermitteln. Javascript hat schließlich nicht nur den Wert "null", sondern noch eine Menge anderer Typen und Werte die einen "false"-Wert liefern. Also verusch mal immer den richtigen Typ zu loggen. Das kann schonmal sehr Aufschlussreich sein. Wenn du das gemacht hast, lade deinen Code am besten nochmal neu hoch, um es nachvollziehbarer zu machen.

            Grüße
            -1UP

            1. Okay meine Vermutung ist folgende.

              Durch das späte Binding des "this"-Keywords zeigt "this" nicht immer auf deine GUIEnviroment-Instanz.

              Wenn du schreibst
              document.onmousemove = GUIenv.doDrag
              zeigt "this" innerhalb deiner "doDrag"-Methode auf "document", da document die aufrufende Instanz ist.

              1. Okay meine Vermutung ist folgende.

                Durch das späte Binding des "this"-Keywords zeigt "this" nicht immer auf deine GUIEnviroment-Instanz.

                Wenn du schreibst
                document.onmousemove = GUIenv.doDrag
                zeigt "this" innerhalb deiner "doDrag"-Methode auf "document", da document die aufrufende Instanz ist.

                Habe noch einen passenden Link rausgesucht: http://www.quirksmode.org/js/this.html

                Entschuldige die vielen Posts, mein Kopf steht gerade im nirgendwo.
                Dafür kennst du jetzt deinen Fehler ;)

                viel Erfolg weiterhin

                -1UnitedPower

                1. Entschuldige die vielen Posts, mein Kopf steht gerade im nirgendwo.

                  Das muss man nicht entschuldigen, schließlich hilfts ja :)

                  Dafür kennst du jetzt deinen Fehler ;)

                  Auf die Idee bin ich grad auch gekommen beim rauchen.
                  Und ja es ist das Problem :D

                  viel Erfolg weiterhin

                  Danke :)
                  Ich wünsch noch eine angenehme Nacht ;p

                  MfG
                  bubble

            2. Ein nächster Schritt beim Debuggen wäre den genauen Typ von "dragObj" zu ermitteln. Javascript hat schließlich nicht nur den Wert "null", sondern noch eine Menge anderer Typen und Werte die einen "false"-Wert liefern. Also verusch mal immer den richtigen Typ zu loggen. Das kann schonmal sehr Aufschlussreich sein. Wenn du das gemacht hast, lade deinen Code am besten nochmal neu hoch, um es nachvollziehbarer zu machen.

              Okay, Problem mehr oder weniger gefunden. this.dragObj startDrag scheint in einem anderen Scope als doDrag und stopDrag zu sein, doDrag und stopDrag allerdings im selben. Was mir die ganze Sache allerdings nur noch unverständlicher macht, da sie ja "exakt" gleich deklariert werden.

              bei startDrag,
              this.dragObj hat den Wert "null" bevor etwas zugewiesen wird, nach dem zuweisen richtiger Weise "object HTMLDivElement" (also ich nehm jetzt mal die jeweilige Stringausgabe)

              bei doDrag ist this.dragObj undefiniert, bis einmal stopDrag ausgeführt wird (dann wird this.dragObj auf "null" gesetzt)

              Und hier nochmal der Quelltext, exakt so wie er in der GUI.js die ich einbinde gespeichert ist: http://pastebin.com/WCQ7uBff

              MfG
              bubble