GraphViewer.js 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032
  1. /**
  2. * Copyright (c) 2006-2016, JGraph Ltd
  3. */
  4. /**
  5. * No CSS and resources available in embed mode. Parameters and docs:
  6. * https://desk.draw.io/solution/articles/16000042542-how-to-embed-html-
  7. */
  8. GraphViewer = function(container, xmlNode, graphConfig)
  9. {
  10. this.init(container, xmlNode, graphConfig);
  11. };
  12. // Editor inherits from mxEventSource
  13. mxUtils.extend(GraphViewer, mxEventSource);
  14. /**
  15. * Redirects editing to absolue URLs.
  16. */
  17. GraphViewer.prototype.editBlankUrl = 'https://www.draw.io/';
  18. /**
  19. * Base URL for relative images.
  20. */
  21. GraphViewer.prototype.imageBaseUrl = 'https://www.draw.io/';
  22. /**
  23. * Redirects editing to absolue URLs.
  24. */
  25. GraphViewer.prototype.toolbarHeight = (document.compatMode == 'BackCompat') ? 28 : 30;
  26. /**
  27. * Redirects editing to absolue URLs.
  28. */
  29. GraphViewer.prototype.lightboxChrome = true;
  30. /**
  31. * Redirects editing to absolue URLs.
  32. */
  33. GraphViewer.prototype.lightboxZIndex = 999;
  34. /**
  35. * Redirects editing to absolue URLs.
  36. */
  37. GraphViewer.prototype.toolbarZIndex = 999;
  38. /**
  39. * If automatic fit should be enabled if zoom is disabled. Default is true.
  40. */
  41. GraphViewer.prototype.autoFit = true;
  42. /**
  43. * If the diagram should be centered. Default is false.
  44. */
  45. GraphViewer.prototype.center = false;
  46. /**
  47. * Specifies if zooming in for auto fit is allowed. Default is false.
  48. */
  49. GraphViewer.prototype.allowZoomIn = false;
  50. /**
  51. * Whether the title should be shown as a tooltip if the toolbar is disabled.
  52. * Default is false.
  53. */
  54. GraphViewer.prototype.showTitleAsTooltip = false;
  55. /**
  56. * Specifies if the constructur should delay the rendering if the container
  57. * is not visible by default.
  58. */
  59. GraphViewer.prototype.checkVisibleState = true;
  60. /**
  61. * Defines the minimum height of the container. Default is 28.
  62. */
  63. GraphViewer.prototype.minHeight = 28;
  64. /**
  65. * Defines the minimum width of the container. Default is 100.
  66. */
  67. GraphViewer.prototype.minWidth = 100;
  68. /**
  69. * Initializes the viewer.
  70. */
  71. GraphViewer.prototype.init = function(container, xmlNode, graphConfig)
  72. {
  73. this.graphConfig = (graphConfig != null) ? graphConfig : {};
  74. this.autoFit = (this.graphConfig['auto-fit'] != null) ?
  75. this.graphConfig['auto-fit'] : this.autoFit;
  76. this.allowZoomIn = (this.graphConfig['allow-zoom-in'] != null) ?
  77. this.graphConfig['allow-zoom-in'] : this.allowZoomIn;
  78. this.center = (this.graphConfig['center'] != null) ?
  79. this.graphConfig['center'] : this.center;
  80. this.checkVisibleState = (this.graphConfig['check-visible-state'] != null) ?
  81. this.graphConfig['check-visible-state'] : this.checkVisibleState;
  82. this.toolbarItems = (this.graphConfig.toolbar != null) ?
  83. this.graphConfig.toolbar.split(' ') : [];
  84. this.zoomEnabled = mxUtils.indexOf(this.toolbarItems, 'zoom') >= 0;
  85. this.layersEnabled = mxUtils.indexOf(this.toolbarItems, 'layers') >= 0;
  86. this.lightboxEnabled = mxUtils.indexOf(this.toolbarItems, 'lightbox') >= 0;
  87. this.lightboxClickEnabled = this.graphConfig.lightbox != false;
  88. this.initialWidth = (container != null) ? container.style.width : null;
  89. this.widthIsEmpty = (this.initialWidth != null) ? this.initialWidth == '' : true;
  90. this.currentPage = parseInt(this.graphConfig.page) || 0;
  91. this.pageId = this.graphConfig.pageId;
  92. this.editor = null;
  93. if (this.graphConfig['toolbar-position'] == 'inline')
  94. {
  95. this.minHeight += this.toolbarHeight;
  96. }
  97. if (xmlNode != null)
  98. {
  99. this.xmlDocument = xmlNode.ownerDocument;
  100. this.xmlNode = xmlNode;
  101. this.xml = mxUtils.getXml(xmlNode);
  102. if (container != null)
  103. {
  104. var render = mxUtils.bind(this, function()
  105. {
  106. this.graph = new Graph(container);
  107. this.graph.defaultPageBackgroundColor = 'transparent';
  108. this.graph.transparentBackground = false;
  109. if (this.graphConfig.move)
  110. {
  111. this.graph.isMoveCellsEvent = function(evt)
  112. {
  113. return true;
  114. };
  115. }
  116. // Adds lightbox and link handling for shapes
  117. if (this.lightboxClickEnabled)
  118. {
  119. container.style.cursor = 'pointer';
  120. }
  121. // Hack for using EditorUi methods on the graph instance
  122. this.editor = new Editor(true, null, null, this.graph);
  123. this.editor.editBlankUrl = this.editBlankUrl;
  124. this.graph.lightbox = true;
  125. this.graph.centerZoom = false;
  126. this.graph.autoExtend = false;
  127. this.graph.autoScroll = false;
  128. this.graph.setEnabled(false);
  129. if (this.graphConfig['toolbar-nohide'] == true)
  130. {
  131. this.editor.defaultGraphOverflow = 'visible';
  132. }
  133. //Extract graph model from html & svg formats
  134. this.xmlNode = this.editor.extractGraphModel(this.xmlNode, true);
  135. // Handles relative images
  136. var self = this;
  137. this.graph.getImageFromBundles = function(key)
  138. {
  139. return self.getImageUrl(key);
  140. };
  141. if (mxClient.IS_SVG)
  142. {
  143. // LATER: Add shadow for labels in graph.container (eg. math, NO_FO), scaling
  144. this.graph.addSvgShadow(this.graph.view.canvas.ownerSVGElement, null, true);
  145. }
  146. // Adds page placeholders
  147. if (xmlNode.nodeName == 'mxfile')
  148. {
  149. var diagrams = xmlNode.getElementsByTagName('diagram');
  150. if (diagrams.length > 0)
  151. {
  152. //Find the page index if the pageId is provided
  153. if (this.pageId != null)
  154. {
  155. for (var i = 0; i < diagrams.length; i++)
  156. {
  157. if (this.pageId == diagrams[i].getAttribute('id'))
  158. {
  159. this.currentPage = i;
  160. break;
  161. }
  162. }
  163. }
  164. var graphGetGlobalVariable = this.graph.getGlobalVariable;
  165. var self = this;
  166. this.graph.getGlobalVariable = function(name)
  167. {
  168. var diagram = diagrams[self.currentPage];
  169. if (name == 'page')
  170. {
  171. return diagram.getAttribute('name') || 'Page-' + (self.currentPage + 1);
  172. }
  173. else if (name == 'pagenumber')
  174. {
  175. return self.currentPage + 1;
  176. }
  177. else if (name == 'pagecount')
  178. {
  179. return diagrams.length;
  180. }
  181. return graphGetGlobalVariable.apply(this, arguments);
  182. };
  183. }
  184. }
  185. this.diagrams = [];
  186. var lastXmlNode = null;
  187. this.selectPage = function(number)
  188. {
  189. if(this.handlingResize)
  190. return;
  191. this.currentPage = mxUtils.mod(number, this.diagrams.length);
  192. this.updateGraphXml(Editor.parseDiagramNode(this.diagrams[this.currentPage]));
  193. };
  194. this.selectPageById = function(id)
  195. {
  196. var found = false;
  197. for (var i = 0; i < this.diagrams.length; i++)
  198. {
  199. if (this.diagrams[i].getAttribute('id') == id)
  200. {
  201. this.selectPage(i);
  202. found = true;
  203. break;
  204. }
  205. }
  206. return found;
  207. };
  208. var update = mxUtils.bind(this, function()
  209. {
  210. if (this.xmlNode == null || this.xmlNode.nodeName != 'mxfile')
  211. {
  212. this.diagrams = [];
  213. }
  214. if (this.xmlNode != lastXmlNode)
  215. {
  216. this.diagrams = this.xmlNode.getElementsByTagName('diagram');
  217. lastXmlNode = this.xmlNode;
  218. }
  219. });
  220. // LATER: Add event for setGraphXml
  221. this.addListener('xmlNodeChanged', update);
  222. update();
  223. // Passes current page via urlParams global variable
  224. // to let the parser know which page we're using
  225. urlParams['page'] = self.currentPage;
  226. this.graph.getModel().beginUpdate();
  227. try
  228. {
  229. // Required for correct parsing of fold parameter
  230. urlParams['nav'] = (this.graphConfig.nav != false) ? '1' : '0';
  231. this.editor.setGraphXml(this.xmlNode);
  232. this.graph.border = (this.graphConfig.border != null) ? this.graphConfig.border : 8;
  233. this.graph.view.scale = this.graphConfig.zoom || 1;
  234. }
  235. finally
  236. {
  237. this.graph.getModel().endUpdate();
  238. }
  239. // Adds left-button panning only if scrollbars are visible
  240. this.graph.panningHandler.useLeftButtonForPanning = true;
  241. this.graph.panningHandler.isForcePanningEvent = function(me)
  242. {
  243. return !mxEvent.isPopupTrigger(me.getEvent()) &&
  244. this.graph.container.style.overflow == 'auto';
  245. };
  246. this.graph.panningHandler.usePopupTrigger = false;
  247. this.graph.panningHandler.pinchEnabled = false;
  248. this.graph.panningHandler.ignoreCell = true;
  249. this.graph.setPanning(false);
  250. this.addSizeHandler();
  251. this.showLayers(this.graph);
  252. this.addClickHandler(this.graph);
  253. this.graph.setTooltips(this.graphConfig.tooltips != false);
  254. this.graph.initialViewState = {
  255. translate: this.graph.view.translate.clone(),
  256. scale: this.graph.view.scale
  257. };
  258. var self = this;
  259. this.graph.customLinkClicked = function(href)
  260. {
  261. var done = true;
  262. if (href.substring(0, 13) == 'data:page/id,')
  263. {
  264. var comma = href.indexOf(',');
  265. if (!self.selectPageById(href.substring(comma + 1)))
  266. {
  267. done = false;
  268. alert(mxResources.get('pageNotFound') || 'Page not found');
  269. }
  270. }
  271. else
  272. {
  273. this.handleCustomLink(href);
  274. }
  275. return done;
  276. };
  277. if (this.graphConfig.toolbar != null)
  278. {
  279. this.addToolbar();
  280. }
  281. else if (this.graphConfig.title != null && this.showTitleAsTooltip)
  282. {
  283. container.setAttribute('title', this.graphConfig.title);
  284. }
  285. this.fireEvent(new mxEventObject('render'));
  286. });
  287. var MutObs = window.MutationObserver ||
  288. window.WebKitMutationObserver ||
  289. window.MozMutationObserver;
  290. if (this.checkVisibleState && container.offsetWidth == 0 && typeof MutObs !== 'undefined')
  291. {
  292. // Delayed rendering if inside hidden container and event available
  293. var par = this.getObservableParent(container);
  294. var observer = new MutObs(mxUtils.bind(this, function(mutation)
  295. {
  296. if (container.offsetWidth > 0)
  297. {
  298. observer.disconnect();
  299. render();
  300. }
  301. }));
  302. observer.observe(par, {attributes: true});
  303. }
  304. else
  305. {
  306. // Immediate rendering in all other cases
  307. render();
  308. }
  309. }
  310. }
  311. };
  312. /**
  313. *
  314. */
  315. GraphViewer.prototype.getObservableParent = function(container)
  316. {
  317. var node = container.parentNode;
  318. while (node != document.body && node.parentNode != null &&
  319. mxUtils.getCurrentStyle(node).display !== 'none')
  320. {
  321. node = node.parentNode;
  322. }
  323. return node;
  324. };
  325. /**
  326. *
  327. */
  328. GraphViewer.prototype.getImageUrl = function(url)
  329. {
  330. if (url != null && url.substring(0, 7) != 'http://' &&
  331. url.substring(0, 8) != 'https://' && url.substring(0, 10) != 'data:image')
  332. {
  333. if (url.charAt(0) == '/')
  334. {
  335. url = url.substring(1, url.length);
  336. }
  337. url = this.imageBaseUrl + url;
  338. }
  339. return url;
  340. };
  341. /**
  342. *
  343. */
  344. GraphViewer.prototype.setXmlNode = function(xmlNode)
  345. {
  346. this.xmlDocument = xmlNode.ownerDocument;
  347. this.xml = mxUtils.getXml(xmlNode);
  348. this.xmlNode = xmlNode;
  349. this.updateGraphXml(xmlNode);
  350. this.fireEvent(new mxEventObject('xmlNodeChanged'));
  351. };
  352. /**
  353. *
  354. */
  355. GraphViewer.prototype.setFileNode = function(xmlNode)
  356. {
  357. if (this.xmlNode == null)
  358. {
  359. this.xmlDocument = xmlNode.ownerDocument;
  360. this.xml = mxUtils.getXml(xmlNode);
  361. this.xmlNode = xmlNode;
  362. }
  363. this.setGraphXml(xmlNode);
  364. };
  365. /**
  366. *
  367. */
  368. GraphViewer.prototype.updateGraphXml = function(xmlNode)
  369. {
  370. this.setGraphXml(xmlNode);
  371. this.fireEvent(new mxEventObject('graphChanged'));
  372. };
  373. /**
  374. *
  375. */
  376. GraphViewer.prototype.setGraphXml = function(xmlNode)
  377. {
  378. if (this.graph != null)
  379. {
  380. this.graph.view.translate = new mxPoint();
  381. this.graph.view.scale = 1;
  382. this.graph.getModel().clear();
  383. this.editor.setGraphXml(xmlNode);
  384. // Restores initial CSS state
  385. if (this.widthIsEmpty)
  386. {
  387. this.graph.container.style.width = '';
  388. this.graph.container.style.height = '';
  389. }
  390. else
  391. {
  392. this.graph.container.style.width = this.initialWidth;
  393. }
  394. this.positionGraph();
  395. this.graph.initialViewState = {
  396. translate: this.graph.view.translate.clone(),
  397. scale: this.graph.view.scale
  398. };
  399. }
  400. };
  401. /**
  402. *
  403. */
  404. GraphViewer.prototype.addSizeHandler = function()
  405. {
  406. var container = this.graph.container;
  407. var bounds = this.graph.getGraphBounds();
  408. var updatingOverflow = false;
  409. if (this.graphConfig['toolbar-nohide'] != true)
  410. {
  411. container.style.overflow = 'hidden';
  412. }
  413. else
  414. {
  415. container.style.overflow = 'visible';
  416. }
  417. var updateOverflow = mxUtils.bind(this, function()
  418. {
  419. if (!updatingOverflow)
  420. {
  421. updatingOverflow = true;
  422. var tmp = this.graph.getGraphBounds();
  423. if (this.graphConfig['toolbar-nohide'] != true)
  424. {
  425. // Shows scrollbars if graph is larger than available width
  426. if (tmp.width + 2 * this.graph.border > container.offsetWidth - 2)
  427. {
  428. container.style.overflow = 'auto';
  429. }
  430. else
  431. {
  432. container.style.overflow = 'hidden';
  433. }
  434. }
  435. else
  436. {
  437. container.style.overflow = 'visible';
  438. }
  439. if (this.toolbar != null && this.graphConfig['toolbar-nohide'] != true)
  440. {
  441. var r = container.getBoundingClientRect();
  442. // Workaround for position:relative set in ResizeSensor
  443. var origin = mxUtils.getScrollOrigin(document.body)
  444. var b = (document.body.style.position === 'relative') ?
  445. document.body.getBoundingClientRect() :
  446. {left: -origin.x, top: -origin.y};
  447. r = {left: r.left - b.left, top: r.top - b.top, bottom: r.bottom - b.top, right: r.right - b.left};
  448. this.toolbar.style.left = r.left + 'px';
  449. if (this.graphConfig['toolbar-position'] == 'bottom')
  450. {
  451. this.toolbar.style.top = r.bottom - 1 + 'px';
  452. }
  453. else
  454. {
  455. if (this.graphConfig['toolbar-position'] != 'inline')
  456. {
  457. this.toolbar.style.width = Math.max(this.minToolbarWidth, container.offsetWidth) + 'px';
  458. this.toolbar.style.top = r.top + 1 + 'px';
  459. }
  460. else
  461. {
  462. this.toolbar.style.top = r.top + 'px';
  463. }
  464. }
  465. }
  466. updatingOverflow = false;
  467. }
  468. });
  469. var lastOffsetWidth = null;
  470. var cachedOffsetWidth = null;
  471. this.handlingResize = false;
  472. // Installs function on instance
  473. this.fitGraph = mxUtils.bind(this, function(maxScale)
  474. {
  475. var cachedOffsetWidth = container.offsetWidth;
  476. if (cachedOffsetWidth != lastOffsetWidth && !this.handlingResize)
  477. {
  478. this.handlingResize = true;
  479. this.graph.maxFitScale = (maxScale != null) ? maxScale : (this.graphConfig.zoom ||
  480. ((this.allowZoomIn) ? null : 1));
  481. this.graph.container.style.overflow = 'hidden';
  482. this.graph.fit();
  483. if (this.center || !(this.graphConfig.resize != false || container.style.height == ''))
  484. {
  485. this.graph.center();
  486. console.log('center0');
  487. }
  488. this.graph.maxFitScale = null;
  489. if (this.graphConfig.resize != false || container.style.height == '')
  490. {
  491. this.updateContainerHeight(container, Math.max(this.minHeight,
  492. this.graph.getGraphBounds().height +
  493. 2 * this.graph.border + 1));
  494. }
  495. this.graph.initialViewState = {
  496. translate: this.graph.view.translate.clone(),
  497. scale: this.graph.view.scale
  498. };
  499. lastOffsetWidth = cachedOffsetWidth;
  500. // Workaround for fit triggering scrollbars triggering doResize (infinite loop)
  501. window.setTimeout(mxUtils.bind(this, function()
  502. {
  503. this.handlingResize = false;
  504. }), 0);
  505. }
  506. });
  507. // Fallback for older browsers
  508. if (GraphViewer.useResizeSensor)
  509. {
  510. if (mxClient.IS_QUIRKS || document.documentMode <= 9)
  511. {
  512. mxEvent.addListener(window, 'resize', updateOverflow);
  513. this.graph.addListener('size', updateOverflow);
  514. }
  515. else
  516. {
  517. new ResizeSensor(this.graph.container, updateOverflow);
  518. }
  519. }
  520. if (this.graphConfig.resize || ((this.zoomEnabled || !this.autoFit) && this.graphConfig.resize != false))
  521. {
  522. this.graph.minimumContainerSize = new mxRectangle(0, 0, this.minWidth, this.minHeight);
  523. this.graph.resizeContainer = true;
  524. }
  525. else
  526. {
  527. // Sets initial size for responsive diagram to stop at actual size
  528. if (this.widthIsEmpty && !(container.style.height != '' && this.autoFit))
  529. {
  530. this.updateContainerWidth(container, bounds.width + 2 * this.graph.border);
  531. }
  532. if (this.graphConfig.resize != false || container.style.height == '')
  533. {
  534. this.updateContainerHeight(container, Math.max(this.minHeight, bounds.height + 2 * this.graph.border + 1));
  535. }
  536. if (!this.zoomEnabled && this.autoFit)
  537. {
  538. var lastOffsetWidth = null;
  539. var scheduledResize = null;
  540. var cachedOffsetWidth = null;
  541. var doResize = mxUtils.bind(this, function()
  542. {
  543. window.clearTimeout(scheduledResize);
  544. if (!this.handlingResize)
  545. {
  546. scheduledResize = window.setTimeout(mxUtils.bind(this, this.fitGraph), 100);
  547. }
  548. });
  549. // Fallback for older browsers
  550. if (GraphViewer.useResizeSensor)
  551. {
  552. if (mxClient.IS_QUIRKS || document.documentMode <= 9)
  553. {
  554. mxEvent.addListener(window, 'resize', doResize);
  555. }
  556. else
  557. {
  558. new ResizeSensor(this.graph.container, doResize);
  559. }
  560. }
  561. }
  562. else if (!(mxClient.IS_QUIRKS || document.documentMode <= 9))
  563. {
  564. this.graph.addListener('size', updateOverflow);
  565. }
  566. }
  567. var positionGraph = mxUtils.bind(this, function()
  568. {
  569. // Allocates maximum width while setting initial view state
  570. var prev = container.style.minWidth;
  571. if (this.widthIsEmpty)
  572. {
  573. container.style.minWidth = '100%';
  574. }
  575. var maxHeight = (this.graphConfig['max-height'] != null) ? this.graphConfig['max-height'] :
  576. ((container.style.height != '' && this.autoFit) ? container.offsetHeight : undefined);
  577. if (container.offsetWidth > 0 && (this.allowZoomIn ||
  578. (bounds.width + 2 * this.graph.border > container.offsetWidth ||
  579. bounds.height + 2 * this.graph.border > maxHeight)))
  580. {
  581. var maxScale = null;
  582. if (maxHeight != null && bounds.height + 2 * this.graph.border > maxHeight - 2)
  583. {
  584. maxScale = (maxHeight - 2 * this.graph.border - 2) / bounds.height;
  585. }
  586. this.fitGraph(maxScale);
  587. }
  588. else if (!this.widthIsEmpty && !(this.graphConfig.resize != false || container.style.height == ''))
  589. {
  590. this.graph.center((!this.widthIsEmpty || bounds.width < this.minWidth) && this.graphConfig.resize != true);
  591. }
  592. else
  593. {
  594. this.graph.view.setTranslate(Math.floor(this.graph.border - bounds.x / this.graph.view.scale),
  595. Math.floor(this.graph.border - bounds.y / this.graph.view.scale));
  596. lastOffsetWidth = container.offsetWidth;
  597. }
  598. container.style.minWidth = prev
  599. });
  600. if (mxClient.IS_QUIRKS || document.documentMode == 8)
  601. {
  602. window.setTimeout(positionGraph, 0);
  603. }
  604. else
  605. {
  606. positionGraph();
  607. }
  608. // Installs function on instance
  609. this.positionGraph = function()
  610. {
  611. bounds = this.graph.getGraphBounds();
  612. lastOffsetWidth = null;
  613. positionGraph();
  614. };
  615. };
  616. /**
  617. *
  618. */
  619. GraphViewer.prototype.updateContainerWidth = function(container, width)
  620. {
  621. container.style.width = width + 'px';
  622. };
  623. /**
  624. *
  625. */
  626. GraphViewer.prototype.updateContainerHeight = function(container, height)
  627. {
  628. if (this.zoomEnabled || !this.autoFit || document.compatMode == 'BackCompat' ||
  629. mxClient.IS_QUIRKS || document.documentMode == 8)
  630. {
  631. container.style.height = height + 'px';
  632. }
  633. };
  634. /**
  635. * Shows the
  636. */
  637. GraphViewer.prototype.showLayers = function(graph, sourceGraph)
  638. {
  639. var layers = this.graphConfig.layers;
  640. if (layers != null || sourceGraph != null)
  641. {
  642. var idx = (layers != null) ? layers.split(' ') : null;
  643. if (sourceGraph != null || idx.length > 0)
  644. {
  645. var source = (sourceGraph != null) ? sourceGraph.getModel() : null;
  646. var model = graph.getModel();
  647. model.beginUpdate();
  648. try
  649. {
  650. var childCount = model.getChildCount(model.root);
  651. // Hides all layers
  652. for (var i = 0; i < childCount; i++)
  653. {
  654. model.setVisible(model.getChildAt(model.root, i),
  655. (sourceGraph != null) ? source.isVisible(source.getChildAt(source.root, i)) : false);
  656. }
  657. // Shows specified layers (eg. 0 1 3)
  658. if (source == null)
  659. {
  660. for (var i = 0; i < idx.length; i++)
  661. {
  662. model.setVisible(model.getChildAt(model.root, parseInt(idx[i])), true);
  663. }
  664. }
  665. }
  666. finally
  667. {
  668. model.endUpdate();
  669. }
  670. }
  671. }
  672. };
  673. /**
  674. *
  675. */
  676. GraphViewer.prototype.addToolbar = function()
  677. {
  678. var container = this.graph.container;
  679. var initialCursor = this.graph.container.style.cursor;
  680. if (this.graphConfig['toolbar-position'] == 'bottom')
  681. {
  682. container.style.marginBottom = this.toolbarHeight + 'px';
  683. }
  684. else if (this.graphConfig['toolbar-position'] != 'inline')
  685. {
  686. container.style.marginTop = this.toolbarHeight + 'px';
  687. }
  688. // Creates toolbar for viewer
  689. var toolbar = container.ownerDocument.createElement('div');
  690. toolbar.style.position = 'absolute';
  691. toolbar.style.overflow = 'hidden';
  692. toolbar.style.boxSizing = 'border-box';
  693. toolbar.style.whiteSpace = 'nowrap';
  694. toolbar.style.textAlign = 'left';
  695. toolbar.style.zIndex = this.toolbarZIndex;
  696. toolbar.style.backgroundColor = '#eee';
  697. toolbar.style.height = this.toolbarHeight + 'px';
  698. this.toolbar = toolbar;
  699. if (this.graphConfig['toolbar-position'] == 'inline')
  700. {
  701. mxUtils.setPrefixedStyle(toolbar.style, 'transition', 'opacity 100ms ease-in-out');
  702. mxUtils.setOpacity(toolbar, 30);
  703. // Changes toolbar opacity on hover
  704. var fadeThread = null;
  705. var fadeThread2 = null;
  706. var fadeOut = mxUtils.bind(this, function(delay)
  707. {
  708. if (fadeThread != null)
  709. {
  710. window.clearTimeout(fadeThread);
  711. fadeThead = null;
  712. }
  713. if (fadeThread2 != null)
  714. {
  715. window.clearTimeout(fadeThread2);
  716. fadeThead2 = null;
  717. }
  718. fadeThread = window.setTimeout(mxUtils.bind(this, function()
  719. {
  720. mxUtils.setOpacity(toolbar, 0);
  721. fadeThread = null;
  722. fadeThread2 = window.setTimeout(mxUtils.bind(this, function()
  723. {
  724. toolbar.style.display = 'none';
  725. fadeThread2 = null;
  726. }), 100);
  727. }), delay || 200);
  728. });
  729. var fadeIn = mxUtils.bind(this, function(opacity)
  730. {
  731. if (fadeThread != null)
  732. {
  733. window.clearTimeout(fadeThread);
  734. fadeThead = null;
  735. }
  736. if (fadeThread2 != null)
  737. {
  738. window.clearTimeout(fadeThread2);
  739. fadeThead2 = null;
  740. }
  741. toolbar.style.display = '';
  742. mxUtils.setOpacity(toolbar, opacity || 30);
  743. });
  744. mxEvent.addListener(this.graph.container, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', mxUtils.bind(this, function(evt)
  745. {
  746. if (!mxEvent.isTouchEvent(evt))
  747. {
  748. fadeIn(30);
  749. fadeOut();
  750. }
  751. }));
  752. mxEvent.addListener(toolbar, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', function(evt)
  753. {
  754. mxEvent.consume(evt);
  755. });
  756. mxEvent.addListener(toolbar, 'mouseenter', mxUtils.bind(this, function(evt)
  757. {
  758. fadeIn(100);
  759. }));
  760. mxEvent.addListener(toolbar, 'mousemove', mxUtils.bind(this, function(evt)
  761. {
  762. fadeIn(100);
  763. mxEvent.consume(evt);
  764. }));
  765. mxEvent.addListener(toolbar, 'mouseleave', mxUtils.bind(this, function(evt)
  766. {
  767. if (!mxEvent.isTouchEvent(evt))
  768. {
  769. fadeIn(30);
  770. }
  771. }));
  772. // Shows/hides toolbar for touch devices
  773. var graph = this.graph;
  774. var tol = graph.getTolerance();
  775. graph.addMouseListener(
  776. {
  777. startX: 0,
  778. startY: 0,
  779. scrollLeft: 0,
  780. scrollTop: 0,
  781. mouseDown: function(sender, me)
  782. {
  783. this.startX = me.getGraphX();
  784. this.startY = me.getGraphY();
  785. this.scrollLeft = graph.container.scrollLeft;
  786. this.scrollTop = graph.container.scrollTop;
  787. },
  788. mouseMove: function(sender, me) {},
  789. mouseUp: function(sender, me)
  790. {
  791. if (mxEvent.isTouchEvent(me.getEvent()))
  792. {
  793. if ((Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol &&
  794. Math.abs(this.scrollTop - graph.container.scrollTop) < tol) &&
  795. (Math.abs(this.startX - me.getGraphX()) < tol &&
  796. Math.abs(this.startY - me.getGraphY()) < tol))
  797. {
  798. if (parseFloat(toolbar.style.opacity || 0) > 0)
  799. {
  800. fadeOut();
  801. }
  802. else
  803. {
  804. fadeIn(30);
  805. }
  806. }
  807. }
  808. }
  809. });
  810. }
  811. var tokens = this.toolbarItems;
  812. var buttonCount = 0;
  813. function addButton(fn, imgSrc, tip, enabled)
  814. {
  815. var a = document.createElement('div');
  816. a.style.borderRight = '1px solid #d0d0d0';
  817. a.style.padding = '3px 6px 3px 6px';
  818. mxEvent.addListener(a, 'click', fn);
  819. if (tip != null)
  820. {
  821. a.setAttribute('title', tip);
  822. }
  823. if (mxClient.IS_QUIRKS)
  824. {
  825. a.style.display = 'inline';
  826. }
  827. else
  828. {
  829. a.style.display = 'inline-block';
  830. }
  831. var img = document.createElement('img');
  832. img.setAttribute('border', '0');
  833. img.setAttribute('src', imgSrc);
  834. if (enabled == null || enabled)
  835. {
  836. mxEvent.addListener(a, 'mouseenter', function()
  837. {
  838. a.style.backgroundColor = '#ddd';
  839. });
  840. mxEvent.addListener(a, 'mouseleave', function()
  841. {
  842. a.style.backgroundColor = '#eee';
  843. });
  844. mxUtils.setOpacity(img, 60);
  845. a.style.cursor = 'pointer';
  846. }
  847. else
  848. {
  849. mxUtils.setOpacity(a, 30);
  850. }
  851. a.appendChild(img);
  852. toolbar.appendChild(a);
  853. buttonCount++;
  854. return a;
  855. };
  856. var layersDialog = null;
  857. var layersDialogEntered = false;
  858. var pageInfo = null;
  859. for (var i = 0; i < tokens.length; i++)
  860. {
  861. var token = tokens[i];
  862. if (token == 'pages')
  863. {
  864. pageInfo = container.ownerDocument.createElement('div');
  865. pageInfo.style.cssText = 'display:inline-block;position:relative;padding:3px 4px 0 4px;' +
  866. 'vertical-align:top;font-family:Helvetica,Arial;font-size:12px;top:4px;cursor:default;'
  867. mxUtils.setOpacity(pageInfo, 70);
  868. var prevButton = addButton(mxUtils.bind(this, function()
  869. {
  870. this.selectPage(this.currentPage - 1);
  871. }), Editor.previousImage, mxResources.get('previousPage') || 'Previous Page');
  872. prevButton.style.borderRightStyle = 'none';
  873. prevButton.style.paddingLeft = '0px';
  874. prevButton.style.paddingRight = '0px';
  875. toolbar.appendChild(pageInfo);
  876. var nextButton = addButton(mxUtils.bind(this, function()
  877. {
  878. this.selectPage(this.currentPage + 1);
  879. }), Editor.nextImage, mxResources.get('nextPage') || 'Next Page');
  880. nextButton.style.paddingLeft = '0px';
  881. nextButton.style.paddingRight = '0px';
  882. var lastXmlNode = null;
  883. var update = mxUtils.bind(this, function()
  884. {
  885. pageInfo.innerHTML = '';
  886. mxUtils.write(pageInfo, (this.currentPage + 1) + ' / ' + this.diagrams.length);
  887. pageInfo.style.display = (this.diagrams.length > 1) ? 'inline-block' : 'none';
  888. prevButton.style.display = pageInfo.style.display;
  889. nextButton.style.display = pageInfo.style.display;
  890. });
  891. // LATER: Add event for setGraphXml
  892. this.addListener('graphChanged', update);
  893. update();
  894. }
  895. else if (token == 'zoom')
  896. {
  897. if (this.zoomEnabled)
  898. {
  899. addButton(mxUtils.bind(this, function()
  900. {
  901. this.graph.zoomOut();
  902. }), Editor.zoomOutImage, mxResources.get('zoomOut') || 'Zoom Out');
  903. addButton(mxUtils.bind(this, function()
  904. {
  905. this.graph.zoomIn();
  906. }), Editor.zoomInImage, mxResources.get('zoomIn') || 'Zoom In');
  907. addButton(mxUtils.bind(this, function()
  908. {
  909. this.graph.view.scaleAndTranslate(this.graph.initialViewState.scale,
  910. this.graph.initialViewState.translate.x,
  911. this.graph.initialViewState.translate.y);
  912. }), Editor.zoomFitImage, mxResources.get('fit') || 'Fit');
  913. }
  914. }
  915. else if (token == 'layers')
  916. {
  917. if (this.layersEnabled)
  918. {
  919. var model = this.graph.getModel();
  920. var layersButton = addButton(mxUtils.bind(this, function(evt)
  921. {
  922. if (layersDialog != null)
  923. {
  924. layersDialog.parentNode.removeChild(layersDialog);
  925. layersDialog = null;
  926. }
  927. else
  928. {
  929. layersDialog = this.graph.createLayersDialog();
  930. mxEvent.addListener(layersDialog, 'mouseleave', function()
  931. {
  932. layersDialog.parentNode.removeChild(layersDialog);
  933. layersDialog = null;
  934. });
  935. var r = layersButton.getBoundingClientRect();
  936. layersDialog.style.width = '140px';
  937. layersDialog.style.padding = '2px 0px 2px 0px';
  938. layersDialog.style.border = '1px solid #d0d0d0';
  939. layersDialog.style.backgroundColor = '#eee';
  940. layersDialog.style.fontFamily = 'Helvetica Neue,Helvetica,Arial Unicode MS,Arial';
  941. layersDialog.style.fontSize = '11px';
  942. layersDialog.style.zIndex = this.toolbarZIndex + 1;
  943. mxUtils.setOpacity(layersDialog, 80);
  944. var origin = mxUtils.getDocumentScrollOrigin(document);
  945. layersDialog.style.left = origin.x + r.left + 'px';
  946. layersDialog.style.top = origin.y + r.bottom + 'px';
  947. document.body.appendChild(layersDialog);
  948. }
  949. }), Editor.layersImage, mxResources.get('layers') || 'Layers');
  950. model.addListener(mxEvent.CHANGE, function()
  951. {
  952. layersButton.style.display = (model.getChildCount(model.root) > 1) ? 'inline-block' : 'none';
  953. });
  954. layersButton.style.display = (model.getChildCount(model.root) > 1) ? 'inline-block' : 'none';
  955. }
  956. }
  957. else if (token == 'lightbox')
  958. {
  959. if (this.lightboxEnabled)
  960. {
  961. addButton(mxUtils.bind(this, function()
  962. {
  963. this.showLightbox();
  964. }), Editor.maximizeImage, (mxResources.get('show') || 'Show'));
  965. }
  966. }
  967. else if (this.graphConfig['toolbar-buttons'] != null)
  968. {
  969. var def = this.graphConfig['toolbar-buttons'][token];
  970. if (def != null)
  971. {
  972. addButton((def.enabled == null || def.enabled) ? def.handler : function() {},
  973. def.image, def.title, def.enabled);
  974. }
  975. }
  976. }
  977. if (this.graph.minimumContainerSize != null)
  978. {
  979. this.graph.minimumContainerSize.width = buttonCount * 34;
  980. }
  981. if (this.graphConfig.title != null)
  982. {
  983. var filename = container.ownerDocument.createElement('div');
  984. filename.style.cssText = 'display:inline-block;position:relative;padding:3px 6px 0 6px;' +
  985. 'vertical-align:top;font-family:Helvetica,Arial;font-size:12px;top:4px;cursor:default;'
  986. filename.setAttribute('title', this.graphConfig.title);
  987. mxUtils.write(filename, this.graphConfig.title);
  988. mxUtils.setOpacity(filename, 70);
  989. toolbar.appendChild(filename);
  990. }
  991. this.minToolbarWidth = buttonCount * 34;
  992. var prevBorder = container.style.border;
  993. var enter = mxUtils.bind(this, function()
  994. {
  995. toolbar.style.width = (this.graphConfig['toolbar-position'] == 'inline') ? 'auto' :
  996. Math.max(this.minToolbarWidth, container.offsetWidth) + 'px';
  997. toolbar.style.border = '1px solid #d0d0d0';
  998. if (this.graphConfig['toolbar-nohide'] != true)
  999. {
  1000. var r = container.getBoundingClientRect();
  1001. // Workaround for position:relative set in ResizeSensor
  1002. var origin = mxUtils.getScrollOrigin(document.body)
  1003. var b = (document.body.style.position === 'relative') ? document.body.getBoundingClientRect() :
  1004. {left: -origin.x, top: -origin.y};
  1005. r = {left: r.left - b.left, top: r.top - b.top, bottom: r.bottom - b.top, right: r.right - b.left};
  1006. toolbar.style.left = r.left + 'px';
  1007. if (this.graphConfig['toolbar-position'] == 'bottom')
  1008. {
  1009. toolbar.style.top = r.bottom - 1 + 'px';
  1010. }
  1011. else
  1012. {
  1013. if (this.graphConfig['toolbar-position'] != 'inline')
  1014. {
  1015. toolbar.style.marginTop = -this.toolbarHeight + 'px';
  1016. toolbar.style.top = r.top + 1 + 'px';
  1017. }
  1018. else
  1019. {
  1020. toolbar.style.top = r.top + 'px';
  1021. }
  1022. }
  1023. if (prevBorder == '1px solid transparent')
  1024. {
  1025. container.style.border = '1px solid #d0d0d0';
  1026. }
  1027. document.body.appendChild(toolbar);
  1028. var hideToolbar = mxUtils.bind(this, function()
  1029. {
  1030. if (toolbar.parentNode != null)
  1031. {
  1032. toolbar.parentNode.removeChild(toolbar);
  1033. }
  1034. if (layersDialog != null)
  1035. {
  1036. layersDialog.parentNode.removeChild(layersDialog);
  1037. layersDialog = null;
  1038. }
  1039. container.style.border = prevBorder;
  1040. });
  1041. mxEvent.addListener(document, 'mousemove', function(evt)
  1042. {
  1043. var source = mxEvent.getSource(evt);
  1044. while (source != null)
  1045. {
  1046. if (source == container || source == toolbar || source == layersDialog)
  1047. {
  1048. return;
  1049. }
  1050. source = source.parentNode;
  1051. }
  1052. hideToolbar();
  1053. });
  1054. mxEvent.addListener(document, 'mouseleave', function(evt)
  1055. {
  1056. hideToolbar();
  1057. });
  1058. }
  1059. else
  1060. {
  1061. toolbar.style.top = -this.toolbarHeight + 'px';
  1062. container.appendChild(toolbar);
  1063. }
  1064. });
  1065. if (this.graphConfig['toolbar-nohide'] != true)
  1066. {
  1067. mxEvent.addListener(container, 'mouseenter', enter);
  1068. }
  1069. else
  1070. {
  1071. enter();
  1072. }
  1073. };
  1074. /**
  1075. * Adds event handler for links and lightbox.
  1076. */
  1077. GraphViewer.prototype.addClickHandler = function(graph, ui)
  1078. {
  1079. graph.linkPolicy = this.graphConfig.target || graph.linkPolicy;
  1080. graph.addClickHandler(this.graphConfig.highlight, mxUtils.bind(this, function(evt, href)
  1081. {
  1082. if (href == null)
  1083. {
  1084. var source = mxEvent.getSource(evt);
  1085. if (source.nodeName.toLowerCase() == 'a')
  1086. {
  1087. href = source.getAttribute('href');
  1088. }
  1089. }
  1090. if (ui != null)
  1091. {
  1092. if (href != null && !(graph.isExternalProtocol(href) || graph.isBlankLink(href)))
  1093. {
  1094. // Hides lightbox if any links are clicked
  1095. // Async handling needed for anchors to work
  1096. window.setTimeout(function()
  1097. {
  1098. ui.destroy();
  1099. }, 0);
  1100. }
  1101. }
  1102. else if (href != null && ui == null && graph.isCustomLink(href) &&
  1103. (mxEvent.isTouchEvent(evt) || !mxEvent.isPopupTrigger(evt)) &&
  1104. graph.customLinkClicked(href))
  1105. {
  1106. mxEvent.consume(evt);
  1107. }
  1108. }), mxUtils.bind(this, function(evt)
  1109. {
  1110. if (ui == null && this.lightboxClickEnabled &&
  1111. (!mxEvent.isTouchEvent(evt) ||
  1112. this.toolbarItems.length == 0))
  1113. {
  1114. this.showLightbox();
  1115. }
  1116. }));
  1117. };
  1118. /**
  1119. * Adds the given array of stencils to avoid dynamic loading of shapes.
  1120. */
  1121. GraphViewer.prototype.showLightbox = function(editable, closable, target)
  1122. {
  1123. if (this.graphConfig.lightbox == 'open' || window.self !== window.top)
  1124. {
  1125. if (this.lightboxWindow != null && !this.lightboxWindow.closed)
  1126. {
  1127. this.lightboxWindow.focus();
  1128. }
  1129. else
  1130. {
  1131. editable = (editable != null) ? editable :
  1132. ((this.graphConfig.editable != null) ?
  1133. this.graphConfig.editable : true);
  1134. closable = (closable != null) ? closable : true;
  1135. target = (target != null) ? target : 'blank';
  1136. var param = {'client': 1, 'lightbox': 1, 'target': target};
  1137. if (editable)
  1138. {
  1139. param.edit = this.graphConfig.edit || '_blank';
  1140. }
  1141. if (closable)
  1142. {
  1143. param.close = 1;
  1144. }
  1145. if (this.layersEnabled)
  1146. {
  1147. param.layers = 1;
  1148. }
  1149. if (this.graphConfig != null && this.graphConfig.nav != false)
  1150. {
  1151. param.nav = 1;
  1152. }
  1153. if (this.graphConfig != null && this.graphConfig.highlight != null)
  1154. {
  1155. param.highlight = this.graphConfig.highlight.substring(1);
  1156. }
  1157. if (this.currentPage != null && this.currentPage > 0)
  1158. {
  1159. param.page = this.currentPage;
  1160. }
  1161. if (typeof window.postMessage !== 'undefined' && (document.documentMode == null || document.documentMode >= 10))
  1162. {
  1163. if (this.lightboxWindow == null)
  1164. {
  1165. mxEvent.addListener(window, 'message', mxUtils.bind(this, function(evt)
  1166. {
  1167. if (evt.data == 'ready' && evt.source == this.lightboxWindow)
  1168. {
  1169. this.lightboxWindow.postMessage(this.xml, '*');
  1170. }
  1171. }));
  1172. }
  1173. }
  1174. else
  1175. {
  1176. // Data is pulled from global variable after tab loads
  1177. param.data = encodeURIComponent(this.xml);
  1178. }
  1179. var domain = 'www.draw.io';
  1180. if (urlParams['dev'] == '1')
  1181. {
  1182. param.dev = '1';
  1183. param.drawdev = '1';
  1184. domain = 'test.draw.io';
  1185. }
  1186. this.lightboxWindow = window.open('https://' + domain +
  1187. '/#P' + encodeURIComponent(JSON.stringify(param)));
  1188. }
  1189. }
  1190. else
  1191. {
  1192. this.showLocalLightbox();
  1193. }
  1194. };
  1195. /**
  1196. * Adds the given array of stencils to avoid dynamic loading of shapes.
  1197. */
  1198. GraphViewer.prototype.showLocalLightbox = function()
  1199. {
  1200. var origin = mxUtils.getDocumentScrollOrigin(document);
  1201. var backdrop = document.createElement('div');
  1202. if (mxClient.IS_QUIRKS)
  1203. {
  1204. backdrop.style.position = 'absolute';
  1205. backdrop.style.left = origin.x + 'px';
  1206. backdrop.style.top = origin.y + 'px';
  1207. backdrop.style.width = document.body.offsetWidth + 'px';
  1208. backdrop.style.height = document.body.offsetHeight + 'px';
  1209. }
  1210. else
  1211. {
  1212. backdrop.style.cssText = 'position:fixed;top:0;left:0;bottom:0;right:0;';
  1213. }
  1214. backdrop.style.zIndex = this.lightboxZIndex;
  1215. backdrop.style.backgroundColor = '#000000';
  1216. mxUtils.setOpacity(backdrop, 70);
  1217. document.body.appendChild(backdrop);
  1218. var closeImg = document.createElement('img');
  1219. closeImg.setAttribute('border', '0');
  1220. closeImg.setAttribute('src', Editor.closeImage);
  1221. if (mxClient.IS_QUIRKS)
  1222. {
  1223. closeImg.style.position = 'absolute';
  1224. closeImg.style.right = 32 + 'px';
  1225. closeImg.style.top = origin.y + 32 + 'px';
  1226. }
  1227. else
  1228. {
  1229. closeImg.style.cssText = 'position:fixed;top:32px;right:32px;';
  1230. }
  1231. closeImg.style.cursor = 'pointer';
  1232. mxEvent.addListener(closeImg, 'click', function()
  1233. {
  1234. ui.destroy();
  1235. });
  1236. // LATER: Make possible to assign after instance was created
  1237. urlParams['pages'] = '1';
  1238. urlParams['page'] = this.currentPage;
  1239. urlParams['nav'] = (this.graphConfig.nav != false) ? '1' : '0';
  1240. urlParams['layers'] = (this.layersEnabled) ? '1' : '0';
  1241. // PostMessage not working and Permission denied for opened access in IE9-
  1242. if (document.documentMode == null || document.documentMode >= 10)
  1243. {
  1244. Editor.prototype.editButtonLink = this.graphConfig.edit;
  1245. Editor.prototype.editButtonFunc = this.graphConfig.editFunc;
  1246. }
  1247. EditorUi.prototype.updateActionStates = function() {};
  1248. EditorUi.prototype.addBeforeUnloadListener = function() {};
  1249. EditorUi.prototype.addChromelessClickHandler = function() {};
  1250. // Workaround for lost reference with same ID (cannot override after instance is created)
  1251. Graph.prototype.shadowId = 'lightboxDropShadow';
  1252. var ui = new EditorUi(new Editor(true), document.createElement('div'), true);
  1253. ui.editor.editBlankUrl = this.editBlankUrl;
  1254. // Workaround for lost reference with same ID
  1255. Graph.prototype.shadowId = 'dropShadow';
  1256. // Disables refresh
  1257. ui.refresh = function() {};
  1258. // Handles escape keystroke
  1259. var keydownHandler = mxUtils.bind(this, function(evt)
  1260. {
  1261. if (evt.keyCode == 27 /* Escape */)
  1262. {
  1263. ui.destroy();
  1264. }
  1265. });
  1266. var destroy = ui.destroy;
  1267. ui.destroy = function()
  1268. {
  1269. mxEvent.removeListener(document.documentElement, 'keydown', keydownHandler);
  1270. document.body.removeChild(backdrop);
  1271. document.body.removeChild(closeImg);
  1272. document.body.style.overflow = 'auto';
  1273. GraphViewer.resizeSensorEnabled = true;
  1274. destroy.apply(this, arguments);
  1275. };
  1276. var graph = ui.editor.graph;
  1277. var lightbox = graph.container;
  1278. lightbox.style.overflow = 'hidden';
  1279. if (this.lightboxChrome)
  1280. {
  1281. lightbox.style.border = '1px solid #c0c0c0';
  1282. lightbox.style.margin = '40px';
  1283. // Installs the keystroke listener in the target
  1284. mxEvent.addListener(document.documentElement, 'keydown', keydownHandler);
  1285. }
  1286. else
  1287. {
  1288. backdrop.style.display = 'none';
  1289. closeImg.style.display = 'none';
  1290. }
  1291. // Handles relative images
  1292. var self = this;
  1293. graph.getImageFromBundles = function(key)
  1294. {
  1295. return self.getImageUrl(key);
  1296. };
  1297. // Handles relative images in print output and temporary graphs
  1298. var uiCreateTemporaryGraph = ui.createTemporaryGraph;
  1299. ui.createTemporaryGraph = function()
  1300. {
  1301. var graph = uiCreateTemporaryGraph.apply(this, arguments);
  1302. graph.getImageFromBundles = function(key)
  1303. {
  1304. return self.getImageUrl(key);
  1305. };
  1306. return graph;
  1307. };
  1308. if (this.graphConfig.move)
  1309. {
  1310. graph.isMoveCellsEvent = function(evt)
  1311. {
  1312. return true;
  1313. };
  1314. }
  1315. if (!mxClient.IS_QUIRKS)
  1316. {
  1317. mxUtils.setPrefixedStyle(lightbox.style, 'border-radius', '4px');
  1318. lightbox.style.position = 'fixed';
  1319. }
  1320. GraphViewer.resizeSensorEnabled = false;
  1321. document.body.style.overflow = 'hidden';
  1322. // Workaround for possible rendering issues
  1323. if (!mxClient.IS_SF && !mxClient.IS_EDGE)
  1324. {
  1325. mxUtils.setPrefixedStyle(lightbox.style, 'transform', 'rotateY(90deg)');
  1326. mxUtils.setPrefixedStyle(lightbox.style, 'transition', 'all .25s ease-in-out');
  1327. }
  1328. this.addClickHandler(graph, ui);
  1329. window.setTimeout(mxUtils.bind(this, function()
  1330. {
  1331. // Disables focus border in Chrome
  1332. lightbox.style.outline = 'none';
  1333. lightbox.style.zIndex = this.lightboxZIndex;
  1334. closeImg.style.zIndex = this.lightboxZIndex;
  1335. document.body.appendChild(lightbox);
  1336. document.body.appendChild(closeImg);
  1337. ui.setFileData(this.xml);
  1338. mxUtils.setPrefixedStyle(lightbox.style, 'transform', 'rotateY(0deg)');
  1339. ui.chromelessToolbar.style.bottom = 60 + 'px';
  1340. ui.chromelessToolbar.style.zIndex = this.lightboxZIndex;
  1341. // Workaround for clipping in IE11-
  1342. document.body.appendChild(ui.chromelessToolbar);
  1343. ui.getEditBlankXml = mxUtils.bind(this, function()
  1344. {
  1345. return this.xml;
  1346. });
  1347. if (mxClient.IS_QUIRKS)
  1348. {
  1349. lightbox.style.position = 'absolute';
  1350. lightbox.style.display = 'block';
  1351. lightbox.style.left = origin.x + 'px';
  1352. lightbox.style.top = origin.y + 'px';
  1353. lightbox.style.width = document.body.clientWidth - 80 + 'px';
  1354. lightbox.style.height = document.body.clientHeight - 80 + 'px';
  1355. lightbox.style.backgroundColor = 'white';
  1356. ui.chromelessToolbar.style.display = 'block';
  1357. ui.chromelessToolbar.style.position = 'absolute';
  1358. ui.chromelessToolbar.style.bottom = '';
  1359. ui.chromelessToolbar.style.top = origin.y +
  1360. document.body.clientHeight - 100 + 'px';
  1361. }
  1362. ui.lightboxFit();
  1363. ui.chromelessResize();
  1364. this.showLayers(graph, this.graph);
  1365. // Click on backdrop closes lightbox
  1366. mxEvent.addListener(backdrop, 'click', function()
  1367. {
  1368. ui.destroy();
  1369. });
  1370. }), 0);
  1371. return ui;
  1372. };
  1373. /**
  1374. *
  1375. */
  1376. GraphViewer.processElements = function(classname)
  1377. {
  1378. mxUtils.forEach(GraphViewer.getElementsByClassName(classname || 'mxgraph'), function(div)
  1379. {
  1380. try
  1381. {
  1382. div.innerHTML = '';
  1383. GraphViewer.createViewerForElement(div);
  1384. }
  1385. catch (e)
  1386. {
  1387. div.innerHTML = e.message;
  1388. throw e;
  1389. }
  1390. });
  1391. };
  1392. /**
  1393. * Adds the given array of stencils to avoid dynamic loading of shapes.
  1394. */
  1395. GraphViewer.getElementsByClassName = function(classname)
  1396. {
  1397. if (document.getElementsByClassName)
  1398. {
  1399. var divs = document.getElementsByClassName(classname);
  1400. // Workaround for changing divs while processing
  1401. var result = [];
  1402. for (var i = 0; i < divs.length; i++)
  1403. {
  1404. result.push(divs[i]);
  1405. }
  1406. return result;
  1407. }
  1408. else
  1409. {
  1410. var tmp = document.getElementsByTagName('*');
  1411. var divs = [];
  1412. for (var i = 0; i < tmp.length; i++)
  1413. {
  1414. var cls = tmp[i].className;
  1415. if (cls != null && cls.length > 0)
  1416. {
  1417. var tokens = cls.split(' ');
  1418. if (mxUtils.indexOf(tokens, classname) >= 0)
  1419. {
  1420. divs.push(tmp[i]);
  1421. }
  1422. }
  1423. }
  1424. return divs;
  1425. }
  1426. };
  1427. /**
  1428. * Adds the given array of stencils to avoid dynamic loading of shapes.
  1429. */
  1430. GraphViewer.createViewerForElement = function(element, callback)
  1431. {
  1432. var data = element.getAttribute('data-mxgraph');
  1433. if (data != null)
  1434. {
  1435. var config = JSON.parse(data);
  1436. var createViewer = function(xml)
  1437. {
  1438. var xmlDoc = mxUtils.parseXml(xml);
  1439. var viewer = new GraphViewer(element, xmlDoc.documentElement, config);
  1440. if (callback != null)
  1441. {
  1442. callback(viewer);
  1443. }
  1444. };
  1445. if (config.url != null)
  1446. {
  1447. GraphViewer.getUrl(config.url, function(xml)
  1448. {
  1449. createViewer(xml);
  1450. });
  1451. }
  1452. else
  1453. {
  1454. createViewer(config.xml);
  1455. }
  1456. }
  1457. };
  1458. /**
  1459. * Adds event if grid size is changed.
  1460. */
  1461. GraphViewer.initCss = function()
  1462. {
  1463. try
  1464. {
  1465. var style = document.createElement('style')
  1466. style.type = 'text/css';
  1467. style.innerHTML = ['div.mxTooltip {',
  1468. '-webkit-box-shadow: 3px 3px 12px #C0C0C0;',
  1469. '-moz-box-shadow: 3px 3px 12px #C0C0C0;',
  1470. 'box-shadow: 3px 3px 12px #C0C0C0;',
  1471. 'background: #FFFFCC;',
  1472. 'border-style: solid;',
  1473. 'border-width: 1px;',
  1474. 'border-color: black;',
  1475. 'font-family: Arial;',
  1476. 'font-size: 8pt;',
  1477. 'position: absolute;',
  1478. 'cursor: default;',
  1479. 'padding: 4px;',
  1480. 'color: black;}',
  1481. 'td.mxPopupMenuIcon div {',
  1482. 'width:16px;',
  1483. 'height:16px;}',
  1484. 'html div.mxPopupMenu {',
  1485. '-webkit-box-shadow:2px 2px 3px #d5d5d5;',
  1486. '-moz-box-shadow:2px 2px 3px #d5d5d5;',
  1487. 'box-shadow:2px 2px 3px #d5d5d5;',
  1488. '_filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=2, OffY=2, Color=\'#d0d0d0\',Positive=\'true\');',
  1489. 'background:white;',
  1490. 'position:absolute;',
  1491. 'border:3px solid #e7e7e7;',
  1492. 'padding:3px;}',
  1493. 'html table.mxPopupMenu {',
  1494. 'border-collapse:collapse;',
  1495. 'margin:0px;}',
  1496. 'html td.mxPopupMenuItem {',
  1497. 'padding:7px 30px 7px 30px;',
  1498. 'font-family:Helvetica Neue,Helvetica,Arial Unicode MS,Arial;',
  1499. 'font-size:10pt;}',
  1500. 'html td.mxPopupMenuIcon {',
  1501. 'background-color:white;',
  1502. 'padding:0px;}',
  1503. 'td.mxPopupMenuIcon .geIcon {',
  1504. 'padding:2px;',
  1505. 'padding-bottom:4px;',
  1506. 'margin:2px;',
  1507. 'border:1px solid transparent;',
  1508. 'opacity:0.5;',
  1509. '_width:26px;',
  1510. '_height:26px;}',
  1511. 'td.mxPopupMenuIcon .geIcon:hover {',
  1512. 'border:1px solid gray;',
  1513. 'border-radius:2px;',
  1514. 'opacity:1;}',
  1515. 'html tr.mxPopupMenuItemHover {',
  1516. 'background-color: #eeeeee;',
  1517. 'color: black;}',
  1518. 'table.mxPopupMenu hr {',
  1519. 'color:#cccccc;',
  1520. 'background-color:#cccccc;',
  1521. 'border:none;',
  1522. 'height:1px;}',
  1523. 'table.mxPopupMenu tr { font-size:4pt;}',
  1524. // Modified to only apply to the print dialog
  1525. '.geDialog { font-family:Helvetica Neue,Helvetica,Arial Unicode MS,Arial;',
  1526. 'font-size:10pt;',
  1527. 'border:none;',
  1528. 'margin:0px;}',
  1529. // These are required for the print dialog
  1530. '.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;}',
  1531. '.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');
  1532. document.getElementsByTagName('head')[0].appendChild(style);
  1533. }
  1534. catch (e)
  1535. {
  1536. // ignore
  1537. }
  1538. };
  1539. /**
  1540. * Lookup for URLs.
  1541. */
  1542. GraphViewer.cachedUrls = {};
  1543. /**
  1544. * Workaround for unsupported CORS in IE9 XHR
  1545. */
  1546. GraphViewer.getUrl = function(url, onload, onerror)
  1547. {
  1548. if (GraphViewer.cachedUrls[url] != null)
  1549. {
  1550. onload(GraphViewer.cachedUrls[url]);
  1551. }
  1552. else
  1553. {
  1554. var xhr = (navigator.userAgent.indexOf('MSIE 9') > 0) ? new XDomainRequest() : new XMLHttpRequest();
  1555. xhr.open('GET', url);
  1556. xhr.onload = function()
  1557. {
  1558. onload((xhr.getText != null) ? xhr.getText() : xhr.responseText);
  1559. };
  1560. xhr.onerror = onerror;
  1561. xhr.send();
  1562. }
  1563. };
  1564. /**
  1565. * Redirects editing to absolue URLs.
  1566. */
  1567. GraphViewer.resizeSensorEnabled = true;
  1568. /**
  1569. * Redirects editing to absolue URLs.
  1570. */
  1571. GraphViewer.useResizeSensor = true;
  1572. /**
  1573. * Copyright Marc J. Schmidt. See the LICENSE file at the top-level
  1574. * directory of this distribution and at
  1575. * https://github.com/marcj/css-element-queries/blob/master/LICENSE.
  1576. */
  1577. (function() {
  1578. // Only used for the dirty checking, so the event callback count is limted to max 1 call per fps per sensor.
  1579. // In combination with the event based resize sensor this saves cpu time, because the sensor is too fast and
  1580. // would generate too many unnecessary events.
  1581. var requestAnimationFrame = window.requestAnimationFrame ||
  1582. window.mozRequestAnimationFrame ||
  1583. window.webkitRequestAnimationFrame ||
  1584. function (fn) {
  1585. return window.setTimeout(fn, 20);
  1586. };
  1587. /**
  1588. * Class for dimension change detection.
  1589. *
  1590. * @param {Element|Element[]|Elements|jQuery} element
  1591. * @param {Function} callback
  1592. *
  1593. * @constructor
  1594. */
  1595. var ResizeSensor = function(element, fn) {
  1596. var callback = function()
  1597. {
  1598. if (GraphViewer.resizeSensorEnabled)
  1599. {
  1600. fn();
  1601. }
  1602. };
  1603. /**
  1604. *
  1605. * @constructor
  1606. */
  1607. function EventQueue() {
  1608. this.q = [];
  1609. this.add = function(ev) {
  1610. this.q.push(ev);
  1611. };
  1612. var i, j;
  1613. this.call = function() {
  1614. for (i = 0, j = this.q.length; i < j; i++) {
  1615. this.q[i].call();
  1616. }
  1617. };
  1618. }
  1619. /**
  1620. * @param {HTMLElement} element
  1621. * @param {String} prop
  1622. * @returns {String|Number}
  1623. */
  1624. function getComputedStyle(element, prop) {
  1625. if (element.currentStyle) {
  1626. return element.currentStyle[prop];
  1627. } else if (window.getComputedStyle) {
  1628. return window.getComputedStyle(element, null).getPropertyValue(prop);
  1629. } else {
  1630. return element.style[prop];
  1631. }
  1632. }
  1633. /**
  1634. *
  1635. * @param {HTMLElement} element
  1636. * @param {Function} resized
  1637. */
  1638. function attachResizeEvent(element, resized) {
  1639. if (!element.resizedAttached) {
  1640. element.resizedAttached = new EventQueue();
  1641. element.resizedAttached.add(resized);
  1642. } else if (element.resizedAttached) {
  1643. element.resizedAttached.add(resized);
  1644. return;
  1645. }
  1646. element.resizeSensor = document.createElement('div');
  1647. element.resizeSensor.className = 'resize-sensor';
  1648. var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden;';
  1649. var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;';
  1650. element.resizeSensor.style.cssText = style;
  1651. element.resizeSensor.innerHTML =
  1652. '<div class="resize-sensor-expand" style="' + style + '">' +
  1653. '<div style="' + styleChild + '"></div>' +
  1654. '</div>' +
  1655. '<div class="resize-sensor-shrink" style="' + style + '">' +
  1656. '<div style="' + styleChild + ' width: 200%; height: 200%"></div>' +
  1657. '</div>';
  1658. element.appendChild(element.resizeSensor);
  1659. // FIXME: Should not change element style
  1660. if (getComputedStyle(element, 'position') == 'static') {
  1661. element.style.position = 'relative';
  1662. }
  1663. var expand = element.resizeSensor.childNodes[0];
  1664. var expandChild = expand.childNodes[0];
  1665. var shrink = element.resizeSensor.childNodes[1];
  1666. var reset = function() {
  1667. expandChild.style.width = 100000 + 'px';
  1668. expandChild.style.height = 100000 + 'px';
  1669. expand.scrollLeft = 100000;
  1670. expand.scrollTop = 100000;
  1671. shrink.scrollLeft = 100000;
  1672. shrink.scrollTop = 100000;
  1673. };
  1674. reset();
  1675. var dirty = false;
  1676. var dirtyChecking = function() {
  1677. if (!element.resizedAttached) return;
  1678. if (dirty) {
  1679. element.resizedAttached.call();
  1680. dirty = false;
  1681. }
  1682. requestAnimationFrame(dirtyChecking);
  1683. };
  1684. requestAnimationFrame(dirtyChecking);
  1685. var lastWidth, lastHeight;
  1686. var cachedWidth, cachedHeight; //useful to not query offsetWidth twice
  1687. var onScroll = function() {
  1688. if ((cachedWidth = element.offsetWidth) != lastWidth || (cachedHeight = element.offsetHeight) != lastHeight) {
  1689. dirty = true;
  1690. lastWidth = cachedWidth;
  1691. lastHeight = cachedHeight;
  1692. }
  1693. reset();
  1694. };
  1695. var addEvent = function(el, name, cb) {
  1696. if (el.attachEvent) {
  1697. el.attachEvent('on' + name, cb);
  1698. } else {
  1699. el.addEventListener(name, cb);
  1700. }
  1701. };
  1702. addEvent(expand, 'scroll', onScroll);
  1703. addEvent(shrink, 'scroll', onScroll);
  1704. }
  1705. var elementType = Object.prototype.toString.call(element);
  1706. var isCollectionTyped = ('[object Array]' === elementType
  1707. || ('[object NodeList]' === elementType)
  1708. || ('[object HTMLCollection]' === elementType)
  1709. || ('undefined' !== typeof jQuery && element instanceof jQuery) //jquery
  1710. || ('undefined' !== typeof Elements && element instanceof Elements) //mootools
  1711. );
  1712. if (isCollectionTyped) {
  1713. var i = 0, j = element.length;
  1714. for (; i < j; i++) {
  1715. attachResizeEvent(element[i], callback);
  1716. }
  1717. } else {
  1718. attachResizeEvent(element, callback);
  1719. }
  1720. this.detach = function() {
  1721. if (isCollectionTyped) {
  1722. var i = 0, j = element.length;
  1723. for (; i < j; i++) {
  1724. ResizeSensor.detach(element[i]);
  1725. }
  1726. } else {
  1727. ResizeSensor.detach(element);
  1728. }
  1729. };
  1730. };
  1731. ResizeSensor.detach = function(element) {
  1732. if (element.resizeSensor) {
  1733. element.removeChild(element.resizeSensor);
  1734. delete element.resizeSensor;
  1735. delete element.resizedAttached;
  1736. }
  1737. };
  1738. window.ResizeSensor = ResizeSensor;
  1739. })();