How to integrate TinyMCE with the all New Blazor Static Server render mode

JanusJanus
4 min read

Blazor static server-side rendering (static SSR), not to be confused with interactive SSR, has finally landed in .NET 8.

This mode does not require a websocket connection, but keep in mind pages are fully rendered on the server and delivered to the client as HTML. This means you can lose some of the exciting interactivity introduced by interactive SSR and CSR via WebAssembly. The upside is that without that constant websocket connection your application is now more scalable. Of course you can introduce interactivity back into the parts of your application that actually need it.

Now unto the main task at hand.

What we will try to achieve is integrating TinyMCE into your Static SSR Application without enabling any interactivity. We will accomplish this by configuring Javascript code to execute when a statically rendered page, possibly with enhanced navigation, is loaded or updated.

Step 1.
Register with tinyMCE and get your free API Key

Step 2.
Generate your Blazor Application. In this case I will just use the template that utilizes Interactive Auto, but per component/page. Not globally. With this mode you must enable interactivity where you need it, so by default everything is rendered in Static SSR Mode.

dotnet new blazor -int Auto -o MyTinyMceProject

Step 3.
Enter the project directory and create a Razor Class Library (RCL)

dotnet new razorclasslib -o MyTinyMceScriptInitilizer

Step 4.
Configure these projects such that the RCL is referenced by the main Blazor project

dotnet sln add MyTinyMceProject/MyTinyMceProject.csproj
dotnet sln add MyTinyMceScriptInitilizer/MyTinyMceScriptInitilizer.csproj
dotnet add MyTinyMceProject/MyTinyMceProject.csproj reference MyTinyMceScriptInitilizer/MyTinyMceScriptInitilizer.csproj

Step 5.
In the RCL we need to create two files. In the root of the project create a razor component MyPageScript.razor with the following code.

<page-script src="@Src"></page-script>

@code {
    [Parameter]
    [EditorRequired]
    public string Src { get; set; } = default!;
}

In the wwwroot folder of the RCL create a file MyTinyMceScriptInitilizer.lib.module.js with the following code.

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();
    }

    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);
}

Step 6.
In the main /Components/Pages folder of the main project create two files.

The first file will be your Blazor component in which you want the TinyMCE Editor. We will call this page MyTinyMcePage.razor and in it place the following code.

@page "/tinymce"

<MyTinyMceScriptInitilizer.MyPageScript Src="./Components/Pages/MyTinyMcePage.razor.js?tinymce" />

<EditForm Model="MyPageData" OnValidSubmit="HandleSubmit" FormName="tinymce-page" method="POST"/>
    <div>
        <label>Content:</label>
        <InputTextArea @bind-Value="MyPageData.Content" id="MyPageData_Content">
        </InputTextArea>
    </div>
</EditForm>

@code {

    [SupplyParameterFromForm(FormName = "tinyemce-page")]
    public MyPageModel MyPageData {get; set;} = new();

    private class MyPageModel
        {
           public string? Content { get; set; }
        }

        public void HandleSubmit() {
            Console.Writeline(MyPageData?.Content)
        }
}

The second file will sit right next to the Blazor component and will be called MyTinyMcePage.razor.js and will contain the following code.

TinyMceInit = function (selector) {
    tinymce.remove(`textarea#${selector}`);
    tinymce.init({
        selector: `textarea#${selector}`,
    });
};

export function onLoad() {
    TinyMceInit('MyPageData_Content');
}

export function onUpdate() {
    TinyMceInit('MyPageData_Content');

}

export function onDispose() {}

Step 7.
Finally, update your App.razor component in the root of your main project to have the link to the tinyMCE CDN. place this

    <script src="https://cdn.tiny.cloud/1/<[YOUR_TINYMCE_API_CODE]>/tinymce/6/tinymce.min.js"
        referrerpolicy="origin"></script>

below

    <script src="_framework/blazor.web.js"></script>

That's it! Launch your project, dotnet watch --project MyTinyMceProject --launch-profile https , and enjoy your freshly minted TinyMCE editor.

0
Subscribe to my newsletter

Read articles from Janus directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Janus
Janus