mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-23 09:20:03 +02:00
Merge pull request #1170 from jgreywolf/1064-ClickEventInClassDiagram
1064 click event in class diagram
This commit is contained in:
@@ -355,6 +355,101 @@ class Shape{
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Interaction
|
||||||
|
|
||||||
|
It is possible to bind a click event to a node, the click can lead to either a javascript callback or to a link which will be opened in a new browser tab. **Note**: This functionality is disabled when using `securityLevel='strict'` and enabled when using `securityLevel='loose'`.
|
||||||
|
|
||||||
|
You would define these actions on a separate line after all classes have been declared.
|
||||||
|
|
||||||
|
```
|
||||||
|
action className "reference" "tooltip"
|
||||||
|
```
|
||||||
|
|
||||||
|
* _action_ is either `link` or `callback`, depending on which type of interaction you want to have called
|
||||||
|
* _className_ is the id of the node that the action will be associated with
|
||||||
|
* _reference_ is either the url link, or the function name for callback. (note: callback function will be called with the nodeId as parameter).
|
||||||
|
* (_optional_) tooltip is a string to be displayed when hovering over element (note: The styles of the tooltip are set by the class .mermaidTooltip.)
|
||||||
|
|
||||||
|
### Examples:
|
||||||
|
|
||||||
|
*URL Link:*
|
||||||
|
|
||||||
|
```
|
||||||
|
classDiagram
|
||||||
|
class Shape
|
||||||
|
link Shape "http://www.github.com" "This is a tooltip for a link"
|
||||||
|
```
|
||||||
|
|
||||||
|
*Callback:*
|
||||||
|
|
||||||
|
```
|
||||||
|
classDiagram
|
||||||
|
class Shape
|
||||||
|
callback Shape "callbackFunction" "This is a tooltip for a callback"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
<script>
|
||||||
|
var callbackFunction = function(){
|
||||||
|
alert('A callback was triggered');
|
||||||
|
}
|
||||||
|
<script>
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Class01
|
||||||
|
class Class02
|
||||||
|
callback Class01 "callbackFunction" "Callback tooltip"
|
||||||
|
link Class02 "http://www.github.com" "This is a link"
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2.
|
||||||
|
|
||||||
|
Beginners tip, a full example using interactive links in an html context:
|
||||||
|
```
|
||||||
|
<body>
|
||||||
|
<div class="mermaid">
|
||||||
|
classDiagram
|
||||||
|
Animal <|-- Duck
|
||||||
|
Animal <|-- Fish
|
||||||
|
Animal <|-- Zebra
|
||||||
|
Animal : +int age
|
||||||
|
Animal : +String gender
|
||||||
|
Animal: +isMammal()
|
||||||
|
Animal: +mate()
|
||||||
|
class Duck{
|
||||||
|
+String beakColor
|
||||||
|
+swim()
|
||||||
|
+quack()
|
||||||
|
}
|
||||||
|
class Fish{
|
||||||
|
-int sizeInFeet
|
||||||
|
-canEat()
|
||||||
|
}
|
||||||
|
class Zebra{
|
||||||
|
+bool is_wild
|
||||||
|
+run()
|
||||||
|
}
|
||||||
|
|
||||||
|
callback Duck callback "Tooltip"
|
||||||
|
click Zebra "http://www.github.com" "This is a link"
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var callback = function(){
|
||||||
|
alert('A callback was triggered');
|
||||||
|
}
|
||||||
|
var config = {
|
||||||
|
startOnLoad:true,
|
||||||
|
securityLevel:'loose',
|
||||||
|
};
|
||||||
|
|
||||||
|
mermaid.initialize(config);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
```
|
||||||
|
|
||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
Styling of the class diagram is done by defining a number of css classes. During rendering these classes are extracted from the file located at src/themes/class.scss
|
Styling of the class diagram is done by defining a number of css classes. During rendering these classes are extracted from the file located at src/themes/class.scss
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
|
||||||
import { logger } from '../../logger';
|
import { logger } from '../../logger';
|
||||||
import { getConfig } from '../../config';
|
import { getConfig } from '../../config';
|
||||||
|
import utils from '../../utils';
|
||||||
|
|
||||||
const MERMAID_DOM_ID_PREFIX = '';
|
const MERMAID_DOM_ID_PREFIX = 'classid-';
|
||||||
|
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
|
|
||||||
@@ -155,14 +155,10 @@ export const setLink = function(ids, linkStr, tooltip) {
|
|||||||
let id = _id;
|
let id = _id;
|
||||||
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
||||||
if (typeof classes[id] !== 'undefined') {
|
if (typeof classes[id] !== 'undefined') {
|
||||||
if (config.securityLevel !== 'loose') {
|
classes[id].link = utils.formatUrl(linkStr, config);
|
||||||
classes[id].link = sanitizeUrl(linkStr);
|
|
||||||
} else {
|
|
||||||
classes[id].link = linkStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tooltip) {
|
if (tooltip) {
|
||||||
classes[id].tooltip = tooltip;
|
classes[id].tooltip = utils.sanitize(tooltip, config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -182,9 +178,10 @@ export const setClickEvent = function(ids, functionName, tooltip) {
|
|||||||
setCssClass(ids, 'clickable');
|
setCssClass(ids, 'clickable');
|
||||||
};
|
};
|
||||||
|
|
||||||
const setClickFunc = function(_id, functionName) {
|
const setClickFunc = function(_id, functionName, tooltip) {
|
||||||
let id = _id;
|
let id = _id;
|
||||||
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
let elemId = MERMAID_DOM_ID_PREFIX + id;
|
||||||
|
|
||||||
if (config.securityLevel !== 'loose') {
|
if (config.securityLevel !== 'loose') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -192,14 +189,17 @@ const setClickFunc = function(_id, functionName) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof classes[id] !== 'undefined') {
|
if (typeof classes[id] !== 'undefined') {
|
||||||
|
if (tooltip) {
|
||||||
|
classes[id].tooltip = utils.sanitize(tooltip, config);
|
||||||
|
}
|
||||||
|
|
||||||
funs.push(function() {
|
funs.push(function() {
|
||||||
const elem = document.querySelector(`[id="${id}"]`);
|
const elem = document.querySelector(`[id="${elemId}"]`);
|
||||||
if (elem !== null) {
|
if (elem !== null) {
|
||||||
elem.setAttribute('title', classes[id].tooltip);
|
|
||||||
elem.addEventListener(
|
elem.addEventListener(
|
||||||
'click',
|
'click',
|
||||||
function() {
|
function() {
|
||||||
window[functionName](id);
|
window[functionName](elemId);
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
@@ -8,9 +8,9 @@ import { parser } from './parser/classDiagram';
|
|||||||
|
|
||||||
parser.yy = classDb;
|
parser.yy = classDb;
|
||||||
|
|
||||||
|
const MERMAID_DOM_ID_PREFIX = 'classid-';
|
||||||
let idCache = {};
|
let idCache = {};
|
||||||
|
|
||||||
let classCnt = 0;
|
|
||||||
const conf = {
|
const conf = {
|
||||||
dividerMargin: 10,
|
dividerMargin: 10,
|
||||||
padding: 5,
|
padding: 5,
|
||||||
@@ -322,7 +322,7 @@ const drawClass = function(elem, classDef) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const id = 'classId' + classCnt;
|
const id = MERMAID_DOM_ID_PREFIX + classDef.id;
|
||||||
const classInfo = {
|
const classInfo = {
|
||||||
id: id,
|
id: id,
|
||||||
label: classDef.id,
|
label: classDef.id,
|
||||||
@@ -342,8 +342,7 @@ const drawClass = function(elem, classDef) {
|
|||||||
title = g
|
title = g
|
||||||
.append('svg:a')
|
.append('svg:a')
|
||||||
.attr('xlink:href', classDef.link)
|
.attr('xlink:href', classDef.link)
|
||||||
.attr('xlink:target', '_blank')
|
.attr('target', '_blank')
|
||||||
.attr('xlink:title', classDef.tooltip)
|
|
||||||
.append('text')
|
.append('text')
|
||||||
.attr('y', conf.textHeight + conf.padding)
|
.attr('y', conf.textHeight + conf.padding)
|
||||||
.attr('x', 0);
|
.attr('x', 0);
|
||||||
@@ -435,6 +434,10 @@ const drawClass = function(elem, classDef) {
|
|||||||
x.setAttribute('x', (rectWidth - x.getBBox().width) / 2);
|
x.setAttribute('x', (rectWidth - x.getBBox().width) / 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (classDef.tooltip) {
|
||||||
|
title.insert('title').text(classDef.tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
membersLine.attr('x2', rectWidth);
|
membersLine.attr('x2', rectWidth);
|
||||||
methodsLine.attr('x2', rectWidth);
|
methodsLine.attr('x2', rectWidth);
|
||||||
|
|
||||||
@@ -442,7 +445,6 @@ const drawClass = function(elem, classDef) {
|
|||||||
classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin;
|
classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin;
|
||||||
|
|
||||||
idCache[id] = classInfo;
|
idCache[id] = classInfo;
|
||||||
classCnt++;
|
|
||||||
return classInfo;
|
return classInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
|
||||||
import { logger } from '../../logger';
|
import { logger } from '../../logger';
|
||||||
import utils from '../../utils';
|
import utils from '../../utils';
|
||||||
import { getConfig } from '../../config';
|
import { getConfig } from '../../config';
|
||||||
@@ -20,25 +19,6 @@ let direction;
|
|||||||
// Functions to be run after graph rendering
|
// Functions to be run after graph rendering
|
||||||
let funs = [];
|
let funs = [];
|
||||||
|
|
||||||
const sanitize = text => {
|
|
||||||
let txt = text;
|
|
||||||
let htmlLabels = true;
|
|
||||||
if (
|
|
||||||
config.flowchart &&
|
|
||||||
(config.flowchart.htmlLabels === false || config.flowchart.htmlLabels === 'false')
|
|
||||||
)
|
|
||||||
htmlLabels = false;
|
|
||||||
if (config.securityLevel !== 'loose' && htmlLabels) { // eslint-disable-line
|
|
||||||
txt = txt.replace(/<br>/g, '#br#');
|
|
||||||
txt = txt.replace(/<br\S*?\/>/g, '#br#');
|
|
||||||
txt = txt.replace(/</g, '<').replace(/>/g, '>');
|
|
||||||
txt = txt.replace(/=/g, '=');
|
|
||||||
txt = txt.replace(/#br#/g, '<br/>');
|
|
||||||
}
|
|
||||||
|
|
||||||
return txt;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function called by parser when a node definition has been found
|
* Function called by parser when a node definition has been found
|
||||||
* @param id
|
* @param id
|
||||||
@@ -63,7 +43,7 @@ export const addVertex = function(_id, text, type, style, classes) {
|
|||||||
vertices[id] = { id: id, styles: [], classes: [] };
|
vertices[id] = { id: id, styles: [], classes: [] };
|
||||||
}
|
}
|
||||||
if (typeof text !== 'undefined') {
|
if (typeof text !== 'undefined') {
|
||||||
txt = sanitize(text.trim());
|
txt = utils.sanitize(text.trim(), config);
|
||||||
|
|
||||||
// strip quotes if string starts and ends with a quote
|
// strip quotes if string starts and ends with a quote
|
||||||
if (txt[0] === '"' && txt[txt.length - 1] === '"') {
|
if (txt[0] === '"' && txt[txt.length - 1] === '"') {
|
||||||
@@ -113,7 +93,7 @@ export const addSingleLink = function(_start, _end, type, linktext) {
|
|||||||
linktext = type.text;
|
linktext = type.text;
|
||||||
|
|
||||||
if (typeof linktext !== 'undefined') {
|
if (typeof linktext !== 'undefined') {
|
||||||
edge.text = sanitize(linktext.trim());
|
edge.text = utils.sanitize(linktext.trim(), config);
|
||||||
|
|
||||||
// strip quotes if string starts and exnds with a quote
|
// strip quotes if string starts and exnds with a quote
|
||||||
if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') {
|
if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') {
|
||||||
@@ -225,7 +205,7 @@ export const setClass = function(ids, className) {
|
|||||||
const setTooltip = function(ids, tooltip) {
|
const setTooltip = function(ids, tooltip) {
|
||||||
ids.split(',').forEach(function(id) {
|
ids.split(',').forEach(function(id) {
|
||||||
if (typeof tooltip !== 'undefined') {
|
if (typeof tooltip !== 'undefined') {
|
||||||
tooltips[id] = sanitize(tooltip);
|
tooltips[id] = utils.sanitize(tooltip, config);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -266,11 +246,7 @@ export const setLink = function(ids, linkStr, tooltip) {
|
|||||||
let id = _id;
|
let id = _id;
|
||||||
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
||||||
if (typeof vertices[id] !== 'undefined') {
|
if (typeof vertices[id] !== 'undefined') {
|
||||||
if (config.securityLevel !== 'loose') {
|
vertices[id].link = utils.formatUrl(linkStr, config);
|
||||||
vertices[id].link = sanitizeUrl(linkStr); // .replace(/javascript:.*/g, '')
|
|
||||||
} else {
|
|
||||||
vertices[id].link = linkStr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setTooltip(ids, tooltip);
|
setTooltip(ids, tooltip);
|
||||||
@@ -429,7 +405,7 @@ export const addSubGraph = function(_id, list, _title) {
|
|||||||
id = id || 'subGraph' + subCount;
|
id = id || 'subGraph' + subCount;
|
||||||
if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
|
||||||
title = title || '';
|
title = title || '';
|
||||||
title = sanitize(title);
|
title = utils.sanitize(title, config);
|
||||||
subCount = subCount + 1;
|
subCount = subCount + 1;
|
||||||
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] };
|
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] };
|
||||||
subGraphs.push(subGraph);
|
subGraphs.push(subGraph);
|
||||||
|
@@ -613,6 +613,9 @@ const render = function(id, txt, cb, container) {
|
|||||||
case 'gantt':
|
case 'gantt':
|
||||||
cb(svgCode, ganttDb.bindFunctions);
|
cb(svgCode, ganttDb.bindFunctions);
|
||||||
break;
|
break;
|
||||||
|
case 'class':
|
||||||
|
cb(svgCode, classDb.bindFunctions);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
cb(svgCode);
|
cb(svgCode);
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,10 @@ g.classGroup text {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
g.classGroup rect {
|
g.classGroup rect {
|
||||||
fill: $nodeBkg;
|
fill: $nodeBkg;
|
||||||
stroke: $nodeBorder;
|
stroke: $nodeBorder;
|
||||||
|
41
src/utils.js
41
src/utils.js
@@ -1,5 +1,6 @@
|
|||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
|
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @function detectType
|
* @function detectType
|
||||||
@@ -73,6 +74,42 @@ export const interpolateToCurve = (interpolate, defaultCurve) => {
|
|||||||
return d3[curveName] || defaultCurve;
|
return d3[curveName] || defaultCurve;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sanitize = (text, config) => {
|
||||||
|
let txt = text;
|
||||||
|
let htmlLabels = true;
|
||||||
|
if (
|
||||||
|
config.flowchart &&
|
||||||
|
(config.flowchart.htmlLabels === false || config.flowchart.htmlLabels === 'false')
|
||||||
|
)
|
||||||
|
htmlLabels = false;
|
||||||
|
|
||||||
|
if (config.securityLevel !== 'loose' && htmlLabels) { // eslint-disable-line
|
||||||
|
txt = txt.replace(/<br>/g, '#br#');
|
||||||
|
txt = txt.replace(/<br\S*?\/>/g, '#br#');
|
||||||
|
txt = txt.replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
txt = txt.replace(/=/g, '=');
|
||||||
|
txt = txt.replace(/#br#/g, '<br/>');
|
||||||
|
}
|
||||||
|
|
||||||
|
return txt;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatUrl = (linkStr, config) => {
|
||||||
|
let url = linkStr.trim();
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
if (config.securityLevel !== 'loose') {
|
||||||
|
return sanitizeUrl(url);
|
||||||
|
} else {
|
||||||
|
if (!/^(https?:)?\/\//i.test(url)) {
|
||||||
|
url = 'http://' + url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const distance = (p1, p2) =>
|
const distance = (p1, p2) =>
|
||||||
p1 && p2 ? Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)) : 0;
|
p1 && p2 ? Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)) : 0;
|
||||||
|
|
||||||
@@ -174,5 +211,7 @@ export default {
|
|||||||
isSubstringInArray,
|
isSubstringInArray,
|
||||||
interpolateToCurve,
|
interpolateToCurve,
|
||||||
calcLabelPosition,
|
calcLabelPosition,
|
||||||
calcCardinalityPosition
|
calcCardinalityPosition,
|
||||||
|
sanitize,
|
||||||
|
formatUrl
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user