我想将Elm与Javascript库集成,以便Elm能够动态创建" cell" (html divs),Javascript将提供他们的id-s并使用它们来执行自定义操作。我想要的序列是
这就是我在开头(full source)实现此目的的方法:
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)
问题在于另一方面的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) }
});
抱怨说无法找到这样的身份证明。显然,该视图尚未呈现。
所以我在Elm应用程序中添加了另一个状态(OnCellAdded
),希望流程是这样的:
Task.perform
)异步任务OnCellAdded
OnCellAdded
被调用,带有id的消息被发送到端口实现看起来像这样(diff)(full source):
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)
但仍然OnCellAdded
在Push
之后立即处理,而不会在其间呈现模型。
我的最后一次尝试是使用Update.andThen
(diff)(full source)
Push ->
let
uid = List.length cells
in
({ model
| cells = [uid] ++ cells
}, Cmd.none)
|> Update.andThen update (OnCellAdded uid)
仍然没有用。我需要一些帮助。
答案 0 :(得分:8)
这似乎是我经验中最干净的解决方案。
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);
See here for an SPA type setup with multiple pages and the need to re-render a JS interop'd graph。还可以更新图表中的数据值。 (控制台日志消息也可能具有指导意义。)
如果有人好奇如何在榆树方面而不是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
requestAnimationFrame是浏览器保留队列的一种方式,它管理并以60fps循环。这提供了许多改进:
有关rAF here和here的更多信息,以及Google here的视频
当我尝试将Chartist.js图表渲染到最初在Elm中创建的div时,我的个人故事开始了。我还想拥有多个页面(SPA样式),并且在各种页面更改时重新创建div元素时,图表需要重新渲染。
我在索引中将div写为直接HTML,但这阻止了我想要的SPA功能。我还使用了JQuery ala $(window).load(tellElmToReRender)
的端口和订阅,以及给Arrive.js一个 - 但是每个都导致了各种错误和缺乏所需的功能。我有点乱搞rAF但是在错误的地方和错误的方式使用它。听完ElmTown - Episode 4 - JS Interop之后,我突然发现了一个顿悟,并意识到应该如何使用它。
答案 1 :(得分:5)
从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);
});
有一个功能请求#19来添加一个钩子,因此可以知道HTML节点何时在DOM中。
您可以获得进度here,很可能是即将发布的版本。
答案 2 :(得分:4)
我想昨天做类似的事情,将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']
})
})
})
使用.morris-chart
类查看任何新元素的dom,一旦发现它使用该新元素创建图表。
所以只有在Elm
运行view
函数然后重新生成DOM之后才会调用它。
也许这样的事情会满足你的需求。