This is my diagram, and as soon as the page loads, the console displays the message “change not within a transaction.” Then, when I try to save, the issue shown in the second image occurs.
the code I have related to the groups is as follows:
onMounted(async () => {
diagram = new Diagram("myDiagramDiv", {
scrollMode: ScrollMode.Infinite,
LinkDrawn: showLinkLabel,
LinkRelinked: showLinkLabel,
'commandHandler.archetypeGroupData': { key: "Group", isGroup: true },
"undoManager.isEnabled": true,
});
diagram.groupTemplate.ungroupable = true
await getQuestionTypes();
await getRules();
await getDataSelect();
await getGroupList();
diagramRef.value = diagram;
$flowchart(diagram);
const dataModel = [
{
category: "Start",
text: "Start",
question_type_id: "Start",
},
{
category: "Question",
text: "Question",
question_type_id: "",
},
{
category: "Option",
text: "Option",
question_type_id: "Option",
},
{
category: "Message",
text: "Message",
question_type_id: "Message",
},
{
category: "Group",
text: "Group",
question_type_id: "Group",
},
{
category: "Validation",
text: "Validation",
question_type_id: "Validation",
},
{
category: "Process",
text: "Process",
question_type_id: "Process",
},
{
category: "Variable",
text: "Variable",
question_type_id: "Variable",
color: "#FFF",
},
{
category: "Loop",
text: "Loop",
question_type_id: "Loop",
},
{
category: "End",
text: "End",
question_type_id: "End",
},
];
new Palette("myPaletteDiv", {
nodeTemplateMap: diagram.nodeTemplateMap,
model: new GraphLinksModel(dataModel),
});
const inspector = new Inspector("myInspectorDiv", diagram, {
multipleSelection: true,
showSize: 4,
showAllProperties: true,
includesOwnProperties: false,
properties: {
key: { name: "Key (*)", type: "text" },
text: {
name: "Label (*)",
show: (nodeData: { category: string }) =>
["Question", "Message", "Option", "Variable", "Process", "Validation", 'Group'].includes(nodeData.category),
},
question_type_id: {
name: "Type of Question (*)",
show: (nodeData: { category: string }) => nodeData.category === "Question",
type: "select",
choices: questionTypes.value.map((item) => item.type),
},
order: {
name: "Order",
show: (nodeData: { category: string }) => nodeData.category === "Option",
type: "input",
},
value: {
name: "Value",
show: (nodeData: { category: string }) => nodeData.category === "Variable",
type: "input",
},
translate: {
name: "Translate (*)",
show: (nodeData: { category: string }) =>
["Question", "Message", "Option", 'Group'].includes(nodeData.category),
type: "input",
},
bgMedia: {
name: "Background Media",
show: (nodeData: { category: string }) =>
["Question", "Message", 'Group'].includes(nodeData.category),
type: "input",
},
bgMediaES: {
name: "Background Media Translate",
show: (nodeData: { category: string }) =>
["Question", "Message", 'Group'].includes(nodeData.category),
type: "input",
},
dataSource: {
name: "Data Source",
show: (nodeData: any) => {
const data = nodeData.hasOwnProperty("si") ? { ...nodeData.si } : nodeData;
return data.question_type_id === "List";
},
type: "select",
choices: dataSource.value.map((item) => item.dataselect_id),
},
length: {
name: "Length",
show: (nodeData: any) => {
const data = nodeData.hasOwnProperty("si") ? { ...nodeData.si } : nodeData;
return ["Number", "Text", "Textarea"].includes(data.question_type_id);
},
type: "input",
},
rules: {
name: "Rules",
show: (nodeData: any) => {
const data = nodeData.hasOwnProperty("si") ? { ...nodeData.si } : nodeData;
return ["Datepicker", "Text", "Textarea"].includes(data.question_type_id);
},
type: "select",
choices: (nodeData: any) => {
const data = nodeData.hasOwnProperty("si") ? { ...nodeData.si } : nodeData;
const questionTypeId = data.question_type_id
? data.question_type_id.toLowerCase()
: "";
return rules.value
.filter((e) => e.question_type_id.toLowerCase() === questionTypeId)
.map((item) => item.field_rule_id);
},
},
required: {
name: "Required",
show: (nodeData: { category: string }) =>
["Question"].includes(nodeData.category),
type: "checkbox",
},
color: {
show: (nodeData: { category: string }) => nodeData.category === "Variable",
type: "color",
defaultValue: "#FFF",
},
list_group: {
name: "Type of group (*)",
show: (nodeData: { category: string }) => nodeData.category === "Group",
type: "select",
choices: groupList.value.map((item) => item.value),
},
},
});
const inspector2 = new Inspector("myInspectorDiv2", diagram, {
inspectSelection: true,
includesOwnProperties: false,
properties: {
helptext: {
type: "input",
name: "Help Text",
show: (nodeData: { category: string }) => nodeData.category === "Message",
},
helptextES: {
type: "input",
name: "Help Text Translate",
show: (nodeData: { category: string }) => nodeData.category === "Message",
},
placeholder: {
type: "input",
name: "Placeholder",
show: (nodeData: { category: string }) => nodeData.category === "Message",
},
placeholderES: {
type: "input",
name: "Placeholder Translate",
show: (nodeData: { category: string }) => nodeData.category === "Message",
},
errorText: {
type: "input",
name: "Error Text",
show: (nodeData: { category: string }) => nodeData.category === "Message",
},
errorTextES: {
type: "input",
name: "Error Text Translate",
show: (nodeData: { category: string }) => nodeData.category === "Message",
},
},
});
document.getElementById("myInspectorDiv2")!.style.display = "none";
const optionTypes = ['Multiple Option', 'Single Option', 'Group', 'List', 'Datepicker'];
const textTypes = ['Text', 'Textarea'];
const propertiesText = ['helptext', 'helptextES', 'placeholder', 'placeholderES', 'errorText', 'errorTextES'];
const propertiesOption = ['helptext', 'helptextES'];
const PropertiesNumber = ['helptext', 'helptextES', 'placeholder', 'placeholderES'];
diagram.addDiagramListener("ChangedSelection", e => {
const selectedNode = e.subject.first();
const category = selectedNode ? selectedNode.data.category : null;
// Mostrar u ocultar el segundo inspector basado en la categoría del nodo seleccionado
if (category === 'Message' || (category === 'Question' && textTypes.includes(selectedNode?.data.question_type_id))) {
showInspector2(propertiesText);
} else if (category === 'Question' && optionTypes.includes(selectedNode?.data.question_type_id) || category === 'Group') {
showInspector2(propertiesOption);
} else if (category === 'Question' && selectedNode?.data.question_type_id === 'Number') {
showInspector2(PropertiesNumber);
} else {
hideInspector2();
}
// Verificar si los nodos Option tienen un padre de la categoría Question
e.subject.each(function (part: go.Node) {
if (part instanceof go.Node && part.data.category === "Option") {
const parent = part.findTreeParentNode();
if (parent && parent.data.category !== "Question") {
removeNodeAndLinks(part);
alert("The 'Option' nodes can only be children of 'Question' nodes");
} else if (parent && parent.data.category === "Question" &&
(parent.data.question_type_id !== 'Multiple Option' && parent.data.question_type_id !== 'Single Option')) {
removeNodeAndLinks(part);
alert("The 'Option' nodes can only be children of 'Question' nodes with type 'Multiple Option' or 'Single Option'");
}
}
});
// Verificar si hay más de un nodo de la categoría Start
let startNodeCount = 0;
e.diagram.nodes.each(function (node: go.Node) {
if (node.data.category === "Start") {
startNodeCount++;
}
});
e.subject.each(function (part: Node) {
if (part instanceof go.Node && part.data.category === "Start") {
if (startNodeCount > 1) {
e.diagram.model.removeNodeData(part.data);
alert("There can only be one node of the 'Start' category");
startNodeCount--;
}
}
});
// Inspeccionar el nodo seleccionado con el inspector principal
if (inspector && inspector.inspectObject !== null) {
inspector.inspectObject(selectedNode ? selectedNode.data : null);
const selectElement = document.querySelector('select[tabindex="2"]');
selectElement?.addEventListener('change', handleInspectorChange);
}
function showInspector2(propertiesToShow: string[]) {
document.getElementById('myInspectorDiv2')!.style.display = 'block';
propertiesToShow.forEach(property => {
inspector2.properties[property].show = true;
});
inspector2.inspectObject(selectedNode ? selectedNode.data : null);
}
function hideInspector2() {
document.getElementById('myInspectorDiv2')!.style.display = 'none';
}
function handleInspectorChange(event: Event) {
switch (selectedNode?.data.question_type_id) {
case 'Text':
case 'Textarea':
showInspector2(propertiesText);
break;
case 'Single Option':
case 'List':
case 'Multiple Option':
case 'Group':
case 'Datepicker':
showInspector2(propertiesOption);
break;
case 'Number':
showInspector2(PropertiesNumber);
break;
default:
hideInspector2();
break;
}
}
});
inspector.inspectObject(diagram);
await renderFlow();
diagram.model = new GraphLinksModel(state.nodeDataArray, state.linkDataArray, {
linkFromPortIdProperty: "formPort",
linkToPortIdProperty: "toPort",
});
// diagram.select(diagram.nodes.first());
});
How can I prevent this behavior?
const saveDiagram = async () => {
const diagram = diagramRef.value as go.Diagram;
if (diagram) {
cleanOrphanLinks()
let hasStartNode = false;
diagram.nodes.each(function (node: go.Node) {
if (node.data.category === "Start") {
hasStartNode = true;
}
});
if (!hasStartNode) {
alert("The diagram must have at least one start node in order to save it");
return;
}
if (await invalidFigure(diagram)) {
alert("All nodes must have a 'key'");
return;
}
const { isValid, errorMessage } = await invalidQuestions(diagram);
if (!isValid) {
alert("Some 'Question' nodes are invalid:\n\n" + errorMessage);
return;
}
if (!await validateLabels(diagram)) {
return;
}
if (await invalidGroups(diagram)) {
alert("All 'Group' nodes must have a 'Type of Group' selected");
return;
}
if (!await validateNoOrphanNodes(diagram)) {
alert("You cannot save the diagram due to orphaned nodes. Please connect or delete them before saving");
return;
}
if (!await validateDiagramCompletion(diagram)) {
alert("All paths in the diagram must end with an 'End' node");
return;
}
diagram.model.nodeDataArray.sort((a, b) => {
const order: any = { "Start": 1, "Question": 2, "Option": 3, "End": 5 };
const aOrder = order[a.category] !== undefined ? order[a.category] : 4;
const bOrder = order[b.category] !== undefined ? order[b.category] : 4;
return aOrder - bOrder;
});
const json = diagram.model.toJson().replace(/\\"/, '');
if (!json) return alert("Diagram is empty");
const jsonData = JSON.parse(json);
try {
await $flowchartApi.saveQuestionFlow(jsonData, question_section_id.value);
alert("Diagram saved successfully");
} catch (error) {
console.error("Error saving diagram:", error);
}
}
};