create-gojs-kit
Version:
A CLI for downloading GoJS samples, extensions, and docs
438 lines (392 loc) • 18.6 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
<meta name="description" content="Support selecting items inside nodes." />
<meta itemprop="description" content="Support selecting items inside nodes." />
<meta property="og:description" content="Support selecting items inside nodes." />
<meta name="twitter:description" content="Support selecting items inside nodes." />
<link rel="preconnect" href="https://rsms.me/">
<link rel="stylesheet" href="../assets/css/style.css">
<!-- Copyright 1998-2025 by Northwoods Software Corporation. -->
<meta itemprop="name" content="Selectable Fields in Record Nodes" />
<meta property="og:title" content="Selectable Fields in Record Nodes" />
<meta name="twitter:title" content="Selectable Fields in Record Nodes" />
<meta property="og:image" content="https://gojs.net/latest/assets/images/screenshots/selectablefields.png" />
<meta itemprop="image" content="https://gojs.net/latest/assets/images/screenshots/selectablefields.png" />
<meta name="twitter:image" content="https://gojs.net/latest/assets/images/screenshots/selectablefields.png" />
<meta property="og:url" content="https://gojs.net/latest/samples/selectableFields.html" />
<meta property="twitter:url" content="https://gojs.net/latest/samples/selectableFields.html" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="og:type" content="website" />
<meta property="twitter:domain" content="gojs.net" />
<title>
Selectable Fields in Record Nodes | GoJS Diagramming Library
</title>
</head>
<body>
<!-- This top nav is not part of the sample code -->
<nav id="navTop" class=" w-full h-[var(--topnav-h)] z-30 bg-white border-b border-b-gray-200">
<div class="max-w-screen-xl mx-auto flex flex-wrap items-start justify-between px-4">
<a class="text-white bg-nwoods-primary font-bold !leading-[calc(var(--topnav-h)_-_1px)] my-0 px-2 text-4xl lg:text-5xl logo"
href="../">
GoJS
</a>
<div class="relative">
<button id="topnavButton" class="h-[calc(var(--topnav-h)_-_1px)] px-2 m-0 text-gray-900 bg-inherit shadow-none md:hidden hover:!bg-inherit hover:!text-nwoods-accent hover:!shadow-none" aria-label="Navigation">
<svg class="h-7 w-7 block" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div id="topnavList" class="hidden md:block">
<div class="absolute right-0 z-30 flex flex-col items-end rounded border border-gray-200 p-4 pl-12 shadow bg-white text-gray-900 font-semibold
md:flex-row md:space-x-4 md:items-start md:border-0 md:p-0 md:shadow-none md:bg-inherit">
<a href="../learn/">Learn</a>
<a href="../samples/">Samples</a>
<a href="../intro/">Intro</a>
<a href="../api/">API</a>
<a href="../download.html">Download</a>
<a href="https://forum.nwoods.com/c/gojs/11" target="_blank" rel="noopener">Forum</a>
<a id="tc" href="https://nwoods.com/contact.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/contact.html', 'contact');">Contact</a>
<a id="tb" href="https://nwoods.com/sales/index.html"
target="_blank" rel="noopener" onclick="getOutboundLink('https://nwoods.com/sales/index.html', 'buy');">Buy</a>
</div>
</div>
</div>
</div>
</nav>
<script>
window.addEventListener("DOMContentLoaded", function () {
// topnav
var topButton = document.getElementById("topnavButton");
var topnavList = document.getElementById("topnavList");
if (topButton && topnavList) {
topButton.addEventListener("click", function (e) {
topnavList
.classList
.toggle("hidden");
e.stopPropagation();
});
document.addEventListener("click", function (e) {
// if the clicked element isn't the list, close the list
if (!topnavList.classList.contains("hidden") && !e.target.closest("#topnavList")) {
topButton.click();
}
});
// set active <a> element
var url = window
.location
.href
.toLowerCase();
var aTags = topnavList.getElementsByTagName('a');
for (var i = 0; i < aTags.length; i++) {
var lowerhref = aTags[i]
.href
.toLowerCase();
if (lowerhref.endsWith('.html'))
lowerhref = lowerhref.slice(0, -5);
if (url.startsWith(lowerhref)) {
aTags[i]
.classList
.add('active');
break;
}
}
}
});
</script>
<div class="flex flex-col prose">
<div class="w-full max-w-screen-xl mx-auto">
<!-- * * * * * * * * * * * * * -->
<!-- Start of GoJS sample code -->
<script src="https://cdn.jsdelivr.net/npm/gojs@3.1.0"></script>
<link rel="stylesheet" href="../assets/css/prism.css"/>
<script src="../assets/js/prism.js"></script>
<div id="allSampleContent" class="p-4 w-full">
<script id="code">
function init() {
myDiagram = new go.Diagram('myDiagramDiv', {
validCycle: go.CycleMode.NotDirected, // don't allow loops
// For this sample, automatically show the state of the diagram's model on the page
ModelChanged: e => {
if (e.isTransactionFinished) showModel();
},
'undoManager.isEnabled': true
});
const UnselectedBrush = 'transparent'; // item appearance, if not "selected"
const SelectedBrush = 'dodgerblue'; // item appearance, if "selected"
function isFieldSelected(item) {
return item.background !== UnselectedBrush;
}
function setFieldSelected(item, sel) {
if (sel) {
item.background = SelectedBrush;
} else {
item.background = UnselectedBrush;
}
}
function onFieldClick(e, item) {
var oldskips = item.diagram.skipsUndoManager;
item.diagram.skipsUndoManager = true;
if (e.control || e.meta) {
setFieldSelected(item, !isFieldSelected(item));
item.part.isSelected = item.panel.elements.any(isFieldSelected);
} else if (e.shift) {
// alternative policy: select all fields between this item and some other one??
if (!isFieldSelected(item)) setFieldSelected(item, true);
item.part.isSelected = true;
} else {
if (!isFieldSelected(item)) {
// deselect all sibling items
item.panel.elements.each(it => {
if (it !== item) setFieldSelected(it, false);
});
setFieldSelected(item, true);
}
item.part.isSelected = true;
}
item.diagram.skipsUndoManager = oldskips;
}
// This template is a Panel that is used to represent each item in a Panel.itemArray.
// The Panel is data bound to the item object.
var fieldTemplate =
new go.Panel('TableRow', { // this Panel is a row in the containing Table
background: UnselectedBrush, // so this port's background can be picked by the mouse
fromSpot: go.Spot.LeftRightSides, // links only go from the right side to the left side
toSpot: go.Spot.LeftRightSides,
// allow drawing links from or to this port:
fromLinkable: true,
toLinkable: true,
// select items -- the background indicates "selected" when not UnselectedBrush
click: onFieldClick
})
.bind('portId', 'name') // this Panel is a "port"
.add(
new go.Shape({
width: 12,
height: 12,
column: 0,
strokeWidth: 2,
margin: 4,
// but disallow drawing links from or to this shape:
fromLinkable: false,
toLinkable: false
})
.bind('figure')
.bind('fill', 'color'),
new go.TextBlock({
margin: new go.Margin(0, 2),
column: 1,
font: 'bold 13px sans-serif',
// and disallow drawing links from or to this text:
fromLinkable: false,
toLinkable: false
})
.bind('text', 'name'),
new go.TextBlock({ margin: new go.Margin(0, 2), column: 2, font: '13px sans-serif' })
.bind('text', 'info')
);
// This template represents a whole "record".
myDiagram.nodeTemplate =
new go.Node('Auto')
.bindTwoWay('location', 'loc', go.Point.parse, go.Point.stringify)
.add(
// this rectangular shape surrounds the content of the node
new go.Shape({ fill: '#EEEEEE' }),
// the content consists of a header and a list of items
new go.Panel('Vertical')
.add(
// this is the header for the whole node
new go.Panel('Auto', { stretch: go.Stretch.Horizontal }) // as wide as the whole node
.add(
new go.Shape({ fill: '#1570A6', stroke: null }),
new go.TextBlock({
alignment: go.Spot.Center,
margin: 3,
stroke: 'white',
textAlign: 'center',
font: 'bold 12pt sans-serif'
})
.bind('text', 'key')
),
// this Panel holds a Panel for each item object in the itemArray;
// each item Panel is defined by the itemTemplate to be a TableRow in this Table
new go.Panel('Table', {
name: 'TABLE',
padding: 2,
minSize: new go.Size(100, 10),
defaultStretch: go.Stretch.Horizontal,
itemTemplate: fieldTemplate
})
.bind('itemArray', 'fields')
) // end Vertical Panel
); // end Node
myDiagram.linkTemplate =
new go.Link({
relinkableFrom: true,
relinkableTo: true,
toShortLength: 4
}) // let user reconnect links
.add(
new go.Shape({ strokeWidth: 1.5 }),
new go.Shape({ toArrow: 'Standard', stroke: null })
);
myDiagram.model = new go.GraphLinksModel({
copiesArrays: true,
copiesArrayObjects: true,
linkFromPortIdProperty: 'fromPort',
linkToPortIdProperty: 'toPort',
nodeDataArray: [
{
key: 'Record1',
fields: [
{ name: 'field1', info: '', color: '#F7B84B', figure: 'Ellipse' },
{ name: 'field2', info: 'the second one', color: '#F25022', figure: 'Ellipse' },
{ name: 'fieldThree', info: '3rd', color: '#00BCF2' }
],
loc: '0 0'
},
{
key: 'Record2',
fields: [
{ name: 'fieldA', info: '', color: '#FFB900', figure: 'Diamond' },
{ name: 'fieldB', info: '', color: '#F25022', figure: 'Rectangle' },
{ name: 'fieldC', info: '', color: '#7FBA00', figure: 'Diamond' },
{ name: 'fieldD', info: 'fourth', color: '#00BCF2', figure: 'Rectangle' }
],
loc: '250 0'
}
],
linkDataArray: [
{ from: 'Record1', fromPort: 'field1', to: 'Record2', toPort: 'fieldA' },
{ from: 'Record1', fromPort: 'field2', to: 'Record2', toPort: 'fieldD' },
{ from: 'Record1', fromPort: 'fieldThree', to: 'Record2', toPort: 'fieldB' }
]
});
// this is a bit inefficient, but should be OK for normal-sized graphs with reasonable numbers of items per node
function findAllSelectedItems() {
var items = [];
for (var nit = myDiagram.nodes; nit.next(); ) {
var node = nit.value;
var table = node.findObject('TABLE');
if (table) {
for (var iit = table.elements; iit.next(); ) {
var itempanel = iit.value;
if (isFieldSelected(itempanel)) items.push(itempanel);
}
}
}
return items;
}
// Override the standard CommandHandler deleteSelection behavior.
// If there are any selected items, delete them instead of deleting any selected nodes or links.
myDiagram.commandHandler.canDeleteSelection = function () {
// method override must be function, not =>
// true if there are any selected deletable nodes or links,
// or if there are any selected items within nodes
return go.CommandHandler.prototype.canDeleteSelection.call(this) || findAllSelectedItems().length > 0;
};
myDiagram.commandHandler.deleteSelection = function () {
// method override must be function, not =>
var items = findAllSelectedItems();
if (items.length > 0) {
// if there are any selected items, delete them
myDiagram.startTransaction('delete items');
for (var i = 0; i < items.length; i++) {
var panel = items[i];
var nodedata = panel.part.data;
var itemarray = nodedata.fields;
var itemdata = panel.data;
var itemindex = itemarray.indexOf(itemdata);
myDiagram.model.removeArrayItem(itemarray, itemindex);
}
myDiagram.commitTransaction('delete items');
} else {
// otherwise just delete nodes and/or links, as usual
go.CommandHandler.prototype.deleteSelection.call(this);
}
};
showModel(); // show the diagram's initial model
function showModel() {
document.getElementById('mySavedModel').innerHTML = myDiagram.model.toJson();
if (window.Prism) window.Prism.highlightAll();
}
}
window.addEventListener('DOMContentLoaded', init);
</script>
<div id="sample">
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 300px"></div>
<p>This shows a variable number of selectable "fields" for each "record".</p>
<p>
To select multiple fields press <code>Ctrl + Click</code> or on mac
<code>Cmd + Click</code>. Draw new links by dragging from the background of
any field. Reconnect a selected link by dragging its diamond-shaped handle.
The user can delete a selected field.
</p>
<p>The model data, automatically updated after each change or undo or redo:</p>
<pre class="lang-js"><code id="mySavedModel"></code></pre>
<p>This sample was derived from the <a href="records.html">Records</a> sample.</p>
</div>
</div>
<!-- * * * * * * * * * * * * * -->
<!-- End of GoJS sample code -->
</div>
<div id="allTagDescriptions" class="p-4 w-full max-w-screen-xl mx-auto">
<hr/>
<h3 class="text-xl">GoJS Features in this sample</h3>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Table Panels</h4>
<p>
The "Table" Panel, <a href="../api/symbols/Panel.html#static-Table" target="api">Panel.Table</a>, arranges objects in rows and columns.
Each object in a Table Panel is put into the cell indexed by the value of <a href="../api/symbols/GraphObject.html#row" target="api">GraphObject.row</a> and <a href="../api/symbols/GraphObject.html#column" target="api">GraphObject.column</a>.
The panel will look at the rows and columns for all of the objects in the panel to determine how many rows and columns the table should have.
More information can be found in the <a href="../intro/tablePanels.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#tables">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Item Arrays</h4>
<p>
It is sometimes useful to display a variable number of elements in a node by data binding to a JavaScript Array.
In GoJS, this is simply achieved by binding (or setting) <a href="../api/symbols/Panel.html#itemArray" target="api">Panel.itemArray</a>.
The <a href="../api/symbols/Panel.html" target="api">Panel</a> will create an element in the panel for each value in the Array.
More information can be found in the <a href="../intro/itemArrays.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#itemarrays">Related samples</a>
</p>
<hr>
<!-- blacklist tags that do not correspond to a specific GoJS feature -->
<h4>Commands</h4>
<p>
A <a href="../api/symbols/CommandHandler.html" target="api">CommandHandler</a> handles all default keyboard input events in a Diagram.
There are many predefined methods on <a>CommandHandler</a> that implement common commands to operate on the Diagram or the current <a>Diagram.selection></a>.
</p>
<p>
You can override <a>CommandHandler.doKeyDown</a> to handle additional keyboard shortcuts or to change which commands are invoked via the keyboard.
<p>
Your code can invoke a command by calling the appropriate method on the <a>Diagram.commandHandler</a>.
Each command method has a corresponding <b>can...</b> predicate that your code can use to enable or disable any buttons that invoke the command.
Your code can customize the behavior of a command by overriding the method on <a>CommandHandler</a>,
or by setting properties on the <a>CommandHandler</a> or <a>Diagram</a> or <a>Part</a>s --
see <a href="../intro/permissions.html">GoJS Permissions</a>.
</p>
<p>
There are several CommandHandler extensions that provide additional functionality.
<p>
More information can be found in the <a href="../intro/commands.html">GoJS Intro</a>.
</p>
<p>
<a href="../samples/index.html#commands">Related samples</a>
</p>
<hr>
</div>
</div>
</body>
<!-- This script is part of the gojs.net website, and is not needed to run the sample -->
<script src="../assets/js/goSamples.js"></script>
</html>