Angular - Datatables: 类型错误:无法读取空值的 'nodeName' 属性

3

这个问题有点棘手,我会尽力给你解释清楚。

简要说明:

我在我的Angular项目中集成了DataTables库,几个月前购买了该项目的主题后就一直使用着。现在主题已经更新,我也随之将项目更新到了最新版本。

奇怪的是,不起作用的是DataTables,而DataTables并没有改变!

代码细节:

从X组件中,我触发了我的共享服务IBOsService的一个方法。

当这个方法被触发时,我的DatatableComponent会以一个Promise的形式导入datatables库。

以前从来没有出现过这种问题,但现在出现了。

DatatableComponent 中:

this.tablesService.initTableData$.subscribe(() => {
                if (!this.datatableInitialized) {
                    log.info('Starting script import promise');

                    Promise.all([
                        System.import('script-loader!my-plugins/datatables-bundle/datatables.min.js')
                    ]).then(values => {
                        log.data('success', JSON.stringify(values));
                        this.render();
                    }, reason => {
                        log.error('error', JSON.stringify(reason));
                    });
                }
            }
        );

在我的控制台上看到:DataTables success [{}]。因此,我理解这个承诺是成功的。

然后我们进入this.render();方法。

该方法一直运行,直到这一行:

const _dataTable = element.DataTable(options);

问题是在我的IDE中,我可以浏览所有变量、方法等... 但我无法浏览到 DataTable(),因此我猜测它只是没有识别这个方法,所以才会抛出错误。

但是,由于脚本是在 promise 中加载的,所以 IDE 没有 DataTables 方法的映射是正常的...

完整组件代码:

import { Component, Input, ElementRef, AfterContentInit, OnInit, Injectable, OnDestroy } from '@angular/core';
import { IBOsService } from '../../../+ibos/ibos.service';
import { Subscription } from 'rxjs/Subscription';
import { Log, Level } from 'ng2-logger';
import { logConfig } from '../../../../environments/log_config';

const log = Log.create('DataTables');
log.color = logConfig.dataTable;

declare let $: any;

@Component({

    selector: 'sa-datatable',
    template: `
        <table class="dataTable {{tableClass}}" width="{{width}}">
            <ng-content></ng-content>
        </table>
    `,
    styles: [
        require('my-plugins/datatables-bundle/datatables.min.css')
    ]
})
@Injectable()
export class DatatableComponent implements OnInit, OnDestroy {

    @Input() public options: any;
    @Input() public filter: any;
    @Input() public detailsFormat: any;

    @Input() public paginationLength: boolean;
    @Input() public columnsHide: boolean;
    @Input() public tableClass: string;
    @Input() public width = '100%';

    public datatableInitialized: boolean;

    public subscription: Subscription;

    constructor(private el: ElementRef, private tablesService: IBOsService) {
        this.tablesService.refreshTable$.subscribe((tableParams) => {
                this.filterData(tableParams);
            }
        );

        this.tablesService.initTableData$.subscribe(() => {
                if (!this.datatableInitialized) {
                    log.info('Starting script import promise');

                    Promise.all([
                        System.import('script-loader!my-plugins/datatables-bundle/datatables.min.js')
                    ]).then(values => {
                        log.data('success', JSON.stringify(values));
                        this.render();
                    }, reason => {
                        log.error('error', JSON.stringify(reason));
                    });
                }
            }
        );
    }

    ngOnInit() {
    }

    render() {
        log.info('Starting render!');

        const element = $(this.el.nativeElement.children[0]);
        let options = this.options || {};

        log.info('1 render!');

        let toolbar = '';
        if (options.buttons) {
            toolbar += 'B';
        }
        log.info('2 render!');

        if (this.paginationLength) {
            toolbar += 'l';
        }

        if (this.columnsHide) {
            toolbar += 'C';
        }

        log.info('3 render!');

        if (typeof options.ajax === 'string') {
            const url = options.ajax;
            options.ajax = {
                url: url,
                // complete: function (xhr) {
                //
                // }
            };
        }

        log.info('4 render!');

        options = $.extend(options, {

            'dom': '<\'dt-toolbar\'<\'col-xs-12 col-sm-6\'f><\'col-sm-6 col-xs-12 hidden-xs text-right\'' + toolbar + '>r>' +
            't' +
            '<\'dt-toolbar-footer\'<\'col-sm-6 col-xs-12 hidden-xs\'i><\'col-xs-12 col-sm-6\'p>>',
            oLanguage: {
                'sSearch': `<span class='input-group-addon'><i class='glyphicon glyphicon-search'></i></span>`,
                'sLengthMenu': '_MENU_'
            },
            'autoWidth': false,
            retrieve: true,
            responsive: true,
            initComplete: (settings, json) => {
                element.parent()
                    .find('.input-sm')
                    .removeClass('input-sm')
                    .addClass('input-md');
            }
        });

        log.info('5 render! element', JSON.stringify(element));
        log.info('5.1 render! options', JSON.stringify(options));

        const _dataTable = element.DataTable(options);

        log.info('5.2 render! _dataTable', JSON.stringify(_dataTable));

        if (this.filter) {
            // Apply the filter
            element.on('keyup change', 'thead th input[type=text]', function () {
                console.log('searching?');
                _dataTable
                    .column($(this).parent().index() + ':visible')
                    .search(this.value)
                    .draw();
            });
        }

        log.info('6 render!');

        if (!toolbar) {
            element.parent().find('.dt-toolbar')
                .append(
                    '<div class="text-right">' +
                    '<img src="assets/img/logo.png" alt="SmartAdmin" style="width: 111px; margin-top: 3px; margin-right: 10px;">' +
                    '</div>'
                );
        }

        log.info('7 render!');

        if (this.detailsFormat) {
            const format = this.detailsFormat;
            element.on('click', 'td.details-control', function () {
                const tr = $(this).closest('tr');
                const row = _dataTable.row(tr);
                if (row.child.isShown()) {
                    row.child.hide();
                    tr.removeClass('shown');
                } else {
                    row.child(format(row.data())).show();
                    tr.addClass('shown');
                }
            });
        }

        log.info('8 render!');

        this.datatableInitialized = true;
    }

    filterData(tableParams) {
        console.log('reloading DT... With these parameters: ' + JSON.stringify(tableParams));

        const element = $(this.el.nativeElement.children[0]);
        const table = element.find('table.dataTable');

        log.data('current table element is: ', JSON.stringify(table));

        Object.keys(tableParams).forEach(function (key) {
            log.warn('current key: ', JSON.stringify(key));
            table.DataTable().column(`${key}:name`).visible(tableParams[key]);
        });

        table.DataTable().ajax.reload();
    }

    ngOnDestroy() {
        if (this.subscription) {
            this.subscription.unsubscribe();
        }
    }
}

你会发现我在组件中添加了日志,这帮助我理解我的组件出了哪些问题。

完整的控制台日志:

enter image description here

有趣的信息:

如果调用filterData(tableParams)方法... DataTable将无问题地呈现。

这告诉我两件事:

  1. DataTable()不是问题所在。
  2. 问题只发生在第一次呈现时,然后在更新时,我遇到没有问题。

非常抱歉我的冗长文字... 但我自己进行了一些示范,并找不到解决方案。

如果您需要澄清或更多详细信息,请告诉我。

提前致谢!

更新:

经过几天的调试,我已经找到了一个可能的错误来源:

enter image description here

问题在于snull

也许更有经验的DataTables用户或遇到此问题的人可以给我一个提示,发生了什么。

我会在这里进行调试... ;)

更新 2:

经过更多调试,我发现DataTables没有进行第一次Ajax调用

我正在使用服务器端DataTables,使用POST中的Ajax调用。

这是我的options对象的代码:

this.options = {
    dom: 'Bfrtip',
    processing: true,
    serverSide: true,
    pageLength: 20,
    searchDelay: 1200,
    ajax: {
        url: this.jsonApiService.buildURL('/test_getUsers.php', 'remote'),
        type: 'POST',
        data: function (d) {
            Object.assign(d, IBOsTable.params);
            log.data('DT options obj. New params are: ', JSON.stringify(IBOsTable.params));
            return d;
        }
    },
    columns: this.initColumns,
};

这个代码在初始化时没有进行 Ajax 调用,但在table.DataTable().ajax.reload();中进行了调用。

这告诉我代码不是错误或损坏的(在reload()中运行得很好)...

我仍然不完全理解为什么这个 DataTables 的初始化不起作用... 但我相信我已经足够接近了!


s 定义在哪里?我没有看到相关的代码。 - Bindrid
@Bindrid 你好!s 不是决定性的因素。我发现我的 DT 没有进行第一次 Ajax 调用,因此没有实际数据可供渲染,这就是错误发生的原因。问题在于找出为什么没有进行第一次 Ajax 调用。干杯!;) - SrAxi
1个回答

2
我终于找到了错误的源头!
如果你读了我的更新,你会发现我找出了我的 DataTables 没有进行第一次 Ajax 调用的原因,但是在 reload() 上却完美地工作了:
这是因为初始化时的 JavaScript 错误导致无法进行 Ajax 调用。提示:TypeError: Cannot read property 'nodeName' of null
与 DataTables 的 Allan 交谈 后,他给了我一个提示,错误可能是由于 thead 的列数不等于 tbody 的列数所造成的。
所以我检查了我的 .html 文件,发现我有一些空的 <thead><tfoot> 标签。我注释掉它们……然后就好了!

enter image description here

我已经反复检查了每种可能的解决方案:
  • 依赖项
  • 在Angular 2应用程序中使用jQuery库
  • 许多不同的实例化DataTable服务器端的方式
  • 在代码中更改方法:TypeScript、jQuery、Ng2等...
最终,我只需要清除我的DataTable加载到的div,以避免这个问题。
有趣的是:使用那个.html文件,我的应用程序工作了三四个月,没有问题,也没有与DataTables相关的问题,直到现在...

我在页脚也有一个空的<tr>,但是在我的情况下,当我给我的数据表添加dt-responsive类时,错误才出现。 - gerard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接