具有多项选择的可操作下拉菜单

Handsontable dropdowns with multiple selections

我正在尝试扩展 handsontable 插件以支持其下拉列表中的多个 selection。我已经尝试通过按照建议 https://github.com/trebuchetty/Handsontable-select2-editor/issues/7 修改 'dropdownEditor' 来扩展库中内置的基本编辑器。我花了几个小时阅读和搜索源代码中的关键字,但我没有想出任何真正有用的东西。

我不介意使用 Angular 扩展或另一种扩展 https://github.com/handsontable/handsontable 插件的本机 ECMA5 或 6 方式来回答这个问题。

到目前为止,我唯一的想法是按照现有模式使用这段代码实际扩展框架。我在下面添加了所有指向的 LOC:multiselectHandsontable.MultiselectDropdownCell 复制了 dropdown 方法,调用了新名称并且一切正常,但是仍然看不到我可以从哪里开始找到我的东西正在寻找。

Handsontable.MultiselectDropdownCell = {
  editor: getEditorConstructor('multiselectdropdown'),
  renderer: getRenderer('autocomplete')
};

Handsontable.cellTypes = {
  text: Handsontable.TextCell,
  date: Handsontable.DateCell,
  numeric: Handsontable.NumericCell,
  checkbox: Handsontable.CheckboxCell,
  autocomplete: Handsontable.AutocompleteCell,
  handsontable: Handsontable.HandsontableCell,
  password: Handsontable.PasswordCell,
  dropdown: Handsontable.DropdownCell,
  multiselect: Handsontable.MultiselectDropdownCell
};

Handsontable.cellLookup = { validator: {
    numeric: Handsontable.NumericValidator,
    autocomplete: Handsontable.AutocompleteValidator
}};

我有一个修改版的下拉编辑器,看起来像:

import {getEditor, registerEditor} from './../editors.js';
import {AutocompleteEditor} from './autocompleteEditor.js';

/**
 * @private
 * @editor MultiSelectDropdownEditor
 * @class MultiSelectDropdownEditor
 * @dependencies AutocompleteEditor
 */
class MultiSelectDropdownEditor extends AutocompleteEditor {
  prepare(row, col, prop, td, originalValue, cellProperties) {
    super.prepare(row, col, prop, td, originalValue, cellProperties);
    this.cellProperties.filter = false;
    this.cellProperties.strict = true;
  }
}

export {MultiSelectDropdownEditor};

registerEditor('multiselectdropdown', MultiSelectDropdownEditor);

在这一点上,当用户 select 从下拉列表中选择一项时,我不知道点击事件发生在何处。调试对我来说很痛苦,因为它是通过 Traceur 进行的。我尝试在模块准备就绪后设置点击事件,DOM 也准备就绪,但是我什至无法通过点击 select 下拉单元格之一触发警报。 'normal' 个单元格我可以通过简单的点击获得:

$('body').on('click','#handsontable td', someAlert)

但是,菜单内容并非如此。右键单击以检查下拉菜单意味着首先禁用上下文菜单,如 http://handsontable.com/ 上的那个。然后您会注意到,右键单击检查任何内容都会触发一个事件,该事件会关闭您尝试检查的下拉菜单。

我在整个库源代码中都设置了断点,我无法理解这一点。

我唯一想做的就是找出代码中突出显示菜单项并将其设置为活动 selection 的部分,将其转换为接受多个 selections(最多可用的整个选项数组,单击一个活动项目将禁用它,只是说)。

然后确保那些 select 离子实际上在 Handsontable 'data scope' 中。

就是这样,我什至不需要它在单元格中呈现已选择的内容,尽管任何帮助都会很棒,因为不幸的是,当下拉列表中的选项是呈现。

我也尝试过使用为 handsontable 制作的 Select2Editor,如 http://jsfiddle.net/4mpyhbjw/40/ and https://github.com/trebuchetty/Handsontable-select2-editor/issues/3 所示,但是它对我的事业没有多大帮助。 handsontable 中的下拉单元格如下所示:

http://docs.handsontable.com/0.15.1/demo-dropdown.html

最后,fiddle:http://jsfiddle.net/tjrygch6/

如果有人能帮助我,我将不胜感激。非常感谢!

更新

我已经设法解析单元格中的值并将类型转换为包含值的数组(因此键入红色蓝色将转换为包含 ['red','blue'] 的数组)。我通过解析选项的内部排序算法和 returns 匹配项的索引得到 运行 这个数组。我得到这个工作正常,我现在将数组传递给突出显示方法。此方法将值传递给核心库 WalkOnTable。我看不出在哪里可以将逻辑更改为 select 多个值,而不是取消突出显示第一个选项。

 this.selectCell = function(row, col, endRow, endCol, scrollToCell, changeListener) {
var coords;
changeListener = typeof changeListener === 'undefined' || changeListener === true;
if (typeof row !== 'number' && !Array.isArray(row) || row < 0 || row >= instance.countRows()) {
  return false;
}
if (typeof col !== 'number' || col < 0 || col >= instance.countCols()) {
  return false;
}
if (typeof endRow !== 'undefined') {
  if (typeof endRow !== 'number' || endRow < 0 || endRow >= instance.countRows()) {
    return false;
  }
  if (typeof endCol !== 'number' || endCol < 0 || endCol >= instance.countCols()) {
    return false;
  }
}
// Normal number value, one item typed in
if (!Array.isArray(row) && typeof row === 'number'){
  coords = new WalkontableCellCoords(row, col);

  walkSelection(coords);
}

这是我认为我需要修改 WalkontableCellCoords 以接受数组然后在打开和关闭下拉列表时突出显示和 select 这两个值的地方。我还需要能够通过触摸或点击事件 select 多个选项。

else {
  // Array found, apply to each value
  new WalkontableCellCoords(row[0], col);
  new WalkontableCellCoords(row[1], col);
}

function walkSelection(coords){
  priv.selRange = new WalkontableCellRange(coords, coords, coords);
  if (document.activeElement && document.activeElement !== document.documentElement && document.activeElement !== document.body) {
    document.activeElement.blur();
  }
  if (changeListener) {
    instance.listen();
  }
  if (typeof endRow === 'undefined') {
    selection.setRangeEnd(priv.selRange.from, scrollToCell);
  } else {
    selection.setRangeEnd(new WalkontableCellCoords(endRow, endCol), scrollToCell);
  }
  instance.selection.finish();
}

return true;

};

更新 2

我已经得到内部方法来识别 select DOM 中的两个值,但它仍然远未正确。

这是要调用的方法 WalkOnTableCellCords 生成的控制台输出,这似乎是在单元格仅包含 1 个值(默认功能)的情况下突出显示下拉列表 selection 的原因.此输出是通过将 black blue 键入包含蓝色和黑色作为列表中的单独选项的下拉单元格。

extended_hot_v15-01.js:5041 DropdownEditor {
            "highlight": {
                    "row": 6,
                    "col": 0
            },
            "from":
                   {
                    "row": 4,
                    "col": 0
                   },
             "to": {
                    "row": 6,
                    "col": 0
                    }
            }

UPDATE 如果有人解决了这个问题,我会亲自飞到你在的地方,和你握手。两次。

好的,希望对你有所帮助。我花了时间阅读 api 并自定义代码 :)

我从 Handsontable 库(最新版本)中获取了示例代码并做了一些小改动。

它可能存在一些错误,但它只是一个原型,因此您当然可以对其进行编辑并使其看起来更好。

出于某种原因,我未能成功使 dropdownlist 可点击。这似乎是 z-index 问题或其他 css 属性游戏。我相信您会找到解决方法。 无论如何,现在,您可以通过按住 shift 键 select 来使用键盘 select。

输出是由逗号分隔的 selected 选项的集合。

例如:

要使该工作正常,请在加载 handsontable 库后添加此代码。它将扩展您的 Handsontable 细胞类型。

(function(Handsontable) {
    var SelectEditor = Handsontable.editors.BaseEditor.prototype.extend();

    SelectEditor.prototype.init = function() {
        // Create detached node, add CSS class and make sure its not visible
        this.select = document.createElement('SELECT');
        Handsontable.Dom.addClass(this.select, 'htSelectEditor');
        this.select.style.display = 'none';

        // Attach node to DOM, by appending it to the container holding the table
        this.instance.rootElement.appendChild(this.select);
    };
    // Create options in prepare() method
    SelectEditor.prototype.prepare = function() {
        // Remember to invoke parent's method
        Handsontable.editors.BaseEditor.prototype.prepare.apply(this, arguments);
        this.isMultiple = !!this.cellProperties.multiple;
        if (this.isMultiple) this.select.multiple = true;
        var selectOptions = this.cellProperties.selectOptions;
        var 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);

        for (var option in options) {
            if (options.hasOwnProperty(option)) {
                var optionElement = document.createElement('OPTION');
                optionElement.value = option;
                Handsontable.Dom.fastInnerHTML(optionElement, options[option]);
                this.select.appendChild(optionElement);
            }
        }
    };
    SelectEditor.prototype.prepareOptions = function(optionsToPrepare) {
        var preparedOptions = {};

        if (Array.isArray(optionsToPrepare)) {
            for (var i = 0, len = optionsToPrepare.length; i < len; i++) {
                preparedOptions[optionsToPrepare[i]] = optionsToPrepare[i];
            }
        } else if (typeof optionsToPrepare == 'object') {
            preparedOptions = optionsToPrepare;
        }

        return preparedOptions;
    };
    SelectEditor.prototype.getValue = function() {
        var result = [];
        var options = this.select && this.select.options;
        var opt;

        for (var i = 0, iLen = options.length; i < iLen; i++) {
            opt = options[i];

            if (opt.selected) {
                result.push(opt.value || opt.text);
            }
        }

        return result.join();
    };

    SelectEditor.prototype.setValue = function(value) {
        this.select.value = value;
    };

    SelectEditor.prototype.open = function() {
        var width = Handsontable.Dom.outerWidth(this.TD);
        // important - group layout reads together for better performance
        var height = Handsontable.Dom.outerHeight(this.TD);
        var rootOffset = Handsontable.Dom.offset(this.instance.rootElement);
        var tdOffset = Handsontable.Dom.offset(this.TD);
        var editorSection = this.checkEditorSection();
        var cssTransformOffset;

        if (this.select && this.select.options && this.isMultiple) {
            var height = 0;
            for (var i = 0; i < this.select.options.length - 1; i++) {
                height += Handsontable.Dom.outerHeight(this.TD);
            }
        }

        switch (editorSection) {
            case 'top':
                cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtScrollbars.vertical.clone.wtTable.holder.parentNode);
                break;
            case 'left':
                cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtScrollbars.horizontal.clone.wtTable.holder.parentNode);
                break;
            case 'corner':
                cssTransformOffset = Handsontable.Dom.getCssTransform(this.instance.view.wt.wtScrollbars.corner.clone.wtTable.holder.parentNode);
                break;
        }
        var selectStyle = this.select.style;

        if (cssTransformOffset && cssTransformOffset !== -1) {
            selectStyle[cssTransformOffset[0]] = cssTransformOffset[1];
        } else {
            Handsontable.Dom.resetCssTransform(this.select);
        }

        selectStyle.height = height + 'px';
        selectStyle.minWidth = width + 'px';
        selectStyle.top = tdOffset.top - rootOffset.top + 'px';
        selectStyle.left = tdOffset.left - rootOffset.left + 'px';
        selectStyle.margin = '0px';
        selectStyle.display = '';

    };

    SelectEditor.prototype.checkEditorSection = function() {
        if (this.row < this.instance.getSettings().fixedRowsTop) {
            if (this.col < this.instance.getSettings().fixedColumnsLeft) {
                return 'corner';
            } else {
                return 'top';
            }
        } else {
            if (this.col < this.instance.getSettings().fixedColumnsLeft) {
                return 'left';
            }
        }
    };

    SelectEditor.prototype.close = function() {
        this.select.style.display = 'none';
    };

    Handsontable.editors.registerEditor('dvirH', SelectEditor);

})(Handsontable);

使用方法:

var container = document.getElementById("example1");
var hot1;

hot1 = new Handsontable(container, {
    data: [
        ['2008', 'Nissan', 11],
        ['2009', 'Honda', 11],
        ['2010', 'Kia', 15]
    ],
    colHeaders: true,
    contextMenu: false,
    columns: [{}, {
        editor: 'select',
        selectOptions: ['Kia', 'Nissan', 'Toyota', 'Honda'],
        //  notice that attribute. You can remove it to get a regular select
        multiple: true
    } {}]
});

现场演示 here

为了方便您。如果您想编辑代码,您可能需要更改 2 种方法。

  1. prepare - 每次用户触发编辑器打开事件时都会调用。用于配置和操作。
  2. init - 每次单击单元格时都会调用该方法。它会创建 html 代码,因此您可以将其更改为复选框等。

另一件事与您有关代码中的内容的问题有关。

Handsontable 将任何单元格类型拆分为编辑器和渲染器。 编辑器的所有 html 代码可能都存在于 init 中,以防您想更改其中一个。 value 是html 当你不是 在编辑模式时出现在单元格中的内容存在于getValue 方法中。

希望对您有所帮助,也希望它适合您当前的版本。

哇。如此努力。现在,一年多过去了,它变得容易多了。

我成功使用了 Chosen jQuery 插件。这很容易。

这是一个人的例子: https://github.com/mydea/handsontable-chosen-editor

选择是美好的。我正在使用带有多选的自动完成功能。这是渲染器:

function customDropdownRenderer(instance, td, row, col, prop, value, cellProperties) {

    var selectedId;
    var optionsList = cellProperties.chosenOptions.data;

    if(typeof optionsList === "undefined" || typeof optionsList.length === "undefined" || !optionsList.length) {
        Handsontable.TextCell.renderer(instance, td, row, col, prop, value, cellProperties);
        return td;
    }

    var values = (value + "").split(",");
    value = [];
    for (var index = 0; index < optionsList.length; index++) {

        if (values.indexOf(optionsList[index].id + "") > -1) {
            selectedId = optionsList[index].id;
            value.push(optionsList[index].label);
        }
    }
    value = value.join(", ");

    Handsontable.TextCell.renderer(instance, td, row, col, prop, value, cellProperties);
    return td;
}

然后我像这样设置特定的列:

columns: [
    {},
    {},
    {type: 'numeric'},
    {type: 'dropdown', source: ['', 'NAME', 'FNB']},
    {},
    {},
    {},
    {},
    {},
    {},
    {},
    {type: 'dropdown', source: ['', 'S', 'M']},
    {},
    {},
    {
        renderer: customDropdownRenderer,
        editor: "chosen",
        width: 150,
        chosenOptions: {
            multiple: true,
            data: productData
        }
    },
    {},
    {editor: false, readOnly: true, width: 1}, 
    {editor: false, readOnly: true, width: 1}
],