diff --git a/Oqtane.Client/UI/Head.razor b/Oqtane.Client/UI/Head.razor
index c3b0f667..5ab1bc26 100644
--- a/Oqtane.Client/UI/Head.razor
+++ b/Oqtane.Client/UI/Head.razor
@@ -70,7 +70,7 @@
if (!script.Contains("><") && !script.Contains("data-reload"))
{
// add data-reload attribute to inline script
- headcontent = headcontent.Replace(script, script.Replace("") - pos), location.ToString().ToLower(), dataAttributes);
+ count += 1;
+ id = $"page{PageState.Page.PageId}-script{count}";
}
+ var pos = script.IndexOf(">") + 1;
+ await interop.IncludeScript(id, "", "", "", type, script.Substring(pos, script.IndexOf("") - pos), location.ToString().ToLower(), dataAttributes);
}
index = content.IndexOf("";
}
- else
- {
- return "";
- }
}
private void SetLocalizationCookie(string cookieValue)
diff --git a/Oqtane.Server/wwwroot/js/reload.js b/Oqtane.Server/wwwroot/js/reload.js
index bb9ad076..66ac9a4c 100644
--- a/Oqtane.Server/wwwroot/js/reload.js
+++ b/Oqtane.Server/wwwroot/js/reload.js
@@ -1,67 +1,74 @@
-const scriptInfoBySrc = new Map();
+const scriptKeys = new Set();
+
+export function onUpdate() {
+ // determine if this is an enhanced navigation
+ let enhancedNavigation = scriptKeys.size !== 0;
+
+ // iterate over all script elements in document
+ const scripts = document.getElementsByTagName('script');
+ for (const script of Array.from(scripts)) {
+ // only process scripts that include a data-reload attribute
+ if (script.hasAttribute('data-reload')) {
+ let key = getKey(script);
+
+ if (enhancedNavigation) {
+ // reload the script if data-reload is always or if the script has not been loaded previously and data-reload is once
+ let dataReload = script.getAttribute('data-reload');
+ if (dataReload === 'always' || (!scriptKeys.has(key) && dataReload == 'once')) {
+ reloadScript(script);
+ }
+ }
+
+ // save the script key
+ if (!scriptKeys.has(key)) {
+ scriptKeys.add(key);
+ }
+ }
+ }
+}
function getKey(script) {
- if (script.hasAttribute("src") && script.src !== "") {
+ if (script.src) {
return script.src;
+ } else if (script.id) {
+ return script.id;
} else {
return script.innerHTML;
}
}
-export function onUpdate() {
- let timestamp = Date.now();
- let enhancedNavigation = scriptInfoBySrc.size !== 0;
-
- // iterate over all script elements in page
- const scripts = document.getElementsByTagName("script");
- for (const script of Array.from(scripts)) {
- let key = getKey(script);
- let scriptInfo = scriptInfoBySrc.get(key);
- if (!scriptInfo) {
- // new script added
- scriptInfo = { timestamp: timestamp };
- scriptInfoBySrc.set(key, scriptInfo);
- if (enhancedNavigation) {
- reloadScript(script);
- }
- } else {
- // existing script
- scriptInfo.timestamp = timestamp;
- if (script.hasAttribute("data-reload") && script.getAttribute("data-reload") === "true") {
- reloadScript(script);
- }
+function reloadScript(script) {
+ try {
+ if (isValid(script)) {
+ replaceScript(script);
}
- }
-
- // remove scripts that are no longer referenced
- for (const [key, scriptInfo] of scriptInfoBySrc) {
- if (scriptInfo.timestamp !== timestamp) {
- scriptInfoBySrc.delete(key);
+ } catch (error) {
+ if (script.src) {
+ console.error(`Script Reload failed to load external script: ${script.src}`, error);
+ } else {
+ console.error(`Script Reload failed to load inline script: ${script.innerHTML}`, error);
}
}
}
-function reloadScript(script) {
- try {
- replaceScript(script);
- } catch (error) {
- if (script.hasAttribute("src") && script.src !== "") {
- console.error("Failed to load external script: ${script.src}", error);
- } else {
- console.error("Failed to load inline script: ${script.innerHtml}", error);
- }
+function isValid(script) {
+ if (script.innerHTML.includes('document.write(')) {
+ console.log(`Script using document.write() not supported by Script Reload: ${script.innerHTML}`);
+ return false;
}
+ return true;
}
function replaceScript(script) {
return new Promise((resolve, reject) => {
- var newScript = document.createElement("script");
+ var newScript = document.createElement('script');
// replicate attributes and content
for (let i = 0; i < script.attributes.length; i++) {
newScript.setAttribute(script.attributes[i].name, script.attributes[i].value);
}
newScript.innerHTML = script.innerHTML;
+ newScript.removeAttribute('data-reload');
// dynamically injected scripts cannot be async or deferred
newScript.async = false;
@@ -70,11 +77,10 @@ function replaceScript(script) {
newScript.onload = () => resolve();
newScript.onerror = (error) => reject(error);
- // remove existing script
+ // remove existing script element
script.remove();
- // replace with new script to force reload in Blazor
+ // replace with new script element to force reload in Blazor
document.head.appendChild(newScript);
});
-}
-
+}
\ No newline at end of file
diff --git a/Oqtane.Shared/Enums/ResourceLoadBehavior.cs b/Oqtane.Shared/Enums/ResourceLoadBehavior.cs
new file mode 100644
index 00000000..091a014f
--- /dev/null
+++ b/Oqtane.Shared/Enums/ResourceLoadBehavior.cs
@@ -0,0 +1,10 @@
+namespace Oqtane.Shared
+{
+ public enum ResourceLoadBehavior
+ {
+ Once,
+ Always,
+ Never,
+ BlazorPageScript
+ }
+}
diff --git a/Oqtane.Shared/Models/Resource.cs b/Oqtane.Shared/Models/Resource.cs
index 92b64bef..f3ee3349 100644
--- a/Oqtane.Shared/Models/Resource.cs
+++ b/Oqtane.Shared/Models/Resource.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using Oqtane.Shared;
namespace Oqtane.Models
@@ -13,7 +12,7 @@ namespace Oqtane.Models
private string _url;
///
- /// A so the Interop can properly create `script` or `link` tags
+ /// A to define the type of resource ie. Script or Stylesheet
///
public ResourceType ResourceType { get; set; }
@@ -45,7 +44,7 @@ namespace Oqtane.Models
public string CrossOrigin { get; set; }
///
- /// For Scripts a Bundle can be used to identify dependencies and ordering in the script loading process
+ /// For Scripts a Bundle can be used to identify dependencies and ordering in the script loading process (for Interactive rendering only)
///
public string Bundle { get; set; }
@@ -60,7 +59,7 @@ namespace Oqtane.Models
public ResourceLocation Location { get; set; }
///
- /// Allows specification of inline script - not applicable to Stylesheets
+ /// For Scripts this allows for the specification of inline script - not applicable to Stylesheets
///
public string Content { get; set; }
@@ -70,9 +69,9 @@ namespace Oqtane.Models
public string RenderMode { get; set; }
///
- /// Indicates that a script should be reloaded on every page transition - not applicable to Stylesheets
+ /// Specifies how a script should be loaded in Static rendering - not applicable to Stylesheets
///
- public bool Reload { get; set; }
+ public ResourceLoadBehavior LoadBehavior { get; set; }
///
/// Cusotm data-* attributes for scripts - not applicable to Stylesheets
@@ -96,7 +95,7 @@ namespace Oqtane.Models
resource.Location = Location;
resource.Content = Content;
resource.RenderMode = RenderMode;
- resource.Reload = Reload;
+ resource.LoadBehavior = LoadBehavior;
resource.DataAttributes = new Dictionary();
if (DataAttributes != null && DataAttributes.Count > 0)
{
@@ -125,5 +124,18 @@ namespace Oqtane.Models
};
}
}
+
+ [Obsolete("Reload is deprecated. Use LoadBehavior property instead for scripts.", false)]
+ public bool Reload
+ {
+ get => (LoadBehavior == ResourceLoadBehavior.BlazorPageScript);
+ set
+ {
+ if (value)
+ {
+ LoadBehavior = ResourceLoadBehavior.BlazorPageScript;
+ };
+ }
+ }
}
}
diff --git a/Oqtane.Shared/Models/Script.cs b/Oqtane.Shared/Models/Script.cs
index de44a5ce..1fd5d612 100644
--- a/Oqtane.Shared/Models/Script.cs
+++ b/Oqtane.Shared/Models/Script.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using Oqtane.Shared;
@@ -27,6 +28,13 @@ namespace Oqtane.Models
this.Type = Type;
}
+ public Script(string Content, ResourceLoadBehavior LoadBehavior)
+ {
+ SetDefaults();
+ this.Content = Content;
+ this.LoadBehavior = LoadBehavior;
+ }
+
public Script(string Src, string Integrity, string CrossOrigin)
{
SetDefaults();
@@ -35,6 +43,22 @@ namespace Oqtane.Models
this.CrossOrigin = CrossOrigin;
}
+ public Script(string Src, string Integrity, string CrossOrigin, string Type, string Content, ResourceLocation Location, string Bundle, ResourceLoadBehavior LoadBehavior, Dictionary DataAttributes, string RenderMode)
+ {
+ SetDefaults();
+ this.Url = Src;
+ this.Integrity = Integrity;
+ this.CrossOrigin = CrossOrigin;
+ this.Type = Type;
+ this.Content = Content;
+ this.Location = Location;
+ this.Bundle = Bundle;
+ this.LoadBehavior = LoadBehavior;
+ this.DataAttributes = DataAttributes;
+ this.RenderMode = RenderMode;
+ }
+
+ [Obsolete("This constructor is deprecated. Use constructor with LoadBehavior parameter instead.", false)]
public Script(string Src, string Integrity, string CrossOrigin, string Type, string Content, ResourceLocation Location, string Bundle, bool Reload, Dictionary DataAttributes, string RenderMode)
{
SetDefaults();
@@ -45,9 +69,10 @@ namespace Oqtane.Models
this.Content = Content;
this.Location = Location;
this.Bundle = Bundle;
- this.Reload = Reload;
+ this.LoadBehavior = (Reload) ? ResourceLoadBehavior.BlazorPageScript : ResourceLoadBehavior.Once;
this.DataAttributes = DataAttributes;
this.RenderMode = RenderMode;
}
+
}
}