+
);
}
@@ -154,26 +126,124 @@ function useForm( { id, idBase, instance, setInstance } ) {
return { html, setFormData };
}
-function serializeForm( form ) {
- return new window.URLSearchParams(
- Array.from( new window.FormData( form ) )
- ).toString();
-}
+function Control( { id, idBase, html, onChange, onSave } ) {
+ const controlRef = useRef();
+ const formRef = useRef();
-const ObservableForm = forwardRef( ( { onChange, ...props }, ref ) => {
- // React won't call the form's onChange handler because it doesn't know
- // about the s that we add using dangerouslySetInnerHTML. We work
- // around this by not using React's event system.
+ // Trigger 'widget-added' when widget is ready and 'widget-updated' when
+ // widget changes. This event is what widgets' scripts use to initialize,
+ // attach events, etc. The event must be fired using jQuery's event bus as
+ // this is what widget scripts expect. If jQuery is not loaded, do nothing -
+ // some widgets will still work regardless.
+ const hasBeenAdded = useRef( false );
+ useEffect( () => {
+ if ( ! window.jQuery ) {
+ return;
+ }
+
+ const { jQuery: $ } = window;
+
+ if ( html ) {
+ $( document ).trigger(
+ hasBeenAdded.current ? 'widget-updated' : 'widget-added',
+ [ $( controlRef.current ) ]
+ );
+ hasBeenAdded.current = true;
+ }
+ }, [
+ html,
+ // Include id and idBase in the deps so that widget-updated is triggered
+ // if they change.
+ id,
+ idBase,
+ ] );
+ // Prefer jQuery 'change' event instead of the native 'change' event because
+ // many widgets use jQuery's event bus to trigger an update.
useEffect( () => {
- const handler = () => onChange( ref.current );
- ref.current.addEventListener( 'change', handler );
- ref.current.addEventListener( 'input', handler );
- return () => {
- ref.current.removeEventListener( 'change', handler );
- ref.current.removeEventListener( 'input', handler );
- };
+ const handler = () => onChange( serializeForm( formRef.current ) );
+
+ if ( window.jQuery ) {
+ const { jQuery: $ } = window;
+ $( formRef.current ).on( 'change', null, handler );
+ return () => $( formRef.current ).off( 'change', null, handler );
+ }
+
+ formRef.current.addEventListener( 'change', handler );
+ return () => formRef.current.removeEventListener( 'change', handler );
}, [ onChange ] );
- return ;
-} );
+ // Non-multi widgets can be saved via a Save button.
+ const handleSubmit = ( event ) => {
+ event.preventDefault();
+ onSave( serializeForm( event.target ) );
+ };
+
+ // We can't use the real widget number as this is calculated by the server
+ // and we may not ever *actually* save this widget. Instead, use a fake but
+ // unique number.
+ const number = useInstanceId( Control );
+
+ return (
+