UNPKG

@maxgraph/core

Version:

maxGraph is a fully client side JavaScript diagramming library that uses SVG and HTML for rendering.

153 lines (149 loc) 6.22 kB
/* Copyright 2021-present The maxGraph project Contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ import { isNode } from '../../util/domUtils.js'; import { translate } from '../../internal/i18n-utils.js'; // @ts-expect-error The properties of PartialGraph are defined elsewhere. export const ValidationMixin = { validationAlert(message) { alert(message); }, isEdgeValid(edge, source, target) { return !this.getEdgeValidationError(edge, source, target); }, getEdgeValidationError(edge = null, source = null, target = null) { if (edge && !this.isAllowDanglingEdges() && (!source || !target)) { return ''; } if (edge && !edge.getTerminal(true) && !edge.getTerminal(false)) { return null; } // Checks if we're dealing with a loop if (!this.isAllowLoops() && source === target && source) { return ''; } // Checks if the connection is generally allowed if (!this.isValidConnection(source, target)) { return ''; } if (source && target) { let error = ''; // Checks if the cells are already connected // and adds an error message if required if (!this.isMultigraph()) { const tmp = this.getDataModel().getEdgesBetween(source, target, true); // Checks if the source and target are not connected by another edge if (tmp.length > 1 || (tmp.length === 1 && tmp[0] !== edge)) { error += `${translate(this.getAlreadyConnectedResource()) || this.getAlreadyConnectedResource()}\n`; } } // Gets the number of outgoing edges from the source // and the number of incoming edges from the target // without counting the edge being currently changed. const sourceOut = source.getDirectedEdgeCount(true, edge); const targetIn = target.getDirectedEdgeCount(false, edge); // Checks the change against each multiplicity rule for (const multiplicity of this.multiplicities) { const err = multiplicity.check(this, // needs to cast to Graph edge, source, target, sourceOut, targetIn); if (err != null) { error += err; } } // Validates the source and target terminals independently const err = this.validateEdge(edge, source, target); if (err != null) { error += err; } return error.length > 0 ? error : null; } return this.isAllowDanglingEdges() ? null : ''; }, validateEdge(edge = null, source = null, target = null) { return null; }, validateGraph(cell = null, context) { cell = cell ?? this.getDataModel().getRoot(); if (!cell) { return 'The root does not exist!'; } context = context ?? {}; let isValid = true; const childCount = cell.getChildCount(); for (let i = 0; i < childCount; i += 1) { const tmp = cell.getChildAt(i); let ctx = context; if (this.isValidRoot(tmp)) { ctx = {}; } const warn = this.validateGraph(tmp, ctx); if (warn) { this.setCellWarning(tmp, warn.replace(/\n/g, '<br>')); } else { this.setCellWarning(tmp, null); } isValid = isValid && warn == null; } let warning = ''; // Adds error for invalid children if collapsed (children invisible) if (cell && cell.isCollapsed() && !isValid) { warning += `${translate(this.getContainsValidationErrorsResource()) || this.getContainsValidationErrorsResource()}\n`; } // Checks edges and cells using the defined multiplicities if (cell && cell.isEdge()) { warning += this.getEdgeValidationError(cell, cell.getTerminal(true), cell.getTerminal(false)) || ''; } else { warning += this.getCellValidationError(cell) || ''; } // Checks custom validation rules const err = this.validateCell(cell, context); if (err != null) { warning += err; } // Updates the display with the warning icons // before any potential alerts are displayed. // LATER: Move this into addCellOverlay. Redraw // should check if overlay was added or removed. if (cell.getParent() == null) { this.getView().validate(); } return warning.length > 0 || !isValid ? warning : null; }, getCellValidationError(cell) { const outCount = cell.getDirectedEdgeCount(true); const inCount = cell.getDirectedEdgeCount(false); const value = cell.getValue(); let error = ''; for (let i = 0; i < this.multiplicities.length; i += 1) { const rule = this.multiplicities[i]; if (rule.source && isNode(value, rule.type, rule.attr, rule.value) && (outCount > rule.max || outCount < rule.min)) { error += `${rule.countError}\n`; } else if (!rule.source && isNode(value, rule.type, rule.attr, rule.value) && (inCount > rule.max || inCount < rule.min)) { error += `${rule.countError}\n`; } } return error.length > 0 ? error : null; }, validateCell(cell, context) { return null; }, };