SharePoint online spfx script editor web part

In this SharePoint Framework tutorial, we will discuss how to create an spfx script editor web part in SharePoint Online Office 365 using React JS.

After creating the SPFx script editor web part, we can add the script editor web part to the SharePoint Online modern page.

Since Microsoft introduced modern pages to Office 365 and SharePoint Online, it is really easy to create beautiful sites and pages without requiring any design experience.

If you need to customize the look and feel of modern pages, you can use custom tenant branding, custom design, etc.

As we know in classic SharePoint we can apply CSS and JavaScript using a script editor web part for quick customization. But in the modern site, the script editor web part or the content editor web part is not available.

For the last week, I was trying to find a simple way to add my HTML content in the SharePoint modern page which is the same as in my classic SharePoint but I was unable to do.

Check out, SharePoint Framework Development Training

But after going through a few more blog posts by many SharePoint experts, I finally found the solution.

If you haven’t taken a look at the script editor web part in modern SharePoint you are missing out.

Create SharePoint Online SPFx script editor web part

Now, we will see how to create SharePoint Online SPFx script editor web part.

First, we will create an SPFX web part.

Step 1: Open your Node.JS command prompt and create a new SPFX web part.

add script editor web part sharepoint online modern page

Step 2: After you got a successful message, open your Visual studio code and open your newly created web part.

add modern script editor web part sharepoint online

Here if you look into the above screenshot, two files which have extra added here but don’t worry as it’s required to apply our customization code.

Step 3: In these steps, just create two extra files which has mark as yellow in the above screenshot.

  • Create a file inside the WebPart called DeveloperDetails.ts
  • Create a file inside components called IModernScriptEditorProps.ts

Step 4: Next apply the below code inside DeveloperDetails.ts

modern script editor web part spfx
import { IPropertyPaneField, PropertyPaneFieldType, IPropertyPaneCustomFieldProps } from "@microsoft/sp-property-pane";

export class developerDetails implements IPropertyPaneField<IPropertyPaneCustomFieldProps> {
    public type: PropertyPaneFieldType = PropertyPaneFieldType.Custom;
    public targetProperty: string;
    public properties: IPropertyPaneCustomFieldProps;

    constructor() {
         this.properties = {
             key: "Logo",
             onRender: this.onRender.bind(this)
        };
    }

    private onRender(elem: HTMLElement): void {
        elem.innerHTML = `
    <div style="margin-top: 30px">
      <div style="float:right">Author: <a href="https://twitter.com/Rajkiran441" tabindex="-1">Rajkiran Swain</a></div>
    </div>`;
    }
}
export default developerDetails;

Step 5: Next open IModernScriptEditorWebpartProps.ts which is located inside the components folder and apply the below code.

See also  Your changes could not be saved because this SharePoint web site has exceeded the storage quota limit
sharepoint spfx script editor web part
import { IPropertyPaneAccessor } from "@microsoft/sp-webpart-base";
export interface IModernScriptEditorWebpartProps {
  script: string;
  title: string;
  propPaneHandle: IPropertyPaneAccessor;  
}

Step 6: Next open the ModernScriptEditorWebpart.tsx which is located inside components folder and apply the below code.

sharepoint online spfx script editor
import * as React from 'react';
import styles from './ModernScriptEditorWebpart.module.scss';
import { IModernScriptEditorWebpartProps } from './IModernScriptEditorWebpartProps';
import { escape } from '@microsoft/sp-lodash-subset';
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";


export default class ModernScriptEditorWebpart extends React.Component<IModernScriptEditorWebpartProps,any> {

  constructor(props: IModernScriptEditorWebpartProps, state: any) {
    super(props);
    this._showDialog = this._showDialog.bind(this);
        this.state = {};
  }
  public componentDidMount(): void {
    this.setState({ script: this.props.script, loaded: this.props.script });
}

private _showDialog() {
    this.props.propPaneHandle.open();
}

  public render(): React.ReactElement<IModernScriptEditorWebpartProps> {
    const viewMode = <span dangerouslySetInnerHTML={{ __html: this.state.script }}></span>;

    return (
      <div className='ms-Fabric'>
                <Placeholder iconName='JS'
                    iconText={this.props.title}
                    description='Please configure the web part'
                    buttonLabel='Edit markup'
                    onConfigure={this._showDialog} />
                {viewMode}
            </div>);    
  }
}

Step 7: Next open IModernScriptEditorProps.ts which you have created in Step -2 and apply the below code.

sharepoint spfx script editor web part
export interface IModernScriptEditorProps {

    script: string;
    title: string;
    removePadding: boolean;
    spPageContextInfo: boolean;
    teamsContext: boolean;
  }

Step 8: Next open ModernScriptEditorWebpartWebPart.ts and apply the below code.

spfx modern script editor
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { SPComponentLoader } from '@microsoft/sp-loader';
import { Version, DisplayMode } from '@microsoft/sp-core-library';
import {
  IPropertyPaneConfiguration,
  PropertyPaneTextField,
  PropertyPaneToggle,
  IPropertyPaneField
} from '@microsoft/sp-property-pane';

import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import * as strings from 'ModernScriptEditorWebpartWebPartStrings';
import ModernScriptEditorWebpart from './components/ModernScriptEditorWebpart';
import { IModernScriptEditorWebpartProps } from './components/IModernScriptEditorWebpartProps';
import DeveloperDetails from './DeveloperDetails';
import { IModernScriptEditorProps } from './IModernScriptEditorProps';

export default class ModernScriptEditorWebpartWebPart extends BaseClientSideWebPart <IModernScriptEditorProps> {
  public _propertyPaneHelper;
  private _unqiueId;

  constructor() {
    super();
    this.scriptUpdate = this.scriptUpdate.bind(this);
}

public scriptUpdate(_property: string, _oldVal: string, newVal: string) {
  this.properties.script = newVal;
  this._propertyPaneHelper.initialValue = newVal;
}

  public render(): void {
    this._unqiueId = this.context.instanceId;
    if (this.displayMode == DisplayMode.Read) {
      if (this.properties.removePadding) {
          let element = this.domElement.parentElement;
          for (let i = 0; i < 5; i++) {
              const style = window.getComputedStyle(element);
              const hasPadding = style.paddingTop !== "0px";
              if (hasPadding) {
                  element.style.paddingTop = "0px";
                  element.style.paddingBottom = "0px";
                  element.style.marginTop = "0px";
                  element.style.marginBottom = "0px";
              }
              element = element.parentElement;
          }
      }
      ReactDom.unmountComponentAtNode(this.domElement);
            this.domElement.innerHTML = this.properties.script;
            this.executeScript(this.domElement);
        } else {
            this.renderEditor();
        }
    }
    private async renderEditor() {
      const editorPopUp = await import(
          './components/ModernScriptEditorWebpart'
      );
      const element: React.ReactElement<IModernScriptEditorWebpartProps> = React.createElement(
          editorPopUp.default,
          {
              script: this.properties.script,
              title: this.properties.title,
              propPaneHandle: this.context.propertyPane,
              key: "pnp" + new Date().getTime()
          }
      );
      ReactDom.render(element, this.domElement);
       }
    
  protected get dataVersion(): Version {
      return Version.parse('1.0');
  }
  protected async loadPropertyPaneResources(): Promise<void> {
    const editorProp = await import(
        '@pnp/spfx-property-controls/lib/PropertyFieldCodeEditor'
    );
    this._propertyPaneHelper = editorProp.PropertyFieldCodeEditor('scriptCode', {
      label: 'Edit HTML Code',
      panelTitle: 'Edit HTML Code',
      initialValue: this.properties.script,
      onPropertyChange: this.scriptUpdate,
      properties: this.properties,
      disabled: false,
      key: 'codeEditorFieldId',
      language: editorProp.PropertyFieldCodeEditorLanguages.HTML
  });
}

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
  let webPartOptions: IPropertyPaneField<any>[] = [
      PropertyPaneTextField("title", {
          label: "Title to show in edit mode",
          value: this.properties.title
      }),
      PropertyPaneToggle("removePadding", {
          label: "Remove top/bottom padding of web part container",
          checked: this.properties.removePadding,
          onText: "Remove padding",
          offText: "Keep padding"
      }),
      PropertyPaneToggle("spPageContextInfo", {
          label: "Enable classic _spPageContextInfo",
          checked: this.properties.spPageContextInfo,
          onText: "Enabled",
          offText: "Disabled"
      }),
      this._propertyPaneHelper
  ];
  if (this.context.sdks.microsoftTeams) {
    let config = PropertyPaneToggle("teamsContext", {
        label: "Enable teams context as _teamsContexInfo",
        checked: this.properties.teamsContext,
        onText: "Enabled",
        offText: "Disabled"
    });
    webPartOptions.push(config);
}
webPartOptions.push(new DeveloperDetails());
return {
  pages: [
      {
          groups: [
              {
                  groupFields: webPartOptions
              }
          ]
      }
  ]
};
}
private evalScript(elem) {
  const data = (elem.text || elem.textContent || elem.innerHTML || "");
  const headTag = document.getElementsByTagName("head")[0] || document.documentElement;
  const scriptTag = document.createElement("script");

  for (let i = 0; i < elem.attributes.length; i++) {
      const attr = elem.attributes[i];
      if(attr.name.toLowerCase() === "onload"  ) continue; 
      scriptTag.setAttribute(attr.name, attr.value);
  }

  scriptTag.type = (scriptTag.src && scriptTag.src.length) > 0 ? "pnp" : "text/javascript";
  scriptTag.setAttribute("pnpname", this._unqiueId);

  try {
      scriptTag.appendChild(document.createTextNode(data));
  } catch (e) {
      scriptTag.text = data;
  }

  headTag.insertBefore(scriptTag, headTag.firstChild);
}

private nodeName(elem, name) {
  return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
}

private async executeScript(element: HTMLElement) {
  const headTag = document.getElementsByTagName("head")[0] || document.documentElement;
  let scriptTags = headTag.getElementsByTagName("script");
  for (let i = 0; i < scriptTags.length; i++) {
      const scriptTag = scriptTags[i];
      if(scriptTag.hasAttribute("pnpname") && scriptTag.attributes["pnpname"].value == this._unqiueId ) {
          headTag.removeChild(scriptTag);
      }            
  }

  if (this.properties.spPageContextInfo && !window["_spPageContextInfo"]) {
      window["_spPageContextInfo"] = this.context.pageContext.legacyPageContext;
  }

  if (this.properties.teamsContext && !window["_teamsContexInfo"]) {
      window["_teamsContexInfo"] = this.context.sdks.microsoftTeams.context;
  }

  (<any>window).ScriptGlobal = {};

  const scripts = [];
  const children_nodes = element.childNodes;

  for (let i = 0; children_nodes[i]; i++) {
      const child: any = children_nodes[i];
      if (this.nodeName(child, "script") &&
          (!child.type || child.type.toLowerCase() === "text/javascript")) {
          scripts.push(child);
      }
  }

  const urls = [];
  const onLoads = [];
  for (let i = 0; scripts[i]; i++) {
      const scriptTag = scripts[i];
      if (scriptTag.src && scriptTag.src.length > 0) {
          urls.push(scriptTag.src);
      }
      if (scriptTag.onload && scriptTag.onload.length > 0) {
          onLoads.push(scriptTag.onload);
      }
  }

  let oldamd = null;
  if (window["define"] && window["define"].amd) {
      oldamd = window["define"].amd;
      window["define"].amd = null;
  }

  for (let i = 0; i < urls.length; i++) {
      try {
          let scriptUrl = urls[i];
          const prefix = scriptUrl.indexOf('?') === -1 ? '?' : '&';
          scriptUrl += prefix + 'pnp=' + new Date().getTime();
          await SPComponentLoader.loadScript(scriptUrl, { globalExportsName: "ScriptGlobal" });
      } catch (error) {
          if (console.error) {
              console.error(error);
          }
      }
  }
  if (oldamd) {
      window["define"].amd = oldamd;
  }

  for (let i = 0; scripts[i]; i++) {
      const scriptTag = scripts[i];
      if (scriptTag.parentNode) { scriptTag.parentNode.removeChild(scriptTag); }
      this.evalScript(scripts[i]);
  }
  for (let i = 0; onLoads[i]; i++) {
      onLoads[i]();
  }
}
}

Step 9: Next go to your command prompt and execute this code using below command.

  • gulp clean
  • gulp build
  • gulp serve
  • gulp bundle –ship
  • gulp package-solution –ship
See also  How to Send Email based on Date in SharePoint list using Power Automate?

Run the above command in the same order. So once you click on gulp serve, it will open a new page where you have to add your newly added web part in the local workbench.

After adding the new SPFx web part, the page will look empty. The result will come after added this web part to your SharePoint Online site.

Step 10: After clicking on Gulf serve, it will open a new tab in the browser and allow me to test my newly created web part here.

modern script editor web part sharepoint online

Next, click on the Edit Markup and go to the properties and apply some HTML code.

modern script editor web part not showing sharepoint online

Next click on the Yellow highlighted mark and it will open a black snipped tool where you can apply your code.

add modern script editor web part sharepoint online

Next, click on Save and see the output on the screen which I have added inside my modern site collection.

add modern script editor web part sharepoint online

Step 11: Next what we will do, prepare a package, and upload the package in your SharePoint App Catalog site.

So we have to prepare the package using the above command which is in step-9. After creating the package, we have to upload in App catalog site which is the same as the below screenshot.

bootstrap in spfx webpart

Notes: Sometime you will get an error in your screen if there are few files are missing.

Fixes: Open your Node.JS command prompt and copy the below command and click on enter.

add script editor web part sharepoint online modern page
npm install @pnp/spfx-property-controls --save --save-exact
spfx modern script editor
npm install @pnp/spfx-controls-react --save

References:

You may like the following SharePoint framework tutorials:

See also  SharePoint Rest API Tutorial and Examples

In this SharePoint tutorial, we learned how to create an spfx script editor web part in SharePoint Online and how to add script editor web part in SharePoint Online modern page.

  • Thanks, but I got a few errors –
    Error – ‘clean’ sub task errored after 133 ms
    ENOTEMPTY: directory not empty, rmdir ‘C:modernScriptEditorWebpartlibwebparts’
    [15:36:06] ‘clean’ errored after
    AND
    Error – ‘copy-static-assets’ sub task errored after 96 ms
    EPERM: operation not permitted, mkdir ‘C:modernScriptEditorWebpartlibwebpartsmodernScriptEditorWebpart’
    [15:36:51] ‘build’ errored after 25

  • Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(24,28): error TS1005: ‘>’ expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(24,51): error TS1005: ‘,’ expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(24,53): error TS1180: Property destructuring pattern expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(24,63): error TS1359: Identifier expected. ‘this’ is a reserved word that cannot be used here.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(24,67): error TS1005: ‘:’ expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(24,73): error TS1005: ‘,’ expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(24,83): error TS1068: Unexpected token. A constructor, method, accessor, or property was expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(24,85): error TS1110: Type expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(24,86): error TS1161: Unterminated regular expression literal.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(27,12): error TS1005: ‘>’ expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(27,21): error TS1005: ‘)’ expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(28,30): error TS1005: ‘>’ expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(28,38): error TS1005: ‘;’ expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(29,35): error TS1005: ‘:’ expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(32,38): error TS1005: ‘:’ expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(32,53): error TS1109: Expression expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(34,15): error TS1161: Unterminated regular expression literal.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(35,3): error TS1128: Declaration or statement expected.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(36,1): error TS1128: Declaration or statement expected.
    Error – ‘tsc’ sub task errored after 6.53 s
    exited with code 2
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(16,30): error TS2307: Cannot find module ‘./DeveloperDetails’.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(17,42): error TS2307: Cannot find module ‘./IModernScriptEditorProps’.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(64,15): error TS2769: No overload matches this call.
    Error – [tsc] src/webparts/modernScriptEditorWebpart/ModernScriptEditorWebpartWebPart.ts(78,9): error TS2307: Cannot find module ‘@pnp/spfx-property-controls/lib/PropertyFieldCodeEditor’.
    Error – ‘tsc’ sub task errored after 6.98 s
    exited with code 2

  • >