返回

Android Tabulator下拉框键盘消失问题解决方案

Android

Android 上 Tabulator 下拉编辑器字段键盘消失问题

碰到了一个棘手的问题:在 Android 设备上,特别是三星平板(Android 13),使用 Tabulator 的下拉列表编辑器时,键盘老是自动消失。看样子是键盘弹出导致表格重绘(table.redraw()),编辑器因此失去焦点,键盘也就跟着消失了。 GIF 动图清晰地展示了这一现象。

问题原因深究

经过一番探查和比对,找到了几个关键点:

  1. 问题复现环境: 这个问题主要出现在基于 Chromium 的浏览器中,如 Chrome 和 Edge。在 Firefox 中则没有这个问题。
  2. IFRAME 环境: 在 JSFiddle 或类似的代码片段环境(它们通常运行在 IFRAME 中)中,问题不会出现。这暗示了 IFRAME 可能避免了某种重绘或尺寸调整。
  3. resize 事件: 推测 Android 键盘的弹出触发了 resize 事件,可能影响了 Tabulator 表格。Tabulator 为了响应布局变化,进行了重绘,导致正在编辑的下拉列表失去焦点。

解决方案大集合

针对以上分析,可以尝试以下几种解决方案,从简单到复杂,逐步排查。

1. 禁用表格自动调整 ( redraw )

最直接的方法是尝试阻止 Tabulator 在 resize 事件发生时自动重绘。 虽然可能有效,但会影响用户体验。

原理: Tabulator 默认会在窗口大小变化时自动调整布局。禁用这个功能可以避免键盘弹出导致的重绘。

代码示例:

// 创建 Tabulator 实例时,设置 autoResize 为 false
var table = new Tabulator("#example-table", {
  height: "311px",
  data:data,
  autoResize: false, // 禁用自动调整
  columns: [
    {
      title: "Location",
      field: "location",
      width: 130,
      editor: "list",
      editorParams: {
        autocomplete: "true",
        allowEmpty: true,
        listOnEmpty: true,
        valuesLookup: true
      }
    },
  ],
});

缺点:
禁用自动调整会让表格大小变得固定,不会自适应不同的手机型号与屏幕。

2. 延迟编辑器激活

让编辑器在获得焦点之后稍微等一下, 也许能规避键盘和重绘的冲突。

原理: 给编辑器的激活留出一点缓冲时间,让键盘完全弹出,避免后续的布局变化。

代码示例:

var table = new Tabulator("#example-table", {
  height: "311px",
  data:data,
  columns: [
    {
      title: "Location",
      field: "location",
      width: 130,
      editor: "list",
      editorParams: {
        autocomplete: "true",
        allowEmpty: true,
        listOnEmpty: true,
        valuesLookup: true
      },
      cellEditing:function(cell){
            setTimeout(function(){
                cell.getElement().querySelector("input, select").focus();
            }, 100); // 延迟 100 毫秒
      }

    },
  ],
});

补充说明: 上面例子直接修改 cellEditing 方法可能会破坏默认编辑逻辑, 使用前需谨慎或只用于测试.

3. 监听 focusout 事件 (失去焦点)

捕获编辑器的 focusout 事件, 确认失焦是预期内的再进行刷新。

原理: focusout 事件会在元素失去焦点时触发。我们可以检查是否真的是用户主动移开了焦点,而不是键盘导致的。

代码示例:

var table = new Tabulator("#example-table", {
    height: "311px",
    data:data,
    columns: [
        {
            title: "Location",
            field: "location",
            width: 130,
            editor: "list",
            editorParams: {
                autocomplete: "true",
                allowEmpty: true,
                listOnEmpty: true,
                valuesLookup: true,
                 elementAttributes:{
                        //防止失焦时 redraw 整个 table
                        onfocusout:"event.stopPropagation();"
                    }
            }
        },
    ],
});

补充说明: 上面使用的 elementAttributes 参数直接给 input 加入了 onfocusout 属性.

4. CSS 方案:固定容器高度

如果 Tabulator 表格外层有一个可滚动的容器,可以尝试固定容器高度,防止键盘影响布局。

原理: 键盘弹出通常会挤压页面内容。如果外层容器高度固定,键盘就不会影响到 Tabulator 表格。

操作步骤:

  1. 找到 Tabulator 表格的父容器(或祖先容器),这个容器通常会有滚动条。
  2. 使用 CSS 设置该容器的 height 为一个固定值(例如,500px)。 也可以考虑设 max-height.
  3. 确保 overflow: autooverflow: scroll,以保证内容超出时出现滚动条。

CSS 示例:

#parent-container { /* 假设这是父容器的 ID */
  height: 500px;
  overflow: auto;
}

补充说明: 这个固定值并非完美解决所有情况, 需要手动调到最合适的值.

5. 自定义编辑器 (终极方案)

以上都解决不了, 那就手动处理输入和选择。

原理: 完全接管编辑器的行为,不依赖 Tabulator 的默认编辑器逻辑,可以最大程度地控制键盘和焦点。

实现步骤(大致思路,非完整代码):

  1. editor: "input" (或自定义函数): 设置一个简单的文本输入框作为编辑器,或者提供一个自定义编辑器函数。
  2. 手动显示选项:
    • 监听输入框的 clickfocus 事件。
    • 在事件处理函数中,动态创建一个包含选项的 div 或其他元素。
    • 将这个 div 定位到输入框下方(或其他合适位置)。
  3. 处理选择:
    • 监听选项 div 中每个选项的 click 事件。
    • 在事件处理函数中,获取选中值,更新 Tabulator 单元格的值。
    • 隐藏选项 div
  4. 处理输入(可选): 如果需要实时过滤选项,监听输入框的 input 事件,根据输入内容更新选项 div

代码示例 (只提供简单的结构, 非完整功能):


function customListEditor(cell, onRendered, success, cancel, editorParams){
    //cell - the cell component for the editable cell
    //onRendered - function to call when the editor has been rendered
    //success - function to call to pass the successfuly updated value to Tabulator
    //cancel - function to call to abort the edit and return to a normal cell
    //editorParams - params object passed into the editorParams column definition property

    //create and style input
    var input = document.createElement("input");

    input.setAttribute("type", "text");

    //create custom এইসব for the editor
	
    //... 创建用于显示列表的 div 等等
	var optionsContainer = document.createElement("div");
	//赋值给 editorParams 方便后续从 cell 中读取.
	editorParams.optionsContainer = optionsContainer
    //... 将输入与 optionsContainer 连结起来
    input.addEventListener("focus", function(e){

		//从资料库里获取数值的逻辑...
		values =  ["United Kingdom","Germany"];
        optionsContainer.innerHTML = ""; // 清空之前的选项
         values.forEach(value=>{
				//建立给用户选的 element
                const optionElement = document.createElement("div");
				optionElement.textContent = value;

				//用户选择了值之后的更新操作
				optionElement.addEventListener("click", function(){
                    success(value); //更新数值
					optionsContainer.style.display = "none"; //关闭选单
                });
                optionsContainer.appendChild(optionElement)
            })
    });
	//...
    //input.value = cell.getValue();

    //input.style.padding = "4px";
    //input.style.width = "100%";
    //input.style.boxSizing = "border-box";

    //...
    onRendered(function(){
        input.focus();
        //input.style.height = "100%";
    });

    function onChange(){
            success(input.value);
    }

    //submit new value on blur or change
    //input.addEventListener("change", onChange); //失去焦点更新
    //input.addEventListener("blur", onChange);   //按下 Enter 更新

    //return the editor element
    return input;
};
var table = new Tabulator("#example-table", {
    height: "311px",
  data:data,
    columns: [
        {
            title: "Location",
            field: "location",
            width: 130,
            editor: customListEditor,
        },
    ],
});

进阶:
可以使用一些前端UI控件库提供的, 能够良好处理移动端输入问题的输入选择框. 比如 select2
只需要在上述 customListEditor 中使用 UI 控件生成组件即可。

安全建议

上面方法主要关注解决显示问题, 安全方面一般不需要考虑. 如果有特殊数据, 对用户输入做好校验与转义永远是好的。