Avatar
← Back

Using JavaScript With Static Rendered Blazor Pages

User Avatar
YodasMyDad 15 days ago

In this blog post, we'll dive into how you can smoothly integrate JavaScript into your Blazor Web App, even when using static server-side rendering (static SSR) and enhanced navigation.

Why Static SSR and Enhanced Navigation?

When using static SSR, your page is rendered server-side, and with enhanced navigation, you can switch between pages without reloading everything.

The problem? Page-specific JavaScript won't run/fire when navigating between pages. But don’t worry, there is a simple solution for that.

Adding Highlight.js To This Blog

I stumbled on this issue when building this blog. I wanted to use the Block List Editor and use the Code Editor to display styled code output. I added the the highlight.js CDN link and then called hljs.highlightAll() on my page. When I refreshed the page, the script ran, however, when I navigated to the page the script didn't run, because of how Blazor enhanced navigation works.

To get around this, you need to add the Javascript file below in the root of your wwwroot in your Blazor RCL, making sure you follow the naming convention of

YourProjectName.lib.module.js

const pageScriptInfoBySrc = new Map();

function registerPageScriptElement(src) {
    if (!src) {
        throw new Error('Must provide a non-empty value for the "src" attribute.');
    }

    let pageScriptInfo = pageScriptInfoBySrc.get(src);

    if (pageScriptInfo) {
        pageScriptInfo.referenceCount++;
    } else {
        pageScriptInfo = {referenceCount: 1, module: null};
        pageScriptInfoBySrc.set(src, pageScriptInfo);
        initializePageScriptModule(src, pageScriptInfo);
    }
}

function unregisterPageScriptElement(src) {
    if (!src) {
        return;
    }

    const pageScriptInfo = pageScriptInfoBySrc.get(src);

    if (!pageScriptInfo) {
        return;
    }

    pageScriptInfo.referenceCount--;
}

async function initializePageScriptModule(src, pageScriptInfo) {
    if (src.startsWith("./")) {
        src = new URL(src.substr(2), document.baseURI).toString();
    } else {
        src = new URL(src, document.baseURI).toString();
    }

    const module = await import(src);

    if (pageScriptInfo.referenceCount <= 0) {
        return;
    }

    pageScriptInfo.module = module;
    module.onLoad?.();
    module.onUpdate?.();
}

function onEnhancedLoad() {
    for (const [src, {module, referenceCount}] of pageScriptInfoBySrc) {
        if (referenceCount <= 0) {
            module?.onDispose?.();
            pageScriptInfoBySrc.delete(src);
        }
    }

    for (const {module} of pageScriptInfoBySrc.values()) {
        module?.onUpdate?.();
    }
}

export function afterWebStarted(blazor) {
    customElements.define('page-script', class extends HTMLElement {
        static observedAttributes = ['src'];

        attributeChangedCallback(name, oldValue, newValue) {
            if (name !== 'src') {
                return;
            }

            this.src = newValue;
            unregisterPageScriptElement(oldValue);
            registerPageScriptElement(newValue);
        }

        disconnectedCallback() {
            unregisterPageScriptElement(this.src);
        }
    });

    blazor.addEventListener('enhancedload', onEnhancedLoad);
}

Naming Convention

Something which caught me out with this, is the naming convention must reflect your Assembly name! For this project I have a project name of Lee.Components, but my assembly is ZauberCMS.Lee.Components. So I need to make sure the name space is ZauberCMS.Lee.Components.lib.module.js!

Now you have this script in your wwwroot, you now want to create an inline JS page for the component. In my case I have a BlogPage.razor where I render all my block lists. So I have created a BlogPage.razor.js for that page like so.

Project Structure For SSR JS

And within the file, add the following JS

export function onLoad() {
    console.log('Loaded');
}

export function onUpdate() {
    console.log('Updated');
}

export function onDispose() {
    console.log('Disposed');
}

So we have a .lib file added to the root, and an inline Js file added to my BlogPage. The last thing to do is add a PageScript component that renders the inline js files. Add the following component to your project

<page-script src="@Src"></page-script>
@code {
    [Parameter, EditorRequired] public string Src { get; set; } = default!;
}

Now you just need to add your PageScript component to your App.Razor or main layout page at the bottom like so

<PageScript Src="./Lee.Components/ContentViews/BlogPage.razor.js?counter" />

Assembly Name

Remember, if you have a different assembly name to your project then you will need to manually link the file with the full path like so

_content/ZauberCMS.Lee.Components/ContentViews/BlogPage.razor.js

Once you have this all setup, you can then make use of the onLoad() and onUpdate() methods. In my case because I have added a link to the CDN for highlight js. I now just need to update it like this

export function onLoad() {
    hljs.highlightAll();
}

export function onUpdate() {
    hljs.highlightAll();
}
© 2024 Lee - Powered By ZauberCMS