AdrienTorris / awesome-blazor

Resources for Blazor, a .NET web framework using C#/Razor and HTML that runs in the browser with WebAssembly.
Creative Commons Zero v1.0 Universal
8.8k stars 973 forks source link

TypeError: Cannot read property 'removeChild' of null in blazor clinet app on using Jquery datatables #466

Closed Nitin5acc closed 3 years ago

Nitin5acc commented 3 years ago

am using jquery datatables on a blazor client app. In a razor component, I have a form that collects user inputs and gets data from the server. I then fill the HTML table with incoming data and invoke Jquery DataTable on it. It works fine as long as the number of rows are the same but I get below exception when the number of rows is reduced -

 Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: Cannot read property 'removeChild' of null
      TypeError: Cannot read property 'removeChild' of null

Below is my razor component -

@page "/internalhealthreport"
@using BMI.SWV.Reporting.Shared;
@using Microsoft.Extensions.Configuration;
@inject HttpClient HttpClient
@inject IJSRuntime JSRuntime
@inject SpinnerService SpinnerService
@inject IConfiguration Configuration
@*@implements IDisposable*@

<body>
    <h1 class="mb-5">INTERNAL HEALTH REPORT</h1>
    <hr />
    <EditForm Model="@this.HealthReportArgs" OnValidSubmit="@HandleValidSubmit" style="width:fit-content">
        <div class="container">
            <DataAnnotationsValidator />
            <ValidationSummary />
            <div class="row">
                <div class="col-sm">
                    <div class="form-group">
                        <label for="txtAccountNo">Account Number</label>
                        <input type="text" class="form-control" name="accountNumber" placeholder="923935" @bind-value="this.HealthReportArgs.accountNumber">
                    </div>
                </div>
                <div class="col-sm">
                    <div class="form-group">
                        <label for="txtIpiBaseNumber">IPI Base Number</label>
                        <input type="text" class="form-control" name="ipiBaseNumber" placeholder="I-002184856-4" @bind-value="this.HealthReportArgs.ipiBaseNumber">
                    </div>
                </div>
                <div class="col-sm">
                    <div class="form-group">
                        <label for="txtIpiNameNumber">IPI Name Number</label>
                        <input type="text" class="form-control" name="ipiNameNumber" placeholder="00454808047" @bind-value="this.HealthReportArgs.ipiNameNumber">
                    </div>
                </div>
                <div class="col-sm">
                    <div class="form-group">
                        <label for="txtTaxId">Tax Id</label>
                        <input type="text" class="form-control" name="taxIdKey" placeholder="211700435" @bind-value="this.HealthReportArgs.taxIdKey">
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-md-12 text-center">
                    <div class="form-group">
                        <input type="submit" class="btn btn-primary btn-lg btn-block" value="Search">
                    </div>
                </div>
            </div>
        </div>
    </EditForm>
    <hr />
    @if (this.HealthReportRows.Count() > 0)
     {
        <div class="row">
            <div class="results-table" style="overflow-x: auto;">
                <table id="resultsTable" class="table table-bordered table-striped table-sm" style="font-size:smaller;white-space:nowrap">
                    <thead class="thead-dark">

                        <tr>
                            <th>Name</th>
                            <th>Title Name</th>
                            <th>Account #</th>
                            <th>AKA Seq #</th>
                            <th>IPI Base #</th>
                            <th>IPI Name #</th>
                            <th>Title #</th>
                            <th>Title ID</th>
                            <th>Sender #</th>
                            <th>Sender Name</th>
                            <th>ISWC</th>
                            <th>SWV Status</th>
                            <th>SWV Status Reason</th>
                            <th>P/W Ind</th>
                            <th>Pay Mode</th>
                            <th>Share</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach (var healthReportRow in this.HealthReportRows)
                        {
                            <tr>
                                <td>@healthReportRow.Name</td>
                                <td>@healthReportRow.TitleName</td>
                                <td>@healthReportRow.AccountNumber</td>
                                <td>@healthReportRow.AKASeqNumber</td>
                                <td>@healthReportRow.IPIBaseNumber</td>
                                <td>@healthReportRow.IPINameNumber</td>
                                <td>@healthReportRow.TitleNumber</td>
                                <td>@healthReportRow.TitleID</td>
                                <td>@healthReportRow.SenderNo</td>
                                <td>@healthReportRow.OriginalSubmitterName</td>
                                <td>@healthReportRow.ISWC</td>
                                <td>@healthReportRow.SongViewStatus</td>
                                <td>@healthReportRow.SongViewReason</td>
                                <td>@healthReportRow.PublisherWriterIndicator</td>
                                <td>@healthReportRow.PayMode</td>
                                <td>@healthReportRow.Share</td>
                            </tr>
                         }
                    </tbody>
                </table>
            </div>
        </div>
     }
</body>

@code {

    public HealthReportArgs HealthReportArgs { get; set; } = new HealthReportArgs() { };

    public HealthReportRow[] HealthReportRows { get; set; } = new HealthReportRow[] { };

    public async void HandleValidSubmit()
    {
        SpinnerService.Show();

        var settings = new Settings(Configuration);
        if (string.IsNullOrWhiteSpace(settings.HealthReportApiUrl))
        {
            throw new Exception($"Configuration value missing: {nameof(settings.HealthReportApiUrl)}");
        }

        this.HealthReportRows = await HttpClient.GetReportRows<HealthReportRow>(apiUrl: settings.HealthReportApiUrl, args: this.HealthReportArgs);

        await JSRuntime.InvokeAsync<object>("TestDataTablesRemove", "#resultsTable");
        await JSRuntime.InvokeAsync<object>("InitDataTable", "#resultsTable");
        this.StateHasChanged();

        SpinnerService.Hide();
    }
}

And here are the scripts in index.html -

function InitDataTable(table) {
            $(document).ready(function () {
                if (!$.fn.DataTable.isDataTable('#resultsTable')) {
                    $(table).DataTable({
                        dom: 'Bfrtip',
                        buttons: [
                            'copy', 'csv', 'excel', 'pdf', 'print'
                        ]
                    });
                }
            });
        }

        function TestDataTablesRemove(table) {
            $(document).ready(function () {
                if ($.fn.DataTable.isDataTable('#resultsTable')) {
                    $('#resultsTable').DataTable().destroy(true);                 
                }
            });
        }
smallprogram commented 3 years ago
@code {

    public HealthReportArgs HealthReportArgs { get; set; } = new HealthReportArgs() { };

    public HealthReportRow[] HealthReportRows { get; set; } = new HealthReportRow[] { };

    public async void HandleValidSubmit()
    {
        SpinnerService.Show();

        var settings = new Settings(Configuration);
        if (string.IsNullOrWhiteSpace(settings.HealthReportApiUrl))
        {
            throw new Exception($"Configuration value missing: {nameof(settings.HealthReportApiUrl)}");
        }

        this.HealthReportRows = await HttpClient.GetReportRows<HealthReportRow>(apiUrl: settings.HealthReportApiUrl, args: this.HealthReportArgs);

        await JSRuntime.InvokeAsync<object>("TestDataTablesRemove", "#resultsTable");

        this.StateHasChanged();  // ⬅

        await JSRuntime.InvokeAsync<object>("InitDataTable", "#resultsTable");

        SpinnerService.Hide();
    }
}
Nitin5acc commented 3 years ago

@smallprogram Can you please tell me more about what do you think is wrong with the line? If I remove that How do I render after the button has been clicked

smallprogram commented 3 years ago

@smallprogram Can you please tell me more about what do you think is wrong with the line? If I remove that How do I render after the button has been clicked

First, call the component life cycle

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {

            await JSRuntime.InvokeAsync<object>("InitDataTable", "#resultsTable");
        }
    }

Later, when the data set changes (increase, decrease, modify), first use JS to delete the table datatables instance, then change the data set, and then instantiate a new datatables instance.

    public async Task HandleValidSubmit()
    {
        // Some animations, behavior determination, etc.

        await JSRuntime.InvokeAsync<object>("TestDataTablesRemove", "#resultsTable");
        // Data set changes

        this.StateHasChanged();
        await JSRuntime.InvokeAsync<object>("InitDataTable", "#resultsTable");

        // Some animations, behavior determination, etc.
    }
sapr9x3 commented 3 years ago

Is it really on-topic for "awesome-blazor" repository?

smallprogram commented 3 years ago

Is it really on-topic for "awesome-blazor" repository?

It has nothing to do with "awesome-blazor" repository, I just help him

Nitin5acc commented 3 years ago

@smallprogram When I try your suggestion the data table is not displayed at all when the data set has changed after button click. What might be wrong ? ( There is no exception though and it loads nicely first time)

Nitin5acc commented 3 years ago

Okay, I had to fix one line of Jquery to get this working- $('#resultsTable').DataTable().destroy();