From 963148c639a2bbfddbbbad937b8bf8cdd6df89ce Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Wed, 27 May 2020 16:03:38 -0400 Subject: [PATCH] Refactor Javascript and Stylesheet loading --- Oqtane.Client/UI/Interop.cs | 35 ++++++++++----- Oqtane.Client/UI/ThemeBuilder.razor | 28 ++++++------ Oqtane.Package/Oqtane.Client.nuspec | 2 +- Oqtane.Package/Oqtane.Server.nuspec | 2 +- Oqtane.Package/Oqtane.Shared.nuspec | 2 +- Oqtane.Server/Pages/_Host.cshtml | 6 +-- Oqtane.Server/wwwroot/js/interop.js | 69 ++++++++++++++++++++++------- 7 files changed, 97 insertions(+), 47 deletions(-) diff --git a/Oqtane.Client/UI/Interop.cs b/Oqtane.Client/UI/Interop.cs index c098ad34..1c05fe41 100644 --- a/Oqtane.Client/UI/Interop.cs +++ b/Oqtane.Client/UI/Interop.cs @@ -1,4 +1,5 @@ using Microsoft.JSInterop; +using Oqtane.Models; using System.Threading.Tasks; namespace Oqtane.UI @@ -56,13 +57,13 @@ namespace Oqtane.UI } } - public Task IncludeMeta(string id, string attribute, string name, string content) + public Task IncludeMeta(string id, string attribute, string name, string content, string key) { try { _jsRuntime.InvokeAsync( "Oqtane.Interop.includeMeta", - id, attribute, name, content); + id, attribute, name, content, key); return Task.CompletedTask; } catch @@ -71,13 +72,13 @@ namespace Oqtane.UI } } - public Task IncludeLink(string id, string rel, string url, string type, string integrity, string crossorigin) + public Task IncludeLink(string id, string rel, string href, string type, string integrity, string crossorigin, string key) { try { _jsRuntime.InvokeAsync( "Oqtane.Interop.includeLink", - id, rel, url, type, integrity, crossorigin); + id, rel, href, type, integrity, crossorigin, key); return Task.CompletedTask; } catch @@ -86,13 +87,28 @@ namespace Oqtane.UI } } - public Task IncludeScript(string id, string src, string content, string location, string integrity, string crossorigin) + public Task IncludeLinks(object[] links) + { + try + { + _jsRuntime.InvokeAsync( + "Oqtane.Interop.includeLinks", + (object) links); + return Task.CompletedTask; + } + catch + { + return Task.CompletedTask; + } + } + + public Task IncludeScript(string id, string src, string integrity, string crossorigin, string content, string location, string key) { try { _jsRuntime.InvokeAsync( "Oqtane.Interop.includeScript", - id, src, content, location, integrity, crossorigin); + id, src, integrity, crossorigin, content, location, key); return Task.CompletedTask; } catch @@ -101,13 +117,13 @@ namespace Oqtane.UI } } - public Task IncludeCSS(string id, string url) + public Task IncludeScripts(object[] scripts) { try { _jsRuntime.InvokeAsync( - "Oqtane.Interop.includeLink", - id, "stylesheet", url, "text/css"); + "Oqtane.Interop.includeScripts", + (object)scripts); return Task.CompletedTask; } catch @@ -131,7 +147,6 @@ namespace Oqtane.UI } } - public ValueTask GetElementByName(string name) { try diff --git a/Oqtane.Client/UI/ThemeBuilder.razor b/Oqtane.Client/UI/ThemeBuilder.razor index ffe91273..2b40e59e 100644 --- a/Oqtane.Client/UI/ThemeBuilder.razor +++ b/Oqtane.Client/UI/ThemeBuilder.razor @@ -23,31 +23,31 @@ await interop.UpdateTitle(PageState.Site.Name + " - " + PageState.Page.Name); } - // update page resources - int stylesheet = 0; - int script = 0; + // include page resources + var links = new List(); + var scripts = new List(); foreach (Resource resource in PageState.Page.Resources) { switch (resource.ResourceType) { case ResourceType.Stylesheet: - stylesheet += 1; - await interop.IncludeLink("app-stylesheet" + stylesheet.ToString("00"), "stylesheet", resource.Url, "text/css", resource.Integrity ?? "", resource.CrossOrigin ?? ""); + links.Add(new { id = "app-stylesheet" + links.Count.ToString("00"), rel = "stylesheet", href = resource.Url, type = "text/css", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", key = "" }); break; case ResourceType.Script: - script += 1; - await interop.IncludeScript("app-script" + script.ToString("00"), resource.Url, "", "body", resource.Integrity ?? "", resource.CrossOrigin ?? ""); + scripts.Add(new { id = "app-script" + scripts.Count.ToString("00"), src = resource.Url, integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", content = "", location = "body", key = "" }); break; } } - // remove any page resources references which are no longer required for this page - await interop.RemoveElementsById("app-stylesheet", "app-stylesheet" + (stylesheet + 1).ToString("00"), ""); - await interop.RemoveElementsById("app-script", "app-script" + (script + 1).ToString("00"), ""); + await interop.IncludeLinks(links.ToArray()); + await interop.IncludeScripts(scripts.ToArray()); + // remove any page resource references which are no longer required for this page + await interop.RemoveElementsById("app-stylesheet", "app-stylesheet" + links.Count.ToString("00"), ""); + await interop.RemoveElementsById("app-script", "app-script" + scripts.Count.ToString("00"), ""); // add favicon if (PageState.Site.FaviconFileId != null) { - await interop.IncludeLink("fav-icon", "shortcut icon", Utilities.ContentUrl(PageState.Alias, PageState.Site.FaviconFileId.Value), "image/x-icon", "", ""); + await interop.IncludeLink("app-favicon", "shortcut icon", Utilities.ContentUrl(PageState.Alias, PageState.Site.FaviconFileId.Value), "image/x-icon", "", "", "id"); } // add PWA support if (PageState.Site.PwaIsEnabled) @@ -97,10 +97,10 @@ "const serialized = JSON.stringify(manifest); " + "const blob = new Blob([serialized], {type: 'application/javascript'}); " + "const url = URL.createObjectURL(blob); " + - "document.getElementById('pwa-manifest').setAttribute('href', url); " + + "document.getElementById('app-manifest').setAttribute('href', url); " + "} " + ", 1000);"; - await interop.IncludeScript("pwa-manifestscript", "", manifest, "body", "", ""); + await interop.IncludeScript("app-pwa", "", "", "", manifest, "body", "id"); // service worker must be in root of site string serviceworker = "if ('serviceWorker' in navigator) { " + @@ -110,6 +110,6 @@ "console.log('ServiceWorker Registration Failed ', err); " + "}); " + "}"; - await interop.IncludeScript("pwa-serviceworker", "", serviceworker, "body", "", ""); + await interop.IncludeScript("app-serviceworker", "", "", "", serviceworker, "body", "id"); } } diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index 72947da2..b826a37d 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -12,7 +12,7 @@ MIT https://github.com/oqtane/oqtane.framework https://www.oqtane.org/Portals/0/icon.jpg - oqtane framework + oqtane Initial Release A modular application framework for Blazor diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index c6f9dd6e..2645e9c2 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -12,7 +12,7 @@ MIT https://github.com/oqtane/oqtane.framework https://www.oqtane.org/Portals/0/icon.jpg - oqtane framework + oqtane Initial Release A modular application framework for Blazor diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index 8ac8924f..8419aef2 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -12,7 +12,7 @@ MIT https://github.com/oqtane/oqtane.framework https://www.oqtane.org/Portals/0/icon.jpg - oqtane framework + oqtane Initial Release A modular application framework for Blazor diff --git a/Oqtane.Server/Pages/_Host.cshtml b/Oqtane.Server/Pages/_Host.cshtml index a3ed9580..e8795611 100644 --- a/Oqtane.Server/Pages/_Host.cshtml +++ b/Oqtane.Server/Pages/_Host.cshtml @@ -11,11 +11,11 @@ Oqtane - + - + - + @(Html.AntiForgeryToken()) diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index 4317b07f..f94c3472 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -27,9 +27,9 @@ Oqtane.Interop = { document.title = title; } }, - includeMeta: function (id, attribute, name, content) { + includeMeta: function (id, attribute, name, content, key) { var meta; - if (id !== "") { + if (id !== "" && key === "id") { meta = document.getElementById(id); } else { @@ -50,13 +50,13 @@ Oqtane.Interop = { } } }, - includeLink: function (id, rel, url, type, integrity, crossorigin) { + includeLink: function (id, rel, href, type, integrity, crossorigin, key) { var link; - if (id !== "") { + if (id !== "" && key === "id") { link = document.getElementById(id); } else { - link = document.querySelector("link[href=\"" + CSS.escape(url) + "\"]"); + link = document.querySelector("link[href=\"" + CSS.escape(href) + "\"]"); } if (link === null) { link = document.createElement("link"); @@ -67,7 +67,7 @@ Oqtane.Interop = { if (type !== "") { link.type = type; } - link.href = url; + link.href = href; if (integrity !== "") { link.integrity = integrity; } @@ -87,10 +87,10 @@ Oqtane.Interop = { } else { link.removeAttribute('type'); } - if (link.href !== url) { + if (link.href !== this.getAbsoluteUrl(href)) { link.removeAttribute('integrity'); link.removeAttribute('crossorigin'); - link.setAttribute('href', url); + link.setAttribute('href', href); } if (integrity !== "") { if (link.integrity !== integrity) { @@ -108,11 +108,19 @@ Oqtane.Interop = { } } }, - includeScript: function (id, src, content, location, integrity, crossorigin) { + includeLinks: function (links) { + for (let i = 0; i < links.length; i++) { + this.includeLink(links[i].id, links[i].rel, links[i].href, links[i].type, links[i].integrity, links[i].crossorigin, links[i].key); + } + }, + includeScript: function (id, src, integrity, crossorigin, content, location, key) { var script; - if (id !== "") { + if (id !== "" && key === "id") { script = document.getElementById(id); } + else { + script = document.querySelector("script[src=\"" + CSS.escape(src) + "\"]"); + } if (script === null) { script = document.createElement("script"); if (id !== "") { @@ -131,16 +139,17 @@ Oqtane.Interop = { script.innerHTML = content; } script.async = false; - if (location === 'head') { - document.head.appendChild(script); - } - if (location === 'body') { - document.body.appendChild(script); - } + this.loadScript(script, location) + .then(() => { + console.log(src + ' loaded'); + }) + .catch(() => { + console.error(src + ' failed'); + }); } else { if (src !== "") { - if (script.src !== src) { + if (script.src !== this.getAbsoluteUrl(src)) { script.removeAttribute('integrity'); script.removeAttribute('crossorigin'); script.src = src; @@ -167,6 +176,32 @@ Oqtane.Interop = { } } }, + loadScript: function (script, location) { + if (location === 'head') { + document.head.appendChild(script); + } + if (location === 'body') { + document.body.appendChild(script); + } + + return new Promise((res, rej) => { + script.onload = res(); + script.onerror = rej(); + }); + }, + includeScripts: function (scripts) { + for (let i = 0; i < scripts.length; i++) { + this.includeScript(scripts[i].id, scripts[i].src, scripts[i].integrity, scripts[i].crossorigin, scripts[i].content, scripts[i].location, scripts[i].key); + } + }, + getAbsoluteUrl: function (url) { + var a = document.createElement('a'); + getAbsoluteUrl = function (url) { + a.href = url; + return a.href; + } + return getAbsoluteUrl(url); + }, removeElementsById: function (prefix, first, last) { var elements = document.querySelectorAll('[id^=' + prefix + ']'); for (var i = elements.length - 1; i >= 0; i--) {