一、效果截图

二、文件引用

多选下拉框扩展自handsontable的BaseEditor。

多选下拉框组件由两个文件构成,

  • 一个下拉框样式表MultiSelect.css
  • 一个组件实现脚本MultiSelect.js

使用时引用这两个文件即可,当然,要先在你的页面中引用handsontable的核心js文件,再引用我这两个文件。

其中js以模块类型引用(type="module")

<link href="../../Scripts/MultiSelect.css" rel="stylesheet" />
<script type="module" src="../../Scripts/MultiSelect.js"></script>

三、使用范例

var columns = [
{ data: 'field1', title: 'title1', width: 120, className: 'htCenter htMiddle', editor: 'MultiSelect', selectOptions: ['1#', '2#', '3#', '4#', '5#', '6#', '7#', '8#'] }
];

四、源代码

1.MultiSelect.js

/// <reference path="handsontable.full.min.js" />

//封闭在IIFE中
(Handsontable => {
class MultiSelectEditor extends Handsontable.editors.BaseEditor { // ...rest of the editor code
/**
* Initializes editor instance, DOM Element and mount hooks.
* 初始化编辑器实例,dom元素和挂载钩子函数
*/
init() {
// Create detached node, add CSS class and make sure its not visible
//增加触发节点,增加class类并确保节点隐藏隐藏
var MultiSelectDomContainer = document.createElement("div");
MultiSelectDomContainer.style.paddingTop = "30px";
this.select = MultiSelectDomContainer;
Handsontable.dom.addClass(this.select, 'htMultiSelectEditor');
this.select.style.display = 'none'; // Attach node to DOM, by appending it to the container holding the table
this.hot.rootElement.appendChild(this.select);
} //编辑器的值加载到单元格
getValue() {
var selects = "";
var childs = this.select.children;
for (var i = 0; i < childs.length; i++) {
if (childs[i].children[0].checked == true) {
selects += "["+childs[i].children[1].innerHTML+"]";
}
}
return selects;
return this.select;
return "";
} //单元格的值加载到编辑器
setValue(value) {
var selectCount = 0;
var selects = value.split("][");
var childs = this.select.children;
for (var i = 0; i < selects.length; i++) {
selects[i]=selects[i].replace("[", "").replace("]", ""); for (var j = 0; j < childs.length; j++) {
if (childs[j].value == selects[i]) {
selectCount += 1;
childs[j].children[0].checked = true;
}
}
}
//this.select.style.paddingTop = selectCount*30+"px";
} //打开编辑器
open() {
this._opened = true;
this.refreshDimensions();
this.select.style.display = '';
} //编辑器样式计算
refreshDimensions() {
this.TD = this.getEditedCell(); // TD is outside of the viewport.
if (!this.TD) {
this.close(); return;
}
//调整弹出框位置,使其位于单元格下面
this.select.style.paddingTop = this.TD.clientHeight + "px"; const { wtOverlays } = this.hot.view.wt;
const currentOffset = Handsontable.dom.offset(this.TD);
const containerOffset = Handsontable.dom.offset(this.hot.rootElement);
const scrollableContainer = wtOverlays.scrollableElement;
const editorSection = this.checkEditorSection();
let width = Handsontable.dom.outerWidth(this.TD) + 1;
let height = Handsontable.dom.outerHeight(this.TD) + 1;
let editTop = currentOffset.top - containerOffset.top - 1 - (scrollableContainer.scrollTop || 0);
let editLeft = currentOffset.left - containerOffset.left - 1 - (scrollableContainer.scrollLeft || 0);
let cssTransformOffset; switch (editorSection) {
case 'top':
cssTransformOffset = Handsontable.dom.getCssTransform(wtOverlays.topOverlay.clone.wtTable.holder.parentNode);
break;
case 'left':
cssTransformOffset = Handsontable.dom.getCssTransform(wtOverlays.leftOverlay.clone.wtTable.holder.parentNode);
break;
case 'top-left-corner':
cssTransformOffset = Handsontable.dom.getCssTransform(wtOverlays.topLeftCornerOverlay.clone.wtTable.holder.parentNode);
break;
case 'bottom-left-corner':
cssTransformOffset = Handsontable.dom.getCssTransform(wtOverlays.bottomLeftCornerOverlay.clone.wtTable.holder.parentNode);
break;
case 'bottom':
cssTransformOffset = Handsontable.dom.getCssTransform(wtOverlays.bottomOverlay.clone.wtTable.holder.parentNode);
break;
default:
break;
} if (this.hot.getSelectedLast()[0] === 0) {
editTop += 1;
}
if (this.hot.getSelectedLast()[1] === 0) {
editLeft += 1;
} const selectStyle = this.select.style; if (cssTransformOffset && cssTransformOffset !== -1) {
selectStyle[cssTransformOffset[0]] = cssTransformOffset[1];
} else {
Handsontable.dom.resetCssTransform(this.select);
} const cellComputedStyle = Handsontable.dom.getComputedStyle(this.TD, this.hot.rootWindow); if (parseInt(cellComputedStyle.borderTopWidth, 10) > 0) {
height -= 1;
}
if (parseInt(cellComputedStyle.borderLeftWidth, 10) > 0) {
width -= 1;
} selectStyle.height = `${height}px`;
selectStyle.minWidth = `${width}px`;
selectStyle.top = `${editTop}px`;
selectStyle.left = `${editLeft}px`;
selectStyle.margin = '0px';
} //获取当前单元格
getEditedCell() {
const { wtOverlays } = this.hot.view.wt;
const editorSection = this.checkEditorSection();
let editedCell; switch (editorSection) {
case 'top':
editedCell = wtOverlays.topOverlay.clone.wtTable.getCell({
row: this.row,
col: this.col
});
this.select.style.zIndex = 101;
break;
case 'corner':
editedCell = wtOverlays.topLeftCornerOverlay.clone.wtTable.getCell({
row: this.row,
col: this.col
});
this.select.style.zIndex = 103;
break;
case 'left':
editedCell = wtOverlays.leftOverlay.clone.wtTable.getCell({
row: this.row,
col: this.col
});
this.select.style.zIndex = 102;
break;
default:
editedCell = this.hot.getCell(this.row, this.col);
this.select.style.zIndex = '';
break;
} return editedCell < 0 ? void 0 : editedCell;
} focus() {
this.select.focus();
} close() {
this._opened = false;
this.select.style.display = 'none';
} //读取选项配置到编辑器
prepare(row, col, prop, td, originalValue, cellProperties) {
// Remember to invoke parent's method
super.prepare(row, col, prop, td, originalValue, cellProperties);
const selectOptions = this.cellProperties.selectOptions;
let options; if (typeof selectOptions === 'function') {
options = this.prepareOptions(selectOptions(this.row, this.col, this.prop));
} else {
options = this.prepareOptions(selectOptions);
} Handsontable.dom.empty(this.select); Handsontable.helper.objectEach(options, (value, key) => {
const optionElement = this.hot.rootDocument.createElement('div'); const checkbox = this.hot.rootDocument.createElement('input');
checkbox.type = "checkbox";
optionElement.appendChild(checkbox); var textSpan = this.hot.rootDocument.createElement('span');
textSpan.innerHTML = value;
optionElement.appendChild(textSpan); optionElement.value = key; Handsontable.dom.addClass(optionElement, 'MultiSelectOption'); //Handsontable.dom.fastInnerHTML(optionElement, value);
this.select.appendChild(optionElement);
});
}
prepareOptions(optionsToPrepare) {
let preparedOptions = {}; if (Array.isArray(optionsToPrepare)) {
for (let i = 0, len = optionsToPrepare.length; i < len; i++) {
preparedOptions[optionsToPrepare[i]] = optionsToPrepare[i];
} } else if (typeof optionsToPrepare === 'object') {
preparedOptions = optionsToPrepare;
} return preparedOptions;
}
} // Put editor in dedicated namespace
//将编辑器添加到专用命名空间
Handsontable.editors.MultiSelectEditor = MultiSelectEditor;
// Register alias
//编辑器注册别名
Handsontable.editors.registerEditor('MultiSelect', MultiSelectEditor); })(Handsontable);

MultiSelect.js

2.MultiSelect.css

.htMultiSelectEditor {
/*
* This hack enables to change <select> dimensions in WebKit browsers
*/
-webkit-appearance: menulist-button !important;
position: absolute;
width: auto;
}
.MultiSelectOption:hover{
background-color:antiquewhite;
border:1px solid gray;
}

MultiSelect.css

五、更新

日期:2022-01-10

问题

  • 修改样式
  • 解决在表格底部,弹出的下拉框被遮挡的问题

1.MutiSelect.js

/// <reference path="handsontable.full.min.js" />

//封闭在IIFE中
(Handsontable => {
class MultiSelectEditor extends Handsontable.editors.BaseEditor { // ...rest of the editor code
/**
* Initializes editor instance, DOM Element and mount hooks.
* 初始化编辑器实例,dom元素和挂载钩子函数
*/
init() {
// Create detached node, add CSS class and make sure its not visible
//增加触发节点,增加class类并确保节点隐藏隐藏
var MultiSelectDomContainer = document.createElement("div");
MultiSelectDomContainer.style.paddingTop = "30px";
this.select = MultiSelectDomContainer;
Handsontable.dom.addClass(this.select, 'htMultiSelectEditor');
this.select.style.display = 'none'; // Attach node to DOM, by appending it to the container holding the table
//this.hot.rootElement.appendChild(this.select);//当前单元格的下拉框子节点(会被遮挡)
this.hot.rootDocument.body.appendChild(this.select);//文档的下拉框子节点(不会被遮挡)
this.select.addEventListener("mousedown", (
function (e) {
return e.stopPropagation();
}));//阻止单光获得值在下拉框元素上面,editermanager的点击事件执行(关闭下拉框),让单元格能获得值
//document.body.appendChild(this.select);
} //编辑器的值加载到单元格
getValue() {
var selects = "";
var childs = this.select.children;
for (var i = 0; i < childs.length; i++) {
if (childs[i].children[0].checked == true) {
selects += "["+childs[i].children[1].innerHTML+"]";
}
}
return selects;
return this.select;
return "";
} //单元格的值加载到编辑器
setValue(value) {
var selectCount = 0;
var selects = value.split("][");
var childs = this.select.children;
for (var i = 0; i < selects.length; i++) {
selects[i]=selects[i].replace("[", "").replace("]", ""); for (var j = 0; j < childs.length; j++) {
if (childs[j].value == selects[i]) {
selectCount += 1;
childs[j].children[0].checked = true;
}
}
}
//this.select.style.paddingTop = selectCount*30+"px";
} //打开编辑器
open() {
this._opened = true;
this.refreshDimensions();
this.select.style.display = '';
} //编辑器样式计算
refreshDimensions() {
this.TD = this.getEditedCell(); // TD is outside of the viewport.
if (!this.TD) {
this.close(); return;
}
//调整弹出框位置,使其位于单元格下面
this.select.style.paddingTop = this.TD.clientHeight + "px"; const { wtOverlays } = this.hot.view.wt;
const currentOffset = Handsontable.dom.offset(this.TD);
const containerOffset = Handsontable.dom.offset(this.hot.rootElement);
const scrollableContainer = wtOverlays.scrollableElement;
const editorSection = this.checkEditorSection();
let width = Handsontable.dom.outerWidth(this.TD) + 1;
let height = Handsontable.dom.outerHeight(this.TD) + 1;
let editTop = currentOffset.top - containerOffset.top - 1 - (scrollableContainer.scrollTop || 0);
let editLeft = currentOffset.left - containerOffset.left - 0 - (scrollableContainer.scrollLeft || 0);
let cssTransformOffset; switch (editorSection) {
case 'top':
cssTransformOffset = Handsontable.dom.getCssTransform(wtOverlays.topOverlay.clone.wtTable.holder.parentNode);
break;
case 'left':
cssTransformOffset = Handsontable.dom.getCssTransform(wtOverlays.leftOverlay.clone.wtTable.holder.parentNode);
break;
case 'top-left-corner':
cssTransformOffset = Handsontable.dom.getCssTransform(wtOverlays.topLeftCornerOverlay.clone.wtTable.holder.parentNode);
break;
case 'bottom-left-corner':
cssTransformOffset = Handsontable.dom.getCssTransform(wtOverlays.bottomLeftCornerOverlay.clone.wtTable.holder.parentNode);
break;
case 'bottom':
cssTransformOffset = Handsontable.dom.getCssTransform(wtOverlays.bottomOverlay.clone.wtTable.holder.parentNode);
break;
default:
break;
} if (this.hot.getSelectedLast()[0] === 0) {
editTop += 1;
}
if (this.hot.getSelectedLast()[1] === 0) {
editLeft += 1;
} const selectStyle = this.select.style; if (cssTransformOffset && cssTransformOffset !== -1) {
selectStyle[cssTransformOffset[0]] = cssTransformOffset[1];
} else {
Handsontable.dom.resetCssTransform(this.select);
} const cellComputedStyle = Handsontable.dom.getComputedStyle(this.TD, this.hot.rootWindow); if (parseInt(cellComputedStyle.borderTopWidth, 10) > 0) {
height -= 1;
}
if (parseInt(cellComputedStyle.borderLeftWidth, 10) > 0) {
width -= 1;
} //selectStyle.height = `${height}px`;
selectStyle.height = "auto";
selectStyle.minWidth = `${width}px`;
selectStyle.top = `${editTop}px`;
selectStyle.left = `${editLeft}px`;
selectStyle.margin = '0px';
} //获取当前单元格
getEditedCell() {
const { wtOverlays } = this.hot.view.wt;
const editorSection = this.checkEditorSection();
let editedCell; switch (editorSection) {
case 'top':
editedCell = wtOverlays.topOverlay.clone.wtTable.getCell({
row: this.row,
col: this.col
});
this.select.style.zIndex = 101;
break;
case 'corner':
editedCell = wtOverlays.topLeftCornerOverlay.clone.wtTable.getCell({
row: this.row,
col: this.col
});
this.select.style.zIndex = 103;
break;
case 'left':
editedCell = wtOverlays.leftOverlay.clone.wtTable.getCell({
row: this.row,
col: this.col
});
this.select.style.zIndex = 102;
break;
default:
editedCell = this.hot.getCell(this.row, this.col);
this.select.style.zIndex = '';
break;
} return editedCell < 0 ? void 0 : editedCell;
} focus() {
this.select.focus();
} close() {
this._opened = false;
this.select.style.display = 'none';
} //读取选项配置到编辑器
prepare(row, col, prop, td, originalValue, cellProperties) {
// Remember to invoke parent's method
super.prepare(row, col, prop, td, originalValue, cellProperties);
const selectOptions = this.cellProperties.selectOptions;
let options; if (typeof selectOptions === 'function') {
options = this.prepareOptions(selectOptions(this.row, this.col, this.prop));
} else {
options = this.prepareOptions(selectOptions);
} Handsontable.dom.empty(this.select); Handsontable.helper.objectEach(options, (value, key) => {
const optionElement = this.hot.rootDocument.createElement('div'); const checkbox = this.hot.rootDocument.createElement('input');
checkbox.type = "checkbox";
optionElement.appendChild(checkbox); var textSpan = this.hot.rootDocument.createElement('span');
textSpan.innerHTML = value;
optionElement.appendChild(textSpan); optionElement.value = key; Handsontable.dom.addClass(optionElement, 'MultiSelectOption'); //Handsontable.dom.fastInnerHTML(optionElement, value);
this.select.appendChild(optionElement);
});
}
prepareOptions(optionsToPrepare) {
let preparedOptions = {}; if (Array.isArray(optionsToPrepare)) {
for (let i = 0, len = optionsToPrepare.length; i < len; i++) {
preparedOptions[optionsToPrepare[i]] = optionsToPrepare[i];
} } else if (typeof optionsToPrepare === 'object') {
preparedOptions = optionsToPrepare;
} return preparedOptions;
} } // Put editor in dedicated namespace
//将编辑器添加到专用命名空间
Handsontable.editors.MultiSelectEditor = MultiSelectEditor;
// Register alias
//编辑器注册别名
Handsontable.editors.registerEditor('MultiSelect', MultiSelectEditor); })(Handsontable);

MutiSelect.js

2.MutiSelect.css

.htMultiSelectEditor {
/*
* This hack enables to change <select> dimensions in WebKit browsers
*/
-webkit-appearance: menulist-button !important;
position: absolute;
width: auto;
height:100px;
z-index:9999;
background-color:white;
border:1px solid gray;
}
.MultiSelectOption:hover{
background-color:antiquewhite;
}

MultiSelect.css

handsontable多选下拉框编辑器扩展的更多相关文章

  1. query多选下拉框插件 jquery-multiselect(修改)

    其实网上关于该控件的使用教程已经很多了,其中 query多选下拉框插件 jquery-multiselect Jquery多选下拉列表插件jquery multiselect功能介绍及使用 这2个的介 ...

  2. Easyui-Combobox多选下拉框

    因为工作需要,引入combobox多选下拉框,并且获取选择的值并以","分开. 效果如下: 代码如下: <html> <head> <title> ...

  3. Extjs4.2 多选下拉框

    //多选下拉框 Ext.define('MDM.view.custom.MultiComboBox', { extend: 'Ext.form.ComboBox', alias: 'widget.mu ...

  4. js:jquery multiSelect 多选下拉框实例

    <!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. DropDownList单选与多选下拉框

    一.单选DropDownList传值 1.添加界面的DropDownList显示值问题 (1)在方法内添加ViewData的方法: var ad = new UnitsRepository(); Vi ...

  6. pentaho cde 自定义复选下拉框 checkbox select

    pentaho  自带的component 虽多,但是当用户需要在一个表格中查看多个组别的数据时,pentaho自带的单选框就不能实现了,所以复选下拉框势在必行,实现效果如下: 实现原理是借用了jqu ...

  7. Bootstrap3级联多选下拉框

    <!DOCTYPE html> <html> <head> <title>Bootstrap3级联多选下拉框</title> <met ...

  8. js怎么能取得多选下拉框选中的多个值?

    方法:获取多选下拉框对象数组→循环判断option选项的selected属性(true为选中,false为未选中)→使用value属性取出选中项的值.实例演示如下: 1.HTML结构 1 2 3 4 ...

  9. js多选下拉框

    1.js原生实现 1.1:引用JS文件 /*! jQuery v1.12.4 | (c) jQuery Foundation | jquery.org/license */ !function(a,b ...

  10. 多选下拉框带搜索(aps.net)

    自己写了一个带搜索功能的多选下拉框,为了要获取值,就没有封装插件,实现思路 1.一个文本框 做搜索 2.一个文本框显示选中文本,一个隐藏控件存值 3.一个div里面绑定CheckBoxList控件(这 ...

随机推荐

  1. OpenHarmony技术日圆满举行丨3.1 Release版本重磅发布,生态落地初具规模

    4 月 25 日,"共建新技术,开拓新领域"OpenAtom OpenHarmony(以下简称"OpenHarmony")技术日在深圳顺利召开.活动现场,Ope ...

  2. Kubernetes(K8S)命令指南

    本文提供了一份全面的Kubernetes(K8S)命令指南,旨在帮助用户掌握和运用K8S的各种命令. 关注[TechLeadCloud],分享互联网架构.云服务技术的全维度知识.作者拥有10+年互联网 ...

  3. SSM框架整合——书籍管理系统

    1.准备工作: 1.1.环境要求 IDEA MySQL 5.7.19 Tomcat 9 Maven 3.6 1.2.数据库设计 创建一个存放书籍数据的数据库表: CREATE DATABASE `ss ...

  4. 想学习eTS开发?教你开发一款IQ-EQ测试应用

    原文:https://mp.weixin.qq.com/s/eZgifjirAW58dFCa0W7kSQ,点击链接查看更多技术内容. 开发者Mack基于HarmonyOS的ArkUI框架开发的IQ- ...

  5. 重走py 之路 ——列表(一)

    前言 因为最近公司有python项目维护,所以把python的基础入门的书整理一遍,因为有些忘记了,同时在看<<python编程>>这本书的时候觉得对有基础的有很多的赘余,打算 ...

  6. leetcode:1381. 设计一个支持增量操作的栈

    1381. 设计一个支持增量操作的栈 请你设计一个支持下述操作的栈. 实现自定义栈类 CustomStack : CustomStack(int maxSize):用 maxSize 初始化对象,ma ...

  7. bookstack书栈网docker搭建

    准备好数据后,直接运行以下命令即可. docker run -d --name bookstack \ --restart always \ --privileged=true\ -p 8181:81 ...

  8. redis cluster 的核心原理分析:gossip 通信、jedis smart 定位、主备切换

    节点间的内部通信机制 基础通信原理 redis cluster 节点间采取 gossip 协议进行通信 gossip:互相之间不断通信,保持整个集群所有节点的数据是完整的 而集中式是将集群元数据(节点 ...

  9. 《c#高级编程》第5章C#5.0中的更改(十)——异步编程

    C#异步编程是一种在单线程上实现并发执行的技术,它通过使用异步方法.任务等高级概念,使得应用程序能够更好地响应用户操作.处理大量数据和操作外部资源.C#异步编程的核心概念包括: 异步方法:使用 asy ...

  10. 力扣1077(MySQL)-项目员工Ⅲ(中等)

    题目: 写 一个 SQL 查询语句,报告在每一个项目中经验最丰富的雇员是谁.如果出现经验年数相同的情况,请报告所有具有最大经验年数的员工. 查询结果格式在以下示例中: employee_id 为 1 ...