如何在JupyterLab中免打包实现ipywidgets7风格的自定义复选框表格组件
解决JupyterLab中ipywidgets8免打包自定义组件问题
问题背景
之前在经典Jupyter Notebook中用ipywidgets7的免打包方式写了一个复选框表格组件,现在要迁移到JupyterLab+ipywidgets8环境,找不到直接插入前端JS的方法,不想为小功能做完整打包,尝试注入jQuery和RequireJS后出现Error displaying widget: model not found错误。
原前端代码
require.undef("my_widgets"); define("my_widgets", ["@jupyter-widgets/base"], function(widgets) { var TableView = widgets.DOMWidgetView.extend({ // Render the view. render: function() { this.table_changed(); // Python -> JavaScript update this.model.on("change:table", this.table_changed, this); // JavaScript -> Python update this.el.onchange = this.input_changed.bind(this); // Script $(this.el).on("click", ".my-table tbody tr", function (event) { if (event.target.type != 'checkbox'){ $(this).find(".my-chk")[0].click() } }); }, table_changed: function() { this.el.innerHTML = this.model.get("table"); }, input_changed: function() { let value = []; let jcheck = $(this.el).find(".my-chk"); for (let i=0;i<jcheck.length;i++) { if (jcheck[i].checked) { value.push(i) } }; $(this.el).find(".my-table")[0].setAttribute("date-value", value.join()); this.model.set('value', value); this.model.save_changes(); }, }); return { TableView: TableView, }; });
原后端代码
import traitlets from jinja2 import Template from ipywidgets import Layout, DOMWidget, register @register class TaskTable(DOMWidget): _view_name = traitlets.Unicode('TableView').tag(sync=True) _view_module = traitlets.Unicode('my_widgets').tag(sync=True) value = traitlets.List([], help="selected").tag(sync=True) table = traitlets.Unicode('', help="rendered HTML table").tag(sync=True) @traitlets.validate('value') def _valid_value(self, proposal): proposal['value'] = [int(x) for x in proposal['value']] return proposal['value'] def render(self, head, body): thead = ['#'] + head tbody = body self.table = template.render(head=thead, body=tbody) template = Template(STATICS_TaskTable) STATICS_TaskTable = """ <table class="my-table"> <thead> <tr> {%- for th in head -%} <th>{{ th }}</th> {%- endfor %} </tr> </thead> <tbody> {%- for row in body -%} <tr> {%- for cell in row -%} {%- if loop.first -%} <th><input class="my-chk" type="checkbox"> {{ cell }}</th> {%- else -%} <td>{{ cell }}</td> {%- endif -%} {%- endfor -%} </tr> {%- endfor -%} </tbody> </table> """ w = TaskTable() w.render(['foo', 'bar'], [[1, 'A', 'S'], [2, 'D', 'F']]) w
解决方案
1. 调整前端代码注入方式
JupyterLab使用ES模块,需通过全局requirejs对象注册模块,同时确保jQuery加载完成:
在JupyterLab的代码单元格中运行以下JS(用%%javascript魔法命令):
%%javascript // 确保jQuery可用 if (typeof $ === 'undefined') { require(['jquery'], function(jq) { $ = jq; registerWidget(); }); } else { registerWidget(); } function registerWidget() { requirejs.undef("my_widgets"); define("my_widgets", ["@jupyter-widgets/base"], function(widgets) { var TableView = widgets.DOMWidgetView.extend({ render: function() { this.table_changed(); this.model.on("change:table", this.table_changed, this); this.el.onchange = this.input_changed.bind(this); $(this.el).on("click", ".my-table tbody tr", function (event) { if (event.target.type !== 'checkbox'){ $(this).find(".my-chk")[0].click(); } }); }, table_changed: function() { this.el.innerHTML = this.model.get("table"); }, input_changed: function() { let value = []; let jcheck = $(this.el).find(".my-chk"); for (let i=0;i<jcheck.length;i++) { if (jcheck[i].checked) { value.push(i); } } $(this.el).find(".my-table")[0].setAttribute("data-value", value.join()); this.model.set('value', value); this.model.save_changes(); }, }); return { TableView: TableView, }; }); }
注:修正了原代码中date-value的拼写错误为符合规范的data-value。
2. 修正后端代码的变量顺序与属性
原代码中模板定义顺序错误,且缺少ipywidgets8要求的版本属性,调整如下:
import traitlets from jinja2 import Template from ipywidgets import Layout, DOMWidget, register STATICS_TaskTable = """ <table class="my-table"> <thead> <tr> {%- for th in head -%} <th>{{ th }}</th> {%- endfor %} </tr> </thead> <tbody> {%- for row in body -%} <tr> {%- for cell in row -%} {%- if loop.first -%} <th><input class="my-chk" type="checkbox"> {{ cell }}</th> {%- else -%} <td>{{ cell }}</td> {%- endif -%} {%- endfor -%} </tr> {%- endfor -%} </tbody> </table> """ template = Template(STATICS_TaskTable) @register class TaskTable(DOMWidget): _view_name = traitlets.Unicode('TableView').tag(sync=True) _view_module = traitlets.Unicode('my_widgets').tag(sync=True) # 新增版本属性,避免ipywidgets8的版本匹配报错 _view_module_version = traitlets.Unicode('1.0.0').tag(sync=True) value = traitlets.List([], help="selected").tag(sync=True) table = traitlets.Unicode('', help="rendered HTML table").tag(sync=True) @traitlets.validate('value') def _valid_value(self, proposal): proposal['value'] = [int(x) for x in proposal['value']] return proposal['value'] def render(self, head, body): thead = ['#'] + head tbody = body self.table = template.render(head=thead, body=tbody) # 创建并渲染组件 w = TaskTable() w.render(['foo', 'bar'], [[1, 'A', 'S'], [2, 'D', 'F']]) w
3. 确保JupyterLab扩展安装完整
执行以下命令安装依赖扩展并重启JupyterLab:
pip install ipywidgets jupyterlab-widgets jupyter labextension install @jupyter-widgets/jupyterlab-manager
关键说明
- ipywidgets8在JupyterLab中仍支持通过全局
requirejs注册模块,无需完整打包 - 必须确保依赖(如jQuery)加载完成后再注册组件
- 新增
_view_module_version是ipywidgets8的强制要求,可避免模型找不到的错误
内容的提问来源于stack exchange,提问作者DarkGoldBar
相关产品推荐
相关产品推荐

