# Embed a custom plugin (/en/realtime-media/flexible-classroom/build/customize-the-ui-and-plugins/embed-custom-plugin/electron)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

In online classroom applications, some customization is often necessary to meet the needs of a particular use case. Agora provides Widgets to help users develop plugins according to their specific needs and embed them into Flexible Classroom.

      
  
      
  
      
  
      
    ## Electron [#electron]

    In online classroom applications, some customization is often necessary to meet the needs of a particular use-case. Agora provides Widgets to help users develop plug-ins according to their specific needs and embed them into Flexible Classroom.

    Widgets are stand-alone plugins that contain interface and functionality. Developers can implement a widget based on customization of the base class, and then register the widget in the Agora Classroom SDK. Agora Classroom SDK supports registering multiple widgets. Widgets can communicate with other widgets, as well as with other plugins in the UI layer.

    <CalloutContainer type="info">
      <CalloutDescription>
        This document applies to Flexible Classroom version `2.8.0` or later.
      </CalloutDescription>
    </CalloutContainer>

    ## Understand the tech [#understand-the-tech-1]

    Flexible Classroom provides a plugin mechanism to help developers expand classroom capabilities under various use-cases. This mechanism also reduces the degree of code coupling between the custom application and the classroom, thereby reducing the difficulty of subsequent upgrading of the integrated source code. See [Plugin technology principles](../../reference/plugin-technology-principles.mdx).

    ## Built-in plugins [#built-in-plugins-1]

    This section introduces the built-in plugins of Flexible Classroom. They extend classroom capabilities and fall into two main categories:

    * Common class plugins, for example:
      * Interactive whiteboard
      * Agora Chat
      * Embedded browser
      * Video sync player (currently supports Youtube videos only)

    * Teaching-aid plugins, for example:
      * Clicker
      * Voter
      * Timer

    The [source codes of the built-in plugins](https://github.com/AgoraIO-Community/apaas-widgets-web/tree/70b0e20d6824533ed36882351629e46da7798ba4/src/gallery) is located in the **apaas-widgets-web** repository.

    | Plugin name                                           | Source code folder       |
    | :---------------------------------------------------- | :----------------------- |
    | Interactive Whiteboard (adapted to AgoraEduSDK)       | `classroom/whiteboard`   |
    | IM module (adapted to AgoraEduSDK)                    | `classroom/im`           |
    | Embedded browser (adapted to AgoraEduSDK)             | `classroom/webview`      |
    | Video synchronization player (adapted to AgoraEduSDK) | `classroom/stream-media` |
    | Clicker (adapted to AgoraEduSDK)                      | `classroom/answer`       |
    | Voting device (adapted to AgoraEduSDK)                | `classroom/vote`         |
    | Timer (Adapted to AgoraEduSDK)                        | `classroom/counter`      |
    | Watermark plug-in (adapted to AgoraEduSDK)            | `classroom/watermark`    |
    | Embedded browser (adapted to AgoraProctorSDK)         | `proctor/webview`        |
    | Chat room (adapted to FcrUIScene)                     | `scene/chatroom`         |
    | Timer (adapted to FcrUIScene)                         | `scene/countdown`        |
    | Voter (adapted to FcrUIScene)                         | `scene/polling`          |
    | Clicker (adapted to FcrUIScene)                       | `scene/quiz`             |
    | Video sync player (adapted to FcrUIScene)             | `scene/stream-media`     |
    | Embedded browser (adapted to FcrUIScene)              | `scene/webview`          |
    | Interactive whiteboard (adapted to FcrUIScene)        | `scene/whiteboard`       |

    ## Custom plugins [#custom-plugins-1]

    If you need to add a custom plugin or a widget, you can inherit the class provided by Flexible Classroom `AgoraWidgetBase` and implement its abstract methods.

    ### The `AgoraWidgetBase` class [#the-agorawidgetbase-class-1]

    The `AgoraWidgetBase` class provides the widget-related operation API. It is defined as follows:

    ```javascript
    export declare abstract class AgoraWidgetBase implements AgoraWidgetRenderable, AgoraMultiInstanceWidget, AgoraWidgetLifecycle {
      constructor(_widgetController: AgoraWidgetController, _classroomStore: EduClassroomStore);

      onInstall(controller: AgoraWidgetController): void {}
      onCreate(properties: any, userProperties: any): void {}
      onPropertiesUpdate(properties: any): void {}
      onUserPropertiesUpdate(userProperties: any): void {}
      onDestroy(): void {}
      onUninstall(controller: AgoraWidgetController): void {}

      locate(): HTMLElement | undefined | null;
      render(dom: HTMLElement): void;
      unload(): void;

      setInstanceId(instId: string): void;
      get instanceId(): string;

      // Widget Name
      abstract get widgetName(): string;

      // Unique widget ID
      get widgetId(): string;

      // Container level
      get zContainer(): 0 | 10;

      // Track synchronization controller
      get trackController(): AgoraWidgetTrackController | undefined;

      // Widget Controller
      get widgetController(): AgoraWidgetController;

      // Classroom Store
      get classroomStore(): EduClassroomStore;

      // Share UIStore
      get shareUIStore(): EduShareUIStore;

      // Classroom configuration
      get classroomConfig(): import("agora-edu-core").EduClassroomConfig;

      // UI configuration
      get uiConfig(): FcrUIConfig;

      // Theme
      get theme(): FcrTheme;

      // Broadcast message
      broadcast(messageType: string, message: unknown): void;

      // Add a broadcast listener
      addBroadcastListener(listener: Omit<AgoraWidgetMessageListener, 'widgetId'>): void;

      // Remove broadcast listener
      removeBroadcastListener(listener: Omit<AgoraWidgetMessageListener, 'widgetId'>): void;

      // Update widget properties
      updateWidgetProperties(properties: any): Promise<{
        data: any;
      }>;

      // Update widget User Properties
      updateWidgetUserProperties(userProperties: any): void;

      // Delete the widget
      deleteWidget(): Promise<{
        data: any;
      }>;

      // Delete widget User Properties
      removeWidgetUserProperties(keys: string[]): Promise<{
        data: any;
      }>;

      // Remove widget Extended Properties
      removeWidgetExtraProperties(keys: string[]): Promise<{
        data: any;
      }>;

      // Set widget as active
      setActive(props?: any): void;

      // Set the widget to the inactive state
      setInactive(props?: any): void;

      // Get the latest component Z-index level
      get latestZIndex(): number;
    }
    ```

    ### Using the `AgoraWidgetBase` class [#using-the-agorawidgetbase-class-1]

    The following code shows the definition of an `ExampleWidget` to implement a basic widget:

    ```javascript
    import { AgoraWidgetBase } from "agora-common-libs";

    export class ExampleWidget extends AgoraWidgetBase {
      private _dom?: HTMLElement
      // Globally unique widget name
      get widgetName(): string {
        return 'example'

      // Mount point
      // Rewrite the locate method to return a node, the widget will be rendered inside this node
      // ExampleWidget is mounted to the whiteboard area
      locate(): HTMLElement | null | undefined {
        return document.querySelector(".widget-slot-board") as HTMLElement;

      // Widget node is mounted
      // At this point, custom rendering can be performed on the DOM node
      render(dom: HTMLElement): void {
        dom.innerHTML = 'This is a custom widget';
        dom.style.height = '100%';
        dom.style.display = "flex";
        dom.style.alignItems = "center";
        dom.style.justifyContent = "center";
        this._dom = dom;

      // Uninstall components
      // Relevant resources can be released here
      unload(): void {
        this._dom = undefined;
      }
    }
    ```

    ### Pass in the custom widget in `launch` [#pass-in-the-custom-widget-in-launch-2]

    Use the following code to pass in your custom widget through the `widgets` parameter in `launch`:

    ```javascript
    const widgets = {
      // Need to import the ExampleWidget class defined above
      'example': ExampleWidget
    };

    AgoraEduSDK.launch(dom, {
      ...
      widgets: widgets
      ...
    });
    ```

    ### Create the custom widget on install [#create-the-custom-widget-on-install-1]

    Since `ExampleWidget` is a custom widget, you need to manually call the `createWidget` method to create a widget:

    ```javascript
    // Modify the WidgetUIStore code file path packages/agora-classroom-sdk/src/infra/stores/common/widget/index.ts to add code
    onInstall() {
      ...
      // Add this code at the end of the onInstall method to open the specified widget after the room is successfully added
      this._disposers.push(
        reaction(
          () => this.classroomStore.widgetStore.widgetController,
          () => {
            // Open our new widget, pass in widgetName here
            this.createWidget('example');
          },
        ),
      );
    }
    ```

    ### Test your implementation [#test-your-implementation-2]

    Start the classroom, the effect after the plug-in is mounted is as follows:

    ![Custom widget mounted in Flexible Classroom](https://web-cdn.agora.io/docs-files/1664451212904)

    ## Create a teaching-aid plugin [#create-a-teaching-aid-plugin-1]

    Flexible Classroom provides developers with an abstract class called `AgoraEduToolWidget`. The class implements the encapsulation of the general capabilities of teaching-aid plugins. Inheriting this class gives the plugin track synchronization, hierarchical control capabilities, and control logic for the display of UI windows.

    With the `ControlledModal` component, you can also quickly implement the outer window of the built-in teaching-aid plugin, reducing a lot of general logic code development. For specific implementation, please refer to the `answer`, `vote` and `counter` folders in the [Github repository](https://github.com/AgoraIO-Community/apaas-widgets-web/tree/70b0e20d6824533ed36882351629e46da7798ba4/src/gallery), which provide the source code of the built-in teaching-aid plugins in Flexible Classroom.

    The following code shows the `AgoraEduToolWidget` class definition:

    ```javascript
    // Use the AgoraEduToolWidget abstract class as the base class to implement draggable and track-synchronized widgets

    export abstract class AgoraEduToolWidget
      extends AgoraWidgetBase
      implements AgoraWidgetLifecycle, AgoraTrackSyncedWidget
    {
      private _controlStateCallbacks: CallableFunction[] = [];
      onUninstall(controller: AgoraWidgetController) {}
      onInstall(controller: AgoraWidgetController) {}
      onCreate(properties: any, userProperties: any): void {}
      onPropertiesUpdate(properties: any): void {}
      onUserPropertiesUpdate(userProperties: any): void {}
      onDestroy(): void {}
      get track(): Track {
        return this.trackController?.track!;
      }
      get zIndex(): number {
        return this.trackController?.zIndex || 0;
      }
      @bound
      updateZIndexToRemote(zIndex: number) {
        this.trackController?.updateRemoteZIndex(zIndex);
        this.widgetController.zIndexController.setZIndex(zIndex);
      }
      @bound
      updateZIndexToLocal(zIndex: number) {
        this.trackController?.updateLocalZIndex(zIndex);
        this.widgetController.zIndexController.setZIndex(zIndex);
      }
      get draggable(): boolean {
        return true;
      }
      get resizable(): boolean {
        return false;
      }
      get dragHandleClassName(): string {
        return 'modal-title';
      }
      get dragCancelClassName(): string {
        return 'modal-title-close';
      }
      get boundaryClassName(): string {
        return 'widget-slot-board';
      }
      get minWidth(): number {
        return 0;
      }
      get minHeight(): number {
        return 0;
      }
      get trackMode(): AgoraWidgetTrackMode {
        return AgoraWidgetTrackMode.TrackPositionOnly;
      }

      @bound
      updateToRemote(
        end: boolean,
        pos: Point,
        dimensions?: Dimensions | undefined,
        options?: TrackOptions | undefined,
      ): void {
        this.trackController?.updateRemoteTrack(end, pos, dimensions, options);
      }

      @bound
      updateToLocal(trackProps: AgoraWidgetTrack): void {
        this.trackController?.updateLocalTrack(trackProps);
      }

      @bound
      handleResize({ width, height }: { width: number; height: number }) {
        this.track.setRealDimensions({ width, height });
        this.track.reposition(false);
      }

      @bound
      handleClose() {
        this.widgetController.broadcast(AgoraExtensionWidgetEvent.WidgetBecomeInactive, this.widgetId);

        this.deleteWidget();
      }

      @bound
      setVisibility(visible: boolean) {
        this.track.setVisibility(visible);
      }

      get controlled() {
        return this.hasPrivilege;
      }

      addControlStateListener(cb: (controlled: boolean) => void) {
        this._controlStateCallbacks.push(cb);
      }

      removeControlStateListener(cb: (controlled: boolean) => void) {
        this._controlStateCallbacks = this._controlStateCallbacks.filter((c) => c !== cb);
      }

      fireControlStateChanged() {
        const controled = this.controlled;
        this._controlStateCallbacks.forEach((cb) => {
          cb(controled);
        });
      }
    }
    ```

    ### Using AgoraEduToolWidget [#using-agoraedutoolwidget-1]

    The following code shows how to create a teaching-aid plugin for pre-class roll calls based on the existing widget capabilities of Flexible Classroom. See the full source code in [rollbook-widget](https://github.com/AgoraIO-Community/flexible-classroom-desktop/).

    ```javascript
    // Create the RollbookWidget class based on the AgoraEduToolWidget class
    // packages/agora-plugin-gallery/src/gallery/rollbook/index.tsx
    import { render, unmountComponentAtNode } from 'react-dom';
    import { App } from './app';
    import { AgoraEduToolWidget } from '../../common/edu-tool-widget';
    import { observable, action, computed } from 'mobx';
    import type { AgoraWidgetController } from 'agora-edu-core';
    import { bound } from 'agora-common-libs';
    import { AgoraExtensionWidgetEvent } from '../../events';
    import { SvgIconEnum } from '../../components/svg-img';

    // Roll call:
    // Teachers can use this plugin to know the participation of students in the classroom
    export class RollbookWidget extends AgoraEduToolWidget {
      private _dom?: HTMLElement;
      @observable
      started = false;
      @observable
      checkInList: string[] = [];
      // Have you checked in?
      @computed
      get isCheckedIn() {
        const { userUuid } = this.classroomConfig.sessionInfo;
        return this.checkInList.includes(userUuid);
      }
      // Check-in username list
      @computed
      get checkInUserNames() {
        return this.checkInList.map((userUuid) => {
          // Get the username corresponding to the user ID from the UserStore (this method does not apply to large classes)
          const user = this.classroomStore.userStore.studentList.get(userUuid);
          return user?.userName || 'Unknown';
        });
      }
      // Initial window width
      get minWidth(): number {
        return 400;
      }
      // Initial window height
      get minHeight(): number {
        return 200;
      }
      // Globally unique widget name
      get widgetName(): string {
         return 'rollbook';
      }
      // Control whether the Widget is controllable
      get hasPrivilege(): boolean {
         return [1, 3].includes(this.classroomConfig.sessionInfo.role);
      }
      get checkInPropKey() {
        const { userUuid } = this.classroomConfig.sessionInfo;
        const key = `checkIn-${userUuid}`;
        return key;
      }

       // The widget node has been mounted, and you can now perform custom rendering on the DOM node.
       render(dom: HTMLElement): void {
         this._dom = dom;
         dom.style.width = '100%';
         dom.style.height = '100%';
         // Rendering UI components using React
         render(<App widget={this} />, dom);
       }

       // Uninstall the component and release related resources at this time
       unload(): void {
         if (this._dom) {
           // Uninstall React components
           unmountComponentAtNode(this._dom);
         }
         this._dom = undefined;
       }

      onCreate(properties: any, userProperties: any): void {
        this._handlePropertiesChange(properties);
      }

      onPropertiesUpdate(properties: any): void {
        this._handlePropertiesChange(properties);
      }

      onInstall(controller: AgoraWidgetController) {
        // Register the plug-in entry to the toolbox
        controller.broadcast(AgoraExtensionWidgetEvent.RegisterCabinetTool, {
          id: this.widgetName,
          name: 'Rollbook',
          iconType: SvgIconEnum.ANSWER,
        });
      }

      onUninstall(controller: AgoraWidgetController) {
        // Remove the plugin entry from the toolbox
        controller.broadcast(AgoraExtensionWidgetEvent.UnregisterCabinetTool, this.widgetName);
      }

     @action
      private _handlePropertiesChange(properties: any) {
        const list: string[] = [];
        Object.keys(properties.extra || {}).forEach((k) => {
          if (k.startsWith('checkIn-')) {
            const userUuid = k.replace('checkIn-', '');
            list.push(userUuid);
          }
        });
        this.checkInList = list;
        this.started = !!properties.extra?.started;
      }

      // Students click to sign in to update the sign-in list
      @bound
      checkIn() {
        this.updateWidgetProperties({
          extra: {
            // Widgets can be updated incrementally using the Key-Value method
            [this.checkInPropKey]: true,
          },
        });
      }

      // The teacher clicks to start signing in and updates the Widget status
      @bound
      startCheckIn() {
        this.setActive({ extra: { started: 1 } });
      }
    }
    ```

    ### Add rendering components [#add-rendering-components-1]

    Add an `App` component for rendering in the plugin window:

    ```javascript
    // packages/agora-plugin-gallery/src/gallery/rollbook/app.tsx
    import React, { FC } from 'react';
    import { observer } from 'mobx-react';
    import { ControlledModal } from '../../common/edu-tool-modal';
    import { EduRoleTypeEnum } from 'agora-edu-core';
    import { RollbookWidget } from '.';
    import { Button } from '../../components/button';

    // Roll roster component

      const view = () =>
        [EduRoleTypeEnum.teacher, EduRoleTypeEnum.assistant].includes(
          widget.classroomConfig.sessionInfo.role,
        ) ? (
          <TeacherView widget={widget} />
        ) : (
          <StudentView widget={widget} />
        );
      return (
        <ControlledModal
          onFullScreen={() => {}}
          canRefresh={false}
          widget={widget}
          title="Rollbook"
          onCancel={widget.handleClose}>
          {view()}
        </ControlledModal>
      );
    };

    // The teacher interface shows the actual check-in list

      const started = widget.started;
      return (
        <div>
          {started ? (
            <React.Fragment>
              <div>Check-In List:</div>
              <ul>
                {widget.checkInUserNames.map((item, i) => (
                  <li key={i.toString()}>{item}</li>
                ))}
              </ul>
            </React.Fragment>
          ) : (
            <Button onClick={widget.startCheckIn}>Start Check-In</Button>
          )}
        </div>
      );
    });

    // StudentView showing check in button

      const isCheckedIn = widget.isCheckedIn;
       return (
         <div>
           <Button onClick={widget.checkIn} disabled={isCheckedIn}>
             {isCheckedIn ? 'Checked-In' : 'Check-In'}
           </Button>
         </div>
       );
    });
    ```

    ### Pass in the custom widget in `launch` [#pass-in-the-custom-widget-in-launch-3]

    Use the following code to pass in your custom widget through the `widgets` parameter in `launch`:

    ```javascript
    const widgets = {
        // Need to import the RollbookWidget class defined above
        'rollbook': RollbookWidget
    };

    AgoraEduSDK.launch(dom, {
    ...
    widgets: widgets
    ...
    });
    ```

    ### Test your implementation [#test-your-implementation-3]

    Start the classroom, you see this plugin in the toolbox as shown in the figure:

    ![Teaching-aid plugin shown in the toolbox](https://web-cdn.agora.io/docs-files/1664451141797)

    The teacher clicks and opens the interface effect of the plugin:

    ![Teacher view after opening the teaching-aid plugin](https://web-cdn.agora.io/docs-files/1664451162107)

    After the teacher clicks Start Check-In, the effect of the plugin is displayed on the student side:

    ![Student view after the teacher starts check-in](https://web-cdn.agora.io/docs-files/1664451174839)

    After the student clicks Check-In, the teacher's check-in list displays the student's effect:

    ![Teacher check-in list after a student checks in](https://web-cdn.agora.io/docs-files/1664451187164)

    
  
