v7.3.0

Using Bryntum TaskBoard in a SharePoint SPFx Fabric Web Part

The SharePoint Framework (SPFx) is a page and web part model that provides support for OS independent client-side SharePoint development. With a few simple steps we can create a React App and upload it as a web part. In this guide we will show how the Bryntum TaskBoard can be easily embedded into SharePoint as a web part.

Requirements

To get started, you need a Microsoft 365 tenant. If you don't have one, you can get one as part of the Microsoft 365 developer program. Please see Microsoft's Set up your Microsoft 365 tenant guide for more information.

You also need an IDE (e.g. WebStorm or VS Code) and a recent version of Node (Microsoft currently recommends node 18). Microsoft's Set up your SharePoint Framework development environment has more information.

To get started, a login to Bryntum's npm registry is required. Please refer to this guide to set up an account.

Initial setup

The demo can be found and launched in the examples folder:

cd examples/frameworks/react/typescript/sharepoint-fabric

To be able to try it, you need to install the dependencies:

npm i

And then modify the config/serve.json file to point to your tenant. Replace the {tenantDomain} placeholder with your tenant:

{
  "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json",
  "port": 4321,
  "https": true,
  "initialPage": "https://YOUR_TENANT_URL_GOES_HERE/_layouts/workbench.aspx"
}

Now you can start the local workbench with the following command (you can leave npx out if you have gulp installed globally):

npx gulp serve

The serve command will open a new browser window pointing the workbench on your tenant. To see if the setup is working correctly, click the [+] sign and add BryntumTaskBoard to the page:

If all is set up correctly you should now see TaskBoard rendered.

Next, click the "Edit web part" button, select an option from the "Demo data" and then click the "Create Tasklist" button in the web part settings panel. This creates a task list with a single task or with a full project.

After a few seconds the task list is created on the remote tenant and shown in the Task Lists dropdown field. Pick it and you should see the tasks from your page:

At this point we can modify the sources of the example to our liking. The web part will be rebuilt automatically as long as we have gulp serve running. The changes will be visible after manually refreshing the page in the browser.

Modify the code

When the workbench is running, we are ready to do some coding and customise the example to our liking.

List creation and adding data model fields

If you have some extra fields which are not on a default SharePoint TaskList can be added to the TaskModel in the following manner:

  // For example, we have a count field which has number dataType
  static get additionalFields() {
    return [
      { name: 'count', dataSource: 'Count', type : 'number' }
    ];
  }

When creating a new list in the PropertyPane, the fields in the additional section are added to the newly created task list (on creation).

Data Model

The data model describes how TaskList data is processed and retrieved by the TaskBoard component.

TaskBoard is configured with a ProjectModel which is the entity responsible for loading and saving data to underlying data stores. We override some methods used for communicating with the server and send our data requests to SharePoint using the pnpjs library.

class TaskListModel extends ProjectModel {

  private service: Service;

  // Override of the crudmanager to bypass Ajax response, no decoding needed
  public decode(response: any): any {
    return response;
  }

  // Override of the crudmanager to bypass Ajax response, no encoding needed
  public encode (requestConfig: any): string {
    return requestConfig;
  }

  /**
   * Override of the crudmanager. Use the List Proxy instead of the default AjaxRequest
   * @param request
   */
  public sendRequest(request: any): Promise<any> {
    return new Promise((resolve, reject) => {
      switch(request.type) {
          case 'load':
            return this.service.load(request);
          case 'sync':
            return this.service.sync(request);
        }
    });
  }
}

The Service class will process data requests and retrieve the data from SharePoint and return the response to the Project. In case of a load request we do the following in the Service class:

public load(request: any): Promise<Response> {

    return new Promise((resolve, reject) => {
      this.proxy.getTaskListItems(this.listId).then(response => {
        this.finish(request, response);
        resolve(response);
      }).catch(handleFail);
    });
  }

The service is using a proxy to process the calls with the pnpjs library. TaskListItems are retrieved with the sp package.

public getTaskListItems(listId: string): Promise<Response> {

    const response = new Response();

    return new Promise((resolve, reject) => {

      sp.web.lists.getById(listId).items.select('*,ParentIDId').getAll().then((tasks) => {

        /*...*/
        response.tasks.rows = ...

        // Get all site users. you might want to filter some out
        sp.web.siteUsers().then((users) => {
          response.resources.rows = users;
          resolve(response);
        });
      }).catch(reject);
    });
  }

The returned Response object is passed back to the ProjectModel in the format expected.

interface ITaskList {
  deleteTaskListItems(listId: string, actions: UpdateAction[]): Promise<UpdateAction[]>;
  addTaskListItems(listId: string, actions: UpdateAction[]): Promise<UpdateAction[]>;
  updateTaskListItems(listId: string, actions: UpdateAction[]): Promise<UpdateAction[]>;
  getTaskListItems(listId: string): Promise<Response>;
}

PropertyPane

The example is built with React. The React integration guide provides good guidelines how to configure and style the taskboard component. The PropertyPane for the web part is used to configure it (e.g. its behavior and appearance). We recommend you to read this guide which shows how to modify the basic structure of a web part.

Deploy

When we are finished coding and the web part is ready for production use, the solution can be easily packaged by running a few commands.

gulp bundle --ship
gulp package-solution --ship

We upload the created bryntum-fabric-sp.sppkg package to our app catalog.

When pressing [Deploy], the web part is available for all the sites within the organisation.

Troubleshooting

No development certificate found

If you get an error saying "No development certificate found" when you run gulp serve, you can run the following command to generate a certificate:

npx gulp trust-dev-cert

Module parse failed: Unexpected token

If you encounter an error like: "You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file." It might be because the source code hasn't been transpiled. Webpack doesn't understand some of the syntax used in source code out of the box.

To fix this, you need to transpile the code with Babel. First, install these packages:

    "@babel/core": "7.26.9",
    "@babel/preset-env": "7.26.9",
    "babel-loader": "8.4.1",

and add the following code in the gulpfile.js:

build.configureWebpack.mergeConfig({
    additionalConfiguration : (generatedConfiguration) => {
        // Add Babel loader rule for Bryntum’s code to transpile
        generatedConfiguration.module.rules.push({
            test    : /\.js$/,
            // Only transpile the relevant Bryntum lib files
            include : /node_modules[\\/]+@bryntum[\\/]+\w+-thin[\\/]+lib/,
            use     : {
                loader  : 'babel-loader',
                options : {
                    presets : [
                        [
                            '@babel/preset-env',
                            {
                                targets : {
                                    chrome : '71',
                                },
                            }
                        ]
                    ]
                }
            }
        });

        return generatedConfiguration;
    }
});

Contents