diff --git a/src/sdk/Formio.ts b/src/sdk/Formio.ts index 669d87d0..b211d577 100644 --- a/src/sdk/Formio.ts +++ b/src/sdk/Formio.ts @@ -7,6 +7,7 @@ import EventEmitter from 'eventemitter3'; import cookies from 'browser-cookies'; const { fetch, Headers } = fetchPonyfill(); import Plugins from './Plugins'; +import { attachResourceToDom } from 'utils'; declare const OktaAuth: any; /** @@ -2366,15 +2367,21 @@ export class Formio { * @param {string} property - The name of the global property that will be added to the global namespace once the library has been loaded. This is used to check to see if the property exists before resolving the promise that the library is ready for use. * @param {string} src - The URL of the library to lazy load. * @param {boolean} polling - Determines if polling should be used to determine if they library is ready to use. If set to false, then it will rely on a global callback called ${name}Callback where "name" is the first property passed to this method. When this is called, that will indicate when the library is ready. In most cases, you will want to pass true to this parameter to initiate a polling method to check for the library availability in the global context. + * @param {HTMLElement} rootElement - The element after which the resource would be attached (useful when requiring resources from ShadowRoot). * @return {Promise} - A promise that will resolve when the plugin is ready to be used. */ - static requireLibrary( - name: string, - property: string, - src: string | Array, - polling: boolean = false, - onload?: (ready: Promise) => void, - ) { + static requireLibrary(name: string, property: string, src: string | Array, polling: boolean = false, onload?: (ready: Promise) => void, rootElement?: HTMLElement ) { + + const resourceToDomOptions = { + name, + src, + formio:Formio, + onload, + rootElement + } + + let hasResourceBeenAdded = false + if (!Formio.libraries.hasOwnProperty(name)) { Formio.libraries[name] = {}; Formio.libraries[name].ready = new Promise((resolve, reject) => { @@ -2392,55 +2399,10 @@ export class Formio { const plugin = get(window, property); if (plugin) { Formio.libraries[name].resolve(plugin); - } else { - src = Array.isArray(src) ? src : [src]; - src.forEach((lib: any) => { - let attrs: any = {}; - let elementType = ''; - if (typeof lib === 'string') { - lib = { - type: 'script', - src: lib, - }; - } - switch (lib.type) { - case 'script': - elementType = 'script'; - attrs = { - src: lib.src, - type: 'text/javascript', - defer: true, - async: true, - referrerpolicy: 'origin', - }; - break; - case 'styles': - elementType = 'link'; - attrs = { - href: lib.src, - rel: 'stylesheet', - }; - break; - } - - // Add the script to the top of the page. - const element = document.createElement(elementType); - if (element.setAttribute) { - for (const attr in attrs) { - element.setAttribute(attr, attrs[attr]); - } - } - if (onload) { - element.addEventListener('load', () => { - Formio.libraries[name].loaded = true; - onload(Formio.libraries[name].ready); - }); - } - const { head } = document; - if (head) { - head.appendChild(element); - } - }); + } + else { + attachResourceToDom(resourceToDomOptions) + hasResourceBeenAdded = true; // if no callback is provided, then check periodically for the script. if (polling) { @@ -2456,6 +2418,11 @@ export class Formio { } const lib = Formio.libraries[name]; + + if(rootElement && !hasResourceBeenAdded) { + attachResourceToDom(resourceToDomOptions); + } + return onload && lib.loaded ? onload(lib.ready) : lib.ready; } diff --git a/src/types/ResourceToDomOptions.ts b/src/types/ResourceToDomOptions.ts new file mode 100644 index 00000000..5ea60a33 --- /dev/null +++ b/src/types/ResourceToDomOptions.ts @@ -0,0 +1,9 @@ +import { Formio } from "sdk"; + +export type ResourceToDomOptions = { + name: string, + src: string | Array, + formio: typeof Formio, + onload?: (ready: Promise) => void, + rootElement?: HTMLElement +}; diff --git a/src/types/index.ts b/src/types/index.ts index a30eaaf3..5f2d0d0f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -10,3 +10,4 @@ export * from './process'; export * from './DataObject'; export * from './formUtil'; export * from './PassedComponentInstance'; +export * from './ResourceToDomOptions'; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index fd49f503..b78ad80a 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,5 +1,5 @@ import { isBoolean, isString } from 'lodash'; -import { BaseComponent, Component } from 'types'; +import { BaseComponent, Component, ResourceToDomOptions } from 'types'; /** * Escapes RegEx characters in provided String value. @@ -64,6 +64,63 @@ export function registerEphemeralState( }); } +export function attachResourceToDom(options: ResourceToDomOptions) { + const { name, formio, onload, rootElement } = options; + let { src } = options; + src = Array.isArray(src) ? src : [src]; + src.forEach((lib: any) => { + let attrs: any = {}; + let elementType = ''; + if (typeof lib === 'string') { + lib = { + type: 'script', + src: lib, + }; + } + switch (lib.type) { + case 'script': + elementType = 'script'; + attrs = { + src: lib.src, + type: 'text/javascript', + defer: true, + async: true, + referrerpolicy: 'origin', + }; + break; + case 'styles': + elementType = 'link'; + attrs = { + href: lib.src, + rel: 'stylesheet', + }; + break; + } + + // Add the script to the top of the page. + const element = document.createElement(elementType); + if (element.setAttribute) { + for (const attr in attrs) { + element.setAttribute(attr, attrs[attr]); + } + } + if (onload) { + element.addEventListener('load', () => { + formio.libraries[name].loaded = true; + onload(formio.libraries[name].ready); + }); + } + if (rootElement) { + rootElement.insertAdjacentElement('afterend', element); + return; + } + const { head } = document; + if (head) { + head.appendChild(element); + } + }); +} + export function resetEphemeralState(component: Component) { if (component.ephemeralState) { delete component.ephemeralState;