goJS disable adding new nodes when no options in dropdown - javascript

I have simple python flask app where I send JSON data to my HTML and with goJS I display my graph which looks like this:
I made custom choices dropdown for users to edit node and link text. Those choice options are read from .txt file and sent to html via flask. Options in dropdown lists are made so that when option is selected once, it can not be selected again, until user delete node or link with that used option, and then he can use that option again. So far, I used this code to make nodes text selectable in dropdown list:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>State Chart</title>
<meta name="description" content="A finite state machine chart with editable and interactive features." />
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="{{url_for('static', filename='go.js')}}"></script>
<!-- custom text editors -->
<script src="{{url_for('static', filename='TextEditorSelectBox.js')}}"></script>
<script src="{{url_for('static', filename='TextEditorRadioButtons.js')}}"></script>
<script src="{{url_for('static', filename='TextEditorSelectBox.js')}}"></script>
<script src="{{url_for('static', filename='DataInspector.js')}}"></script>
<link href="https://gojs.net/latest/extensions/DataInspector.css" rel="stylesheet">
<link href="{{url_for('static', filename='DataInspector.css')}}" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script id="code">
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv", // must name or refer to the DIV HTML element
{
// start everything in the middle of the viewport
initialContentAlignment: go.Spot.Center,
// have mouse wheel events zoom in and out instead of scroll up and down
"toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom,
// support double-click in background creating a new node
"clickCreatingTool.archetypeNodeData": { text: "new node" },
// enable undo & redo
"textEditingTool.defaultTextEditor": window.TextEditorSelectBox,
"undoManager.isEnabled": true,
"layout": new go.ForceDirectedLayout(),
"ModelChanged": function(e) {
console.log("Diagram model changed!");
if (e.change === go.ChangedEvent.Remove && e.modelChange === "linkDataArray") {
console.log("eee");
console.log(e);
var linkdata = e.oldValue;
console.log("linkdata");
console.log(linkdata);
var oldstr = linkdata.text;
console.log("oldstr");
console.log(oldstr);
if (!oldstr) return;
var choices = e.model.modelData.choices;
console.log("choices");
console.log(choices);
var idx = choices.indexOf(oldstr);
if (idx < 0) {
console.log("adding choice: " + oldstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.push(oldstr);
e.model.set(e.model.modelData, "choices", newchoices);
}
}
}
});
//myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
console.log("myDiagram.model.modelData");
console.log(myDiagram.model.modelData);
console.log("myDiagram.model.modelData.choices");
console.log(myDiagram.model.modelData.choices);
// when the document is modified, add a "*" to the title and enable the "Save" button
myDiagram.addDiagramListener("Modified", function(e) {
var button = document.getElementById("SaveButton");
if (button) button.disabled = !myDiagram.isModified;
var idx = document.title.indexOf("*");
if (myDiagram.isModified) {
if (idx < 0) document.title += "*";
}
else {
if (idx >= 0) document.title = document.title.substr(0, idx);
}
});
myDiagram.addDiagramListener("textEdited", function(e) {
console.log("Text is edited");
console.log(e);
//CHECK IF LINK,
//IF YES REMOVE THAT OPTION FROM LIST
});
myDiagram.addDiagramListener("SelectionDeleting", function(e) {
console.log("inside SelectionDeleting");
console.log(e);
//CHECK IF LINK,
//IF YES PUT THAT OPTION BACK IN OPTION LIST
});
// define the Node template
myDiagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
// define the node's outer shape, which will surround the TextBlock
$(go.Shape, "RoundedRectangle",
{
parameter1: 20, // the corner has a large radius
fill: $(go.Brush, "Linear", { 0: "rgb(254, 201, 0)", 1: "rgb(254, 162, 0)" }),
stroke: null,
portId: "", // this Shape is the Node's port, not the whole Node
fromLinkable: true, fromLinkableDuplicates: true,
toLinkable: true, toLinkableDuplicates: true,
cursor: "pointer"
}),
$(go.TextBlock,
{
font: "bold 11pt helvetica, bold arial, sans-serif",
editable: true, // editing the text automatically updates the model data
//textEditor: window.TextEditorRadioButtons, // defined in textEditorRadioButtons.js
// this specific TextBlock has its own choices:
textEditor: window.TextEditorRadioButtons,
//choices: JSON.parse('{{ choices | tojson | safe}}')
choices: nodeChoices
},
new go.Binding("text").makeTwoWay())
);
myDiagram.nodeTemplate.selectionAdornmentTemplate =
$(go.Adornment, "Spot",
$(go.Panel, "Auto",
$(go.Shape, { stroke: "dodgerblue", strokeWidth: 2, fill: null }),
$(go.Placeholder)
),
$(go.Panel, "Horizontal",
{ alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom },
$("Button",
{ click: editText }, // defined below, to support editing the text of the node
$(go.TextBlock, "t",
{ font: "bold 10pt sans-serif", desiredSize: new go.Size(15, 15), textAlign: "center" })
),
$("Button",
{ // drawLink is defined below, to support interactively drawing new links
click: drawLink, // click on Button and then click on target node
actionMove: drawLink // drag from Button to the target node
},
$(go.Shape,
{ geometryString: "M0 0 L8 0 8 12 14 12 M12 10 L14 12 12 14" })
),
$("Button",
{
actionMove: dragNewNode, // defined below, to support dragging from the button
_dragData: { text: "?????", color: "lightgray" }, // node data to copy
click: clickNewNode // defined below, to support a click on the button
},
$(go.Shape,
{ geometryString: "M0 0 L3 0 3 10 6 10 x F1 M6 6 L14 6 14 14 6 14z", fill: "gray" })
)
)
);
//myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
console.log("myDiagram.model.modelData");
console.log(myDiagram.model.modelData);
console.log(myDiagram.model.modelData.choices);
function editText(e, button) {
//console.log(e);
var node = button.part.adornedPart;
console.log("node");
//console.log(node);
e.diagram.commandHandler.editTextBlock(node.findObject("TEXTBLOCK"));
//$("#nodeText").val(node.findObject("TEXTBLOCK"));
}
function drawLink(e, button) {
var node = button.part.adornedPart;
var tool = e.diagram.toolManager.linkingTool;
tool.startObject = node.port;
e.diagram.currentTool = tool;
tool.doActivate();
}
// used by both clickNewNode and dragNewNode to create a node and a link
// from a given node to the new node
function createNodeAndLink(data, fromnode) {
var diagram = fromnode.diagram;
var model = diagram.model;
var nodedata = model.copyNodeData(data);
model.addNodeData(nodedata);
var newnode = diagram.findNodeForData(nodedata);
var linkdata = model.copyLinkData({});
model.setFromKeyForLinkData(linkdata, model.getKeyForNodeData(fromnode.data));
model.setToKeyForLinkData(linkdata, model.getKeyForNodeData(newnode.data));
model.addLinkData(linkdata);
diagram.select(newnode);
return newnode;
}
// the Button.click event handler, called when the user clicks the "N" button
function clickNewNode(e, button) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("Create Node and Link");
var fromnode = button.part.adornedPart;
var newnode = createNodeAndLink(button._dragData, fromnode);
newnode.location = new go.Point(fromnode.location.x + 200, fromnode.location.y);
e.diagram.commitTransaction("Create Node and Link");
}
// the Button.actionMove event handler, called when the user drags within the "N" button
function dragNewNode(e, button) {
var tool = e.diagram.toolManager.draggingTool;
if (tool.isBeyondDragSize()) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("button drag"); // see doDeactivate, below
var newnode = createNodeAndLink(data, button.part.adornedPart);
newnode.location = e.diagram.lastInput.documentPoint;
// don't commitTransaction here, but in tool.doDeactivate, after drag operation finished
// set tool.currentPart to a selected movable Part and then activate the DraggingTool
tool.currentPart = newnode;
e.diagram.currentTool = tool;
tool.doActivate();
}
}
// using dragNewNode also requires modifying the standard DraggingTool so that it
// only calls commitTransaction when dragNewNode started a "button drag" transaction;
// do this by overriding DraggingTool.doDeactivate:
var tool = myDiagram.toolManager.draggingTool;
tool.doDeactivate = function() {
// commit "button drag" transaction, if it is ongoing; see dragNewNode, above
if (tool.diagram.undoManager.nestedTransactionNames.elt(0) === "button drag") {
tool.diagram.commitTransaction();
}
go.DraggingTool.prototype.doDeactivate.call(tool); // call the base method
};
// replace the default Link template in the linkTemplateMap
myDiagram.linkTemplate =
$(go.Link, // the whole link panel
{
curve: go.Link.Bezier,
adjusting: go.Link.Stretch,
reshapable: true,
relinkableFrom: true,
relinkableTo: true,
toShortLength: 3
},
new go.Binding("points").makeTwoWay(),
new go.Binding("curviness"),
$(go.Shape, // the link shape
{ strokeWidth: 1.5 }),
$(go.Shape, // the arrowhead
{ toArrow: "standard", stroke: null }),
$(go.Panel, "Auto",
$(go.Shape, // the label background, which becomes transparent around the edges
{
fill: $(go.Brush, "Radial", { 0: "rgb(240, 240, 240)", 0.3: "rgb(240, 240, 240)", 1: "rgba(240, 240, 240, 0)" }),
stroke: null
}),
$(go.TextBlock,
{
background: "white",
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
var choices = tb.diagram.model.modelData.choices;
console.log("choices");
console.log(choices);
console.log("newstr");
console.log(newstr);
console.log("oldstr");
console.log(oldstr);
var idx = choices.indexOf(newstr);
if (idx >= 0 && oldstr !== newstr) {
console.log(choices);
console.log("choices");
console.log(choices);
console.log("removing choice " + idx + ": " + newstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.splice(idx, 1);
tb.diagram.model.set(tb.diagram.model.modelData, "choices", newchoices);
tb.editable = false; // don't allow choice again
}
}
},
// editing the text automatically updates the model data
//new go.Binding("text"),
new go.Binding("text").makeTwoWay(),
new go.Binding("choices").ofModel())
)
);
var inspector = new Inspector('myInspectorDiv', myDiagram,
{
// uncomment this line to only inspect the named properties below instead of all properties on each object:
// includesOwnProperties: false,
properties: {
"text": { },
// an example of specifying the type
"password": { show: Inspector.showIfPresent, type: 'password' },
// key would be automatically added for nodes, but we want to declare it read-only also:
"key": { readOnly: true, show: Inspector.showIfPresent },
// color would be automatically added for nodes, but we want to declare it a color also:
"color": { show: Inspector.showIfPresent, type: 'color' },
// Comments and LinkComments are not in any node or link data (yet), so we add them here:
"Comments": { show: Inspector.showIfNode },
"flag": { show: Inspector.showIfNode, type: 'checkbox' },
"LinkComments": { show: Inspector.showIfLink },
"isGroup": { readOnly: true, show: Inspector.showIfPresent }
}
});
// read in the JSON data from flask
loadGraphData();
}
function loadGraphData() {
var graphDataString = JSON.parse('{{ diagramData | tojson | safe}}');
//console.log("graphDataString");
//console.log(graphDataString);
myDiagram.model = go.Model.fromJson(graphDataString);
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
console.log("whole model");
console.log(myDiagram.model);
}
function saveGraphData(form, event) {
console.log("inside saveGraphData");
event.preventDefault();
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
form.submit();
}
function zoomToFit(){
console.log("inside zoomToFit");
myDiagram.zoomToRect(myDiagram.documentBounds);
}
function zoomIn(){
console.log("inside zoomIn");
myDiagram.commandHandler.increaseZoom();
}
function zoomOut(){
console.log("inside zoomOut");
myDiagram.commandHandler.decreaseZoom();
}
</script>
</head>
<body onload="init()">
<div id=formWrapper style="padding: 30px;">
<form method="POST" action="http://localhost:5000/updateResultFile" name="updateResultFileForm"
id="updateResultFileForm"
onsubmit="saveGraphData(this, event);">
<div id="graphWrapper" style="margin-bottom: 15px;">
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 800px;margin-bottom: 15px;"></div>
<div style="display: none;"><input id="mySavedModel" name="mySavedModel"></div>
<button class="btn btn-default" type="submit"> Save <i class="fa fa-save"> </i> </button>
</div>
</form>
<div id="myInspectorDiv">
</div>
<div>
<button class="btn btn-default" onclick="zoomToFit()"> Zoom to fit <i class="fa fa-search"> </i> </button>
<button class="btn btn-default" onclick="zoomIn()"> Zoom in <i class="fa fa-search-plus"> </i> </button>
<button class="btn btn-default" onclick="zoomOut()"> Zoom out <i class="fa fa-search-minus"> </i> </button>
</div>
</div>
</body>
</html>
Now I want to make when the user used all options for links and none of them is available to use, I would like to disable availability to add a new link. Also if he uses all options for node texts, I would like to disable the possibility to add new nodes. But, if the user deletes some node and that option for node text is free again, I would like to enable him the possibility to add new nodes with that option but I don't have any idea how to make diagram disable his core part to add new nodes or links?
Also, it is somehow hard to make double click on link text to make dropdown with options appear. Is there any trick or option to make it easier to use?

I didn't realize you had asked this so many times.
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
initialContentAlignment: go.Spot.Center,
"undoManager.isEnabled": true,
"ModelChanged": function(e) { // just for demonstration purposes,
if (e.isTransactionFinished) { // show the model data in the page's TextArea
document.getElementById("mySavedModel").textContent = e.model.toJson();
}
if (e.change === go.ChangedEvent.Remove) {
if (e.modelChange === "linkDataArray") {
var linkdata = e.oldValue;
var oldstr = linkdata.text;
if (!oldstr) return;
var linkchoices = e.model.modelData.linkchoices;
var idx = linkchoices.indexOf(oldstr);
if (idx < 0) {
console.log("deletion adding link choice: " + oldstr);
var newlinkchoices = Array.prototype.slice.call(linkchoices);
newlinkchoices.push(oldstr);
e.model.set(e.model.modelData, "linkchoices", newlinkchoices);
}
} else if (e.modelChange === "nodeDataArray") {
var nodedata = e.oldValue;
var oldstr = nodedata.text;
if (!oldstr) return;
var nodechoices = e.model.modelData.nodechoices;
var idx = nodechoices.indexOf(oldstr);
if (idx < 0) {
console.log("deletion adding node choice: " + oldstr);
var newnodechoices = Array.prototype.slice.call(nodechoices);
newnodechoices.push(oldstr);
e.model.set(e.model.modelData, "nodechoices", newnodechoices);
}
}
}
}
});
myDiagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
{ fill: "white", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{
margin: 8,
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
if (oldstr !== newstr) {
console.log("changing from " + oldstr + " to " + newstr);
var model = tb.diagram.model;
var nodechoices = model.modelData.nodechoices;
if (!nodechoices) {
nodechoices = [];
model.set(model.modelData, "nodechoices", nodechoices);
}
var idx = nodechoices.indexOf(newstr);
if (idx >= 0) {
console.log("removing node choice " + idx + ": " + newstr);
model.removeArrayItem(nodechoices, idx);
}
if (oldstr) {
console.log(" adding node choice " + oldstr);
model.addArrayItem(nodechoices, oldstr);
}
}
}
},
new go.Binding("text").makeTwoWay(),
new go.Binding("choices", "nodechoices").ofModel())
);
myDiagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "OpenTriangle" }),
$(go.TextBlock,
{
background: "white",
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
if (oldstr !== newstr) {
console.log("changing from " + oldstr + " to " + newstr);
var model = tb.diagram.model;
var linkchoices = model.modelData.linkchoices;
if (!linkchoices) {
linkchoices = [];
model.set(model.modelData, "linkchoices", linkchoices);
}
var idx = linkchoices.indexOf(newstr);
if (idx >= 0) {
console.log("removing link choice " + idx + ": " + newstr);
model.removeArrayItem(linkchoices, idx);
}
if (oldstr) {
console.log(" adding link choice " + oldstr);
model.addArrayItem(linkchoices, oldstr);
}
}
}
},
new go.Binding("text").makeTwoWay(),
new go.Binding("choices", "linkchoices").ofModel())
);
myDiagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", color: "lightblue" },
{ key: 2, text: "Beta", color: "orange" },
{ key: 3, text: "Gamma", color: "lightgreen" },
{ key: 4, text: "Delta", color: "pink" }
],
[
{ from: 1, to: 2, text: "one" },
{ from: 1, to: 3, text: "two" },
{ from: 3, to: 4, text: "three" }
]);
myDiagram.model.set(myDiagram.model.modelData, "nodechoices", ["Epsilon"]);
myDiagram.model.set(myDiagram.model.modelData, "linkchoices", ["four"]);
}

Related

How do you apply Smart Routing on links with ports on JointJS?

I am trying to apply smart routing of links with the use of ports using JointJS. This documentation shows the one I am trying to achieve. The example on the docs though shows only the programmatic way of adding Link from point A to point B. How do you do this with the use of ports?
Here's my code: JSFiddle.
HTML:
<html>
<body>
<button id="btnAdd">Add Table</button>
<div id="dbLookupCanvas"></div>
</body>
</html>
JS
$(document).ready(function() {
$('#btnAdd').on('click', function() {
AddTable();
});
InitializeCanvas();
// Adding of two sample tables on first load
AddTable(50, 50);
AddTable(250, 50);
});
var graph;
var paper
var selectedElement;
var namespace;
function InitializeCanvas() {
let canvasContainer = $('#dbLookupCanvas').parent();
namespace = joint.shapes;
graph = new joint.dia.Graph({}, {
cellNamespace: namespace
});
paper = new joint.dia.Paper({
el: document.getElementById('dbLookupCanvas'),
model: graph,
width: canvasContainer.width(),
height: 500,
gridSize: 10,
drawGrid: true,
cellViewNamespace: namespace,
validateConnection: function(cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
return (magnetS !== magnetT);
},
snapLinks: {
radius: 20
}
});
//Dragging navigation on canvas
var dragStartPosition;
paper.on('blank:pointerdown',
function(event, x, y) {
dragStartPosition = {
x: x,
y: y
};
}
);
paper.on('cell:pointerup blank:pointerup', function(cellView, x, y) {
dragStartPosition = null;
});
$("#dbLookupCanvas")
.mousemove(function(event) {
if (dragStartPosition)
paper.translate(
event.offsetX - dragStartPosition.x,
event.offsetY - dragStartPosition.y);
});
// Remove links not connected to anything
paper.model.on('batch:stop', function() {
var links = paper.model.getLinks();
_.each(links, function(link) {
var source = link.get('source');
var target = link.get('target');
if (source.id === undefined || target.id === undefined) {
link.remove();
}
});
});
paper.on('cell:pointerdown', function(elementView) {
resetAll(this);
let isElement = elementView.model.isElement();
if (isElement) {
var currentElement = elementView.model;
currentElement.attr('body/stroke', 'orange');
selectedElement = elementView.model;
} else
selectedElement = null;
});
paper.on('blank:pointerdown', function(elementView) {
resetAll(this);
});
$('#dbLookupCanvas')
.attr('tabindex', 0)
.on('mouseover', function() {
this.focus();
})
.on('keydown', function(e) {
if (e.keyCode == 46)
if (selectedElement) selectedElement.remove();
});
}
function AddTable(xCoord = undefined, yCoord = undefined) {
// This is a sample database data here
let data = [
{columnName: "radomData1"},
{columnName: "radomData2"}
];
if (xCoord == undefined && yCoord == undefined)
{
xCoord = 50;
yCoord = 50;
}
const rect = new joint.shapes.standard.Rectangle({
position: {
x: xCoord,
y: yCoord
},
size: {
width: 150,
height: 200
},
ports: {
groups: {
'a': {},
'b': {}
}
}
});
$.each(data, (i, v) => {
const port = {
group: 'a',
args: {}, // Extra arguments for the port layout function, see `layout.Port` section
label: {
position: {
name: 'right',
args: {
y: 6
} // Extra arguments for the label layout function, see `layout.PortLabel` section
},
markup: [{
tagName: 'text',
selector: 'label'
}]
},
attrs: {
body: {
magnet: true,
width: 16,
height: 16,
x: -8,
y: -4,
stroke: 'red',
fill: 'gray'
},
label: {
text: v.columnName,
fill: 'black'
}
},
markup: [{
tagName: 'rect',
selector: 'body'
}]
};
rect.addPort(port);
});
rect.resize(150, data.length * 40);
graph.addCell(rect);
}
function resetAll(paper) {
paper.drawBackground({
color: 'white'
});
var elements = paper.model.getElements();
for (var i = 0, ii = elements.length; i < ii; i++) {
var currentElement = elements[i];
currentElement.attr('body/stroke', 'black');
}
var links = paper.model.getLinks();
for (var j = 0, jj = links.length; j < jj; j++) {
var currentLink = links[j];
currentLink.attr('line/stroke', 'black');
currentLink.label(0, {
attrs: {
body: {
stroke: 'black'
}
}
});
}
}
Any help would be appreciated. Thanks!
The default link created when you draw a link from a port is joint.dia.Link.
To change this you can use the defaultLink paper option, and configure the router you would like.
defaultLink documentation reference
const paper = new joint.dia.Paper({
el: document.getElementById('dbLookupCanvas'),
model: graph,
width: canvasContainer.width(),
height: 500,
gridSize: 10,
drawGrid: true,
cellViewNamespace: namespace,
validateConnection: function(cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
return (magnetS !== magnetT);
},
snapLinks: {
radius: 20
},
defaultLink: () => new joint.shapes.standard.Link({
router: { name: 'manhattan' },
connector: { name: 'rounded' },
})
});
You could also provide several default options in the paper.
defaultLink: () => new joint.shapes.standard.Link(),
defaultRouter: { name: 'manhattan' },
defaultConnector: { name: 'rounded' }

Issue in creating ports with GoJS

I am testing the GoJS library for Javascript.
I'm looking to create ports on the sides of a group.
I attach an executable extract from the program.
When I create a new object from the palette, right-click on the object, and select "Add left port", the debugging message shows that the variable "arr" is declared "undefined" at execution. (program lines 111 and 112).
Thank you for your help.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dynamic Ports</title>
<meta name="description" content="Nodes with varying lists of ports on each of four sides." />
<!-- Copyright 1998-2017 by Northwoods Software Corporation. -->
<meta charset="UTF-8">
<script src="../release/go-debug.js"></script>
<script src="../assets/js/goSamples.js"></script> <!-- this is only for the GoJS Samples framework -->
<!-- LL <script>goCode("diagramEvents", 600, 200)</script>-->
<script src="/Users/leonlevy/gojs/site/extensionsTS/ResizeMultipleScript.js"></script>
<span id="diagramEventsMsg" style="color: red"></span>
<span id="changeMethodsMsg" style="color: red"></span>
<span id="BackgroundDoubleClicked" style="color: red"></span>
<p>
<script id="code">
function init() {
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var $ = go.GraphObject.make; //for conciseness in defining node templates
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
allowDrop: true, // from Palette
// what to do when a drag-drop occurs in the Diagram's background
mouseDrop: function(e) { finishDrop(e, null); },
// layout: null, // Diagram has no layout
/* $(go.GridLayout,
{ wrappingWidth: Infinity, alignment: go.GridLayout.Position, cellSize: new go.Size(1, 1) }),*/
initialContentAlignment: go.Spot.Center,
"commandHandler.archetypeGroupData": { isGroup: true, category: "OfGroups" },
"undoManager.isEnabled": true
});
// LL
function showMessage(s) {
document.getElementById("diagramEventsMsg").textContent = s;
}
// when the document is modified, add a "*" to the title and enable the "Save" button
myDiagram.addDiagramListener("Modified", function(e) {
var button = document.getElementById("SaveButton");
if (button) button.disabled = !myDiagram.isModified;
var idx = document.title.indexOf("*");
if (myDiagram.isModified) {
if (idx < 0) document.title += "*";
} else {
if (idx >= 0) document.title = document.title.substr(0, idx);
}
});
myDiagram.addDiagramListener("ObjectSingleClicked",
function(e) {
var part = e.subject.part;
var heightPart = part.actualBounds.height;
var widthPart = part.actualBounds.width;
var xPart = part.actualBounds.position.x;
var yPart = part.actualBounds.position.y;
var xMouseClic = myDiagram.lastInput.documentPoint.x;
var yMouseClic = myDiagram.lastInput.documentPoint.y;
var xPartMiddle = xPart+ widthPart/2;
var side = xMouseClic<= xPartMiddle ? "Left" : "Right";
if (!(part instanceof go.Link)) {console.log("hello leon");}
});
myDiagram.addDiagramListener("BackgroundSingleClicked",
function(e) {
showMessage("Clicked on background " + myDiagram.lastInput.documentPoint);
//console.log("hello leon");}
});
myDiagram.addDiagramListener("BackgroundDoubleClicked", // LL KO
function(e) {
showMessage("Double-clicked at " );//+ e.myDiagram.lastInput.documentPoint);
//showMessage("Clicked on " + myDiagram.lastInput.documentPoint);
});
myDiagram.addDiagramListener("ClipboardPasted",
function(e) { showMessage("Pasted " + e.myDiagram.selection.count + " parts"); });
// LL end
// To simplify this code we define a function for creating a context menu button:
function makeButton(text, action, visiblePredicate) {
return $("ContextMenuButton",
$(go.TextBlock, text),
{ click: action },
//{showMessage( "button: " + myDiagram.lastInput.button);
// don't bother with binding GraphObject.visible if there's no predicate
visiblePredicate ? new go.Binding("visible", "", function(o, e) { return o.myDiagram ? visiblePredicate(o, e) : false; }).ofObject() : {});
//}
}
var nodeMenu = // context menu for each Node
$(go.Adornment, "Horizontal",
makeButton("Add left port",
function (e, obj) { addPort("left"); }),
makeButton("Add right port",
function (e, obj) { addPort("right"); }),
); //Leon end nodeM
// Add a port to the specified side of the selected nodes.
function addPort(side) {
myDiagram.startTransaction("addPort");
myDiagram.selection.each(function(node) {
// skip any selected Links
if (!(node instanceof go.Node)) return;
// compute the next available index number for the side
var i = 0;
while (node.findPort(side + i.toString()) !== node) i++;
// now this new port name is unique within the whole Node because of the side prefix
var name = side + i.toString();
// get the Array of port data to be modified
var arr = node.data[side + "Array"];
showMessage ("node: " + node + "; name: " + name + ";arr: " + arr + ";node.data: " + node.data);
if (arr) {
// create a new port data object
var newportdata = {
portId: name,
portColor: go.Brush.randomColor()
// if you add port data properties here, you should copy them in copyPortData above
};
// and add it to the Array of port data
myDiagram.model.insertArrayItem(arr, -1, newportdata);
}
});
myDiagram.commitTransaction("addPort");
}
var portSize = new go.Size(8, 8);
// this function is used to highlight a Group that the selection may be dropped into
function highlightGroup(e, grp, show) {
if (!grp) return; showMessage("ok")
e.handled = true;
if (show) {
// cannot depend on the grp.diagram.selection in the case of external drag-and-drops;
// instead depend on the DraggingTool.draggedParts or .copiedParts
var tool = grp.diagram.toolManager.draggingTool;
var map = tool.draggedParts || tool.copiedParts; // this is a Map
// now we can check to see if the Group will accept membership of the dragged Parts
if (grp.canAddMembers(map.toKeySet())) {
grp.isHighlighted = true;
return;
}
}
grp.isHighlighted = false;
}
// Upon a drop onto a Group, we try to add the selection as members of the Group.
// Upon a drop onto the background, or onto a top-level Node, make selection top-level.
// If this is OK, we're done; otherwise we cancel the operation to rollback everything.
function finishDrop(e, grp) {
var ok = (grp !== null
? grp.addMembers(grp.diagram.selection, true)
: e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true));
if (!ok) e.diagram.currentTool.doCancel();
}
var portMenu = // context menu for each port
$(go.Adornment, "Vertical",
makeButton("Remove port",
// in the click event handler, the obj.part is the Adornment;
// its adornedObject is the port
function (e, obj) { removePort(obj.part.adornedObject); }),
makeButton("Change color",
function (e, obj) { changeColor(obj.part.adornedObject); }),
makeButton("Remove side ports",
function (e, obj) { removeAll(obj.part.adornedObject); })
);
// support double-clicking in the background to add a copy of this data as a node
myDiagram.toolManager.clickCreatingTool.archetypeNodeData = {
name: "Unit",
leftArray: [],
rightArray: [],
topArray: [],
bottomArray: []
};
myDiagram.groupTemplateMap.add("OfGroups",
$(go.Group, "Table",
{ locationObjectName: "BODY",
locationSpot: go.Spot.Center,
selectionObjectName: "BODY",
contextMenu: nodeMenu
},
{
background: "transparent",
resizable: true,
// highlight when dragging into the Group
mouseDragEnter: function(e, grp, prev) { highlightGroup(e, grp, true); },
mouseDragLeave: function(e, grp, next) { highlightGroup(e, grp, false); },
//computesBoundsAfterDrag: true,
mouseDrop: finishDrop,
resizable: true, resizeObjectName: "SHAPE"
//, minSize: new go.Size(36, 46)
},
new go.Binding("background", "isHighlighted", function(h) { return h ? "rgba(0,0,255,0.)" : "transparent"; }).ofObject(),
// save the modified size in the model node data
new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, "Rectangle",
{ fill: null, stroke: "rgba(0,50,255,1)", strokeWidth: 3, name: "SHAPE"}),
$(go.Panel, "Vertical", {alignment: go.Spot.Top},// title above Placeholder
$(go.Panel, "Horizontal", // button next to TextBlock
{ stretch: go.GraphObject.Horizontal, background: "rgba(0,0,255,0.1)" },
$("SubGraphExpanderButton",
{ alignment: go.Spot.Right, margin: 5 }),
$(go.TextBlock,
{ //text: "verticalAlignment: Top", verticalAlignment: go.Spot.Top,
alignment: go.Spot.Left,
editable: true,
margin: 5,
font: "bold 18px sans-serif",
opacity: 0.75,
stroke: "#404040"
},
new go.Binding("text", "text").makeTwoWay())
), // end Horizontal Panel
$(go.Placeholder,
{ padding: 5, alignment: go.Spot.TopLeft })
), // end Vertical Panel
// the Panel holding the left port elements, which are themselves Panels,
// created for each item in the itemArray, bound to data.leftArray
$(go.Panel, "Vertical",
new go.Binding("itemArray", "leftArray"),
{ row: 1, column: 0,
itemTemplate:
$(go.Panel,
{ _side: "left", // internal property to make it easier to tell which side it's on
fromSpot: go.Spot.Left, toSpot: go.Spot.Left,
fromLinkable: true, toLinkable: true, cursor: "pointer",
contextMenu: portMenu },
new go.Binding("portId", "portId"),
$(go.Shape, "Rectangle",
{ stroke: null, strokeWidth: 0,
desiredSize: portSize,
margin: new go.Margin(1,0) },
new go.Binding("fill", "portColor"))
) // end itemTemplate
}
), // end Vertical Panel
// the Panel holding the right port elements, which are themselves Panels,
// created for each item in the itemArray, bound to data.rightArray
$(go.Panel, "Vertical",
new go.Binding("itemArray", "rightArray"),
{ row: 1, column: 2,
itemTemplate:
$(go.Panel,
{ _side: "right",
fromSpot: go.Spot.Right, toSpot: go.Spot.Right,
fromLinkable: true, toLinkable: true, cursor: "pointer",
contextMenu: portMenu },
new go.Binding("portId", "portId"),
$(go.Shape, "Rectangle",
{ stroke: null, strokeWidth: 0,
desiredSize: portSize,
margin: new go.Margin(1, 0) },
new go.Binding("fill", "portColor"))
) // end itemTemplate
}
), // end Vertical Panel
)
); // end groupTemplateMap.add("OfGroups"
// initialize the Palette and its contents
myPalette =
$(go.Palette, "myPaletteDiv",
{
groupTemplateMap: myDiagram.groupTemplateMap,
layout: $(go.GridLayout, { wrappingColumn: 1, alignment: go.GridLayout.Position })
});
myPalette.nodeTemplate =
$(go.Node, "Horizontal",
$(go.Shape,
{ width: 10, height: 10, fill: "white" },
new go.Binding("fill", "color")),
$(go.TextBlock,
new go.Binding("text", "color"))
);
myPalette.model = new go.GraphLinksModel([
{ key: 101, text: 'UNIT', isGroup: true, isSubProcess: true,
category: "OfGroups", isAdHoc: true, loc: '0 0' },
]);
}
</script>
</head>
<body onload="init()">
<!-- LL -->
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">
<div id="sample">
<div style="width: 100%; display: flex; justify-content: space-between">
<div id="myPaletteDiv" style="width: 100px; margin-right: 2px; background-color: whitesmoke; border: solid 1px black"></div>
<div id="myDiagramDiv" style="flex-grow: 1; height: 480px; border: solid 1px black"></div>
</div>
<p>
See the Ports Intro page for an explanation of GoJS ports.
</p>
<textarea id="mySavedModel" style="width:100%;height:250px">
</textarea>
</div>
</div>
</body>
</html>
You need to make sure that the node is bound to the correct data. In particular, the data object should have a leftArray property that is an Array holding port descriptor objects. (And it seems to expect a rightArray property too.) You can determine the expected properties for each Node by looking at the source property names of each of the Bindings that are in the node template.
You can determine the expected properties for each of the port descriptors in either data Array by looking at the properties that the Panel.itemTemplate uses in its data Bindings.

gojs dinamical dropdown choices for node and link text

I want to make custom choices dropdown for users to edit node and link text.
So far, I used this code to make user select nodes and links text selectable in dropdown list:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>State Chart</title>
<meta name="description" content="A finite state machine chart with editable and interactive features." />
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="{{url_for('static', filename='go.js')}}"></script>
<!-- custom text editors -->
<script src="{{url_for('static', filename='TextEditorSelectBox.js')}}"></script>
<script src="{{url_for('static', filename='TextEditorRadioButtons.js')}}"></script>
<script src="{{url_for('static', filename='TextEditorSelectBox.js')}}"></script>
<script src="{{url_for('static', filename='DataInspector.js')}}"></script>
<link href="https://gojs.net/latest/extensions/DataInspector.css" rel="stylesheet">
<link href="{{url_for('static', filename='DataInspector.css')}}" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<style>
body {
overflow-x: hidden;
}
#wrapper {
padding-left: 0;
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
}
#wrapper.toggled {
padding-left: 250px;
}
#sidebar-wrapper {
z-index: 1000;
position: fixed;
left: 250px;
width: 0;
height: 100%;
margin-left: -250px;
overflow-y: auto;
background: #000;
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
}
#wrapper.toggled #sidebar-wrapper {
width: 250px;
}
#page-content-wrapper {
width: 100%;
position: absolute;
padding: 15px;
}
#wrapper.toggled #page-content-wrapper {
position: absolute;
margin-right: -250px;
}
/* Sidebar Styles */
.sidebar-nav {
position: absolute;
top: 0;
width: 250px;
margin: 0;
padding: 0;
list-style: none;
}
.sidebar-nav li {
text-indent: 20px;
line-height: 40px;
}
.sidebar-nav li a {
display: block;
text-decoration: none;
color: #999999;
}
.sidebar-nav li a:hover {
text-decoration: none;
color: #fff;
background: rgba(255, 255, 255, 0.2);
}
.sidebar-nav li a:active, .sidebar-nav li a:focus {
text-decoration: none;
}
.sidebar-nav>.sidebar-brand {
height: 65px;
font-size: 18px;
line-height: 60px;
}
.sidebar-nav>.sidebar-brand a {
color: #999999;
}
.sidebar-nav>.sidebar-brand a:hover {
color: #fff;
background: none;
}
#media(min-width:768px) {
#wrapper {
padding-left: 0;
}
#wrapper.toggled {
padding-left: 250px;
}
#sidebar-wrapper {
width: 0;
}
#wrapper.toggled #sidebar-wrapper {
width: 250px;
}
#page-content-wrapper {
padding: 20px;
position: relative;
}
#wrapper.toggled #page-content-wrapper {
position: relative;
margin-right: 0;
}
}
</style>
<script id="code">
var nodeChoices = ['choice 1', 'choice 2', 'choice 3', 'choice 4', 'choice 5'];
const originalNodeChoices = ['choice 1', 'choice 2', 'choice 3', 'choice 4', 'choice 5'];
var linkChoices = ['link choice 1', 'link choice 2', 'link choice 3', 'link choice 4', 'link choice 5'];
const originalLinkChoices = ['link choice 1', 'link choice 2', 'link choice 3', 'link choice 4', 'link choice 5'];
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv", // must name or refer to the DIV HTML element
{
// start everything in the middle of the viewport
initialContentAlignment: go.Spot.Center,
// have mouse wheel events zoom in and out instead of scroll up and down
"toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom,
// support double-click in background creating a new node
"clickCreatingTool.archetypeNodeData": { text: "new node" },
// enable undo & redo
"textEditingTool.defaultTextEditor": window.TextEditorSelectBox,
"undoManager.isEnabled": true,
"layout": new go.ForceDirectedLayout(),
"ModelChanged": function(e) {
//console.log("Diagram model changed!");
if (e.change === go.ChangedEvent.Remove && e.modelChange === "linkDataArray") {
console.log("eee its linkDataArray");
console.log(e);
var linkdata = e.oldValue;
console.log("linkdata");
console.log(linkdata);
var oldstr = linkdata.text;
console.log("oldstr");
console.log(oldstr);
if (!oldstr) return;
var customModelData = e.model.modelData.choices;
var choices = e.model.modelData.choices.linkChoices;
console.log("choices");
console.log(choices);
var idx = choices.indexOf(oldstr);
if (idx < 0) {
console.log("adding choice: " + oldstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.push(oldstr);
e.model.set(e.model.modelData, "choices", newchoices);
}
}else if(e.change === go.ChangedEvent.Remove && e.modelChange === "nodeDataArray"){
console.log("eee its nodeDataArray");
}
/*
else{
console.log("elseeeeeee111!");
console.log("e.change");
console.log(e.change);
console.log("e.modelChange");
console.log(e.modelChange);
}*/
}
});
//myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
console.log("myDiagram.model.modelData");
console.log(myDiagram.model.modelData);
console.log("myDiagram.model.modelData.choices");
console.log(myDiagram.model.modelData.choices);
// when the document is modified, add a "*" to the title and enable the "Save" button
myDiagram.addDiagramListener("Modified", function(e) {
var button = document.getElementById("SaveButton");
if (button) button.disabled = !myDiagram.isModified;
var idx = document.title.indexOf("*");
if (myDiagram.isModified) {
if (idx < 0) document.title += "*";
}
else {
if (idx >= 0) document.title = document.title.substr(0, idx);
}
});
myDiagram.addDiagramListener("textEdited", function(e) {
console.log("Text is edited");
console.log(e);
//CHECK IF LINK,
//IF YES REMOVE THAT OPTION FROM LIST
});
myDiagram.addDiagramListener("SelectionDeleting", function(e) {
console.log("inside SelectionDeleting");
console.log(e);
//CHECK IF LINK,
//IF YES PUT THAT OPTION BACK IN OPTION LIST
});
// define the Node template
myDiagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
// define the node's outer shape, which will surround the TextBlock
$(go.Shape, "RoundedRectangle",
{
parameter1: 20, // the corner has a large radius
fill: $(go.Brush, "Linear", { 0: "rgb(254, 201, 0)", 1: "rgb(254, 162, 0)" }),
stroke: null,
portId: "", // this Shape is the Node's port, not the whole Node
fromLinkable: true, fromLinkableDuplicates: true,
toLinkable: true, toLinkableDuplicates: true,
cursor: "pointer"
}),
$(go.TextBlock,
{
font: "bold 11pt helvetica, bold arial, sans-serif",
editable: true, // editing the text automatically updates the model data
//textEditor: window.TextEditorRadioButtons, // defined in textEditorRadioButtons.js
// this specific TextBlock has its own choices:
textEditor: window.TextEditorRadioButtons,
textEdited: function(tb, oldstr, newstr) {
console.log("textEdited nodeeeeeeeeeeeeeeeeeeeeeeeee!!!");
var choices = tb.diagram.model.modelData.choices.nodeChoices;
console.log("choices");
console.log(choices);
console.log("newstr");
console.log(newstr);
console.log("oldstr");
console.log(oldstr);
var idx = choices.indexOf(newstr);
if (idx >= 0 && oldstr !== newstr ) {
console.log(choices);
console.log("choices");
console.log(choices);
console.log("removing choice " + idx + ": " + newstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.splice(idx, 1);
console.log("after remove");
console.log(newchoices);
var currentChoicesFromModel = tb.diagram.model.modelData.choices;
console.log("currentChoicesFromModel");
console.log(currentChoicesFromModel);
currentChoicesFromModel.nodeChoices = newchoices;
console.log("currentChoicesFromModel after update: ");
console.log(currentChoicesFromModel);
tb.diagram.model.set(tb.diagram.model.modelData, "choices", currentChoicesFromModel);
//tb.editable = false; // don't allow choice again
}else{
console.log("elseeeeee");
}
},
//choices: JSON.parse('{{ choices | tojson | safe}}')
choices: nodeChoices
},
new go.Binding("text").makeTwoWay())
);
myDiagram.nodeTemplate.selectionAdornmentTemplate =
$(go.Adornment, "Spot",
$(go.Panel, "Auto",
$(go.Shape, { stroke: "dodgerblue", strokeWidth: 2, fill: null }),
$(go.Placeholder)
),
$(go.Panel, "Horizontal",
{ alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom },
$("Button",
{ click: editText }, // defined below, to support editing the text of the node
$(go.TextBlock, "t",
{ font: "bold 10pt sans-serif", desiredSize: new go.Size(15, 15), textAlign: "center" })
),
$("Button",
{ // drawLink is defined below, to support interactively drawing new links
click: drawLink, // click on Button and then click on target node
actionMove: drawLink // drag from Button to the target node
},
$(go.Shape,
{ geometryString: "M0 0 L8 0 8 12 14 12 M12 10 L14 12 12 14" })
),
$("Button",
{
actionMove: dragNewNode, // defined below, to support dragging from the button
_dragData: { text: "?????", color: "lightgray" }, // node data to copy
click: clickNewNode // defined below, to support a click on the button
},
$(go.Shape,
{ geometryString: "M0 0 L3 0 3 10 6 10 x F1 M6 6 L14 6 14 14 6 14z", fill: "gray" })
)
)
);
//myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
console.log("myDiagram.model.modelData");
console.log(myDiagram.model.modelData);
console.log(myDiagram.model.modelData.choices);
function editText(e, button) {
//console.log(e);
var node = button.part.adornedPart;
console.log("node");
//console.log(node);
e.diagram.commandHandler.editTextBlock(node.findObject("TEXTBLOCK"));
//$("#nodeText").val(node.findObject("TEXTBLOCK"));
}
function drawLink(e, button) {
var node = button.part.adornedPart;
var tool = e.diagram.toolManager.linkingTool;
tool.startObject = node.port;
e.diagram.currentTool = tool;
tool.doActivate();
}
// used by both clickNewNode and dragNewNode to create a node and a link
// from a given node to the new node
function createNodeAndLink(data, fromnode) {
var diagram = fromnode.diagram;
var model = diagram.model;
var nodedata = model.copyNodeData(data);
model.addNodeData(nodedata);
var newnode = diagram.findNodeForData(nodedata);
var linkdata = model.copyLinkData({});
model.setFromKeyForLinkData(linkdata, model.getKeyForNodeData(fromnode.data));
model.setToKeyForLinkData(linkdata, model.getKeyForNodeData(newnode.data));
model.addLinkData(linkdata);
diagram.select(newnode);
return newnode;
}
// the Button.click event handler, called when the user clicks the "N" button
function clickNewNode(e, button) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("Create Node and Link");
var fromnode = button.part.adornedPart;
var newnode = createNodeAndLink(button._dragData, fromnode);
newnode.location = new go.Point(fromnode.location.x + 200, fromnode.location.y);
e.diagram.commitTransaction("Create Node and Link");
}
// the Button.actionMove event handler, called when the user drags within the "N" button
function dragNewNode(e, button) {
var tool = e.diagram.toolManager.draggingTool;
if (tool.isBeyondDragSize()) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("button drag"); // see doDeactivate, below
var newnode = createNodeAndLink(data, button.part.adornedPart);
newnode.location = e.diagram.lastInput.documentPoint;
// don't commitTransaction here, but in tool.doDeactivate, after drag operation finished
// set tool.currentPart to a selected movable Part and then activate the DraggingTool
tool.currentPart = newnode;
e.diagram.currentTool = tool;
tool.doActivate();
}
}
// using dragNewNode also requires modifying the standard DraggingTool so that it
// only calls commitTransaction when dragNewNode started a "button drag" transaction;
// do this by overriding DraggingTool.doDeactivate:
var tool = myDiagram.toolManager.draggingTool;
tool.doDeactivate = function() {
// commit "button drag" transaction, if it is ongoing; see dragNewNode, above
if (tool.diagram.undoManager.nestedTransactionNames.elt(0) === "button drag") {
tool.diagram.commitTransaction();
}
go.DraggingTool.prototype.doDeactivate.call(tool); // call the base method
};
// replace the default Link template in the linkTemplateMap
myDiagram.linkTemplate =
$(go.Link, // the whole link panel
{
curve: go.Link.Bezier,
adjusting: go.Link.Stretch,
reshapable: true,
relinkableFrom: true,
relinkableTo: true,
toShortLength: 3
},
new go.Binding("points").makeTwoWay(),
new go.Binding("curviness"),
$(go.Shape, // the link shape
{ strokeWidth: 1.5 }),
$(go.Shape, // the arrowhead
{ toArrow: "standard", stroke: null }),
$(go.Panel, "Auto",
$(go.Shape, // the label background, which becomes transparent around the edges
{
fill: $(go.Brush, "Radial", { 0: "rgb(240, 240, 240)", 0.3: "rgb(240, 240, 240)", 1: "rgba(240, 240, 240, 0)" }),
stroke: null
}),
$(go.TextBlock,
{
background: "white",
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
var choices = tb.diagram.model.modelData.choices.linkChoices;
console.log("choices");
console.log(choices);
console.log("newstr");
console.log(newstr);
console.log("oldstr");
console.log(oldstr);
var idx = choices.indexOf(newstr);
if (idx >= 0 && oldstr !== newstr ) {
console.log(choices);
console.log("choices");
console.log(choices);
console.log("removing choice " + idx + ": " + newstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.splice(idx, 1);
tb.diagram.model.set(tb.diagram.model.modelData.choices.linkChoices, "choices", newchoices);
//tb.editable = false; // don't allow choice again
}else{
console.log("elseeeeee");
}
}
},
// editing the text automatically updates the model data
//new go.Binding("text"),
new go.Binding("text").makeTwoWay(),
new go.Binding("choices").ofModel())
)
);
var inspector = new Inspector('myInspectorDiv', myDiagram,
{
// uncomment this line to only inspect the named properties below instead of all properties on each object:
// includesOwnProperties: false,
properties: {
"text": { },
// an example of specifying the type
"password": { show: Inspector.showIfPresent, type: 'password' },
// key would be automatically added for nodes, but we want to declare it read-only also:
"key": { readOnly: true, show: Inspector.showIfPresent },
// color would be automatically added for nodes, but we want to declare it a color also:
"color": { show: Inspector.showIfPresent, type: 'color' },
// Comments and LinkComments are not in any node or link data (yet), so we add them here:
"Comments": { show: Inspector.showIfNode },
"flag": { show: Inspector.showIfNode, type: 'checkbox' },
"LinkComments": { show: Inspector.showIfLink },
"isGroup": { readOnly: true, show: Inspector.showIfPresent }
}
});
// read in the JSON data from flask
loadGraphData();
}
function loadGraphData() {
var graphDataString = JSON.parse('{{ diagramData | tojson | safe}}');
//console.log("graphDataString");
//console.log(graphDataString);
var allChoices = JSON.parse('{{ allChoices | tojson | safe}}');
console.log("allChoices");
console.log(allChoices);
myDiagram.model = go.Model.fromJson(graphDataString);
//myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
var customModelData = {
"linkChoices": linkChoices, "nodeChoices": nodeChoices
};
myDiagram.model.set(myDiagram.model.modelData, "choices", allChoices);
//myDiagram.model.set(myDiagram.model.modelData, "choices", JSON.parse('{{ link_choices | tojson | safe}}'));
//myDiagram.model.set(myDiagram.model.modelData, "choices", customModelData);
console.log("whole model modelData");
console.log(myDiagram.model.modelData);
}
function saveGraphData(form, event) {
console.log("inside saveGraphData");
event.preventDefault();
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
form.submit();
}
function zoomToFit(){
console.log("inside zoomToFit");
myDiagram.zoomToRect(myDiagram.documentBounds);
}
function zoomIn(){
console.log("inside zoomIn");
myDiagram.commandHandler.increaseZoom();
}
function zoomOut(){
console.log("inside zoomOut");
myDiagram.commandHandler.decreaseZoom();
}
</script>
</head>
<body onload="init()">
<div id="wrapper">
<!-- Sidebar -->
<div id="sidebar-wrapper">
<ul class="sidebar-nav">
<li class="sidebar-brand"> CUSTOM CHOICES MODE </li>
<li> Free editing mode </li>
<li> Fill mode </li>
<li> Custom choices mode </li>
<li> Locked choices mode </li>
</ul>
</div>
<!-- /#sidebar-wrapper -->
<!-- Page Content -->
<div id="page-content-wrapper">
<div class="container-fluid">
<div id=formWrapper style="padding: 30px;">
Toggle Menu
<form method="POST" action="http://localhost:5000/updateResultFile" name="updateResultFileForm"
id="updateResultFileForm"
onsubmit="saveGraphData(this, event);">
<div id="graphWrapper" style="margin-bottom: 15px;">
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 800px;margin-bottom: 15px;"></div>
<div style="display: none;"><input id="mySavedModel" name="mySavedModel"></div>
<button class="btn btn-default" type="submit"> Save <i class="fa fa-save"> </i> </button>
</div>
</form>
<div id="myInspectorDiv">
</div>
<div>
<button class="btn btn-default" onclick="zoomToFit()"> Zoom to fit <i class="fa fa-search"> </i> </button>
<button class="btn btn-default" onclick="zoomIn()"> Zoom in <i class="fa fa-search-plus"> </i> </button>
<button class="btn btn-default" onclick="zoomOut()"> Zoom out <i class="fa fa-search-minus"> </i> </button>
</div>
</div>
</div>
</div>
</div>
<script src="{{url_for('static', filename='jquery.min.js')}}"></script>
<script src="{{url_for('static', filename='bootstrap.bundle.min.js')}}"></script>
<!-- Menu Toggle Script -->
<script>
var menuOpen = false;
$("#menu-toggle").click(function(e) {
e.preventDefault();
$("#wrapper").toggleClass("toggled");
if(menuOpen){
menuOpen = false;
$("#myDiagramDiv").css({border: "solid 1px black"});
}else{
menuOpen = true;
$("#myDiagramDiv").css({border: ""});
}
});
</script>
</body>
</html>
So basicly I have made custom choices for nodes and links. For example, for node I have these choices:
node choice 1
node choice 2
node choice 3
and for links I have these choices:
link choice 1
link choice 2
link choice 3
What I want to accomplish is that when I select "node choice 1" from dropdown list on one node, that that option "node choice 1" can not be selected again for another node. But if user deletes node, or sets empty selection on node text, "node choice 1" must be available again.
Same thing goes for links.
I think my code works fine for links, but I have problems with detecting node text changes and updating dataModel content.
I seem to have missed this before. Here's some code that answers your question by implementing it:
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
initialContentAlignment: go.Spot.Center,
"undoManager.isEnabled": true,
"ModelChanged": function(e) { // just for demonstration purposes,
if (e.isTransactionFinished) { // show the model data in the page's TextArea
document.getElementById("mySavedModel").textContent = e.model.toJson();
}
if (e.change === go.ChangedEvent.Remove) {
if (e.modelChange === "linkDataArray") {
var linkdata = e.oldValue;
var oldstr = linkdata.text;
if (!oldstr) return;
var linkchoices = e.model.modelData.linkchoices;
var idx = linkchoices.indexOf(oldstr);
if (idx < 0) {
console.log("deletion adding link choice: " + oldstr);
var newlinkchoices = Array.prototype.slice.call(linkchoices);
newlinkchoices.push(oldstr);
e.model.set(e.model.modelData, "linkchoices", newlinkchoices);
}
} else if (e.modelChange === "nodeDataArray") {
var nodedata = e.oldValue;
var oldstr = nodedata.text;
if (!oldstr) return;
var nodechoices = e.model.modelData.nodechoices;
var idx = nodechoices.indexOf(oldstr);
if (idx < 0) {
console.log("deletion adding node choice: " + oldstr);
var newnodechoices = Array.prototype.slice.call(nodechoices);
newnodechoices.push(oldstr);
e.model.set(e.model.modelData, "nodechoices", newnodechoices);
}
}
}
}
});
myDiagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape,
{ fill: "white", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer" },
new go.Binding("fill", "color")),
$(go.TextBlock,
{
margin: 8,
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
if (oldstr !== newstr) {
console.log("changing from " + oldstr + " to " + newstr);
var model = tb.diagram.model;
var nodechoices = model.modelData.nodechoices;
if (!nodechoices) {
nodechoices = [];
model.set(model.modelData, "nodechoices", nodechoices);
}
var idx = nodechoices.indexOf(newstr);
if (idx >= 0) {
console.log("removing node choice " + idx + ": " + newstr);
model.removeArrayItem(nodechoices, idx);
}
if (oldstr) {
console.log(" adding node choice " + oldstr);
model.addArrayItem(nodechoices, oldstr);
}
}
}
},
new go.Binding("text").makeTwoWay(),
new go.Binding("choices", "nodechoices").ofModel())
);
myDiagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "OpenTriangle" }),
$(go.TextBlock,
{
background: "white",
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
if (oldstr !== newstr) {
console.log("changing from " + oldstr + " to " + newstr);
var model = tb.diagram.model;
var linkchoices = model.modelData.linkchoices;
if (!linkchoices) {
linkchoices = [];
model.set(model.modelData, "linkchoices", linkchoices);
}
var idx = linkchoices.indexOf(newstr);
if (idx >= 0) {
console.log("removing link choice " + idx + ": " + newstr);
model.removeArrayItem(linkchoices, idx);
}
if (oldstr) {
console.log(" adding link choice " + oldstr);
model.addArrayItem(linkchoices, oldstr);
}
}
}
},
new go.Binding("text").makeTwoWay(),
new go.Binding("choices", "linkchoices").ofModel())
);
myDiagram.model = new go.GraphLinksModel(
[
{ key: 1, text: "Alpha", color: "lightblue" },
{ key: 2, text: "Beta", color: "orange" },
{ key: 3, text: "Gamma", color: "lightgreen" },
{ key: 4, text: "Delta", color: "pink" }
],
[
{ from: 1, to: 2, text: "one" },
{ from: 1, to: 3, text: "two" },
{ from: 3, to: 4, text: "three" }
]);
myDiagram.model.set(myDiagram.model.modelData, "nodechoices", ["Epsilon"]);
myDiagram.model.set(myDiagram.model.modelData, "linkchoices", ["four"]);
}

How can I only display 3 columns in my 13-column slick grid?

I have slick grid in 10 columns .but i need to display only 3 columns only.how is it possible pls any one help me urgent.here my slick grid here slick grid code. This code display all columns default but i want to display only 3 columns.
CSS
.slick-row.selected .cell-selection {
background-color: transparent; /* show default selected row background */
}
HTML
<div style="position:relative">
<div style="width:600px;">
<div class="grid-header" style="width:100%">
<label>SlickGrid</label>
<span style="float:right" class="ui-icon ui-icon-search" title="Toggle search panel"
onclick="toggleFilterRow()"></span>
</div>
<div id="myGrid" style="width:100%;height:500px;"></div>
<div id="pager" style="width:100%;height:20px;"></div>
</div>
<div class="options-panel">
<b>Search:</b>
<hr/>
<div style="padding:6px;">
<label style="width:200px;float:left">Show tasks with % at least: </label>
<div style="padding:2px;">
<div style="width:100px;display:inline-block;" id="pcSlider"></div>
</div>
<br/>
<label style="width:200px;float:left">And title including:</label>
<input type=text id="txtSearch" style="width:100px;">
<br/><br/>
<button id="btnSelectRows">Select first 10 rows</button>
<br/>
<h2>Demonstrates:</h2>
<ul>
<li>a filtered Model (DataView) as a data source instead of a simple array</li>
<li>grid reacting to model events (onRowCountChanged, onRowsChanged)</li>
<li><b>FAST</b> DataView recalculation and <b>real-time</b> grid updating in response to data changes.<br/>The grid holds <b>50'000</b> rows, yet you are able to sort, filter, scroll, navigate and edit as if it had 50 rows.</li>
<li>adding new rows, bidirectional sorting</li>
<li>column options: cannotTriggerInsert</li>
<li>events: onCellChange, onAddNewRow, onKeyDown, onSelectedRowsChanged, onSort</li>
<li><font color=red>NOTE:</font> all filters are immediately applied to new/edited rows</li>
<li>Handling row selection against model changes.</li>
<li>Paging.</li>
<li>inline filter panel</li>
</ul>
<h2>View Source:</h2>
<ul>
<li><A href="https://github.com/mleibman/SlickGrid/blob/gh-pages/examples/example4-model.html"
target="_sourcewindow"> View the source for this example on
Github</a></li>
</ul>
</div>
</div>
</div>
<div id="inlineFilterPanel" style="display:none;background:#dddddd;padding:3px;color:black;">
Show tasks with title including <input type="text" id="txtSearch2"> and % at least
<div style="width:100px;display:inline-block;" id="pcSlider2"></div>
</div>
JavaScript
<script src="slick.grid/lib/firebugx.js"></script>
<script> var dataView; var grid; var data = []; var columns = [
{id: "sel", name: "#", field: "num", behavior: "select", cssClass:
"cell-selection", width: 40, cannotTriggerInsert: true, resizable:
false, selectable: false }, {id: "title", name: "Title", field:
"title", width: 120, minWidth: 120, cssClass: "cell-title", editor:
Slick.Editors.Text, validator: requiredFieldValidator, sortable:
true}, {id: "duration", name: "Duration", field: "duration",
editor: Slick.Editors.Text, sortable: true}, {id: "%",
defaultSortAsc: false, name: "% Complete", field: "percentComplete",
width: 80, resizable: false, formatter:
Slick.Formatters.PercentCompleteBar, editor:
Slick.Editors.PercentComplete, sortable: true}, {id: "start", name:
"Start", field: "start", minWidth: 60, editor: Slick.Editors.Date,
sortable: true}, {id: "finish", name: "Finish", field: "finish",
minWidth: 60, editor: Slick.Editors.Date, sortable: true}, {id:
"effort-driven", name: "Effort Driven", width: 80, minWidth: 20,
maxWidth: 80, cssClass: "cell-effort-driven", field: "effortDriven",
formatter: Slick.Formatters.Checkmark, editor:
Slick.Editors.Checkbox, cannotTriggerInsert: true, sortable: true} ];
var options = { editable: true, enableAddRow: true,
enableCellNavigation: true, asyncEditorLoading: true,
forceFitColumns: false, topPanelHeight: 25 };
var sortcol = "title"; var sortdir = 1; var percentCompleteThreshold
= 0; var searchString = "";
function requiredFieldValidator(value) { if (value == null || value
== undefined || !value.length) {
return {valid: false, msg: "This is a required field"}; } else {
return {valid: true, msg: null}; } }
function myFilter(item, args) { if (item["percentComplete"] <
args.percentCompleteThreshold) {
return false; }
if (args.searchString != "" &&
item["title"].indexOf(args.searchString) == -1) {
return false; }
return true; }
function percentCompleteSort(a, b) { return a["percentComplete"] -
b["percentComplete"]; }
function comparer(a, b) { var x = a[sortcol], y = b[sortcol];
return (x == y ? 0 : (x > y ? 1 : -1)); }
function toggleFilterRow() {
grid.setTopPanelVisibility(!grid.getOptions().showTopPanel); }
$(".grid-header .ui-icon")
.addClass("ui-state-default ui-corner-all")
.mouseover(function (e) {
$(e.target).addClass("ui-state-hover")
})
.mouseout(function (e) {
$(e.target).removeClass("ui-state-hover")
});
$(function () { // prepare the data for (var i = 0; i < 50000;
i++) {
var d = (data[i] = {});
d["id"] = "id_" + i;
d["num"] = i;
d["title"] = "Task " + i;
d["duration"] = "5 days";
d["percentComplete"] = Math.round(Math.random() * 100);
d["start"] = "01/01/2009";
d["finish"] = "01/05/2009";
d["effortDriven"] = (i % 5 == 0); }
dataView = new Slick.Data.DataView({ inlineFilters: true }); grid
= new Slick.Grid("#myGrid", dataView, columns, options); grid.setSelectionModel(new Slick.RowSelectionModel());
var pager = new Slick.Controls.Pager(dataView, grid, $("#pager"));
var columnpicker = new Slick.Controls.ColumnPicker(columns, grid,
options); // columnpicker = new Slick.Controls.ColumnPicker(Columns,
Grid, GridOptions); //grid.setSelectedcolumn([]); // move the
filter panel defined in a hidden div into grid top panel
$("#inlineFilterPanel")
.appendTo(grid.getTopPanel())
.show();
grid.onCellChange.subscribe(function (e, args) {
dataView.updateItem(args.item.id, args.item); });
grid.onAddNewRow.subscribe(function (e, args) {
var item = {"num": data.length, "id": "new_" + (Math.round(Math.random() * 10000)), "title": "New task",
"duration":
"1 day", "percentComplete": 0, "start": "01/01/2009", "finish":
"01/01/2009", "effortDriven": false};
$.extend(item, args.item);
dataView.addItem(item); });
grid.onKeyDown.subscribe(function (e) {
// select all rows on ctrl-a
if (e.which != 65 || !e.ctrlKey) {
return false;
}
var rows = [];
for (var i = 0; i < dataView.getLength(); i++) {
rows.push(i);
}
grid.setSelectedRows(rows);
e.preventDefault(); });
grid.onSort.subscribe(function (e, args) {
sortdir = args.sortAsc ? 1 : -1;
sortcol = args.sortCol.field;
if ($.browser.msie && $.browser.version <= 8) {
// using temporary Object.prototype.toString override
// more limited and does lexicographic sort only by default, but can be much faster
var percentCompleteValueFn = function () {
var val = this["percentComplete"];
if (val < 10) {
return "00" + val;
} else if (val < 100) {
return "0" + val;
} else {
return val;
}
};
// use numeric sort of % and lexicographic for everything else
dataView.fastSort((sortcol == "percentComplete") ? percentCompleteValueFn : sortcol, args.sortAsc);
} else {
// using native sort with comparer
// preferred method but can be very slow in IE with huge datasets
dataView.sort(comparer, args.sortAsc);
} });
// wire up model events to drive the grid
dataView.onRowCountChanged.subscribe(function (e, args) {
grid.updateRowCount();
grid.render(); });
dataView.onRowsChanged.subscribe(function (e, args) {
grid.invalidateRows(args.rows);
grid.render(); });
dataView.onPagingInfoChanged.subscribe(function (e, pagingInfo) {
var isLastPage = pagingInfo.pageNum == pagingInfo.totalPages - 1;
var enableAddRow = isLastPage || pagingInfo.pageSize == 0;
var options = grid.getOptions();
if (options.enableAddRow != enableAddRow) {
grid.setOptions({enableAddRow: enableAddRow});
} });
var h_runfilters = null;
// wire up the slider to apply the filter to the model
$("#pcSlider,#pcSlider2").slider({
"range": "min",
"slide": function (event, ui) {
Slick.GlobalEditorLock.cancelCurrentEdit();
if (percentCompleteThreshold != ui.value) {
window.clearTimeout(h_runfilters);
h_runfilters = window.setTimeout(updateFilter, 10);
percentCompleteThreshold = ui.value;
}
} });
// wire up the search textbox to apply the filter to the model
$("#txtSearch,#txtSearch2").keyup(function (e) {
Slick.GlobalEditorLock.cancelCurrentEdit();
// clear on Esc
if (e.which == 27) {
this.value = "";
}
searchString = this.value;
updateFilter(); });
function updateFilter() {
dataView.setFilterArgs({
percentCompleteThreshold: percentCompleteThreshold,
searchString: searchString
});
dataView.refresh(); }
$("#btnSelectRows").click(function () {
if (!Slick.GlobalEditorLock.commitCurrentEdit()) {
return;
}
var rows = [];
for (var i = 0; i < 10 && i < dataView.getLength(); i++) {
rows.push(i);
}
grid.setSelectedRows(rows); });
// initialize the model after all the events have been hooked up
dataView.beginUpdate(); dataView.setItems(data);
dataView.setFilterArgs({
percentCompleteThreshold: percentCompleteThreshold,
searchString: searchString }); dataView.setFilter(myFilter); dataView.endUpdate();
// if you don't want the items that are not visible (due to being
filtered out // or being on a different page) to stay selected,
pass 'false' to the second arg dataView.syncGridSelection(grid,
true);
$("#gridContainer").resizable(); }) </script>
I think the easiest way is to only add the 3 columns you want to be visible to the "columns" variable.
If you want to show more columns at a later time you can add more columns on the fly (refer to this stackoverflow question on how to do this: Can I add a column to slickgrid on on the fly? )
Update: Example to only show the first 3 columns
insert this line:
columns = columns.slice(0, 3);
before the following line:
grid = new Slick.Grid("#myGrid", dataView, columns, options);

Swimlane - GoJS library

I would like to ask, if anyone knows how to convert the orientation of the diagram of swimlanes in GOjs library from being top-level to a single row orientation, it is because I want to have a diagram that connects a node to another from a different group.
And also is it possible to add attributes to each node, like onmouseover, class, id, name, and etc.?
Currently I have this copied and paste code:
<!DOCTYPE html>
<html>
<head>
<title>Swimlane</title>
<!-- Copyright 1998-2014 by Northwoods Software Corporation. -->
<link href="goSamples.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="go.js"></script>
<script id="code">
// These parameters need to be set before defining the templates.
// this controls whether the swimlanes are horizontal stacked vertically, or the other way:
var HORIZONTAL = true;
// this controls the minimum length of any swimlane
var MINLENGTH = 200;
// this controls the minimum breadth of any swimlane
var MINBREADTH = 100;
// compute the minimum length needed to hold all of the subgraphs
function computeMinPlaceholderSize(diagram) {
var len = MINLENGTH;
for (var it = diagram.nodes; it.next(); ) {
var group = it.value;
if (!(group instanceof go.Group)) continue;
var holder = group.placeholder;
if (holder !== null) {
var sz = holder.actualBounds;
len = Math.max(len, (HORIZONTAL ? sz.width : sz.height));
}
}
return (HORIZONTAL ? new go.Size(len, NaN) : new go.Size(NaN, len));
}
// get the minimum placeholder size for a particular Group;
// when group is null, return the minimum size
function computePlaceholderSize(group) {
if (group instanceof go.Group) {
var holder = group.placeholder;
if (holder !== null) {
return holder.actualBounds.size;
}
}
return (HORIZONTAL ? new go.Size(MINLENGTH, MINBREADTH) : new go.Size(MINBREADTH, MINLENGTH));
}
// define a custom ResizingTool to limit how far one can shrink a Group
function GroupResizingTool() {
go.ResizingTool.call(this);
}
go.Diagram.inherit(GroupResizingTool, go.ResizingTool);
GroupResizingTool.prototype.isLengthening = function() {
return (this.handle.alignment === (HORIZONTAL ? go.Spot.Right : go.Spot.Bottom));
};
GroupResizingTool.prototype.computeMinSize = function() {
var msz = computePlaceholderSize(null); // get the minimum size
if (this.isLengthening()) { // compute the minimum length of all lanes
var sz = computeMinPlaceholderSize(this.diagram);
if (HORIZONTAL) {
msz.width = Math.max(msz.width, sz.width);
} else {
msz.height = Math.max(msz.height, sz.height);
}
} else { // find the minimum size of this single lane
var sz = computePlaceholderSize(this.adornedObject.part);
msz.width = Math.max(msz.width, sz.width);
msz.height = Math.max(msz.height, sz.height);
}
return msz;
};
GroupResizingTool.prototype.resize = function(newr) {
if (this.isLengthening()) { // changing the length of all of the lanes
for (var it = myDiagram.nodes; it.next(); ) {
var group = it.value;
if (!(group instanceof go.Group)) continue;
var shape = group.findObject("SHAPE");
if (shape !== null) { // set its desiredSize, but leave the other direction alone
if (HORIZONTAL) {
shape.width = newr.width;
} else {
shape.height = newr.height;
}
}
}
} else { // changing the breadth and length of a single lane
go.ResizingTool.prototype.resize.call(this, newr);
}
};
// end GroupResizingTool class
// define a custom grid layout that makes sure the length of each lane is the same
// and that each lane is broad enough to hold its subgraph
function StackLayout() {
go.GridLayout.call(this);
}
go.Diagram.inherit(StackLayout, go.GridLayout);
StackLayout.prototype.doLayout = function(coll) {
var diagram = this.diagram;
if (diagram === null) return;
diagram.startTransaction("StackLayout");
// make sure all of the Group Shapes are big enough
var minsize = computeMinPlaceholderSize(diagram);
for (var it = diagram.nodes; it.next(); ) {
var group = it.value;
if (!(group instanceof go.Group)) continue;
var shape = group.findObject("SHAPE");
if (shape !== null) { // change the desiredSize to be big enough in both directions
var sz = computePlaceholderSize(group);
if (HORIZONTAL) {
shape.width = (isNaN(shape.width) ? minsize.width : Math.max(shape.width, minsize.width));
if (!isNaN(shape.height)) shape.height = Math.max(shape.height, sz.height);
} else {
if (!isNaN(shape.width)) shape.width = Math.max(shape.width, sz.width);
shape.height = (isNaN(shape.height) ? minsize.height : Math.max(shape.height, minsize.height));
}
var cell = group.resizeCellSize;
if (!isNaN(shape.width) && !isNaN(cell.width) && cell.width > 0) shape.width = Math.ceil(shape.width / cell.width) * cell.width;
if (!isNaN(shape.height) && !isNaN(cell.height) && cell.height > 0) shape.height = Math.ceil(shape.height / cell.height) * cell.height;
}
}
// now do all of the usual stuff, according to whatever properties have been set on this GridLayout
go.GridLayout.prototype.doLayout.call(this, coll);
diagram.commitTransaction("StackLayout");
};
// end StackLayout class
function init() {
if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagram",
{
// use a custom ResizingTool (along with a custom ResizeAdornment on each Group)
resizingTool: new GroupResizingTool(),
// use a simple layout that ignores links to stack the top-level Groups on top of each other
layout:
$(StackLayout,
{
cellSize: new go.Size(1, 1),
spacing: new go.Size(0, 0),
wrappingColumn: (HORIZONTAL ? 1 : Infinity),
wrappingWidth: Infinity,
isViewportSized: false
}),
// don't allow dropping onto the diagram's background
mouseDrop: function(e) { e.diagram.currentTool.doCancel(); },
// a clipboard copied node is pasted into the original node's group (i.e. lane).
"commandHandler.copiesGroupKey": true
});
// When a Node has been moved, make sure all of the top-level Groups get laid out again in a stack
function relayoutDiagramStack(e) {
myDiagram.layout.invalidateLayout(); // but don't invalidate all Layouts that are in Groups
myDiagram.layoutDiagram();
}
myDiagram.addDiagramListener("SelectionMoved", relayoutDiagramStack);
myDiagram.addDiagramListener("SelectionCopied", relayoutDiagramStack);
// this is a Part.dragComputation function for limiting where a Node may be dragged
function stayInGroup(part, pt, gridpt) {
// don't constrain top-level nodes
var grp = part.containingGroup;
if (grp === null) return pt;
// try to stay within the background Shape of the Group
var back = grp.findObject("SHAPE");
if (back === null) return pt;
// allow dragging a Node out of a Group if the Shift key is down
if (part.diagram.lastInput.shift) return pt;
var b = part.actualBounds;
var p1 = back.getDocumentPoint(go.Spot.TopLeft);
var p2 = back.getDocumentPoint(go.Spot.BottomRight);
// find the padding inside the group's placeholder that is around the member parts
var m = grp.placeholder.padding;
// now limit the location appropriately
var x = Math.max(p1.x + m.left, Math.min(pt.x, p2.x - m.right - b.width - 1));
var y = Math.max(p1.y + m.top, Math.min(pt.y, p2.y - m.bottom - b.height - 1));
return new go.Point(x, y);
}
myDiagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "Rectangle",
{ fill: "white", portId: "", cursor: "pointer", fromLinkable: true, toLinkable: true }),
$(go.TextBlock, { margin: 5 },
new go.Binding("text", "key")),
// limit dragging of Nodes to stay within the containing Group, defined above
{
dragComputation: stayInGroup,
mouseDrop: function (e, node) { // dropping a copy of some Nodes and Links onto this Node adds them to this Node's Group
if (!e.shift && !e.control) return; // cannot change groups with an unmodified drag-and-drop
var grp = node.containingGroup;
if (grp !== null) {
var ok = grp.addMembers(node.diagram.selection, true);
if (!ok) grp.diagram.currentTool.doCancel();
}
},
layoutConditions: go.Part.LayoutAdded | go.Part.LayoutNodeSized
}
);
// each Group is a "swimlane" with a header on the left and a resizable lane on the right
myDiagram.groupTemplate =
$(go.Group, HORIZONTAL ? "Horizontal" : "Vertical",
{
movable: false, copyable: false, deletable: false, // can't move or copy or delete lanes
avoidable: false,
selectionObjectName: "SHAPE", // selecting a lane causes the body of the lane to be highlit, not the label
resizable: true, resizeObjectName: "SHAPE", // allow lanes to be resized, but the custom resizeAdornmentTemplate only permits one kind of resizing
layout: $(go.LayeredDigraphLayout, // automatically lay out the lane's subgraph
{ direction: HORIZONTAL ? 0 : 90, columnSpacing: 10, layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource }),
computesBoundsAfterDrag: true, // needed to prevent recomputing Group.placeholder bounds too soon
computesBoundsIncludingLinks: false,
computesBoundsIncludingLocation: true,
mouseDrop: function (e, grp) { // dropping a copy of some Nodes and Links onto this Group adds them to this Group
if (!e.shift && !e.control) return; // cannot change groups with an unmodified drag-and-drop
var ok = grp.addMembers(grp.diagram.selection, true);
if (!ok) grp.diagram.currentTool.doCancel();
}
},
// the lane header consisting of a Shape and a TextBlock
$(go.Panel, "Horizontal",
{ angle: HORIZONTAL ? 270 : 0, // maybe rotate the header to read sideways going up
alignment: go.Spot.Center },
$(go.Shape, "Diamond",
{ width: 8, height: 8 },
new go.Binding("fill", "color")),
$(go.TextBlock, // the lane label
{ font: "bold 16pt sans-serif" },
new go.Binding("text", "key"))
), // end Horizontal Panel
$(go.Panel, "Auto", // the lane consisting of a background Shape and a Placeholder representing the subgraph
$(go.Shape, "Rectangle",
{ name: "SHAPE", fill: "white", minSize: computePlaceholderSize(null) },
new go.Binding("fill", "color")),
$(go.Placeholder,
{ padding: 10, alignment: go.Spot.TopLeft })
) // end Auto Panel
); // end Group
// define a custom resize adornment that only has a single resize handle
myDiagram.groupTemplate.resizeAdornmentTemplate =
$(go.Adornment, "Spot",
$(go.Placeholder),
$(go.Shape, // for changing the length of a lane
{
alignment: HORIZONTAL ? go.Spot.Right: go.Spot.Bottom,
desiredSize: HORIZONTAL ? new go.Size(7, 50) : new go.Size(50, 7),
fill: "lightblue", stroke: "dodgerblue",
cursor: HORIZONTAL ? "col-resize" : "row-resize"
}),
$(go.Shape, // for changing the breadth of a lane
{
alignment: HORIZONTAL ? go.Spot.Bottom : go.Spot.Right,
desiredSize: HORIZONTAL ? new go.Size(50, 7) : new go.Size(7, 50),
fill: "lightblue", stroke: "dodgerblue",
cursor: HORIZONTAL ? "row-resize" : "col-resize"
})
);
myDiagram.linkTemplate =
$(go.Link,
{ routing: go.Link.AvoidsNodes, corner: 5 },
{ relinkableFrom: true, relinkableTo: true },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" }),
{
mouseDrop: function (e, link) { // dropping a copy of some Nodes and Links onto this Link adds them to this Link's Group
if (!e.shift && !e.control) return; // cannot change groups with an unmodified drag-and-drop
var grp = link.containingGroup;
if (grp !== null) {
var ok = grp.addMembers(link.diagram.selection, true);
if (!ok) grp.diagram.currentTool.doCancel();
}
},
layoutConditions: go.Part.LayoutAdded
}
);
// define some sample graphs in some of the lanes
myDiagram.model = new go.GraphLinksModel(
[ // node data
{ key: "Lane1", isGroup: true, color: "lightblue" },
{ key: "Lane2", isGroup: true, color: "lightgreen" },
{ key: "Lane3", isGroup: true, color: "lightyellow" },
{ key: "Lane4", isGroup: true, color: "orange" },
{ key: "oneA", group: "Lane1" },
{ key: "oneB", group: "Lane1" },
{ key: "oneC", group: "Lane1" },
{ key: "oneD", group: "Lane1" },
{ key: "twoA", group: "Lane2" },
{ key: "twoB", group: "Lane2" },
{ key: "twoC", group: "Lane2" },
{ key: "twoD", group: "Lane2" },
{ key: "twoE", group: "Lane2" },
{ key: "twoF", group: "Lane2" },
{ key: "twoG", group: "Lane2" },
{ key: "fourA", group: "Lane4" },
{ key: "fourB", group: "Lane4" },
{ key: "fourC", group: "Lane4" },
{ key: "fourD", group: "Lane4" },
],
[ // link data
{ from: "oneA", to: "oneB" },
{ from: "oneA", to: "oneC" },
{ from: "oneB", to: "oneD" },
{ from: "oneC", to: "oneD" },
{ from: "twoA", to: "twoB" },
{ from: "twoA", to: "twoC" },
{ from: "twoA", to: "twoF" },
{ from: "twoB", to: "twoD" },
{ from: "twoC", to: "twoD" },
{ from: "twoD", to: "twoG" },
{ from: "twoE", to: "twoG" },
{ from: "twoF", to: "twoG" },
{ from: "fourA", to: "fourB" },
{ from: "fourB", to: "fourC" },
{ from: "fourC", to: "fourD" }
]);
myDiagram.model.undoManager.isEnabled = true;
}
</script>
</head>
<body onload="init()">
<div id="sample">
<div id="myDiagram" style="border: solid 1px blue; width:100%; height:600px;"></div>
</div>
</body>
</html>
I've tried setting
var HORIZONTAL = true;
to
var HORIZONTAL = false;
I may have the correct orientation though, but the flow is changed. I wonder if the column could just be horizontal and the flow runs like the top-level view.
The "flow" is changed because setting HORIZONTAL = false also changes the Group's layout that it users for its members:
myDiagram.groupTemplate =
$(go.Group, HORIZONTAL ? "Horizontal" : "Vertical",
{
...
layout: $(go.LayeredDigraphLayout, // automatically lay out the lane's subgraph
{ direction: HORIZONTAL ? 0 : 90, columnSpacing: 10, layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource }),
So you're going to have to modify that direction: HORIZONTAL ? 0 : 90 to your liking if you don't want it to change when HORIZONTAL does.
It is not possible to add HTML attributes to each node, but you can store any arbitrary data for each node in the node data itself.

Categories

Resources