Browse Source

Merge pull request #6060 from Madhu94/abcwidgetfactory-clone-method

Add a clone method to ABCWidgetFactory.
Jason Grout 6 years ago
parent
commit
f43c740d83

+ 17 - 3
packages/docmanager/src/widgetmanager.ts

@@ -85,8 +85,22 @@ export class DocumentWidgetManager implements IDisposable {
     context: DocumentRegistry.Context
   ): IDocumentWidget {
     let widget = factory.createNew(context);
-    Private.factoryProperty.set(widget, factory);
+    this._initializeWidget(widget, factory, context);
+    return widget;
+  }
 
+  /**
+   * When a new widget is created, we need to hook it up
+   * with some signals, update the widget extensions (for
+   * this kind of widget) in the docregistry, among
+   * other things.
+   */
+  private _initializeWidget(
+    widget: IDocumentWidget,
+    factory: DocumentRegistry.WidgetFactory,
+    context: DocumentRegistry.Context
+  ) {
+    Private.factoryProperty.set(widget, factory);
     // Handle widget extensions.
     let disposables = new DisposableSet();
     each(this._registry.widgetExtensions(factory.name), extender => {
@@ -101,7 +115,6 @@ export class DocumentWidgetManager implements IDisposable {
     void context.ready.then(() => {
       void this.setCaption(widget);
     });
-    return widget;
   }
 
   /**
@@ -184,7 +197,8 @@ export class DocumentWidgetManager implements IDisposable {
     if (!factory) {
       return undefined;
     }
-    let newWidget = this.createWidget(factory, context);
+    let newWidget = factory.clone(widget as IDocumentWidget, context);
+    this._initializeWidget(newWidget, factory, context);
     return newWidget;
   }
 

+ 12 - 0
packages/docregistry/src/default.ts

@@ -406,6 +406,18 @@ export abstract class ABCWidgetFactory<
     return widget;
   }
 
+  /**
+   * Clone a widget given a context
+   *
+   * ### Notes
+   * This implementation defaults to creating a new widget.
+   * Subclasses can override this if they wish to handle
+   * cloning a widget differently.
+   */
+  clone(widget: T, context: DocumentRegistry.IContext<U>): T {
+    return this.createNew(context);
+  }
+
   /**
    * Create a widget for a context.
    */

+ 5 - 0
packages/docregistry/src/registry.ts

@@ -994,6 +994,11 @@ export namespace DocumentRegistry {
      * It should emit the [widgetCreated] signal with the new widget.
      */
     createNew(context: IContext<U>): T;
+
+    /**
+     * Clone an existing widget given a context
+     */
+    clone(widget: T, context: IContext<U>): T;
   }
 
   /**

+ 52 - 0
tests/test-docmanager/src/manager.spec.ts

@@ -30,6 +30,37 @@ class WidgetFactory extends ABCWidgetFactory<IDocumentWidget> {
   }
 }
 
+/**
+ * A test documentWidget that maintains some state in
+ * count
+ */
+class CloneTestWidget extends DocumentWidget {
+  constructor(args: any) {
+    super(args);
+    this.counter = args.count;
+  }
+  counter: number = 0;
+}
+
+/**
+ * A widget factory for CloneTestWidget widgets
+ */
+class WidgetFactoryWithSharedState extends ABCWidgetFactory<CloneTestWidget> {
+  protected createNewWidget(
+    context: DocumentRegistry.Context
+  ): CloneTestWidget {
+    return new CloneTestWidget({ context, content: new Widget(), count: 0 });
+  }
+
+  clone(widget: CloneTestWidget, context: DocumentRegistry.Context) {
+    return new CloneTestWidget({
+      context,
+      content: new Widget(),
+      count: widget.counter + 1
+    });
+  }
+}
+
 describe('@jupyterlab/docmanager', () => {
   let manager: DocumentManager;
   let services: ServiceManager.IManager;
@@ -42,6 +73,10 @@ describe('@jupyterlab/docmanager', () => {
     canStartKernel: true,
     preferKernel: true
   });
+  const widgetFactoryShared = new WidgetFactoryWithSharedState({
+    name: 'CloneTestWidget',
+    fileTypes: []
+  });
 
   before(() => {
     services = new ServiceManager({ standby: 'never' });
@@ -51,6 +86,7 @@ describe('@jupyterlab/docmanager', () => {
   beforeEach(() => {
     const registry = new DocumentRegistry({ textModelFactory });
     registry.addWidgetFactory(widgetFactory);
+    registry.addWidgetFactory(widgetFactoryShared);
     DocumentRegistry.defaultFileTypes.forEach(ft => {
       registry.addFileType(ft);
     });
@@ -319,6 +355,22 @@ describe('@jupyterlab/docmanager', () => {
         widget = new Widget();
         expect(manager.cloneWidget(widget)).to.be.undefined;
       });
+
+      it('should allow widget factories to override clone', () => {
+        widget = manager.createNew('foo', 'CloneTestWidget');
+        const clonedWidget: CloneTestWidget = manager.cloneWidget(
+          widget
+        ) as CloneTestWidget;
+        expect(clonedWidget.counter).to.equal(1);
+        const newWidget: CloneTestWidget = manager.createNew(
+          'bar',
+          'CloneTestWidget'
+        ) as CloneTestWidget;
+        expect(newWidget.counter).to.equal(0);
+        expect(
+          (manager.cloneWidget(clonedWidget) as CloneTestWidget).counter
+        ).to.equal(2);
+      });
     });
 
     describe('#closeFile()', () => {

+ 12 - 0
tests/test-docregistry/src/default.spec.ts

@@ -234,6 +234,18 @@ describe('docregistry/default', () => {
         expect(widget).to.be.an.instanceof(Widget);
       });
     });
+
+    describe('#clone()', () => {
+      it('should call createNew in default implementation', () => {
+        const factory = createFactory();
+        const context = createFileContext();
+        const widget = factory.createNew(context);
+        const clonedWidget: IDocumentWidget = factory.clone(widget, context);
+        expect(clonedWidget).to.not.equal(widget);
+        expect(clonedWidget.hasClass('WidgetFactory')).to.be.true;
+        expect(clonedWidget.context).to.equal(widget.context);
+      });
+    });
   });
 
   describe('Base64ModelFactory', () => {