0

Using Raphael, I wish to be able to drag a shape (an ellipse in the example below) containing a text object, dragging either the shape or the text. I hoped to do this by setting the functions passed to the text element's drag() method to delegate to the associated shape (trying a more polymorphic approach to this other one). However, this results in an error "obj.addEventListener is not a function" when text.drag(...) is called.

I'm new to javascript, so I've probably made a really obvious blunder, but I can't spot it. Have I misused call() in my delegating functions moveText, dragText, and upText? Or is this a Raphael thing? Any help would be greatly appreciated.

<html>
<head>
<title>Raphael delegated drag test</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="js/raphael.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<script type="text/javascript">
window.onload = function initPage() {
    'use strict';
    var paper = Raphael("holder", 640, 480);
    var shape = paper.ellipse(190,100,30, 20).attr({
            fill: "green", 
            stroke: "green", 
            "fill-opacity": 0, 
            "stroke-width": 2, 
            cursor: "move"
        });
    var text = paper.text(190,100,"Ellipse").attr({
            fill: "green", 
            stroke: "none", 
            cursor: "move"
        });

    // Associate the shape and text elements with each other
    shape.text = text;
    text.shape = shape;

    // Drag start
    var dragShape = function () {
        this.ox = this.attr("cx");
        this.oy = this.attr("cy");
    } 
    var dragText = function () {
        dragShape.call(this.shape);
    }

    // Drag move
    var moveShape = function (dx, dy) {
        this.attr({cx: this.ox + dx, cy: this.oy + dy});
        this.text.attr({x: this.ox + dx, y: this.oy + dy});
    }
    var moveText = function (dx,dy) {
        moveShape.call(this.shape,dx,dy);
    }

    // Drag release
    var upShape = function () {
    }       
    var upText = function () {
        upShape.call(this.shape);
    }

    shape.drag(moveShape, dragShape, upShape);
    text.drag(moveText, dragText, upText);
};
</script> 
    <div id="holder"></div>
</body>
</html>

Solution

As pointed out in this answer, the problem arises from the choice of the attribute names:

// Associate the shape and text elements with each other
shape.text = text;
text.shape = shape;

Changing these to more verbose names (and less likely to conflict with Raphael) makes the problem go away, but it's safer to set them as data attributes :

// Associate the shape and text elements with each other
shape.data("enclosedText",text);
text.data("parentShape",shape);

// Drag start
var dragShape = function () {
    this.ox = this.attr("cx");
    this.oy = this.attr("cy");
} 
var dragText = function () {
    dragShape.call(this.data("parentShape"));
}

// Drag move
var moveShape = function (dx, dy) {
    this.attr({cx: this.ox + dx, cy: this.oy + dy});
    this.data("enclosedText").attr({x: this.ox + dx, y: this.oy + dy});
}
var moveText = function (dx,dy) {
    moveShape.call(this.data("parentShape"),dx,dy);
}
Community
  • 1
  • 1
beldaz
  • 4,299
  • 3
  • 43
  • 63

1 Answers1

3
// Associate the shape and text elements with each other
shape.text = text;
text.shape = shape;

You are adding attributes to Raphael objects. Without knowing how Raphael works (or will work in the future) this is dangerous and apparently also what's causing the problem. If you really want to associate them i recommend using Raphaels Element.data: http://raphaeljs.com/reference.html#Element.data

bennedich
  • 12,150
  • 6
  • 33
  • 41
  • How would you make the wrapper work with dragging? Bring able to add attributes seems to be one if the things that makes Javascript so versatile and I don't think it's so apparent that's the core problem. I'm using a fixed version of Raphael so future proofing isn't such an issue for me. – beldaz Jan 12 '12 at 06:22
  • Sorry, I meant 'apparent' as 'as it turned out'. Adding attributes like that is a powerful trait but I strongly recommend not doing it in anything but your own code or where you are explicitly encouraged to. About the dragging thing; Raphael lacks any support except `set`. So it really depends on the project requirements. An easy solution would be inject the ellipse and text objects into a closure and bind the handlers inside. The downside is alot of objects means alot of functions/handlers. Another way would be to use mutual class names and do lookups via an object (used as a dict/hashtable). – bennedich Jan 12 '12 at 07:46
  • I can get it all to work by replacing the attribute names in two lines you identified as the problem area (edit showing this to follow). So were indeed right. I'll stick with the approach and remember to take more care with attribute names in future. Your alternatives sound too advanced for me right now ;) – beldaz Jan 12 '12 at 09:06
  • 3
    I just realized; Raphael has, like jQuery, a `data` method for storing arbitrary data associated with the object: http://raphaeljs.com/reference.html#Element.data – bennedich Jan 12 '12 at 13:07
  • Come to think of it, what would be even nicer would be to say "treat all obj1's drag events as if they were obj2's events". I suspect that would require a Raphael hack, which is beyond me at present. – beldaz Jan 13 '12 at 06:28
  • +1 for explaining that Raphael's .data function is for storing arbitrary data, something Raphael's documentation fails to do. – user56reinstatemonica8 Feb 23 '12 at 18:03