为啥我的 Handsontable 自定义编辑器中的复选框不切换?

Posted

技术标签:

【中文标题】为啥我的 Handsontable 自定义编辑器中的复选框不切换?【英文标题】:Why don't checkboxes in my Handsontable Custom Editor toggle?为什么我的 Handsontable 自定义编辑器中的复选框不切换? 【发布时间】:2021-08-26 23:03:50 【问题描述】:

我正在使用最新的 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, col, prop, td, originalValue, 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, td, row, col, prop, 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", "Attr2": "yes", "Attr3": "somevalue")],
            [JSON.stringify("Attr1": "no", "Attr2": "no", "Attr3": "")],
        ],
        columns: [
            renderer: CustomAttributesRenderer, editor: CustomAttributesEditor
        ],
        rowHeaders: true,
        colHeaders: true,
        filters: true,
        dropdownMenu: true
    );
)

结果正确显示,由于渲染器没有显示复选框,单元格最初没有可见的复选框,然后当您单击单元格时,复选框会出现。问题是当您单击复选框时它不会切换。

我认为 handsontable 中的某些内容正在重新创建 td 并消除状态更改,但我不知道如何防止这种情况发生。是否有一个完全可用的自定义编辑器小提琴或我可以参考的东西来弄清楚如何防止冒泡?

您能提供的任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

好吧,经过几天的折腾,我找到了完成这项工作所需添加的完整代码集。在这里发布以防它帮助其他人尝试在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 = `$heightpx`;
        selectStyle.minWidth = `$widthpx`;
        selectStyle.top = `$editToppx`;
        selectStyle.left = `$editLeftpx`;
        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
    ],

【讨论】:

以上是关于为啥我的 Handsontable 自定义编辑器中的复选框不切换?的主要内容,如果未能解决你的问题,请参考以下文章

Handsontable,自定义文本编辑器,复制/粘贴问题

自定义渲染器未应用于 Table 渲染 handsontable

handsontable整理

使用python中的Tornado模块在handsontable中渲染自定义数据

自定义单元格渲染器操作未在 handsontable 中触发

计算不适用于 Handsontable 中使用 AJAX 的自定义单元格渲染