DataTables with Blazor

DataTables with Blazor

robigitrobigit Posts: 1Questions: 0Answers: 0
edited May 2019 in Free community support

Hi, I'm experimenting with Blazor Single Page Application and I wanted DataTables through JavaScript InterOp.
The App is still the tutorial one, with only a page changed

On my index.htm I put the following function that I call from my razor page Ruoli

function CaricaDataTables(table)
{
$(document).ready(function () {
$(table).DataTable();
});
}

It works perfectly

Except for the fact that when I change page, the filter and the footer remain on the page

Do you have an suggestion on where I'm mistaking ?

Thanks in advace

Replies

  • colincolin Posts: 15,240Questions: 1Answers: 2,599

    Hi @robigit ,

    DataTables adds control elements to the DOM around the table - which is what is being left behind. If there are triggers or events for the page change, you could destroy() which would remove those elements.

    Hope that helps,

    Cheers,

    Colin

  • tomstoms Posts: 10Questions: 0Answers: 0
    edited August 2019

    Just in case there is someone else out there like me who found themselves here because they are trying out Blazor, but need the answer to be shown explicitly.

    Here are all the pieces and the places they need to go in the basic client side app for .Net Core 3.0.0-preview6. In case it's not obvious don't copy the whole code verbatim, the part that is in head needs to be copied into head, etc. I'm just showing the pieces that get the data table into the page and then remove it. As per the suggestion above.

    In wwwroot/index.html:

    <head>
    
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet" />
        <script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
    </head>
    <body>
    
        <script>
            function TestDataTablesAdd(table) {
                $(document).ready(function () {
                    $(table).DataTable();
                });
            }
        </script>
    
        <script>
            function TestDataTablesRemove(table) {
                $(document).ready(function () {
                    $(table).DataTable().destroy();
                });
            }
        </script>
    </body>
    

    In your copy of FetchData.razor or SomeFile.razor you need:

    @inject IJSRuntime JSRuntime
    
    <body onbeforeunload="TestDataTablesRemove('#example')">
    
            <table id="example" class="display" style="width:100%">
    
            </table>
    </body>
    
    @code {
    
        protected override async Task OnInitAsync()
        {
        //Leave the code in that populates the table. I removed it so that I only show the needed pieces
        //This setups up the basic datatables.net table and the onbeforeunload removes it
            await JSRuntime.InvokeAsync<object>("TestDataTablesAdd", "#example");
        }
    
    }
    
  • allanallan Posts: 63,530Questions: 1Answers: 10,473 Site admin

    Nice one - thanks for posting this!

    Allan

  • dlasalde@fbchomeloans.comdlasalde@fbchomeloans.com Posts: 2Questions: 0Answers: 0
    edited August 2019

    This example is no longer working for dotnet core 3.0.100-preview8-013656

  • tomstoms Posts: 10Questions: 0Answers: 0

    Yeah, it looks like the call to destroy() is no longer needed, so onbeforeunload isn't needed either. There still needs to be a body tag around the HTML that contains the table. Also Microsoft renamed OnInitAsync to OnInitializedAsync.

  • tomstoms Posts: 10Questions: 0Answers: 0

    Here is the complete code for the two files using the “Blazor WebAssembly App” template in .NET Core 3.0 Preview 8.

    wwwroot/index.html:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width" />
        <title>BlazorClientApp1</title>
        <base href="/" />
        <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
        <link href="css/site.css" rel="stylesheet" />
    
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet" />
        <script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
    
    </head>
    <body>
        <app>Loading...</app>
    
        <script src="_framework/blazor.webassembly.js"></script>
    
        <script>
            function TestDataTablesAdd(table) {
                $(document).ready(function () {
                    $(table).DataTable();
                });
            }
        </script>
    
    </body>
    </html>
    

    FetchData.razor:

    @page "/fetchdata"
    @inject HttpClient Http
    @inject IJSRuntime JSRuntime
    
    <h1>Weather forecast</h1>
    
    <p>This component demonstrates fetching data from the server.</p>
    
    <body>
        @if (forecasts == null)
        {
            <p><em>Loading...</em></p>
        }
        else
        {
            <table id="example" class="display" style="width:100%">
                <thead>
                    <tr>
                        <th>Date</th>
                        <th>Temp. (C)</th>
                        <th>Temp. (F)</th>
                        <th>Summary</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (var forecast in forecasts)
                    {
                        <tr>
                            <td>@forecast.Date.ToShortDateString()</td>
                            <td>@forecast.TemperatureC</td>
                            <td>@forecast.TemperatureF</td>
                            <td>@forecast.Summary</td>
                        </tr>
                    }
                </tbody>
            </table>
        }
    </body>
    
    @code {
        WeatherForecast[] forecasts;
    
        protected override async Task OnInitializedAsync()
        {
            forecasts = await Http.GetJsonAsync<WeatherForecast[]>("sample-data/weather.json");
            await JSRuntime.InvokeAsync<object>("TestDataTablesAdd", "#example");
        }
    
        public class WeatherForecast
        {
            public DateTime Date { get; set; }
    
            public int TemperatureC { get; set; }
    
            public string Summary { get; set; }
    
            public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
        }
    }
    
    
  • colincolin Posts: 15,240Questions: 1Answers: 2,599

    Hi @toms ,

    Thanks for sharing that code, that's helpful.

    Cheers,

    Colin

  • DrGriffDrGriff Posts: 3Questions: 0Answers: 0

    @toms

    Just downloaded latest version of Blazor and tried to follow your example above.

    When using JSruntime.InvokeAsync (same syntax as you had) then I get the error that it cannot convert a string to an object[] (this is for "#example").

  • dlasalde@fbchomeloans.comdlasalde@fbchomeloans.com Posts: 2Questions: 0Answers: 0
    edited September 2019

    Hi @toms

    I'm using dotnet core 3.0.100-preview8-013656 I'm trying to get it to work with the "Blazor Server App" version which just has the "_Host.cshtml" razor page and it doesn't have the "index.html" but doesn't work.

  • DrGriffDrGriff Posts: 3Questions: 0Answers: 0

    With reference to my above post, by "latest" I'm referring to .NET Core 3.0 preview 9 with Blazor preview 9.

  • tomstoms Posts: 10Questions: 0Answers: 0

    Hi @dlasalde@fbchomeloans.com

    You can see I was careful to say that my example was a Client Side app, which is simpler because all your code is running in the web browser. As I'm sure you know, Blazor Server Side is a bit more tricky since the C# code is running on the Web Server, but the JavaScript still needs to the be handled at the client. The reason it looks like it doesn't work is because if you use the same code in the server side app, but just put the index.html pieces into _Host.cshtml then what you get is actually more of a dice roll. Try clicking back and forth between the counter page and the FetchData page several times. If your computer system is like mine then eventually you should see it work at least once.

    The issue is in OnInitializedAsync().

    What I think is happening is the call to get the data is run at the server which is all fine and good.

            forecasts = await Http.GetJsonAsync<WeatherForecast[]>("sample-data/weather.json");
    

    Then the server is sending the JS call to the client and at the same time in the background sending over the data it received from the previous call. This creates a race condition. If the data gets to the browser first then the table is created and your data table will get the format applied. However if the JS call gets there first then the data table doesn't exist in the browser. Somewhere in the background I'm sure it is spitting out an object not found error and of course the format doesn't get applied.

    I don't know of a good way to get around this. The only spot I've found so far is to move the JS call into the OnAfterRenderAsync function, but I'm not sure if that is a good idea. The OnAfterRenderAsync function gets called multiple times. I assume anytime the table is rendered.

    At any rate if you want to try it put the code that I added to index.html into _Host.cshtml and then add the following to the FetchData.razor:

        protected override async Task OnAfterRenderAsync()
        {
            //This if statement is supposed to only be required until Microsoft fixes the timing of the call to OnAfterRender. Supposedly this will happen for the release version.
            if (!ComponentContext.IsConnected)
            {
                return;
            }
            await JSRuntime.InvokeAsync<object>("TestDataTablesAdd", "#example");
        }
    
  • tomstoms Posts: 10Questions: 0Answers: 0

    Here is the complete code for the two files using the “Blazor Server App” template in .NET Core 3.0 Preview 8. Also for this one I downloaded JQuery and Datatables.net instead of using the CDN. Also I downloaded Blazor.Polyfill and put it in a folder I created called wwwroot\Git, so I could enable my app to work with IE11.

    You can get the JQuery and Datatables.net by right clicking on your project in VS and choosing "Add" -> "Client-Side Library...", then type in jquery or datatables.net and select them from the list.

    You can download Blazor.Polyfill from here:
    https://github.com/Daddoon/Blazor.Polyfill

    _Host.cshtml :

    @page "/"
    @namespace BlazorServerSide1.Pages
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>BlazorServerSide1</title>
        <base href="~/" />
        <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
        <link href="css/site.css" rel="stylesheet" />
    
        <script type="text/javascript" src="~/lib/jquery/jquery.min.js"></script>
    
        <link href="/lib/datatables/css/jquery.dataTables.min.css" rel="stylesheet" />
        <script src="/lib/datatables/js/jquery.dataTables.min.js"></script>
    
    </head>
    <body>
        <app>
            @* Remove the following line of code to disable prerendering *@
            @(await Html.RenderStaticComponentAsync<App>())
        </app>
        @*This next line enables support for IE11 and requires Blazor.Polyfill for GitHub*@ 
        <script type="text/javascript" src="~/Git/Blazor.Polyfill/blazor.polyfill.min.js"></script>
        <script src="_framework/blazor.server.js"></script>
    
        <script>
            function JSHello() {
                alert("Hello\nHow are you?");
            }
        </script>
    
        <script>
            function TestJQuery() {
                if (window.jQuery) {
                    // jQuery is loaded
                    alert("JQuery is working!");
                } else {
                    // jQuery is not loaded
                    alert("JQuery Doesn't Work");
                }
            }
        </script>
        <script>
            function TestDataTablesAdd(table) {
                $(document).ready(function () {
                    $(table).DataTable();
                });
            }
        </script>
    
        <script>
            function TestDataTablesRemove(table) {
                $(document).ready(function () {
                    $(table).DataTable().destroy();
                });
            }
        </script>
    
    </body>
    </html>
    
    

    FetchData.razor:

    @page "/fetchdata"
    @*Rename BlazorServerSide1 to whatever your app name is*@
    @using BlazorServerSide1.Data
    @inject WeatherForecastService ForecastService
    @inject IJSRuntime JSRuntime
    @inject IComponentContext ComponentContext
    
    
    <h1>Weather forecast</h1>
    
    <p>This component demonstrates fetching data from a service.</p>
    
    <body>
        <p>
            <button class="btn btn-primary" @onclick="CallHello">Call JSHello</button>&nbsp&nbsp
            <button class="btn btn-primary" @onclick="CallTestJQuery">Test JQuery</button>
        </p>
        <br />
        @*<p>
                <button class="btn btn-primary" @onclick="CallAddTable">Add Table</button>&nbsp&nbsp
                <button class="btn btn-primary" @onclick="CallRemoveTable">Remove Table</button>
            </p>
            <br />*@
    
        @if (forecasts == null)
        {
            <p><em>Loading...</em></p>
        }
        else
        {
            <table id="example" class="display" style="width:100%">
                <thead>
                    <tr>
                        <th>Date</th>
                        <th>Temp. (C)</th>
                        <th>Temp. (F)</th>
                        <th>Summary</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (var forecast in forecasts)
                    {
                        <tr>
                            <td>@forecast.Date.ToShortDateString()</td>
                            <td>@forecast.TemperatureC</td>
                            <td>@forecast.TemperatureF</td>
                            <td>@forecast.Summary</td>
                        </tr>
                    }
                </tbody>
            </table>
        }
    </body>
    @code {
        WeatherForecast[] forecasts;
    
        protected override async Task OnInitializedAsync()
        {
            forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    
            //This is a dice roll that seems to get more likely the more times you click between this page and the counter page
            //JSRuntime.InvokeAsync<object>("TestDataTablesAdd", "#example");
        }
    
        protected override async Task OnAfterRenderAsync()
        {
            //This if statement is supposed to only be required until Microsoft fixes the timing of the call to OnAfterRender. Supposedly in the release version.
            if (!ComponentContext.IsConnected)
            {
                return;
            }
            await JSRuntime.InvokeAsync<object>("TestDataTablesAdd", "#example");
    
            //try
            //{
            //    await JSRuntime.InvokeAsync<object>("TestDataTablesAdd", "#example");
            //}
            //catch { }
        }
    
        protected void CallHello()
        {
            JSRuntime.InvokeAsync<bool>("JSHello", "");
    
            //Doesn't work with server side app because JS calls need to be async
            //((IJSInProcessRuntime)JSRuntime).Invoke<object>("JSHello", "");
        }
        protected void CallTestJQuery()
        {
            JSRuntime.InvokeAsync<bool>("TestJQuery", "");
        }
        protected void CallAddTable()
        {
            JSRuntime.InvokeAsync<bool>("TestDataTablesAdd", "#example");
        }
        protected void CallRemoveTable()
        {
            //Doesn't work with the JS call in  OnAfterRenderAsync because OnAfterRenderAsync gets called again and re-formats the table again 
            JSRuntime.InvokeAsync<bool>("TestDataTablesRemove", "#example");
        }
    }
    
    
  • tomstoms Posts: 10Questions: 0Answers: 0

    @DrGriff

    I haven't downloaded Preview 9 yet, so I'm not sure what they "polished" to make it not work. Keep in mind the release date for the non-preview is in two weeks, so they may "polish" it again.

    I would look here:

    https://devblogs.microsoft.com/aspnet/asp-net-core-and-blazor-updates-in-net-core-3-0-preview-9/

    or here:

    https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interop?view=aspnetcore-3.0

    Basically what you need to do is make sure that JQuery works, make sure that the table is created at the client and then make the JS call to apply the table format. I've included in my code above some examples for testing JQuery and JavaScript. If you can find examples for those for preview 9 then you should be able to get it working. I've also included some manual buttons that I've commented out, but they show you how to manually make the calls to apply the Datatables.net format.

  • DrGriffDrGriff Posts: 3Questions: 0Answers: 0

    @toms
    Many thanks for the feedback and the code examples:I'll certainly try that again in a bit.

    Not sure if this next bit is contraversial, but one consequence of Blazor is that (in theory at least) it allows the Developer to write client-side code solely in C#, dropping reliance on JavaScript. Might this suggest that in future Blazor developers will eventually be looking for a "Blazor component" version of DataTables as opposed to the current JavaScript implementation?

  • tomstoms Posts: 10Questions: 0Answers: 0

    @DrGriff

    Well, I'm not affiliated with Datatables.net or Microsoft, but I think the point of Microsoft adding the JS Interop was so that you could choose to use either depending on which components you prefer. If you want to use a Blazor component there is no need to wait for the future there are free or paid for options out there. It just adds competition and variety.

    I still haven't upgraded to Preview 9, so this is just a shot in the dark based on your error, but maybe try changing the java call to this:

            await JSRuntime.InvokeAsync<object>("TestDataTablesAdd", new[] { "#example" });
    

    and change your JS script to :

        <script>
            function TestDataTablesAdd() {
                var table = arguments[0];
                $(document).ready(function () {
                    $(table).DataTable();
                });
            }
        </script>
    
  • tomstoms Posts: 10Questions: 0Answers: 0

    @DrGriff

    I finally updated to Preview 9 and tested by creating a new app using the Preview 9 client side template which they named “Blazor WebAssembly App”. It worked on my computer using the same code as I did in Preview 8, so I don't know what caused the error.

    Maybe try running the command to get the latest templates again. I know I had to run it a couple of times when I first updated to preview 8.

    From Console (with admin rights):
    dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.0.0-preview9.19424.4

  • rdunawayrdunaway Posts: 6Questions: 1Answers: 0

    I cannot use the body element to initiate the table removal because Blazor doesn't connect the <body> with </body> when there is C# code mixed in.

    I have implemented the dispose method which does fire when I leave the page. I have alert popups letting me know.

    The problem is that the controls are not removed. When I make the same JS interopt call from a button the controls remove nicely so I know the JS code works when called.

    Any ideas?

    @page "/fetchdata"
    @using bm2portal_blazor.Shared
    @inject HttpClient Http
    @inject IJSRuntime JSRuntime

    @implements IDisposable

    Weather forecast

    This component demonstrates fetching data from the server.

    <button class="btn btn-primary" @onclick="@CallRemoveTable">Remove Table</button>

    @if (forecasts == null)
    {

    Loading...

    }
    else
    {

    @foreach (var forecast in forecasts) { }
    Date Temp. (C) Temp. (F) Summary
    @forecast.Date.ToShortDateString() @forecast.TemperatureC @forecast.TemperatureF @forecast.Summary

    }

    @code {
    WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetJsonAsync<WeatherForecast[]>("WeatherForecast");
    
        // applies the datatable.net plugin to the table.
        await JSRuntime.InvokeAsync<string>("applyDataTable", new string[] { "#table_id" });
    
    }
    
    // This works when called from a button.
    protected void CallRemoveTable()
    {
        JSRuntime.InvokeAsync<bool>("TestDataTablesRemove", "#table_id");
    }
    
    // This executes before moving to the next page but doesn't remove the datatable.net controls.
    public void Dispose()
    {
        JSRuntime.InvokeAsync<bool>("TestDataTablesRemove", "#table_id");
    }
    

    }

  • rdunawayrdunaway Posts: 6Questions: 1Answers: 0

    Ok, here is what I'm doing to clean up the table wrapper that is left behind.

    I implement the Dispose override.

    // C# code in the component.
    public void Dispose()
    {
    JSRuntime.InvokeAsync<bool>("TestDataTablesRemove", "#table_id");
    }

    // JavaScript Function
    function TestDataTablesRemove(table) {
    $(document).ready(function () {
    $(table).DataTable().destroy();
    });

            // Removes the datatable wrapper from the dom.
            var elem = document.querySelector(table + '_wrapper');
            elem.parentNode.removeChild(elem);
    
        }
    
  • tomstoms Posts: 10Questions: 0Answers: 0

    Good call on the Dispose override. I knew there had to be a better way. Now that you've pointed it out it seems obvious.

  • zXSwordXzzXSwordXz Posts: 1Questions: 0Answers: 0

    I know this is an old thread but I want to ask a question. I'm running .net core 3.1 with Blazor Server release version and everything works great except for one thing. When I add/remove rows. The grid add the row at the top but doesn't reset, paging, and record count. Anyone one had the same issue and how did you resolve it? I trying to do
    $(table).DataTable().ajax.reload(); and $(table).DataTable().ajax.draw();
    but neither of them work.

  • JohnnyBGuuuudsJohnnyBGuuuuds Posts: 1Questions: 0Answers: 0

    Hey, old post but am kinda stuck at the wrappers left behind - I am a complete noob so forgive my question when i ask where do I add the Dispose override mentioned above?

  • mguinnessmguinness Posts: 85Questions: 12Answers: 1

    Since Blazor WebAssembly 3.2.0 now available and officially supported, I've created the fiddle below using the client-side example from Comment_157079 by @toms.

    https://blazorfiddle.com/s/dczu8r1f

  • colincolin Posts: 15,240Questions: 1Answers: 2,599

    Excellent, thanks for sharing,

    Colin

  • gerhardmeijgerhardmeij Posts: 2Questions: 0Answers: 0

    For a complete tutorial including Blazor source code, check out this post:

    https://gmtech.co.za/how-to-add-js-data-tables-to-your-blazor-project/

  • henonhenon Posts: 1Questions: 0Answers: 0

    Check out https://mudblazor.com it has a data table with pagination, sorting and filtering and is very easy to use.

  • dttbentdttbent Posts: 1Questions: 0Answers: 0

    Unfortunately I get an error message in the line await JSRuntime.InvokeAsync<object>("TestDataTablesAdd", "#example"); (from JsFiddle).

    Argument 2: cannot convert from 'string' to 'object[]'

    What can I do? I have already tried to pass an array char[]. Unfortunately without success.

    About help I would be very happy. Many thanks in advance.

  • anant_jaiswananant_jaiswan Posts: 1Questions: 0Answers: 0

    Hello guys

    Did anyone solve the issue?

    Because none of the above solutions worked for me.

    @onclick="(()=>EditData(model.Id))"
    onclick not working inside table while using jquery datatable

This discussion has been closed.