From 822ac046e0d45eb9693fe2e0fef3e00264d712c4 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Tue, 27 Jan 2026 22:51:09 +0800 Subject: [PATCH] docs: update DOM widget value persistence best practices guide - Restructure document to clearly separate simple vs complex widget patterns - Add detailed explanation of ComfyUI's built-in callback mechanism - Provide complete implementation examples for both patterns - Remove outdated sync chain diagrams and replace with practical guidance - Emphasize using DOM element as source of truth for simple widgets - Document proper use of internal state with widget.callback for complex widgets --- .../value-persistence-best-practices.md | 301 +++++++++++------- 1 file changed, 191 insertions(+), 110 deletions(-) diff --git a/docs/dom-widgets/value-persistence-best-practices.md b/docs/dom-widgets/value-persistence-best-practices.md index 95e4a11e..219bb202 100644 --- a/docs/dom-widgets/value-persistence-best-practices.md +++ b/docs/dom-widgets/value-persistence-best-practices.md @@ -1,144 +1,225 @@ # DOM Widget Value Persistence - Best Practices -## Problem +## Overview -DOM widgets with text inputs failed to persist values after: -- Loading workflows -- Switching workflows -- Reloading pages +DOM widgets require different persistence patterns depending on their complexity. This document covers two patterns: -## Root Cause +1. **Simple Text Widgets**: DOM element as source of truth (e.g., textarea, input) +2. **Complex Widgets**: Internal value with `widget.callback` (e.g., LoraPoolWidget, RandomizerWidget) -**Multiple sources of truth** causing sync issues: -- Internal state variable (`internalValue` in main.ts) -- Vue reactive ref (`textValue` in component) -- DOM element value (actual textarea) -- ComfyUI widget value (`props.widget.value`) +## Understanding ComfyUI's Built-in Callback Mechanism -**Broken sync chains:** -``` -getValue() → internalValue (not actual DOM value) -setValue(v) → internalValue → onSetValue() → textValue.value (async chain) -serializeValue() → textValue.value (different from getValue) -watch() → another sync layer +When `widget.value` is set (e.g., during workflow load), ComfyUI's `domWidget.ts` triggers this flow: + +```typescript +// From ComfyUI_frontend/src/scripts/domWidget.ts:146-149 +set value(v: V) { + this.options.setValue?.(v) // 1. Update internal state + this.callback?.(this.value) // 2. Notify listeners for UI updates +} ``` -## Solution +This means: +- `setValue()` handles storing the value +- `widget.callback()` is automatically called to notify the UI +- You don't need custom callback mechanisms like `onSetValue` -Follow ComfyUI built-in `addMultilineWidget` pattern: +--- -### ✅ Do +## Pattern 1: Simple Text Input Widgets -1. **Single source of truth**: Use the DOM element directly - ```typescript - // main.ts - const widget = node.addDOMWidget(name, type, container, { - getValue() { - return widget.inputEl?.value ?? '' - }, - setValue(v: string) { - if (widget.inputEl) { - widget.inputEl.value = v ?? '' - } - } - }) - ``` +For widgets where the value IS the DOM element's text content (textarea, input fields). -2. **Register DOM reference** when component mounts - ```typescript - // Vue component - onMounted(() => { - if (textareaRef.value) { - props.widget.inputEl = textareaRef.value - } - }) - ``` +### When to Use -3. **Clean up reference** on unmount - ```typescript - onUnmounted(() => { - if (props.widget.inputEl === textareaRef.value) { - props.widget.inputEl = undefined - } - }) - ``` +- Single text input/textarea widgets +- Value is a simple string +- No complex state management needed -4. **Simplify interface** - only expose what's needed - ```typescript - export interface MyWidgetInterface { - inputEl?: HTMLTextAreaElement - callback?: (v: string) => void - } - ``` +### Implementation -### ❌ Don't +**main.ts:** +```typescript +const widget = node.addDOMWidget(name, type, container, { + getValue() { + return widget.inputEl?.value ?? '' + }, + setValue(v: string) { + if (widget.inputEl) { + widget.inputEl.value = v ?? '' + } + } +}) +``` -1. **Don't create internal state variables** - ```typescript - // Wrong - let internalValue = '' - getValue() { return internalValue } - ``` +**Vue Component:** +```typescript +onMounted(() => { + if (textareaRef.value) { + props.widget.inputEl = textareaRef.value + } +}) -2. **Don't use v-model** for text inputs in DOM widgets - ```html - -