Inject/execute JS 代码到 IPython notebook 并禁止其在页面重新加载时进一步执行
Inject/execute JS code to IPython notebook and forbid its further execution on page reload
我正在编写必须将 javascript 代码嵌入到 IPython 笔记本并执行它的库。 HTML/JS 代码如下所示:
<div id="unique_id"></div>
<script>
var div = document.getElementById("unique_id");
// Do the job and get "output"
div.textContent = output; // display output after the cell
</script>
和 python 代码:
from IPython import display
display.display(display.HTML(code))
副作用是 javascript 代码存储在笔记本单元格的输出中,每次重新加载页面或打开笔记本时都会再次 运行 .
有没有办法禁止代码在重新加载时执行?或者是否可以 运行 javascript 代码而不将其保存在输出中?
我找到了窍门。
诀窍是使用 IPython.display.display()
的 update=True
参数,它将用新的输出替换输出(示例参见 here)。
所以需要做的是:首先输出完成工作的javascript,然后等到创建了具有特定ID的div,用输出填充它。一旦这个 display()
被调用,我们可以第二次调用 display
用实际的 HTML 和 div 更新第一个。所以 javascript 代码一旦完成就会用结果填充它,但代码本身不会被保存。
测试代码如下:
首先,定义回调函数(貌似,重要的是这里显示为HTML("<script> ... </script>")
而不是Javascript(...)
):
from IPython.display import display, HTML, Javascript
js_getResults = """<script>
function getResults(data, div_id) {
var checkExist = setInterval(function() {
if ($('#' + div_id).length) {
document.getElementById(div_id).textContent = data;
clearInterval(checkExist);
}
}, 100);
};
</script>"""
display(HTML(js_getResults))
然后在一个单元格中执行更新技巧:
js_request = '$.get("http://slow.server/", function(data){getResults(data, "unique_id");});'
html_div = '<div id="unique_id">Waiting for response...</div>'
display(Javascript(js_request), display_id='unique_disp_id')
display(HTML(html_div), display_id='unique_disp_id', update=True)
执行完get()
的回调后,内容Waiting for response...
会被服务器输出替换
在 运行 进入在每个打开的笔记本上执行的相同问题 Javascript 之后,我将@Vladimir 的解决方案改编为更通用的形式:
- 在每次渲染时使用新的唯一 ID(因为旧 ID 与笔记本的 HTML 输出一起保存)。
- 没有轮询来确定何时呈现 HTML 元素。
- 当然,当笔记本关闭时,JS所做的HTML修改不会被保存。
关键洞察:更换电池输出
from IPython.display import clear_output, display, HTML, Javascript
# JavaScript code here will execute once and will not be saved into the notebook.
display(Javascript('...'))
# `clear_output` replaces the need for `display_id` + `update`
clear_output()
# JavaScript code here *will* be saved into the notebook and executed on every open.
display(HTML('...'))
让它发挥作用
这里的挑战是 HTML
和 Javascript
块可以乱序呈现,并且操作 HTML 元素的代码只需要执行一次。
import random
from IPython.display import display, Javascript, HTML, clear_output
unique_id = str(random.randint(100000, 999999))
display(Javascript(
'''
var id = '%(unique_id)s';
// Make a new global function with a unique name, to prevent collisions with past
// executions of this cell (since JS state is reused).
window['render_' + id] = function() {
// Put data fetching function here.
$('#' + id).text('Hello at ' + new Date());
}
// See if the `HTML` block executed first, and if so trigger the render.
if ($('#' + id).length) {
window['render_' + id]();
}
''' % dict(unique_id=unique_id)
# Use % instead of .format since the latter requires {{ and }} escaping.
))
clear_output()
display(HTML(
'''
<div id="%(unique_id)s"></div>
<!-- When this script block executes, the <div> is ready for data. -->
<script type="text/javascript">
var id = '%(unique_id)s';
// See if the `Javascript` block executed first, and if so trigger the render.
if (window['render_' + id]) {
window['render_' + id]();
}
</script>
''' % {'unique_id': unique_id}
))
为了保持 notebook 干净,我会将此管道代码放入一个单独的 .py 文件中,然后从 Jupyter 导入它。
我正在编写必须将 javascript 代码嵌入到 IPython 笔记本并执行它的库。 HTML/JS 代码如下所示:
<div id="unique_id"></div>
<script>
var div = document.getElementById("unique_id");
// Do the job and get "output"
div.textContent = output; // display output after the cell
</script>
和 python 代码:
from IPython import display
display.display(display.HTML(code))
副作用是 javascript 代码存储在笔记本单元格的输出中,每次重新加载页面或打开笔记本时都会再次 运行 .
有没有办法禁止代码在重新加载时执行?或者是否可以 运行 javascript 代码而不将其保存在输出中?
我找到了窍门。
诀窍是使用 IPython.display.display()
的 update=True
参数,它将用新的输出替换输出(示例参见 here)。
所以需要做的是:首先输出完成工作的javascript,然后等到创建了具有特定ID的div,用输出填充它。一旦这个 display()
被调用,我们可以第二次调用 display
用实际的 HTML 和 div 更新第一个。所以 javascript 代码一旦完成就会用结果填充它,但代码本身不会被保存。
测试代码如下:
首先,定义回调函数(貌似,重要的是这里显示为HTML("<script> ... </script>")
而不是Javascript(...)
):
from IPython.display import display, HTML, Javascript
js_getResults = """<script>
function getResults(data, div_id) {
var checkExist = setInterval(function() {
if ($('#' + div_id).length) {
document.getElementById(div_id).textContent = data;
clearInterval(checkExist);
}
}, 100);
};
</script>"""
display(HTML(js_getResults))
然后在一个单元格中执行更新技巧:
js_request = '$.get("http://slow.server/", function(data){getResults(data, "unique_id");});'
html_div = '<div id="unique_id">Waiting for response...</div>'
display(Javascript(js_request), display_id='unique_disp_id')
display(HTML(html_div), display_id='unique_disp_id', update=True)
执行完get()
的回调后,内容Waiting for response...
会被服务器输出替换
在 运行 进入在每个打开的笔记本上执行的相同问题 Javascript 之后,我将@Vladimir 的解决方案改编为更通用的形式:
- 在每次渲染时使用新的唯一 ID(因为旧 ID 与笔记本的 HTML 输出一起保存)。
- 没有轮询来确定何时呈现 HTML 元素。
- 当然,当笔记本关闭时,JS所做的HTML修改不会被保存。
关键洞察:更换电池输出
from IPython.display import clear_output, display, HTML, Javascript
# JavaScript code here will execute once and will not be saved into the notebook.
display(Javascript('...'))
# `clear_output` replaces the need for `display_id` + `update`
clear_output()
# JavaScript code here *will* be saved into the notebook and executed on every open.
display(HTML('...'))
让它发挥作用
这里的挑战是 HTML
和 Javascript
块可以乱序呈现,并且操作 HTML 元素的代码只需要执行一次。
import random
from IPython.display import display, Javascript, HTML, clear_output
unique_id = str(random.randint(100000, 999999))
display(Javascript(
'''
var id = '%(unique_id)s';
// Make a new global function with a unique name, to prevent collisions with past
// executions of this cell (since JS state is reused).
window['render_' + id] = function() {
// Put data fetching function here.
$('#' + id).text('Hello at ' + new Date());
}
// See if the `HTML` block executed first, and if so trigger the render.
if ($('#' + id).length) {
window['render_' + id]();
}
''' % dict(unique_id=unique_id)
# Use % instead of .format since the latter requires {{ and }} escaping.
))
clear_output()
display(HTML(
'''
<div id="%(unique_id)s"></div>
<!-- When this script block executes, the <div> is ready for data. -->
<script type="text/javascript">
var id = '%(unique_id)s';
// See if the `Javascript` block executed first, and if so trigger the render.
if (window['render_' + id]) {
window['render_' + id]();
}
</script>
''' % {'unique_id': unique_id}
))
为了保持 notebook 干净,我会将此管道代码放入一个单独的 .py 文件中,然后从 Jupyter 导入它。