Group and Node 'Inheritance' (?) from Template Map

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>

I’m not sure what you mean by “inherit”. There is no relationship between templates – they are and should be completely independent of each other.

The Planogram sample demonstrates a custom memberValidation predicate in order to control what kinds of Parts can be added to Groups. In that sample it prevents adding “racks” to other “racks”, but permits all simple Nodes to be added to a “rack”, where each “rack” is a Group.

Sorry, what I mean by inherit is would all group template maps inherit and behave as their group template?

For example, I create a group template A. It is given some attributes. If I then create groupTemplateMap.add(B), will B not act the same as A?


Also I’ve just realised the reason why I cannot add to the Task group is because the graphical might not be correct. What part of a group definition defines the area where a nodes/elements can be added to a group?


Thanks,


Z

No, each template is quite independent of each other. The order in which they are added to a template Map does not matter.

The whole visual tree of GraphObjects, copied from the template, is the group. However you can define mouse events on whatever particular GraphObjects that you choose, and those events would only apply to those GraphObjects (and if it is a Panel, its elements will inherit the behavior). That allows you to design nodes where dropping on a particular object has a different effect than dropping on the rest of the node.

Thanks, yes, I had the group mapping set up wrong but also, as I was using the swimlane example the dragComputation: stayInGroup operation was putting everything into the highest level group rather than nesting into the sub group.


However, the new setup has caused the linking functionality to stop working… new topic…

Z