0
votes

I need some expert knowledge about SPFx web parts in combination with SharePoint 2016 and InternetExplorer 11. The idea is to create a web part that uses Pannellum to display images. In Firefox it's already working. With IE11, however, the error messages appear in the console:
SCRIPT5022: TypeMismatchError BaseURL.ts (16,7)

SCRIPT5007: Die Eigenschaft "toString" eines undefinierten oder Nullverweises kann nicht abgerufen werden.
Message in English: The "toString" property of an undefined or null reference cannot be retrieved. LogEvent.js (26,1)

In the developer tools I can see that the image has been downloaded. But as the code of the webpart tries to call createObjectURL with the image as blob object it crashes.

Screeshot of BaseURL.ts

The webpart has been created with Yeoman (3.1.0) and tested with Gulp. The execute the script for Pannellum in the webpart I use the executeScript function from this repo: https://github.com/SharePoint/sp-dev-fx-webparts/blob/dev/samples/react-script-editor/src/webparts/scriptEditor/ScriptEditorWebPart.ts

I guess my problem has to to with this: IE + XMLHttp + CreateObjectURL Error.

Has someone experience with this?

Here some example code:

import { Version } from '@microsoft/sp-core-library';
import {
  BaseClientSideWebPart,
  IPropertyPaneConfiguration,
  PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { escape } from '@microsoft/sp-lodash-subset';
import { SPComponentLoader } from '@microsoft/sp-loader';
import styles from './TestWebPart.module.scss';
import * as strings from 'TestWebPartStrings';

require('../../../node_modules/pannellum/build/pannellum.css');
require('../../../node_modules/pannellum/build/pannellum.js');

export default class TestWebPart extends BaseClientSideWebPart<ITestWebPartProps> {
  public render(): void {
    let innerHTML: string = `
    <script src="http://localhost:4321/node_modules/pannellum/build/pannellum.js"></script>
    <script>
      pannellum.viewer('panorama', {
        "type": "equirectangular",
        "panorama": "test2.jpg",
        "autoLoad": true
      });
    </script>
    <style>
      #panorama {
        width: 600px;
        height: 400px;
      }
    </style>
    <div id="panorama"></div>
    `;
    this.domElement.innerHTML = innerHTML;
    this.executeScript(this.domElement);
  }

  private evalScript(elem) {

    const data = (elem.text || elem.textContent || elem.innerHTML || '');
    const headTag = document.getElementsByTagName('head')[0] || document.documentElement;
    const scriptTag = document.createElement('script');

    scriptTag.type = 'text/javascript';
    if (elem.src && elem.src.length > 0) {
      return;
    }
    if (elem.onload && elem.onload.length > 0) {
      scriptTag.onload = elem.onload;
    }

    try {
      // doesn't work on ie...
      scriptTag.appendChild(document.createTextNode(data));
    } catch (e) {
      // IE has funky script nodes
      scriptTag.text = data;
    }

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

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

  // Finds and executes scripts in a newly added element's body.
  // Needed since innerHTML does not run scripts.
  //
  // Argument element is an element in the dom.
  private async executeScript(element: HTMLElement) {
    // Define global name to tack scripts on in case script to be loaded is not AMD/UMD

    if (!window['_spPageContextInfo']) {
      window['_spPageContextInfo'] = this.context.pageContext.legacyPageContext;
    }

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

    // main section of function
    const scripts = [];
    const childrenNodes = element.childNodes;

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

    const urls = [];
    const onLoads = [];
    for (let i: number = 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 = undefined;
    if (window['define'] && window['define'].amd) {
      oldamd = window['define'].amd;
      window['define'].amd = undefined;
    }

    for (let i: number = 0; i < urls.length; i++) {
      try {
        let scriptUrl = urls[i];
        const prefix = scriptUrl.indexOf('?') === -1 ? '?' : '&';
        scriptUrl += prefix + 'cow=' + 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: number = 0; scripts[i]; i++) {
      const scriptTag = scripts[i];
      if (scriptTag.parentNode) { scriptTag.parentNode.removeChild(scriptTag); }
      this.evalScript(scripts[i]);
    }
    // execute any onload people have added
    for (let i: number = 0; onLoads[i]; i++) {
      onLoads[i]();
    }
  }
}
2
Can you try to provide any small sample code which we can try to test with IE 11 and other browser to check the result? It can help us to get the idea about the issue. Image of the code will not help us to find the issue.Deepak-MSFT
I added a sample code. I hope it helps.David H.
IE does not well support URL.createObjectURL(). It is better to use window.navigator.msSaveOrOpenBlob, for non-ie, we can use URL.createObjectURL() Ref: stackoverflow.com/questions/24007073/… and developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURLDeepak-MSFT
Thanks you very much for this tipp. Do you know which file I have to modify? It's difficult to find details on that.David H.
You need to find all the occurrences where you are using URL.createObjectURL() and you need to use window.navigator.msSaveOrOpenBlob there. You can try to identify the browser using JS code and try to execute the specific code for specific browser.Deepak-MSFT

2 Answers

0
votes

I have not found any solution for this issue. Even other panorama viewers which were working as a standalone solution in the IE11, did not work in the combination of IE11, SharePoint 2016 and SPFx Webpart.

Every time it worked in the Sharepoint with iframe. After deploying it onto SharePoint, it stopped working because of exceptions. Most exceptions had to do with security.

The most promising approach was the Photo Sphere Viewer as iframe. The issue there was that the image had to be into the CDN directory with the other js- and HTML-files. Otherwise there was an exception because of security and CORS (Cross-Origin Resource Sharing).

0
votes

IE does not support URLSearchParams . Add the below snippet in your code(Polyfill), preferably in constructor():

(function (w) {

    w.URLSearchParams = w.URLSearchParams || function (searchString) {
        var self = this;
        self.searchString = searchString;
        self.get = function (name) {
            var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
            if (results == null) {
                return null;
            }
            else {
                return decodeURI(results[1]) || 0;
            }
        };
        self.has = function (name) {
            var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
            if (results == null) {
                return false;
            }
            else {
                return true;
            }
        }
    }

})(window);