DataTables with Blazor

DataTables with Blazor

robigitrobigit Posts: 1Questions: 0Answers: 0
edited May 20 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: 5,846Questions: 0Answers: 1,010

    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: 9Questions: 0Answers: 0
    edited August 1

    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: 50,248Questions: 1Answers: 7,465 Site admin

    Nice one - thanks for posting this!

    Allan

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

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

  • tomstoms Posts: 9Questions: 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: 9Questions: 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: 5,846Questions: 0Answers: 1,010

    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 9

    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: 9Questions: 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: 9Questions: 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: 9Questions: 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: 9Questions: 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: 9Questions: 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

Sign In or Register to comment.