handsontable多选下拉框编辑器扩展
一、效果截图
二、文件引用
多选下拉框扩展自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);
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; }
五、更新
日期: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);
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; }