Hi
I want search input box on node.
- Based on text present in search input, I want to filter my scrolling table item array list
Node SS for sample:
Hi
I want search input box on node.
Node SS for sample:
The natural way to implement this is to customize a TextEditor so that as the user types, the filter used by the Panel.itemArray Binding conversion function to produce different Arrays is updated. Because the filter state presumably is not a property in the node data object, you’ll need to explicitly call Part.updateTargetBindings on the Node passing the name of the data source property, as if the Array had actually changed value.
Any sample code available for this?
This sample code is for a different goal, but it demonstrates how you can customize the standard TextEditor so that on each “input” event it filters a list. However, this sample code is different from what you want in that the list is in an Adornment and clicking on one of the items chooses it as the new TextBlock.text value. You’ll need to adapt this code so that there’s no Adornment because you already have a Table Panel in your Node.
<!DOCTYPE html>
<html>
<head>
<title>Simple Choices Selector Adornment</title>
<!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>
<body>
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
<textarea id="mySavedModel" style="width:100%;height:150px"></textarea>
<script src="https://cdn.jsdelivr.net/npm/gojs/release/go.js"></script>
<script src="https://cdn.jsdelivr.net/npm/create-gojs-kit/dist/extensions/ScrollingTable.js"></script>
<script id="code">
// This is copied and adapted from extensions/TextEditor.js
// in order to implement filtering in the ChoicesAdornment in this sample.
// See the additional code in the textarea's "input" listener.
/*
* Copyright (C) 1998-2024 by Northwoods Software Corporation. All Rights Reserved.
*/
// This is the definitions of the predefined text editor used by TextEditingTool
// when you set or bind TextBlock.editable to true.
// You do not need to load this file in order to use in-place text editing.
// HTML + JavaScript text editor menu, made with HTMLInfo
// This is a re-implementation of the default text editor
// This file exposes one instance of HTMLInfo, window.TextEditor
// Typical usage is:
// ```js
// new go.Diagram(...,
// {
// 'textEditingTool.defaultTextEditor': window.TextEditor,
// . . .
// })
// ```
// or:
// ```js
// myDiagram.toolManager.textEditingTool.defaultTextEditor = window.TextEditor;
// ```
// ```js
// $(go.Node, . . .,
// . . .
// $(go.TextBlock, { textEditor: window.TextEditor, . . . })
// . . .
// )
// ```
// If you do use this code, copy it into your project and modify it there.
// See also TextEditor.html
((window) => {
const TextEditor = new go.HTMLInfo();
const textarea = document.createElement('textarea');
textarea.id = 'myTextArea';
textarea.addEventListener('input', (e) => {
const tool = TextEditor.tool;
if (tool.textBlock === null)
return;
const tempText = tool.measureTemporaryTextBlock(textarea.value);
const scale = textarea.textScale;
textarea.style.width =
20 +
Math.max(tool.textBlock.measuredBounds.width, tempText.measuredBounds.width) * scale +
'px';
textarea.rows = Math.max(tool.textBlock.lineCount, tempText.lineCount);
// filter the choices list held in the "TABLE" panel of the Adornment
const ad = tool.textBlock.part;
const table = ad.findObject("TABLE");
if (!table) return;
const filter = textarea.value.toLowerCase();
table.elements.each(itempanel => {
const itemtext = itempanel.elt(0).text;
itempanel.visible = itemtext.toLowerCase().includes(filter);
});
const scroller = ad.findObject("SCROLLER");
if (scroller && scroller._updateScrollBar) scroller._updateScrollBar(table);
}, false);
textarea.addEventListener('keydown', (e) => {
if (e.isComposing)
return;
const tool = TextEditor.tool;
if (tool.textBlock === null)
return;
const code = e.code;
if (code === 'Enter') {
// Enter
if (tool.textBlock.isMultiline === false)
e.preventDefault();
tool.acceptText(go.TextEditingAccept.Enter);
}
else if (code === 'Tab') {
// Tab
tool.acceptText(go.TextEditingAccept.Tab);
e.preventDefault();
}
else if (code === 'Escape') {
// Esc
tool.doCancel();
if (tool.diagram !== null)
tool.diagram.doFocus();
}
}, false);
// handle focus:
textarea.addEventListener('focus', (e) => {
const tool = TextEditor.tool;
if (!tool || tool.currentTextEditor === null || tool.state === go.TextEditingState.None)
return;
if (tool.state === go.TextEditingState.Active) {
tool.state = go.TextEditingState.Editing;
}
if (tool.selectsTextOnActivate) {
textarea.select();
textarea.setSelectionRange(0, 9999);
}
}, false);
// Disallow blur.
// If the textEditingTool blurs and the text is not valid,
// we do not want focus taken off the element just because a user clicked elsewhere.
textarea.addEventListener('blur', (e) => {
const tool = TextEditor.tool;
if (!tool || tool.currentTextEditor === null || tool.state === go.TextEditingState.None)
return;
textarea.focus();
if (tool.selectsTextOnActivate) {
textarea.select();
textarea.setSelectionRange(0, 9999);
}
}, false);
TextEditor.valueFunction = () => textarea.value;
TextEditor.mainElement = textarea; // to reference it more easily
TextEditor.tool = null; // Initialize
// used to be in doActivate
TextEditor.show = (textBlock, diagram, tool) => {
if (!diagram || !diagram.div)
return;
if (!(textBlock instanceof go.TextBlock))
return;
if (TextEditor.tool !== null)
return; // Only one at a time.
TextEditor.tool = tool; // remember the TextEditingTool for use by listeners
// This is called during validation, if validation failed:
if (tool.state === go.TextEditingState.Invalid) {
textarea.style.border = '3px solid red';
textarea.focus();
return;
}
// This part is called during initalization:
const loc = textBlock.getDocumentPoint(go.Spot.Center);
const pos = diagram.position;
const sc = diagram.scale;
let textscale = textBlock.getDocumentScale() * sc;
if (textscale < tool.minimumEditorScale)
textscale = tool.minimumEditorScale;
// Add slightly more width/height to stop scrollbars and line wrapping on some browsers
// +6 is firefox minimum, otherwise lines will be wrapped improperly
const textwidth = textBlock.naturalBounds.width * textscale + 6;
const textheight = textBlock.naturalBounds.height * textscale + 2;
const left = (loc.x - pos.x) * sc;
const yCenter = (loc.y - pos.y) * sc; // this is actually the center, used to set style.top
const valign = textBlock.verticalAlignment;
const oneLineHeight = textBlock.lineHeight + textBlock.spacingAbove + textBlock.spacingBelow;
const allLinesHeight = oneLineHeight * textBlock.lineCount * textscale;
const center = 0.5 * textheight - 0.5 * allLinesHeight;
// add offset to yCenter to get the appropriate position:
const yOffset = valign.y * textheight -
valign.y * allLinesHeight +
valign.offsetY -
center -
allLinesHeight / 2;
textarea.value = textBlock.text;
// the only way you can mix font and fontSize is if the font inherits and the fontSize overrides
// in the future maybe have textarea contained in its own div
diagram.div.style['font'] = textBlock.font;
const paddingsize = 1;
textarea.style['position'] = 'absolute';
textarea.style['zIndex'] = '100';
textarea.style['font'] = 'inherit';
textarea.style['fontSize'] = textscale * 100 + '%';
textarea.style['lineHeight'] = 'normal';
textarea.style['width'] = textwidth + 'px';
textarea.style['left'] = ((left - textwidth / 2) | 0) - paddingsize + 'px';
textarea.style['top'] = ((yCenter + yOffset) | 0) - paddingsize + 'px';
textarea.style['textAlign'] = textBlock.textAlign;
textarea.style['margin'] = '0';
textarea.style['padding'] = paddingsize + 'px';
textarea.style['border'] = '0';
textarea.style['outline'] = 'none';
textarea.style['whiteSpace'] = 'pre-wrap';
textarea.style['overflow'] = 'hidden'; // for proper IE wrap
textarea.rows = textBlock.lineCount;
textarea.textScale = textscale; // attach a value to the textarea, for convenience
textarea.className = 'goTXarea';
// Show:
diagram.div.appendChild(textarea);
// After adding, focus:
textarea.focus();
if (tool.selectsTextOnActivate) {
textarea.select();
textarea.setSelectionRange(0, 9999);
}
};
TextEditor.hide = (diagram, tool) => {
TextEditor.tool = null; // forget reference to TextEditingTool
if (diagram.div)
diagram.div.removeChild(textarea);
};
window.TextEditor = TextEditor;
})(window);
const myDiagram =
new go.Diagram("myDiagramDiv", {
"BackgroundSingleClicked": e => {
if (ChoicesAdornment.diagram &&
ChoicesAdornment.actualBounds.containsPoint(e.diagram.lastInput.documentPoint)) return;
showChoices(null)
},
"textEditingTool.starting": go.TextEditingStarting.SingleClick,
"undoManager.isEnabled": true,
"ModelChanged": e => { // just for demonstration purposes,
if (e.isTransactionFinished) { // show the model data in the page's TextArea
document.getElementById("mySavedModel").textContent = e.model.toJson();
}
}
});
// the Adornment holding the list of choices
const ChoicesAdornment =
new go.Adornment("Spot")
.add(
new go.Placeholder(), // placeholder for the TextBlock in the Node
new go.Panel("Auto", {
alignment: go.Spot.BottomLeft,
alignmentFocus: go.Spot.TopLeft,
stretch: go.Stretch.Horizontal
})
.add(
// border around whole filter + list
new go.Shape({ fill: null, stroke: "lightgray", strokeWidth: 4 }),
new go.Panel("Vertical", {
stretch: go.Stretch.Horizontal,
defaultStretch: go.Stretch.Horizontal
})
.add(
new go.Panel("Auto")
.add(
new go.Shape({ fill: "lightyellow" }),
new go.TextBlock("filter", {
name: "FILTER",
stretch: go.Stretch.Horizontal,
margin: new go.Margin(2, 2, 1, 2),
font: "italic 10pt sans-serif",
isMultiline: false,
editable: true,
textEditor: window.TextEditor
})
),
new go.Panel("Auto")
.add(
new go.Shape({ fill: "white", stroke: "gray", strokeWidth: 2 }),
go.GraphObject.build("ScrollingTable", {
name: "SCROLLER",
maxSize: new go.Size(NaN, 65),
stretch: go.Stretch.Fill,
"TABLE.rowSizing": go.Sizing.None,
"TABLE.itemTemplate":
new go.Panel("TableRow", {
isActionable: true, // to allow a click event in an Adornment
click: (e, item) => {
if (e.diagram.currentTool instanceof go.TextEditingTool) {
e.diagram.currentTool.stopTool();
}
const tb = item.part.adornedPart.findObject("TB");
if (!tb) return;
e.diagram.commit(diag => {
tb.text = item.data;
showChoices(null);
});
},
// for mouse-over highlighting
background: "transparent",
mouseEnter: (e, item) => item.background = "cyan",
mouseLeave: (e, item) => item.background = "transparent"
})
.add(
new go.TextBlock({ margin: 1 })
.bind("text", "") // TextBlock.text gets the whole Array item value
)
})
.bind("TABLE.itemArray", "choices")
.bind("SCROLLBAR.visible", "choices", arr => arr.length > 6)
)
)
)
);
function showChoices(node) {
const ad = ChoicesAdornment;
const currentnode = ad.adornedPart;
// if ChoicesAdornment is shown, when no node or when toggling visibility on currentnode,
// remove the ChoicesAdornment
if (currentnode && (!node || currentnode === node)) {
const oldshp = currentnode.findObject("SHP");
if (oldshp) oldshp.figure = "LineDown";
currentnode.removeAdornment("Choices");
ad.adornedObject = null;
if (currentnode === node) return;
ad._key = undefined;
}
if (!node) return;
// maybe showing on a different node, so any filter no longer applies
if (ad._key !== node.key) {
const filter = ad.findObject("FILTER");
if (filter) filter.text = "";
const choices = ad.findObject("TABLE");
if (choices) {
choices.itemArray = [];
choices.topIndex = 0;
}
}
const tb = node.findObject("TB");
const shp = node.findObject("SHP");
if (!tb || !shp) return;
shp.figure = "LineUp";
ad.adornedObject = tb;
node.addAdornment("Choices", ad);
// if there's no "choices" data Array, use a default one
if (!Array.isArray(node.data.choices) || node.data.choices.length === 0) {
ad.data = { choices: ["Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta", "Eta", "Theta"] }; // default choices Array
}
// remember which node it last showed for
ad._key = node.key;
}
myDiagram.nodeTemplate =
new go.Node("Auto")
.add(
new go.Shape({ fill: "white" }),
new go.Panel("Vertical")
.add(
new go.TextBlock({ margin: 4, font: "bold 12pt sans-serif" })
.bind("text", "title"),
new go.Panel("Horizontal", {
click: (e, pnl) => { // show or hide the ChoicesAdornment
e.handled = true;
showChoices(pnl.part);
},
// for mouse-over highlighting
background: "transparent",
mouseEnter: (e, pnl) => pnl.background = "lightgray",
mouseLeave: (e, pnl) => pnl.background = "transparent"
})
.add(
new go.TextBlock("(choose)", {
name: "TB",
width: 100,
margin: new go.Margin(4, 4, 2, 4),
font: "italic 10pt sans-serif",
stroke: "blue"
})
.bindTwoWay("text", "value")
.bind("font", "value", v => v ? "bold 10pt sans-serif" : "italic 10pt sans-serif")
.bind("stroke", "value", v => v ? "black" : "blue"),
new go.Shape("LineDown", {
name: "SHP",
width: 14, height: 12,
margin: 2,
strokeWidth: 2,
})
)
)
);
myDiagram.model = new go.GraphLinksModel(
[
{ key: 1, title: "Alpha", choices: ["one", "two", "three", "four", "five", "six", "seven"], value: "one" },
{ key: 2, title: "Beta", choices: ["hello", "goodbye"] },
{ key: 3, title: "Gamma", choices: ["only one"] },
{ key: 4, title: "Delta" }, // use a default choices Array
]);
</script>
</body>
</html>
I am using above sample code in Angular component as well as imported above mention library.
still I am getting below exception:
Code:
const TextEditor = new go.HTMLInfo();
const tool = TextEditor.tool;
Exception:
TS2339: Property tool does not exist on type HTMLInfo.
It’s added by this code – just cast it there as any
.
Hi walter,
any is working for me,
But now I am facing issue to implement window.TextEditor inside ChoicesAdornment for typescript file.
Why not use the source file, extensionsJSM/TextEditor.ts
?
Hi
I have used Search Box Sample as mentioned above its nearly work for me, but my node UI is not looking good.
Expected Node UI:
Node UI after use Search Box Sample:
node Template I am using:
var SSDnodeTemplate =$(go.Node,
“Auto”,
{
alignment: go.Spot.Center,
},)
.add(
$(go.Panel, “Table”,
{
stretch: go.GraphObject.Fill,
defaultRowSeparatorStroke: go.Brush.lighten(“grey”),
columnSpan: 2,
},
$(go.RowColumnDefinition, //changes for BugID: INT-574
{
row: 1,
isRow: true,
background: “#fef5d9”,
stretch: go.GraphObject.Fill,
},),
$(go.RowColumnDefinition, {row: 0, sizing: go.RowColumnDefinition.None}),
$(go.Shape, "RoundedRectangle", {
name: "Icon",
row:0,
columnSpan: 2,
stretch: go.GraphObject.Fill,
alignment: go.Spot.Center,
fromLinkableSelfNode: true,
toLinkableSelfNode: true,
strokeWidth: 0,
},
),
$(go.TextBlock,
{
name: 'myTextBlock',
//row: 0,
columnSpan: 2,
margin: 14,
textAlign: "center",
isMultiline: false,
editable: false,
height: 15,
font: "15px sans-serif",
},
new go.Binding("text", "text").makeTwoWay(),
),
$(go.TextBlock, {
row: 0,
column: 0,
margin: 1,
textAlign: "left",
alignment: go.Spot.TopLeft,
font: "8pt Sans-Serif",
},
new go.Binding("text", "isSelected",
(sel, shp) => shp.part.data.nodedetails.type == "" || shp.part.data.nodedetails.type == "N/a" ? shp.part.data.category : shp.part.data.nodedetails.type).ofObject(),
),
$(go.TextBlock, {
row: 0,
column: 1,
margin: 2,
textAlign: "right",
alignment: go.Spot.TopRight,
font: "8pt Sans-Serif",
},
new go.Binding("text", "parentnode").makeTwoWay(),
),
$(go.TextBlock, {
row: 0,
column: 1,
alignment: go.Spot.BottomRight,
font: "9pt Sans-Serif",
},
new go.Binding("text", "nodedetails", nd => nd.extension)//File Extention
),
$(go.TextBlock, {
row: 0,
column: 0,
margin: 2,
alignment: go.Spot.BottomLeft,
font: "8pt Sans-Serif",
},
new go.Binding("text", "nodedetails", nd => nd.reference).makeTwoWay(),
),
$("PanelExpanderButton", "",
{
row: 0,
column: 1,
alignment: go.Spot.TopRight,
//click: FillBrList,
click: (e, pnl) => { // show or hide the ChoicesAdornment
e.handled = true;
showChoices(pnl.part);
},
},
new go.Binding("visible", "isrulepresent", setBooleanvalue).makeTwoWay(),
new go.TextBlock({
name: "TB",
row:0,
width: 0.1,
columnSpan: 2,
margin: new go.Margin(1, 1, 1, 1),
font: "italic 10pt sans-serif",
stroke: "blue"
}),
new go.Shape("LineDown", {
name: "SHP",
row:0,
width: 0, height: 0,
strokeWidth: 2,
})
) ,
)
)
As I said with the code above, the list is in an Adornment, whereas you want the list to be in your Node.
So, is it not possible to show list in node with search feature.
Of course it is. Just define your Node template with both the filter box and the “Table” Panel list of elements. Remember that the code is referring to an Adornment, not a Node, so you’ll need to remove the indirection through Adornment.adornedPart or adornedObject.
Do you have sample code for this.
I gave you such a sample, above, where the functionality was in an Adornment instead of in a Node. If you want us to do that work for you, you’ll have to wait until I have enough free time.
ok will waiting for your response.
<!DOCTYPE html>
<html>
<head>
<title>Filter</title>
<!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>
<body>
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
<textarea id="mySavedModel" style="width:100%;height:150px"></textarea>
<script src="https://cdn.jsdelivr.net/npm/gojs/release/go.js"></script>
<script src="https://cdn.jsdelivr.net/npm/create-gojs-kit/dist/extensions/ScrollingTable.js"></script>
<script id="code">
// This is copied and adapted from extensions/TextEditor.js
// in order to implement filtering in the node in this sample.
// See the additional code in the textarea's "input" listener.
/*
* Copyright (C) 1998-2024 by Northwoods Software Corporation. All Rights Reserved.
*/
// This is the definitions of the predefined text editor used by TextEditingTool
// when you set or bind TextBlock.editable to true.
// You do not need to load this file in order to use in-place text editing.
// HTML + JavaScript text editor menu, made with HTMLInfo
// This is a re-implementation of the default text editor
// This file exposes one instance of HTMLInfo, window.TextEditor
// Typical usage is:
// ```js
// new go.Diagram(...,
// {
// 'textEditingTool.defaultTextEditor': window.TextEditor,
// . . .
// })
// ```
// or:
// ```js
// myDiagram.toolManager.textEditingTool.defaultTextEditor = window.TextEditor;
// ```
// ```js
// $(go.Node, . . .,
// . . .
// $(go.TextBlock, { textEditor: window.TextEditor, . . . })
// . . .
// )
// ```
// If you do use this code, copy it into your project and modify it there.
// See also TextEditor.html
((window) => {
const TextEditor = new go.HTMLInfo();
const textarea = document.createElement('textarea');
textarea.id = 'myTextArea';
textarea.addEventListener('input', (e) => {
const tool = TextEditor.tool;
if (tool.textBlock === null)
return;
const tempText = tool.measureTemporaryTextBlock(textarea.value);
const scale = textarea.textScale;
textarea.style.width =
20 +
Math.max(tool.textBlock.measuredBounds.width, tempText.measuredBounds.width) * scale +
'px';
textarea.rows = Math.max(tool.textBlock.lineCount, tempText.lineCount);
// filter the choices list held in the "TABLE" panel
const ad = tool.textBlock.part;
const table = ad.findObject("TABLE");
if (!table) return;
const filter = textarea.value.toLowerCase();
table.elements.each(itempanel => {
const itemtext = itempanel.elt(0).text;
itempanel.visible = itemtext.toLowerCase().includes(filter);
});
const scroller = ad.findObject("SCROLLER");
if (scroller && scroller._updateScrollBar) scroller._updateScrollBar(table);
}, false);
textarea.addEventListener('keydown', (e) => {
if (e.isComposing)
return;
const tool = TextEditor.tool;
if (tool.textBlock === null)
return;
const code = e.code;
if (code === 'Enter') {
// Enter
if (tool.textBlock.isMultiline === false)
e.preventDefault();
tool.acceptText(go.TextEditingAccept.Enter);
}
else if (code === 'Tab') {
// Tab
tool.acceptText(go.TextEditingAccept.Tab);
e.preventDefault();
}
else if (code === 'Escape') {
// Esc
tool.doCancel();
if (tool.diagram !== null)
tool.diagram.doFocus();
}
}, false);
// handle focus:
textarea.addEventListener('focus', (e) => {
const tool = TextEditor.tool;
if (!tool || tool.currentTextEditor === null || tool.state === go.TextEditingState.None)
return;
if (tool.state === go.TextEditingState.Active) {
tool.state = go.TextEditingState.Editing;
}
if (tool.selectsTextOnActivate) {
textarea.select();
textarea.setSelectionRange(0, 9999);
}
}, false);
// Disallow blur.
// If the textEditingTool blurs and the text is not valid,
// we do not want focus taken off the element just because a user clicked elsewhere.
textarea.addEventListener('blur', (e) => {
const tool = TextEditor.tool;
if (!tool || tool.currentTextEditor === null || tool.state === go.TextEditingState.None)
return;
textarea.focus();
if (tool.selectsTextOnActivate) {
textarea.select();
textarea.setSelectionRange(0, 9999);
}
}, false);
TextEditor.valueFunction = () => textarea.value;
TextEditor.mainElement = textarea; // to reference it more easily
TextEditor.tool = null; // Initialize
// used to be in doActivate
TextEditor.show = (textBlock, diagram, tool) => {
if (!diagram || !diagram.div)
return;
if (!(textBlock instanceof go.TextBlock))
return;
if (TextEditor.tool !== null)
return; // Only one at a time.
TextEditor.tool = tool; // remember the TextEditingTool for use by listeners
// This is called during validation, if validation failed:
if (tool.state === go.TextEditingState.Invalid) {
textarea.style.border = '3px solid red';
textarea.focus();
return;
}
// This part is called during initalization:
const loc = textBlock.getDocumentPoint(go.Spot.Center);
const pos = diagram.position;
const sc = diagram.scale;
let textscale = textBlock.getDocumentScale() * sc;
if (textscale < tool.minimumEditorScale)
textscale = tool.minimumEditorScale;
// Add slightly more width/height to stop scrollbars and line wrapping on some browsers
// +6 is firefox minimum, otherwise lines will be wrapped improperly
const textwidth = textBlock.naturalBounds.width * textscale + 6;
const textheight = textBlock.naturalBounds.height * textscale + 2;
const left = (loc.x - pos.x) * sc;
const yCenter = (loc.y - pos.y) * sc; // this is actually the center, used to set style.top
const valign = textBlock.verticalAlignment;
const oneLineHeight = textBlock.lineHeight + textBlock.spacingAbove + textBlock.spacingBelow;
const allLinesHeight = oneLineHeight * textBlock.lineCount * textscale;
const center = 0.5 * textheight - 0.5 * allLinesHeight;
// add offset to yCenter to get the appropriate position:
const yOffset = valign.y * textheight -
valign.y * allLinesHeight +
valign.offsetY -
center -
allLinesHeight / 2;
textarea.value = textBlock.text;
// the only way you can mix font and fontSize is if the font inherits and the fontSize overrides
// in the future maybe have textarea contained in its own div
diagram.div.style['font'] = textBlock.font;
const paddingsize = 1;
textarea.style['position'] = 'absolute';
textarea.style['zIndex'] = '100';
textarea.style['font'] = 'inherit';
textarea.style['fontSize'] = textscale * 100 + '%';
textarea.style['lineHeight'] = 'normal';
textarea.style['width'] = textwidth + 'px';
textarea.style['left'] = ((left - textwidth / 2) | 0) - paddingsize + 'px';
textarea.style['top'] = ((yCenter + yOffset) | 0) - paddingsize + 'px';
textarea.style['textAlign'] = textBlock.textAlign;
textarea.style['margin'] = '0';
textarea.style['padding'] = paddingsize + 'px';
textarea.style['border'] = '0';
textarea.style['outline'] = 'none';
textarea.style['whiteSpace'] = 'pre-wrap';
textarea.style['overflow'] = 'hidden'; // for proper IE wrap
textarea.rows = textBlock.lineCount;
textarea.textScale = textscale; // attach a value to the textarea, for convenience
textarea.className = 'goTXarea';
// Show:
diagram.div.appendChild(textarea);
// After adding, focus:
textarea.focus();
if (tool.selectsTextOnActivate) {
textarea.select();
textarea.setSelectionRange(0, 9999);
}
};
TextEditor.hide = (diagram, tool) => {
TextEditor.tool = null; // forget reference to TextEditingTool
if (diagram.div)
diagram.div.removeChild(textarea);
};
window.TextEditor = TextEditor;
})(window);
const myDiagram =
new go.Diagram("myDiagramDiv", {
LayoutCompleted: (e) => {
// update all of the scrollbars
e.diagram.nodes.each((n) => {
var table = n.findObject('TABLE');
if (table !== null && table.panel._updateScrollBar) table.panel._updateScrollBar(table);
});
},
"textEditingTool.starting": go.TextEditingStarting.SingleClick,
"undoManager.isEnabled": true,
"ModelChanged": e => { // just for demonstration purposes,
if (e.isTransactionFinished) { // show the model data in the page's TextArea
document.getElementById("mySavedModel").textContent = e.model.toJson();
}
}
});
myDiagram.nodeTemplate =
new go.Node("Auto", { width: 100, height: 100, resizable: true })
.add(
new go.Shape({ fill: "goldenrod" }),
new go.Panel("Table", {
stretch: go.Stretch.Fill, alignment: go.Spot.Top, margin: 0.5
})
.add(
new go.TextBlock({ row: 0, font: "bold 12pt sans-serif", margin: new go.Margin(4, 4, 2, 4) })
.bind("text", "title"),
new go.Panel({ row: 1, stretch: go.Stretch.Horizontal, background: "lightyellow" })
.add(
new go.TextBlock("filter", {
name: "FILTER",
stretch: go.Stretch.Horizontal,
margin: new go.Margin(2, 2, 0, 2),
font: "italic 10pt sans-serif",
stroke: "gray",
isMultiline: false,
editable: true,
textEditor: window.TextEditor
})
),
new go.Panel("Auto", { row: 2, stretch: go.Stretch.Fill })
.add(
new go.Shape({ fill: "white", strokeWidth: 0 }),
go.GraphObject.build("ScrollingTable", {
name: "SCROLLER",
stretch: go.Stretch.Fill,
"TABLE.rowSizing": go.Sizing.None,
"TABLE.itemTemplate":
new go.Panel("TableRow")
.add(
new go.TextBlock({ margin: 1, stretch: go.Stretch.Horizontal })
.bind("text", "") // TextBlock.text gets the whole Array item value
)
})
.bind("TABLE.itemArray", "choices")
)
)
);
myDiagram.model = new go.GraphLinksModel(
[
{ key: 1, title: "Alpha", choices: ["one", "two", "three", "four", "five", "six", "seven"], value: "one" },
{ key: 2, title: "Beta", choices: ["hello", "goodbye"] },
{ key: 3, title: "Gamma", choices: ["only one"] },
{ key: 4, title: "Delta", choices: ["Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta", "Eta", "Theta"] }, // use a default choices Array
]);
</script>
</body>
</html>