vorige Präsentation: Funktion Aufrufen | zurück zum Buch-Kapitel [esc] | Nächste Präsentation Prototypen + Vererbung
Dieses Kapitel ist ein Tutorial zu folgenden Projekt: Ein SVG-Editor soll programmiert werden, mit dem man Kreise und Rechtecke im Browser erzeugen und positioniern kann.
An diesem Beipiel wird die Behandlung von Event und die Notwendigkeit von Closures erläutert.
Der Fokus in diesem Kapitel liegt auf der Drag und Drop Funktionalität.
Dieses Programm wird umfangreich. Deswegen werden von Anfang an Strukturen zur Wiederverwertung von Code gelegt:
Für den Editor, und für Rechtecke (Rectangles) und Kreise (Circles) werden jeweils Konstruktor-Funktionen angelegt, mit der Editor-, Rechteck-, und Kreis-Objekte erzeugt werden können.
Die Konstruktur-Funktion des Editor Objekts nimmt folgende Argumente
css_selector
- ein String, wird als CSS-Selector interpretiert. Der Inhalt des ausgewählten DOM-Elements wird gelöscht, statt dessen wird der Editor angezeigtw
- optional eine Zahl oder ein String der eine Zahl enthält. Breite des Editors. Default Wert 600.h
- optional eine Zahl oder ein String der eine Zahl enthält. Höhe des Editors. Default Wert 600.Das Editor Objekt hat folgende Attribute:
dom
- das SVG-Elementrenderlist
- eine Liste mit Objekten, die im Editor dargestellt werdenDas Editor Objekt hat folgende Methoden
rectangle
- Erzeugt ein Objekt mit dem Rectangle
-Konstruktur, fügt es in der renderlist eincircle
- Erzeugt ein Objekt mit dem Circle
-Konstruktur, fügt es in der renderlist einfunction Editor( css_selector, w, h ) { this.renderlist = []; this.dom = document.createElementNS("http://www.w3.org/2000/svg", "svg"); this.dom.setAttribute("width", w || 600); this.dom.setAttribute("height", h || 600); emptyAndAppendChild( document.querySelector( css_selector ), this.dom ); return this; } Editor.prototype.rectangle = function( x,y,w,h,att, text ) { var o; o = new Rectangle( x,y,w,h, att, this.dom ); this.renderlist.push(o); return o; } Editor.prototype.circle = function( x,y,r,att, text ) { var o; o = new Circle( x,y,r,att, this.dom ); this.renderlist.push(o); return o; }
Circle und Rectangle funktionieren sehr ähnlich, deswegen werden sie hier gemeinsam
beschreiben. Als SVG-Elemente betrachtet gibt es größere Unterschiede: so wird die
Position eines <circles>
über die Attribute cx
und cy
gesetzt, die Position eines
<rect>
über x
und y
. Diese Unterschiede werden durch die neu erstellen Javascript-Objekte
möglichst verborgen.
Die Signatur der Konstruktur-Funktion sind:
Fast alle Argumente sind also gleich und gleich bedeutend. Der einzige Unterschied: Die
Größe des Rechtecks wird über Breite w
und Höhe h
definiert, die Größe
des Kreis über den Radius r
. Der Aufruf der Konstruktors erzeugt das SVG-Element,
fügt es in den canvas ein, setzt die Attribute und fügt geeignete Event-Handler für drag-and-drop
ein.
Die anderen Argumente sind:
x
und y
- Position (beim Rechteck: der linken oberen Ecke. beim Kreis: des Mittelpunktes)att
- ein Objekt. Die Attribute des Objekts werden auf das erzeugte SVG-Element übertragen, z.B. {'stroke': 'red', 'fill': 'white'}
canvas
- eine DOM-Element das geeignet ist ein <rect>
oder <circle>
Element aufzunehmen. z.B. ein <svg>
Element.Das Rectangle und das Circle Objekt haben folgende Attribute:
dom
- das SVG-Element, also der Kreis oder das Rechteckcanvas
- das parent-Element in das es eingefügt wurdeid
- die ID (wird auch im SVG-Element selbst noch einmal gesetzt)Das Rectangle und das Circle Objekt haben folgende Methoden
getPosition
- Liefert die Position (aus den SVG-Attributen x/y bzw. cx/xy) als Objekt mit Eigenschaften x
und y
setPosition( position )
- erwartet die Position als Objekt mit Eigenschaften x
und y
, positioniert das Element entsprechendvar nextCircleId = idGenerator( "cicle" ); function Circle( x,y,r,att, canvas ) { var shape,a; this.id = nextCircleId(); this.canvas = canvas; // <circle> this.dom = document.createElementNS("http://www.w3.org/2000/svg", "circle"); this.dom.setAttribute("id", this.id); this.dom.setAttribute("cx", x); this.dom.setAttribute("cy", y); this.dom.setAttribute("r", r); addAttributes(this.dom, att, {"fill": "white", "stroke": "black", "stroke-width": 1}); canvas.appendChild(this.dom); addDragability( this, this.dom ); } Circle.prototype.getPosition = function () { return { x: parseInt( this.dom.getAttribute( "cx" ) ), y : parseInt( this.dom.getAttribute("cy") ) }; }; Circle.prototype.setPosition = function ( position ) { this.dom.setAttribute("cx", position.x ); this.dom.setAttribute("cy", position.y ); };
Wie funktioniert Drag-and-Drop? Wir haben die Events mousedown
, mouseup
und mousemove
zur Verfügung.
Das Auslesen der aktuellen Mous-Position ist leider nicht einheitlich implementiert. Hier eine Implementierung die in allen Browsern funktionieren sollte:
function cursorPoint(e) { var posx = 0; var posy = 0; if (!e) var e = window.event; if (e.pageX || e.pageY) { posx = e.pageX; posy = e.pageY; } else if (e.clientX || e.clientY) { posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; } // posx and posy contain the mouse position relative to the document // Do something with this information return { 'x': posx, 'y': posy }; }
Wenn wir Event-Listener-Funktionen für diese Events definieren haben wir der Funktion jeweils Zugriff auf ein Element der DOM, das durch das Event betroffen ist.
Also zum Beispiel:
function onMouseDown(e) { console.log("Mouse down. this is " + this.id + " event is " + e); } element.addEventListener( 'mousedown', onMouseDown);
Wenn die Funktion onMouseDown aufgerufen wird enthält
this
das angeklickte Element, und e
enthält ein Event-Objekt mit
weiteren Details.
In unserem Javascript-Programm wird ein Kreis nicht alleine durch
die SVG-Node <circle>
repräsentiert, sondern durch ein Javascript-Objekt
das mit dem Konstruktor Circle
erzeugt wurde.
Wie kann die Listener-Funktion Zugriff zu diesem Javascript-Objekt erhalten?
Ein Versuch wäre, das Javascript-Objekt im SVG-Element zu speichern. zum Beispiel in einem Data-Attribut. In HTML5 darf man ja zu jedem Tag / Element beliebige neue Attribute dazu erfinden, solange der Name des Attributs mit “data-“ beginnt.
<h1 data-geheim="wichtig daten">Normaler Überschrift</h1> .... <circle data-weight="10kg">
Ein Data-Attribut könnte man mit Javascript setzen. Hier ein Versuch:
// Versuch ein Objekt zu speichern c = document.getElementById("circle_1"); c.setAttribute("data-myobject", { color: 'red', no: 42 } ); // auslesen der Daten o = c.getAttribute("data-myobject"); typeof o // ergibt "string" console.log(o) // ergibt "[object Object]"
Beim Speichern in der DOM wurde also mein Objekt in einen String umgewandelt, und nur der String wurde gespeichert.
Es ist nicht möglich beliebige Javascript-Objekte in der DOM zu speicher!
Die übliche Lösung in Javascript ist die Verwendung von Closures: die Event-Listener-Funktion ist eine Closure, die das Javascript-Objekt kennt, und deswegen darauf Zugriff hat.
function addDragability( jsobj, element ) { function onMouseDown(e) { console.log("Mouse down"); console.log("my element is " + this); console.log("my javascript object is " + jsobj); console.log("my event is " + e); } element.addEventListener( 'mousedown', onMouseDown); } addDragability( jsobj, element ); addDragability( other_jsobj, other_element );
Wenn man die Beschreibung von Drag-and-Drop nochmal betrachtet …
und das nun in Event-Listener umsetzen will kommt man zu folgender Struktur
function addDragability( jsobj, element ) { function onMove(e) { // die Differenz zwischen aktueller Maus-Position // und der Start-Position berechnen. Das ergibt // einen Translations-Vektor. // Diesen Vektor zur Startposition des Objekts // addieren, aktuelle Position des Objekts setzen } function onMouseDown(e) { // Drag-und-Drop beginnt: den richtigen Eventlistener // für mousemove setzen // Die Position der Maus und des Objekt zu diesem // Start-Zeitpunkt müssen gespeichert werden. } function onMouseUp(e) { // Drag-and-Drop beenden: nicht mehr gebrauchte // event-listener entfernen } console.log( jsobj + " und damit " + element + " sind nun draggable");
vorige Präsentation: Funktion Aufrufen | zurück zum Buch-Kapitel [esc] | Nächste Präsentation Prototypen + Vererbung
/
#