Search code examples
blazorblazor-server-sidecodemirror

CodeMirror binding not working in Blazor App


I am working on a Blazor Server application that uses a code editor. For code editor I am using CodeMirror. The text area is rendering fine on the Blazor page but the data binding is not working with the corresponding C# field.

Blazor page screenshot

My _Host.cshtml file is as follows:

@page "/"
@namespace BlazorApp1.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = null;
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>BlazorApp1</title>
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />

    <script src="~/codemirror/codemirror.js"></script>
    <link rel="stylesheet" href="~/codemirror/codemirror.css">

    <script src="~/codemirror/mode/clike.js"></script>
    <script src="~/codemirror/addon/display/fullscreen.js"></script>
    <link rel="stylesheet" href="~/codemirror/addon/display/fullscreen.css">
    <link rel="stylesheet" href="/codemirror/theme/monokai.css">
    <script>
        function loadCodeEditor() {
            var codemirrorEditor = CodeMirror.fromTextArea(document.getElementById('codearea'), {
                lineNumbers: true,
                styleActiveLine: true,
                matchBrackets: true,
                mode: "text/x-csharp",
                theme: "monokai",
                extraKeys: {
                    "F11": function (cm) {
                        cm.setOption("fullScreen", !cm.getOption("fullScreen"));
                    },
                    "Esc": function (cm) {
                        if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
                    }
                }
            });
            codemirrorEditor.setSize(900, 300);
        }
    </script>

</head>
<body>
    <app>
        <component type="typeof(App)" render-mode="ServerPrerendered" />
    </app>

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.server.js"></script>
</body>
</html>

And the razor page code is as follows:

@page "/"
@inject IJSRuntime JSRuntime;

<h1>Hello, world!</h1>

Welcome to your new app.

<textarea id="codearea" rows="20" @bind="UserCodeInput" @bind:event="oninput" style="height:100px;"></textarea>

<SurveyPrompt Title="How is Blazor working for you?" />

Input Code: @UserCodeInput

@code{

    public string UserCodeInput { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JSRuntime.InvokeVoidAsync("loadCodeEditor");
        }
    }
}

If I remove id="codearea" from the textarea attribute and comment out the JSInterop method call then the binding works fine.

Please suggest on how to fix the binding with CodeMirror.


Solution

  • Finally I am able to bind the field with the CodeMirror editor. Thanks to below threads:

    When I edit the code in a TextArea using CodeMirror how to reflect this into another textarea with js or jQuery

    How to update c# value in Blazor using javascript?

    I had to pass the DotNet object reference while invoking the JavaScript function and then in JavaScript function use the onchange event of CodeMirror to invoke the C# method and pass the value of the code editor.

    Below are the updated files with the working code:

    _Host.cshtml

    @page "/"
    @namespace BlazorApp1.Pages
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    @{
        Layout = null;
    }
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>BlazorApp1</title>
        <base href="~/" />
        <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
        <link href="css/site.css" rel="stylesheet" />
    
        <script src="~/codemirror/codemirror.js"></script>
        <link rel="stylesheet" href="~/codemirror/codemirror.css">
    
        <script src="~/codemirror/mode/clike.js"></script>
        <script src="~/codemirror/addon/display/fullscreen.js"></script>
        <link rel="stylesheet" href="~/codemirror/addon/display/fullscreen.css">
        <link rel="stylesheet" href="/codemirror/theme/cobalt.css">
        <link rel="stylesheet" href="/codemirror/theme/monokai.css">
        
    
    </head>
    <body>
        <app>
            <component type="typeof(App)" render-mode="ServerPrerendered" />
        </app>
    
    
    
        <div id="blazor-error-ui">
            <environment include="Staging,Production">
                An error has occurred. This application may no longer respond until reloaded.
            </environment>
            <environment include="Development">
                An unhandled exception has occurred. See browser dev tools for details.
            </environment>
            <a href="" class="reload">Reload</a>
            <a class="dismiss">🗙</a>
        </div>
    
        <script src="_framework/blazor.server.js"></script>
        <script>
            function loadCodeEditor(dontNetObjRef) {
                    var codemirrorEditor = CodeMirror.fromTextArea(document.getElementById('codearea'), {
                        lineNumbers: true,
                        styleActiveLine: true,
                        matchBrackets: true,
                        mode: "text/x-csharp",
                        theme: "monokai",
                        extraKeys: {
                            "F11": function (cm) {
                                cm.setOption("fullScreen", !cm.getOption("fullScreen"));
                            },
                            "Esc": function (cm) {
                                if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
                            }
                        }
                    });
                codemirrorEditor.setSize(900, 500);
                codemirrorEditor.on("change", editor => {
                    dontNetObjRef.invokeMethodAsync("UpdateField", editor.getValue());
                    console.log(editor.getValue());
                });
                }
        </script>
    </body>
    </html>
    

    The razor page:

    @page "/"
    @inject IJSRuntime JSRuntime;
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <textarea id="codearea"></textarea>
    
    <SurveyPrompt Title="How is Blazor working for you?" />
    
    <br />
    <br />
    
    Input Code: @UserCodeInput
    
    @code{
    
        public string UserCodeInput { get; set; }
    
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
    
                await JSRuntime.InvokeVoidAsync("loadCodeEditor", DotNetObjectReference.Create(this));
            }
        }
    
        [JSInvokable("UpdateField")]
        public Task UpdateField(string codeValue)
        {
            UserCodeInput = codeValue;
            StateHasChanged();
            return Task.CompletedTask;
        }
    }