1 (function(global){ // BEGIN CLOSURE 2 3 var Joint = global.Joint; 4 5 var point = Joint.point; 6 var rect = Joint.rect; 7 8 /** 9 * @name Joint.dia 10 * @namespace Holds functionality related to all diagrams and their elements. 11 */ 12 var dia = Joint.dia = { 13 /** 14 * Current dragged object. 15 * @private 16 */ 17 _currentDrag: false, 18 /** 19 * Current zoomed object. 20 * @private 21 */ 22 _currentZoom: false, 23 /** 24 * Table with all registered objects. 25 * - registered objects can embed and can be embedded 26 * - the table is of the form: {RaphaelPaper1: [shape1, shape2, ...]} 27 * @private 28 */ 29 _registeredObjects: {}, 30 /** 31 * Table whith all registered joints. 32 * - the table is of the form: {RaphaelPaper1: [joint1, joint2, ...]} 33 * @private 34 */ 35 _registeredJoints: {}, 36 /** 37 * Create new joint and register it. All joints appearing in a diagram should 38 * be created using this function. Otherwise they won't be registered and 39 * therefore not serialized when needed. 40 * @param {Object} args Joint parameters. 41 * @see Joint 42 * @return {Joint} 43 */ 44 Joint: function(args){ 45 var j = Joint.apply(null, arguments); 46 this.registerJoint(j); 47 return j; 48 }, 49 /** 50 * Returns registered elements of the current paper. 51 * @return {array} Array of registered elements. 52 */ 53 registeredElements: function(){ 54 return (this._registeredObjects[Joint.paper().euid()] || (this._registeredObjects[Joint.paper().euid()] = [])); 55 }, 56 /** 57 * Returns registered joints of the current paper. 58 * @return {array} Array of registered joints. 59 */ 60 registeredJoints: function(){ 61 return (this._registeredJoints[Joint.paper().euid()] || (this._registeredJoints[Joint.paper().euid()] = [])); 62 }, 63 /** 64 * Register object to the current paper. 65 * You don't have to use this method unless you really know what you're doing. 66 * @param {Element|Joint} obj Object to be registered. 67 * @return {Element|Joint} Registered object. 68 */ 69 register: function(obj){ 70 (this._registeredObjects[Joint.paper().euid()] || (this._registeredObjects[Joint.paper().euid()] = [])).push(obj); 71 }, 72 /** 73 * Cancel registration of an element in the current paper. 74 * @param {Element} obj Object to be unregistered. 75 */ 76 unregister: function(obj){ 77 var register = (this._registeredObjects[Joint.paper().euid()] || (this._registeredObjects[Joint.paper().euid()] = [])), 78 idx = register.length; 79 while (idx--) 80 if (register[idx] === obj) 81 register.splice(idx, 1); 82 }, 83 /** 84 * Register joint to the current paper. Avoid registering the the same joint twice. 85 * You don't have to use this method unless you really know what you're doing. 86 * @param {Joint} j Joint object to be registered. 87 */ 88 registerJoint: function(j){ 89 (this._registeredJoints[Joint.paper().euid()] || (this._registeredJoints[Joint.paper().euid()] = [])).push(j); 90 }, 91 /** 92 * Cancel registration of a joint in the current paper. 93 * @param {Joint} j Joint to be unregistered. 94 */ 95 unregisterJoint: function(j){ 96 var register = (this._registeredJoints[Joint.paper().euid()] || (this._registeredJoints[Joint.paper().euid()] = [])), 97 idx = register.length; 98 while (idx--) 99 if (register[idx] === j) 100 register.splice(idx, 1); 101 } 102 }; 103 104 /** 105 * Abstract object of all diagram elements. 106 * This object is never used directly, instead, specific diagram elements inherits from it. 107 * Allows easy creation of new specific diagram elements preserving all features that Joint library and Joint.dia plugin offer. 108 * <h3>Wrapper</h3> 109 * All custom elements must have a wrapper set. Wrapper is the key object that Joint library counts with. 110 * There cannot be any element without a wrapper. Usually it is an object which wraps all the subelements 111 * that a specific diagram element contains. The wrapper must be set in init method. 112 * To set a wrapper, use setWrapper(aWrapper) method. The single parameter to the method is a Raphaël vector object. 113 * Later on, you can access this object using wrapper property. 114 * <h3>Inner</h3> 115 * Inner objects are subelements of an element. Although they are optional, they are commonly used. To add a subelement 116 * to the element, use addInner(anInner) method. It takes a Raphaël vector object as an argument. All inner objects are 117 * placed to an array that you can access using inner property. 118 * <h3><i>init</i> method</h3> 119 * The <i>init</i> method has to be part of every element you create. It takes all element options as an argument, 120 * sets wrapper and adds inners. 121 * <h3><i>joint</i> method</h3> 122 * If you have specific elements, in which connections are not controlled by wrapper, you can implement your own joint method. 123 * <h3><i>zoom</i> method</h3> 124 * As Joint.dia library does not know how your specific element should behave after scaling, you can use zoom method to implement 125 * the desired behaviour. 126 * @name Element 127 * @memberOf Joint.dia 128 * @constructor 129 * @example 130 var mydia = Joint.dia.mydia = {}; 131 var Element = Joint.dia.Element; 132 133 mydia.MyElement = Element.extend({ 134 // init method must be always presented 135 init: function(properties){ 136 var p = this.properties; 137 // parameters processing 138 p.position = properties.position; 139 p.radius = properties.radius || 30; 140 // every element must have a wrapper 141 this.setWrapper(this.paper.circle(p.position.x, p.position.y, p.radius)); 142 // optional inner elements 143 this.addInner(this.paper.text(p.position.x, p.position.y, "my element")); 144 } 145 }); 146 147 // ... 148 149 var e = mydia.MyElement.create({ 150 position: {x: 50, y: 50}, 151 radius: 20 152 }); 153 */ 154 var Element = dia.Element = function(){}; 155 156 /** 157 * Use this to instantiate particular elements. 158 * @private 159 */ 160 Element.create = function(properties){ 161 var instance = new this(properties); 162 if (instance.init) instance.init(properties); 163 instance.defaults(instance.properties); 164 instance.paper.safari(); // fix webkit bug 165 return instance; 166 }; 167 168 /** 169 * @private 170 */ 171 Element.extend = function(prototype){ 172 var C = prototype.constructor = function(properties){ 173 this.construct(properties); 174 }; 175 C.base = this; 176 var proto = C.prototype = new this(); 177 Joint.Mixin(proto, prototype); 178 Joint.Supplement(C, this); 179 return C; 180 }; 181 182 Element.prototype = { 183 parentElement: null, 184 toolbox: null, 185 _isElement: true, 186 // auxiliaries for scaling and translating 187 lastScaleX: 1.0, 188 lastScaleY: 1.0, 189 dx: undefined, 190 dy: undefined, 191 // original bounding box (before scaling a translating) 192 // set in setWrapper() 193 origBBox: undefined, 194 195 construct: function(properties){ 196 this.properties = { 197 dx: 0, dy: 0, // translation 198 rot: 0, // rotation 199 sx: 1.0, sy: 1.0, // scale 200 module: this.module, 201 object: this.object, 202 parent: properties.parent 203 }; 204 this.wrapper = null; 205 this.shadow = null; 206 this.shadowAttrs = { 207 stroke: 'none', 208 fill: '#999', 209 translation: '7,7', 210 opacity: 0.5 211 }; 212 this.inner = []; 213 // ghost attributes 214 this.ghostAttrs = { 215 opacity: 0.5, 216 "stroke-dasharray": "-", 217 stroke: "black" 218 }; 219 this._opt = { 220 draggable: true, // enable dragging? 221 ghosting: false, // enable ghosting? 222 toolbox: false // enable toolbox? 223 }; 224 225 this.paper = Joint.paper(); 226 dia.register(this); // register me in the global table 227 }, 228 defaults: function(properties) { 229 if (properties.shadow) { 230 Joint.Mixin(this.shadowAttrs, properties.shadow); 231 this.createShadow(); 232 } 233 }, 234 /** 235 * @methodOf Joint.dia.Element# 236 * @return Element unique id. 237 */ 238 euid: function(){ 239 return Joint.generateEuid.call(this); 240 }, 241 // this is needed in joint library when 242 // manipulating with a raphael object joints array 243 // - just delegate joints array methods to the wrapper 244 joints: function(){ 245 return this.wrapper.joints(); 246 }, 247 248 /** 249 * Used in joint.js for unified access to the wrapper. 250 * For all RaphaelObjects returns just this. 251 * @private 252 * @return {RaphaelObject} Return wrapper. 253 */ 254 yourself: function(){ 255 return this.wrapper; 256 }, 257 258 updateJoints: function(){ 259 var joints = this.wrapper.joints(); 260 if (joints){ 261 for (var i = 0, l = joints.length; i < l; i++){ 262 joints[i].update(); 263 } 264 } 265 }, 266 267 /** 268 * Toggle ghosting of the element. 269 * Dragging a diagram object causes moving of the wrapper and all inners, and update 270 * of all correspondent connections. It can be sometimes expensive. If your elements 271 * are complex and you want to prevent all this rendering and computations, 272 * you can enable ghosting. It means that only a ghost of your wrapper will be dragged. 273 * @methodOf Joint.dia.Element# 274 * @return {Element} 275 */ 276 toggleGhosting: function(){ 277 this._opt.ghosting = !this._opt.ghosting; 278 return this; 279 }, 280 281 /** 282 * Create a ghost shape which is used when dragging. 283 * (in the case _opt.ghosting is enabled) 284 * @private 285 */ 286 createGhost: function(){ 287 this.ghost = this.cloneWrapper(this.ghostAttrs); 288 }, 289 290 /** 291 * Create a shadow. 292 * @private 293 */ 294 createShadow: function(){ 295 this.shadowAttrs.rotation = this.wrapper.attrs.rotation; 296 this.shadow = this.cloneWrapper(this.shadowAttrs); 297 this.shadow.toBack(); 298 }, 299 300 /** 301 * Creates the same object as the wrapper is. 302 * Used for ghosting and shadows. 303 * @private 304 * @return {RaphaelObject} created clone 305 */ 306 cloneWrapper: function(attrs) { 307 var wa = this.wrapper.attrs, 308 paper = this.wrapper.paper, 309 clone; 310 311 switch (this.wrapper.type) { 312 case "rect": 313 clone = paper.rect(wa.x, wa.y, wa.width, wa.height, wa.r); 314 break; 315 case "circle": 316 clone = paper.circle(wa.cx, wa.cy, wa.r); 317 break; 318 case "ellipse": 319 clone = paper.ellipse(wa.cx, wa.cy, wa.rx, wa.ry); 320 break; 321 default: 322 break; 323 } 324 clone.attr(attrs); 325 return clone; 326 }, 327 328 /** 329 * Get object position. 330 * @private 331 * @return point 332 */ 333 objPos: function(objname){ 334 switch (this[objname].type){ 335 case "rect": 336 return point(this[objname].attr("x"), this[objname].attr("y")); 337 case "circle": 338 case "ellipse": 339 return point(this[objname].attr("cx"), this[objname].attr("cy")); 340 default: 341 break; 342 } 343 }, 344 345 wrapperPos: function(){ 346 return this.objPos("wrapper"); 347 }, 348 ghostPos: function(){ 349 return this.objPos("ghost"); 350 }, 351 352 /** 353 * Sends the wrapper and all inners to the front. 354 * @methodOf Joint.dia.Element# 355 * @return {Element} 356 */ 357 toFront: function(){ 358 this.shadow && this.shadow.toFront(); 359 this.wrapper && this.wrapper.toFront(); 360 for (var i = 0, len = this.inner.length; i < len; i++) 361 this.inner[i].toFront(); 362 return this; 363 }, 364 365 /** 366 * Sends the wrapper and all inners to the back. 367 * @methodOf Joint.dia.Element# 368 * @return {Element} 369 */ 370 toBack: function(){ 371 for (var i = this.inner.length - 1; i >= 0; --i) 372 this.inner[i].toBack(); 373 this.wrapper && this.wrapper.toBack(); 374 this.shadow && this.shadow.toBack(); 375 return this; 376 }, 377 378 /** 379 * dia.Element mousedown event. 380 * @private 381 */ 382 dragger: function(e){ 383 if (!this.wholeShape._opt.draggable) return; 384 dia._currentDrag = this.wholeShape; 385 if (dia._currentDrag._opt.ghosting){ 386 dia._currentDrag.createGhost(); 387 dia._currentDrag.ghost.toFront(); 388 } else 389 dia._currentDrag.toFront(); 390 391 dia._currentDrag.removeToolbox(); 392 // small hack to get the connections to front 393 dia._currentDrag.translate(1,1); 394 395 dia._currentDrag.dx = e.clientX; 396 dia._currentDrag.dy = e.clientY; 397 e.preventDefault && e.preventDefault(); 398 }, 399 400 /** 401 * dia.Element zoom tool mousedown event. 402 * @private 403 */ 404 zoomer: function(e){ 405 dia._currentZoom = this; 406 dia._currentZoom.toFront(); 407 dia._currentZoom.removeToolbox(); 408 409 var bb = rect(dia._currentZoom.origBBox); 410 dia._currentZoom.dx = e.clientX; 411 dia._currentZoom.dy = e.clientY; 412 dia._currentZoom.dWidth = bb.width * dia._currentZoom.lastScaleX; 413 dia._currentZoom.dHeight = bb.height * dia._currentZoom.lastScaleY; 414 415 e.preventDefault && e.preventDefault(); 416 }, 417 /** 418 * Move the element by offsets. 419 * @methodOf Joint.dia.Element# 420 * @param {Number} dx Offset in x-axis. 421 * @param {Number} dy Offset in y-axis. 422 */ 423 translate: function(dx, dy){ 424 // save translation 425 this.properties.dx += dx; 426 this.properties.dy += dy; 427 // translate wrapper, all inner and toolbox 428 this.wrapper.translate(dx, dy); 429 this.shadow && this.shadow.translate(dx, dy); 430 for (var i = this.inner.length - 1; i >= 0; --i){ 431 this.inner[i].translate(dx, dy); 432 } 433 this.translateToolbox(dx, dy); 434 this.paper.safari(); 435 }, 436 437 /** 438 * Add wrapper. 439 * @methodOf Joint.dia.Element# 440 * @param {RaphaelObject} s Vector object specifying a wrapper. 441 */ 442 setWrapper: function(s){ 443 this.wrapper = s; // set wrapper 444 this.wrapper.wholeShape = this; // set wrapper's reference to me 445 this.type = this.wrapper.type; // set my type 446 this.origBBox = this.wrapper.getBBox(); // save original bounding box 447 // if dragging enabled, register mouse down event handler 448 if (this._opt && this._opt.draggable){ 449 this.wrapper.mousedown(this.dragger); 450 this.wrapper.node.style.cursor = "move"; 451 } 452 // make sure wrapper has the joints method 453 if (!this.wrapper.joints){ 454 this.wrapper._joints = []; 455 this.wrapper.joints = function(){ return this._joints; }; 456 } 457 // add toolbox if enabled 458 this.addToolbox(); 459 return this; 460 }, 461 462 /** 463 * Add a subelement. 464 * @methodOf Joint.dia.Element# 465 * @param {Element} s The subelement to be added. 466 * @return {Element} this 467 */ 468 addInner: function(s){ 469 this.inner.push(s); 470 // @remove one of them? 471 s.wholeShape = this; 472 s.parentElement = this; 473 if (s._isElement) s.properties.parent = this.euid(); 474 // if dragging enabled, register mouse down event handler 475 if (!s._isElement && this._opt && this._opt.draggable){ 476 s.mousedown(this.dragger); 477 s.node.style.cursor = "move"; 478 } 479 s.toFront(); // always push new inner to the front 480 return this; 481 }, 482 483 /** 484 * Remove a subelement. 485 * @methodOf Joint.dia.Element# 486 * @param {Element} s The subelement to be removed. 487 * @return {Element} this 488 */ 489 delInner: function(s){ 490 var 491 i = 0, 492 len = this.inner.length; 493 for (; i < len; i++) 494 if (this.inner[i] == s) 495 break; 496 if (i < len){ 497 this.inner.splice(i, 1); 498 s.parentElement = null; 499 if (s._isElement) s.properties.parent = undefined; 500 } 501 return this; 502 }, 503 504 /** 505 * Show toolbox. 506 * @private 507 */ 508 addToolbox: function(){ 509 // do not show toolbox if it is not enabled 510 if (!this._opt.toolbox){ 511 return this; 512 } 513 514 var 515 self = this, 516 bb = this.wrapper.getBBox(), // wrapper bounding box 517 tx = bb.x - 10, // toolbox x position 518 ty = bb.y - 22; // toolbox y position 519 520 this.toolbox = []; 521 this.toolbox.push(this.paper.rect(tx, ty, 33, 22, 5).attr({fill: "white"})); 522 // zoom in/out (mint icon: search.png) 523 this.toolbox.push(this.paper.image("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAE5SURBVHjaYvz//z8DsQAggFhARGRkpBETE1M/kGkOxIz//v078+HDh4odO3acBPJ//4eaCBBADCA6Kirq4JlzJ978/vPrNwifOHX4fUhIyFmgvDQQs4HUgDBAALFAbTDX1zNiZmFmBfONDM14WFlZdYFMCSD+AsS/QOIAAcQEVcyIw5m8IJNhHIAAAisGufHMuZNfgE74A8Knzx7/LiLO91tfXx9kOgsjEIDUAQQQ2FqQZ3q7Jk6AWs2gqCbOkZDn8l9AiLuNi4vrxfHjx7cC1X8HCCCwYqiv/aBu5NXQ0FD9+/dfr4uf/te7N1/Mu337ttmbN2/uAwQQzIO/gfg11DNsN4BA/LD4n8f33swF8v8DFQoAaS6AAGLEFilQN3JCbQLhH0B8HyCAGHHFIFQDB1QTSNEXgAADAEQ2gYZ9CcycAAAAAElFTkSuQmCC", tx, ty, 11, 11)); 524 this.toolbox[this.toolbox.length-1].toFront(); 525 Joint.addEvent(this.toolbox[this.toolbox.length-1].node, "mousedown", function(e){ 526 dia.Element.prototype.zoomer.apply(self, [e]); 527 }); 528 // embed (mint icon: page_spearmint_up.png) 529 this.toolbox.push(this.paper.image("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAEJSURBVHjaYvj//z8DFGOAnz9/rjl27Jg0AwMDExAzAAQQI0ghFPz/8usZjM3ACJTnYBEC0iyfmZmZZYBCXwECiAkm+evXL4bff34w/P33C4z//PvB8O33awYmJiZeoDQ/ELMBBBALSKGJiQkPOzs7AxsbC8OaTXMZWFhZoEb8g5nFDsTMAAHEBFIIZLwCuo/hy5dvDCF+yQx/fv+BuAvhRDAACCCQM0AO5YRJfv78lSE+Ko/h79+/DP8RJoMBQACheHDv4wYGdOAs28DAyMioCmS+AAggJgYSAEAAoZiMUxHUZIAAYkES4AJSQjD3o4HvQPwXIIDgJgMVM4PCEhREWBT/BUUFQIABAMuFbgea+o0EAAAAAElFTkSuQmCC", tx + 22, ty, 11, 11)); 530 this.toolbox[this.toolbox.length-1].toFront(); 531 this.toolbox[this.toolbox.length-1].node.onclick = function(){ self.embed(); }; 532 // unembed (mint icon: page_spearmint_down.png) 533 this.toolbox.push(this.paper.image("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAEJSURBVHjaYvj//z8DFGOAnz9/rjl27Jg0AwMDExAzAAQQI0ghFPz/8usZjM3ACJTnYBEC0iyfmZmZZYBCXwECiIkBCfz99wuO//z7wfDt92sGJiYmXqAUPxCzAQQQi4mJyX0gQwFZExcXJ8OaTXMYODmZYULsQMwMEEAgk9WB+D0jIyNElJ2NYdXG2QzsHOwMSE4EA4AAYjpz5swvIC3By8sLVrh2yzygiRwQTzD8Q1EMEEBwD/779+//7gcNDCysKN5gcJZtYADaqgpkvgAIILgM0CMYCtEBQAChBB1ORVCTAQKIBUmAC0gJATEnFvXfQSELEEBwk4GKQeHEBgoiLIr/AvEvgAADAH4mYO9cg5S2AAAAAElFTkSuQmCC", tx + 11, ty, 11, 11)); 534 this.toolbox[this.toolbox.length-1].toFront(); 535 this.toolbox[this.toolbox.length-1].node.onclick = function(){ self.unembed(); }; 536 // delete (icon: stop.png) 537 // this.toolbox.push(this.paper.image("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oFEBQbDFwnwRsAAAE8SURBVBjTVZG9agJREEbP1TWL266wja2tWggipEhpIxh9gIUgiIW1vZWvkHJJHVLYig+ghWARbGzEYgMKrojr/t4UNwoZmGY4882BEfyVHA5HmOaEXA6khCSB83nK4fAmHOcAoAFI2+7RaIwxTQhDiGO1cLu1WK3egS6AkIPBiFptjGU9kc3Cfg++D4WCSg8CyWLxRRD0MxjGBMNQYLMJlQoUi9BuQ6kEx6PAMDrAs4aUcL3C5QLLJVSrUC6D68J8Duez0gIySKk8fV8ppCnoOux24HkQRUoH0EhTNTBNpeG6CqzX4XSC2eyRrBEEUzyvha7Deq1Oe54CXVcFxfE3sBXStgsYxjuW9UqaCsJQAfcOwx/i+EU4zkY8ntLrfZLPdwB1NklUYpJ0heNsHk8BIIr6RNEH/2t7BwF+AeKFndSgPkjIAAAAAElFTkSuQmCC", tx + 11, ty + 11, 11, 11)); 538 this.toolbox.push(this.paper.path("M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248").attr({fill: "#000", stroke: "none"}).translate(tx, ty).scale(0.5)); 539 this.toolbox[this.toolbox.length-1].toFront(); 540 this.toolbox[this.toolbox.length-1].node.onclick = function(){ self.remove(); }; 541 // clone (mint icon: sound_grey.png) 542 this.toolbox.push(this.paper.image("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAEjSURBVHjaYvz//z8DsQAggJjwSaanpwsBMReMDxBATAQMO/zv379eRkZGdiBmAgggJiymqaWlpS0GSrIAFZ4A0h5AYR4gZgEIICaoAg6ggolACea/f/9aAulAoDD3169fNwPZ0kA2B0gxQADBTBYECuYCaa7bt2/vACkEYs4zZ84cA9KsQAwKBUaAAGIBqfzz5w8jExPTRiCTXUFBwQ9IfwP5x8TExAJI/4IpBgggsOJ58+Y9B1JRQMwGdOdjoFP2ghRwcnL6A4P2KUghiA8QQGDFQIH/QGf8BDJ/L1myZC8fHx/IeiZmZmbr379/H4ApBgggFlgoANX/A1L/gJoYP336BHIG47Nnz1zu3r0LUvgD5FqAAGLEF4Og0EHy4G+AAAMAho1gqqugDLgAAAAASUVORK5CYII=", tx, ty + 11, 11, 11)); 543 this.toolbox[this.toolbox.length-1].toFront(); 544 this.toolbox[this.toolbox.length-1].node.onmousedown = function(){ dia._currentDrag = self.clone()[0]; console.log(dia._currentDrag[0])}; 545 // toolbox wrapper 546 return this; 547 }, 548 549 /** 550 * Hide (remove) toolbox. 551 * @todo Will be public after it is properly tested. 552 * @private 553 */ 554 removeToolbox: function(){ 555 if (this.toolbox) 556 for (var i = this.toolbox.length - 1; i >= 0; --i) 557 this.toolbox[i].remove(); 558 this.toolbox = null; 559 return this; 560 }, 561 562 /** 563 * Show/hide toolbox. 564 * @todo Will be public after it is properly tested. 565 * @private 566 */ 567 toggleToolbox: function(){ 568 this._opt.toolbox = !this._opt.toolbox; 569 if (this._opt.toolbox){ 570 this.addToolbox(); 571 } else { 572 this.removeToolbox(); 573 } 574 return this; 575 }, 576 577 /** 578 * Move toolbox by offset (dx, dy). 579 * @private 580 */ 581 translateToolbox: function(dx, dy){ 582 if (this.toolbox) 583 for (var i = this.toolbox.length - 1; i >= 0; --i) 584 this.toolbox[i].translate(dx, dy); 585 }, 586 587 /** 588 * Disconnects element from all joints. Empties the element joints array. 589 * Note that it preserves registration of the element in its joints. 590 * @methodOf Joint.dia.Element# 591 */ 592 disconnect: function(){ 593 var joints = this.joints(), idx = joints.length, j; 594 while (idx--){ 595 j = joints[idx]; 596 597 if (j.endObject().wholeShape === this){ 598 j.freeJoint(j.endObject()); 599 j.draw(["dummyEnd"]); 600 j.update(); 601 } 602 if (j.startObject().wholeShape === this){ 603 j.freeJoint(j.startObject()); 604 j.draw(["dummyStart"]); 605 j.update(); 606 } 607 } 608 }, 609 610 /** 611 * Unregister the element from its joints registeredObjects. 612 * After the call, the element is not registered in any of its joints. 613 * @private 614 */ 615 unregisterFromJoints: function(){ 616 var joints = this.joints(), idx = joints.length; 617 while (idx--) joints[idx].unregister(this); 618 return this; 619 }, 620 621 /** 622 * Remove element. 623 * @methodOf Joint.dia.Element# 624 * @return {null} 625 */ 626 remove: function(){ 627 var inners = this.inner, idx = inners.length; 628 this.unregisterFromJoints(); 629 this.disconnect(); 630 this.removeToolbox(); 631 this.unembed(); 632 while (idx--) inners[idx].remove(); 633 this.wrapper.remove(); 634 dia.unregister(this); 635 this.removed = true; 636 return null; 637 }, 638 639 /** 640 * Remove element and all joints pointing from and to this element. 641 * @methodOf Joint.dia.Element# 642 * @return {null} 643 */ 644 liquidate: function(){ 645 var joints = this.joints(), idx = joints.length, j, inners = this.inner; 646 // remove joints 647 while (idx--){ 648 j = joints[idx]; 649 j.freeJoint(j.startObject()); 650 j.freeJoint(j.endObject()); 651 j.clean(["connection", "startCap", "endCap", "handleStart", "handleEnd", "label"]); 652 dia.unregisterJoint(j); 653 j.unregister(this); 654 } 655 this.removeToolbox(); 656 this.unembed(); 657 // liquidate subelements 658 idx = inners.length; 659 while (idx--){ 660 if (inners[idx].liquidate) inners[idx].liquidate(); 661 else inners[idx].remove(); 662 } 663 this.wrapper.remove(); 664 dia.unregister(this); 665 this.removed = true; 666 return null; 667 }, 668 669 /** 670 * Enable/disable dragging of the element. 671 * @methodOf Joint.dia.Element# 672 * @param {boolean} enable True/false. 673 * @return {Element} Return this. 674 */ 675 draggable: function(enable){ 676 this._opt.draggable = enable; 677 this.wrapper.node.style.cursor = enable ? "move" : null; 678 var idx = this.inner.length; 679 while (idx--) this.inner[idx].node.style.cursor = enable ? "move" : null; 680 return this; 681 }, 682 683 /** 684 * Highlights the element. 685 * Override in inherited objects or @todo set in options. 686 * @methodOf Joint.dia.Element# 687 * @return {Element} Return this. 688 */ 689 highlight: function(){ 690 this.wrapper.attr("stroke", "red"); 691 return this; 692 }, 693 694 /** 695 * Unhighlights the element. 696 * @methodOf Joint.dia.Element# 697 * @return {Element} Return this. 698 */ 699 unhighlight: function(){ 700 this.wrapper.attr("stroke", this.properties.attrs.stroke || "#000"); 701 return this; 702 }, 703 704 /** 705 * Embed me into the first registered dia.Element whos bounding box 706 * contains my bounding box origin. Both elements will behave as a whole. 707 * @todo It is probably out of date. Retest!!! 708 * @methodOf Joint.dia.Element# 709 * @return {Element} 710 */ 711 embed: function(){ 712 var 713 ros = dia._registeredObjects[this.paper.euid()], 714 myBB = rect(this.wrapper.getBBox()), 715 embedTo = null; 716 717 // for all registered objects (sharing the same raphael paper) 718 for (var i = 0, len = ros.length; i < len; i++){ 719 var 720 shape = ros[i], 721 shapeBB = rect(shape.getBBox()); 722 723 // does shape contain my origin point? 724 if (shapeBB.containsPoint(myBB.origin())) 725 embedTo = shape; // if yes, save the shape 726 727 if (shape == this.parentElement){ 728 shape.delInner(this); 729 730 // just for optimization, a shape can be a subshape of 731 // only one shape, so if I have been deleted from my parent, 732 // I am free, and further, if I know where to embed -> do not search deeper 733 if (embedTo) break; 734 } 735 } 736 737 // embed if possible 738 embedTo && embedTo.addInner(this); 739 return this; 740 }, 741 742 /** 743 * Decouple embedded element from its parent. 744 * @methodOf Joint.dia.Element# 745 * @return {Element} 746 */ 747 unembed: function(){ 748 if (this.parentElement){ 749 this.parentElement.delInner(this); 750 this.parentElement = null; 751 this.properties.parent = undefined; 752 } 753 return this; 754 }, 755 756 /** 757 * Scale element. 758 * @methodOf Joint.dia.Element# 759 * @param {Number} sx Scale in x-axis. 760 * @param {Number} &optional sy Scale in y-axis. 761 * @example e.scale(1.5); 762 */ 763 scale: function(sx, sy){ 764 // save translation 765 this.properties.sx = sx; 766 this.properties.sy = sy; 767 768 this.shadow && this.shadow.scale.apply(this.shadow, arguments); 769 this.wrapper.scale.apply(this.wrapper, arguments); 770 this.zoom.apply(this, arguments); 771 // apply scale to all subshapes that are Elements (were embeded) 772 for (var i = 0, len = this.inner.length; i < len; i++){ 773 var inner = this.inner[i]; 774 if (inner._isElement){ 775 inner.scale.apply(inner, arguments); 776 } 777 } 778 if (this._doNotRedrawToolbox) return; 779 this.removeToolbox(); 780 this.addToolbox(); 781 }, 782 /** 783 * This method should be overriden by inherited elements to implement 784 * the desired scaling behaviour. 785 * @methodOf Joint.dia.Element# 786 * @param {Number} sx Scale in x-axis. 787 * @param {Number} &optional sy Scale in y-axis. 788 */ 789 zoom: function(sx, sy){ 790 // does nothing, overriden by specific elements 791 }, 792 793 /** 794 * @methodOf Joint.dia.Element# 795 * @return {Object} Bounding box of the element. 796 */ 797 getBBox: function(){ 798 return this.wrapper.getBBox(); 799 }, 800 801 /** 802 * @see Joint 803 * @methodOf Joint.dia.Element# 804 */ 805 joint: function(to, opt){ 806 var toobj = (to._isElement) ? to.wrapper : to, 807 j = this.wrapper.joint.apply(this.wrapper, [toobj, opt]); 808 Joint.dia.registerJoint(j); 809 return j; 810 }, 811 812 /** 813 * Delegate attr message to my wrapper. 814 * @private 815 */ 816 attr: function(){ 817 return Raphael.el.attr.apply(this.wrapper, arguments); 818 } 819 }; 820 821 822 /** 823 * Document mousemove event. 824 * @private 825 */ 826 Element.mouseMove = function(e){ 827 e = e || window.event; 828 // object dragging 829 if (dia._currentDrag){ 830 if (dia._currentDrag._opt.ghosting) // if ghosting, move ghost 831 dia._currentDrag.ghost.translate(e.clientX - dia._currentDrag.dx, e.clientY - dia._currentDrag.dy); 832 else // otherwise, move the whole shape 833 dia._currentDrag.translate(e.clientX - dia._currentDrag.dx, e.clientY - dia._currentDrag.dy); 834 835 dia._currentDrag.dx = e.clientX; 836 dia._currentDrag.dy = e.clientY; 837 } 838 839 // object zooming 840 if (dia._currentZoom){ 841 var 842 dx = e.clientX - dia._currentZoom.dx, 843 dy = e.clientY - dia._currentZoom.dy; 844 845 dia._currentZoom.dWidth -= dx; 846 dia._currentZoom.dHeight -= dy; 847 // correction 848 if (dia._currentZoom.dWidth < 1) dia._currentZoom.dWidth = 1; 849 if (dia._currentZoom.dHeight < 1) dia._currentZoom.dHeight = 1; 850 851 // scaling parameters 852 var 853 sx = dia._currentZoom.dWidth / dia._currentZoom.origBBox.width, 854 sy = dia._currentZoom.dHeight / dia._currentZoom.origBBox.height; 855 856 // do not redraw toolbox because it is not there 857 dia._currentZoom._doNotRedrawToolbox = true; 858 dia._currentZoom.scale(sx, sy); // scale 859 r.safari(); 860 861 // save for later usage 862 dia._currentZoom.dx = e.clientX; 863 dia._currentZoom.dy = e.clientY; 864 dia._currentZoom.lastScaleX = sx; 865 dia._currentZoom.lastScaleY = sy; 866 } 867 }; 868 869 /** 870 * Document mouseup event. 871 * @private 872 */ 873 Element.mouseUp = function(e){ 874 // if ghosting is enabled, translate whole shape to the position of 875 // the ghost, then remove ghost and update joints 876 if (dia._currentDrag && dia._currentDrag._opt.ghosting){ 877 var 878 gPos = dia._currentDrag.ghostPos(), 879 wPos = dia._currentDrag.wrapperPos(); 880 881 dia._currentDrag.translate(gPos.x - wPos.x, gPos.y - wPos.y); 882 dia._currentDrag.ghost.remove(); 883 dia._currentDrag.updateJoints(); 884 } 885 // add toolbar again when dragging is stopped 886 if (dia._currentDrag){ 887 dia._currentDrag.addToolbox(); 888 dia._currentDrag.toFront(); 889 // small hack: change slightely the position to get the connections to front 890 dia._currentDrag.translate(1,1); 891 } 892 893 // add toolbar again when zooming is stopped 894 if (dia._currentZoom){ 895 // remove toolbox, because scale above may create one, 896 // so there would be two toolboxes after addToolbox() below 897 dia._currentZoom.removeToolbox(); 898 dia._currentZoom.addToolbox(); 899 dia._currentZoom.toFront(); 900 } 901 902 dia._currentDrag = false; 903 dia._currentZoom = false; 904 }; 905 906 Joint.addEvent(document, "mousemove", Element.mouseMove); 907 Joint.addEvent(document, "mouseup", Element.mouseUp); 908 909 910 })(this); // END CLOSURE