DHTMLX Kanban. Guarding against duplicate or contradictory links example
The Kanban's link editor already prevents users from creating duplicate links. Already-linked cards are filtered out of the dropdown. However, when links are added programmatically via api.exec("add-link", ...), that UI filter is bypassed. The add-link handler only checks for duplicate IDs, so duplicate source/target/relation combinations can slip through. An interceptor can guard against this and block or report invalid programmatic additions.
Live example
const { Kanban } = kanban;
const { cards, columns, links } = getData();
const board = new Kanban("#root", {
columns,
cards,
links,
editorShape: [
{
type:"text",
key: "label",
label:"Label",
},
{
type: "links",
key: "links",
label: "Links",
},
],
});
function sameDirection(link, test){
return test.source == link.source
&& test.target == link.target;
}
function oppositeDirection(link, test){
return test.target == link.source
&& test.source == link.target;
}
function alreadyLinked(link) {
const links = board.api.getState().links;
return links.find(
l =>
(sameDirection(link, l) || oppositeDirection(link, l)) && link.relation === l.relation
);
}
board.api.intercept("add-link", ({ link }) => {
const exists = alreadyLinked(link);
if (exists) {
dhx.message({
text: "These cards are already linked with a link of this type",
css: "dhx_message--error",
expire: 2000,
});
return false;
}
});
function addIncorrectLink() {
dhx.message({
text: "Trying to add a link that already exists or mutually contradicts some of the links that already exist",
expire: 2000,
});
board.api.exec("add-link", {
link: links[0]
})
}<link rel="stylesheet" href="https://snippet.dhtmlx.com/codebase/assets/css/auxiliary_controls.css">
<!-- auxiliary controls for interacting with the sample -->
<div class="g-wrap" style="height: 100%;">
<div style="display: flex; flex-wrap: wrap; height: auto;">
<button class="dhx_sample-btn dhx_sample-btn--cta" onclick="addIncorrectLink()">Add incorrect link</button>
</div>
<!-- component container -->
<div id="root" style="height: 100%;"></div>
</div>
<!-- dataset -->
<script src="https://snippet.dhtmlx.com/codebase/data/kanban/01/dataset.js"></script>
<style>
.dhx_sample-btn {
margin: 4px;
}
</style>The code intercepts add-link events and checks the existing links via board.api.getState().links. The alreadyLinked function tests whether a link with the same source/target pair (or its reverse) and the same relation type already exists. If a duplicate is found, the interceptor returns false to block the link and displays an error message using dhx.message. A demo button triggers api.exec("add-link") with a known duplicate to demonstrate the validation.
Solution overview
- Use
board.api.intercept("add-link", callback)to validate before a link is created - Get current links from
board.api.getState().links - Check both same-direction and opposite-direction duplicates for the same
relationtype - Return
falsefrom the interceptor to block the link - Show feedback with
dhx.messagewhen a link is rejected
Key points
- Check both directions: A→B and B→A with the same relation type are considered contradictory. Check
oppositeDirectionas well assameDirection relationmatters: Two links between the same cards are valid if they have different relation typesapi.getState().links: This returns the current links array, useful for any validation that needs to inspect existing data- Returning
falsefromintercept: This silently cancels the action. Always pair it with user feedback (likedhx.message) so the user knows why their action was blocked
API reference
- api.intercept: Intercepts events before they are applied
- api.getState: Returns the current board state
- links: Link data structure