多项选择的动态下拉列表

时间:2015-08-11 00:37:43

标签: javascript angularjs drop-down-menu multi-select handsontable

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

我不介意使用Angular扩展或其他原生ECMA5或扩展https://github.com/handsontable/handsontable插件的方式来解决这个问题。

到目前为止,我唯一的想法是使用这些代码按照存在的模式实际扩展框架。我添加了以下指向的所有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);

此时我不知道用户从下拉列表中选择项目时发生点击事件的位置。调试对我来说很痛苦,因为它是通过Traceur实现的。我尝试在模块准备就绪后设置点击事件,并且DOM也是如此,但是根据其中一个选择下拉单元格的点击,我甚至无法触发警报。正常'我可以通过简单的单击获得单击:

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

但是,菜单内容不是这样。右键单击以检查下拉菜单意味着首先禁用像http://handsontable.com/上的上下文菜单。然后您会注意到右键单击以检查任何内容将触发关闭您要检查的下拉菜单的事件。

我已经通过库源代码放置了断点,我无法想出这个。

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

然后确保这些选择实际上在Handsontable'数据范围内。

多数民众赞成,我不需要它甚至在单元格中渲染所选择的东西,虽然任何帮助都会很棒,因为不幸的是,我还没有找到当下拉选项中的选项时的位置渲染。

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

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

最后,还有一个小提琴:http://jsfiddle.net/tjrygch6/

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

更新

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

 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以接受数组,然后在打开和关闭下拉列表时突出显示并选择这两个值的地方。我还需要能够通过触摸或点击事件选择多个选项。

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

我已经获得了内部方法来识别和部分选择DOM中的两个值,但它仍远未正确。

Showing the selection (sort of) of both items based on two values typed into the cell, also showing the output in the console for the coord being returned from the WalkOnTable util being used behind the scenes in this handsontable plugin. Output is below

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

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

更新如果有人解决了这个问题,我会亲自飞往您所在的任何地方,并握手。两次。

3 个答案:

答案 0 :(得分:4)

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

我从Handsontable库(最新版本)中获取了示例代码并进行了少量更改。

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

出于某种原因,我没有成功使dropdownlist可点击。它似乎是z-index问题或其他css属性游戏。我相信你会找到解决方法。 无论如何,你可以通过按住shift来选择键盘来进行选择。

输出是以逗号分隔的已连接选定选项的集合。

例如:

enter image description here enter image description here

为了完成这项工作,在加载handontable libary之后添加此代码。它将扩展您的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

中的现场演示

让你轻松自如。如果要编辑代码,可能需要更改两种方法。

  1. prepare - 每次用户触发编辑器打开事件时都会调用。用于配置和操作。
  2. init - 每次单击一个单元格时都会调用该方法。它会创建html代码,因此您可以将其更改为复选框,例如。
  3. 另一件事与您关于代码中的内容的问题有关。

    Handsontable将任何单元格类型拆分为编辑器和渲染。 编辑器的所有html代码可能都存在于init中,以防您想要更改其中一个。 valuegetValue方法中处于编辑模式时显示在单元格中的html内容。

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

答案 1 :(得分:2)

哇。这么多努力。现在,一年多以后,它变得更容易了。

我成功使用了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}
],

答案 2 :(得分:0)

使用jExcel可以为您提供更多的支持。

https://bossanova.uk/jexcel/examples/working-with-dropdowns

<html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jexcel/2.1.0/js/jquery.jexcel.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jexcel/2.1.0/js/jquery.jdropdown.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jexcel/2.1.0/css/jquery.jexcel.min.css" type="text/css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jexcel/2.1.0/css/jquery.jdropdown.min.css" type="text/css" />

<div id="my"></div>

<script>
data2 = [
    [3, 'Cheese', 0],
    [1, 'Apples', 1],
    [2, 'Carrots', 0],
    [1, 'Oranges', 0],
];

dropdown = function(instance, cell, c, r, source) {
    // Get a value from the same row but previous column
    var value = $(instance).jexcel('getValue', c-1 + '-' + r);

    // Return the values will be part in your current column
    if (value == 1) {
        return ['Apples','Bananas','Oranges'];
    } else if (value == 2) {
        return ['Carrots'];
    } else {
        return source;
    }
}

$('#my2').jexcel({
    data:data2,
    colHeaders: ['Category','Product', 'Buy later'],
    colWidths: [ 300, 200, 100 ],
    columns: [
        { type: 'dropdown', source: [ {'id':'1', 'name':'Fruits'},  {'id':'2', 'name':'Legumes'}, {'id':'3', 'name':'General Food'} ] },
        { type: 'dropdown', source: [ 'Apples', 'Bananas', 'Carrots', 'Oranges', 'Cheese' ], filter:dropdown },
        { type: 'checkbox' },
    ]
});
</script>
</html>