1/** 2 * Copyright (c) 2006-2016, JGraph Ltd 3 */ 4/** 5 * No CSS and resources available in embed mode. Parameters and docs: 6 * https://www.diagrams.net/doc/faq/embed-html-options 7 */ 8GraphViewer = function(container, xmlNode, graphConfig) 9{ 10 this.init(container, xmlNode, graphConfig); 11}; 12 13// Editor inherits from mxEventSource 14mxUtils.extend(GraphViewer, mxEventSource); 15 16/** 17 * Redirects editing to absolue URLs. 18 */ 19GraphViewer.prototype.editBlankUrl = 'https://app.diagrams.net/'; 20 21/** 22 * Base URL for relative images. 23 */ 24GraphViewer.prototype.imageBaseUrl = 'https://viewer.diagrams.net/'; 25 26/** 27 * Redirects editing to absolue URLs. 28 */ 29GraphViewer.prototype.toolbarHeight = (document.compatMode == 'BackCompat') ? 24 : 26; 30 31/** 32 * Redirects editing to absolue URLs. 33 */ 34GraphViewer.prototype.lightboxChrome = true; 35 36/** 37 * Redirects editing to absolue URLs. 38 */ 39GraphViewer.prototype.lightboxZIndex = 999; 40 41/** 42 * Redirects editing to absolue URLs. 43 */ 44GraphViewer.prototype.toolbarZIndex = 999; 45 46/** 47 * If automatic fit should be enabled if zoom is disabled. Default is true. 48 */ 49GraphViewer.prototype.autoFit = false; 50 51/** 52 * If automatic crop should be enabled when layers are toggled. Default is false. 53 */ 54GraphViewer.prototype.autoCrop = false; 55 56/** 57 * Specifies if the graph should be moved if a layer is made visible that 58 * extends the graph beyong the top left corner. Default is true. Is this is 59 * false then the viewport of the viewer will include all cells in all layers 60 * regardless of their initial visible state. 61 */ 62GraphViewer.prototype.autoOrigin = true; 63 64/** 65 * If the diagram should be centered. Default is false. 66 */ 67GraphViewer.prototype.center = false; 68 69/** 70 * Force centering of the diagram. Default is false. 71 */ 72GraphViewer.prototype.forceCenter = false; 73 74/** 75 * Specifies if zooming in for auto fit is allowed. Default is false. 76 */ 77GraphViewer.prototype.allowZoomIn = false; 78 79/** 80 * Specifies if zooming out for auto fit is allowed. Default is true. 81 * If toolbar-nohide is true then overflow content is visible. 82 */ 83GraphViewer.prototype.allowZoomOut = true; 84 85/** 86 * Whether the title should be shown as a tooltip if the toolbar is disabled. 87 * Default is false. 88 */ 89GraphViewer.prototype.showTitleAsTooltip = false; 90 91/** 92 * Specifies if the constructur should delay the rendering if the container 93 * is not visible by default. 94 */ 95GraphViewer.prototype.checkVisibleState = true; 96 97/** 98 * Defines the minimum height of the container. Default is 28. 99 */ 100GraphViewer.prototype.minHeight = 28; 101 102/** 103 * Defines the minimum width of the container. Default is 100. 104 */ 105GraphViewer.prototype.minWidth = 100; 106 107/** 108 * Implements viewBox to keep the contents inside the bounding box 109 * of the container. This is currently not supported in Safari (due 110 * to clipping in labels with viewBox) and all browsers that do not 111 * support foreignObjects (eg. IE11). 112 */ 113GraphViewer.prototype.responsive = false; 114 115/** 116 * Initializes the viewer. 117 */ 118GraphViewer.prototype.init = function(container, xmlNode, graphConfig) 119{ 120 this.graphConfig = (graphConfig != null) ? graphConfig : {}; 121 this.autoFit = (this.graphConfig['auto-fit'] != null) ? 122 this.graphConfig['auto-fit'] : this.autoFit; 123 this.autoCrop = (this.graphConfig['auto-crop'] != null) ? 124 this.graphConfig['auto-crop'] : this.autoCrop; 125 this.autoOrigin = (this.graphConfig['auto-origin'] != null) ? 126 this.graphConfig['auto-origin'] : this.autoOrigin; 127 this.allowZoomOut = (this.graphConfig['allow-zoom-out'] != null) ? 128 this.graphConfig['allow-zoom-out'] : this.allowZoomOut; 129 this.allowZoomIn = (this.graphConfig['allow-zoom-in'] != null) ? 130 this.graphConfig['allow-zoom-in'] : this.allowZoomIn; 131 this.forceCenter = (this.graphConfig['forceCenter'] != null) ? 132 this.graphConfig['forceCenter'] : this.forceCenter; 133 this.center = (this.graphConfig['center'] != null) ? 134 this.graphConfig['center'] : (this.center || this.forceCenter); 135 this.checkVisibleState = (this.graphConfig['check-visible-state'] != null) ? 136 this.graphConfig['check-visible-state'] : this.checkVisibleState; 137 this.toolbarItems = (this.graphConfig.toolbar != null) ? 138 this.graphConfig.toolbar.split(' ') : []; 139 this.zoomEnabled = mxUtils.indexOf(this.toolbarItems, 'zoom') >= 0; 140 this.layersEnabled = mxUtils.indexOf(this.toolbarItems, 'layers') >= 0; 141 this.tagsEnabled = mxUtils.indexOf(this.toolbarItems, 'tags') >= 0; 142 this.lightboxEnabled = mxUtils.indexOf(this.toolbarItems, 'lightbox') >= 0; 143 this.lightboxClickEnabled = this.graphConfig.lightbox != false; 144 this.initialWidth = (container != null) ? container.style.width : null; 145 this.widthIsEmpty = (this.initialWidth != null) ? this.initialWidth == '' : true; 146 this.currentPage = parseInt(this.graphConfig.page) || 0; 147 this.responsive = ((this.graphConfig['responsive'] != null) ? 148 this.graphConfig['responsive'] : this.responsive) && 149 !this.zoomEnabled && !mxClient.NO_FO && !mxClient.IS_SF; 150 this.pageId = this.graphConfig.pageId; 151 this.editor = null; 152 var self = this; 153 154 if (this.graphConfig['toolbar-position'] == 'inline') 155 { 156 this.minHeight += this.toolbarHeight; 157 } 158 159 if (xmlNode != null) 160 { 161 this.xmlDocument = xmlNode.ownerDocument; 162 this.xmlNode = xmlNode; 163 this.xml = mxUtils.getXml(xmlNode); 164 165 if (container != null) 166 { 167 var render = mxUtils.bind(this, function() 168 { 169 this.graph = new Graph(container); 170 this.graph.enableFlowAnimation = true; 171 this.graph.defaultPageBackgroundColor = 'transparent'; 172 this.graph.transparentBackground = false; 173 174 if (this.responsive && this.graph.dialect == mxConstants.DIALECT_SVG) 175 { 176 var root = this.graph.view.getDrawPane().ownerSVGElement; 177 var canvas = this.graph.view.getCanvas(); 178 179 if (this.graphConfig.border != null) 180 { 181 root.style.padding = this.graphConfig.border + 'px'; 182 } 183 else if (container.style.padding == '') 184 { 185 root.style.padding = '8px'; 186 } 187 188 root.style.boxSizing = 'border-box'; 189 root.style.overflow = 'visible'; 190 191 this.graph.fit = function() 192 { 193 // Automatic 194 }; 195 196 this.graph.sizeDidChange = function() 197 { 198 var bounds = this.view.graphBounds; 199 var tr = this.view.translate; 200 201 root.setAttribute('viewBox', 202 (bounds.x + tr.x - this.panDx) + ' ' + 203 (bounds.y + tr.y - this.panDy) + ' ' + 204 (bounds.width + 1) + ' ' + 205 (bounds.height + 1)); 206 this.container.style.backgroundColor = 207 root.style.backgroundColor; 208 209 this.fireEvent(new mxEventObject(mxEvent.SIZE, 'bounds', bounds)); 210 }; 211 } 212 213 if (this.graphConfig.move) 214 { 215 this.graph.isMoveCellsEvent = function(evt) 216 { 217 return true; 218 }; 219 } 220 221 // Adds lightbox and link handling for shapes 222 if (this.lightboxClickEnabled) 223 { 224 container.style.cursor = 'pointer'; 225 } 226 227 // Hack for using EditorUi methods on the graph instance 228 this.editor = new Editor(true, null, null, this.graph); 229 this.editor.editBlankUrl = this.editBlankUrl; 230 this.graph.lightbox = true; 231 this.graph.centerZoom = false; 232 this.graph.autoExtend = false; 233 this.graph.autoScroll = false; 234 this.graph.setEnabled(false); 235 236 if (this.graphConfig['toolbar-nohide'] == true) 237 { 238 this.editor.defaultGraphOverflow = 'visible'; 239 } 240 241 //Extract graph model from html & svg formats 242 this.xmlNode = this.editor.extractGraphModel(this.xmlNode, true); 243 244 if (this.xmlNode != xmlNode) 245 { 246 this.xml = mxUtils.getXml(this.xmlNode); 247 this.xmlDocument = this.xmlNode.ownerDocument; 248 } 249 250 // Handles relative images 251 var self = this; 252 253 this.graph.getImageFromBundles = function(key) 254 { 255 return self.getImageUrl(key); 256 }; 257 258 if (mxClient.IS_SVG) 259 { 260 // LATER: Add shadow for labels in graph.container (eg. math, NO_FO), scaling 261 this.graph.addSvgShadow(this.graph.view.canvas.ownerSVGElement, null, true); 262 } 263 264 // Adds page placeholders 265 if (this.xmlNode.nodeName == 'mxfile') 266 { 267 var diagrams = this.xmlNode.getElementsByTagName('diagram'); 268 269 if (diagrams.length > 0) 270 { 271 //Find the page index if the pageId is provided 272 if (this.pageId != null) 273 { 274 for (var i = 0; i < diagrams.length; i++) 275 { 276 if (this.pageId == diagrams[i].getAttribute('id')) 277 { 278 this.currentPage = i; 279 break; 280 } 281 } 282 } 283 284 var graphGetGlobalVariable = this.graph.getGlobalVariable; 285 var self = this; 286 287 this.graph.getGlobalVariable = function(name) 288 { 289 var diagram = diagrams[self.currentPage]; 290 291 if (name == 'page') 292 { 293 return diagram.getAttribute('name') || 'Page-' + (self.currentPage + 1); 294 } 295 else if (name == 'pagenumber') 296 { 297 return self.currentPage + 1; 298 } 299 else if (name == 'pagecount') 300 { 301 return diagrams.length; 302 } 303 304 return graphGetGlobalVariable.apply(this, arguments); 305 }; 306 } 307 } 308 309 this.diagrams = []; 310 var lastXmlNode = null; 311 312 this.selectPage = function(number) 313 { 314 if(this.handlingResize) 315 return; 316 317 this.currentPage = mxUtils.mod(number, this.diagrams.length); 318 this.updateGraphXml(Editor.parseDiagramNode(this.diagrams[this.currentPage])); 319 }; 320 321 this.selectPageById = function(id) 322 { 323 var index = this.getIndexById(id); 324 var found = index >= 0; 325 326 if (found) 327 { 328 this.selectPage(index); 329 } 330 331 return found; 332 }; 333 334 var update = mxUtils.bind(this, function() 335 { 336 if (this.xmlNode == null || this.xmlNode.nodeName != 'mxfile') 337 { 338 this.diagrams = []; 339 } 340 if (this.xmlNode != lastXmlNode) 341 { 342 this.diagrams = this.xmlNode.getElementsByTagName('diagram'); 343 lastXmlNode = this.xmlNode; 344 } 345 }); 346 347 // Replaces background page reference with SVG 348 var graphSetBackgroundImage = this.graph.setBackgroundImage; 349 350 this.graph.setBackgroundImage = function(img) 351 { 352 if (img != null && Graph.isPageLink(img.src)) 353 { 354 var src = img.src; 355 var comma = src.indexOf(','); 356 357 if (comma > 0) 358 { 359 var index = self.getIndexById(src.substring(comma + 1)); 360 361 if (index >= 0) 362 { 363 img = self.getImageForGraphModel( 364 Editor.parseDiagramNode( 365 self.diagrams[index])); 366 img.originalSrc = src; 367 } 368 } 369 } 370 371 graphSetBackgroundImage.apply(this, arguments); 372 }; 373 374 // Overrides graph bounds to include background pages 375 var graphGetGraphBounds = this.graph.getGraphBounds; 376 377 this.graph.getGraphBounds = function(img) 378 { 379 var bounds = graphGetGraphBounds.apply(this, arguments); 380 var img = this.backgroundImage; 381 382 // Check img.originalSrc to ignore background 383 // images but not background pages 384 if (img != null) 385 { 386 var t = this.view.translate; 387 var s = this.view.scale; 388 389 bounds = mxRectangle.fromRectangle(bounds); 390 bounds.add(new mxRectangle( 391 (t.x + img.x) * s, (t.y + img.y) * s, 392 img.width * s, img.height * s)); 393 } 394 395 return bounds; 396 }; 397 398 // LATER: Add event for setGraphXml 399 this.addListener('xmlNodeChanged', update); 400 update(); 401 402 // Passes current page via urlParams global variable 403 // to let the parser know which page we're using 404 urlParams['page'] = self.currentPage; 405 var visible = null; 406 407 this.graph.getModel().beginUpdate(); 408 try 409 { 410 // Required for correct parsing of fold parameter 411 urlParams['nav'] = (this.graphConfig.nav != false) ? '1' : '0'; 412 413 this.editor.setGraphXml(this.xmlNode); 414 this.graph.view.scale = this.graphConfig.zoom || 1; 415 visible = this.setLayersVisible(); 416 417 if (!this.responsive) 418 { 419 this.graph.border = (this.graphConfig.border != null) ? this.graphConfig.border : 8; 420 } 421 } 422 finally 423 { 424 this.graph.getModel().endUpdate(); 425 } 426 427 // Adds left-button panning only if scrollbars are visible 428 if (!this.responsive) 429 { 430 this.graph.panningHandler.isForcePanningEvent = function(me) 431 { 432 return !mxEvent.isPopupTrigger(me.getEvent()) && 433 this.graph.container.style.overflow == 'auto'; 434 }; 435 436 this.graph.panningHandler.useLeftButtonForPanning = true; 437 this.graph.panningHandler.ignoreCell = true; 438 this.graph.panningHandler.usePopupTrigger = false; 439 this.graph.panningHandler.pinchEnabled = false; 440 } 441 442 this.graph.setPanning(false); 443 444 if (this.graphConfig.toolbar != null) 445 { 446 this.addToolbar(); 447 } 448 else if (this.graphConfig.title != null && this.showTitleAsTooltip) 449 { 450 container.setAttribute('title', this.graphConfig.title); 451 } 452 453 if (!this.responsive) 454 { 455 this.addSizeHandler(); 456 } 457 458 // Crops to visible layers if no layers toolbar button 459 if (this.showLayers(this.graph) && !this.forceCenter && (!this.layersEnabled || this.autoCrop)) 460 { 461 this.crop(); 462 } 463 464 this.addClickHandler(this.graph); 465 this.graph.setTooltips(this.graphConfig.tooltips != false); 466 this.graph.initialViewState = { 467 translate: this.graph.view.translate.clone(), 468 scale: this.graph.view.scale 469 }; 470 471 if (visible != null) 472 { 473 this.setLayersVisible(visible); 474 } 475 476 this.graph.customLinkClicked = function(href) 477 { 478 if (Graph.isPageLink(href)) 479 { 480 var comma = href.indexOf(','); 481 482 if (!self.selectPageById(href.substring(comma + 1))) 483 { 484 alert(mxResources.get('pageNotFound') || 'Page not found'); 485 } 486 } 487 else 488 { 489 this.handleCustomLink(href); 490 } 491 492 return true; 493 }; 494 495 // Updates origin after tree cell folding 496 var graphFoldTreeCell = this.graph.foldTreeCell; 497 498 this.graph.foldTreeCell = mxUtils.bind(this, function() 499 { 500 this.treeCellFolded = true; 501 502 return graphFoldTreeCell.apply(this.graph, arguments); 503 }); 504 505 this.fireEvent(new mxEventObject('render')); 506 }); 507 508 var MutObs = window.MutationObserver || 509 window.WebKitMutationObserver || 510 window.MozMutationObserver; 511 512 if (this.checkVisibleState && container.offsetWidth == 0 && typeof MutObs !== 'undefined') 513 { 514 // Delayed rendering if inside hidden container and event available 515 var par = this.getObservableParent(container); 516 517 var observer = new MutObs(mxUtils.bind(this, function(mutation) 518 { 519 if (container.offsetWidth > 0) 520 { 521 observer.disconnect(); 522 render(); 523 } 524 })); 525 526 observer.observe(par, {attributes: true}); 527 } 528 else 529 { 530 // Immediate rendering in all other cases 531 render(); 532 } 533 } 534 } 535}; 536 537/** 538 * 539 */ 540GraphViewer.prototype.getObservableParent = function(container) 541{ 542 var node = container.parentNode; 543 544 while (node != document.body && node.parentNode != null && 545 mxUtils.getCurrentStyle(node).display !== 'none') 546 { 547 node = node.parentNode; 548 } 549 550 return node; 551}; 552 553/** 554 * 555 */ 556GraphViewer.prototype.getImageUrl = function(url) 557{ 558 if (url != null && url.substring(0, 7) != 'http://' && 559 url.substring(0, 8) != 'https://' && url.substring(0, 10) != 'data:image') 560 { 561 if (url.charAt(0) == '/') 562 { 563 url = url.substring(1, url.length); 564 } 565 566 url = this.imageBaseUrl + url; 567 } 568 569 return url; 570}; 571 572/** 573 * 574 */ 575GraphViewer.prototype.getImageForGraphModel = function(node) 576{ 577 var graph = Graph.createOffscreenGraph(this.graph.getStylesheet()); 578 graph.getGlobalVariable = this.graph.getGlobalVariable; 579 document.body.appendChild(graph.container); 580 581 var codec = new mxCodec(node.ownerDocument); 582 var root = codec.decode(node).root; 583 graph.model.setRoot(root); 584 585 var svgRoot = graph.getSvg(); 586 var bounds = graph.getGraphBounds(); 587 document.body.removeChild(graph.container); 588 589 return new mxImage(Editor.createSvgDataUri(mxUtils.getXml(svgRoot)), 590 bounds.width, bounds.height, bounds.x, bounds.y); 591}; 592 593/** 594 * 595 */ 596GraphViewer.prototype.getIndexById = function(id) 597{ 598 if (this.diagrams != null) 599 { 600 for (var i = 0; i < this.diagrams.length; i++) 601 { 602 if (this.diagrams[i].getAttribute('id') == id) 603 { 604 return i; 605 } 606 } 607 } 608 609 return -1; 610}; 611 612/** 613 * 614 */ 615GraphViewer.prototype.setXmlNode = function(xmlNode) 616{ 617 //Extract graph model from html & svg formats 618 xmlNode = this.editor.extractGraphModel(xmlNode, true); 619 620 this.xmlDocument = xmlNode.ownerDocument; 621 this.xml = mxUtils.getXml(xmlNode); 622 this.xmlNode = xmlNode; 623 624 this.updateGraphXml(xmlNode); 625 this.fireEvent(new mxEventObject('xmlNodeChanged')); 626}; 627 628/** 629 * 630 */ 631GraphViewer.prototype.setFileNode = function(xmlNode) 632{ 633 if (this.xmlNode == null) 634 { 635 this.xmlDocument = xmlNode.ownerDocument; 636 this.xml = mxUtils.getXml(xmlNode); 637 this.xmlNode = xmlNode; 638 } 639 640 this.setGraphXml(xmlNode); 641}; 642 643/** 644 * 645 */ 646GraphViewer.prototype.updateGraphXml = function(xmlNode) 647{ 648 this.setGraphXml(xmlNode); 649 this.fireEvent(new mxEventObject('graphChanged')); 650}; 651 652/** 653 * 654 */ 655GraphViewer.prototype.setLayersVisible = function(visible) 656{ 657 var allVisible = true; 658 659 if (!this.autoOrigin) 660 { 661 var result = []; 662 var model = this.graph.getModel(); 663 664 model.beginUpdate(); 665 try 666 { 667 for (var i = 0; i < model.getChildCount(model.root); i++) 668 { 669 var layer = model.getChildAt(model.root, i); 670 allVisible = allVisible && model.isVisible(layer); 671 result.push(model.isVisible(layer)); 672 model.setVisible(layer, (visible != null) ? visible[i] : true); 673 } 674 } 675 finally 676 { 677 model.endUpdate(); 678 } 679 } 680 681 return (allVisible) ? null : result; 682}; 683 684/** 685 * 686 */ 687GraphViewer.prototype.setGraphXml = function(xmlNode) 688{ 689 if (this.graph != null) 690 { 691 this.graph.view.translate = new mxPoint(); 692 this.graph.view.scale = 1; 693 var visible = null; 694 695 this.graph.getModel().beginUpdate(); 696 try 697 { 698 this.graph.getModel().clear(); 699 this.editor.setGraphXml(xmlNode); 700 visible = this.setLayersVisible(true); 701 } 702 finally 703 { 704 this.graph.getModel().endUpdate(); 705 } 706 707 if (!this.responsive) 708 { 709 // Restores initial CSS state 710 if (this.widthIsEmpty) 711 { 712 this.graph.container.style.width = ''; 713 this.graph.container.style.height = ''; 714 } 715 else 716 { 717 this.graph.container.style.width = this.initialWidth; 718 } 719 720 this.positionGraph(); 721 } 722 723 this.graph.initialViewState = { 724 translate: this.graph.view.translate.clone(), 725 scale: this.graph.view.scale 726 }; 727 728 if (visible) 729 { 730 this.setLayersVisible(visible); 731 } 732 } 733}; 734 735/** 736 * 737 */ 738GraphViewer.prototype.addSizeHandler = function() 739{ 740 var container = this.graph.container; 741 var bounds = this.graph.getGraphBounds(); 742 var updatingOverflow = false; 743 744 if (this.graphConfig['toolbar-nohide'] != true) 745 { 746 container.style.overflow = 'hidden'; 747 } 748 else 749 { 750 container.style.overflow = 'visible'; 751 } 752 753 var updateOverflow = mxUtils.bind(this, function() 754 { 755 if (!updatingOverflow) 756 { 757 updatingOverflow = true; 758 var tmp = this.graph.getGraphBounds(); 759 760 if (this.graphConfig['toolbar-nohide'] != true) 761 { 762 // Shows scrollbars if graph is larger than available width 763 if (tmp.width + 2 * this.graph.border > container.offsetWidth - 2) 764 { 765 container.style.overflow = 'auto'; 766 } 767 else 768 { 769 container.style.overflow = 'hidden'; 770 } 771 } 772 else 773 { 774 container.style.overflow = 'visible'; 775 } 776 777 if (this.toolbar != null && this.graphConfig['toolbar-nohide'] != true) 778 { 779 var r = container.getBoundingClientRect(); 780 781 // Workaround for position:relative set in ResizeSensor 782 var origin = mxUtils.getScrollOrigin(document.body) 783 var b = (document.body.style.position === 'relative') ? 784 document.body.getBoundingClientRect() : 785 {left: -origin.x, top: -origin.y}; 786 r = {left: r.left - b.left, top: r.top - b.top, bottom: r.bottom - b.top, right: r.right - b.left}; 787 788 this.toolbar.style.left = r.left + 'px'; 789 790 if (this.graphConfig['toolbar-position'] == 'bottom') 791 { 792 this.toolbar.style.top = r.bottom - 1 + 'px'; 793 } 794 else 795 { 796 if (this.graphConfig['toolbar-position'] != 'inline') 797 { 798 this.toolbar.style.width = Math.max(this.minToolbarWidth, container.offsetWidth) + 'px'; 799 this.toolbar.style.top = r.top + 1 + 'px'; 800 } 801 else 802 { 803 this.toolbar.style.top = r.top + 'px'; 804 } 805 } 806 } 807 else if (this.toolbar != null) 808 { 809 this.toolbar.style.width = Math.max(this.minToolbarWidth, container.offsetWidth) + 'px'; 810 } 811 812 // Updates origin after tree cell folding 813 if (this.treeCellFolded) 814 { 815 this.treeCellFolded = false; 816 this.positionGraph(this.graph.view.translate); 817 this.graph.initialViewState.translate = this.graph.view.translate.clone(); 818 } 819 820 updatingOverflow = false; 821 } 822 }); 823 824 var lastOffsetWidth = null; 825 var cachedOffsetWidth = null; 826 this.handlingResize = false; 827 828 // Installs function on instance 829 this.fitGraph = mxUtils.bind(this, function(maxScale) 830 { 831 var cachedOffsetWidth = container.offsetWidth; 832 833 if (cachedOffsetWidth != lastOffsetWidth && !this.handlingResize) 834 { 835 this.handlingResize = true; 836 837 // Hides scrollbars to force update of translate 838 if (container.style.overflow == 'auto') 839 { 840 container.style.overflow = 'hidden'; 841 } 842 843 this.graph.maxFitScale = (maxScale != null) ? maxScale : (this.graphConfig.zoom || 844 ((this.allowZoomIn) ? null : 1)); 845 this.graph.fit(null, null, null, null, null, true); 846 847 if (this.center || !(this.graphConfig.resize != false || container.style.height == '')) 848 { 849 this.graph.center(); 850 } 851 852 this.graph.maxFitScale = null; 853 854 if (this.graphConfig.resize != false || container.style.height == '') 855 { 856 this.updateContainerHeight(container, Math.max(this.minHeight, 857 this.graph.getGraphBounds().height + 858 2 * this.graph.border + 1)); 859 } 860 861 this.graph.initialViewState = { 862 translate: this.graph.view.translate.clone(), 863 scale: this.graph.view.scale 864 }; 865 866 lastOffsetWidth = cachedOffsetWidth; 867 868 // Workaround for fit triggering scrollbars triggering doResize (infinite loop) 869 window.setTimeout(mxUtils.bind(this, function() 870 { 871 this.handlingResize = false; 872 }), 0); 873 } 874 }); 875 876 // Fallback for older browsers 877 if (GraphViewer.useResizeSensor) 878 { 879 if (document.documentMode <= 9) 880 { 881 mxEvent.addListener(window, 'resize', updateOverflow); 882 this.graph.addListener('size', updateOverflow); 883 } 884 else 885 { 886 new ResizeSensor(this.graph.container, updateOverflow); 887 } 888 } 889 890 if (this.graphConfig.resize || ((this.zoomEnabled || !this.autoFit) && this.graphConfig.resize != false)) 891 { 892 this.graph.minimumContainerSize = new mxRectangle(0, 0, this.minWidth, this.minHeight); 893 this.graph.resizeContainer = true; 894 } 895 else 896 { 897 // Sets initial size for responsive diagram to stop at actual size 898 if (this.widthIsEmpty && !(container.style.height != '' && this.autoFit)) 899 { 900 this.updateContainerWidth(container, bounds.width + 2 * this.graph.border); 901 } 902 903 if (this.graphConfig.resize != false || container.style.height == '') 904 { 905 this.updateContainerHeight(container, Math.max(this.minHeight, bounds.height + 2 * this.graph.border + 1)); 906 } 907 908 if (!this.zoomEnabled && this.autoFit) 909 { 910 var lastOffsetWidth = null; 911 var scheduledResize = null; 912 var cachedOffsetWidth = null; 913 914 var doResize = mxUtils.bind(this, function() 915 { 916 window.clearTimeout(scheduledResize); 917 918 if (!this.handlingResize) 919 { 920 scheduledResize = window.setTimeout(mxUtils.bind(this, this.fitGraph), 100); 921 } 922 }); 923 924 // Fallback for older browsers 925 if (GraphViewer.useResizeSensor) 926 { 927 if (document.documentMode <= 9) 928 { 929 mxEvent.addListener(window, 'resize', doResize); 930 } 931 else 932 { 933 new ResizeSensor(this.graph.container, doResize); 934 } 935 } 936 } 937 else if (!(document.documentMode <= 9)) 938 { 939 this.graph.addListener('size', updateOverflow); 940 } 941 } 942 943 var positionGraph = mxUtils.bind(this, function(origin) 944 { 945 // Allocates maximum width while setting initial view state 946 var prev = container.style.minWidth; 947 948 if (this.widthIsEmpty) 949 { 950 container.style.minWidth = '100%'; 951 } 952 953 var maxHeight = (this.graphConfig['max-height'] != null) ? this.graphConfig['max-height'] : 954 ((container.style.height != '' && this.autoFit) ? container.offsetHeight : undefined); 955 956 if (container.offsetWidth > 0 && origin == null && this.allowZoomOut && (this.allowZoomIn || 957 bounds.width + 2 * this.graph.border > container.offsetWidth || 958 bounds.height + 2 * this.graph.border > maxHeight)) 959 { 960 var maxScale = null; 961 962 if (maxHeight != null && bounds.height + 2 * this.graph.border > maxHeight - 2) 963 { 964 maxScale = (maxHeight - 2 * this.graph.border - 2) / bounds.height; 965 } 966 967 this.fitGraph(maxScale); 968 } 969 else if (!this.widthIsEmpty && origin == null && !(this.graphConfig.resize != false || container.style.height == '')) 970 { 971 this.graph.center((!this.widthIsEmpty || bounds.width < this.minWidth) && this.graphConfig.resize != true); 972 } 973 else 974 { 975 origin = (origin != null) ? origin : new mxPoint(); 976 977 this.graph.view.setTranslate(Math.floor(this.graph.border - bounds.x / this.graph.view.scale) + origin.x, 978 Math.floor(this.graph.border - bounds.y / this.graph.view.scale) + origin.y); 979 lastOffsetWidth = container.offsetWidth; 980 } 981 982 container.style.minWidth = prev 983 }); 984 985 if (document.documentMode == 8) 986 { 987 window.setTimeout(positionGraph, 0); 988 } 989 else 990 { 991 positionGraph(); 992 } 993 994 // Installs function on instance 995 this.positionGraph = function(origin) 996 { 997 bounds = this.graph.getGraphBounds(); 998 lastOffsetWidth = null; 999 positionGraph(origin); 1000 }; 1001}; 1002 1003/** 1004 * Moves the origin of the graph to the top, right corner. 1005 */ 1006GraphViewer.prototype.crop = function() 1007{ 1008 var graph = this.graph; 1009 var bounds = graph.getGraphBounds(); 1010 var border = graph.border; 1011 var s = graph.view.scale; 1012 var x0 = (bounds.x != null) ? Math.floor(graph.view.translate.x - bounds.x / s + border) : border; 1013 var y0 = (bounds.y != null) ? Math.floor(graph.view.translate.y - bounds.y / s + border) : border; 1014 1015 graph.view.setTranslate(x0, y0); 1016}; 1017 1018/** 1019 * 1020 */ 1021GraphViewer.prototype.updateContainerWidth = function(container, width) 1022{ 1023 container.style.width = width + 'px'; 1024}; 1025 1026/** 1027 * 1028 */ 1029GraphViewer.prototype.updateContainerHeight = function(container, height) 1030{ 1031 if (this.forceCenter || this.zoomEnabled || !this.autoFit || document.compatMode == 'BackCompat' || 1032 document.documentMode == 8) 1033 { 1034 container.style.height = height + 'px'; 1035 } 1036}; 1037 1038/** 1039 * Shows the 1040 */ 1041GraphViewer.prototype.showLayers = function(graph, sourceGraph) 1042{ 1043 var layers = this.graphConfig.layers; 1044 var idx = (layers != null && layers.length > 0) ? layers.split(' ') : []; 1045 var layerIds = this.graphConfig.layerIds; 1046 var hasLayerIds = layerIds != null && layerIds.length > 0; 1047 var result = false; 1048 1049 if (idx.length > 0 || hasLayerIds || sourceGraph != null) 1050 { 1051 var source = (sourceGraph != null) ? sourceGraph.getModel() : null; 1052 var model = graph.getModel(); 1053 model.beginUpdate(); 1054 1055 try 1056 { 1057 var childCount = model.getChildCount(model.root); 1058 1059 // Shows specified layers (eg. 0 1 3) 1060 if (source == null) 1061 { 1062 var layersFound = false, visibleLayers = {}; 1063 1064 if (hasLayerIds) 1065 { 1066 for (var i = 0; i < layerIds.length; i++) 1067 { 1068 var layer = model.getCell(layerIds[i]); 1069 1070 if (layer != null) 1071 { 1072 layersFound = true; 1073 visibleLayers[layer.id] = true; 1074 } 1075 } 1076 } 1077 else 1078 { 1079 for (var i = 0; i < idx.length; i++) 1080 { 1081 var layer = model.getChildAt(model.root, parseInt(idx[i])); 1082 1083 if (layer != null) 1084 { 1085 layersFound = true; 1086 visibleLayers[layer.id] = true; 1087 } 1088 } 1089 } 1090 1091 //To prevent hiding all layers, only apply if the specified layers are found 1092 //This prevents incorrect settings from showing an empty viewer 1093 for (var i = 0; layersFound && i < childCount; i++) 1094 { 1095 var layer = model.getChildAt(model.root, i); 1096 model.setVisible(layer, visibleLayers[layer.id] || false); 1097 } 1098 } 1099 else 1100 { 1101 // Match visible layers in source graph 1102 for (var i = 0; i < childCount; i++) 1103 { 1104 model.setVisible(model.getChildAt(model.root, i), 1105 source.isVisible(source.getChildAt(source.root, i))); 1106 } 1107 } 1108 } 1109 finally 1110 { 1111 model.endUpdate(); 1112 } 1113 1114 result = true; 1115 } 1116 1117 return result; 1118}; 1119 1120/** 1121 * 1122 */ 1123GraphViewer.prototype.addToolbar = function() 1124{ 1125 var container = this.graph.container; 1126 var initialCursor = this.graph.container.style.cursor; 1127 1128 if (this.graphConfig['toolbar-position'] == 'bottom') 1129 { 1130 container.style.marginBottom = this.toolbarHeight + 'px'; 1131 } 1132 else if (this.graphConfig['toolbar-position'] != 'inline') 1133 { 1134 container.style.marginTop = this.toolbarHeight + 'px'; 1135 } 1136 1137 // Creates toolbar for viewer 1138 var toolbar = container.ownerDocument.createElement('div'); 1139 toolbar.style.position = 'absolute'; 1140 toolbar.style.overflow = 'hidden'; 1141 toolbar.style.boxSizing = 'border-box'; 1142 toolbar.style.whiteSpace = 'nowrap'; 1143 toolbar.style.textAlign = 'left'; 1144 toolbar.style.zIndex = this.toolbarZIndex; 1145 toolbar.style.backgroundColor = '#eee'; 1146 toolbar.style.height = this.toolbarHeight + 'px'; 1147 this.toolbar = toolbar; 1148 1149 if (this.graphConfig['toolbar-position'] == 'inline') 1150 { 1151 mxUtils.setPrefixedStyle(toolbar.style, 'transition', 'opacity 100ms ease-in-out'); 1152 mxUtils.setOpacity(toolbar, 30); 1153 1154 // Changes toolbar opacity on hover 1155 var fadeThread = null; 1156 var fadeThread2 = null; 1157 1158 var fadeOut = mxUtils.bind(this, function(delay) 1159 { 1160 if (fadeThread != null) 1161 { 1162 window.clearTimeout(fadeThread); 1163 fadeThead = null; 1164 } 1165 1166 if (fadeThread2 != null) 1167 { 1168 window.clearTimeout(fadeThread2); 1169 fadeThead2 = null; 1170 } 1171 1172 fadeThread = window.setTimeout(mxUtils.bind(this, function() 1173 { 1174 mxUtils.setOpacity(toolbar, 0); 1175 fadeThread = null; 1176 1177 fadeThread2 = window.setTimeout(mxUtils.bind(this, function() 1178 { 1179 toolbar.style.display = 'none'; 1180 fadeThread2 = null; 1181 }), 100); 1182 }), delay || 200); 1183 }); 1184 1185 var fadeIn = mxUtils.bind(this, function(opacity) 1186 { 1187 if (fadeThread != null) 1188 { 1189 window.clearTimeout(fadeThread); 1190 fadeThead = null; 1191 } 1192 1193 if (fadeThread2 != null) 1194 { 1195 window.clearTimeout(fadeThread2); 1196 fadeThead2 = null; 1197 } 1198 1199 toolbar.style.display = ''; 1200 mxUtils.setOpacity(toolbar, opacity || 30); 1201 }); 1202 1203 mxEvent.addListener(this.graph.container, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', mxUtils.bind(this, function(evt) 1204 { 1205 if (!mxEvent.isTouchEvent(evt)) 1206 { 1207 fadeIn(30); 1208 fadeOut(); 1209 } 1210 })); 1211 1212 mxEvent.addListener(toolbar, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', function(evt) 1213 { 1214 mxEvent.consume(evt); 1215 }); 1216 1217 mxEvent.addListener(toolbar, 'mouseenter', mxUtils.bind(this, function(evt) 1218 { 1219 fadeIn(100); 1220 })); 1221 1222 mxEvent.addListener(toolbar, 'mousemove', mxUtils.bind(this, function(evt) 1223 { 1224 fadeIn(100); 1225 mxEvent.consume(evt); 1226 })); 1227 1228 mxEvent.addListener(toolbar, 'mouseleave', mxUtils.bind(this, function(evt) 1229 { 1230 if (!mxEvent.isTouchEvent(evt)) 1231 { 1232 fadeIn(30); 1233 } 1234 })); 1235 1236 // Shows/hides toolbar for touch devices 1237 var graph = this.graph; 1238 var tol = graph.getTolerance(); 1239 1240 graph.addMouseListener( 1241 { 1242 startX: 0, 1243 startY: 0, 1244 scrollLeft: 0, 1245 scrollTop: 0, 1246 mouseDown: function(sender, me) 1247 { 1248 this.startX = me.getGraphX(); 1249 this.startY = me.getGraphY(); 1250 this.scrollLeft = graph.container.scrollLeft; 1251 this.scrollTop = graph.container.scrollTop; 1252 }, 1253 mouseMove: function(sender, me) {}, 1254 mouseUp: function(sender, me) 1255 { 1256 if (mxEvent.isTouchEvent(me.getEvent())) 1257 { 1258 if ((Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol && 1259 Math.abs(this.scrollTop - graph.container.scrollTop) < tol) && 1260 (Math.abs(this.startX - me.getGraphX()) < tol && 1261 Math.abs(this.startY - me.getGraphY()) < tol)) 1262 { 1263 if (parseFloat(toolbar.style.opacity || 0) > 0) 1264 { 1265 fadeOut(); 1266 } 1267 else 1268 { 1269 fadeIn(30); 1270 } 1271 } 1272 } 1273 } 1274 }); 1275 } 1276 1277 var tokens = this.toolbarItems; 1278 var buttonCount = 0; 1279 1280 function addButton(fn, imgSrc, tip, enabled) 1281 { 1282 var a = document.createElement('div'); 1283 a.style.borderRight = '1px solid #d0d0d0'; 1284 a.style.padding = '3px 6px 3px 6px'; 1285 mxEvent.addListener(a, 'click', fn); 1286 1287 if (tip != null) 1288 { 1289 a.setAttribute('title', tip); 1290 } 1291 1292 a.style.display = 'inline-block'; 1293 var img = document.createElement('img'); 1294 img.setAttribute('border', '0'); 1295 img.setAttribute('src', imgSrc); 1296 img.style.width = '18px'; 1297 1298 if (enabled == null || enabled) 1299 { 1300 mxEvent.addListener(a, 'mouseenter', function() 1301 { 1302 a.style.backgroundColor = '#ddd'; 1303 }); 1304 1305 mxEvent.addListener(a, 'mouseleave', function() 1306 { 1307 a.style.backgroundColor = '#eee'; 1308 }); 1309 1310 mxUtils.setOpacity(img, 60); 1311 a.style.cursor = 'pointer'; 1312 } 1313 else 1314 { 1315 mxUtils.setOpacity(a, 30); 1316 } 1317 1318 a.appendChild(img); 1319 toolbar.appendChild(a); 1320 1321 buttonCount++; 1322 1323 return a; 1324 }; 1325 1326 var layersDialog = null; 1327 var tagsComponent = null; 1328 var tagsDialog = null; 1329 var pageInfo = null; 1330 1331 for (var i = 0; i < tokens.length; i++) 1332 { 1333 var token = tokens[i]; 1334 1335 if (token == 'pages') 1336 { 1337 pageInfo = container.ownerDocument.createElement('div'); 1338 pageInfo.style.cssText = 'display:inline-block;position:relative;top:5px;padding:0 4px 0 4px;' + 1339 'vertical-align:top;font-family:Helvetica,Arial;font-size:12px;;cursor:default;' 1340 mxUtils.setOpacity(pageInfo, 70); 1341 1342 var prevButton = addButton(mxUtils.bind(this, function() 1343 { 1344 this.selectPage(this.currentPage - 1); 1345 }), Editor.previousImage, mxResources.get('previousPage') || 'Previous Page'); 1346 1347 prevButton.style.borderRightStyle = 'none'; 1348 prevButton.style.paddingLeft = '0px'; 1349 prevButton.style.paddingRight = '0px'; 1350 toolbar.appendChild(pageInfo); 1351 1352 var nextButton = addButton(mxUtils.bind(this, function() 1353 { 1354 this.selectPage(this.currentPage + 1); 1355 }), Editor.nextImage, mxResources.get('nextPage') || 'Next Page'); 1356 1357 nextButton.style.paddingLeft = '0px'; 1358 nextButton.style.paddingRight = '0px'; 1359 1360 var lastXmlNode = null; 1361 1362 var update = mxUtils.bind(this, function() 1363 { 1364 pageInfo.innerHTML = ''; 1365 mxUtils.write(pageInfo, (this.currentPage + 1) + ' / ' + this.diagrams.length); 1366 pageInfo.style.display = (this.diagrams.length > 1) ? 'inline-block' : 'none'; 1367 prevButton.style.display = pageInfo.style.display; 1368 nextButton.style.display = pageInfo.style.display; 1369 }); 1370 1371 // LATER: Add event for setGraphXml 1372 this.addListener('graphChanged', update); 1373 update(); 1374 } 1375 else if (token == 'zoom') 1376 { 1377 if (this.zoomEnabled) 1378 { 1379 addButton(mxUtils.bind(this, function() 1380 { 1381 this.graph.zoomOut(); 1382 }), Editor.zoomOutImage, mxResources.get('zoomOut') || 'Zoom Out'); 1383 1384 addButton(mxUtils.bind(this, function() 1385 { 1386 this.graph.zoomIn(); 1387 }), Editor.zoomInImage, mxResources.get('zoomIn') || 'Zoom In'); 1388 1389 addButton(mxUtils.bind(this, function() 1390 { 1391 this.graph.view.scaleAndTranslate(this.graph.initialViewState.scale, 1392 this.graph.initialViewState.translate.x, 1393 this.graph.initialViewState.translate.y); 1394 }), Editor.zoomFitImage, mxResources.get('fit') || 'Fit'); 1395 } 1396 } 1397 else if (token == 'layers') 1398 { 1399 if (this.layersEnabled) 1400 { 1401 var model = this.graph.getModel(); 1402 1403 var layersButton = addButton(mxUtils.bind(this, function(evt) 1404 { 1405 if (layersDialog != null) 1406 { 1407 layersDialog.parentNode.removeChild(layersDialog); 1408 layersDialog = null; 1409 } 1410 else 1411 { 1412 layersDialog = this.graph.createLayersDialog(mxUtils.bind(this, function() 1413 { 1414 if (this.autoCrop) 1415 { 1416 this.crop(); 1417 } 1418 else if (this.autoOrigin) 1419 { 1420 var bounds = this.graph.getGraphBounds(); 1421 var v = this.graph.view; 1422 1423 if (bounds.x < 0 || bounds.y < 0) 1424 { 1425 this.crop(); 1426 this.graph.originalViewState = this.graph.initialViewState; 1427 1428 this.graph.initialViewState = { 1429 translate: v.translate.clone(), 1430 scale: v.scale 1431 }; 1432 } 1433 else if (this.graph.originalViewState != null && 1434 bounds.x / v.scale + this.graph.originalViewState.translate.x - v.translate.x > 0 && 1435 bounds.y / v.scale + this.graph.originalViewState.translate.y - v.translate.y > 0) 1436 { 1437 v.setTranslate(this.graph.originalViewState.translate.x, 1438 this.graph.originalViewState.translate.y); 1439 this.graph.originalViewState = null; 1440 1441 this.graph.initialViewState = { 1442 translate: v.translate.clone(), 1443 scale: v.scale 1444 }; 1445 } 1446 } 1447 })); 1448 1449 mxEvent.addListener(layersDialog, 'mouseleave', function() 1450 { 1451 layersDialog.parentNode.removeChild(layersDialog); 1452 layersDialog = null; 1453 }); 1454 1455 var r = layersButton.getBoundingClientRect(); 1456 1457 layersDialog.style.width = '140px'; 1458 layersDialog.style.padding = '2px 0px 2px 0px'; 1459 layersDialog.style.border = '1px solid #d0d0d0'; 1460 layersDialog.style.backgroundColor = '#eee'; 1461 layersDialog.style.fontFamily = Editor.defaultHtmlFont; 1462 layersDialog.style.fontSize = '11px'; 1463 layersDialog.style.zIndex = this.toolbarZIndex + 1; 1464 mxUtils.setOpacity(layersDialog, 80); 1465 var origin = mxUtils.getDocumentScrollOrigin(document); 1466 layersDialog.style.left = origin.x + r.left - 1 + 'px'; 1467 layersDialog.style.top = origin.y + r.bottom - 2 + 'px'; 1468 1469 document.body.appendChild(layersDialog); 1470 } 1471 }), Editor.layersImage, mxResources.get('layers') || 'Layers'); 1472 1473 model.addListener(mxEvent.CHANGE, function() 1474 { 1475 layersButton.style.display = (model.getChildCount(model.root) > 1) ? 'inline-block' : 'none'; 1476 }); 1477 1478 layersButton.style.display = (model.getChildCount(model.root) > 1) ? 'inline-block' : 'none'; 1479 } 1480 } 1481 else if (token == 'tags') 1482 { 1483 if (this.tagsEnabled) 1484 { 1485 var tagsButton = addButton(mxUtils.bind(this, function(evt) 1486 { 1487 if (tagsComponent == null) 1488 { 1489 tagsComponent = this.graph.createTagsDialog(mxUtils.bind(this, function() 1490 { 1491 return true; 1492 })); 1493 1494 tagsComponent.div.getElementsByTagName('div')[0].style.position = ''; 1495 tagsComponent.div.style.maxHeight = '160px'; 1496 tagsComponent.div.style.maxWidth = '120px'; 1497 tagsComponent.div.style.padding = '2px'; 1498 tagsComponent.div.style.overflow = 'auto'; 1499 tagsComponent.div.style.height = 'auto'; 1500 tagsComponent.div.style.position = 'fixed'; 1501 tagsComponent.div.style.fontFamily = Editor.defaultHtmlFont; 1502 tagsComponent.div.style.fontSize = '11px'; 1503 tagsComponent.div.style.backgroundColor = '#eee'; 1504 tagsComponent.div.style.color = '#000'; 1505 tagsComponent.div.style.border = '1px solid #d0d0d0'; 1506 tagsComponent.div.style.zIndex = this.toolbarZIndex + 1; 1507 mxUtils.setOpacity(tagsComponent.div, 80); 1508 } 1509 1510 if (tagsDialog != null) 1511 { 1512 tagsDialog.parentNode.removeChild(tagsDialog); 1513 tagsDialog = null; 1514 } 1515 else 1516 { 1517 tagsDialog = tagsComponent.div; 1518 1519 mxEvent.addListener(tagsDialog, 'mouseleave', function() 1520 { 1521 tagsDialog.parentNode.removeChild(tagsDialog); 1522 tagsDialog = null; 1523 }); 1524 1525 var r = tagsButton.getBoundingClientRect(); 1526 var origin = mxUtils.getDocumentScrollOrigin(document); 1527 tagsDialog.style.left = origin.x + r.left - 1 + 'px'; 1528 tagsDialog.style.top = origin.y + r.bottom - 2 + 'px'; 1529 document.body.appendChild(tagsDialog); 1530 tagsComponent.refresh(); 1531 } 1532 }), Editor.tagsImage, mxResources.get('tags') || 'Tags'); 1533 1534 model.addListener(mxEvent.CHANGE, mxUtils.bind(this, function() 1535 { 1536 tagsButton.style.display = (this.graph.getAllTags().length > 0) ? 'inline-block' : 'none'; 1537 })); 1538 1539 tagsButton.style.display = (this.graph.getAllTags().length > 0) ? 'inline-block' : 'none'; 1540 } 1541 } 1542 else if (token == 'lightbox') 1543 { 1544 if (this.lightboxEnabled) 1545 { 1546 addButton(mxUtils.bind(this, function() 1547 { 1548 this.showLightbox(); 1549 }), Editor.fullscreenImage, (mxResources.get('fullscreen') || 'Fullscreen')); 1550 } 1551 } 1552 else if (this.graphConfig['toolbar-buttons'] != null) 1553 { 1554 var def = this.graphConfig['toolbar-buttons'][token]; 1555 1556 if (def != null) 1557 { 1558 def.elem = addButton((def.enabled == null || def.enabled) ? def.handler : function() {}, 1559 def.image, def.title, def.enabled); 1560 } 1561 } 1562 } 1563 1564 if (this.graph.minimumContainerSize != null) 1565 { 1566 this.graph.minimumContainerSize.width = buttonCount * 34; 1567 } 1568 1569 if (this.graphConfig.title != null) 1570 { 1571 var filename = container.ownerDocument.createElement('div'); 1572 filename.style.cssText = 'display:inline-block;position:relative;padding:3px 6px 0 6px;' + 1573 'vertical-align:top;font-family:Helvetica,Arial;font-size:12px;top:4px;cursor:default;' 1574 filename.setAttribute('title', this.graphConfig.title); 1575 mxUtils.write(filename, this.graphConfig.title); 1576 mxUtils.setOpacity(filename, 70); 1577 1578 toolbar.appendChild(filename); 1579 this.filename = filename; 1580 } 1581 1582 this.minToolbarWidth = buttonCount * 34; 1583 var prevBorder = container.style.border; 1584 1585 var enter = mxUtils.bind(this, function() 1586 { 1587 toolbar.style.width = (this.graphConfig['toolbar-position'] == 'inline') ? 'auto' : 1588 Math.max(this.minToolbarWidth, container.offsetWidth) + 'px'; 1589 toolbar.style.border = '1px solid #d0d0d0'; 1590 1591 if (this.graphConfig['toolbar-nohide'] != true) 1592 { 1593 var r = container.getBoundingClientRect(); 1594 1595 // Workaround for position:relative set in ResizeSensor 1596 var origin = mxUtils.getScrollOrigin(document.body) 1597 var b = (document.body.style.position === 'relative') ? document.body.getBoundingClientRect() : 1598 {left: -origin.x, top: -origin.y}; 1599 r = {left: r.left - b.left, top: r.top - b.top, bottom: r.bottom - b.top, right: r.right - b.left}; 1600 1601 toolbar.style.left = r.left + 'px'; 1602 1603 if (this.graphConfig['toolbar-position'] == 'bottom') 1604 { 1605 toolbar.style.top = r.bottom - 1 + 'px'; 1606 } 1607 else 1608 { 1609 if (this.graphConfig['toolbar-position'] != 'inline') 1610 { 1611 toolbar.style.marginTop = -this.toolbarHeight + 'px'; 1612 toolbar.style.top = r.top + 1 + 'px'; 1613 } 1614 else 1615 { 1616 toolbar.style.top = r.top + 'px'; 1617 } 1618 } 1619 1620 if (prevBorder == '1px solid transparent') 1621 { 1622 container.style.border = '1px solid #d0d0d0'; 1623 } 1624 1625 document.body.appendChild(toolbar); 1626 1627 var hideToolbar = mxUtils.bind(this, function() 1628 { 1629 if (toolbar.parentNode != null) 1630 { 1631 toolbar.parentNode.removeChild(toolbar); 1632 } 1633 1634 if (layersDialog != null) 1635 { 1636 layersDialog.parentNode.removeChild(layersDialog); 1637 layersDialog = null; 1638 } 1639 1640 container.style.border = prevBorder; 1641 }); 1642 1643 mxEvent.addListener(document, 'mousemove', function(evt) 1644 { 1645 var source = mxEvent.getSource(evt); 1646 1647 while (source != null) 1648 { 1649 if (source == container || source == toolbar || source == layersDialog) 1650 { 1651 return; 1652 } 1653 1654 source = source.parentNode; 1655 } 1656 1657 hideToolbar(); 1658 }); 1659 1660 mxEvent.addListener(document.body, 'mouseleave', function(evt) 1661 { 1662 hideToolbar(); 1663 }); 1664 } 1665 else 1666 { 1667 toolbar.style.top = -this.toolbarHeight + 'px'; 1668 container.appendChild(toolbar); 1669 } 1670 }); 1671 1672 if (this.graphConfig['toolbar-nohide'] != true) 1673 { 1674 mxEvent.addListener(container, 'mouseenter', enter); 1675 } 1676 else 1677 { 1678 enter(); 1679 } 1680 1681 if (this.responsive && typeof ResizeObserver !== 'undefined') 1682 { 1683 new ResizeObserver(function() 1684 { 1685 if (toolbar.parentNode != null) 1686 { 1687 enter(); 1688 } 1689 }).observe(container) 1690 } 1691}; 1692 1693GraphViewer.prototype.disableButton = function(token) 1694{ 1695 var def = this.graphConfig['toolbar-buttons'][token]; 1696 1697 if (def != null) 1698 { 1699 mxUtils.setOpacity(def.elem, 30); 1700 mxEvent.removeListener(def.elem, 'click', def.handler); 1701 //Workaround to stop highlighting the disabled button 1702 mxEvent.addListener(def.elem, 'mouseenter', function() 1703 { 1704 def.elem.style.backgroundColor = '#eee'; 1705 }); 1706 } 1707}; 1708 1709/** 1710 * Adds event handler for links and lightbox. 1711 */ 1712GraphViewer.prototype.addClickHandler = function(graph, ui) 1713{ 1714 graph.linkPolicy = this.graphConfig.target || graph.linkPolicy; 1715 1716 graph.addClickHandler(this.graphConfig.highlight, mxUtils.bind(this, function(evt, href) 1717 { 1718 if (href == null) 1719 { 1720 var source = mxEvent.getSource(evt); 1721 1722 while (source != graph.container && source != null && href == null) 1723 { 1724 if (source.nodeName.toLowerCase() == 'a') 1725 { 1726 href = source.getAttribute('href'); 1727 } 1728 1729 source = source.parentNode; 1730 } 1731 } 1732 1733 if (ui != null) 1734 { 1735 if (href == null || graph.isCustomLink(href)) 1736 { 1737 mxEvent.consume(evt); 1738 } 1739 else if (!graph.isExternalProtocol(href) && 1740 !graph.isBlankLink(href)) 1741 { 1742 // Hides lightbox if any links are clicked 1743 // Async handling needed for anchors to work 1744 window.setTimeout(function() 1745 { 1746 ui.destroy(); 1747 }, 0); 1748 } 1749 } 1750 else if (href != null && ui == null && graph.isCustomLink(href) && 1751 (mxEvent.isTouchEvent(evt) || !mxEvent.isPopupTrigger(evt)) && 1752 graph.customLinkClicked(href)) 1753 { 1754 // Workaround for text selection in Firefox on Windows 1755 mxUtils.clearSelection(); 1756 mxEvent.consume(evt); 1757 } 1758 }), mxUtils.bind(this, function(evt) 1759 { 1760 if (ui == null && this.lightboxClickEnabled && 1761 (!mxEvent.isTouchEvent(evt) || 1762 this.toolbarItems.length == 0)) 1763 { 1764 this.showLightbox(); 1765 } 1766 })); 1767}; 1768 1769/** 1770 * Adds the given array of stencils to avoid dynamic loading of shapes. 1771 */ 1772GraphViewer.prototype.showLightbox = function(editable, closable, target) 1773{ 1774 if (this.graphConfig.lightbox == 'open' || window.self !== window.top) 1775 { 1776 if (this.lightboxWindow != null && !this.lightboxWindow.closed) 1777 { 1778 this.lightboxWindow.focus(); 1779 } 1780 else 1781 { 1782 editable = (editable != null) ? editable : 1783 ((this.graphConfig.editable != null) ? 1784 this.graphConfig.editable : true); 1785 closable = (closable != null) ? closable : true; 1786 target = (target != null) ? target : 'blank'; 1787 1788 var param = {'client': 1, 'target': target}; 1789 1790 if (editable) 1791 { 1792 param.edit = this.graphConfig.edit || '_blank'; 1793 } 1794 1795 if (closable) 1796 { 1797 param.close = 1; 1798 } 1799 1800 if (this.layersEnabled) 1801 { 1802 param.layers = 1; 1803 } 1804 1805 if (this.tagsEnabled) 1806 { 1807 param.tags = {}; 1808 } 1809 1810 if (this.graphConfig != null && this.graphConfig.nav != false) 1811 { 1812 param.nav = 1; 1813 } 1814 1815 if (this.graphConfig != null && this.graphConfig.highlight != null) 1816 { 1817 param.highlight = this.graphConfig.highlight.substring(1); 1818 } 1819 1820 if (this.currentPage != null && this.currentPage > 0) 1821 { 1822 param.page = this.currentPage; 1823 } 1824 1825 if (typeof window.postMessage !== 'undefined' && (document.documentMode == null || document.documentMode >= 10)) 1826 { 1827 if (this.lightboxWindow == null) 1828 { 1829 mxEvent.addListener(window, 'message', mxUtils.bind(this, function(evt) 1830 { 1831 if (evt.data == 'ready' && evt.source == this.lightboxWindow) 1832 { 1833 this.lightboxWindow.postMessage(this.xml, '*'); 1834 } 1835 })); 1836 } 1837 } 1838 else 1839 { 1840 // Data is pulled from global variable after tab loads 1841 param.data = encodeURIComponent(this.xml); 1842 } 1843 1844 if (urlParams['dev'] == '1') 1845 { 1846 param.dev = '1'; 1847 } 1848 1849 this.lightboxWindow = window.open(((urlParams['dev'] != '1') ? 1850 EditorUi.lightboxHost : 'https://test.draw.io') + 1851 '/#P' + encodeURIComponent(JSON.stringify(param))); 1852 } 1853 } 1854 else 1855 { 1856 this.showLocalLightbox(); 1857 } 1858}; 1859 1860/** 1861 * Adds the given array of stencils to avoid dynamic loading of shapes. 1862 */ 1863GraphViewer.prototype.showLocalLightbox = function() 1864{ 1865 var origin = mxUtils.getDocumentScrollOrigin(document); 1866 var backdrop = document.createElement('div'); 1867 1868 backdrop.style.cssText = 'position:fixed;top:0;left:0;bottom:0;right:0;'; 1869 backdrop.style.zIndex = this.lightboxZIndex; 1870 backdrop.style.backgroundColor = '#000000'; 1871 mxUtils.setOpacity(backdrop, 70); 1872 1873 document.body.appendChild(backdrop); 1874 1875 var closeImg = document.createElement('img'); 1876 closeImg.setAttribute('border', '0'); 1877 closeImg.setAttribute('src', Editor.closeBlackImage); 1878 closeImg.style.cssText = 'position:fixed;top:32px;right:32px;'; 1879 closeImg.style.cursor = 'pointer'; 1880 1881 mxEvent.addListener(closeImg, 'click', function() 1882 { 1883 ui.destroy(); 1884 }); 1885 1886 // LATER: Make possible to assign after instance was created 1887 urlParams['pages'] = '1'; 1888 urlParams['page'] = this.currentPage; 1889 urlParams['page-id'] = this.graphConfig.pageId; 1890 urlParams['layer-ids'] = (this.graphConfig.layerIds != null && this.graphConfig.layerIds.length > 0) 1891 ? this.graphConfig.layerIds.join(' ') : null; 1892 urlParams['nav'] = (this.graphConfig.nav != false) ? '1' : '0'; 1893 urlParams['layers'] = (this.layersEnabled) ? '1' : '0'; 1894 1895 if (this.tagsEnabled) 1896 { 1897 urlParams['tags'] = '{}'; 1898 } 1899 1900 // PostMessage not working and Permission denied for opened access in IE9- 1901 if (document.documentMode == null || document.documentMode >= 10) 1902 { 1903 Editor.prototype.editButtonLink = this.graphConfig.edit; 1904 Editor.prototype.editButtonFunc = this.graphConfig.editFunc; 1905 } 1906 1907 EditorUi.prototype.updateActionStates = function() {}; 1908 EditorUi.prototype.addBeforeUnloadListener = function() {}; 1909 EditorUi.prototype.addChromelessClickHandler = function() {}; 1910 1911 // Workaround for lost reference with same ID is to change 1912 // ID which must be done before calling EditorUi constructor 1913 var previousShadowId = Graph.prototype.shadowId; 1914 Graph.prototype.shadowId = 'lightboxDropShadow'; 1915 1916 var ui = new EditorUi(new Editor(true), document.createElement('div'), true); 1917 ui.editor.editBlankUrl = this.editBlankUrl; 1918 1919 // Overrides instance variable and restores prototype state 1920 ui.editor.graph.shadowId = 'lightboxDropShadow'; 1921 Graph.prototype.shadowId = previousShadowId; 1922 1923 // Disables refresh 1924 ui.refresh = function() {}; 1925 1926 // Handles escape keystroke 1927 var keydownHandler = mxUtils.bind(this, function(evt) 1928 { 1929 if (evt.keyCode == 27 /* Escape */) 1930 { 1931 ui.destroy(); 1932 } 1933 }); 1934 1935 var destroy = ui.destroy; 1936 ui.destroy = function() 1937 { 1938 mxEvent.removeListener(document.documentElement, 'keydown', keydownHandler); 1939 document.body.removeChild(backdrop); 1940 document.body.removeChild(closeImg); 1941 document.body.style.overflow = 'auto'; 1942 GraphViewer.resizeSensorEnabled = true; 1943 1944 destroy.apply(this, arguments); 1945 }; 1946 1947 var graph = ui.editor.graph; 1948 var lightbox = graph.container; 1949 lightbox.style.overflow = 'hidden'; 1950 1951 if (this.lightboxChrome) 1952 { 1953 lightbox.style.border = '1px solid #c0c0c0'; 1954 lightbox.style.margin = '40px'; 1955 1956 // Installs the keystroke listener in the target 1957 mxEvent.addListener(document.documentElement, 'keydown', keydownHandler); 1958 } 1959 else 1960 { 1961 backdrop.style.display = 'none'; 1962 closeImg.style.display = 'none'; 1963 } 1964 1965 // Handles relative images 1966 var self = this; 1967 1968 graph.getImageFromBundles = function(key) 1969 { 1970 return self.getImageUrl(key); 1971 }; 1972 1973 // Handles relative images in print output and temporary graphs 1974 var uiCreateTemporaryGraph = ui.createTemporaryGraph; 1975 1976 ui.createTemporaryGraph = function() 1977 { 1978 var newGraph = uiCreateTemporaryGraph.apply(this, arguments); 1979 1980 newGraph.getImageFromBundles = function(key) 1981 { 1982 return self.getImageUrl(key); 1983 }; 1984 1985 return newGraph; 1986 }; 1987 1988 if (this.graphConfig.move) 1989 { 1990 graph.isMoveCellsEvent = function(evt) 1991 { 1992 return true; 1993 }; 1994 } 1995 1996 mxUtils.setPrefixedStyle(lightbox.style, 'border-radius', '4px'); 1997 lightbox.style.position = 'fixed'; 1998 1999 GraphViewer.resizeSensorEnabled = false; 2000 document.body.style.overflow = 'hidden'; 2001 2002 // Workaround for possible rendering issues 2003 if (!mxClient.IS_SF && !mxClient.IS_EDGE) 2004 { 2005 mxUtils.setPrefixedStyle(lightbox.style, 'transform', 'rotateY(90deg)'); 2006 mxUtils.setPrefixedStyle(lightbox.style, 'transition', 'all .25s ease-in-out'); 2007 } 2008 2009 this.addClickHandler(graph, ui); 2010 2011 window.setTimeout(mxUtils.bind(this, function() 2012 { 2013 // Disables focus border in Chrome 2014 lightbox.style.outline = 'none'; 2015 lightbox.style.zIndex = this.lightboxZIndex; 2016 closeImg.style.zIndex = this.lightboxZIndex; 2017 2018 document.body.appendChild(lightbox); 2019 document.body.appendChild(closeImg); 2020 2021 ui.setFileData(this.xml); 2022 2023 mxUtils.setPrefixedStyle(lightbox.style, 'transform', 'rotateY(0deg)'); 2024 ui.chromelessToolbar.style.bottom = 60 + 'px'; 2025 ui.chromelessToolbar.style.zIndex = this.lightboxZIndex; 2026 2027 // Workaround for clipping in IE11- 2028 document.body.appendChild(ui.chromelessToolbar); 2029 2030 ui.getEditBlankXml = mxUtils.bind(this, function() 2031 { 2032 return this.xml; 2033 }); 2034 2035 ui.lightboxFit(); 2036 ui.chromelessResize(); 2037 this.showLayers(graph, this.graph); 2038 2039 // Click on backdrop closes lightbox 2040 mxEvent.addListener(backdrop, 'click', function() 2041 { 2042 ui.destroy(); 2043 }); 2044 }), 0); 2045 2046 return ui; 2047}; 2048 2049GraphViewer.prototype.updateTitle = function(title) 2050{ 2051 title = title || ''; 2052 2053 if (this.showTitleAsTooltip && this.graph != null && this.graph.container != null) 2054 { 2055 this.graph.container.setAttribute('title', title); 2056 } 2057 2058 if (this.filename != null) 2059 { 2060 this.filename.innerHTML = ''; 2061 mxUtils.write(this.filename, title); 2062 this.filename.setAttribute('title', title); 2063 } 2064}; 2065 2066/** 2067 * 2068 */ 2069GraphViewer.processElements = function(classname) 2070{ 2071 mxUtils.forEach(GraphViewer.getElementsByClassName(classname || 'mxgraph'), function(div) 2072 { 2073 try 2074 { 2075 div.innerHTML = ''; 2076 GraphViewer.createViewerForElement(div); 2077 } 2078 catch (e) 2079 { 2080 div.innerHTML = e.message; 2081 2082 if (window.console != null) 2083 { 2084 console.error(e); 2085 } 2086 } 2087 }); 2088}; 2089 2090/** 2091 * Adds the given array of stencils to avoid dynamic loading of shapes. 2092 */ 2093GraphViewer.getElementsByClassName = function(classname) 2094{ 2095 if (document.getElementsByClassName) 2096 { 2097 var divs = document.getElementsByClassName(classname); 2098 2099 // Workaround for changing divs while processing 2100 var result = []; 2101 2102 for (var i = 0; i < divs.length; i++) 2103 { 2104 result.push(divs[i]); 2105 } 2106 2107 return result; 2108 } 2109 else 2110 { 2111 var tmp = document.getElementsByTagName('*'); 2112 var divs = []; 2113 2114 for (var i = 0; i < tmp.length; i++) 2115 { 2116 var cls = tmp[i].className; 2117 2118 if (cls != null && cls.length > 0) 2119 { 2120 var tokens = cls.split(' '); 2121 2122 if (mxUtils.indexOf(tokens, classname) >= 0) 2123 { 2124 divs.push(tmp[i]); 2125 } 2126 } 2127 } 2128 2129 return divs; 2130 } 2131}; 2132 2133/** 2134 * Adds the given array of stencils to avoid dynamic loading of shapes. 2135 */ 2136GraphViewer.createViewerForElement = function(element, callback) 2137{ 2138 var data = element.getAttribute('data-mxgraph'); 2139 2140 if (data != null) 2141 { 2142 var config = JSON.parse(data); 2143 2144 var createViewer = function(xml) 2145 { 2146 var xmlDoc = mxUtils.parseXml(xml); 2147 var viewer = new GraphViewer(element, xmlDoc.documentElement, config); 2148 2149 if (callback != null) 2150 { 2151 callback(viewer); 2152 } 2153 }; 2154 2155 if (config.url != null) 2156 { 2157 GraphViewer.getUrl(config.url, function(xml) 2158 { 2159 createViewer(xml); 2160 }); 2161 } 2162 else 2163 { 2164 createViewer(config.xml); 2165 } 2166 } 2167}; 2168 2169/** 2170 * Adds event if grid size is changed. 2171 */ 2172GraphViewer.initCss = function() 2173{ 2174 try 2175 { 2176 var style = document.createElement('style') 2177 style.type = 'text/css'; 2178 style.innerHTML = ['div.mxTooltip {', 2179 '-webkit-box-shadow: 3px 3px 12px #C0C0C0;', 2180 '-moz-box-shadow: 3px 3px 12px #C0C0C0;', 2181 'box-shadow: 3px 3px 12px #C0C0C0;', 2182 'background: #FFFFCC;', 2183 'border-style: solid;', 2184 'border-width: 1px;', 2185 'border-color: black;', 2186 'font-family: Arial;', 2187 'font-size: 8pt;', 2188 'position: absolute;', 2189 'cursor: default;', 2190 'padding: 4px;', 2191 'color: black;}', 2192 'td.mxPopupMenuIcon div {', 2193 'width:16px;', 2194 'height:16px;}', 2195 'html div.mxPopupMenu {', 2196 '-webkit-box-shadow:2px 2px 3px #d5d5d5;', 2197 '-moz-box-shadow:2px 2px 3px #d5d5d5;', 2198 'box-shadow:2px 2px 3px #d5d5d5;', 2199 '_filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=2, OffY=2, Color=\'#d0d0d0\',Positive=\'true\');', 2200 'background:white;', 2201 'position:absolute;', 2202 'border:3px solid #e7e7e7;', 2203 'padding:3px;}', 2204 'html table.mxPopupMenu {', 2205 'border-collapse:collapse;', 2206 'margin:0px;}', 2207 'html td.mxPopupMenuItem {', 2208 'padding:7px 30px 7px 30px;', 2209 'font-family:Helvetica Neue,Helvetica,Arial Unicode MS,Arial;', 2210 'font-size:10pt;}', 2211 'html td.mxPopupMenuIcon {', 2212 'background-color:white;', 2213 'padding:0px;}', 2214 'td.mxPopupMenuIcon .geIcon {', 2215 'padding:2px;', 2216 'padding-bottom:4px;', 2217 'margin:2px;', 2218 'border:1px solid transparent;', 2219 'opacity:0.5;', 2220 '_width:26px;', 2221 '_height:26px;}', 2222 'td.mxPopupMenuIcon .geIcon:hover {', 2223 'border:1px solid gray;', 2224 'border-radius:2px;', 2225 'opacity:1;}', 2226 'html tr.mxPopupMenuItemHover {', 2227 'background-color: #eeeeee;', 2228 'color: black;}', 2229 'table.mxPopupMenu hr {', 2230 'color:#cccccc;', 2231 'background-color:#cccccc;', 2232 'border:none;', 2233 'height:1px;}', 2234 'table.mxPopupMenu tr { font-size:4pt;}', 2235 // Modified to only apply to the print dialog 2236 '.geDialog { font-family:Helvetica Neue,Helvetica,Arial Unicode MS,Arial;', 2237 'font-size:10pt;', 2238 'border:none;', 2239 'margin:0px;}', 2240 // These are required for the print dialog 2241 '.geDialog { position:absolute; background:white; overflow:hidden; padding:30px; border:1px solid #acacac; -webkit-box-shadow:0px 0px 2px 2px #d5d5d5; -moz-box-shadow:0px 0px 2px 2px #d5d5d5; box-shadow:0px 0px 2px 2px #d5d5d5; _filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=2, OffY=2, Color=\'#d5d5d5\', Positive=\'true\'); z-index: 2;}.geDialogClose { position:absolute; width:9px; height:9px; opacity:0.5; cursor:pointer; _filter:alpha(opacity=50);}.geDialogClose:hover { opacity:1;}.geDialogTitle { box-sizing:border-box; white-space:nowrap; background:rgb(229, 229, 229); border-bottom:1px solid rgb(192, 192, 192); font-size:15px; font-weight:bold; text-align:center; color:rgb(35, 86, 149);}.geDialogFooter { background:whiteSmoke; white-space:nowrap; text-align:right; box-sizing:border-box; border-top:1px solid #e5e5e5; color:darkGray;}', 2242 '.geBtn { background-color: #f5f5f5; border-radius: 2px; border: 1px solid #d8d8d8; color: #333; cursor: default; font-size: 11px; font-weight: bold; height: 29px; line-height: 27px; margin: 0 0 0 8px; min-width: 72px; outline: 0; padding: 0 8px; cursor: pointer;}.geBtn:hover, .geBtn:focus { -webkit-box-shadow: 0px 1px 1px rgba(0,0,0,0.1); -moz-box-shadow: 0px 1px 1px rgba(0,0,0,0.1); box-shadow: 0px 1px 1px rgba(0,0,0,0.1); border: 1px solid #c6c6c6; background-color: #f8f8f8; background-image: linear-gradient(#f8f8f8 0px,#f1f1f1 100%); color: #111;}.geBtn:disabled { opacity: .5;}.gePrimaryBtn { background-color: #4d90fe; background-image: linear-gradient(#4d90fe 0px,#4787ed 100%); border: 1px solid #3079ed; color: #fff;}.gePrimaryBtn:hover, .gePrimaryBtn:focus { background-color: #357ae8; background-image: linear-gradient(#4d90fe 0px,#357ae8 100%); border: 1px solid #2f5bb7; color: #fff;}.gePrimaryBtn:disabled { opacity: .5;}'].join('\n'); 2243 document.getElementsByTagName('head')[0].appendChild(style); 2244 } 2245 catch (e) 2246 { 2247 // ignore 2248 } 2249}; 2250 2251/** 2252 * Lookup for URLs. 2253 */ 2254GraphViewer.cachedUrls = {}; 2255 2256/** 2257 * Workaround for unsupported CORS in IE9 XHR 2258 */ 2259GraphViewer.getUrl = function(url, onload, onerror) 2260{ 2261 if (GraphViewer.cachedUrls[url] != null) 2262 { 2263 onload(GraphViewer.cachedUrls[url]); 2264 } 2265 else 2266 { 2267 var xhr = (navigator.userAgent != null && navigator.userAgent.indexOf('MSIE 9') > 0) ? 2268 new XDomainRequest() : new XMLHttpRequest(); 2269 xhr.open('GET', url); 2270 2271 xhr.onload = function() 2272 { 2273 onload((xhr.getText != null) ? xhr.getText() : xhr.responseText); 2274 }; 2275 2276 xhr.onerror = onerror; 2277 xhr.send(); 2278 } 2279}; 2280 2281/** 2282 * Redirects editing to absolue URLs. 2283 */ 2284GraphViewer.resizeSensorEnabled = true; 2285 2286/** 2287 * Redirects editing to absolue URLs. 2288 */ 2289GraphViewer.useResizeSensor = true; 2290 2291/** 2292 * Copyright Marc J. Schmidt. See the LICENSE file at the top-level 2293 * directory of this distribution and at 2294 * https://github.com/marcj/css-element-queries/blob/master/LICENSE. 2295 */ 2296(function() { 2297 2298 // Only used for the dirty checking, so the event callback count is limted to max 1 call per fps per sensor. 2299 // In combination with the event based resize sensor this saves cpu time, because the sensor is too fast and 2300 // would generate too many unnecessary events. 2301 var requestAnimationFrame = window.requestAnimationFrame || 2302 window.mozRequestAnimationFrame || 2303 window.webkitRequestAnimationFrame || 2304 function (fn) { 2305 return window.setTimeout(fn, 20); 2306 }; 2307 2308 /** 2309 * Class for dimension change detection. 2310 * 2311 * @param {Element|Element[]|Elements|jQuery} element 2312 * @param {Function} callback 2313 * 2314 * @constructor 2315 */ 2316 var ResizeSensor = function(element, fn) { 2317 2318 var callback = function() 2319 { 2320 if (GraphViewer.resizeSensorEnabled) 2321 { 2322 fn(); 2323 } 2324 }; 2325 2326 /** 2327 * 2328 * @constructor 2329 */ 2330 function EventQueue() { 2331 this.q = []; 2332 this.add = function(ev) { 2333 this.q.push(ev); 2334 }; 2335 2336 var i, j; 2337 this.call = function() { 2338 for (i = 0, j = this.q.length; i < j; i++) { 2339 this.q[i].call(); 2340 } 2341 }; 2342 } 2343 2344 /** 2345 * @param {HTMLElement} element 2346 * @param {String} prop 2347 * @returns {String|Number} 2348 */ 2349 function getComputedStyle(element, prop) { 2350 if (element.currentStyle) { 2351 return element.currentStyle[prop]; 2352 } else if (window.getComputedStyle) { 2353 return window.getComputedStyle(element, null).getPropertyValue(prop); 2354 } else { 2355 return element.style[prop]; 2356 } 2357 } 2358 2359 /** 2360 * 2361 * @param {HTMLElement} element 2362 * @param {Function} resized 2363 */ 2364 function attachResizeEvent(element, resized) { 2365 if (!element.resizedAttached) { 2366 element.resizedAttached = new EventQueue(); 2367 element.resizedAttached.add(resized); 2368 } else if (element.resizedAttached) { 2369 element.resizedAttached.add(resized); 2370 return; 2371 } 2372 2373 element.resizeSensor = document.createElement('div'); 2374 element.resizeSensor.className = 'resize-sensor'; 2375 var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden;'; 2376 var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;'; 2377 2378 element.resizeSensor.style.cssText = style; 2379 element.resizeSensor.innerHTML = 2380 '<div class="resize-sensor-expand" style="' + style + '">' + 2381 '<div style="' + styleChild + '"></div>' + 2382 '</div>' + 2383 '<div class="resize-sensor-shrink" style="' + style + '">' + 2384 '<div style="' + styleChild + ' width: 200%; height: 200%"></div>' + 2385 '</div>'; 2386 element.appendChild(element.resizeSensor); 2387 2388 // FIXME: Should not change element style 2389 if (getComputedStyle(element, 'position') == 'static') { 2390 element.style.position = 'relative'; 2391 } 2392 2393 var expand = element.resizeSensor.childNodes[0]; 2394 var expandChild = expand.childNodes[0]; 2395 var shrink = element.resizeSensor.childNodes[1]; 2396 2397 var reset = function() { 2398 expandChild.style.width = 100000 + 'px'; 2399 expandChild.style.height = 100000 + 'px'; 2400 2401 expand.scrollLeft = 100000; 2402 expand.scrollTop = 100000; 2403 2404 shrink.scrollLeft = 100000; 2405 shrink.scrollTop = 100000; 2406 }; 2407 2408 reset(); 2409 var dirty = false; 2410 2411 var dirtyChecking = function(){ 2412 if (!element.resizedAttached) return; 2413 2414 if (dirty) { 2415 element.resizedAttached.call(); 2416 dirty = false; 2417 } 2418 2419 requestAnimationFrame(dirtyChecking); 2420 }; 2421 2422 requestAnimationFrame(dirtyChecking); 2423 var lastWidth, lastHeight; 2424 var cachedWidth, cachedHeight; //useful to not query offsetWidth twice 2425 2426 var onScroll = function() { 2427 if ((cachedWidth = element.offsetWidth) != lastWidth || (cachedHeight = element.offsetHeight) != lastHeight) { 2428 dirty = true; 2429 2430 lastWidth = cachedWidth; 2431 lastHeight = cachedHeight; 2432 } 2433 reset(); 2434 }; 2435 2436 var addEvent = function(el, name, cb) { 2437 if (el.attachEvent) { 2438 el.attachEvent('on' + name, cb); 2439 } else { 2440 el.addEventListener(name, cb); 2441 } 2442 }; 2443 2444 addEvent(expand, 'scroll', onScroll); 2445 addEvent(shrink, 'scroll', onScroll); 2446 } 2447 2448 var elementType = Object.prototype.toString.call(element); 2449 var isCollectionTyped = ('[object Array]' === elementType 2450 || ('[object NodeList]' === elementType) 2451 || ('[object HTMLCollection]' === elementType) 2452 || ('undefined' !== typeof jQuery && element instanceof jQuery) //jquery 2453 || ('undefined' !== typeof Elements && element instanceof Elements) //mootools 2454 ); 2455 2456 if (isCollectionTyped) { 2457 var i = 0, j = element.length; 2458 for (; i < j; i++) { 2459 attachResizeEvent(element[i], callback); 2460 } 2461 } else { 2462 attachResizeEvent(element, callback); 2463 } 2464 2465 this.detach = function() { 2466 if (isCollectionTyped) { 2467 var i = 0, j = element.length; 2468 for (; i < j; i++) { 2469 ResizeSensor.detach(element[i]); 2470 } 2471 } else { 2472 ResizeSensor.detach(element); 2473 } 2474 }; 2475 }; 2476 2477 ResizeSensor.detach = function(element) { 2478 if (element.resizeSensor) { 2479 element.removeChild(element.resizeSensor); 2480 delete element.resizeSensor; 2481 delete element.resizedAttached; 2482 } 2483 }; 2484 2485 window.ResizeSensor = ResizeSensor; 2486})(); 2487