BlazorMonaco icon indicating copy to clipboard operation
BlazorMonaco copied to clipboard

Two way binding with Editor

Open kvinita opened this issue 1 year ago • 1 comments

I have installed and configured the BlazorMonaco in my Blazor WebAssembly application as directed here: https://github.com/serdarciplak/BlazorMonaco

The editor is shown below: image

Below is my razor file: Home.razor

@page "/"

<PageTitle>Home</PageTitle>

<div class="parent">
    <div class="header">
        <button data-toggle="tooltip" data-placement="bottom" title="Format"
                @onclick=@(()=>FormatXml())>
            <svg version="1.1" class="fa-icon svelte-1mc5hvj" fill="white" width="16" height="16" aria-label="" role="presentation" viewBox="0 0 512 512" style=""><path d="M 0,32 v 64 h 416 v -64 z M 160,160 v 64 h 352 v -64 z M 160,288 v 64 h 288 v -64 z M 0,416 v 64 h 320 v -64 z"></path></svg>
        </button>

        <button data-toggle="tooltip" data-placement="bottom" title="Compact"
                @onclick=@(()=>CompactXml())>
            <svg version="1.1" class="fa-icon svelte-1mc5hvj" fill="white" width="16" height="16" aria-label="" role="presentation" viewBox="0 0 512 512" style=""><path d="M 0,32 v 64 h 512 v -64 z M 0,160 v 64 h 512 v -64 z M 0,288 v 64 h 352 v -64 z"></path></svg>
        </button>
    </div>
    <StandaloneCodeEditor Id="my-editor-id" ConstructionOptions="EditorConstructionOptions" CssClass="my-editor-class" />
</div>

@code {
    private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor)
    {
        return new StandaloneEditorConstructionOptions
            {
                AutomaticLayout = true,
                Language = "xml"
            };
    }
}

On click of a button, I need to get the entire text in the editor and perform some operations on it and display the modified text in the editor. Basically I need a way to do two-way binding with the editor. How do I achieve this?

Let me know if additional details are required. Any leads would be appreciated.

kvinita avatar May 15 '24 07:05 kvinita

My workarouond for now is to use a reference with GetValue and SetValue. Simple example is like this:

<button data-toggle="tooltip" data-placement="bottom" title="Compact"
                @onclick=@(()=>CompactXml())>
<StandaloneCodeEditor @ref="editor" OnDidChangeModelContent="OnChange" />

@code {
    private StandaloneCodeEditor editor = null!;
    private string cache = "";

    protected override void OnInitialized()
    {
        editor = new();
    }

    private async Task OnChange()
    {
        cache = await editor.GetValue();
    }

    private async Task CompactXml()
    {
        // modify the cache
        await editor.SetValue(cache);
    }
}

CHL-NexAIoT avatar Sep 19 '24 03:09 CHL-NexAIoT

BlazorMonaco does not support two-way value binding as that would require the whole editor value to be passed between JS and Blazor on every value change. And it's even worse for Blazor server apps, as the data also needs to be transferred between the client and the server. As the value gets bigger, things would get even slower and completely break at some point.

As suggested in the above comment, you can manually call editor.GetValue() to get the value only when you need it.

serdarciplak avatar Dec 25 '24 22:12 serdarciplak

You can create your own wrapper component which supports two way binding.

CodeInput.razor

@using BlazorMonaco.Editor
@inject IJSRuntime JSRuntime

<StandaloneCodeEditor
    Id="@_id"
    ConstructionOptions="@(_ => _options)"
    @ref="_editorRef"
    OnDidChangeModelContent="@(_ => OnContentChanged())" />

@code {
    private readonly string _id = Guid.NewGuid().ToString();
    private StandaloneEditorConstructionOptions _options = new();
    private StandaloneCodeEditor? _editorRef;
    private bool _rendered;

    [Parameter] public string Language { get; set; } = "html";
    [Parameter] public string? Value { get; set; }
    [Parameter] public EventCallback<string?> ValueChanged { get; set; }

    protected override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            _rendered = true;
        }
        return base.OnAfterRenderAsync(firstRender);
    }

    protected override async Task OnInitializedAsync()
    {
        _options = new StandaloneEditorConstructionOptions
        {
            AutomaticLayout = true,
            Language = Language
        };

        if (Value != null)
        {
            while (_editorRef == null || !_rendered)
            {
                await Task.Delay(100);
            }
            await _editorRef.SetValue(Value);
        }
    }

    private Task OnContentChanged()
    {
        if (_editorRef == null)
        {
            return Task.CompletedTask;
        }
        _ = Task.Run(async () =>
        {
            Value = await _editorRef.GetValue();
            await ValueChanged.InvokeAsync(Value);
        });
        return Task.CompletedTask;
    }
}

pandapknaepel avatar Feb 04 '25 08:02 pandapknaepel

You can create your own wrapper component which supports two way binding.

Be careful, HTML ids cannot start with numbers, is better to add a prefix:

private readonly string _id = $"editor-{Guid.NewGuid().ToString()}";

This prevents this error:

Microsoft.JSInterop.JSException: Failed to execute 'querySelector' on 'Document': '#1e16aa0f-8738-477a-9d1f-cb0cef5d664c-preview span[contenteditable='true']' is not a valid selector.

easis avatar Mar 23 '25 22:03 easis