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

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.JavaScript 这个文件就是 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<Task<IJSObjectReference>> tuiEditorModuleTask;public BulmaRazorJsInterop(IJSRuntime jsRuntime){tuiEditorModuleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>("import", "./_content/BulmaRazor/js/tuieditor.min.js").AsTask());}public async ValueTask<IJSObjectReference> TuiEditorInit(TuiEditorOptions options){var module = await tuiEditorModuleTask.Value;return await module.InvokeAsync<IJSObjectReference>("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<div id="@Id"></div>@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<string,object>,为了方便,我们过滤了空值:

    internal class JsParams:Dictionary<string,object>{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; }/// <summary>/// Editor's height style value. Height is applied as border-box ex) '300px', '100%', 'auto'/// </summary>public string Height { get; set; }/// <summary>/// 是否是查看器/// </summary>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<string> GetMarkdown(){return editor?.InvokeAsync<string>("getMarkdown") ?? new ValueTask<string>("");}public void InsertText(string text){editor?.InvokeVoidAsync("insertText", text);}public ValueTask<bool> IsViewer(){return editor?.InvokeAsync<bool>("isViewer") ?? new ValueTask<bool>(false);}//...其他需要的方法

对象事件

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

    public static class JSCallbackManager{private static ConcurrentDictionary<string, Dictionary<string, Delegate>> eventHandlerDict = new();public static void AddEventHandler(string objId, string eventKey, Delegate @delegate){var eventHandlerList = eventHandlerDict.GetOrAdd(objId, (key) => new Dictionary<string, Delegate>());eventHandlerList[eventKey]= @delegate;}public static void DisposeObject(string objId){if (eventHandlerDict.Remove(objId, out Dictionary<string, Delegate> handlers)){handlers.Clear();}}[JSInvokable]public static object JSCallback(string objId, string eventKey){if (eventHandlerDict.TryGetValue(objId, out Dictionary<string, Delegate> 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<TuiEditor> OnLoad { get; set; }protected override void OnInitialized(){if (Options == null)Options = new TuiEditorOptions();Options.elid = Id;//这里添加回调委托,并把js事件公开成了Blazor组件事件JSCallbackManager.AddEventHandler(Id, "load", new Func<Task>(() => 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<TuiEditor> 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<Task>(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<string> 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 绑定语法了:

<TuiEditor @bind-Value="markdown"></TuiEditor>
@code{private string markdown = "# Init Title";
}

效果如下:

在线效果点击这里

源代码

  • BulmaRazor官网

  • Gitee地址

  • Github地址

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/304014.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

把握人工智能命脉的有效方法

最近广州的天气老是变幻无常&#xff0c;往往今天还热得要命第二天就寒风瑟瑟&#xff08;如下图&#xff09;&#xff0c;让小天甚是怀念每天艳阳高照的夏天&#xff0c;虽然热了点但好歹不用担心猝不及防地收到寒风暴雨黄色预警。说到夏天&#xff0c;不得不提一下1956年的那…

微软的焦虑?想多了!从.NET6 Preview2到大厂招聘,起飞

看了篇文章叫《从.NET看微软的焦虑》&#xff0c;这里忍不住先吐槽一下&#xff0c;看完不仅毫无收获&#xff0c;而且有一种先起个夺眼球的标题&#xff0c;然后再东拼西凑找证据。讲真的&#xff0c;微软市值基本上等于“阿里腾讯百度”三者之和&#xff0c;居然还焦虑的无法…

TED演讲:区块链将如何改变世界?看完太震撼了!

区块链是什么&#xff1f;如果你不知道&#xff0c;你应该了解&#xff1b;如果你知道&#xff0c;有可能你仍需要了解一些它工作原理。唐泰普斯科特在此使这改变世界、建立信任的科技变得简明易懂。他表示&#xff0c;这就是第二代互联网&#xff0c;将有可能改变我们的金钱、…

re管理器Java_自定义布局管理器-FormLayout

第二部分&#xff1a;自定义布局管理器在java.awt包与javax.swing包下有许多现成的布局类&#xff0c;比如BorderLayout、FlowLayout&#xff0c;还有较为复杂的、用于精确定位的布局类GridBagLayout、SpringLayout等。起初我刚刚从事gooey时(06年中)&#xff0c;企图依靠JDK自…

如何看待 70% 的程序员,缺乏数据结构和算法知识?

金三银四来了&#xff0c;各大厂动静不小&#xff0c;都在储备人才&#xff0c;绝对是程序员面试的黄金时间了&#xff0c;不少同学也在后台反馈面试中遇到的一些问题&#xff0c;所以今天想跟大家说说算法。说起算法&#xff0c;那大厂面试是绝对必考的&#xff0c;可以说是一…

Sorry,关注这些 IT 技术类公众号,真的可以为所欲为

工作和生活节奏超快的今天&#xff0c;想要不断提升自我&#xff0c;碎片化阅读学习是你最佳的选择&#xff0c;如果你已经有了一颗学习的心&#xff0c;却苦于不知道从哪里学习&#xff0c;那么&#xff0c;这些学习的工具和途径就很重要了。今天为你推荐一些 IT技术领域的微信…

数据告诉你,抖音是如何在半年之内逆袭的

从春节至今&#xff0c;音乐短视频社区“抖音”在苹果应用商店免费排行榜上连续多天霸榜。凭借多元的音乐风格、酷炫的视觉编辑功能、个性化的分发机制以及良好的社区氛围&#xff0c;抖音在上线不久后便受到了年轻用户的追捧。在这一年半的时间里&#xff0c;抖音到底成长到了…

非名校出身的我,是如何拿到Facebook、谷歌、微软、亚马逊和Twitter的Offer的?

非名校出身&#xff0c;也没有知名科技公司的工作经验&#xff0c;他竟同时拿到了美国5家顶尖科技公司的Offer。他究竟是如何做到的&#xff1f;这篇文章是专门为那些即将开始找工作的人写的。很多正在找工作的人可能会担心因为自己不是毕业于常青藤名校而无法在顶尖科技公司找…

GitLab 服务器的迁移以及注意点

Git 已经是代码托管工具中的主流了&#xff0c;如果是自己搭建私有的 Git 服务器我们一般会使用 GitLab &#xff0c;在《在CentOS7中安装GitLab》 一文中有介绍怎样在 CentOS7 中安装 GitLab 。文本主要介绍怎样迁移 GtiLab 。环境CentOS&#xff1a;7.4GitLab&#xff1a;10.…

RHEL5下DNS配置详解3

view 是bind中的另外的一个技巧他在有防火墙的环境中非常有用。View允许你呈现出不同的配置文件给不同的客户&#xff0c;当你的服务器既要给内网的用户又要给外网的用户提供查询服务时使用view将是非常方便的。下其实访问控制列表就是一个有名字的地址匹配列表。它的语法格式为…

百叶窗效果显示图片源码(c#)

2019独角兽企业重金招聘Python工程师标准>>> 显示图片&#xff1a; this.pictureBox.Image Image.FromFile("image.jpg", false); 百叶窗有两种显示效果&#xff0c;一种是垂直百叶窗&#xff0c;另一种是水平百叶窗。 实现百叶窗显示图像有两种方式&…

16个顶级思维模型

思维模型会给你提供一种视角或思维框架&#xff0c;从而决定你观察事物和看待世界的视角。顶级的思维模型能提高你成功的可能性&#xff0c;并帮你避免失败。打造多元思维模型想法来自查理芒格&#xff0c;而查理芒格是沃伦巴菲特的得力助手。Farnam Street曾这样描述思维模型&…

使用BeetleX网关对Web应用进行灰度发布

灰度发布可以更有效地保障服务运作的可靠性&#xff0c;即能让服务得以更新的同时也不影响业务动作。BeetleX网关支持全动态化实时配置&#xff0c;对应用进行灰度发布可以说是非常便利。灰度发布在BeetleX网关中涉及到以下:添加新应用服务&#xff0c;测试路由配置&#xff0c…

17道因为太难而被禁用的Google面试题

即使是最成功的公司&#xff0c;它的招聘过程有时也会很不靠谱&#xff0c;经常会出一些奇怪的看似没有答案的面试问题&#xff0c;但标准答案却让应聘者还没来得及接近「起跑线」就被「退赛」了。Google 曾经就是这样的公司&#xff0c;招聘人员会出一些难为应聘者的高质量问题…

在 .NET Core 中构建 REST API

翻译自 Camilo Reyes 2020年8月26日的文章 《Build a REST API in .NET Core》 [1]REST API 可以使用简单的动词&#xff08;如 POST、PUT、PATCH 等&#xff09;将大型解决方案背后的复杂性隐藏起来。在本文中&#xff0c;Camilo Reyes 解释了如何在 .NET Core 中创建 REST AP…

一个检查SPN的小工具

如果大家配过kerberos的话会发现&#xff0c;AD本身并没有一个可以检查SPN的工具&#xff0c;而SPN一旦配重复的话会出现奇怪的错误&#xff0c;所以我就写了这么一个简单的工具。 截图 使用时候&#xff0c;输入你要查询的AD的名称或者IP&#xff0c;然后输入一个该域的用户信…

如果把整个因特网都印出来 你认为会怎么样

2019独角兽企业重金招聘Python工程师标准>>> 如果把整个因特网都印出来的话... 将会用掉 4500 万个墨盒&#xff0c;总计五十万公升的墨水。如果把这些墨水换成燃油&#xff0c;足够让747连飞 18,000 英里&#xff08;28,800 公里&#xff09;&#xff0c;从纽约不降…

ASP.NET Core中间件初始化探究

前言在日常使用ASP.NET Core开发的过程中我们多多少少会设计到使用中间件的场景&#xff0c;ASP.NET Core默认也为我们内置了许多的中间件&#xff0c;甚至有时候我们需要自定义中间件来帮我们处理一些请求管道过程中的处理。接下来&#xff0c;我们将围绕着以下几个问题来简单…

基于Python实现的微信好友数据分析

最近微信迎来了一次重要的更新&#xff0c;允许用户对”发现”页面进行定制。不知道从什么时候开始&#xff0c;微信朋友圈变得越来越复杂&#xff0c;当越来越多的人选择”仅展示最近三天的朋友圈”&#xff0c;大概连微信官方都是一脸的无可奈何。逐步泛化的好友关系&#xf…

java虚拟机和javaGC_Java虚拟机(三):GC算法和种类

一、介绍GC(Garbage Collection)&#xff0c;垃圾收集Java中&#xff0c;GC的对象是堆空间和永久区二、GC算法1. 引用计数法老牌垃圾回收算法通过引用计算来回收垃圾Java中未使用&#xff0c;使用者有COM、ActionScript3、Python实现&#xff1a;1> 对于一个对象A&#xff0…