【问题标题】:Knockout binding for DataTables with FixedColumns extension not working as expected具有 FixedColumns 扩展的 DataTables 的敲除绑定未按预期工作
【发布时间】:2015-12-11 18:19:51
【问题描述】:

我正在为 DataTables 使用敲除绑定 suggested here。但是,当使用 FixedColumns 扩展(将原始数据表克隆到新数据表)时,我失去新数据表和现有 viewmodel/bindingContext 之间的绑定。

例如,在固定列上有一个选择复选框以从表中选择项目将不会按预期运行。

绑定如下所示:

ko.bindingHandlers.dataTablesForEach = { 
page: 0,
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { 
    var binding = ko.utils.unwrapObservable(valueAccessor());

    if (binding.options.paging) {
        binding.data.subscribe(function(changes) {
            var table = $(element).closest('table').DataTable();
            ko.bindingHandlers.dataTablesForEach.page = table.page();
            table.destroy();
        }, null, 'arrayChange');
    }

    var nodes = Array.prototype.slice.call(element.childNodes, 0);
    ko.utils.arrayForEach(nodes, function(node) {
        if (node && node.nodeType !== 1) {
            node.parentNode.removeChild(node);
        }
    });

    return ko.bindingHandlers.foreach.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    var binding = ko.utils.unwrapObservable(valueAccessor()),
        key = 'DataTablesForEach_Initialized';

    var table;
    if (!binding.options.paging) {
        table = $(element).closest('table').DataTable();
        table.destroy();
    }

    ko.bindingHandlers.foreach.update(element, valueAccessor, allBindings, viewModel, bindingContext);

    table = $(element).closest('table').DataTable(binding.options);

    if (binding.options.paging) {
        if (table.page.info().pages - ko.bindingHandlers.dataTablesForEach.page === 0) {
            table.page(--ko.bindingHandlers.dataTablesForEach.page).draw(false);
        } else {
            table.page(ko.bindingHandlers.dataTablesForEach.page).draw(false);
        }
    }

    if (!ko.utils.domData.get(element, key) && (binding.data || binding.length)) {
        ko.utils.domData.set(element, key, true);
    }

    return {
        controlsDescendantBindings: true
    };
}

(See the full working example).

【问题讨论】:

    标签: knockout.js datatables


    【解决方案1】:

    哇...这是一个棘手的问题。我认为您需要在应用绑定之前使 html 可用,或者您需要克隆数据并将绑定应用到该数据。第一个选项是不可能的,因为在初始化数据表时会调用 fixedColumns 插件。这是在这个绑定中发生的,并且在调用 ko.applyBindings 时发生。矛盾的.. :)

    我还想补充一点,您不会因为固定列而丢失绑定。绑定仍然存在。只是插件创建的新 html 浮动在原始绑定的 html 之上。

    不过,我确实让这个 hack 起作用了......

    在调用 applybindings 后添加这个...

    编辑:需要将事件绑定到文档,因为固定列插件会根据页面动态重写元素。

    $(document).on('click','.DTFC_LeftBodyWrapper input[type="checkbox"]',function(){
       var value = $(this).attr('value');
         $('.dataTables_scrollBody').find('input[value="'+value+'"]').click(); });
    

    我想如果你不能绑定克隆的 html,那么你可以强制它与原始 html 交互......这真的很 hacky 但它有效..¯_(ツ)_/¯

    forked working example CODEPEN

    【讨论】:

    • 是的,我也想过。我尝试在新的 cloned 初始化后将其应用BindingsToNode 但未成功。--- --- 是的,我还注意到我没有 失去 上下文(使用 ko.新表上的 contextFor(...) 表明 bindingContext 在那里)。然而,不知何故,该表的 2 向绑定 broken.--- --- 是的,它有点 hacky,但它是一个开始:p 感谢您的输入!
    • 当然绑定仍然存在...所有固定列插件所做的就是获取您指定的列,将它们克隆到一个新表中,并在原始表上方显示完整的单独表固定位置。如果您右键单击固定列并在调试器中检查并找到它的父标签<table>,并将其显示样式更改为display:none;,您将看到您的原始表格仍然存在并且仍然绑定到您的可观察数组。跨度>
    • 至于这个“但是,不知何故,该表的 2 路绑定被破坏了”——不,它不是“破坏”。当淘汰赛开始将绑定应用于 html 时,它根本不存在。 clone 列是在敲除应用绑定后创建的。另外,要考虑这一点:每次您的用户选择一个新页面时,克隆的列将被删除并替换为新的 html。 Knockout 在动态 html 中表现不佳。
    • 很抱歉回复晚了,但是我有很多工作,然后我去度假了。这周我会再尝试一次改进。是的,我知道绑定不存在。但是,我希望将绑定重新应用于 DOM 元素(在固定列初始化之后)可以解决这个问题。
    • No.. 将绑定重新应用于“固定列”将无法解决该问题。我一遍又一遍地想出一个解决方案,但一直在绕圈子。恕我直言,处理固定列的最佳选择是编写自己的事件处理程序以与“固定列”进行交互,这些“固定列”将以编程方式与原始剔除绑定数据表列进行交互。
    【解决方案2】:

    我最终得到了一个绑定,它在创建克隆表(即固定列表)后将淘汰上下文重新应用到它:

    import $ from 'jquery';
    import ko from 'knockout';
    import 'datatables.net';
    import 'datatables.net-fixedColumns';
    
    const defaultOptions = {
        deferRender: true,
        paging: true
    };
    
    ko.bindingHandlers.datatables = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            const $element = $(element),
                binding = ko.unwrap(valueAccessor()),
                options = binding.options || {};
    
            $.extend(true, options, defaultOptions);
    
            if (binding.rowTemplateId && binding.data) {
    
                // bind the header first
                ko.applyBindingsToDescendants(viewModel, $element.find('thead')[0]);
    
                setupTableBody($element, binding, bindingContext);
    
                if (ko.isObservable(binding.data)) {
    
                    // destroy and build the table again when the data changes
                    binding.data.subscribe(() => {
                        $element.DataTable().destroy();
                        $element.find('tbody').remove();
    
                        setupTableBody($element, binding, bindingContext);
                        initializeDataTable(element, options);
                    }, null, 'arrayChange');
                }
            }
    
            initializeDataTable(element, options);
    
            ko.utils.domNodeDisposal.addDisposeCallback(element, () => {
                $(element).DataTable().destroy();
            });
    
            return {
                controlsDescendantBindings: true
            };
        }
    };
    
    function initializeDataTable(element, options) {
        const table = $(element).DataTable(options);
    
        if (options.fixedColumns) {
    
            // we need to apply the context to the cloned table for the first time
            setTimeout(() => {
                applyBindingsToClonedRows(element, options.fixedColumns);
            }, 0);
    
            // register handler to fix the cloned table column width
            // when the table is (re)drawn
            table.on('draw.dt', (event) => {
                $(event.target).DataTable().fixedColumns().relayout();
            });
    
            // register handler to fix the cloned table binding context
            // when the table is (re)drawn
            table.on('draw.dt.DTFC', (event) => {
                applyBindingsToClonedRows(event.target, options.fixedColumns);
            });
        }
    }
    
    function setupTableBody($element, binding, bindingContext) {
    
        // render each element of the body with the template
        let tbody = $element.find('tbody')[0];
        if (!tbody) {
            tbody = document.createElement('tbody');
            $element.append(tbody);
        }
        ko.renderTemplateForEach(ko.unwrap(binding.rowTemplateId), binding.data, {}, tbody, bindingContext);
    }
    
    function applyBindingsToClonedRows(originalTable, fixedColumnsOptions) {
        const $table = $(originalTable);
        const rows = $table.find('tbody>tr');
    
        if (fixedColumnsOptions.leftColumns) {
            const clonedRows = $table.parent().parent().parent().find('.DTFC_LeftBodyWrapper .DTFC_Cloned').find('tbody>tr');
            for (let i = 0; i < rows.length; i++) {
                ko.applyBindings(ko.contextFor(rows[i]), clonedRows[i]);
            }
        }
    
        if (fixedColumnsOptions.rightColumns) {
            const clonedRows = $table.parent().parent().parent().find('.DTFC_RightBodyWrapper .DTFC_Cloned').find('tbody>tr');
            for (let i = 0; i < rows.length; i++) {
                ko.applyBindings(ko.contextFor(rows[i]), clonedRows[i]);
            }
        }
    }
    

    我创建了一个github repo,其中还包含一些示例,因为它可能对其他人有所帮助。

    如果您觉得这回答了问题,请投票,我会将其标记为答案 - 它肯定回答了我最初的问题,但我希望能得到一些反馈。

    请注意这是 ES2015 代码,因此您需要对其进行转译。

    【讨论】:

      猜你喜欢
      • 2013-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-07
      • 1970-01-01
      • 2010-09-19
      • 2012-08-17
      • 2018-03-09
      相关资源
      最近更新 更多