BaseViewer.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814
  1. /**
  2. * The code in the <project-logo></project-logo> area
  3. * must not be changed.
  4. *
  5. * @see http://bpmn.io/license for more information.
  6. */
  7. import {
  8. assign,
  9. find,
  10. isNumber,
  11. omit
  12. } from 'min-dash';
  13. import {
  14. domify,
  15. assignStyle,
  16. query as domQuery,
  17. remove as domRemove
  18. } from 'min-dom';
  19. import {
  20. innerSVG
  21. } from 'tiny-svg';
  22. import Diagram from 'diagram-js';
  23. import BpmnModdle from 'bpmn-moddle';
  24. import inherits from 'inherits-browser';
  25. import {
  26. importBpmnDiagram
  27. } from './import/Importer';
  28. /**
  29. * @template T
  30. *
  31. * @typedef {import('diagram-js/lib/core/EventBus').EventBusEventCallback<T>} EventBusEventCallback
  32. */
  33. /**
  34. * @typedef {import('didi').ModuleDeclaration} ModuleDeclaration
  35. *
  36. * @typedef {import('./model/Types').Moddle} Moddle
  37. * @typedef {import('./model/Types').ModdleElement} ModdleElement
  38. * @typedef {import('./model/Types').ModdleExtension} ModdleExtension
  39. *
  40. * @typedef { {
  41. * width?: number|string;
  42. * height?: number|string;
  43. * position?: string;
  44. * container?: string|HTMLElement;
  45. * moddleExtensions?: ModdleExtensions;
  46. * additionalModules?: ModuleDeclaration[];
  47. * } & Record<string, any> } BaseViewerOptions
  48. *
  49. * @typedef {Record<string, ModdleElement>} ModdleElementsById
  50. *
  51. * @typedef { {
  52. * [key: string]: ModdleExtension;
  53. * } } ModdleExtensions
  54. *
  55. * @typedef { {
  56. * warnings: string[];
  57. * } } ImportXMLResult
  58. *
  59. * @typedef {ImportXMLResult & Error} ImportXMLError
  60. *
  61. * @typedef {ImportXMLResult} ImportDefinitionsResult
  62. *
  63. * @typedef {ImportXMLError} ImportDefinitionsError
  64. *
  65. * @typedef {ImportXMLResult} OpenResult
  66. *
  67. * @typedef {ImportXMLError} OpenError
  68. *
  69. * @typedef { {
  70. * format?: boolean;
  71. * preamble?: boolean;
  72. * } } SaveXMLOptions
  73. *
  74. * @typedef { {
  75. * xml?: string;
  76. * error?: Error;
  77. * } } SaveXMLResult
  78. *
  79. * @typedef { {
  80. * svg: string;
  81. * } } SaveSVGResult
  82. *
  83. * @typedef { {
  84. * xml: string;
  85. * } } ImportParseStartEvent
  86. *
  87. * @typedef { {
  88. * error?: ImportXMLError;
  89. * definitions?: ModdleElement;
  90. * elementsById?: ModdleElementsById;
  91. * references?: ModdleElement[];
  92. * warnings: string[];
  93. * } } ImportParseCompleteEvent
  94. *
  95. * @typedef { {
  96. * error?: ImportXMLError;
  97. * warnings: string[];
  98. * } } ImportDoneEvent
  99. *
  100. * @typedef { {
  101. * definitions: ModdleElement;
  102. * } } SaveXMLStartEvent
  103. *
  104. * @typedef {SaveXMLResult} SaveXMLDoneEvent
  105. *
  106. * @typedef { {
  107. * error?: Error;
  108. * svg: string;
  109. * } } SaveSVGDoneEvent
  110. */
  111. /**
  112. * A base viewer for BPMN 2.0 diagrams.
  113. *
  114. * Have a look at {@link Viewer}, {@link NavigatedViewer} or {@link Modeler} for
  115. * bundles that include actual features.
  116. *
  117. * @param {BaseViewerOptions} [options] The options to configure the viewer.
  118. */
  119. export default function BaseViewer(options) {
  120. /**
  121. * @type {BaseViewerOptions}
  122. */
  123. options = assign({}, DEFAULT_OPTIONS, options);
  124. /**
  125. * @type {Moddle}
  126. */
  127. this._moddle = this._createModdle(options);
  128. /**
  129. * @type {HTMLElement}
  130. */
  131. this._container = this._createContainer(options);
  132. /* <project-logo> */
  133. addProjectLogo(this._container);
  134. /* </project-logo> */
  135. this._init(this._container, this._moddle, options);
  136. }
  137. inherits(BaseViewer, Diagram);
  138. /**
  139. * Parse and render a BPMN 2.0 diagram.
  140. *
  141. * Once finished the viewer reports back the result to the
  142. * provided callback function with (err, warnings).
  143. *
  144. * ## Life-Cycle Events
  145. *
  146. * During import the viewer will fire life-cycle events:
  147. *
  148. * * import.parse.start (about to read model from XML)
  149. * * import.parse.complete (model read; may have worked or not)
  150. * * import.render.start (graphical import start)
  151. * * import.render.complete (graphical import finished)
  152. * * import.done (everything done)
  153. *
  154. * You can use these events to hook into the life-cycle.
  155. *
  156. * @throws {ImportXMLError} An error thrown during the import of the XML.
  157. *
  158. * @fires BaseViewer#ImportParseStartEvent
  159. * @fires BaseViewer#ImportParseCompleteEvent
  160. * @fires Importer#ImportRenderStartEvent
  161. * @fires Importer#ImportRenderCompleteEvent
  162. * @fires BaseViewer#ImportDoneEvent
  163. *
  164. * @param {string} xml The BPMN 2.0 XML to be imported.
  165. * @param {ModdleElement|string} [bpmnDiagram] The optional diagram or Id of the BPMN diagram to open.
  166. *
  167. * @return {Promise<ImportXMLResult>} A promise resolving with warnings that were produced during the import.
  168. */
  169. BaseViewer.prototype.importXML = async function importXML(xml, bpmnDiagram) {
  170. const self = this;
  171. function ParseCompleteEvent(data) {
  172. return self.get('eventBus').createEvent(data);
  173. }
  174. let aggregatedWarnings = [];
  175. try {
  176. // hook in pre-parse listeners +
  177. // allow xml manipulation
  178. /**
  179. * A `import.parse.start` event.
  180. *
  181. * @event BaseViewer#ImportParseStartEvent
  182. * @type {ImportParseStartEvent}
  183. */
  184. xml = this._emit('import.parse.start', { xml: xml }) || xml;
  185. let parseResult;
  186. try {
  187. parseResult = await this._moddle.fromXML(xml, 'bpmn:Definitions');
  188. } catch (error) {
  189. this._emit('import.parse.complete', {
  190. error
  191. });
  192. throw error;
  193. }
  194. let definitions = parseResult.rootElement;
  195. const references = parseResult.references;
  196. const parseWarnings = parseResult.warnings;
  197. const elementsById = parseResult.elementsById;
  198. aggregatedWarnings = aggregatedWarnings.concat(parseWarnings);
  199. // hook in post parse listeners +
  200. // allow definitions manipulation
  201. /**
  202. * A `import.parse.complete` event.
  203. *
  204. * @event BaseViewer#ImportParseCompleteEvent
  205. * @type {ImportParseCompleteEvent}
  206. */
  207. definitions = this._emit('import.parse.complete', ParseCompleteEvent({
  208. error: null,
  209. definitions: definitions,
  210. elementsById: elementsById,
  211. references: references,
  212. warnings: aggregatedWarnings
  213. })) || definitions;
  214. const importResult = await this.importDefinitions(definitions, bpmnDiagram);
  215. aggregatedWarnings = aggregatedWarnings.concat(importResult.warnings);
  216. /**
  217. * A `import.parse.complete` event.
  218. *
  219. * @event BaseViewer#ImportDoneEvent
  220. * @type {ImportDoneEvent}
  221. */
  222. this._emit('import.done', { error: null, warnings: aggregatedWarnings });
  223. return { warnings: aggregatedWarnings };
  224. } catch (err) {
  225. let error = err;
  226. aggregatedWarnings = aggregatedWarnings.concat(error.warnings || []);
  227. addWarningsToError(error, aggregatedWarnings);
  228. error = checkValidationError(error);
  229. this._emit('import.done', { error, warnings: error.warnings });
  230. throw error;
  231. }
  232. };
  233. /**
  234. * Import parsed definitions and render a BPMN 2.0 diagram.
  235. *
  236. * Once finished the viewer reports back the result to the
  237. * provided callback function with (err, warnings).
  238. *
  239. * ## Life-Cycle Events
  240. *
  241. * During import the viewer will fire life-cycle events:
  242. *
  243. * * import.render.start (graphical import start)
  244. * * import.render.complete (graphical import finished)
  245. *
  246. * You can use these events to hook into the life-cycle.
  247. *
  248. * @throws {ImportDefinitionsError} An error thrown during the import of the definitions.
  249. *
  250. * @param {ModdleElement} definitions The definitions.
  251. * @param {ModdleElement|string} [bpmnDiagram] The optional diagram or ID of the BPMN diagram to open.
  252. *
  253. * @return {Promise<ImportDefinitionsResult>} A promise resolving with warnings that were produced during the import.
  254. */
  255. BaseViewer.prototype.importDefinitions = async function importDefinitions(definitions, bpmnDiagram) {
  256. this._setDefinitions(definitions);
  257. const result = await this.open(bpmnDiagram);
  258. return { warnings: result.warnings };
  259. };
  260. /**
  261. * Open diagram of previously imported XML.
  262. *
  263. * Once finished the viewer reports back the result to the
  264. * provided callback function with (err, warnings).
  265. *
  266. * ## Life-Cycle Events
  267. *
  268. * During switch the viewer will fire life-cycle events:
  269. *
  270. * * import.render.start (graphical import start)
  271. * * import.render.complete (graphical import finished)
  272. *
  273. * You can use these events to hook into the life-cycle.
  274. *
  275. * @throws {OpenError} An error thrown during opening.
  276. *
  277. * @param {ModdleElement|string} bpmnDiagramOrId The diagram or Id of the BPMN diagram to open.
  278. *
  279. * @return {Promise<OpenResult>} A promise resolving with warnings that were produced during opening.
  280. */
  281. BaseViewer.prototype.open = async function open(bpmnDiagramOrId) {
  282. const definitions = this._definitions;
  283. let bpmnDiagram = bpmnDiagramOrId;
  284. if (!definitions) {
  285. const error = new Error('no XML imported');
  286. addWarningsToError(error, []);
  287. throw error;
  288. }
  289. if (typeof bpmnDiagramOrId === 'string') {
  290. bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId);
  291. if (!bpmnDiagram) {
  292. const error = new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found');
  293. addWarningsToError(error, []);
  294. throw error;
  295. }
  296. }
  297. // clear existing rendered diagram
  298. // catch synchronous exceptions during #clear()
  299. try {
  300. this.clear();
  301. } catch (error) {
  302. addWarningsToError(error, []);
  303. throw error;
  304. }
  305. // perform graphical import
  306. const { warnings } = await importBpmnDiagram(this, definitions, bpmnDiagram);
  307. return { warnings };
  308. };
  309. /**
  310. * Export the currently displayed BPMN 2.0 diagram as
  311. * a BPMN 2.0 XML document.
  312. *
  313. * ## Life-Cycle Events
  314. *
  315. * During XML saving the viewer will fire life-cycle events:
  316. *
  317. * * saveXML.start (before serialization)
  318. * * saveXML.serialized (after xml generation)
  319. * * saveXML.done (everything done)
  320. *
  321. * You can use these events to hook into the life-cycle.
  322. *
  323. * @throws {Error} An error thrown during export.
  324. *
  325. * @fires BaseViewer#SaveXMLStart
  326. * @fires BaseViewer#SaveXMLDone
  327. *
  328. * @param {SaveXMLOptions} [options] The options.
  329. *
  330. * @return {Promise<SaveXMLResult>} A promise resolving with the XML.
  331. */
  332. BaseViewer.prototype.saveXML = async function saveXML(options) {
  333. options = options || {};
  334. let definitions = this._definitions,
  335. error, xml;
  336. try {
  337. if (!definitions) {
  338. throw new Error('no definitions loaded');
  339. }
  340. // allow to fiddle around with definitions
  341. /**
  342. * A `saveXML.start` event.
  343. *
  344. * @event BaseViewer#SaveXMLStartEvent
  345. * @type {SaveXMLStartEvent}
  346. */
  347. definitions = this._emit('saveXML.start', {
  348. definitions
  349. }) || definitions;
  350. const result = await this._moddle.toXML(definitions, options);
  351. xml = result.xml;
  352. xml = this._emit('saveXML.serialized', {
  353. xml
  354. }) || xml;
  355. } catch (err) {
  356. error = err;
  357. }
  358. const result = error ? { error } : { xml };
  359. /**
  360. * A `saveXML.done` event.
  361. *
  362. * @event BaseViewer#SaveXMLDoneEvent
  363. * @type {SaveXMLDoneEvent}
  364. */
  365. this._emit('saveXML.done', result);
  366. if (error) {
  367. throw error;
  368. }
  369. return result;
  370. };
  371. /**
  372. * Export the currently displayed BPMN 2.0 diagram as
  373. * an SVG image.
  374. *
  375. * ## Life-Cycle Events
  376. *
  377. * During SVG saving the viewer will fire life-cycle events:
  378. *
  379. * * saveSVG.start (before serialization)
  380. * * saveSVG.done (everything done)
  381. *
  382. * You can use these events to hook into the life-cycle.
  383. *
  384. * @throws {Error} An error thrown during export.
  385. *
  386. * @fires BaseViewer#SaveSVGDone
  387. *
  388. * @return {Promise<SaveSVGResult>} A promise resolving with the SVG.
  389. */
  390. BaseViewer.prototype.saveSVG = async function saveSVG() {
  391. this._emit('saveSVG.start');
  392. let svg, err;
  393. try {
  394. const canvas = this.get('canvas');
  395. const contentNode = canvas.getActiveLayer(),
  396. defsNode = domQuery('defs', canvas._svg);
  397. const contents = innerSVG(contentNode),
  398. defs = defsNode ? '<defs>' + innerSVG(defsNode) + '</defs>' : '';
  399. const bbox = contentNode.getBBox();
  400. svg =
  401. '<?xml version="1.0" encoding="utf-8"?>\n' +
  402. '<!-- created with bpmn-js / http://bpmn.io -->\n' +
  403. '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
  404. '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ' +
  405. 'width="' + bbox.width + '" height="' + bbox.height + '" ' +
  406. 'viewBox="' + bbox.x + ' ' + bbox.y + ' ' + bbox.width + ' ' + bbox.height + '" version="1.1">' +
  407. defs + contents +
  408. '</svg>';
  409. } catch (e) {
  410. err = e;
  411. }
  412. /**
  413. * A `saveSVG.done` event.
  414. *
  415. * @event BaseViewer#SaveSVGDoneEvent
  416. * @type {SaveSVGDoneEvent}
  417. */
  418. this._emit('saveSVG.done', {
  419. error: err,
  420. svg: svg
  421. });
  422. if (err) {
  423. throw err;
  424. }
  425. return { svg };
  426. };
  427. BaseViewer.prototype._setDefinitions = function(definitions) {
  428. this._definitions = definitions;
  429. };
  430. /**
  431. * Return modules to instantiate with.
  432. *
  433. * @return {ModuleDeclaration[]} The modules.
  434. */
  435. BaseViewer.prototype.getModules = function() {
  436. return this._modules;
  437. };
  438. /**
  439. * Remove all drawn elements from the viewer.
  440. *
  441. * After calling this method the viewer can still be reused for opening another
  442. * diagram.
  443. */
  444. BaseViewer.prototype.clear = function() {
  445. if (!this.getDefinitions()) {
  446. // no diagram to clear
  447. return;
  448. }
  449. // remove drawn elements
  450. Diagram.prototype.clear.call(this);
  451. };
  452. /**
  453. * Destroy the viewer instance and remove all its remainders from the document
  454. * tree.
  455. */
  456. BaseViewer.prototype.destroy = function() {
  457. // diagram destroy
  458. Diagram.prototype.destroy.call(this);
  459. // dom detach
  460. domRemove(this._container);
  461. };
  462. /**
  463. * Register an event listener.
  464. *
  465. * Remove an event listener via {@link BaseViewer#off}.
  466. *
  467. * @template T
  468. *
  469. * @param {string|string[]} events The event(s) to listen to.
  470. * @param {number} [priority] The priority with which to listen.
  471. * @param {EventBusEventCallback<T>} callback The callback.
  472. * @param {any} [that] Value of `this` the callback will be called with.
  473. */
  474. BaseViewer.prototype.on = function(events, priority, callback, that) {
  475. return this.get('eventBus').on(events, priority, callback, that);
  476. };
  477. /**
  478. * Remove an event listener.
  479. *
  480. * @param {string|string[]} events The event(s).
  481. * @param {Function} [callback] The callback.
  482. */
  483. BaseViewer.prototype.off = function(events, callback) {
  484. this.get('eventBus').off(events, callback);
  485. };
  486. /**
  487. * Attach the viewer to an HTML element.
  488. *
  489. * @param {HTMLElement} parentNode The parent node to attach to.
  490. */
  491. BaseViewer.prototype.attachTo = function(parentNode) {
  492. if (!parentNode) {
  493. throw new Error('parentNode required');
  494. }
  495. // ensure we detach from the
  496. // previous, old parent
  497. this.detach();
  498. // unwrap jQuery if provided
  499. if (parentNode.get && parentNode.constructor.prototype.jquery) {
  500. parentNode = parentNode.get(0);
  501. }
  502. if (typeof parentNode === 'string') {
  503. parentNode = domQuery(parentNode);
  504. }
  505. parentNode.appendChild(this._container);
  506. this._emit('attach', {});
  507. this.get('canvas').resized();
  508. };
  509. /**
  510. * Get the definitions model element.
  511. *
  512. * @return {ModdleElement} The definitions model element.
  513. */
  514. BaseViewer.prototype.getDefinitions = function() {
  515. return this._definitions;
  516. };
  517. /**
  518. * Detach the viewer.
  519. *
  520. * @fires BaseViewer#DetachEvent
  521. */
  522. BaseViewer.prototype.detach = function() {
  523. const container = this._container,
  524. parentNode = container.parentNode;
  525. if (!parentNode) {
  526. return;
  527. }
  528. /**
  529. * A `detach` event.
  530. *
  531. * @event BaseViewer#DetachEvent
  532. * @type {Object}
  533. */
  534. this._emit('detach', {});
  535. parentNode.removeChild(container);
  536. };
  537. BaseViewer.prototype._init = function(container, moddle, options) {
  538. const baseModules = options.modules || this.getModules(options),
  539. additionalModules = options.additionalModules || [],
  540. staticModules = [
  541. {
  542. bpmnjs: [ 'value', this ],
  543. moddle: [ 'value', moddle ]
  544. }
  545. ];
  546. const diagramModules = [].concat(staticModules, baseModules, additionalModules);
  547. const diagramOptions = assign(omit(options, [ 'additionalModules' ]), {
  548. canvas: assign({}, options.canvas, { container: container }),
  549. modules: diagramModules
  550. });
  551. // invoke diagram constructor
  552. Diagram.call(this, diagramOptions);
  553. if (options && options.container) {
  554. this.attachTo(options.container);
  555. }
  556. };
  557. /**
  558. * Emit an event on the underlying {@link EventBus}
  559. *
  560. * @param {string} type
  561. * @param {Object} event
  562. *
  563. * @return {Object} The return value after calling all event listeners.
  564. */
  565. BaseViewer.prototype._emit = function(type, event) {
  566. return this.get('eventBus').fire(type, event);
  567. };
  568. /**
  569. * @param {BaseViewerOptions} options
  570. *
  571. * @return {HTMLElement}
  572. */
  573. BaseViewer.prototype._createContainer = function(options) {
  574. const container = domify('<div class="bjs-container"></div>');
  575. assignStyle(container, {
  576. width: ensureUnit(options.width),
  577. height: ensureUnit(options.height),
  578. position: options.position
  579. });
  580. return container;
  581. };
  582. /**
  583. * @param {BaseViewerOptions} options
  584. *
  585. * @return {Moddle}
  586. */
  587. BaseViewer.prototype._createModdle = function(options) {
  588. const moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions);
  589. return new BpmnModdle(moddleOptions);
  590. };
  591. BaseViewer.prototype._modules = [];
  592. // helpers ///////////////
  593. function addWarningsToError(err, warningsAry) {
  594. err.warnings = warningsAry;
  595. return err;
  596. }
  597. function checkValidationError(err) {
  598. // check if we can help the user by indicating wrong BPMN 2.0 xml
  599. // (in case he or the exporting tool did not get that right)
  600. const pattern = /unparsable content <([^>]+)> detected([\s\S]*)$/;
  601. const match = pattern.exec(err.message);
  602. if (match) {
  603. err.message =
  604. 'unparsable content <' + match[1] + '> detected; ' +
  605. 'this may indicate an invalid BPMN 2.0 diagram file' + match[2];
  606. }
  607. return err;
  608. }
  609. const DEFAULT_OPTIONS = {
  610. width: '100%',
  611. height: '100%',
  612. position: 'relative'
  613. };
  614. /**
  615. * Ensure the passed argument is a proper unit (defaulting to px)
  616. */
  617. function ensureUnit(val) {
  618. return val + (isNumber(val) ? 'px' : '');
  619. }
  620. /**
  621. * Find BPMNDiagram in definitions by ID
  622. *
  623. * @param {ModdleElement<Definitions>} definitions
  624. * @param {string} diagramId
  625. *
  626. * @return {ModdleElement<BPMNDiagram>|null}
  627. */
  628. function findBPMNDiagram(definitions, diagramId) {
  629. if (!diagramId) {
  630. return null;
  631. }
  632. return find(definitions.diagrams, function(element) {
  633. return element.id === diagramId;
  634. }) || null;
  635. }
  636. /* <project-logo> */
  637. import {
  638. open as openPoweredBy,
  639. BPMNIO_IMG,
  640. LOGO_STYLES,
  641. LINK_STYLES
  642. } from './util/PoweredByUtil';
  643. import {
  644. event as domEvent
  645. } from 'min-dom';
  646. /**
  647. * Adds the project logo to the diagram container as
  648. * required by the bpmn.io license.
  649. *
  650. * @see http://bpmn.io/license
  651. *
  652. * @param {Element} container
  653. */
  654. function addProjectLogo(container) {
  655. const img = BPMNIO_IMG;
  656. const linkMarkup =
  657. '<a href="http://bpmn.io" ' +
  658. 'target="_blank" ' +
  659. 'class="bjs-powered-by" ' +
  660. 'title="Powered by bpmn.io" ' +
  661. '>' +
  662. img +
  663. '</a>';
  664. const linkElement = domify(linkMarkup);
  665. assignStyle(domQuery('svg', linkElement), LOGO_STYLES);
  666. assignStyle(linkElement, LINK_STYLES, {
  667. position: 'absolute',
  668. bottom: '15px',
  669. right: '15px',
  670. zIndex: '100'
  671. });
  672. container.appendChild(linkElement);
  673. domEvent.bind(linkElement, 'click', function(event) {
  674. openPoweredBy();
  675. event.preventDefault();
  676. });
  677. }
  678. /* </project-logo> */