Hi All,
Using a combination of the swim lanes and workflow examples I’ve set up a palette and node/group diagram. Just using the ‘default’ of the group template and ‘default’ node template I can add nodes from the palette to groups - as would be seen in the swim lane example.
I’m trying to expand this to have group nodes called ‘Tasks’ that can be added to groups ‘Lanes’. This works in that Tasks will go into Lanes. But I also have two new nodes ‘Tags’ and ‘Topics’ that I want to be able to add to the Task group. The issue is, I can’t add Topic/Tags to the Tasks. Although, they can be added to the top group Lane.
Do, template mappings not ‘inherit’ all the functionality of their templates?
All Code below… (hold- shift to drop into a group)
Thanks
Z
<!doctype html>
<html>
<head>
<title>SwimLane</title>
<style type="text/css">
#myOverview {
position: absolute;
top: 20px;
left: 140px;
background-color: aliceblue;
z-index: 300; /* make sure its in front */
border: solid 1px blue;
width:200px;
height:100px
}
</style>
<script 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;
diagram.nodes.each(function(group) {
if (!(group instanceof go.Group)) return;
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;
};
function init() {
//if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
var $ = go.GraphObject.make;
// this function is used to highlight a Group that the selection may be dropped into
function highlightGroup(e, grp, show) {
if (!grp) return;
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;
// 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);
if (!ok) grp.diagram.currentTool.doCancel();
}
//Overall Diagram
myDiagram =
$(go.Diagram, "myDiagram",
{
// start everything in the middle of the viewport
initialContentAlignment: go.Spot.Center,
allowDrop: true, // must be true to accept drops from the Palette
// 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
"commandHandler.copiesGroupKey": true,
// automatically re-layout the swim lanes after dragging the selection
"SelectionMoved": relayoutDiagramStack, // this DiagramEvent listener is
"SelectionCopied": relayoutDiagramStack, // defined below
// enable undo & redo
"undoManager.isEnabled": 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();
}
// 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);
}
//NODE TEMPLATE - TASK - needs to become a group - and named
myDiagram.nodeTemplate =
$(go.Node, "Auto",
$(go.Shape, "RoundedRectangle",
{ 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
}
);
// This for the Topic
myDiagram.nodeTemplateMap.add("Topic",
$(go.Node, go.Panel.Auto,
$(go.Panel, "Auto",
{background: "transparent"},
$(go.Shape, "Rectangle",
{ minSize: new go.Size(80, 15), maxSize: new go.Size(80, 15), height:15, width:80, fill: "#FFC2FF", stroke: "#CC0099" }),
$(go.TextBlock, "Topic",
{ margin: 1, font: "9pt Helvetica, Arial, sans-serif", stroke: "#000000" })
)
));
// This for the Tag
myDiagram.nodeTemplateMap.add("Tag",
$(go.Node, go.Panel.Auto,
$(go.Panel, "Auto",
{background: "transparent"},
$(go.Shape, "Rectangle",
{ minSize: new go.Size(80, 15), maxSize: new go.Size(80, 15), height:15, width:80, fill: "#FFFFCC", stroke: "#E69900" }),
$(go.TextBlock, "Tag",
{ margin: 1, font: "9pt Helvetica, Arial, sans-serif", stroke: "#000000" })
)
));
//GROUP TEMPLATE - everything for the node is defined here - Activity - needs to have a name on the top
// 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", //contition if true or if false - but is always
{
movable: true, copyable: false, deletable: true, // can move, can be deleted, avoidable???
avoidable: false,
selectionObjectName: "SHAPE", // selecting a lane causes the body of the lane to be highlit, not the label
resizable: true, resizeObjectName: "SHAPE", // the custom resizeAdornmentTemplate only permits two kinds 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();
}
},
$(go.Panel, "Auto", // the lane consisting of a background Shape and a Placeholder representing the subgraph
$(go.Shape, "RoundedRectangle",
{ 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
// TASK - a group that can be placed in an Activity Group
myDiagram.groupTemplateMap.add("Task",
$(go.Group, go.Panel.Auto,
{
background: "transparent",
// 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
},
$(go.Shape, "RoundedRectangle",
{minSize: new go.Size(100, 100), 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
}));
//LINKS updated - so is like .03
myDiagram.linkTemplate =
$(go.Link,
{ routing: go.Link.AvoidsNodes, corner: 5 },
{ relinkableFrom: true, relinkableTo: true },
$(go.Shape),
$(go.Shape, { toArrow: "Standard" }),
{ // dropping a copy of some Nodes and Links onto this Link adds them to this Link's Group
mouseDrop: function (e, link) {
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
}
);
// create the Palette
var myPalette =
$(go.Palette, "myPalette",
{
//groupTemplate: myDiagram.nodeTemplate
// limit items to one column
//layout: $(go.GridLayout, { wrappingColumn: 1 })
}
);
myPalette.nodeTemplate =
$(go.Node, "Vertical", //node template is a Node object (can have a Panel layout)
$(go.Panel, "Auto",
$(go.Shape, "RoundedRectangle", new go.Binding("fill", "color"),
{ height: 40, width: 40, fill: "white" }),
$(go.TextBlock,
{
font: "bold 11pt Helvetica, Arial, sans-serif",
//stroke: lightText,
margin: 8,
maxSize: new go.Size(40, 40),
wrap: go.TextBlock.WrapFit,
textAlign: "center",
editable: false
},
new go.Binding("text", "text"))
));
myPalette.groupTemplate =
$(go.Group, "Vertical",
$(go.Panel, "Auto",
$(go.Shape, "RoundedRectangle", new go.Binding("fill", "color"),
{ height: 40, width: 40, fill: "white" }),
$(go.TextBlock,
{
font: "bold 11pt Helvetica, Arial, sans-serif",
//stroke: lightText,
margin: 8,
maxSize: new go.Size(40, 40),
wrap: go.TextBlock.WrapFit,
textAlign: "center",
editable: false
},
new go.Binding("text", "text"))
));
myPalette.model.nodeDataArray = [
{category:"Lane", name: "Lane", text: "Lane", isGroup: true, color:"#C0C0C0", },
{category:"Node", key:"Node", name: "Node", text: "Node", isGroup: false, color:"#00A9C9"},
{category:"Task" ,key:"Task", name: "Task", text: "Task", isGroup: true, color:"#00A9C9", figure:"Task"},
{category:"Topic", key:"Topic", name: "Topic", text: "Topic", isGroup: false, color:"#FFC2FF", figure:"Topic"},
{category:"Tag", key:"Tag", name: "Tag", text: "Tag", isGroup: false, color:"#FFFFCC", figure:"Tag"}
//{category: "Task", text: "Task 03", figure: "Task", color: "#00A9C9" },
];
//myPalette.nodeTemplateMap = myDiagram.nodeTemplateMap;
//load(); // load an initial diagram from some JSON text
//Needs to be loaded from JSON as .03
// define some sample graphs in some of the lanes
myDiagram.model = new go.GraphLinksModel(
[ // node data
//{ key: "Lane1", isGroup: true, color: "lightblue", category:"Task" },
{ 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" }
]);
} // end init
</script>
</head>
<body onload="init()">
<div id="sample">
<div style="width:100%; white-space:nowrap;">
<span style="display: inline-block; vertical-align: top; padding: 5px; width:100px">
<div id="myPalette" style="border: solid 1px gray; height: 720px"></div>
</span>
<span style="display: inline-block; vertical-align: top; padding: 5px; width:80%">
<div id="myDiagram" style="border: solid 1px gray; height: 720px"></div>
</span>
</div>
</body>
</html>