Building SharePoint Framework Client Side Web Parts using jQuery and jQuery plugins from CDN

In the past you might have built SharePoint solutions using jQuery and jQuery plugins loaded from a CDN. Now that the SharePoint Framework is released, here is how you would build your solution as a Client Side Web Part.

The Weather Web Part

To illustrate how to build SharePoint Framework Client Side Web Parts using jQuery and jQuery plugins loaded from CDN we will use the Weather Web Part that shows the current weather in the specified location using the Simple Weather jQuery plugin.

Weather Web Part built using the SharePoint Framework

Loading jQuery and jQuery plugins in the SharePoint Framework declaratively

The way loading external scripts should work

SharePoint Framework offers us an easy way of loading external scripts in our Web Parts. External scripts can be specified in the ./config/config.json file, by adding them to the externals section.

After creating a new Client-Side Web Part, the contents of the ./config/config.json file look similar to:

{
  "entries": [
    {
      "entry": "./lib/webparts/weather/WeatherWebPart.js",
      "manifest": "./src/webparts/weather/WeatherWebPart.manifest.json",
      "outputPath": "./dist/weather.bundle.js"
    }
  ],
  "externals": {
    "@microsoft/sp-client-base": "[email protected]/sp-client-base/dist/sp-client-base.js",
    "@microsoft/sp-client-preview": "[email protected]/sp-client-preview/dist/sp-client-preview.js",
    "@microsoft/sp-lodash-subset": "[email protected]/sp-lodash-subset/dist/sp-lodash-subset.js",
    "office-ui-fabric-react": "node_modules/office-ui-fabric-react/dist/office-ui-fabric-react.js",
    "react": "node_modules/react/dist/react.min.js",
    "react-dom": "node_modules/react-dom/dist/react-dom.min.js",
    "react-dom/server": "node_modules/react-dom/dist/react-dom-server.min.js"
  },
  "localizedResources": {
    "mystrings": "webparts/weather/loc/{locale}.js"
  }
}

In order to load jQuery, all you would need to do is to add a new entry to the externals section:

{
  "entries": [
    {
      // omitted for brevity
    }
  ],
  "externals": {
    // standard entries omitted for brevity
    "jquery": "https://code.jquery.com/jquery-2.1.1.min.js"
  },
  "localizedResources": {
    // omitted for brevity
  }
}

In order to use jQuery in your Web Part, you would add:

import * as jQuery from 'jQuery';  

and SharePoint Framework would take care of everything else. Because jQuery is defined as an AMD module, we only need to specify its CDN URL in the externals configuration.

In the case of the Weather Web Part we need not only jQuery but also the Simple Weather jQuery plugin. Because the plugin is not an AMD module we have to specify some additional configuration in order to load it:

{
  "entries": [
    {
      // omitted for brevity
    }
  ],
  "externals": {
    // standard entries omitted for brevity
    "jquery": "https://code.jquery.com/jquery-2.1.1.min.js",
    "simpleWeather": {
      "path": "https://cdnjs.cloudflare.com/ajax/libs/jquery.simpleWeather/3.1.0/jquery.simpleWeather.min.js",
      "globalName": "jQuery"
    }
  },
  "localizedResources": {
    // omitted for brevity
  }
}

As you can see, for the Simple Weather plugin, we specify not only its path, but also the name of the global variable used by the plugin, which registers itself by extending the jQuery variable. For this to work however, the Simple Weather plugin should load after jQuery. We can configure this, by specifying a dependency between the two scripts:

{
  "entries": [
    {
      // omitted for brevity
    }
  ],
  "externals": {
    // standard entries omitted for brevity
    "jquery": "https://code.jquery.com/jquery-2.1.1.min.js",
    "simpleWeather": {
      "path": "https://cdnjs.cloudflare.com/ajax/libs/jquery.simpleWeather/3.1.0/jquery.simpleWeather.min.js",
      "globalName": "jQuery",
      "globalDependencies": ["jquery"]
    }
  },
  "localizedResources": {
    // omitted for brevity
  }
}

If you would try to build your project now you would get an error similar to following:

Error - [copyAssets] External "simpleWeather" is referencing dependency "jquery", which is not defined as a global, non-AMD external.

Values in the globalDependencies property can only point to other external scripts defined as non-AMD scripts. So how do you define an external script as a non-AMD script?

In order to define an external script as a non-AMD script you need to specify the name of the global variable to which the script will be attached using the globalName property:

{
  "entries": [
    {
      // omitted for brevity
    }
  ],
  "externals": {
    // standard entries omitted for brevity
    "jquery": {
      "path": "https://code.jquery.com/jquery-2.1.1.min.js",
      "globalName": "jQuery"
    },
    "simpleWeather": {
      "path": "https://cdnjs.cloudflare.com/ajax/libs/jquery.simpleWeather/3.1.0/jquery.simpleWeather.min.js",
      "globalName": "jQuery",
      "globalDependencies": ["jquery"]
    }
  },
  "localizedResources": {
    // omitted for brevity
  }
}

Then, in order to load both scripts in our Web Part, we add the following code snippet:

import * as jQuery from 'jquery';  
require('simpleWeather');  

In theory this should work: we specified external URLs for jQuery and the simpleWeather jQuery plugin, we configured the dependency between the plugin and jQuery and we reference both scripts from the Web Part.
Unfortunately, if you would try to load the Web Part, you would see an error stating that jQuery is not defined. Examining the page load process would show that jQuery and the simpleWeather plugin are loaded simultaneously:

Error while loading Web Part using jQuery and jQuery plugin defined declaratively

Luckily, there is another way, one that works, that we can use to load external scripts in SharePoint Framework Client-Side Web Parts.

Update September 5, 2016: In the v0.2.0 of the SharePoint Framework loading jQuery and non-AMD jQuery plugins has been fixed and works as expected. While you can still use the ModuleLoader to load your scripts in code, you can use the declarative approach which results in cleaner code. See the updated source code of the sample project to learn how loading jQuery and non-AMD jQuery plugins from CDN works when done declaratively.

Loading jQuery and jQuery plugins in the SharePoint Framework in code

The way loading external scripts currently works

Loading external scripts declaratively is a great and easy way to leverage other building blocks for developing Web Parts on the SharePoint Framework. Unfortunately, at this moment, it doesn't work as expected. Luckily, there is another way to load external scripts in SharePoint Framework Client-Side Web Parts.

While there are many frameworks that allow you to load external scripts in your JavaScript solutions, I would recommend that in SharePoint Framework solutions you use the native ModuleLoader class. Using its loadScript function you can load both global scripts as well as external AMD modules.

To use the ModuleLoader class, first you need to import it into your Web Part class by adding the import ModuleLoader from [email protected]/sp-module-loader'; statement, like:

import {  
  BaseClientSideWebPart,
  IPropertyPaneSettings,
  IWebPartContext,
  PropertyPaneTextField
} from [email protected]/sp-client-preview';

import ModuleLoader from [email protected]/sp-module-loader';

// omitted for brevity

This makes the ModuleLoader class available in your Web Part for you to use.

Loading external scripts only once

In most cases you will be loading external scripts and other resources inside the Client Side Web Part's render function. Doing so you should keep in mind that the render function is being called when the Web Part is rendered but also when it is re-rendered after a property in the property pane has changed. Luckily the SharePoint Framework offers us an easy way to track whether the Web Part is rendering initially or if its re-rendering.

Consider the following code snippet:

public render(mode: DisplayMode, data?: IWebPartData): void {  
  if (this.renderedOnce === false) {
    this.domElement.innerHTML = `<div class="${styles.weather}"></div>`;

    ModuleLoader.loadScript('https://code.jquery.com/jquery-2.1.1.min.js', 'jQuery').then(($: any): void => {
      this.jQuery = $;
      ModuleLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/jquery.simpleWeather/3.1.0/jquery.simpleWeather.min.js', 'jQuery').then((): void => {
        this.renderContents();
      });
    });
  }
  else {
    this.renderContents();
  }
}

In this example we use the standard render function to load our external scripts. The actual rendering logic is moved to our custom renderContents function. Using the standard renderedOnce property we check if the Web Part has already rendered or if its rendering initially. If it's rendering initially we load our scripts using the ModuleLoader.loadScript function. Otherwise, having our scripts already present in the page, we skip loading the scripts and move directly to the rendering part.

When loading jQuery using the ModuleLoader we load it as a global script associated with the jQuery variable. Also, as we're loading the Simple Weather jQuery plugin we need the global variable which the plugin is using to add itself to jQuery. Because the plugin is not an AMD module, the jQuery variable must be available in the global scope.

Also note, that after loading jQuery and before loading the Simple Weather plugin, we store the reference to jQuery in the this.jQuery variable. We we will need it in our custom renderContents function to instantiate the Simple Weather plugin in the Web Part.

Having loaded both scripts we continue with rendering the weather information using the Simple Weather plugin.

Using jQuery plugins in SharePoint Framework Client Side Web Parts

Having loaded jQuery and jQuery plugin the last part left is to use them in our Web Part. We do that in a custom renderContents function which we call from the standard render function after loading external scripts.

private renderContents(): void {  
  this.container = this.jQuery(`.${styles.weather}`, this.domElement);

  const location: string = this.properties.location;

  if (!location || location.length === 0) {
    this.container.html('<p>Please specify a location</p>');
    return;
  }

  const webPart: WeatherWebPart = this;

  this.jQuery.simpleWeather({
      location: location,
      woeid: '',
      unit: 'c',
      success: (weather: any): void => {
          let html: string = '<h2><i class="icon-' + weather.code + '"></i> ' + weather.temp + '&deg;' + weather.units.temp + '</h2>';
          html += '<ul><li>' + weather.city + ' ' + weather.region + '</li></ul>';
          webPart.container.html(html).removeAttr('style').css('background', `url('http://loremflickr.com/500/139/${location}')`);
      },
      error: (error: any): void => {
          webPart.container.html(`<p>${error.message}</p>`).removeAttr('style');
      }
  });
}

First we retrieve the container for our Web Part. We do this using the custom generated class name and scoping the search to the current Web Part element defined in the render function. That way, if there are multiple Weather Web Parts on the page they won't conflict with each other.

Next, we retrieve the name of the location for which we should render the weather information from the Web Part properties. If the user didn't provide any value we show an information message asking her to configure the Web Part.

Finally, using the reference to jQuery we obtained while loading jQuery, we call the Simple Weather plugin and instantiate it passing the information about the location along with some other settings.

Weather Web Part built using the SharePoint Framework

Summary

When building Client Side Web Parts using the SharePoint Framework you can bundle JavaScript frameworks together with your Web Part but you can also load them from CDN. For loading scripts from CDN the SharePoint Framework offers the ModuleLoader class which you can use for loading AMD modules as well as global scripts. This makes it suitable for building solutions using jQuery and many popular jQuery plugins.

Comments

comments powered by Disqus