我想将Elm与Javascript库集成,以便Elm动态创建"cells"(html div),Javascript将提供其id-s并使用它们来执行自定义操作.我想要的顺序是
这就是我在开始时实现这一点的方法(完整源代码):
port onCellAdded : CellID -> Cmd msg
update : Msg -> Model -> (Model, Cmd Msg)
update message ({cells} as model) =
case message of
Push ->
let
uid = List.length cells
in
({ model
| cells = [uid] ++ cells
}, onCellAdded uid)
Run Code Online (Sandbox Code Playgroud)
问题是Javascript在另一边
var container = document.getElementById('app');
var demoApp = Elm.RenderDemo.embed(container);
demoApp.ports.onCellAdded.subscribe(function(cellID) {
if(document.getElementById('cell:' + cellID) === null) { window.alert("Cannot find cell " + cellID) }
});
Run Code Online (Sandbox Code Playgroud)
抱怨说找不到这样的身份证.显然,该视图尚未呈现.
所以我OnCellAdded
在Elm应用程序中添加了另一个state(),希望流程如下:
Task.perform
)一个异步任务OnCellAdded
OnCellAdded
被调用,带有id的消息被发送到端口update message ({cells} as model) =
case message of
Push ->
let
uid = List.length cells
in
({ model
| cells = [uid] ++ cells
}, msgToCmd (OnCellAdded uid))
OnCellAdded counter ->
(model, onCellAdded counter)
msgToCmd : msg -> Cmd msg
msgToCmd msg =
Task.perform identity identity (Task.succeed msg)
Run Code Online (Sandbox Code Playgroud)
但是OnCellAdded
,Push
如果没有在中间渲染模型,仍会立即处理.
我的最后一次尝试是使用Update.andThen
(差异)(完整来源)
Push ->
let
uid = List.length cells
in
({ model
| cells = [uid] ++ cells
}, Cmd.none)
|> Update.andThen update (OnCellAdded uid)
Run Code Online (Sandbox Code Playgroud)
它仍然无效.我需要一些帮助.
这似乎是我经验中最干净的解决方案.
var container = document.getElementById('app');
var demoApp = Elm.RenderDemo.embed(container);
var requestAnimationFrame =
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame; //Cross browser support
var myPerfectlyTimedFunc = function(cellID) {
requestAnimationFrame(function() {
if(document.getElementById('cell:' + cellID) === null) {
window.alert("Cannot find cell " + cellID)
}
})
}
demoApp.ports.onCellAdded.subscribe(myPerfectlyTimedFunc);
Run Code Online (Sandbox Code Playgroud)
请参阅此处了解具有多个页面的SPA类型设置以及重新呈现JS互操作图的需要.还可以更新图表中的数据值.(控制台日志消息也可能具有指导意义.)
如果有人对如何在Elm侧而不是html/js侧实现这一点感到好奇,请参阅Elm Defer Command库.
如您所述,问题是:
Elm使用requestAnimationFrame(rAF)本身作为排队DOM渲染的方法,并且有充分的理由.假设Elm在不到1/60秒内进行多次DOM操作,而不是单独渲染每个操作 - 这将是非常低效的 - Elm会将它们传递给浏览器的rAF,它将作为整体DOM渲染的缓冲区/队列.换句话说,之后view
会在动画帧上调用update
,因此view
每次调用后都不会发生调用update
.
在过去,人们会使用:
setInterval(someAnimationFunc, 16.6) //16.6ms for 60fps
Run Code Online (Sandbox Code Playgroud)
requestAnimationFrame是浏览器保留队列的一种方式,它以60fps的速度管理和循环.这提供了许多改进:
当我尝试将Chartist.js图形渲染到最初在Elm中创建的div时,我的个人故事开始了.我还想拥有多个页面(SPA样式),并且在各种页面更改时重新创建div元素时,图表需要重新呈现.
我在索引中将div写为直接HTML,但这阻止了我想要的SPA功能.我还使用了JQuery ala的端口和订阅$(window).load(tellElmToReRender)
,以及给Arrive.js一个 - 但是每个都导致了各种错误和缺乏所需的功能.我有点乱搞rAF但是在错误的地方和错误的方式使用它.这是在听完ElmTown - 第4集 - JS Interop之后,在那里我顿悟了一下,并意识到应该如何使用它.
截至0.17.1
目前,没有好办法实现这一目标.
我建议最简单的方法是使用setTimeout等待至少60ms或等到下一个requestAnimationFrame
考虑这个例子:
demoApp.ports.onCellAdded.subscribe(function(cellID) {
setTimeout(function() {
if(document.getElementById('cell:' + cellID) === null) {
window.alert("Cannot find cell " + cellID)
}
}, 60);
});
Run Code Online (Sandbox Code Playgroud)
有一个功能请求#19来添加一个钩子,因此可以知道HTML节点何时在DOM中.
你可以在这里取得进展,很可能是在即将发布的版本中.
我昨天想做类似的事情,将MorrisJS集成到Elm
生成的 div 中
最终我遇到了Arrive JS,它使用大多数现代浏览器中可用的新MutationObserver来观察 DOM 的变化。
所以在我的情况下,代码看起来像这样(简化):
$(document).ready(() => {
$(document).arrive('.morris-chart', function () {
Morris.Bar({
element: this,
data: [
{ y: '2006', a: 100, b: 90 },
{ y: '2007', a: 75, b: 65 },
{ y: '2008', a: 50, b: 40 },
{ y: '2009', a: 75, b: 65 },
{ y: '2010', a: 50, b: 40 },
{ y: '2011', a: 75, b: 65 },
{ y: '2012', a: 100, b: 90 }
],
xkey: 'y',
ykeys: ['a', 'b'],
labels: ['Series A', 'Series B']
})
})
})
Run Code Online (Sandbox Code Playgroud)
这会使用.morris-chart
该类监视 dom 中的任何新元素,一旦发现它就会使用该新元素创建图表。
所以只有在Elm
运行该view
函数然后重新生成 DOM后才会调用它。
也许这样的东西会满足您的需求。