- Working With Groups
- Loading Groups
- Rendering
- Collapsing/Expanding Groups
- Templating
- Drag and Drop Groups
- Autosizing Groups
- Layouts
Groups
Groups are elements in your UI that act as a container for zero or more Nodes. In the UI, these can be collapsed, and Edges to/from the Nodes inside the Group are then temporarily displaced to the Group container.
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.
The Toolkit's default JSON parser can parse information about groups, and about node membership in groups. You can do this in two ways:
- Explicitly declared Groups
var t = jsPlumbToolkit.newInstance();
t.load({
data:{
groups:[
{ id:"g1", type:"groupType1" },
{ id:"g2", type:"groupType1" }
],
nodes:[
{ id:"1", group:"g1" },
{ id:"2", group:"g1" },
{ id:"3", group:"g2" },
{ id:"4", group:"g2" }
],
edges:[
{ "source":"1", target:"2" },
{ "source":"1", target:"3" },
{ "source":"4", target:"3" },
{ "source":"4", target:"2" }
]
}
});
Here, we have a groups
section which lists out all the groups in the dataset. Then in the nodes
we map each Node to a Group, which is expected to be declared in the payload. The default behaviour of the Toolkit is to not create Groups that are referenced by a Node but which do not exist. You can set that on a flag though:
- Implicitly declared Groups
var t = jsPlumbToolkit.newInstance({
createMissingGroups:true
});
t.load({
data:{
nodes:[
{ id:"1", group:"g1" },
{ id:"2", group:"g1" },
{ id:"3", group:"g2" },
{ id:"4", group:"g2" }
],
edges:[
{ "source":"1", target:"2" },
{ "source":"1", target:"3" },
{ "source":"4", target:"3" },
{ "source":"4", target:"2" }
]
}
});
By setting createMissingGroups:true
in the Toolkit factory method, we no longer need to declare the Groups in the JSON. This approach has the drawback that you cannot specify the type
of the Groups that are created: they will be given a type of "default"
. Also, you cannot associate arbitrary data with these automatically created groups, meaning in your UI you will not be able to render anything unique to each Group. For some use cases, of course, this is perfectly acceptable.
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":{
template:"tmplNode",
events:{
click:function(p) {
alert("You clicked on node " + p.node.id);
}
}
}
},
groups:{
"groupType1":{
template:"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 may be dropped onto the Group.
- constrain False by default - Nodes may be dragged outside of the bounds of their Group.
- revert True by default - a Node dragged outside of its 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 dropped outside of the Group (and not dropped onto another Group) are removed from the Group (but remain in the dataset)
- 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.
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 in the Group to Nodes 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.
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":{
template:"tmplGroupType1",
endpoint:"Blank",
anchor:"Continuous"
}
}
}
...
});
Any valid Anchor/Endpoint (including custom Endpoints) can be used here.
Specifying the Node 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 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 jtk-group-content="true" class="aGroupInner">
<!-- Nodes go here -->
</div>
</div>
</script>
You can drag Groups from a palette onto a Surface using the Drop Manager used to drag Nodes onto a Surface, with two differences:
- you need to set
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" 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
:
var toolkit = window.toolkit = jsPlumbToolkit.newInstance({
groupFactory:function(type, data, callback) {
data.title = "Group " + (toolkit.getGroupCount() + 1);
callback(data);
},
nodeFactory:function(type, data, callback) {
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"
};
}
});
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":{
template:"tmplGroupTypeOne",
autoSize:true
},
"groupType2":{
template:"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):
or on a single group:
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:"Hierarchical",
parameters:{
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.