Skip to content

Commit a759b47

Browse files
fcollonvalbrichet
andauthored
Improve form renderer registry (jupyterlab#13823)
* Use `pluginId.propertyName` as key for form renderers Rename to use _renderer_ instead of _component_ as it is a more common wording in Lumino/Lab code base. * Update migration guide * Fix computing default for metadataform * Fix rst syntax * Remove `settings` from `FormContext` Co-authored-by: Nicolas Brichet <[email protected]> * Rename `customComponent` in `customRenderer` * Drop `MetadataForm.ISettings` Co-authored-by: Nicolas Brichet <[email protected]>
1 parent 1654861 commit a759b47

File tree

23 files changed

+587
-619
lines changed

23 files changed

+587
-619
lines changed

docs/source/extension/extension_migration.rst

+9-6
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,17 @@ bumped their major version (following semver convention). We want to point out p
176176
need to switch to the Blueprint components as the interfaces of those components in JupyterLab
177177
do not match those of Blueprint JS.
178178
* Remove ``Collapse`` React component.
179-
* Change the ``IFormComponentRegistry`` token, from ``@jupyterlab/ui-components:ISettingEditorRegistry`` to
180-
``@jupyterlab/ui-components:IFormComponentRegistry``.
181-
* The ``FormComponentRegistry`` registers ``FormComponent`` instead of field renderers.
182-
The methods have been renamed from ``...renderer`` to ``...component``. For example, ``addRenderer`` is now ``addComponent``.
183-
A ``FormComponent`` defines a ``fieldRenderer`` or a ``widgetRenderer``.
179+
* Form component registry changes:
180+
- Rename the plugin ``'@jupyterlab/ui-components-extension:form-component-registry'`` to ``'@jupyterlab/ui-components-extension:form-renderer-registry'``
181+
- Rename the ``IFormComponentRegistry`` token to ``IFormRendererRegistry``, from ``@jupyterlab/ui-components:ISettingEditorRegistry``
182+
to ``@jupyterlab/ui-components:IFormRendererRegistry``.
183+
- The ``FormRendererRegistry`` registers ``IFormRenderer`` instead of ``Field`` renderers.
184+
A ``IFormRenderer`` defines a ``fieldRenderer`` (this is the renderer to set for backward compatibility)
185+
or a ``widgetRenderer``.
186+
The renderer id must follow the convention ``<ISettingRegistry.IPlugin.id>.<propertyName>``. This is to
187+
ensure a custom renderer is not used for property with the same name but different schema.
184188
- ``@jupyterlab/translation`` from 3.x to 4.x
185189
Renamed the method ``locale`` into the property ``languageCode`` in the ``NullTranslator``
186-
187190
- ``jupyter.extensions.hub-extension`` from 3.x to 4.x
188191
* Renamed ``jupyter.extensions.hub-extension`` to ``@jupyterlab/hub-extension:plugin``.
189192
* Renamed ``jupyter.extensions.hub-extension:plugin`` to ``@jupyterlab/hub-extension:menu``.

packages/completer-extension/src/index.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import {
1717
} from '@jupyterlab/completer';
1818
import { ISettingRegistry } from '@jupyterlab/settingregistry';
1919
import {
20-
IFormComponent,
21-
IFormComponentRegistry
20+
IFormRenderer,
21+
IFormRendererRegistry
2222
} from '@jupyterlab/ui-components';
2323
import type { FieldProps } from '@rjsf/core';
2424

@@ -42,13 +42,13 @@ const defaultProvider: JupyterFrontEndPlugin<void> = {
4242
const manager: JupyterFrontEndPlugin<ICompletionProviderManager> = {
4343
id: COMPLETION_MANAGER_PLUGIN,
4444
requires: [ISettingRegistry],
45-
optional: [IFormComponentRegistry],
45+
optional: [IFormRendererRegistry],
4646
provides: ICompletionProviderManager,
4747
autoStart: true,
4848
activate: (
4949
app: JupyterFrontEnd,
5050
settings: ISettingRegistry,
51-
editorRegistry: IFormComponentRegistry | null
51+
editorRegistry: IFormRendererRegistry | null
5252
): ICompletionProviderManager => {
5353
const AVAILABLE_PROVIDERS = 'availableProviders';
5454
const PROVIDER_TIMEOUT = 'providerTimeout';
@@ -101,12 +101,15 @@ const manager: JupyterFrontEndPlugin<ICompletionProviderManager> = {
101101
.catch(console.error);
102102

103103
if (editorRegistry) {
104-
const component: IFormComponent = {
104+
const renderer: IFormRenderer = {
105105
fieldRenderer: (props: FieldProps) => {
106106
return renderAvailableProviders(props);
107107
}
108108
};
109-
editorRegistry.addComponent('availableProviders', component);
109+
editorRegistry.addRenderer(
110+
`${COMPLETION_MANAGER_PLUGIN}.availableProviders`,
111+
renderer
112+
);
110113
}
111114

112115
return manager;

packages/lsp-extension/src/index.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ import { IRunningSessionManagers, IRunningSessions } from '@jupyterlab/running';
2727
import { ISettingRegistry } from '@jupyterlab/settingregistry';
2828
import { ITranslator } from '@jupyterlab/translation';
2929
import {
30-
IFormComponent,
31-
IFormComponentRegistry,
30+
IFormRenderer,
31+
IFormRendererRegistry,
3232
LabIcon,
3333
pythonIcon
3434
} from '@jupyterlab/ui-components';
@@ -42,7 +42,7 @@ const plugin: JupyterFrontEndPlugin<ILSPDocumentConnectionManager> = {
4242
activate,
4343
id: '@jupyterlab/lsp-extension:plugin',
4444
requires: [ISettingRegistry, ITranslator],
45-
optional: [IRunningSessionManagers, IFormComponentRegistry],
45+
optional: [IRunningSessionManagers, IFormRendererRegistry],
4646
provides: ILSPDocumentConnectionManager,
4747
autoStart: true
4848
};
@@ -88,7 +88,7 @@ function activate(
8888
settingRegistry: ISettingRegistry,
8989
translator: ITranslator,
9090
runningSessionManagers: IRunningSessionManagers | null,
91-
settingRendererRegistry: IFormComponentRegistry | null
91+
settingRendererRegistry: IFormRendererRegistry | null
9292
): ILSPDocumentConnectionManager {
9393
const LANGUAGE_SERVERS = 'languageServers';
9494
const languageServerManager = new LanguageServerManager({});
@@ -184,12 +184,15 @@ function activate(
184184
}
185185

186186
if (settingRendererRegistry) {
187-
const component: IFormComponent = {
187+
const renderer: IFormRenderer = {
188188
fieldRenderer: (props: FieldProps) => {
189189
return renderServerSetting(props, translator);
190190
}
191191
};
192-
settingRendererRegistry.addComponent(LANGUAGE_SERVERS, component);
192+
settingRendererRegistry.addRenderer(
193+
`${plugin.id}.${LANGUAGE_SERVERS}`,
194+
renderer
195+
);
193196
}
194197

195198
return connectionManager;

packages/metadataform-extension/schema/metadataforms.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
},
7676
"metadataOptions": {
7777
"properties": {
78-
"customComponent": {
78+
"customRenderer": {
7979
"type": "string"
8080
},
8181
"metadataLevel": {

packages/metadataform-extension/src/index.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
import { INotebookTools } from '@jupyterlab/notebook';
1313
import { ISettingRegistry } from '@jupyterlab/settingregistry';
1414
import { ITranslator } from '@jupyterlab/translation';
15-
import { IFormComponentRegistry } from '@jupyterlab/ui-components';
15+
import { IFormRendererRegistry } from '@jupyterlab/ui-components';
1616
import { JSONExt, PartialJSONArray } from '@lumino/coreutils';
1717

1818
import {
@@ -30,7 +30,7 @@ namespace Private {
3030
registry: ISettingRegistry,
3131
notebookTools: INotebookTools,
3232
translator: ITranslator,
33-
formComponentRegistry: IFormComponentRegistry
33+
formComponentRegistry: IFormRendererRegistry
3434
): Promise<IMetadataFormProvider> {
3535
let canonical: ISettingRegistry.ISchema | null;
3636
let loaded: { [name: string]: ISettingRegistry.IMetadataForm[] } = {};
@@ -221,9 +221,9 @@ namespace Private {
221221
}
222222

223223
// Optionally links key to a custom widget.
224-
if (options.customComponent) {
225-
const component = formComponentRegistry.getComponent(
226-
options.customComponent as string
224+
if (options.customRenderer) {
225+
const component = formComponentRegistry.getRenderer(
226+
options.customRenderer as string
227227
);
228228

229229
// If renderer is defined (custom widget has been registered), set it as used widget.
@@ -274,15 +274,15 @@ const metadataForm: JupyterFrontEndPlugin<IMetadataFormProvider> = {
274274
requires: [
275275
INotebookTools,
276276
ITranslator,
277-
IFormComponentRegistry,
277+
IFormRendererRegistry,
278278
ISettingRegistry
279279
],
280280
provides: IMetadataFormProvider,
281281
activate: async (
282282
app: JupyterFrontEnd,
283283
notebookTools: INotebookTools,
284284
translator: ITranslator,
285-
componentsRegistry: IFormComponentRegistry,
285+
componentsRegistry: IFormRendererRegistry,
286286
settings: ISettingRegistry
287287
): Promise<IMetadataFormProvider> => {
288288
return await Private.loadSettingsMetadataForm(

packages/metadataform/src/form.tsx

+11-17
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
* @module metadataform
66
*/
77

8-
import React from 'react';
9-
import Form, { IChangeEvent } from '@rjsf/core';
10-
import { JSONSchema7 } from 'json-schema';
11-
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
128
import { ReactWidget } from '@jupyterlab/apputils';
13-
import { RJSFTemplatesFactory } from '@jupyterlab/ui-components';
9+
import { FormComponent } from '@jupyterlab/ui-components';
10+
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
11+
import { IChangeEvent } from '@rjsf/core';
12+
import { JSONSchema7 } from 'json-schema';
13+
import React from 'react';
1414

1515
import { MetadataForm } from './token';
1616

@@ -25,11 +25,6 @@ export class FormWidget extends ReactWidget {
2525
super();
2626
this.addClass('jp-FormWidget');
2727
this._props = props;
28-
this._templateFactory = new RJSFTemplatesFactory({
29-
translator: this._props.translator,
30-
compact: true,
31-
showModifiedFromDefault: props.showModified
32-
});
3328
}
3429

3530
/**
@@ -38,27 +33,26 @@ export class FormWidget extends ReactWidget {
3833
*/
3934
render(): JSX.Element {
4035
const formContext = {
41-
settings: this._props.settings,
36+
defaultFormData: this._props.settings.default(),
4237
updateMetadata: this._props.metadataFormWidget.updateMetadata
4338
};
4439
return (
45-
<Form
40+
<FormComponent
4641
schema={this._props.properties as JSONSchema7}
47-
formData={this._props.formData}
42+
formData={this._props.formData as Record<string, any>}
4843
formContext={formContext}
49-
FieldTemplate={this._templateFactory.fieldTemplate}
50-
ArrayFieldTemplate={this._templateFactory.arrayTemplate}
51-
ObjectFieldTemplate={this._templateFactory.objectTemplate}
5244
uiSchema={this._props.uiSchema}
5345
liveValidate
5446
idPrefix={`jp-MetadataForm-${this._props.pluginId}`}
5547
onChange={(e: IChangeEvent<ReadonlyPartialJSONObject>) => {
5648
this._props.metadataFormWidget.updateMetadata(e.formData);
5749
}}
50+
compact={true}
51+
showModifiedFromDefault={this._props.showModified}
52+
translator={this._props.translator}
5853
/>
5954
);
6055
}
6156

6257
private _props: MetadataForm.IProps;
63-
private _templateFactory: RJSFTemplatesFactory;
6458
}

packages/metadataform/src/metadataform.ts

+13-23
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
*/
77

88
import { NotebookTools } from '@jupyterlab/notebook';
9+
import { BaseSettings } from '@jupyterlab/settingregistry';
910
import {
1011
ITranslator,
1112
nullTranslator,
1213
TranslationBundle
1314
} from '@jupyterlab/translation';
1415
import {
1516
JSONExt,
17+
JSONObject,
18+
JSONValue,
1619
PartialJSONObject,
1720
PartialJSONValue,
1821
ReadonlyPartialJSONObject,
@@ -142,7 +145,10 @@ export class MetadataFormWidget
142145
* in metadata before performing update.
143146
* It uses an arrow function to allow using 'this' properly when called from a custom field.
144147
*/
145-
updateMetadata = (formData: ReadonlyPartialJSONObject, reload?: boolean) => {
148+
updateMetadata = (
149+
formData: ReadonlyPartialJSONObject,
150+
reload?: boolean
151+
): void => {
146152
if (this.notebookTools == undefined) return;
147153

148154
const notebook = this.notebookTools.activeNotebookPanel;
@@ -365,7 +371,7 @@ export class MetadataFormWidget
365371
this._metadataSchema
366372
);
367373

368-
const formData = {} as PartialJSONObject;
374+
const formData = {} as JSONObject;
369375

370376
for (let metadataKey of Object.keys(this._metadataSchema.properties)) {
371377
// Do not display the field if it's Notebook metadata and the notebook model is null.
@@ -416,12 +422,14 @@ export class MetadataFormWidget
416422
}
417423

418424
// Fill the formData with the current metadata value.
419-
if (hasValue) formData[metadataKey] = workingObject as PartialJSONValue;
425+
if (hasValue) formData[metadataKey] = workingObject as JSONValue;
420426
}
421427

422428
this.buildWidget({
423429
properties: formProperties,
424-
settings: new Private.Settings(this._metaInformation),
430+
settings: new BaseSettings({
431+
schema: this._metadataSchema as PartialJSONObject
432+
}),
425433
uiSchema: this._uiSchema,
426434
translator: this.translator || null,
427435
formData: formData,
@@ -432,7 +440,7 @@ export class MetadataFormWidget
432440
}
433441

434442
protected translator: ITranslator;
435-
private _form: FormWidget | undefined = undefined;
443+
private _form: FormWidget | undefined;
436444
private _metadataSchema: MetadataForm.IMetadataSchema;
437445
private _metaInformation: MetadataForm.IMetaInformation;
438446
private _uiSchema: MetadataForm.IUiSchema;
@@ -452,24 +460,6 @@ namespace Private {
452460
[metadata: string]: PartialJSONObject | PartialJSONValue | undefined;
453461
}
454462

455-
/**
456-
* The settings to send to RJSF templates in formContext.
457-
*/
458-
export class Settings implements MetadataForm.ISettings {
459-
constructor(metaInformation: MetadataForm.IMetaInformation) {
460-
this.metaInformation = metaInformation;
461-
}
462-
463-
/**
464-
* Returns the default value for a specific key.
465-
* @param metadataKey - the key for which we expect default value.
466-
*/
467-
default(metadataKey: string) {
468-
return this.metaInformation[metadataKey]?.default;
469-
}
470-
471-
metaInformation: MetadataForm.IMetaInformation;
472-
}
473463
/**
474464
* Recursive function to clean the empty nested metadata before updating real metadata.
475465
* this function is called when a nested metadata is undefined (or default), so maybe some

packages/metadataform/src/token.ts

+6-23
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@
77

88
import { CellType } from '@jupyterlab/nbformat';
99
import { NotebookTools } from '@jupyterlab/notebook';
10-
import { ISettingRegistry } from '@jupyterlab/settingregistry';
10+
import { BaseSettings, ISettingRegistry } from '@jupyterlab/settingregistry';
1111
import { ITranslator } from '@jupyterlab/translation';
1212
import {
1313
PartialJSONObject,
14-
PartialJSONValue,
15-
ReadonlyPartialJSONObject,
14+
ReadonlyJSONObject,
1615
Token
1716
} from '@lumino/coreutils';
1817
import { MetadataFormWidget } from './metadataform';
@@ -23,22 +22,6 @@ export namespace MetadataForm {
2322
*/
2423
export type IMetadataSchema = ISettingRegistry.IMetadataSchema;
2524

26-
/**
27-
* The settings to send to RJSF templates in formContext.
28-
*/
29-
export interface ISettings {
30-
/**
31-
* Returns the default value for a specific key.
32-
* @param metadataKey - the key for which we expect default value.
33-
*/
34-
default(metadataKey: string): PartialJSONValue | undefined;
35-
36-
/**
37-
* The meta information associated to all properties.
38-
*/
39-
metaInformation: IMetaInformation;
40-
}
41-
4225
/**
4326
* The meta information associated to all properties.
4427
*/
@@ -132,12 +115,12 @@ export namespace MetadataForm {
132115
/**
133116
* Meta information associated to properties.
134117
*/
135-
settings: ISettings;
118+
settings: BaseSettings;
136119

137120
/**
138121
* Current data of the form.
139122
*/
140-
formData: ReadonlyPartialJSONObject | null;
123+
formData: ReadonlyJSONObject;
141124

142125
/**
143126
* Translator object.
@@ -178,7 +161,7 @@ export namespace MetadataForm {
178161
* The list contains also the conditional fields, which are not necessary
179162
* displayed and filled.
180163
*/
181-
get metadataKeys(): string[];
164+
readonly metadataKeys: string[];
182165

183166
/**
184167
* Get the properties of a MetadataKey.
@@ -205,7 +188,7 @@ export namespace MetadataForm {
205188
* This function build an object with all the root object to update
206189
* in metadata before performing update.
207190
*/
208-
updateMetadata(formData: ReadonlyPartialJSONObject, reload?: boolean): void;
191+
updateMetadata(formData: ReadonlyJSONObject, reload?: boolean): void;
209192
}
210193
}
211194

0 commit comments

Comments
 (0)