Question

I'm writing a HTML5 diagramming application using qooxdoo for object system and widget toolkit, and RaphaelJS as a drawing backend. The data model for the diagram contains high-level objects like Item, Line, etc.; these are implemented as qooxdoo classes with properties for position, dimensions, color and other data. Each class is capable of rendering its instances to Raphael paper, say, using render() method. At this moment, a visual ("element" in Raphael's terms) is created.

The problem is, some properties should be set before Raphael visual is created. In Raphael, you can't draw a circle without providing its center coordinates and radius; you can't create a path without path definition; you can't create a text label without actual text, and so on. Furthermore, some properties can be set only after the visual is created: you can't set color, stroke style etc. for a non-existing visual. So we can imagine the following workflow:

var circle = new my.Circle();
circle.set({ x: 10, y: 20, r: 30 }); // can't set color here - no visual yet
circle.render(paper);
circle.set({ stroke: "red", strokeWidth: 5 });

OK, we can control this workflow if we create objects manually. But if the whole scene graph is unmarshalled from JSON (to load saved diagram), there is no control over the sequence of invocations, and all properties would be set at once. That's why my Circle class contains the following in its members section:

// Setter for stroke
_applyStroke: function(val, old) {
 this.element && this.element.attr({ stroke: val });
}
// The same for fill, stroke width, stroke style, arrowhead style etc.
// ...
render: function(paper) {
 this.element = paper.circle(this.getX(), this.getY(), this.getR());
 this._applyStroke(this.getStroke());
 this._applyStrokeWidth(this.getStrokeWidth());
 // repeat for each style property
}

Is there any way to the same with less boilerplate? I was thinking about creating dummy Raphael "elements" to accept style attributes before an actual element is created, and, after its creation, to commit dummy attributes to the actual element. But this approach seems to require many changes to the existing code. I'm wondering if there is more elegant way to accomplish this? AOP-based solutions are acceptable, since AOP works great in qooxdoo.

Was it helpful?

Solution

I guess the answer depends on a couple of constraints regarding RafaelJS which I don't know well enough.

  1. If that is ok in your app you could create the element in the my.Circle constructor, with some default values for x, y and r. You would then let the normal property apply methods propagate to the element attributes. (This would sort of defeat an explicit render() method).

  2. If that would render the element too early, and display all the changes to its attributes (which you might want to avoid), I would look for a hide/display-type control of the element. Maybe you can build and change an element when still hidden. The render() method would then just toggle the visibility.

  3. If this all doesn't work, and you are forced to create the element in the render() method for some reason, there is probably no way around some kind of little protocol:

    • Create the element in the render() method with whatever values you have for the mandatory attributes (e.g. x, y and r), be they init values or user-defined.

    • The apply methods that belong to properties that can only be set on existing elements could write their changes to an internal queue (probably just a list of attribute maps), instead of writing directly to the element. This way you capture all "premature" property changes.

    • The render() method then just applies the elements in the queue. This would implement the "defered property application" you mention in the question title. The downside would be that all further property changes would only take effect with the next render() call (avoiding element creation this time).

    • Of course you could make a combination of that and what you have already, writing to the queue when the element is not yet created and modifying it directly otherwise.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top