Groups
Groups are elements in your model/UI that act as a container for zero or more nodes or other groups. In the UI, these can be collapsed, and edges to/from the nodes/groups inside the group are then temporarily displaced to the group container. There is no limit imposed on how deeply groups may be nested.
Working with Groups
Groups have in common with nodes, edges and ports the two concepts of id
and of type
, and as with the other graph objects, type
will be set to "default"
if it cannot be determined. Group IDs and types either follow the default rules (ie. they are given by the id
and type
parameters, respectively, in the group's data), or they are derived by applying the current idFunction
and typeFunction
.
Groups, as with the other graph objects, can have arbitrary JSON data associated with them.
Rendering
Groups are rendered using client side templates just as Nodes are. They are declared inside the view
alongside nodes, edges and ports. A simple example to start:
toolkit.render({
...
view:{
nodes:{
"default":{
templateId:"tmplNode",
events:{
click:function(p) {
alert("You clicked on node " + p.node.id);
}
}
}
},
groups:{
"groupType1":{
templateId:"tmplGroupType1",
constrain:true
}
}
}
});
Here we declare that Groups of type groupType1
are to be rendered using the template tmplGroupType1
, and that members of the Group are constrained to the Group element (that is, they cannot be dragged outside of the Group's bounds).
There are several flags available to control the drag behaviour of members of a group:
droppable True by default - indicates that nodes/groups may be dropped onto the group, either from some other group or from the main canvas.
constrain False by default - nodes/groups may be dragged outside of the bounds of the group. When you do drag a node/group outside of the bounds of its parent, what happens next depends on the other flags you have set and where you have dropped it.
orphan:true
, for instance, will cause the node/group to be removed from its parent group.revert
will reinstate the node/group's position inside its parent, unless it was dropped on another group.revert True by default - a node/group dragged outside of its parent group will, unless dropped on another group, revert back to its position inside the group.
prune False by default. If true, Nodes dropped outside of the Group (and not dropped onto another Group) are removed from the dataset (not just the Group...the entire dataset).
orphan False by default. If true, nodes/groups dropped outside of the group (and not dropped onto another group) are removed from the group (but remain in the dataset). When you set this to
true
,revert
is automatically forced tofalse
.dropOverride False by default. If true, Nodes dropped onto other Groups first have any rules established by this Group applied. For instance, if the Groups stated
prune:true
, then the Node would be removed from the dataset rather than be dropped onto another Group.autoSize
autoGrow
autoShrink
Collapsing/Expanding Groups
You can collapse/expand a group using the collapseGroup
and expandGroup
methods on the Surface
widget. When you collapse a Group, any Edges from any of the member Nodes/Groups in the Group to Nodes/Groups outside of the Group are relocated to the Group's container, and a CSS class is applied to the Group's container, indicating the collapsed state. When you subsequently expand the Group, the Edges are placed back onto their appropriate Nodes/Groups.
It is important to note that when a Group is collapsed, the jsPlumb Toolkit does not hide the member Nodes automatically for you. But a CSS class of jtk-group-collapsed
is added to the Group's container, for you to handle this in your CSS.
The Endpoint and Anchor to be used in the collapsed state can be specified in the Group definition in the view
:
toolkit.render({
...
view:{
groups:{
"groupType1":{
templateId:"tmplGroupType1",
endpoint:"Blank",
anchor:"Continuous"
}
}
}
...
});
Any valid Anchor/Endpoint (including custom Endpoints) can be used here.
Magnetizing a collapsed/expanded Group
By default, the Surface widget will run the Magnetizer whenever a group is collapsed or expanded: when a Group is expanded, surrounding elements are adjusted so as to ensure the group does not intersect with any other element. When a group is collapsed, the rest of the elements in the view are gathered in towards the collapsed group. This behaviour can be switched off - see the afterGroupChange
flag in the Surface widget magnetizer options
Templating
Specifying the canvas
It is not necessarily the case that you wish to use your entire Group template as the parent of the Group's members. You can set a data-jtk-group-content
attribute on the element that you wish to have acting as the parent for the members:
<script type="jtk" id="tmplMyGroup">
<div class="aGroup">
<h1>${title}</h1>
<div data-jtk-group-content="true" class="aGroupInner">
<!-- Child elements go here -->
</div>
</div>
</script>
Whenever a Group is resized by the auto sizing code in the Surface, the Surface looks for an element in the Group with this attribute, and if found, this is the element to which the Surface applies the change of size. Otherwise the size is applied to the Group's main element. Keep this in mind from a CSS perspective: your CSS should allow the size of the content area to mandate the size of its parent. Scroll/auto overflow is not supported inside a Group element.
Drag and Drop Groups
You can drag Groups from a palette onto a Surface using the Drop Manager, with two differences from how you would do this to drag a Nide:
- you need to set
data-jtk-is-group="true"
as an attribute on an element that you wish to drag on to the Surface as a Group - the current
GroupFactory
will be called to prepare a dataset for your new Group, rather than the currentNodeFactory
.
Here's an example from the Groups demo that ships with the Toolkit (showing both Nodes and Groups, but we're just going to focus on the Groups setup):
<div class="sidebar node-palette">
<div title="Drag Node to canvas" data-node-type="node" class="sidebar-item">
<i class="icon-tablet"></i>Drag Node
</div>
<div title="Drag Group to canvas" data-jtk-is-group="true" data-node-type="group" class="sidebar-item">
<i class="icon-tablet"></i>Drag Group
</div>
</div>
The Toolkit instance is configured with both a nodeFactory
and a groupFactory
:
const toolkit = jsPlumbToolkitBrowserUI.newInstance({
groupFactory:(type:string, data:ObjectData, callback:(o:ObjectData)=>any) => {
data.title = "Group " + (toolkit.getGroupCount() + 1)
callback(data)
},
nodeFactory:(type:string, data:ObjectData, callback:(o:ObjectData)=>any) => {
data.name = (toolkit.getNodeCount() + 1)
callback(data)
}
});
...and the call to configure the drag/drop is no different to that which you'd make just for Nodes:
new SurfaceDropManager({
surface:renderer,
source:document.querySelector(".node-palette"),
selector:"[data-node-type]",
dataGenerator:function(e) {
return {
type:"default"
};
}
});
Autosizing Groups
Groups can be configured to automatically resize themselves to encompass the extents of their child Nodes, via the autoSize
flag on a Group definition in a View:
view:{
groups:{
"groupType1":{
templateId:"tmplGroupTypeOne",
autoSize:true
},
"groupType2":{
templateId:"tmplGroupTypeOne",
autoSize:true,
maxSize:[600,600]
}
}
}
In this example, both Group types are declared to auto size, but groupType2
will grow to a maximum of 600 pixels in each axis.
Autosizing is run after a data load (ie. you call toolkit.load(...)
), or when data exists in a Toolkit instance and you call toolkit.render(...)
.
You can run auto sizing on demand (on all Groups):
surface.autoSizeGroups()
or on a single group:
surface.sizeGroupToFit(group:Group)
Layouts
By default, every group has an Absolute
layout assigned to it. If your node data has left
/top
properties in it, these values will automatically be used to place nodes inside of their parent groups.
Specifying in the view
To specify the layout for a specific type of group, set it in that group's entry in your view:
view:{
...
groups:{
"someGroupType":{
...
layout:{
type:HierarchicalLayout.type, // (or just use 'Hierarchical' as a string
options:{
orientation:"vertical"
}
}
}
}
...
}
The format of the layout
parameter is identical to the layout
parameter in the root of the view
.
Layout on demand
You can force a layout in a group like this:
surface.relayoutGroup(group:string|Group)
This will cause a layout to be run immediately on the given Group (which may be passed in as the Group object, or just its id)
Relationship to group size
By default, a layout in a group will cause the auto size routine to be run for the group immediately afterwards.