Skip to content

Instantly share code, notes, and snippets.

@balmas
Created January 22, 2024 13:55
Show Gist options
  • Save balmas/2df476fb1e879a663abe6ce47b058d8c to your computer and use it in GitHub Desktop.
Save balmas/2df476fb1e879a663abe6ce47b058d8c to your computer and use it in GitHub Desktop.
JointJS: Optional Ports
<div id="paper-container"></div>
<div id="side-container">
<fieldset id="output-ports">
<legend>Output ports:</legend>
<div>
<input type="checkbox" id="i1" name="1" checked>
<label for="i1">1</label>
</div>
<div>
<input type="checkbox" id="i2" name="2" checked>
<label for="i2">2</label>
</div>
<div>
<input type="checkbox" id="i3" name="3">
<label for="i3">3</label>
</div>
<div>
<input type="checkbox" id="i4" name="4">
<label for="i4">4</label>
</div>
<div>
<input type="checkbox" id="i5" name="5">
<label for="i5">5</label>
</div>
<div>
<input type="checkbox" id="i6" name="6" checked>
<label for="i6">6</label>
</div>
<div>
<input type="checkbox" id="i7" name="7" checked>
<label for="i7">7</label>
</div>
<div>
<input type="checkbox" id="i8" name="8">
<label for="i8">8</label>
</div>
<div>
<input type="checkbox" id="i9" name="9" checked>
<label for="i9">9</label>
</div>
<div>
<input type="checkbox" id="i0" name="0" checked>
<label for="i0">0</label>
</div>
<div>
<input type="checkbox" id="istar" name="*">
<label for="istart">*</label>
</div>
</fieldset>
</div>
<a target="_blank" href="https://www.jointjs.com">
<img id="logo" src="https://assets.codepen.io/7589991/jointjs-logo.svg" width="200" height="50"></img>
</a>
const { dia, shapes } = joint;
// Paper
const paperContainer = document.getElementById("paper-container");
const graph = new dia.Graph({}, { cellNamespace: shapes });
const paper = new dia.Paper({
model: graph,
cellViewNamespace: shapes,
width: "100%",
height: "100%",
gridSize: 20,
drawGrid: { name: "mesh" },
async: true,
sorting: dia.Paper.sorting.APPROX,
background: { color: "#F3F7F6" },
defaultLink: () => new shapes.standard.Link(),
validateConnection: (sv, _sm, tv, _tm) => sv !== tv,
linkPinning: false,
defaultAnchor: { name: "perpendicular" }
});
paperContainer.appendChild(paper.el);
const PORT_WIDTH = 30;
const PORT_HEIGHT = 20;
const PORT_GAP = 20;
const el1 = new joint.shapes.standard.Rectangle({
position: {
x: 50,
y: 50
},
size: {
width: 400,
height: 90
},
attrs: {
root: {
magnet: false
},
body: {
strokeWidth: 2,
fill: "#555555"
},
label: {
fontWeight: "bold",
fontSize: 20,
fontFamily: "sans-serif",
fill: "#ffffff",
stroke: "#333333",
strokeWidth: 5,
paintOrder: "stroke",
text: "Optional Ports"
}
},
ports: {
groups: {
digits: {
markup: [
{
tagName: "rect",
selector: "portBody"
},
{
tagName: "text",
selector: "portLabel"
}
],
attrs: {
portBody: {
x: 0,
y: -PORT_HEIGHT / 2,
width: "calc(w)",
height: "calc(h)",
fill: "#ffffff",
stroke: "#333333",
strokeWidth: 2,
magnet: "active",
cursor: "grab"
},
portLabel: {
x: "calc(0.5*w)",
textAnchor: "middle",
textVerticalAnchor: "middle",
pointerEvents: "none",
fontWeight: "bold",
fontSize: 12,
fontFamily: "sans-serif"
}
},
size: { width: PORT_WIDTH, height: PORT_HEIGHT },
position: "absolute"
}
},
items: []
}
});
const el2 = new joint.shapes.standard.Rectangle({
position: {
x: 50,
y: 300
},
size: {
width: 400,
height: 90
}
});
const l1 = new joint.shapes.standard.Link({
source: { id: el1.id, port: "1" },
target: { id: el2.id }
});
const l2 = new joint.shapes.standard.Link({
source: { id: el1.id, port: "2" },
target: { id: el2.id }
});
graph.addCells([el1, el2, l1, l2]);
function setPorts(el, digits) {
let width = 0;
// Optional ports
const digitPorts = digits.map((digit, index) => {
const x = index * (PORT_WIDTH + PORT_GAP);
width = x + PORT_WIDTH;
return {
id: `${digit}`,
group: "digits",
attrs: {
portLabel: {
text: `${digit}`
}
},
args: {
x,
y: "100%"
}
};
});
if (digitPorts.length > 0) {
width += PORT_GAP;
}
// Required port.
const fallbackPort = {
id: "fallback",
group: "digits",
size: { width: PORT_WIDTH * 2, height: PORT_HEIGHT },
attrs: {
portLabel: {
text: "fallback"
}
},
args: {
x: width,
y: "100%"
}
};
width += 2 * PORT_WIDTH;
el1.prop(["ports", "items"], [...digitPorts, fallbackPort], {
rewrite: true
});
el1.prop(["size", "width"], width);
}
// Update element from html inputs
const outputPortsEl = document.getElementById("output-ports");
outputPortsEl.addEventListener("change", () => update());
function update() {
const digits = [];
Array.from(outputPortsEl.querySelectorAll("input")).forEach((input) => {
if (input.checked) digits.push(input.name);
});
setPorts(el1, digits);
}
update();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.4.0/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jointjs/3.4.4/joint.min.js"></script>
$side_width: 200px;
#paper-container {
position: absolute;
left: $side_width;
top: 0;
right: 0;
bottom: 0;
overflow: scroll;
}
#side-container {
position: absolute;
left: 0;
top: 0;
width: $side_width;
bottom: 0;
overflow: scroll;
font-family: sans-serif;
padding: 5px;
legend {
background-color: #555;
color: #fff;
padding: 3px 6px;
}
}
#logo {
position: absolute;
bottom: 20px;
right: 20px;
background-color: #ffffff;
border: 1px solid #d3d3d3;
padding: 5px;
box-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.3);
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/jointjs/3.4.4/joint.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment