如何优雅的移植JavaScript组件到Blazor 您所在的位置:网站首页 调用css的代码 如何优雅的移植JavaScript组件到Blazor

如何优雅的移植JavaScript组件到Blazor

2023-10-24 18:17| 来源: 网络整理| 查看: 265

Blazor作为一个新兴的交互式 Web UI 的框架,有其自身的优缺点,如果现有的 JavaScript 组件能移植到 Blazor,无疑让 Blazor 如虎添翼,本文就介绍一下自己在开发 BulmaRazor 组件库的时,封装现有的 JavaScript 组件的方法,文中以 TuiEditor 为例。

开始

首先找到现有 TuiEditor 的主页或者文档,这一步很简单,我们找到官网 https://ui.toast.com/tui-editor/ ,分析一下组件的使用方法,一般都是有样式文件,有 JavaScript 文件,有一个 options 对象来初始化一个主对象,主对象上有方法和事件,大概就是这些了,我们先下载所需的文件,然后一步一步处理。

样式部分

该组件需要两个样式 codemirror.min.css 和 toastui-editor.min.css ,由于一个组件库不只这一个组件,为了引用方便,我们需要使用 BuildBundlerMinifier 合并文件,不知道 BuildBundlerMinifier 的同学网上查一下。 在网站的根目录需要有 BuildBundlerMinifier 所需的配置文件 bundleconfig.json,对应的配置如下 :

{ "outputFileName": "wwwroot/bulmarazor.min.css", "inputFiles": [ "wwwroot/css/tuieditor/codemirror.min.css", "wwwroot/css/tuieditor/toastui-editor.min.css" ] },

项目中很可能还有其他的样式文件,一起合并就好了,引用的时候我们只需要一个样式文件,这里就是 bulmarazor.min.css。

脚本部分

tuieditor 的 JavaScript 文件只有一个,当然一般 JavaScript 组件的脚本文件都是一个,如果是普通的 web 开发的话直接引入就可以了,但是在 Blazor 中有些麻烦,需要使用 JavaScript 互操作,互操作是指 C# 代码可调用到 JavaScript 代码,而 JavaScript 代码也可调用到 C# 代码。

C# 调用 JavaScript 代码有两种方法,一种是使用 IJSRuntime 调用挂载到 window 对象上的方法,另一种是使用模块隔离的方式调用,这里我们需要模块隔离,因为有以下优点:

导入的 JavaScript 不再污染全局命名空间。 库和组件的使用者不需要引用相关的 JavaScript。

关于 JavaScript 模块,可以参考这里 这里 ,使用 JavaScript 模块依赖于 import 和 export,而一般的 JavaScript 类库并不支持,所以我们需要些一些导出的代码,文件结构如下: 我们忽视红色标注,先来看一下 toastui-editor-export.js 这个文件:

export function initEditor(options) { options.el = document.getElementById(options.elid); let editor = new toastui.Editor.factory(options); return editor; }

toastui-editor-all.min.js 这个文件就是 JavaScript 组件文件,我们不用去改它,也不应该去改它,因为后续升级了我们可以直接覆盖的,toastui-editor-export.js 就是我们专门写的一个导出类库中所需功能的导出文件。为了引用方便我们还是需要合并一下,就是图片示现的那样,合并配置如下:

{ "outputFileName": "wwwroot/js/tuieditor.min.js", "inputFiles": [ "wwwroot/jsplugin/tuieditor/toastui-editor-all.min.js", "wwwroot/jsplugin/tuieditor/toastui-editor-export.js" ] }

现在我们使用隔离的方式引用 wwwroot/js/tuieditor.min.js 就可以了。当我们新建一个Razor组件项目的时候,会带有调用的例子,我们比猫画虎搞定:

using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; namespace BulmaRazor.Components { public class BulmaRazorJsInterop : IAsyncDisposable { private readonly Lazy tuiEditorModuleTask; public BulmaRazorJsInterop(IJSRuntime jsRuntime) { tuiEditorModuleTask = new(() => jsRuntime.InvokeAsync( "import", "./_content/BulmaRazor/js/tuieditor.min.js").AsTask()); } public async ValueTask TuiEditorInit(TuiEditorOptions options) { var module = await tuiEditorModuleTask.Value; return await module.InvokeAsync("initEditor", options.ToParams()); } public async ValueTask DisposeAsync() { if (tuiEditorModuleTask.IsValueCreated) { var module = await tuiEditorModuleTask.Value; await module.DisposeAsync(); } } } } Blazor 组件部分

组件文件是 TuiEditor.razor,UI代码是非常简单的,就是一个带有 id 属性的 div 容器,id 很重要,是我们互操作的基础,这里我们使用GUID生成唯一的id。 我们需要在 blazor 组件呈现之后调用 JavaScript 代码来初始化我们的 JavaScript 组件,调用 JavaScript 代码之后返回了js 对象的引用editor,注意editor和上述 var module = await tuiEditorModuleTask.Value; 中的 module 是一样的,都是 JavaScript 对象引用。大致的代码如下:

@inject BulmaRazorJsInterop JsInterop @code { readonly string Id = "tuiEditor_" + Guid.NewGuid().ToString("N"); IJSObjectReference editor; [Parameter] public TuiEditorOptions Options { get; set; } protected override void OnInitialized() { if (Options == null) Options = new TuiEditorOptions(); Options.elid = Id; base.OnInitialized(); } protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); editor = await JsInterop.TuiEditorInit(Options); } } Options选项部分

TuiEditor 组件中有个参数 TuiEditorOptions ,是要对应 JavaScript 中的 options 参数的,我们需要自己定义一个,这里我们使用两个类来实现,一个是针对 JavaScript 的 JsParams 类似字典的对象,一个是针对使用者的 TuiEditorOptions 。 JsParams 就是一个Dictionary,为了方便,我们过滤了空值:

internal class JsParams:Dictionary { public void AddNotNull(string key, object value) { if (value != null) { base.Add(key,value); } } }

TuiEditorOptions 类除了参数之外,包含一个 ToParams() 的方法把自己转换成 JsParams:

public class TuiEditorOptions { internal string elid { get; set; } /// /// Editor's height style value. Height is applied as border-box ex) '300px', '100%', 'auto' /// public string Height { get; set; } /// /// 是否是查看器 /// public bool? Viewer { get; set; } //...其他参数 internal JsParams ToParams() { JsParams ps = new JsParams(); var def = BulmaRazorOptions.DefaultOptions.TuiEditorOptions; ps.AddNotNull("elid", elid); ps.AddNotNull("viewer",Viewer); ps.AddNotNull("height", Height ?? def.Height); //...其他参数 return ps; } }

有几个原因使用 JsParams :

null值可以不传递,因为js的options一般都用默认值,减少传输; 可以使用默认设置,如上有个BulmaRazorOptions.DefaultOptions.TuiEditorOptions; 可以灵活的手动处理参数,上面例子没有提现出来,不过组件写多了肯定会遇到这种情况; 对象的方法

JavaScript 组件一般也会公开许多实例方法,比如获得焦点,设置内容,获取内容等等,在前面我们已经保存了 JavaScript 组件实例的引用,也就是在 TuiEditor 中的 editor 对象,需要公开哪些方法在 TuiEditor.razor 中添加就是了:

public void Focus() { editor?.InvokeVoidAsync("focus"); } public ValueTask GetMarkdown() { return editor?.InvokeAsync("getMarkdown") ?? new ValueTask(""); } public void InsertText(string text) { editor?.InvokeVoidAsync("insertText", text); } public ValueTask IsViewer() { return editor?.InvokeAsync("isViewer") ?? new ValueTask(false); } //...其他需要的方法 对象事件

JavaScript 组件对象有自己的事件,在 JavaScript 中直接设置 JavaScript 函数就可以了,但是并不能把 C# 方法或者委托传递给 js,这里就需要用到 JavaScript 调用C#方法了。 Blazor 框架中 JavaScript 只能调用静态方法,而我们实际中是基于对象来写逻辑的,所有我专门写了一个类来处理js的调用,JSCallbackManager:

public static class JSCallbackManager { private static ConcurrentDictionary eventHandlerDict = new(); public static void AddEventHandler(string objId, string eventKey, Delegate @delegate) { var eventHandlerList = eventHandlerDict.GetOrAdd(objId, (key) => new Dictionary()); eventHandlerList[eventKey]= @delegate; } public static void DisposeObject(string objId) { if (eventHandlerDict.Remove(objId, out Dictionary handlers)) { handlers.Clear(); } } [JSInvokable] public static object JSCallback(string objId, string eventKey) { if (eventHandlerDict.TryGetValue(objId, out Dictionary handlers)) { if (handlers.TryGetValue(eventKey, out Delegate d)) { var obj = d.DynamicInvoke(); return obj; } } return null; } }

我们使用一个嵌套的字典来保存了Blazor组件的回调委托,每一个组件对象都有一个唯一的Id,每一个组件类型都可以有不同名称的 JavaScript 事件回调。 比如我们想订阅 JavaScript 组件实例的 load 事件,我们需要改两个地方,第一个是 toastui-editor-export.js 导出文件:

export function initEditor(options) { options.el = document.getElementById(options.elid); options.events = { load: function () { DotNet.invokeMethodAsync("BulmaRazor", "JSCallback", options.elid, "load"); } } let editor = new toastui.Editor.factory(options); return editor; }

JavaScript 的事件还是需要用 js来做,然后在js方法内部调用 C# 方法。第二个是需要在 TuiEditor 中添加回调委托:

[Parameter] public EventCallback OnLoad { get; set; } protected override void OnInitialized() { if (Options == null) Options = new TuiEditorOptions(); Options.elid = Id; //这里添加回调委托,并把js事件公开成了Blazor组件事件 JSCallbackManager.AddEventHandler(Id, "load", new Func(() => OnLoad.InvokeAsync(this))); base.OnInitialized(); } protected override ValueTask DisposeAsync(bool disposing) { //移除对象的所有回调委托 JSCallbackManager.DisposeObject(Id); return base.DisposeAsync(disposing); }

这样我们就把 JavaScript 组件事件移植到了 Blazor 组件。

修整

经过上述不知,组件基本移植完了,但还不能很好的使用,第一,因为界面是 js在操作,所以我们应该禁用 Blazor组件的渲染:

protected override bool ShouldRender() { return false; }

在js的options中有个initialValue属性,是初始化内容的,我们改成Blazor的形式,最好是可以绑定:

[Parameter] public EventCallback OnBlur { get; set; } protected override void OnInitialized() { if (Options == null) Options = new TuiEditorOptions(); Options.InitialValue = _value; Options.elid = Id; //这里也是通过js事件触发 JSCallbackManager.AddEventHandler(Id, "blur", new Func(async () => { await setValue(); await OnBlur.InvokeAsync(this); })); base.OnInitialized(); } private string _value; [Parameter] public string Value { get { return _value; } set { _value = value; SetMarkdown(value, true); } } [Parameter] public EventCallback ValueChanged { get; set; } private async Task setValue() { _value = await GetMarkdown(); await ValueChanged.InvokeAsync(_value); } public void SetMarkdown(string markdown, bool cursorToEnd = true) { editor?.InvokeVoidAsync("setMarkdown", markdown, cursorToEnd); }

这样我们就可以使用 Blazor 绑定语法了:

@code{ private string markdown = "# Init Title"; }

效果如下:

在线效果点击这里

源代码 BulmaRazor官网 Gitee地址 Github地址

希望喜欢 Blazor 和 BulmaRazor 的朋友给个Star鼓励一下!该项目从2021年的春节假期开始,一个人做真心的累和耗时,您的鼓励是我坚持下去的最大动力!



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有