Bryntum
23 October 2023

Integrating Bryntum Gantt with Microsoft SharePoint

Looking for an efficient way to enhance your team’s project management capabilities in your Microsoft SharePoint environment? Look no further! […]

Looking for an efficient way to enhance your team’s project management capabilities in your Microsoft SharePoint environment? Look no further!

This tutorial will walk you through creating a Bryntum Gantt React app with Microsoft SharePoint.

Gantt charts are powerful tools for visualizing and managing project timelines, particularly for teams working on complex projects. Gantt charts give a clear view of a project’s progress, enabling your team members to identify potential obstacles and make real-time adjustments as needed.

For our Gantt chart, we’ll use JSON data from an Excel Gantt as initial data for the Bryntum Gantt and the SharePoint Framework library for client-side SharePoint development. We’ll create a Bryntum Gantt SharePoint React app locally, deploy it to your SharePoint site as a SharePoint app, and then add the custom Bryntum Gantt SharePoint app to a SharePoint site page.

Prerequisites

You’ll need to set up a Microsoft 365 tenant to build and deploy client-side web apps using the SharePoint Framework. If you already have a Microsoft 365 tenant, create an app catalog site.

You’ll also need some initial data for the Gantt chart in a JSON format that’s compatible with the specific structure expected by the Bryntum Gantt chart Crud Manager. The Crud Manager will fetch and update data using SharePoint REST APIs. If you already have an Excel Gantt chart, see our tutorial on migrating from an Excel Gantt chart to a Bryntum Gantt chart to learn how to write a migration script to convert data from an Excel Gantt chart to a Bryntum Gantt-compatible JSON file. We use the JSON data created in that tutorial as example starting data in this tutorial. Here’s a link to JSON files: example Bryntum Gantt JSON data.

Getting started with the Bryntum Gantt SharePoint demo app

The SharePoint Framework simplifies client-side SharePoint development, allowing you to build SharePoint apps using modern web technologies and tools in your development environment of choice.

We’ll use the SharePoint Fabric example app from the Bryntum Gantt distribution code as a starting point. The example app uses Office UI Fabric React components. To download the Bryntum Gantt distribution code, download the free Bryntum Gantt trial. If you have a Bryntum Gantt license, you can download the Bryntum Gantt distribution code from the Bryntum customer zone. When you’ve downloaded the Bryntum Gantt distribution code, navigate to the React TypeScript SharePoint-Fabric example folder and open it in your IDE:

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

We created this demo using a Yeoman plugin for the SharePoint Framework. The plugin is a generator that sets up a SharePoint client-side web part project. SharePoint client-side web parts are the building blocks of pages on a SharePoint site that can execute client-side code in the browser. You can build web parts with vanilla JavaScript or popular web frameworks like React, Angular, and Vue. The example project uses React and TypeScript.

This example project uses npm packages from the Bryntum private npm repository. To access these packages, you must be logged in to this repository as a licensed or trial user. If you don’t have repository access, follow this guide in our docs: Repository access.

The SharePoint Framework version used in the example project requires that you have Node.js version 16.13.0 or higher but lower than version 17.0.0 installed on your machine.

Install the project dependencies:

npm i

You also need to install gulp as a global dependency for SharePoint Framework development:

npm install -g gulp

Gulp is a JavaScript toolkit used to automate repetitive tasks. The SharePoint Framework build toolchain uses gulp tasks to build and deploy projects.

Now, we need to run the following gulp utility task:

gulp trust-dev-cert

The SharePoint Framework local web server we’ll use to test the SharePoint Bryntum Gantt app uses HTTPS by default. Your development environment won’t trust the self-signed certificate issued to implement the test, so the gulp utility task configures your development environment to trust the certificate. Every SharePoint Framework project includes this utility task.

Running a development web server to test the app

You can test client-side SharePoint web parts locally using the SharePoint Workbench, which includes a client-side page and canvas where you can add, delete, and test your SharePoint web parts.

We run the local development server using the gulp task serve, which launches the workbench URL in a browser window. We need to update the default URL to include the domain of your SharePoint tenant site. In the config folder, update the "initialPage" property in the serve.json file with the following, replacing <your-sharepoint-tenant-id> with your tenant ID:

"initialPage": "https://<your-sharepoint-tenant-id>.sharepoint.com/_layouts/workbench.aspx"

Run the gulp task with the following command:

npm run start

Your browser will open, and you’ll see the SharePoint Workbench. Click on the add icon to add the Bryntum Gantt as a web part of the workbench. A toolbox will open, listing the web parts available for you to add. Click on the BryntumGantt web part that is locally available.

The Bryntum Gantt client-side web part is now added to the client-side page.

The Bryntum Gantt SharePoint web part has some initial demo data. To add the data to your Bryntum Gantt SharePoint web part, click on the pencil icon on the top left of the web part. In the web part property pane that opens, select the “Full project demo data” radio button and click “Create Tasklist: BryntumGanttDemo”.

This creates a task list from the tasks data in the launch-saas.json file in the src/webparts/bryntumGantt/resources/data folder. The task list is a SharePoint list, a collection of data displayed in rows and columns. We’ll use the list to store the Bryntum Gantt SharePoint web part data.

The web part property pane is configured in the getPropertyPaneConfiguration method of the BryntumGanttWebPart class. You can find this class in the src/webparts/bryntumGantt/BryntumGanttWebPart.ts file.

The tasks data in the launch-saas.json file is used to create a SharePoint list in the DemoData.ts file in the webparts/bryntumGantt/data/service folder. This file exports a DemoData class that has a createFullExample method.

The Service.ts file in the webparts/bryntumGantt/data/service folder exports a class called Service. It has a public ensureList method that’s used to create a SharePoint task list using the createFullExample method. The Service class is one of the props passed into the React App in the webparts/bryntumGantt/components/App.tsx file. It’s then passed into the Gantt component.

The Bryntum Gantt ProjectModel is configured to load data from and sync data to a SharePoint list using the Bryntum Crud Manager. The modified ProjectModel is named TaskListModel. It’s in the TaskListModel.ts file in the webparts/bryntumGantt/data/model folder. The TaskListModel loads and syncs data using the Service class.

Our Bryntum Gantt SharePoint web part overwrites the default Ajax requests used by the Bryntum Crud Manager (when data is loaded into the Bryntum Gantt or there’s a sync request after data is changed) and uses custom load and sync methods instead. These custom methods get and update list data using PnPjs libraries to make SharePoint REST API requests. You can see the custom load and sync methods in the BaseService.ts file in the webparts/bryntumGantt/data/service folder.

To learn how the Bryntum Gantt uses the SharePoint list as a data source, read the guide in the guide/guide.md file.

Preparing the Gantt chart JSON data

We’ll use the JSON data created in our previous tutorial on migrating from an Excel Gantt chart to a Bryntum Gantt chart. We’ll start with the JSON Gantt data for a simple Gantt chart.

The SharePoint Fabric example app creates a Microsoft list from the Gantt data in the launch-saas.json file. The app expects the Bryntum Gantt tasks data to have a tree data structure, where sub-tasks are added to a nested children array. But the JSON Gantt data for a simple Gantt chart has a flat data structure: It is one level deep, with the parent of a task indicated by a "parentId" property. We need to transform the flat JSON data into tree data.

We also need to make some changes to the transformed flat data. The createFullExample method (defined in DemoData.ts) is called by the Service class to create a Microsoft list from the launch-saas.json data. This method requires each task to have a "startDate" property. Additionally, the Microsoft list that will be the data store for the Gantt in SharePoint needs the task "duration" to be a number, not a string, as indicated in the TaskModel.ts file. The duration also needs to be a number so that the transformJson.js script we create can use it to add an "endDate" to tasks that don’t have one.

Let’s create the transformJson.js script now. First create a folder in the root directory called transformJson. In this folder, create a file called input.json and add the JSON Gantt data for a simple Gantt chart to it. Now create a file called transformJson.js and add the following lines of code to it:


const fs = require("fs");
// Read from input.json, transform the data, then write to output.json
fs.readFile("input.json", "utf-8", (err, data) => {
    if (err) throw err;
    const jsonData = JSON.parse(data);
    const tasks = jsonData.tasks.rows;
    const treeData = transformToTree(tasks);
    adjustProperties(treeData);
    jsonData.tasks.rows = treeData;
    // Ensure "tasks", "resources", "assignments", "dependencies", and "timeRanges" exist
    const properties = [
        "tasks",
        "resources",
        "assignments",
        "dependencies",
        "timeRanges",
    ];
    for (let prop of properties) {
        if (!jsonData.hasOwnProperty(prop)) {
            jsonData[prop] = { rows: [] };
        }
    }
    const outputData = JSON.stringify(jsonData, null, 2);
    fs.writeFile("output.json", outputData, (err) => {
        if (err) throw err;
        console.log("Tree data written to file");
    });
});

We use the readFile method of the Node file system module (fs) to asynchronously read the contents of the JSON file with our flat Bryntum Gantt data. The transformToTree helper function transforms the tasks of the parsed JSON Gantt data object to a tree data structure. If your JSON data already has a tree structure, you can adjust the code so that the transformToTree function is not called on the JSON data.

All tasks need a "startDate" property, so we use the adjustProperties helper function to add any missing "startDate" key-value pairs to the JSON Gantt data object.

We then update the jsonData task rows to make sure that the jsonData object has all the required Bryntum Gantt data properties. If one of the properties is missing, it’s added to the JSON Gantt data object with an empty array value for the "rows" property.

The Node file system writeFile method then converts the JSON Gantt data object back into a JSON string and writes it to a JSON file called output.json.

Let’s define the transformToTree function. Add the following lines of code to the top of the file:


function transformToTree(arr) {
    const map = {};
    let node;
    const tree = [];
    for (let i = 0; i < arr.length; i += 1) {
        map[arr[i].id] = i;
        arr[i].children = [];
    }
    for (let i = 0; i < arr.length; i += 1) {
        node = arr[i];
        if (node.parentId !== undefined) {
            arr[map[node.parentId]].children.push(node);
        } else {
            tree.push(node);
        }
    }
    return tree;
}

This function loops through the task rows array and returns a tree data structure of the same data. The map variable stores a hash map of the tasks’ IDs and index positions in the array. The first for loop creates an empty children array for each task. The second for loop adds tasks without a parent to the tree array, and each task with a parent to the children property of its parent in the passed-in array, arr. This way, the tree array and the tree array objects are modified to point to the passed-in array objects.

Now add the following lines of code to define the adjustProperties function:


function adjustProperties(nodes) {
    for (let node of nodes) {
        // If duration is a string, convert it to a number
        if (node?.duration && typeof node.duration === "string") {
            node.duration = parseInt(node.duration);
        }
        if (node.children.length > 0) {
            if (!node.startDate) {
                // Get start date based on the children
                let grandChildMinStartDateTimeStamp = Infinity;
                node.children.forEach((grandChild) => {
                    if (grandChild.startDate) {
                        const grandChildStartDateTimeStamp = new Date(
                            grandChild.startDate
                        ).valueOf();
                        if (
                            grandChildStartDateTimeStamp <
                            grandChildMinStartDateTimeStamp
                        ) {
                            grandChildMinStartDateTimeStamp =
                                grandChildStartDateTimeStamp;
                        }
                    }
                });
                node.startDate = new Date(
                    grandChildMinStartDateTimeStamp
                ).toISOString();
            }
            adjustProperties(node.children);
        }
    }
}

This function loops through the tasks array and converts any task duration that is a string to a number. If a task has children tasks, the tasks tree is transversed recursively, and a startDate is added to parent nodes that don’t have one using the earliest startDate of the child tasks.

To convert your Gantt JSON data to a tree structure, first navigate into the transformJson directory:

cd transformJson

Now, run the script we created:

node transformJson.js

The output of the script will be in the output.json file. The data is now ready to be added to a Microsoft list using the SharePoint Fabric example app.

Using your own Gantt chart JSON data

In the launch-saas.json file in the src/webparts/bryntumGantt/resources/data folder, replace the JSON object with the JSON object in the output.json file.

Now we can add columns to the Gantt chart. In the src/webparts/bryntumGantt/components/Gantt/GanttConfig.tsx file, change the columns for the Gantt chart:


    columns: [
        { type: "name", width: 180, text: "TASK" },
        {
          type: "resourceassignment",
          text: "Assigned Resources",
          showAvatars: false,
          width: 160,
        },
    
        {
          type: "percent",
          text: "PROGRESS",
          field: "renderedPercentDone",
          showValue: true,
          width: 160,
        },
        { type: "date", field: "startDate", text: "START", width: 110 },
        { type: "date", field: "endDate", text: "END", width: 110 },
      ],

To include task fields that are not in the default SharePoint Task List, we can update the TaskModel.ts file in the src/webparts/bryntumGantt/data/model folder. Add the following objects to the array returned from the additionalFields method:


{ name: "resourceAssignment", dataSource: "ResourceAssignment" },

Now we’ll change the function that will create the SharePoint list that will be the data store for our Bryntum Gantt SharePoint web part. In the src/webparts/bryntumGantt/data/service/DemoData.ts file, replace the createFullExample method with the following lines of code:


public createFullExample(taskList: ITaskList, listId: string): Promise {
    const mockData: any = ganttProject;
    const tasks = mockData.tasks.rows;
    const dependencies = mockData.dependencies.rows;
    const generatedIdMap = {};
    const iterateTasks = async(children, parent) => {
        if (children) {
            for (let i = 0; i < children.length; i++) {
                const child = children[i];
                const newStartDate = new Date(child.startDate);
                let newEndDate;
                if (child.endDate) {
                    newEndDate = new Date(child.endDate);
                } else {
                    if (child.duration) {
                        newEndDate = DateHelper.add(newStartDate, child.duration, 'days');
                    } else if (child.children.length > 0) {
                        // If no duration is set, but there are children, calculate the end date based on the children
                        let grandChildMaxEndDateTimeStamp = 0;
                        child.children.forEach((grandChild) => {
                            const grandChildStartDate = new Date(grandChild.startDate);
                            if (grandChild.endDate) {
                                const grandChildEndDateTimeStamp = new Date(grandChild.endDate).valueOf();
                                if (grandChildEndDateTimeStamp > grandChildMaxEndDateTimeStamp) {
                                    grandChildMaxEndDateTimeStamp = grandChildEndDateTimeStamp;
                                }  
                            } else if (grandChild.duration) {
                                const grandChildEndDateTimeStamp = DateHelper.add(grandChildStartDate, grandChild.duration, 'days').valueOf();
                                if (grandChildEndDateTimeStamp > grandChildMaxEndDateTimeStamp) {
                                    grandChildMaxEndDateTimeStamp = grandChildEndDateTimeStamp;
                                }  
                            } else {
                                const grandChildEndDateTimeStamp = new Date(grandChildStartDate).valueOf();
                                if (grandChildEndDateTimeStamp > grandChildMaxEndDateTimeStamp) {
                                    grandChildMaxEndDateTimeStamp = grandChildEndDateTimeStamp;
                                }                                  
                            }                        
                        });
                        newEndDate = new Date(grandChildMaxEndDateTimeStamp);
                    } else {
                        newEndDate = newStartDate;
                    }
                }
                // Persist as percentage
                // Bryntum: out of 100, SharePoint List: out of 1
                const percentComplete = child.percentDone / 100;
                
                const data = {
                    Title           : child.name,
                    StartDate       : newStartDate,
                    DueDate         : newEndDate,
                    PercentComplete : percentComplete,
                    ManuallyScheduled: true,
                    ResourceAssignment: child.resourceAssignment,
                };
                
                if (parent) {
                    data['ParentIDId'] = parent.id;
                }
                
                const dependency = dependencies.filter(item => {
                    return item.toTask === child.id;
                });
                if (dependency.length > 0) {
                    data['PredecessorsId'] = dependency.map(item => generatedIdMap[item.fromTask]);
                }
                const addResult: UpdateAction[] = await taskList.addTaskListItems(listId, [new UpdateAction({}, data)]);
                generatedIdMap[child.id] = addResult[0].data.id;
                child.id = addResult[0].data.id;
                await iterateTasks(child.children, child);
            }
        }
    };
    return new Promise((resolve, reject) => {
        iterateTasks(tasks, null).then(resolve).catch(reject);
    });
}

This recursive, async iterateTasks function loops through all of the tasks from the launch-saas.json file and creates JavaScript Date objects for the start date and end date of each task. If a task has no end date, the function creates one, as the SharePoint list requires.

The addTaskListItems method then creates the SharePoint list one task at a time. In the data object, we set ManuallyScheduled to true so that the start dates of tasks will not be affected by any task dependencies or constraints. The function also adds the ResourceAssignment field. Note that the SharePoint list field names start with a capital letter.

Now we’ll change the description of the SharePoint Bryntum Gantt web part. Open the BryntumGanttWebPart.manifest.json file in the src/webparts/bryntumGantt folder. In the "preconfiguredEntries" property, find the "properties" property and change the "description" property to "Simple Bryntum Gantt".

Now run the gulp serve task using the following command:

npm run start

Add the data as we previously did with the example data. You may need to wait and refresh the page for the new task list to show.

You should now see your Bryntum Gantt chart SharePoint web part in the SharePoint workbench:

Notice that the “ASSIGNED RESOURCES” column doesn’t show any values from your JSON data. To assign SharePoint team members, double-click on a cell and select team members.

You can also use the data from the more complex agile Gantt JSON data that we created in our previous tutorial. Once you’ve converted the flat JSON data to tree JSON data using the transformJson.js script, add the following object to the array returned from the additionalFields method in the TaskModel.ts file:

       
{ name : 'category', dataSource : 'Category'},

The category field is not part of the default fields of a Microsoft Task List, so we need to add it as an additional field.

Now change the Gantt chart columns in the GanttConfig.tsx file:


columns: [
   { type: "name", width: 190, text: "Milestone description" },
    { width: 80, text: "Category", field: "category" },
    {
      type: "resourceassignment",
      text: "Assigned to",
      showAvatars: false,
      width: 100,
    },
    {
      type: "percent",
      text: "PROGRESS",
      field: "renderedPercentDone",
      showValue: true,
      width: 100,
    },
    { type: "date", field: "startDate", text: "START", width: 110 },
    { type: "date", field: "endDate", text: "END", width: 110 },
  ],

Now we can update the createFullExample method in the DemoData.ts file so that the category value for each task is added to the created SharePoint list. Add the following key-value pair to the data object:

Category : child.category,

Open the BryntumGanttWebPart.manifest.json file in the src/webparts/bryntumGantt folder and locate the "preconfiguredEntries" property. In the "properties" property, change the "description" property to "Agile Bryntum Gantt".

Now run the gulp serve task using the following command:

npm run start

Once you’ve added the Agile Bryntum Gantt web part and the demo data in the SharePoint workbench, you’ll see the Gantt chart with the agile Gantt data:

Now let’s deploy this Bryntum Gantt SharePoint web part to a SharePoint page.

Packaging your Bryntum Gantt client-side web part for deployment

First, stop the gulp serve task if it’s still running.

To add a client-side web part to a SharePoint page, you need to deploy and register the web part with SharePoint. The first step is to package the web part, which you can do by running the following commands in the project root directory:

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

The gulp bundle command bundles your client-side Bryntum Gantt web part. The --ship flag adds the JavaScript, CSS, and other assets to the bundled package.

The package-solution.json file in the config folder defines the package metadata. The gulp package-solution command creates the web part package, bryntum-gantt.sppkg, which you can find in the ./sharepoint/solution folder.

You can view the raw package contents in the ./sharepoint/solution/debug folder. The .sppkg file uses Microsoft Open Packaging Conventions to package your solution. The package format is similar to the SharePoint add-in package format.

Deploying your Bryntum Gantt package to the SharePoint app catalog

To deploy your Bryntum Gantt package to your SharePoint app catalog, do the following:

  1. Go to More features in the SharePoint admin center and sign in with an account that has admin permissions for your organization.
  2. Under Apps, select Open.
  3. On the Manage apps page, select Upload to upload your bryntum-gantt.sppkg file.
  4. In the Enable app panel, select “Only enable this app”, then click the Enable app button.

Now go to your SharePoint tenant site and create a team site if you don’t have one already. Install the Bryntum Gantt app on your team site. Next, create a page if your team site does not have one, and add your Bryntum Gantt app web part to your page. Add the demo data as you did when developing locally in SharePoint workbench.

Click Publish on the top-right of the page. You can copy the page address and share it with your Microsoft SharePoint team members.

On the deployed site, you’ll see the Bryntum Gantt web part:

Any changes you make to the Gantt chart will be saved to the Microsoft list and persisted.

You can view the task list data source in the Site contents of your team site. Click on the “Agile Bryntum GanttDemo” task list item.

This will display the list data:

You can edit the list and saved edits will show in the Bryntum Gantt web part on your team site page. Any edits you make to the Bryntum Gantt will show in the list.

If you click the three dots next to the “Completed” link, you’ll see the list data in a Gantt-chart view:

This Gantt chart has limited functionality compared to the Bryntum Gantt chart.

Conclusion

You’ve successfully integrated a fully functional Gantt chart application in your Microsoft SharePoint environment, providing your team with a powerful tool to visualize and manage project timelines effectively.

So what’s next? With your customized Gantt chart in place, your team can now efficiently manage and track projects, making necessary adjustments as they go. Don’t forget to explore further customizations and features to tailor the application to your team’s needs even more.

Have you already implemented the Bryntum Gantt chart with Microsoft SharePoint? We would love to hear your feedback! Share your experience and let us know how this integration has improved your team’s project management processes.

Bryntum

Bryntum Gantt