在 Flutter Web 中使用 js 库
Posted
技术标签:
【中文标题】在 Flutter Web 中使用 js 库【英文标题】:Use js library in flutter web 【发布时间】:2020-06-02 04:41:15 【问题描述】:我需要带有 bpmn.js 视图的小部件:https://github.com/bpmn-io/bpmn-js
使用 htmlElementView:
// ignore: undefined_prefixed_name
ui.platformViewRegistry
.registerViewFactory('bpmn_view', (int viewId) => element);
return Column(
children: <Widget>[
Expanded(
child: HtmlElementView(key: UniqueKey(), viewType: "bpmn_view")),
],
);
带js:
const html = '''
<div id="canvas">canvas</div>
<script>
(function ()
window.addEventListener('view_bpmn', function (e)
var bpmnJS = new BpmnJS(
container: "#canvas"
);
bpmnJS.importXML(e.details);
, false);
());
</script>
''';
element.setInnerHtml(html,
validator: NodeValidatorBuilder.common()..allowElement('script'));
但执行时出现错误:
VM4761 bpmn-viewer.development.js:18864 Uncaught TypeError: Cannot read property 'appendChild' of null
at Viewer.BaseViewer.attachTo (VM4761 bpmn-viewer.development.js:18864)
at Viewer.BaseViewer._init (VM4761 bpmn-viewer.development.js:18911)
at Viewer.BaseViewer (VM4761 bpmn-viewer.development.js:18454)
at new Viewer (VM4761 bpmn-viewer.development.js:19082)
at <anonymous>:3:25
at main.dart:185
at future.dart:316
at internalCallback (isolate_helper.dart:50)
而且我无法为 BpmnJS 设置选择器,例如:
var bpmnJS = new BpmnJS(
container: "document.querySelector('flt-platform-view').shadowRoot.querySelector('#canvas')";
);
我怎样才能让它工作?
【问题讨论】:
您为什么尝试使用viewType
访问。由于在您的影子 dom 内,画布 id 是 canvas
使用类似这样的东西。document.querySelector('flt-platform-view').shadowRoot.querySelector('#canvas');
啊,好的。谢谢。我对此进行更改和编辑。
我想我只是使用 iframe 元素,但我想知道如何在 Flutter Web 中正确使用它。
以我的拙见,Iframe 不是一个好的解决方案。尤其是当颤振重建 HTMLElementView
小部件时,它甚至可能完全重新渲染 iframe 例如当调整大小时。主应用程序和 Iframe 之间的通信也可能是另一个绘制点。我将在今天晚些时候尝试这个问题。
【参考方案1】:
由于 BpmnJS container
参数接受 DOMElement 类型值,我们可以直接传递 querySelector 的结果:
_element = html.DivElement()
..id = 'canvas'
..append(html.ScriptElement()
..text = """
const canvas = document.querySelector("flt-platform-view").shadowRoot.querySelector("#canvas");
const viewer = new BpmnJS( container: canvas );
""");
// ignore: undefined_prefixed_name
ui.platformViewRegistry
.registerViewFactory('bpmn-view', (int viewId) => _element);
BpmnJS 模块应该附加到 index.html 文件(在您项目的*** web 文件夹中):
<!DOCTYPE html>
<head>
<title>BpmnJS Demo</title>
<script defer src="main.dart.js" type="application/javascript"></script>
<script src="https://unpkg.com/bpmn-js@6.4.2/dist/bpmn-navigated-viewer.development.js"></script>
</head>
...
这里是完整的代码:
import 'dart:ui' as ui;
import 'package:universal_html/html.dart' as html;
import 'package:flutter/material.dart';
class BpmnDemo extends StatefulWidget
@override
_BpmnDemoState createState() => _BpmnDemoState();
class _BpmnDemoState extends State<BpmnDemo>
html.DivElement _element;
@override
void initState()
super.initState();
_element = html.DivElement()
..id = 'canvas'
..append(html.ScriptElement()
..text = """
const canvas = document.querySelector("flt-platform-view").shadowRoot.querySelector("#canvas");
const viewer = new BpmnJS( container: canvas );
const uri = "https://cdn.staticaly.com/gh/bpmn-io/bpmn-js-examples/dfceecba/url-viewer/resources/pizza-collaboration.bpmn";
fetch(uri).then(res => res.text().then(xml => viewer.importXML(xml)));
""");
// ignore: undefined_prefixed_name
ui.platformViewRegistry
.registerViewFactory('bpmn-view', (int viewId) => _element);
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(),
body: Center(
child: HtmlElementView(key: UniqueKey(), viewType: "bpmn-view")),
);
更新:
此示例显示如何从 dart 代码加载图表并使用 dart:js
库:
import 'dart:ui' as ui;
import 'dart:js' as js;
import 'package:universal_html/html.dart' as html;
import 'package:flutter/material.dart';
class BpmnDemo extends StatefulWidget
@override
_BpmnDemoState createState() => _BpmnDemoState();
class _BpmnDemoState extends State<BpmnDemo>
html.DivElement _element;
js.JsObject _viewer;
@override
void initState()
super.initState();
_element = html.DivElement();
_viewer = js.JsObject(
js.context['BpmnJS'],
[
js.JsObject.jsify('container': _element)
],
);
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory('bpmn-view', (int viewId) => _element);
loadDiagram('assets/pizza-collaboration.bpmn');
loadDiagram(String src) async
final bundle = DefaultAssetBundle.of(context);
final xml = await bundle.loadString(src);
_viewer.callMethod('importXML', [xml]);
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(),
body: Center(child: HtmlElementView(key: UniqueKey(), viewType: "bpmn-view")),
);
更新 2:
当HtmlElementView
使用IFrame
元素时,可能会出现从 js 库调用方法的某些复杂情况。在这种情况下,我们可以尝试两种选择:
-
将 IFrame 上下文存储在 dart 端,然后将
callMethod
与保存的上下文一起使用。
使用postMessage方法与IFrame
通信
import 'dart:ui' as ui;
import 'dart:js' as js;
import 'dart:html' as html;
import 'package:flutter/material.dart';
class IFrameDemoPage extends StatefulWidget
@override
_IFrameDemoPageState createState() => _IFrameDemoPageState();
class _IFrameDemoPageState extends State<IFrameDemoPage>
html.IFrameElement _element;
js.JsObject _connector;
@override
void initState()
super.initState();
js.context["connect_content_to_flutter"] = (content)
_connector = content;
;
_element = html.IFrameElement()
..style.border = 'none'
..srcdoc = """
<!DOCTYPE html>
<head>
<script>
// variant 1
parent.connect_content_to_flutter && parent.connect_content_to_flutter(window)
function hello(msg)
alert(msg)
// variant 2
window.addEventListener("message", (message) =>
if (message.data.id === "test")
alert(message.data.msg)
)
</script>
</head>
<body>
<h2>I'm IFrame</h2>
</body>
</html>
""";
// ignore:undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
'example',
(int viewId) => _element,
);
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.filter_1),
tooltip: 'Test with connector',
onPressed: ()
_connector.callMethod('hello', ['Hello from first variant']);
,
),
IconButton(
icon: Icon(Icons.filter_2),
tooltip: 'Test with postMessage',
onPressed: ()
_element.contentWindow.postMessage(
'id': 'test',
'msg': 'Hello from second variant',
, "*");
,
)
],
),
body: Container(
child: HtmlElementView(viewType: 'example'),
),
);
【讨论】:
谢谢!我可以在没有fetch
操作的情况下将 xml 传递给查看器吗?
我加了一个例子。
优秀的答案!有没有办法从 JS 向 Dart 端发回消息?
@nbloqs here 是 PayPal 反馈抖动的示例,但要使用大量方法,您必须使用 package:js(不是 dart:js 库)创建包装器。
@nbloqs 我在回答中添加了一些建议。以上是关于在 Flutter Web 中使用 js 库的主要内容,如果未能解决你的问题,请参考以下文章