问题描述
我使用的是最新的 Handsontable 9.0.0 版,没有任何框架,我正在尝试遵循 Cell editor 开发人员指南,但我不知所措。
我的要求是在一个单元格中显示几个复选框和一个文本框(不是我的想法)。我的想法是让单元格的数据是一个小的 json 字符串 {"Attr1": true,"Attr2": false} 并有一个自定义渲染器/编辑器来解析单元格值并适当地设置复选框。
我在这里做了一个小提琴:http://jsfiddle.net/9k1x4z6b/2/
我为自定义属性列创建了一个类和一个渲染器函数,并像这样为该列设置了渲染器和编辑器
class CustomAttributesEditor extends Handsontable.editors.BaseEditor {
/**
* Initializes editor instance,DOM Element and mount hooks.
*/
// constructor (props) {
// super(props)
// }
prepare(row,col,prop,td,originalValue,cellProperties) {
// Invoke the original method...
super.prepare(row,cellProperties);
td.innerHTML = '';
this.AttributeNames = ['Attr1','Attr2'];
this.ctrls = {};
let parsedValue = JSON.parse(Handsontable.helper.stringify(originalValue));
// Create checkBox controls
for (let i = 0; i < this.AttributeNames.length; i++) {
let AttributeName = this.AttributeNames[i];
let span = document.createElement('span');
span.style.whiteSpace = 'Nowrap';
let checkBox = document.createElement('input');
this.ctrls[AttributeName] = checkBox;
checkBox.type = 'checkBox';
if (parsedValue[AttributeName] == 'yes') {
checkBox.checked = true;
}
let label = document.createElement('label');
label.innerHTML = AttributeName;
label.htmlFor = checkBox.id;
span.appendChild(checkBox);
span.appendChild(label);
td.appendChild(span);
td.appendChild(document.createElement('br'));
}
// Create a control that is shown/hidden when the "Attr2" checkBox is toggled
let CustomAttributesAttr3SubDiv = document.createElement('div');
var label = document.createElement('label');
label.innerHTML = "Attr3 supplier:";
CustomAttributesAttr3SubDiv.appendChild(label);
var CustomAttributesAttr3 = document.createElement('input');
if (parsedValue.hasOwnProperty('Attr3')) {
CustomAttributesAttr3.value = parsedValue['Attr3'];
}
this.ctrls['Attr3'] = CustomAttributesAttr3;
this.AttributeNames.push('Attr3');
CustomAttributesAttr3.setAttribute('title','Attr3');
CustomAttributesAttr3.style.width = '12em';
CustomAttributesAttr3SubDiv.appendChild(CustomAttributesAttr3);
CustomAttributesAttr3SubDiv.appendChild(document.createElement('br'));
td.appendChild(CustomAttributesAttr3SubDiv);
let Attr2CheckBox = this.ctrls['Attr2'];
//CustomAttributes_ShowHideValueCtrl(Attr2CheckBox);
$(Attr2CheckBox).off('change').on('change',function () {
//CustomAttributes_ShowHideValueCtrl(this); // irrelevant to checkBox problem. function shows Attr3 input when Attr2CheckBox is checked,hides otherwise
});
//preventDefault();
}
getValue(){
// This function returns the set value of the controls
let ctrls = this.ctrls;
let resultDict = {};
for (let ctrlID in ctrls){
let ctrl = ctrls[ctrlID];
let FormattedAttributeName = ctrlID.replaceAll(' ','_');
let val = null;
if (ctrl.type == 'checkBox'){
if (ctrl.checked == true) {
val = 'yes';
} else {
val = null;
}
} else {
val = ctrl.value;
}
resultDict[FormattedAttributeName] = val;
}
return JSON.stringify(resultDict)
}
setValue(value){
// this function sets the value of the controls to match the data value
let parsedValue = {};
try {
parsedValue = JSON.parse(Handsontable.helper.stringify(value));
} catch (exc) {
for (let i = 0; i < this.AttributeNames.length; i++) {
parsedValue[this.AttributeNames[i]] = 'no';
}
}
let ctrls = this.ctrls;
let resultDict = {};
for (let ctrlID in ctrls){
let ctrl = ctrls[ctrlID];
let FormattedAttributeName = ctrlID.replaceAll(' ','_');
let val = parsedValue[FormattedAttributeName];
if (ctrl.type == 'checkBox'){
if (val == 'yes'){
ctrl.checked = true;
} else {
ctrl.checked = false;
}
} else {
ctrl.value = val;
}
}
}
saveValue(value,ctrlDown){
super.saveValue(value,ctrlDown);
}
open(){}
close(){}
focus(){}
}
function CustomAttributesRenderer(instance,row,value,cellProperties) {
// This function shows labels for the checked Attr1-3 values
let AttributeNames = ['Attr1','Attr2','Attr3'];
parsedValue = JSON.parse(Handsontable.helper.stringify(value));
Handsontable.dom.empty(td);
for (let i = 0; i < AttributeNames.length; i++) {
let AttributeName = AttributeNames[i];
let span = document.createElement('span');
span.style.whiteSpace = 'Nowrap';
if (parsedValue[AttributeName] == 'yes') {
let label = document.createElement('label');
label.innerHTML = AttributeName;
span.appendChild(label);
td.appendChild(span);
}
td.appendChild(document.createElement('br'));
}
return td;
}
document.addEventListener("DOMContentLoaded",function () {
var container = document.getElementById('divBFEPartMatrix');
var hot = new Handsontable(container,{
data: [
[JSON.stringify({"Attr1": "yes","Attr2": "yes","Attr3": ""})],[JSON.stringify({"Attr1": "yes","Attr3": "somevalue"})],[JSON.stringify({"Attr1": "no","Attr2": "no",],columns: [
{renderer: CustomAttributesRenderer,editor: CustomAttributesEditor}
],rowHeaders: true,colHeaders: true,filters: true,dropdownMenu: true
});
})
结果显示正确,由于渲染器未显示复选框,单元格最初没有可见的复选框,然后当您单击单元格时,复选框出现。问题是当您单击复选框时,它不会切换。
我认为 handsontable 中的某些内容正在重新创建 td 并消除状态更改,但我不知道如何防止这种情况发生。是否有一个完全有效的自定义编辑器小提琴或我可以参考的东西来找出如何防止冒泡?
如果您能提供任何帮助,我们将不胜感激。
解决方法
好吧,又折腾了几天,我想出了我需要添加的完整代码集来完成这项工作。发布在这里以防它帮助其他人尝试在 handsontable 中制作自定义单元格编辑器/渲染器。
class SubstitutePartEditor extends Handsontable.editors.BaseEditor {
init(){
// This function creates the edit div
let div = document.createElement('div');
this.div = div;
div.style.display = 'none';
div.style.position = 'absolute';
div.style.width = 'auto';
div.style.backgroundColor = 'white';
this.AttributeNames = ['The waste bin is part of the waste cart','This item includes SUPPLIER tapestry'];
this.ctrls = {};
let cbIsSubstitutePart = document.createElement('input');
this.ctrls['IsSubstitutePart'] = cbIsSubstitutePart;
cbIsSubstitutePart.type = 'checkbox';
div.appendChild(cbIsSubstitutePart);
div.appendChild(document.createElement('br'));
let SubstitutePartSubDiv = document.createElement('div');
SubstitutePartSubDiv.style.display = 'inline-block';
div.appendChild(SubstitutePartSubDiv);
let inputSubstitutePart = document.createElement('textarea');
this.ctrls['SubstitutePart'] = inputSubstitutePart;
inputSubstitutePart.style.width = '220px';
inputSubstitutePart.style.height = '88px';
SubstitutePartSubDiv.appendChild(inputSubstitutePart);
this.hot.rootElement.appendChild(div);
}
UpdateDependentControls(){
let RequiredCtrl = this.ctrls['IsSubstitutePart'];
let Ctrl = this.ctrls['SubstitutePart'];
if (RequiredCtrl.checked == true){
Ctrl.style.display = '';
} else {
Ctrl.style.display = 'none';
}
$(RequiredCtrl).off('change').on('change',function(){
if (RequiredCtrl.checked == true){
Ctrl.style.display = '';
} else {
Ctrl.style.display = 'none';
}
});
}
getValue(){
// This function returns the set value of the controls
let ctrls = this.ctrls;
let resultDict = {};
for (let ctrlID in ctrls){
let ctrl = ctrls[ctrlID];
let FormattedAttributeName = ctrlID.replaceAll(' ','_');
let val = null;
if (ctrl.type == 'checkbox'){
if (ctrl.checked == true) {
val = 'yes';
} else {
val = null;
}
} else {
val = ctrl.value;
}
resultDict[FormattedAttributeName] = val;
}
return JSON.stringify(resultDict)
}
setValue(value){
// this function sets the value of the controls to match the data value
let parsedValue = {};
try {
parsedValue = JSON.parse(Handsontable.helper.stringify(value));
} catch (exc) {
parsedValue = {
IsSubstitutePart: 'no',SubstitutePart: "This item requires a waiver from the operator's foreign regulatory agency,<FOREIGN REGULATORY AGENCY NAME>."
};
}
let ctrls = this.ctrls;
let resultDict = {};
for (let ctrlID in ctrls){
let ctrl = ctrls[ctrlID];
let FormattedAttributeName = ctrlID.replaceAll(' ','_');
let val = parsedValue[FormattedAttributeName];
if (ctrl.type == 'checkbox'){
if (val == 'yes'){
ctrl.checked = true;
} else {
ctrl.checked = false;
}
} else {
ctrl.value = val;
}
}
}
saveValue(value,ctrlDown){
super.saveValue(value,ctrlDown);
}
open() {
this._opened = true;
this.refreshDimensions();
this.UpdateDependentControls();
this.div.style.display = '';
}
refreshDimensions() {
this.TD = this.getEditedCell();
// TD is outside of the viewport.
if (!this.TD) {
this.close();
return;
}
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.div.style;
if (cssTransformOffset && cssTransformOffset !== -1) {
selectStyle[cssTransformOffset[0]] = cssTransformOffset[1];
} else {
Handsontable.dom.resetCssTransform(this.div);
}
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.div.style.zIndex = '';
break;
}
return editedCell < 0 ? void 0 : editedCell;
}
focus() {
this.div.focus();
}
close() {
this._opened = false;
this.div.style.display = 'none';
}
}
function SubstitutePartRenderer(instance,td,row,col,prop,value,cellProperties) {
// This function draws the multi checkboxes for the SubstitutePart input field
// Note: if AttributeNames changes you must also update BFEPartMatrix_Edit.ascx line ~240 to match (<- this is where the data is saved)
//Handsontable.renderers.HtmlRenderer.apply(this,arguments);
let parsedValue = {};
try {
parsedValue = JSON.parse(Handsontable.helper.stringify(value));
} catch {
// nothing to do
}
Handsontable.dom.empty(td);
let div = document.createElement('div');
//div.style.whiteSpace = 'nowrap';
div.style.display = 'block';
td.appendChild(div);
if (parsedValue.hasOwnProperty('IsSubstitutePart')) {
if (parsedValue.IsSubstitutePart == 'yes') {
} else {
td.innerHTML = 'N/A';
return;
}
} else {
td.innerHTML = 'N/A';
return;
}
let SubstitutePartSubDiv = document.createElement('div');
SubstitutePartSubDiv.style.display = 'inline-block';
div.appendChild(SubstitutePartSubDiv);
// text area
let inputSubstitutePart = document.createElement('label');
inputSubstitutePart.innerHTML = parsedValue['SubstitutePart'].escape();
inputSubstitutePart.style.width = '220px';
inputSubstitutePart.style.height = '88px';
SubstitutePartSubDiv.appendChild(inputSubstitutePart);
return td;
}
然后像这样设置热列的渲染和编辑器
columns: [
{renderer: CustomAttributesRenderer,editor: CustomAttributesEditor}
],