TinyMCE自定义对话框TabPanel标签切换事件失效?3招解决!
2025-03-21 01:33:29
TinyMCE 自定义对话框 TabPanel 标签切换事件失效问题解决
在 TinyMCE V6 自定义对话框中,使用 TabPanel 组件时,你可能遇到 onTabChange
事件无法触发的问题。试图用 onChange
事件也无济于事。 这篇博客会帮你彻底解决这个问题,让你能够顺利捕获标签页切换事件。
问题原因分析
问题的根源在于 TinyMCE 自定义对话框的 API 版本和事件处理方式。 原代码 onTabChange: (dialogApi, details) => {}
试图在 dialog 定义级别上处理这个事件, 这个方式通常用在 dialog 的 button 一类。 TinyMCE 内部处理 TabPanel 组件的 tabchange
事件时,需要特定的绑定方式才能正确触发。直接在 dialog
对象上定义 onTabChange
是不能生效的。
解决方案
提供三种解决策略,由易到难,分别针对不同需求场景。
方案一:使用 onChange
事件(简单方案)
虽然问题的原贴说无法用, 但是我们根据官方文档还是有办法用的, 但要求 tab
内组件至少有一个 可以联动的组件,比如一个输入框或选择框。如果 tab 内没有这种, 则不能使用这个方案。
-
原理:
onChange
事件会在对话框内任何表单字段的值发生变化时触发。我们可以巧妙利用这一点,通过一个隐藏的输入框,来间接触发并获取当前激活的标签页。 -
代码示例:
let activeTab = 'Insert_UGC_From_ID'; // 默认激活的标签页
const InsertUgcPanel = {
title: 'Insertion',
body: {
type: 'tabpanel',
tabs: [
{
name: 'Insert_UGC_From_ID',
title: '通过 ID 插入',
items: [
//...其他组件
{
type: 'input',
name: 'active_tab_hidden_id', //用于追踪 tab
hidden: true, //添加隐藏
value: 'Insert_UGC_From_ID' // 初始值
}
]
},
{
name: 'Insert_UGC_From_URL',
title: '通过 URL 插入',
items: [
//...其他组件
{
type: 'input',
name: 'active_tab_hidden_url', //用于追踪 tab
hidden: true,
value: 'Insert_UGC_From_URL' //初始化 value 无意义,必须存在
}
]
},
// ... 其他标签页
]
},
buttons: [/* ... 按钮配置 ... */],
onChange: (dialogApi, details) => {
if (details.name.startsWith('active_tab_hidden')) { // 如果与追踪 tab 相关的事件触发
activeTab = dialogApi.getData()[details.name]; // 获得新值
}
console.log("当前标签:", activeTab); //debug 使用, 请部署时移除
},
onAction: (dialogApi, details) => {
if (details.name === 'insert-UGC-button') {
//可以读取到被修改过的 activeTab
const data = dialogApi.getData();
console.log("最后点击确定时候的 Tab:",activeTab);
dialogApi.close();
} else if (details.name === 'doesnothing') {
dialogApi.close();
}
}
};
-
实现思路解释:
每个 tab 内添加一个 input 组件。
name 分别命名active_tab_hidden_id
以及active_tab_hidden_url
, 来追踪当前点击的是哪个 tab。
初始value
没有作用, 随意赋值, 但必须要保证有一个初始的value
, 可以用tab
的name
来赋值. -
安全建议: 这个隐藏字段只是用来辅助触发事件,不存储敏感数据。
方案二:利用事件冒泡 (进阶技巧)
如果你希望在不添加额外隐藏组件的情况下实现对 tab 切换进行监听,可以使用手动事件触发, 但稍显繁琐
-
原理: TinyMCE 的对话框组件基于 HTML 构建。我们可以通过模拟点击标签页的 DOM 元素,手动触发
tabchange
事件。 -
代码示例:
let activeTab = 'Insert_UGC_From_ID'; // 默认激活的标签页
const InsertUgcPanel = {
title: 'Insertion',
body: {
type: 'tabpanel',
tabs: [
{
name: 'Insert_UGC_From_ID',
title: '通过 ID 插入',
items: [
// 其他项目
]
},
{
name: 'Insert_UGC_From_URL',
title: '通过URL插入',
items: [
//其他项目
]
}
]
},
buttons: [ /* ... 按钮配置... */ ],
onAction: (dialogApi, details) => {
if (details.name === 'insert-UGC-button') {
//使用activeTab的值
const data = dialogApi.getData();
console.log("最终Tab:",activeTab);
dialogApi.close();
} else if (details.name === 'doesnothing') {
dialogApi.close();
}
},
onClose: (dialogApi)=>{
//对话框销毁时候移除监听, 避免重复调用.
const dialogRoot = dialogApi.getRootEl(); //获取根元素,这里是整个dialog的div
dialogRoot.removeEventListener('click',tabChangeListener);
},
onMount: (dialogApi) => {
// 添加 tab 页的点击事件监听
const dialogRoot = dialogApi.getRootEl(); //获取根元素,这里是整个dialog的div
dialogRoot.addEventListener('click',tabChangeListener);
function tabChangeListener(event) {
//判断是 tab 的点击
if (event.target.classList.contains('tox-tab')) {
//通过解析tox-tab的aria-controls="tab-name"中的tab-name值
const targetTabId = event.target.getAttribute('aria-controls');
if (targetTabId)
{
const tabName=targetTabId;
if (activeTab !== tabName)
{
activeTab = tabName; //进行值的变更
console.log("切换了, 新tab:", activeTab); //输出debug,部署请移除
}
}
}
}
}
};
- 代码解析:
onMount
:在对话框渲染完成后执行,添加对整个对话框元素的click
监听,用于后续判断事件发生源。- 通过
getRootEl()
获取对话框的根元素。 - 增加一个名为
tabChangeListener
的监听函数,并绑定到对话框元素的click
事件。 - 对事件的
target
(触发事件的具体 dom)做判断。 通过分析,所有的 tab 均有tox-tab
的 class,所以通过event.target.classList.contains('tox-tab')
判断这个click
事件是否由 tab 发出。 -
再从 tab 对象中取得 `aria-controls`,根据分析,可以从其中拿到 `tabName`.
- 进行
activeTab
变量的值修改, 并执行自己的onTabChange
逻辑。 onClose
: 添加移除click
监听逻辑。
- 进阶- 事件委托: 如果对话框结构比较复杂,也可以使用事件委托,将事件监听绑定到对话框的根元素,然后根据事件目标(
event.target
)来判断是否是标签页的点击事件。
方案三: 反射获取并修改(非必要不使用)
警告: 反射属于非公开的 api,TinyMCE
随时可以进行版本升级,所以尽量不使用这种方式 。如果你的业务属于必须确保稳定的类型, 请跳过这种方式. 如果前两种能实现,就不要使用这个方式。
这个是万策尽的方式,提供给其他方法完全不行的时候尝试使用。
-
原理: 通过
TinyMCE
提供的底层getEl()
以及getData
反射获得值 -
代码及使用:
let activeTab = 'Insert_UGC_From_ID';
const InsertUgcPanel = {
title: 'Insertion',
body: {
type: 'tabpanel',
tabs: [
{
name: 'Insert_UGC_From_ID',
title: '通过 ID 插入',
items: [
]
},
{
name: 'Insert_UGC_From_URL',
title: '通过URL插入',
items: [
]
}
]
},
buttons: [/* ... 按钮 ... */],
onAction: (dialogApi, details) => {
if (details.name === 'insert-UGC-button') {
//可以拿到 activeTab 的值
console.log("最后选定:",activeTab)
const data = dialogApi.getData();
dialogApi.close();
} else if (details.name === 'doesnothing') {
dialogApi.close();
}
},
onMount:(dialogApi)=>{
//利用反射获得实例
let instance = dialogApi.getInstance().componentStack[0];
//利用反射, 对 onChange 进行 override
let originalOnChange = instance.onChange;
// 重写
instance.onChange = (...a)=>{
originalOnChange.apply(instance, a);
let data = dialogApi.getData();
for (let k in data)
{
if(Object.hasOwn(data, k))//遍历,找到被选中的 Tab
{
let nowValue = data[k];
if(typeof(nowValue)=='boolean' && nowValue == true)
{
//判断 Tab 是否发生了改变
if(activeTab !== k)
{
activeTab = k; //直接赋值为 tab 的 name
//执行你的逻辑
console.log('现在选中的 Tab:',activeTab);
}
break;
}
}
}
};
}
};
-
代码分析:
利用getInstance().componentStack[0]
获取组件堆栈并修改onChange
属性, hook 住内部的onChange
. 再使用getData
获取当前的数据变化,由于组件运行机制, 一旦切换, 必定会引起一次数据的变更.利用 tab 被切换的特性,我们可以分析 tab 中, 被切换的必定导致一个
boolean
类型value
为true
,找到这个并确认与之前的记录不符合, 进行修改.
总结
以上三种方法都可以实现 TinyMCE 自定义对话框中 TabPanel 组件的标签切换事件监听。方案一最简单,但需要界面配合增加元素。方案二更为灵活, 可以使用自定义的事件, 不需要修改结构,但使用了事件冒泡相关的技术。方案三用了一些"非常规"手段,可以作为最终解决方案。可以根据实际情况选择最适合你的方案。