6 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
9 * This file contains the VML renderers.
11 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
14 * http://github.com/sporritt/jsplumb
15 * http://code.google.com/p/jsplumb
17 * Dual licensed under the MIT and GPL2 licenses.
22 // http://ajaxian.com/archives/the-vml-changes-in-ie-8
23 // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/
24 // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
26 var vmlAttributeMap = {
27 "stroke-linejoin":"joinstyle",
28 "joinstyle":"joinstyle",
30 "miterlimit":"miterlimit"
32 jsPlumbStylesheet = null;
34 if (document.createStyleSheet && document.namespaces) {
37 ".jsplumb_vml", "jsplumb\\:textbox", "jsplumb\\:oval", "jsplumb\\:rect",
38 "jsplumb\\:stroke", "jsplumb\\:shape", "jsplumb\\:group"
40 rule = "behavior:url(#default#VML);position:absolute;";
42 jsPlumbStylesheet = document.createStyleSheet();
44 for (var i = 0; i < ruleClasses.length; i++)
45 jsPlumbStylesheet.addRule(ruleClasses[i], rule);
47 // in this page it is also mentioned that IE requires the extra arg to the namespace
48 // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
49 // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either.
50 // var iev = document.documentMode;
51 //if (!iev || iev < 8)
52 document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml");
54 // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML");
62 _getGroup = function(container, connectorClass) {
63 var id = jsPlumb.getId(container),
66 g = _node("group", [0,0,scale, scale], {"class":connectorClass});
67 //g.style.position=absolute;
68 //g["coordsize"] = "1000,1000";
69 g.style.backgroundColor="red";
71 //jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance.
72 //jsPlumb.CurrentLibrary.getDOMElement(container).appendChild(g);
73 //document.body.appendChild(g);
77 _atts = function(o, atts) {
79 // IE8 fix: setattribute does not work after an element has been added to the dom!
80 // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
81 //o.setAttribute(i, atts[i]);
83 /*There is an additional problem when accessing VML elements by using get/setAttribute. The simple solution is following:
85 if (document.documentMode==8) {
88 ele.setAttribute(‘opacity’,1);
95 _node = function(name, d, atts, parent, _jsPlumb, deferToJsPlumbContainer) {
97 var o = document.createElement("jsplumb:" + name);
98 if (deferToJsPlumbContainer)
99 _jsPlumb.appendElement(o, parent);
101 jsPlumb.CurrentLibrary.appendElement(o, parent);
102 o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml";
107 _pos = function(o,d, zIndex) {
108 o.style.left = d[0] + "px";
109 o.style.top = d[1] + "px";
110 o.style.width= d[2] + "px";
111 o.style.height= d[3] + "px";
112 o.style.position = "absolute";
114 o.style.zIndex = zIndex;
116 _conv = jsPlumb.vml.convertValue = function(v) {
117 return Math.floor(v * scale);
119 // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so,
120 // or 1 if not. TODO in the future, support variable opacity.
121 _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) {
122 if ("transparent" === styleToCheck)
123 component.setOpacity(type, "0.0");
125 component.setOpacity(type, "1.0");
127 _applyStyles = function(node, style, component, _jsPlumb) {
128 var styleToWrite = {};
129 if (style.strokeStyle) {
130 styleToWrite.stroked = "true";
131 var strokeColor = jsPlumbUtil.convertStyle(style.strokeStyle, true);
132 styleToWrite.strokecolor = strokeColor;
133 _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component);
134 styleToWrite.strokeweight = style.lineWidth + "px";
136 else styleToWrite.stroked = "false";
138 if (style.fillStyle) {
139 styleToWrite.filled = "true";
140 var fillColor = jsPlumbUtil.convertStyle(style.fillStyle, true);
141 styleToWrite.fillcolor = fillColor;
142 _maybeSetOpacity(styleToWrite, fillColor, "fill", component);
144 else styleToWrite.filled = "false";
146 if(style.dashstyle) {
147 if (component.strokeNode == null) {
148 component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style.dashstyle }, node, _jsPlumb);
151 component.strokeNode.dashstyle = style.dashstyle;
153 else if (style["stroke-dasharray"] && style.lineWidth) {
154 var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",",
155 parts = style["stroke-dasharray"].split(sep),
157 for(var i = 0; i < parts.length; i++) {
158 styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep);
160 if (component.strokeNode == null) {
161 component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse }, node, _jsPlumb);
164 component.strokeNode.dashstyle = styleToUse;
167 _atts(node, styleToWrite);
170 * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent.
172 VmlComponent = function() {
173 var self = this, renderer = {};
174 jsPlumb.jsPlumbUIComponent.apply(this, arguments);
176 this.opacityNodes = {
180 this.initOpacityNodes = function(vml) {
181 self.opacityNodes.stroke = _node("stroke", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb.instance);
182 self.opacityNodes.fill = _node("fill", [0,0,1,1], {opacity:"0.0"}, vml, self._jsPlumb.instance);
184 this.setOpacity = function(type, value) {
185 var node = self.opacityNodes[type];
186 if (node) node.opacity = "" + value;
188 var displayElements = [ ];
189 this.getDisplayElements = function() {
190 return displayElements;
193 this.appendDisplayElement = function(el, doNotAppendToCanvas) {
194 if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el);
195 displayElements.push(el);
198 jsPlumbUtil.extend(VmlComponent, jsPlumb.jsPlumbUIComponent, {
200 if (this.bgCanvas) jsPlumbUtil.removeElement(this.bgCanvas);
201 jsPlumbUtil.removeElement(this.canvas);
206 * Base class for Vml connectors. extends VmlComponent.
208 var VmlConnector = jsPlumb.ConnectorRenderers.vml = function(params) {
209 this.strokeNode = null;
211 VmlComponent.apply(this, arguments);
212 var clazz = this._jsPlumb.instance.connectorClass + (params.cssClass ? (" " + params.cssClass) : "");
213 this.paint = function(style) {
214 if (style !== null) {
216 // we need to be at least 1 pixel in each direction, because otherwise coordsize gets set to
217 // 0 and overlays cannot paint.
218 this.w = Math.max(this.w, 1);
219 this.h = Math.max(this.h, 1);
221 var segments = this.getSegments(), p = { "path":"" },
222 d = [this.x, this.y, this.w, this.h];
224 // create path from segments.
225 for (var i = 0; i < segments.length; i++) {
226 p.path += jsPlumb.Segments.vml.SegmentRenderer.getPath(segments[i]);
231 if (style.outlineColor) {
232 var outlineWidth = style.outlineWidth || 1,
233 outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
235 strokeStyle : jsPlumbUtil.convertStyle(style.outlineColor),
236 lineWidth : outlineStrokeWidth
238 for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa];
240 if (this.bgCanvas == null) {
242 p.coordsize = (d[2] * scale) + "," + (d[3] * scale);
243 this.bgCanvas = _node("shape", d, p, params.parent, this._jsPlumb.instance, true);
244 _pos(this.bgCanvas, d);
245 this.appendDisplayElement(this.bgCanvas, true);
246 this.attachListeners(this.bgCanvas, this);
247 this.initOpacityNodes(this.bgCanvas, ["stroke"]);
250 p.coordsize = (d[2] * scale) + "," + (d[3] * scale);
251 _pos(this.bgCanvas, d);
252 _atts(this.bgCanvas, p);
255 _applyStyles(this.bgCanvas, outlineStyle, this);
259 if (this.canvas == null) {
261 p.coordsize = (d[2] * scale) + "," + (d[3] * scale);
262 this.canvas = _node("shape", d, p, params.parent, this._jsPlumb.instance, true);
263 //var group = _getGroup(params.parent); // test of append everything to a group
264 //group.appendChild(self.canvas); // sort of works but not exactly;
265 //params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups
267 this.appendDisplayElement(this.canvas, true);
268 this.attachListeners(this.canvas, this);
269 this.initOpacityNodes(this.canvas, ["stroke"]);
272 p.coordsize = (d[2] * scale) + "," + (d[3] * scale);
273 _pos(this.canvas, d);
274 _atts(this.canvas, p);
277 _applyStyles(this.canvas, style, this, this._jsPlumb.instance);
282 jsPlumbUtil.extend(VmlConnector, VmlComponent, {
283 reattachListeners : function() {
284 if (this.canvas) this.reattachListenersForElement(this.canvas, this);
286 setVisible:function(v) {
288 this.canvas.style.display = v ? "block" : "none";
291 this.bgCanvas.style.display = v ? "block" : "none";
298 * Base class for Vml Endpoints. extends VmlComponent.
301 var VmlEndpoint = window.VmlEndpoint = function(params) {
302 VmlComponent.apply(this, arguments);
303 this._jsPlumb.vml = null;//, opacityStrokeNode = null, opacityFillNode = null;
304 this.canvas = document.createElement("div");
305 this.canvas.style.position = "absolute";
306 this._jsPlumb.clazz = this._jsPlumb.instance.endpointClass + (params.cssClass ? (" " + params.cssClass) : "");
308 // TODO vml endpoint adds class to VML at constructor time. but the addClass method adds VML
309 // to the enclosing DIV. what to do? seems like it would be better to just target the div.
310 // HOWEVER...vml connection has no containing div. why not? it feels like it should.
312 //var group = _getGroup(params.parent);
313 //group.appendChild(self.canvas);
314 params._jsPlumb.appendElement(this.canvas, params.parent);
316 this.paint = function(style, anchor) {
317 var p = { }, vml = this._jsPlumb.vml;
319 jsPlumbUtil.sizeElement(this.canvas, this.x, this.y, this.w, this.h);
320 if (this._jsPlumb.vml == null) {
321 p["class"] = this._jsPlumb.clazz;
322 vml = this._jsPlumb.vml = this.getVml([0,0, this.w, this.h], p, anchor, this.canvas, this._jsPlumb.instance);
323 this.attachListeners(vml, this);
325 this.appendDisplayElement(vml, true);
326 this.appendDisplayElement(this.canvas, true);
328 this.initOpacityNodes(vml, ["fill"]);
331 _pos(vml, [0,0, this.w, this.h]);
335 _applyStyles(vml, style, this);
338 jsPlumbUtil.extend(VmlEndpoint, VmlComponent, {
339 reattachListeners : function() {
340 if (this._jsPlumb.vml) this.reattachListenersForElement(this._jsPlumb.vml, this);
344 // ******************************* vml segments *****************************************************
346 jsPlumb.Segments.vml = {
348 getPath : function(segment) {
350 "Straight":function(segment) {
351 var d = segment.params;
352 return "m" + _conv(d.x1) + "," + _conv(d.y1) + " l" + _conv(d.x2) + "," + _conv(d.y2) + " e";
354 "Bezier":function(segment) {
355 var d = segment.params;
356 return "m" + _conv(d.x1) + "," + _conv(d.y1) +
357 " c" + _conv(d.cp1x) + "," + _conv(d.cp1y) + "," + _conv(d.cp2x) + "," + _conv(d.cp2y) + "," + _conv(d.x2) + "," + _conv(d.y2) + " e";
359 "Arc":function(segment) {
360 var d = segment.params,
361 xmin = Math.min(d.x1, d.x2),
362 xmax = Math.max(d.x1, d.x2),
363 ymin = Math.min(d.y1, d.y2),
364 ymax = Math.max(d.y1, d.y2),
365 sf = segment.anticlockwise ? 1 : 0,
366 pathType = (segment.anticlockwise ? "at " : "wa "),
367 makePosString = function() {
369 return "0,0," + _conv(2*d.r) + "," + _conv(2 * d.r);
373 [ function() { return [xmin, ymin ];}, function() { return [xmin - d.r, ymin - d.r ];}],
374 [ function() { return [xmin - d.r, ymin ];}, function() { return [xmin, ymin - d.r ];}],
375 [ function() { return [xmin - d.r, ymin - d.r ];}, function() { return [xmin, ymin ];}],
376 [ function() { return [xmin, ymin - d.r ];}, function() { return [xmin - d.r, ymin ];}]
377 ][segment.segment][sf]();
379 return _conv(xy[0]) + "," + _conv(xy[1]) + "," + _conv(xy[0] + (2*d.r)) + "," + _conv(xy[1] + (2*d.r));
382 return pathType + " " + makePosString() + "," + _conv(d.x1) + "," + _conv(d.y1) + "," + _conv(d.x2) + "," + _conv(d.y2) + " e";
385 })[segment.type](segment);
390 // ******************************* /vml segments *****************************************************
392 // ******************************* vml endpoints *****************************************************
394 jsPlumb.Endpoints.vml.Dot = function() {
395 jsPlumb.Endpoints.Dot.apply(this, arguments);
396 VmlEndpoint.apply(this, arguments);
397 this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("oval", d, atts, parent, _jsPlumb); };
399 jsPlumbUtil.extend(jsPlumb.Endpoints.vml.Dot, VmlEndpoint);
401 jsPlumb.Endpoints.vml.Rectangle = function() {
402 jsPlumb.Endpoints.Rectangle.apply(this, arguments);
403 VmlEndpoint.apply(this, arguments);
404 this.getVml = function(d, atts, anchor, parent, _jsPlumb) { return _node("rect", d, atts, parent, _jsPlumb); };
406 jsPlumbUtil.extend(jsPlumb.Endpoints.vml.Rectangle, VmlEndpoint);
409 * VML Image Endpoint is the same as the default image endpoint.
411 jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image;
414 * placeholder for Blank endpoint in vml renderer.
416 jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank;
418 // ******************************* /vml endpoints *****************************************************
420 // ******************************* vml overlays *****************************************************
423 * VML Label renderer. uses the default label renderer (which adds an element to the DOM)
425 jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label;
428 * VML Custom renderer. uses the default Custom renderer (which adds an element to the DOM)
430 jsPlumb.Overlays.vml.Custom = jsPlumb.Overlays.Custom;
433 * Abstract VML arrow superclass
435 var AbstractVmlArrowOverlay = function(superclass, originalArgs) {
436 superclass.apply(this, originalArgs);
437 VmlComponent.apply(this, originalArgs);
438 var self = this, path = null;
440 self.isAppendedAtTopLevel = true;
441 var getPath = function(d) {
442 return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) +
443 " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) +
444 " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) +
445 " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) +
448 this.paint = function(params, containerExtents) {
449 // only draws for connectors, not endpoints.
450 if (params.component.canvas && containerExtents) {
451 var p = {}, d = params.d, connector = params.component;
452 if (params.strokeStyle) {
454 p.strokecolor = jsPlumbUtil.convertStyle(params.strokeStyle, true);
456 if (params.lineWidth) p.strokeweight = params.lineWidth + "px";
457 if (params.fillStyle) {
459 p.fillcolor = params.fillStyle;
462 var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x),
463 ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y),
464 xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x),
465 ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y),
466 w = Math.abs(xmax - xmin),
467 h = Math.abs(ymax - ymin),
468 dim = [xmin, ymin, w, h];
470 // for VML, we create overlays using shapes that have the same dimensions and
471 // coordsize as their connector - overlays calculate themselves relative to the
472 // connector (it's how it's been done since the original canvas implementation, because
473 // for canvas that makes sense).
475 p.coordsize = (connector.w * scale) + "," + (connector.h * scale);
477 dim[0] = connector.x;
478 dim[1] = connector.y;
479 dim[2] = connector.w;
480 dim[3] = connector.h;
482 if (self.canvas == null) {
483 var overlayClass = connector._jsPlumb.overlayClass || "";
484 var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : "";
485 p["class"] = clazz + " " + overlayClass;
486 self.canvas = _node("shape", dim, p, connector.canvas.parentNode, connector._jsPlumb.instance, true);
487 connector.appendDisplayElement(self.canvas, true);
488 self.attachListeners(self.canvas, connector);
489 self.attachListeners(self.canvas, self);
492 _pos(self.canvas, dim);
493 _atts(self.canvas, p);
498 this.reattachListeners = function() {
499 if (self.canvas) self.reattachListenersForElement(self.canvas, self);
502 this.cleanup = function() {
503 if (self.canvas != null) jsPlumb.CurrentLibrary.removeElement(self.canvas);
506 jsPlumbUtil.extend(AbstractVmlArrowOverlay, [VmlComponent, jsPlumb.Overlays.AbstractOverlay], {
507 setVisible : function(state) {
508 this.canvas.style.display = state ? "block" : "none";
512 jsPlumb.Overlays.vml.Arrow = function() {
513 AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);
515 jsPlumbUtil.extend(jsPlumb.Overlays.vml.Arrow, [ jsPlumb.Overlays.Arrow, AbstractVmlArrowOverlay ]);
517 jsPlumb.Overlays.vml.PlainArrow = function() {
518 AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);
520 jsPlumbUtil.extend(jsPlumb.Overlays.vml.PlainArrow, [ jsPlumb.Overlays.PlainArrow, AbstractVmlArrowOverlay ]);
522 jsPlumb.Overlays.vml.Diamond = function() {
523 AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);
525 jsPlumbUtil.extend(jsPlumb.Overlays.vml.Diamond, [ jsPlumb.Overlays.Diamond, AbstractVmlArrowOverlay ]);
527 // ******************************* /vml overlays *****************************************************