实现在Flutter Web中嵌入codemirror

dart codemirror -> flutter codemirror

Posted by MetaNetworks on April 13, 2020
本页面总访问量

实现在Flutter Web中嵌入codemirror

博文的Flutter版本

Flutter 1.17.0 • channel beta • https://github.com/flutter/flutter.git Framework • revision d3ed9ec945 (6 天前) • 2020-04-06 14:07:34 -0700 Engine • revision c9506cb8e9 Tools • Dart 2.8.0 (build 2.8.0-dev.18.0 eea9717938)

CodeMirror 是一款“Online Source Editor”,基于Javascript,短小精悍,实时在线代码高亮显示,他不是某个富文本编辑器的附属产品,他是许多大名鼎鼎的在线代码编辑器的基础库。

codemirror目前只有Google团队推出的dart web项目,但是未在flutter web中实现

我们知道,在flutter_web中使用原生html插件,需要使用HtmlElementView组件支持,并在initState中注册组件并配置组件…以下是痛苦的踩坑之路

1. 声明全局变量于Stateful Widget中

  • code-mirror的html ID
  • DivElement,用于存放
  • CodeMirror,处理回调
  • HtmlElementView,UI
  • optionscodemirror的配置
1
2
3
4
5
6
  // UI变量
  String _codemirrorId = "code-edit";
  html.DivElement _codeContent = html.DivElement();
  CodeMirror _codeMirror;
  HtmlElementView _codeView;
  Map options = {'mode': 'clike', 'theme': 'monokai'};

2. initState中注册并生成HtmlElementView

注意1:这里的uidart:ui

import 'dart:ui' as ui;

注意2:// ignore: undefined_prefixed_name用于忽略registerViewFactory未定义问题

1
2
3
4
5
6
7
8
9
10
11
  @override
  void initState() {
    super.initState();
    // 注册codemirror标签
    // ignore: undefined_prefixed_name
    ui.platformViewRegistry
        .registerViewFactory(_codemirrorId, (int viewId) => _codeContent);
    _codeView = HtmlElementView(
      viewType: _codemirrorId,
    );
  }

3. build方法中放入_codeView

注意:一定要设置Container的宽高,否则出现白屏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 body: Container(
        alignment: Alignment.center,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            FlatButton(onPressed: showCodeEditor, child: Text("show")),
            Container(
              decoration: BoxDecoration(color: Colors.grey),
              width: 400,
              height: 400,
>>>>>>>>>>    child: _codeView, <<<<<<<<<<<<<<<<<<<<<<<<<<<<
            ),
          ],
        ),
      ),

4. 在_codeView中导入CodeMirror

注意:_codeView在对应的html中,使用了shadowRoot进行隔离,在index.htmlheader中添加CSS、JS是无效的,无法作用在shadowRoot内部,所以我们要另辟蹊径!

目前有两种方案:

  • 使用iFrame element导入一个完整的html页面,然后通过访问iFramecontentDocument变量的querySelector方法拿到html body里我们定义好的一个空div,然后使用codemirror.fromElement方法填充进去

    ❎不行,因为Dart SDK 2.8.0还没有将contentDocument开放出来…GitHub Issue链接给出的解决方案是转化为JsObject,但是这样就没法使用codemirror的构造函数进行构造了,而且JsObject生涩难用,成本实在太高

  • 直接在shadowRoot中配置!(以下操作为该步骤)

4.1 拿到_codeView的shadowRoot

注意!_codeView的shadowRoot没法通过codeView.shadowRoot方法(无此方法)!只能通过DOM数查找,所有的HtmlView在DOM树种都是flt-platform-view,开发时如果有多个,这个下标注意更换为对应数字

1
var node = html.document.getElementsByTagName("flt-platform-view")[0] as html.HtmlElement;

4.2 在DivElement中实例化codemirror

1
_codeMirror = CodeMirror.fromElement(_codeContent, options: options);

4.3 导入要使用的第三方CSS、JS

这里使用cdn.bootcss.com的CSS和JS文件

注意innerHTMLOuterHtml的区别,我们要使用的是setInnerHtml方法。注意:需要传入一个支持tagattributes的验证器,否则header无法添加

1
2
3
4
5
6
7
var headElement = html.HeadElement();
// node 验证器
final NodeValidatorBuilder _htmlValidator=new NodeValidatorBuilder.common()
    ..allowElement('link',attributes: ["rel","href"])
    ..allowElement('script',attributes: ["src"]);
// 设置header
    headElement.setInnerHtml('<link href="https://cdn.bootcss.com/codemirror/5.52.2/codemirror.min.css" rel="stylesheet"><script src="https://cdn.bootcss.com/codemirror/5.52.2/codemirror.min.js"></script><script src="https://cdn.bootcss.com/codemirror/5.52.2/mode/clike/clike.min.js"></script><script src="https://cdn.bootcss.com/codemirror/5.52.2/addon/selection/active-line.min.js"></script><link href="https://cdn.bootcss.com/codemirror/5.52.2/theme/monokai.min.css" rel="stylesheet">',validator: _htmlValidator);

4.4 将Header添加进shadowRootchildren

1
node.shadowRoot.children.insert(0, headElement);

至此,如果配置正确,将成功实现codemirror嵌入flutter_web了,效果如下

但是仍然有些问题!空格Tab按键会和Flutter有些许冲突,这个之后日益完善吧~