削除、編集、作成を維持する
グラフが永続レコードのビュー である場合、書き込みは楽観視できません。 3 before-hook は、ユーザーが開始したすべての書き込みに、消費者が決定する提案を作成します。 キャンバスとデータベースが一致しないことはありません。
onBeforeDelete— すべての削除アフォーダンス (サイドバーの一括行、 ノード / エッジ / ノート コンテキスト メニュー エントリ) ターゲット セット全体が解決されると、cascadingEdges: ノードを削除すると破壊される関係。falseを拒否権に戻すか、{ accept: true, nodes, edges }を戻して、 実際にサーバーは削除されました。ctx.confirm()は組み込みの確認モーダルです。onBeforeNodeCreate— 作成 ▸ ノードの追加 およびキャンバス メニューの ここにノードを追加、グラフ空間positionとpromptData()フォームを使用します。何 返された値がグラフに入ります。onBeforeEdgeEditCommit— エッジ編集がコミットされると起動します (エッジ編集を右クリックします) エッジ ▸ エッジを編集)。それを拒否すると、エッジはデータを保持します。
3 つすべてが非同期である可能性があり、1 つが保留されている間、2 つ目のジェスチャは無視されます。 プログラム的な graph.removeNode() / addNode() は決してゲートされないため、独自のコード 独自のフックによってブロックされません。
試してみましょう: ノードを選択し、サイドバーで 削除 を押します (published レコードが表示されます) 拒否されました)、エッジを右クリックして関係を変更するか、作成▸追加を使用します ノード。 editors.deletion.enabled: false を設定して削除アフォーダンスを削除します すべてのクリックを拒否するのではなく、完全に拒否します。
js
const options = {
// Label each attribute with its value, so an edit's effect is visible.
render: {
defaultNodeStyle: {
size: 16,
textColor: '#334155',
text: (node) => node.getData()?.label,
textVerticalShift: -1.6
}
},
UI: {
mode: 'full',
// Off: `full` mode brings a minimap and the data dock, and this card is about
// neither (see the Minimap and Data table cards for those).
minimap: false,
table: false,
// Give the edge editor a relationship dropdown instead of a free-text field.
editors: {
edgeEditor: {
fields: [{
key: 'label',
label: 'Relationship',
type: 'select',
options: RELATIONSHIPS.map((value) => ({ value, label: value }))
}]
}
}
},
callbacks: {
// Nothing leaves the model until the server agrees. The library resolves the
// whole target set first — including `cascadingEdges`, the relationships that
// removing a node would take with it — so you can persist the real consequence.
onBeforeDelete: async ({ nodes, edges, cascadingEdges, origin, confirm }) => {
const links = edges.length + cascadingEdges.length
const ok = await confirm({
title: 'Delete from event?',
body: `${nodes.length} attribute(s) and ${links} relationship(s) will be `
+ `deleted server-side. (asked from the ${origin})`,
confirmLabel: 'Delete'
})
if (!ok) return false
graph.notifier.info('Saving…', 'POST /events/delete')
await wait(700) // a backend call that could fail
// The server refused the published record: keep exactly what it deleted.
const refused = nodes.filter((node) => node.getData().published)
if (refused.length) {
graph.notifier.warning('Kept', `${refused[0].getData().label} is published — not deleted.`)
}
return { accept: true, nodes: nodes.filter((node) => !node.getData().published) }
},
// Create ▸ Add node, or the canvas menu's "Add Node Here". `promptData` opens a
// form; the id/data you return is what enters the graph.
onBeforeNodeCreate: async ({ position, promptData }) => {
const values = await promptData({
title: 'New attribute',
fields: [
{ key: 'label', label: 'Value', type: 'text' },
{
key: 'type', label: 'Type', type: 'select', defaultValue: 'ip-src',
options: ['ip-src', 'domain', 'md5', 'url'].map((v) => ({ value: v, label: v }))
}
]
})
if (!values) return false // cancelled
graph.notifier.info('Saving…', 'POST /attributes/add')
await wait(500)
graph.notifier.success('Created', `${values.label} at ${Math.round(position.x)},${Math.round(position.y)}`)
return { accept: true, id: `attr-${values.label}`, data: values }
},
// Right-click an edge ▸ Edit Edge. The commit is a proposal too: refuse it and
// the edge keeps its data, with the modal still open to correct.
onBeforeEdgeEditCommit: async ({ previousData, nextData }) => {
if (!RELATIONSHIPS.includes(nextData.label)) {
graph.notifier.warning('Rejected', `"${nextData.label}" is not a known relationship.`)
return false
}
graph.notifier.info('Saving…', 'PUT /object_references')
await wait(500)
graph.notifier.success('Updated', `${previousData.label} → ${nextData.label}`)
return true
}
}
}js
const data = {
nodes: [
{ id: 'ip-8888', data: { label: '8.8.8.8', type: 'ip-src' } },
{ id: 'dom-evil', data: { label: 'evil.example.com', type: 'domain' } },
{ id: 'md5', data: { label: 'd41d8cd9…f8427e', type: 'md5' } },
// The server refuses to delete this one — a published record.
{ id: 'file-obj', data: { label: 'invoice.doc', type: 'file', published: true } }
],
edges: [
{ id: 'r1', from: 'file-obj', to: 'md5', data: { label: 'contains' } },
{ id: 'r2', from: 'dom-evil', to: 'ip-8888', data: { label: 'resolves-to' } }
]
}