Showing posts with label SharePoint Framework. Show all posts
Showing posts with label SharePoint Framework. Show all posts

January 29, 2026

Introducing Heft: The Modern Build Tool Replacing Gulp in SharePoint Framework (SPFx)

Introducing Heft: Modern Build Tool Replacing Gulp in SPFx Development

For a long time, Gulp was the default build tool for SharePoint Framework (SPFx) projects. Developers relied on familiar commands like gulp serve and gulp bundle to compile, package, and deploy their SPFx solutions.

However, as SPFx applications grew in size and complexity, the traditional Gulp-based build system began to struggle with performance, scalability, and long-term maintainability.

To address these challenges, Microsoft introduced Heft - a modern build orchestrator from the Rush Stack ecosystem - and made it the default SPFx build tool starting with SPFx v1.22.

In this article, we’ll explore the differences between SPFx Heft vs Gulp, why Microsoft made the switch, and how Heft improves the modern SharePoint Framework development workflow.

The Gulp Era in SharePoint Framework (SPFx)

In the early days, Gulp handled almost everything in an SPFx project:

  • Compiling TypeScript
  • Bundling with Webpack
  • Running the local dev server
  • Packaging .sppkg files
  • Automating the build pipeline

Typical workflows looked like this:

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

For small projects, this worked fine. For large, long-living enterprise solutions, it did not.

Why Gulp Started to Fail

1. Slow Builds at Scale: Gulp runs tasks mostly sequentially, lacks smart caching, and often triggers full rebuilds for small changes. Result: Slow feedback loops and reduced productivity.

2. Fragile gulpfile.js: Task chains become complex, hard to debug, and frequently break during SPFx upgrades. Result: Build scripts harder to maintain than the app.

3. Poor Fit for Monorepos & Enterprise: Gulp wasn’t designed for monorepos, sharing build logic was painful, and dependency conflicts were common. Result: Scaling SPFx across teams became difficult.

4. Weak Type Safety & Debugging: Mostly JavaScript-based with unclear errors and poor traceability across tools. Result: Developers spent more time debugging the toolchain than writing features.

Enter Heft: The Modern SPFx Build Tool

Heft is a modern build orchestrator from Microsoft’s Rush Stack team, built to support large, enterprise-scale TypeScript solutions.

Unlike Gulp, which is a general-purpose task runner, Heft understands how modern development tools relate to one another - including TypeScript, ESLint, Jest, and Webpack.

Heft focuses on:

  • Clearly defined build phases
  • Plugin-based architecture
  • Incremental builds and smart caching
  • Parallel execution where possible

SPFx internally uses Heft to handle:

  • Compilation
  • Bundling
  • Linting
  • Testing
  • Packaging

SPFx Workflow Update: With SPFx v1.22, Gulp is replaced by Heft - but the developer experience remains familiar.

Task Command
Dev Server heft start
Production Build heft build --production
Package heft package-solution --production

These commands are mapped to standard npm scripts (npm start, npm run build), so day-to-day development workflows remain unchanged.

SPFx Heft vs Gulp: What Actually Changed?

Feature Gulp Heft
Build approach Scripted tasks Phase-based orchestration
Performance Slower at scale Faster with caching & parallelism
Configuration gulpfile.js JSON-based configs
Type safety Limited Strong
Monorepo support Weak Built-in
Debugging Hard to trace Clear errors & logs

Deployment: What Did NOT Change

The deployment process remains exactly the same:

  • Output is still a .sppkg file
  • Deployment still happens via:
  • SharePoint App Catalog
  • CI/CD pipelines (Azure DevOps, GitHub Actions)

Only the build engine changed - not the deployment process.

Node.js & SPFx Compatibility

  • SPFx v1.21.1+ → Node.js 22 LTS
  • Older SPFx → Node.js 16 / 18
  • SPFx ≤ 1.21 uses the Gulp-based toolchain
  • Heft becomes the default starting from SPFx 1.22

Heft officially replaces Gulp starting with SPFx 1.22 onward.

Why Heft Actually Matters

Moving to Heft brings real, practical benefits:

  • Faster rebuilds
  • Less configuration code
  • Fewer breaking changes
  • Consistent builds across teams

Less time fighting the build system, more time writing features.

Frequently Asked Questions (FAQs)

What is Heft in SharePoint Framework (SPFx)?

Heft is a modern build orchestrator developed by Microsoft’s Rush Stack team. It replaces the traditional Gulp-based build system in SharePoint Framework (SPFx) starting from version 1.22, providing faster builds, better scalability, and improved developer experience.

Why did Microsoft replace Gulp with Heft in SPFx?

Microsoft replaced Gulp with Heft to improve performance, maintainability, and scalability of SPFx projects. While Gulp worked well for smaller solutions, it struggled with large enterprise applications. Heft introduces incremental builds, parallel execution, and modern tooling integration.

Is Gulp still used in SPFx projects?

Yes, older SPFx versions (up to 1.21) still use the Gulp-based build system. Starting from SPFx version 1.22, Heft is the default build tool for all new and updated projects.

Does Heft change the SPFx deployment process?

No. The deployment process remains unchanged. Developers still generate .sppkg files and deploy them through the SharePoint App Catalog or automated CI/CD pipelines such as Azure DevOps and GitHub Actions.

Which Node.js version should be used with Heft in SPFx?

SPFx version 1.21.1 and later support Node.js 22 LTS, while older SPFx versions typically rely on Node.js 16 or 18 depending on compatibility.

Final Thoughts

Gulp served SPFx well in its early days, but modern enterprise needs demanded something better.

Heft is not just a replacement, it’s an upgrade.

The shift from Gulp to Heft reflects Microsoft’s move toward a faster, and more scalable build system for SharePoint Framework projects.

If you have any questions, reach out to our SharePoint Consulting team here.

April 25, 2025

Retrieving Viva Engage (Yammer) Posts in SPFx – A Complete Guide

Overview

With Microsoft Viva Engage (formerly Yammer) becoming a central hub for company-wide communication and community engagement, integrating its content directly into SharePoint through SPFx (SharePoint Framework) can provide a seamless user experience.

While Viva Engage content can be embedded using the out-of-the-box (OOTB) web part, it may not provide the flexibility and customization required for more complex or tailored use cases. That’s where SPFx steps in. With custom development, you can fetch and display posts from specific communities, apply filters, or even perform additional business logic as needed.

In this blog post, we will walk you through how to retrieve Viva Engage posts using SPFx and Microsoft Graph API. This is especially useful when the OOTB web part doesn't offer sufficient customization for your needs.

Prerequisites

Before diving into code, make sure you have the following ready:

  • SPFx development environment set up
  • Access to Microsoft 365 admin portal (to grant API permissions)
  • Viva Engage license and active community
  • Basic knowledge of React (optional, but helpful)

Step 1: Register Your App in Azure AD

To access Viva Engage data, you need permissions via Microsoft Graph API.

  1. Go to Azure Portal
  2. Navigate to Azure Active Directory > App registrations
  3. Click New registration
  4. Name your app (e.g., SPFxVivaEngageIntegration)
  5. Add redirect URI: https://localhost:5432
  6. Once registered, go to API permissions > Add permission
  7. Choose APIs my organization uses and search for Yammer
  8. Select Yammer and then Delegated permissions
  9. Add: user_impersonation
  10. Click Grant admin consent

Step 2: Get Access Token

Here’s how to get a token for Yammer (Viva Engage) using AAD token provider in SPFx:

 private async getViVaEngageToken(): Promise<void> {  
  try {  
   const tokenProvider = await this.props.spcontext.aadTokenProviderFactory.getTokenProvider();  
   const token = await tokenProvider.getToken("https://api.yammer.com");  
   this.setState(  
    { vivaEngageToken: token },  
    this.getAllPostsfromGroups  
   );  
  } catch (error) {  
   console.error("Error getting token: ", error);  
  }  
 }  

Step 3: Retrieve Group ID Using Community Name

This method fetches all groups and finds the group ID based on the community name.

 private async getGroupIdByName(communityName: string): Promise&lt;{ topLevelMessages: any[] }&gt; {  
  try {  
   const response = await this.props.spcontext.httpClient.get(  
    `https://api.yammer.com/api/v1/groups.json`,  
    HttpClient.configurations.v1,  
    {  
     headers: {  
      Authorization: `Bearer ${this.state.vivaEngageToken}`,  
      "Content-type": "application/json",  
     },  
    }  
   );  
   const data = await response.json();  
   const group = data.find((g: any) =&gt; g.full_name === communityName);  
   if (group) {  
    return await this.getPosts(group.id);  
   } else {  
    console.warn("Community not found.");  
    return { topLevelMessages: [] };  
   }  
  } catch (error) {  
   console.error("Error fetching group ID: ", error);  
   return { topLevelMessages: [] };  
  }  
 }  

Step 4: Retrieve Posts from Viva Engage

This method calls the messages API and returns only the top-level posts (ignores replies).

 private async getPosts(communityId: string): Promise&lt;{ topLevelMessages: any[] }&gt; {  
  try {  
   const apiUrl = `https://api.yammer.com/api/v1/messages/in_group/${communityId}.json?threaded=true`;  
   const response = await this.props.spcontext.httpClient.get(  
    apiUrl,  
    HttpClient.configurations.v1,  
    {  
     headers: {  
      Authorization: `Bearer ${this.state.vivaEngageToken}`,  
      "Content-type": "application/json",  
     },  
    }  
   );  
   const data = await response.json();  
   const messages = data?.messages || [];  
   const topLevelMessages = messages.filter((msg: any) =&gt; !msg.replied_to_id);  
   return { topLevelMessages };  
  } catch (error) {  
   console.error("Error fetching posts: ", error);  
   return { topLevelMessages: [] };  
  }  
 }  

Conclusion

By following the steps above, you can build a custom SPFx web part to pull Viva Engage posts using a community name and show them dynamically in your SharePoint site. This allows for a much more flexible and personalized experience beyond the OOTB web part.

You now have a streamlined way to:

  • Authenticate with Azure AD
  • Retrieve a Viva Engage community’s group ID using its name
  • Fetch and return only the main posts (excluding replies)

This approach is perfect if you're building SPFx web parts or extensions to display real-time conversations and announcements from Viva Engage.

If you have any questions you can reach out our SharePoint Consulting team here.

January 23, 2025

Upload a Large File in SharePoint Document Library from SPFx Web Part

      Introduction:

      • In this blog, we will demonstrate how to upload large files to a SharePoint document library from the SPFx web part, even when the file size exceeds 100MB or even reaches the GB range.
      • SharePoint provides us with the REST API for uploading files to the document library. The API Is {site_url}/_api/web/getfolderbyserverrelativeurl('/sites/{site_name}/{library_name}')/file s/add(overwrite=true,url='{file_name}'). However, the issue is that this API only allows us to upload files up to 2MB in size. Any files larger than 2MB cannot be uploaded using this API. 
      •  Here I came up with a solution that allows us to upload files larger than 10 MB, going up to GBs, in SharePoint Document Library from the SPFx Web Part. To achieve this, we can use the chunk upload process. The SharePoint REST API provides methods Or query parameters for the chunk upload process, including “Start Upload”, “Continue Upload”, and “Finish Upload”. Using chunk upload, we can handle uploading any size of the file. 

      Function that handles the large upload in the SharePoint document library:

      • Create a custom function to handle large file uploads in the library. This function requires parameters such as file data, filename, SharePoint site URL, document library name, user's digest value, and desired chunk size.
      • At the beginning of the function, we need to declare some variables, such as the headers to be passed in our REST API, the starting point for uploading, and the endpoint, and so on.int, and so on.
      • After that, we need to call another function to started the upload session for file uploading. In this function, we simply add a blank file to our document library to initialize our uploading session. From the API response, we receive the unique ID of the blank file, which helps us identify the file whose content we need to overwrite.
      • After that, we need to generate a unique GUID that is used in our method for starting upload, continuing upload, and finishing upload.
      • After that, we have to check the starting position of the file. According to that, we divide the file into chunks. If the condition is true, then we have to call the "Start Upload" method with a unique GUID, which we have generated, to begin uploading the first chunk of the file to the document library.
      • After uploading the first chunk, we loop through every subsequent chunk of the file and call the "Continue Upload" REST API using the same GUID that we used in the "Start Upload" method to upload the chunks. We continue uploading the chunks until we reach the 2nd to last chunk.
      •  For the last step, we upload the last chunk of the file using the SharePoint REST API method called "Finish upload". to signal to SharePoint that this is the last chunk of the file, thus completing the uploading process.

      private async UploadLargeFile(
        file: Blob,
        siteUrl: string,
        libraryName: string,
        fileName: string,
        chunkSize: number,
        digest: any
      ) {
        const headers = {
          "Accept": "application/json;odata=verbose",
          "X-RequestDigest": digest
        };
        const fileSize = file.size;
        const uploadId = this.GenrateUploadId();
        let start = 0;
        let end = chunkSize;
        let chunkNumber = 0;
        let fileId = "";
      
        const uploadSessionResponse = await this.StartUploadSession(siteUrl, libraryName, fileName, headers);
        fileId = uploadSessionResponse.d.UniqueId;
      
        while (start < fileSize) {
          const chunk = file.slice(start, end);
          const isLastChunk = end >= fileSize;
      
          if (chunkNumber === 0) {
            await this.UploadFirstChunk(siteUrl, libraryName, fileName, chunk, uploadId, headers, fileId);
          } else if (isLastChunk) {
            await this.UploadLastChunk(siteUrl, libraryName, fileName, chunk, uploadId, headers, start, fileId);
          } else {
            await this.UploadIntermediateChunk(siteUrl, libraryName, fileName, chunk, uploadId, headers, start, fileId);
          }
      
          start = end;
          end = start + chunkSize;
          chunkNumber++;
        }
      }
      
      // Function for the generate unique GUI ID
      private GenrateUploadId(): string {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
          const r = Math.random() * 16 | 0;
          const v = c === 'x' ? r : (r & 0x3 | 0x8);
          return v.toString(16);
        });
      }
      
      // Starting Upload Session Method
      private async StartUploadSession(siteUrl: string, libraryName: string, fileName: string, headers: any) {
        try {
          return await this.Retry(async () => {
            const response = await fetch(
              `${siteUrl}/_api/Web/Lists/getByTitle('${libraryName}')/RootFolder/Files/Add(url='${fileName}',overwrite=true)`,
              {
                method: 'POST',
                headers: headers
              }
            );
      
            if (!response.ok) {
              const errorText = await response.text();
              console.error('Failed to start upload session:', errorText);
              throw new Error(`Failed to start upload session: ${errorText}`);
            }
      
            return response.json();
          });
        } catch (error) {
          console.error('Failed to start upload session after retries:', error);
          throw error;
        }
      }

      Start Upload Method:

      • This method is called when attempting to upload the first chunk of the file to our SharePoint document library.
      • In this method, we called the SharePoint Post REST API with the parameter of the start upload method along with a unique GUID.
      • The endpoint for the API is "`${siteUrl}/_api/web/GetFileById('${fileId}')/StartUpload(uploadId=guid'${uploadId}') `".
      private async UploadFirstChunk(
        siteUrl: string,
        libraryName: string,
        fileName: string,
        chunk: any,
        uploadId: string,
        headers: any,
        fileId: string
      ) {
        try {
          return await this.Retry(async () => {
            const response = await fetch(
              `${siteUrl}/_api/web/GetFileById('${fileId}')/StartUpload(uploadId=guid'${uploadId}')`,
              {
                method: 'POST',
                headers: headers,
                body: chunk
              }
            );
      
            if (!response.ok) {
              const errorText = await response.text();
              console.error('Failed to upload first chunk:', errorText);
              throw new Error(`Failed to upload first chunk: ${errorText}`);
            }
      
            return response.json();
          });
        } catch (error) {
          console.error('Failed to upload first chunk after retries:', error);
          await this.CancelUpload(siteUrl, fileId, uploadId, headers);
          await this.DeleteFile(siteUrl, fileId, headers);
          throw error;
        }
      }


      Continue Upload Method:

      • The "Continue Upload" method in SharePoint's REST API allows for the upload of intermediate chunks of a file during a large file upload session.
      •   The API endpoint for continuing the upload is: "/_api/web/GetFileById('')/ContinueUpload(uploadId=guid'',fil eOffset=)". 
      • This endpoint specifies the file being uploaded (fileId), the unique upload session ID (uploadId), and the starting byte position of the chunk (fileOffset). 
      • The "file Offset" parameter specifies the starting byte position of the chunk being uploaded in the overall file. It helps SharePoint understand where this chunk fits within the entire file. 
      • it indicates the position in the file where the current chunk starts. 
      • For example, if the first chunk is 1MB (1048576 bytes) in size, the file Offset for the second chunk would be 1048576, the third chunk would be 2097152, and so on. 
      private async UploadIntermediateChunk(siteUrl: string, libraryName: string, fileName: string, chunk: any, uploadId: string, headers: any, start: any, fileId: string) {
          try {
            return await this.Retry(async () => {
              const response = await fetch(`${siteUrl}/_api/web/GetFileById('${fileId}')/ContinueUpload(uploadId=guid'${uploadId}',fileOffset=${start})`, {
                method: 'POST',
                headers: headers,
                body: chunk
              });
      
              if (!response.ok) {
                const errorText = await response.text();
                console.error('Failed to upload chunk:', errorText);
                throw new Error(`Failed to upload chunk: ${errorText}`);
              }
              return response.json();
            });
          } catch (error) {
            console.error('Failed to upload intermediate chunk after retries:', error);
            await this.CancelUpload(siteUrl, fileId, uploadId, headers);
            await this.DeleteFile(siteUrl, fileId, headers);
            throw error;
          }
        }

      Finish Upload Method:

      • The "Finish Upload" method is used to upload the final chunk of a large file to a SharePoint library, signaling the end of the upload process.
      • The method sends a POST request to the SharePoint API endpoint to finish the upload.
      • API endpoint is: "/_api/web/GetFileById('<fileId>')/FinishUpload(uploadId=guid'<uploadId>',fileOffset=<start>)".
      private async UploadLastChunk(siteUrl: string, libraryName: string, fileName: string, chunk: any, uploadId: string, headers: any, start: any, fileId: string) {
        try {
          return await this.Retry(async () => {
            const response = await fetch(`${siteUrl}/_api/web/GetFileById('${fileId}')/FinishUpload(uploadId=guid'${uploadId}',fileOffset=${start})`, {
              method: 'POST',
              headers: headers,
              body: chunk
            });
      
            if (!response.ok) {
              const errorText = await response.text();
              console.error('Failed to upload chunk:', errorText);
              throw new Error(`Failed to upload chunk: ${errorText}`);
            }
      
            return response.json();
          });
        } catch (error) {
          console.error('Failed to upload last chunk after retries:', error);
          await this.CancelUpload(siteUrl, fileId, uploadId, headers);
          await this.DeleteFile(siteUrl, fileId, headers);
          throw error;
        }
      }


      Cancel Upload And Delete File:

      • The "Cancel Upload" method is used to cancel an ongoing large file upload session in SharePoint. This is typically done when an error occurs during the upload process, and you want to terminate the session to prevent incomplete or corrupted files from being saved.
      • Sends a request to the SharePoint API to cancel the current upload session identified by uploadId.Utilizes the unique fileId and uploadId to specify which upload session to cancel.
      • Helps ensure that partially uploaded files are not left in an inconsistent state.
      • The "Delete File" method is used to delete a file from a SharePoint library. This is usually called after canceling an upload session to remove any partially uploaded files and clean up the SharePoint library.
      • Sends a request to the SharePoint API to delete the file identified by file.
      • Ensures that any incomplete or unwanted file uploads are removed, maintaining the integrity of the document library.
      private async CancelUpload(siteUrl: string, fileId: string, uploadId: string, headers: any) {
         try {
           const response = await fetch(`${siteUrl}/_api/web/GetFileById('${fileId}')/CancelUpload(uploadId=guid'${uploadId}')`, {
             method: 'POST',
             headers: headers
           });
      
           if (!response.ok) {
             const errorText = await response.text();
             console.error('Failed to cancel upload session:', errorText);
             throw new Error(`Failed to cancel upload session: ${errorText}`);
           }
      
         } catch (error) {
           console.error('Error occurred while canceling upload session:', error);
         }
       };
       private async DeleteFile(siteUrl: string, fileId: string, headers: any) {
         try {
           const response = await fetch(`${siteUrl}/_api/web/GetFileById('${fileId}')`, {
             method: 'DELETE',
             headers: headers
           });
      
           if (!response.ok) {
             const errorText = await response.text();
             console.error('Failed to delete file:', errorText);
             throw new Error(`Failed to delete file: ${errorText}`);
           }
         } catch (error) {
           console.error('Error occurred while deleting file:', error);
         }
       }


      Summary:

      This blog explains how to upload large files to SharePoint using a segmented approach for efficiency and reliability. It starts with the Start Upload Method, which initializes the upload session and prepares the file. Next, the Continue Upload Method handles middle segments, ensuring sequential upload using fileOffset. Finally, the Finish Upload Method completes the upload by sending the last segment, ensuring all parts are integrated into SharePoint. These methods include error handling and retries to ensure successful uploads, overcome file size limits,  and enhance system performance.

      If you have any questions you can reach out our SharePoint Consulting team here.

      July 21, 2022

      How use SharePoint theme color in SCSS in SPFx

      Introduction:

      In this blog, we will learn how can we use the color of current applied theme in SharePoint in SPFx web parts or SPFx extension.

      Steps:

      Step 1) Go to the SCSS file of your web part or extension.

      Step 2) Now at the top of the SCSS file, declare the variables and assign the theme color as a value to this variable as given below:
       $themePrimary: '[theme:themePrimary, default:#dc1928]';  
       $themeSecondary: '[theme:themeSecondary, default:#2488d8]';  
       $themeTertiary: "[theme:themeTertiary, default:#69afe5]";  
       $themeLight: "[theme:themeLight, default:#b3d6f2]";  
       $themeAccent: "[theme:themeAccent, default:inherit]";  
       $themeDark: "[theme:themeDark, default:#005a9e]";  
       $themeDarkAlt: "[theme:themeDarkAlt, default:#106ebe]";  
       $themeDarker: "[theme:themeDarker, default:#004578]";  
      

      Here we are creating the variables for all the colors of the theme, so we can use directly the variables.

      Step 3) Now, add the class name in which you want to apply the color of the theme and in this class name we can use the variable names we have declared at the top of SCSS file.
       .menuButton{  
         background-color: $themePrimary;  
       }  
      

      Step 4) Now, when you use menuButton class in your HTML it will apply the primary color of your selected theme as the background color.

      Alternate Approach:

      If you do not want to use variables, you can also apply theme color direct to the class as given below:
       .menuButton{  
         background-color: "[theme:themePrimary, default:#dc1928]";  
       }  

      Conclusion:

      This is now we can use theme color in SCSS. Hope this blog will be helpful for you!

      If you have any questions you can reach out our SharePoint Consulting team here.

      January 13, 2022

      How to use the context of another site while using PnP in SharePoint Framework (SPFx) web part?

      Introduction:

      When we use PnP to work with SharePoint lists, PnP will use the context of the current site. We cannot use the context of another site.

      In this blog, we will learn how can we get the context of another site.

      Steps:

      Step 1: First, we need to install npm package for sp-pnp-js. Execute below command to install it:

       npm I sp-pnp-js  
      

      Step 2: Once it is installed, import the web from sp-pnp-js as below:

       import { Web } from "sp-pnp-js";  
      

      Step 3: Now, we will get the web context of particular site collection as below:

      • var siteUrl = "https://***********.sharepoint.com/sites/*******";
      • let web: any = new Web(siteUrl);

      Step 4: Now, you can use this web context to work with SharePoint lists as below:

      • var tickers = await web.lists.getByTitle("List Name").items.select("Title”).get();

      Now, in a tickers variable, you will be able to fetch the list items from another site collection.

      Conclusion:

      This is how we can use the context of another site in the SPFx web part. Hope this helps!

      If you have any questions you can reach out our SharePoint Consulting team here.

      December 9, 2021

      [Issue Resolved]: The term 'yo' is not recognized as the name of a cmdlet, function, script file or operable program

      Introduction:

      In this blog, we will see how can we resolve the error The term 'yo' is not recognized as the name of a cmdlet, function, script file, or operable program. Generally, this error occurs while we have set up a new environment for SPFx development.

      Reason:

      The reason for this error was the wrong path was selected in the user path environment variable.

      Solution:

      Follow the below steps to resolve this error:

      Step 1) Open the start menu and search for the 'environment'. Now select the Edit the system environment variables.

      Step 2) Now go to the Advanced tab and click on Environment Variables.

      Step 3) On clicking Environment Variables, it will open another popup. Now select the PATH variable and click on the Edit button.

      Step 4) On clicking the Edit button, it will open another popup.

      Step 5) Now in Variable value, add the below path and click on the OK button:

      • C:\Users\{username}\AppData\Roaming\npm
        • Note: Replace {username} with your username.

      Step 6) Now click on the OK button in Environment Variable and System properties popups.

      Step 7) Restart the command prompt, and the issue will be resolved.

      Conclusion:

      This is how we can resolve the issue “The term 'yo' is not recognized”. Hope this helps!

      If you have any questions you can reach out our SharePoint Consulting team here.

      July 22, 2021

      How to apply styles dynamically In SPFx web part?

      Introduction:

      We implemented an intranet portal for a Real Estate Agency based out of Bellevue, Washington, United States. We came across a requirement to apply styles dynamically in the custom SPFx webpart based on the color selected in the property panel of the SPFx webpart. Let's see how this functionality can be achieved.

      Requirement:

      We need to apply the color of text based on the color selected in the property panel. In the property panel, we have added a textbox, where users can add a color code or a color name that they want as the text color.

      Implementation:

      We have applied the class name for the div where we are showing texts. In the CSS file, we need to define the color of the text using variables. 

      The CSS will be as below:
       .ProjectTitle{  
            color: var(--projectTitleColor);  
       }  
      

      And our HTML will be as below:
       <div className="ProjectTitle">  
            Intranet Portal       
       </div>  
      

      As we can see, in the CSS we have used a variable for applying the color. Now we will assign the value to this variable. First, we will get the value from the text box of the property panel as below:
       var colorName = this.props.TextColor;  
      

      Now we will use colorName variable, to assign a color to the style’s variable. 
       let div = document.createElement('style');  
       div.innerHTML = ':root {-- projectTitleColor: ' + colorName + ' }';  
       document.getElementsByTagName('head')[0].append(div);  
      

      So here we created a new element for style and appended this element in the head element of the page.
      Whatever value we assign to the variable – projectTitleColor, it will assign to the color style in the CSS file. Here in this example, we have used a CSS file, but you can also use the same steps for SCSS file as well.

      Conclusion:

      This is how we can apply dynamic styles. Hope this helps you!

      If you have any questions you can reach out our SharePoint Consulting team here.

      February 25, 2021

      [Issue Resolved]: Part URI is not valid per rules defined in the Open Packaging Conventions specification

      Introduction

      We at Binary Republik recently implemented Intranet Portal on Modern SharePoint for a Real Estate Agency based out of Washington, United States. We also implemented custom webparts with SharePoint Framework (SFPx) as part of the Intranet Portal implementation to meet the business user expectations. While implementing the custom SFPx webparts, we encountered a strange error. Let's see in detail what error we faced in which scenario, what was the root cause of the error and what's the resolution for the same. 


      Error/Issue:

      • After uploading the ".sppkg" solution of SPFx webpart in the "app catalog", it was giving the error “Invalid SharePoint App package. Error: Part URI is not valid per rules defined in the Open Packaging Conventions specification”.


      Scenario:

      • We created SPFx webpart solution, the solution was built successfully. Then we executed the below commands to create package:
        • gulp bundle --ship
        • gulp package-solution –ship
      • The package was also created successfully, so we uploaded the same to the "app catalog" and it was giving an error as shown above.


      Reason:

      • After some research and analysis, we found, this was because of the parent folder of our SPFx webpart solution.
      • Here is the path of our solution: D:\Projects\Project Management
      • The parent folder name of our solution was "Project Management" (please note, there is a space in the name of the parent folder) and while creating the solution we have selected the option to "Use the current folder" in "Where do you want to place the files?" selection.

      • So, when there is space in the name of the parent folder of your solution, it will give the error “Error: Part URI is not valid per rules defined in the Open Packaging Conventions specification”.


      Solution:

      • To solve this error, remove space from the name of the parent folder of your solution. We renamed the parent folder name to be “ProjectManagement”.
      • Then executed below commands:
        • gulp clean
        • gulp build
        • gulp bundle --ship
        • gulp package-solution –ship
      • And deployed the new package in the "app catalog" and the package was deployed successfully.


      Hope this helps!
       
      If you have any questions you can reach out our SharePoint Consulting team here.

      January 21, 2021

      How to export information to a CSV (Comma-separated values) file in SharePoint Framework (SPFx)?

      Introduction:

      We recently implemented a customized Learning Management System (LMS) in SharePoint Online using SharePoint Framework (SPFx) for a Postal and Parcel company based out of Melbourne, Victoria, Australia. We also implemented a simple tabular format report in SPFx that will allow the admin user to see which user has completed which training and which training is pending. The requirement was also to allow admin users to export this tabular report to a CSV file. In this blog, we will learn about exporting data/information to a CSV file in SharePoint Framework (SPFx). 

      We will be using the below npm packages in our SPFx solution.

      Office-UI-Fabric-React:

      • The “Office-UI-Fabric-React” package will be used to develop different user controls.
      • This package gets installed automatically when we create an SPFx solution.

      React-CSV:

      • The “React-CSV” package will be used to generate a CSV file.
      • We will run the below NPM command in CMD (Command Prompt) to install the “React-CSV” package.

       npm install react-csv –save;  


      Below are the steps to convert simple data into CSV format:

      Step 1: Import the “CSVLink” class from the “React-CSV” and the “CommandBarButton” class from the “Office-UI-Fabric-React”. 

       import { CSVLink } from "react-csv";  
       import { CommandBarButton } from 'office-ui-fabric-react';  
      


      Step2: We will be using the “CSVLink” class and “CommandBarButton” as shown below.

       <CSVLink data={csvList} filename={'UserInformationReport.csv'}>  
             <CommandBarButton className={styles.ExportButton} iconProps={{ iconName: 'ExcelLogoInverse' }} text='' />  
        </CSVLink>  

      • This will create a button to export information into a CSV file.

      Below are the “CSVLink” class properties:

      • Data: This is a required property and we need to pass the values as an array.
      • Filename: We have to provide the filename for the CSV file to be exported.
      • Headers: This will provide a custom header for our CSV file and also this property is used to define the order of the CSV fields.

      We have provided the below array data in the data property.
       Let csvList = [{"Outlet":"Outlet 1","Work Center Code":"123456","Email Address":"user1@tenant.com.au","Name":"Test User 1","Completed Trainings":"Training 1, Training 2, Training 3","Pending Trainings":"Training 4, Training 5","Completion Percentage":60,"First Assessment Date":"11/6/2020","Last Assessment Date":"11/26/2020"},  
       {"Outlet":"Outlet 2","Work Center Code":"456789","Email Address":"user2@tenant.com.au","Name":"Test User 2","Completed Trainings":"Training 1, Training 2","Pending Trainings":"Training 3, Training 4, Training 5","Completion Percentage":40,"First Assessment Date":"11/3/2020","Last Assessment Date":"12/2/2020"},  
       {"Outlet":"Outlet 2","Work Center Code":"456789","Email Address":"user3@tenant.com.au","Name":"Test User 3","Completed Trainings":"Training 1, Training 2, Training 3, Training 4","Pending Trainings":"Training 5","Completion Percentage":80,"First Assessment Date":"11/6/2020","Last Assessment Date":"11/26/2020"}];  
      

      Step 3: Below is the CSV button for exporting the information to a CSV file.


      • By clicking on this button, a CSV file "UserInformationReport.csv" will be generated with the above data.

      CONCLUSION:

      This is how we can export information to a CSV (Comma-separated values) file in SharePoint Framework (SPFx). Hope this helps you, good day!

      If you have any questions you can reach out our SharePoint Consulting team here.

      December 2, 2020

      How to clear Office UI Fabric multi-select dropdown in SPFx webpart with React Framework?

      Scenario:

      We implemented a webpart using SharePoint Framework (SPFx) that includes the filter functionality. As per the requirement, we have one dropdown that allows multiple options selection for filter functionality. Now when user clicks on the "Clear Filter" button, it should clear the selection of options from the dropdown.

      Below is our dropdown control with multiple items selection:
       <Dropdown  
        multiSelect  
        className="multiSelectDrodpown"  
        options={currentObj.state.dropdownOptions}  
        defaultSelectedKeys={this.state.selectedOptions}  
        onChanged={(val) => currentObj.filterData(val)}           
       ></Dropdown>  
      

      Note:
      • dropdownOptions state contains all the options which will be display in multi select dropdown control.
      • selectedOptions is the state which contains the options which are currently selected.


      Problem Statement:

      • When we select the values from the multi-select dropdown control, we pass those values to defaultSelectedKeys parameter of the multi-dropdown control to show the selected values. 
      • But when we clear the value of the state which we are using in defaultSelectedKeys parameter(selectedOptions state in our scenario), it does not clear the selection of multi-select dropdown.


      Solution:

      • To resolve this issue, we need to use the "key" parameter of the office fabric UI dropdown control and set the unique number as the value on click event of the "Clear Filter" button.
      • Using a new value for key parameter means, it renders fresh control each time.
      • Here, we use randomIndex state to set a new key value to generate a random number. Below is an example to use key attribute:
       <Dropdown  
        multiSelect  
        className="multiSelectDrodpown"  
        options={currentObj.state.dropdownOptions}  
        defaultSelectedKeys={this.state.selectedOptions}  
        onChanged={(val) => currentObj.filterData(val)}     
        key={this.state.randomIndex}        
       ></Dropdown>  
      
      • Now, when user clicks the "Clear Filter" button, it will update randomIndex state and set a new random number as its value.
      • Here, we are using Math.floor and Math.random()  function to generate random numbers.
       private async clearFields() {  
            await this.setState({    
                randomIndex: Math.floor(Math.random() * 6) + 1          
             });  
       }  
      


      Conclusion:

      This is how we can clear the value of multi-select dropdownlist in SharePoint Framework (SPFx).

      If you have any questions you can reach out our SharePoint Consulting team here.

      March 30, 2020

      Manage more than 100 API calls with Batch Request in SharePoint Framework (SPFx) using React

      Requirement:
      We have IDs of 1000 list items and want to fetch list items for only those particular IDs using Rest API with OR condition in the filter.

      Problem Statement:
      1. If we append all IDs in the filter parameter with OR condition, it will give REST API length limitation (255 characters).
      2. If we use API with OR condition for all IDs and use this API in a batch request, batch API will return a blank array.
      3. If we create API for each ID and create an array of APIs & use this array in a batch request, it will work for maximum up to 100 APIs only. As we have 1000 list items to be retrieved, this will also not work due to the 100 APIs limit with a single batch request.

      Solution:
      We can create API for 100 Item IDs with OR condition in the filter API and create a bunch of 100 APIs in an array and then use this array in a batch request.

      You can find batch utility code here on GitHub. Here is the code snippet for this solution:
       //Here we have imported batchutility.ts  
       import { BatchUtils } from "../../BatchUtils";  
       public componentDidMount() {  
         var listItemIds = [1, 2, 3, 4, 5, 6, ..............................., 1000];  
         var url = this.props.SPUrl + "/_api/web/lists/getbytitle('" + this.props.projectPhaseListName + "')/items?$filter=";  
         this.getDashboardBatchData(listItemIds, 0, listItemIds.length, url);  
       }  
       //Below function will create bunch of filters with max of 100 id in filter with or condition  
       public getDashboardBatchData(listItemIds, Index, totalCount, url) {  
         var loopLen = Index + 100;  
         if (Index <= totalCount) {  
           var filterString = "";  
           var tempApi = '';  
           var callNext = true;  
           if (totalCount > Index && totalCount < loopLen) {  
             loopLen = totalCount;  
             callNext = false;  
           }  
           for (var i = Index; i < loopLen; i++) {  
             if (filterString == '') {  
               filterString = "ID eq " + listItemIds[i];  
             }  
             else {  
               filterString += " or ID eq " + listItemIds[i];  
             }  
           }  
           tempApi = url + filterString;  
           dashboardBatchArray.push(tempApi);  
           if (callNext) {  
             this.getDashboardBatchData(listItemIds, loopLen, totalCount, url);  
           }  
           else {  
             //dashboardBatchArray will have all APIs with max of 100 filters for ID  
             this.processBatch(dashboardBatchArray);  
           }  
         }  
         else {  
           //dashboardBatchArray will have all APIs with max of 100 filters for ID  
           this.processBatch(dashboardBatchArray);  
         }  
       }  
       //Below funcion will create bunch of 100 APIs and will be used in batch request  
       public processBatch(dashboardBatchArray){  
         var index = 0;  
         var arrayLength = dashboardBatchArray.length;  
         var tempArray = [];  
         var chunk_size = 100;  
         //Below code will create array of 100 APIs in single bunch  
         for (index = 0; index < arrayLength; index += chunk_size) {  
           var myChunk = dashboardBatchArray.slice(index, index + chunk_size);  
           tempArray.push(myChunk);  
         }  
         //Below code will execute all apis of tempArray  
         let listItemsArray = [];  
         for (var i = 0; i < tempArray.length; i++) {  
           await BatchUtils.GetBatchAll({ rootUrl: this.props.SPUrl, FormDigestValue: '', batchUrls: tempArray[i] }).then((batchResult) => {  
             if (batchResult.length > 0) {  
               for (var i = 0; i < batchResult.length; i++) {  
                 for (var j = 0; j < batchResult[i].d.results.length; j++) {  
                   listItemsArray.push(batchResult[i].d.results[j]);  
                 }  
               }  
             }  
           }  
         }  
         //You will see all 1000 items in listItemsArray variable  
         console.log(listItemsArray);  
       }  
      

      If you have any questions you can reach out our SharePoint Consulting team here.

      July 5, 2017

      Prepare environment for SharePoint Framework (SPFx) Development

      Microsoft has already announced the General Availability for SharePoint Framework. We can develop modern web parts for SharePoint using SharePoint Framework (SPFx). Web Parts developed using SPFx can be added to modern view pages as well as classic pages in SharePoint.

      Prepare your development environment for SharePoint Framework development:

      A. Install Node Dependencies for SharePoint Framework.

      1. Download and Install Node.js.
      2. Open command prompt and execute below commands one by one to install bower, grunt-cli and yeoman respectively.
      i. npm install -g bower
      ii. npm install -g grunt-cli
      iii. npm install -g yo

      3. Install Microsoft SharePoint Generator by executing below command in command prompt.
      i. npm i @microsoft/generator-sharepoint -g

      4. Install Gulp by executing below command in command prompt.
      i. npm install gulp -g

      B. Install desired source code editor (e.g. Visual Studio Code, ATOM, Sublime).

      Build your first web part using SharePoint Framework (SPFx):

      A. Open Node.js Command Prompt.
      B. Navigate to the directory where you want to have your solution located at (e.g. cd c:\SPFx).
      C. Execute command: yo
      D. This will list down the available generators. As we have already installed Microsoft SharePoint Generator, this will appear as an option here.
      E. Select “Microsoft SharePoint Generator” and press enter.

      F. This will ask for below details, enter appropriate details and move forward:
      i. Solution Name – e.g. “HelloWorld”.
      ii. Select “Use the current folder” option.
      iii. You can select the JavaScript Web Framework from available options. I have selected “No JavaScript Web Framework” option for this application.
      iv. What is your webpart name? – e.g. “HelloWorldWP”
      v. What is your webpart description? – e.g. “Hello world web part developed using SharePoint Framework.”.
      vi. This will take a few minutes to prepare the solution.

      G. Once solution is created, we can see success message for solution creation as shown below:

      H. To check the solution, open the solution in Visual Studio Code from file system directory (C:\SPFx) where we created the solution.

      I. As this post is focused on to prepare the development environment for SPFx, we will not go in depth with the source code, but as we can see in below screenshot, default Hello World web part appears in the solution:

      Run SPFx solution on development environment:

      A. Open Node.js Command Prompt and navigate to the directory where we created the solution (C:\SPFx).
      B. Execute command “gulp serve” as shown in below screenshot:

      C. On execution, we get SharePoint User Interface in browser on localhost (It is NOT required to have SharePoint installed on development environment). And when we click “+” button on modern UI, we can see Hello World web part in available options as show below:

      D. When web part is added on the page, we can see the same as shown below:

      So, the environment is prepared and verified for SharePoint Framework (SPFx) development. For verification, we have used default Hello World web part, we can develop the web parts as per requirements. 

      If you have any questions you can reach out our SharePoint Consulting team here.