Update JS datatable in Blazor - datatables

I am using the Datatables.net library (https://datatables.net) in my Blazor app by following the suggested solution here (Using datatables.net with server-side Blazor application).
This all works well for the initial load, however, when I want to update my data and essentially reload/redraw the datatable, I can not for the life of me get it to re-render correctly and the datatable goes empty and says there is no data.
Essentially I am wanting to update the following and have the JS datatables redraw:
<tbody>
#foreach (var administrator in Administrators)
{
<tr>
<td>#administrator.Id</td>
<td>#administrator.LastName</td>
<td>#administrator.AccessLevel.GetDisplayName()</td>
<td>#administrator.DateCreated.ToShortDateString()</td>
</tr>
}
</tbody>
I do this via a button click (server side) that runs code to go to the database and update the list of Administrators. Sometimes it seems that the JS interop code to create the table runs before the actual HTML is updated by the change of the Administrators list even when I call `StateHasChanged()``` at every turn.
I have tried updating my JS function to .clear() and .destory() the datatable before recreating it, I have just tried to only create it once and call invalidate() and .draw() but nothing seemed to make a difference.
I have searched through the net looking for a way to achieve this but all the solutions seem to only cater for the initial render and do not talk about modifying/updating the data. The closest I could find was not to use the HTML/DOM to generate the data for the datatables library but send a JSON object back and rebuild it but I feel that defeats the purpose of using Blazor and means if I want to have any customised html display (e.g. a link) I need to really start to modify the JS code.

Related

In a regular MVC view table, rendering more than a few Blazor components triggers a disconnect from the server

In a .NET Core 3.1 MVC application, we use blazor components to for creating and editing records in a table.
The table is built in a CSHTML view, and the rows are supplied by foreaching through a list.
Each row has one 'edit' button, which is actually a blazor component set to 'prerender', and which takes in the record as a parameter.
When the view loads, some 500+ records are added to the table, and each record gets one instance of the component. We didn't expect very good performance out of it, but appereantly after around 70 rows are added to the table, the blazor server sends a disconnect signal to the client - the reason for which I have not yet discovered - and I'm guessing that somewhere a limit is reached.
We know there is no exception happening in the blazor component itself, because the same behavior can be reproduced when you use a component that has just a simple paragraph tag with text.
Some solutions we've come up with are:
Moving the whole table to a blazor component, since we've built pure blazor apps that never have had this issue. But this comes at a downside because, that would mean we need to manually do the JS stuff and manage the table instantiation ourselves. This is not an option.
Using the RenderTreeBuilder API to use 1 instance of the EditModalComponent, and manually build the component when it's necessary. But so far, I haven't been able to output the actual component, just the namespace. This still seems like it would be a best-of-both-worlds situation, where I can keep the number of rendered components to 1 (instead of 500). This would be my bet.
I'm about to open a question on using the RenderTreeBuilder in this particular case, but I'm wondering whether anyone can explain to me, why below solution will trigger a disconnect after a certain number of components? Am I overloading the SignalR connection?
<!-- Table implements Datatables.js -->
<table id="table" class="table hover responsive row-border" width="100%">
<thead>
<tr>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var record in Model.Records) // Count would be around 500 records.
{
<tr>
<td>#record.FullName</td>
<td><component type="typeof(EditModalComponent)" render-mode="ServerPrerendered" param-Record="#record" /></td>
</tr>
}
</tbody>
</table>

Blazor Server, Conditional Elements + JS Invoking

I have a Blazor Server app that is a multi-step "wizard" form. After each relevant step the state is adjusted, and new HTML is shown/hidden via conditional statements (simple example below).
if (IsStepSignature)
{
<div>Signature HTML here</div>
}
This all works just fine. My problem comes when I need to invoke some JS logic on the dynamically generated HTML from above (e.g. click handlers to hook up external JS libraries). When I handle the "Next" click, I can invoke the JS just fine...but it is not yet seeing the dynamic HTML from above. Is there a way to invoke some JS, and control it so that it doesn't execute until after the page is redrawn from the C# code execution?
5/18/2020 Update from Nik P
Can leverage some flags and use OnAfterRenderAsync to control this ordering. This does work, but it does require some extra hops to/from the server. Below is what I see when implementing this. This may just be the nature of Blazor Server, as one of the pros/cons is some known added chattiness. In total these requests were 2.5K, so extremely small.
CLIENT --> Browser Dispatch Event (NEXT CLICK)
Render <-- SERVER
CLIENT --> Render Complete
Invoke JS <-- SERVER
CLIENT --> Invoke Complete
The issue you are having has to do with the element not existing in the client side HTML at all until after the re-render takes place. So one way to do this is to set a boolean flag in your C# code that says there is code that needs to be run after the render, and populate support fields that you will need for your JS Interop. Whenever you need to run the JS interop, set your flag to true, set your support fields to the values you need for the JS interop call, and then do something that kicks a DOM diff calculation. (Even StateHasChanged should be enough, but adding your items conditionally as you mentioned will also do it) Then, override your OnAfterRenderAsync method as follows:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender)
{
// any first render code
}
if(yourFlag)
{
YourJSInteropMethod(supportField1, supportfield2);
yourflag = false;
}
}
The simplicity in this approach is that the DOM update will always happen ahead of the OnAfterRenderAsync call, so your HTML will be populated with what you are targeting with JS.

How to insert vue component into contenteditable div

I want to create simple wysiwyg editor with vue. I have found only one real wysiwyg editor created on vue.js. Here it is __https://quasar.dev/vue-components/editor (But I didn't find there ability to insert image). Others vue wysiwyg editors is only wrappers to tinymce or ckeditor etc.
I am planning to create SIMPLE vue-editor using base commands from example from mozilla developer site. Here it is https://codepen.io/chrisdavidmills/pen/gzYjag .
At first I want to know how to insert vue components into contenteditable div. What I mean? For example, I want to create editor plugin which will insert image on toolbar icon click. Inserted image should be with attached events (click event), it could be resizable etc. The base idea to this I found in answer here https://stackoverflow.com/a/59326255/1581741 , (author #AJT82 ). He gave me example of inserting vue-component (image) into contenteditable div. Here it is https://codesandbox.io/s/vue-template-m1ypk .
So, I have next questions.
User open empty editor and insert component into it. I need something to store to database.
1) What exactly should I store to database?
2) Stored text with images should be rendered somewhere at the site as article for viewing only. This means I should have generated html (for example, <img src="" />). How I will take it from inserted vue-component?
3) User can edit stored earlier code from editor. How to insert (inserted and stored to database earlier) vue-component again?
I am lost in this questions.
I made you an example showing how you can inject absolutely anything into any WYSIWYG component (except for really bad ones :)
Using your first choice of WYSIWYG editor, and probably the slickest of them all...
https://quasar.dev/vue-components/editor
Here you can see how easy it is to inject a random cat image for example. You could pop up a dialog and ask for an image name, you could allow the user to upload an image, wait for a promise that returns the link, then insert that image via the returned link, or do even wilder things.
https://codepen.io/njsteele/pen/wvBNYJY
The component render is handled here:
<!-- Here is where we render the component and capture it's output... -->
<div ref="renderComponent" v-show="false">
<!-- Due to limitation of codepen, must not self-close -->
<component :is="renderThisComponent" v-bind="renderTheseProps">
</component>
</div>
Clicking "Insert Random Cat" will insert a random animated cat GIF from Cats As A Service.
Clicking Insert Quasar Components will let you select from a q-icon component, and an animated indeterminate progress circle. You can also add your own components. This works with absolutely any Vue component, but it will not update it once it's been rendered in your WYSIWYG editor, and it's plain HTML after that. I also used a ref render, which works but it's basic, to show you how easy it is to accomplish. I would instead upgrade this to a proxy component so it never has to get rendered into the DOM the first time or wait for a $nextTick.
You can also see you can inject tokens (although this came from the Quasar playground). It shows how you can inject vars you might have related to the current user/etc.
If you want, you can also allow users to build their own components, or allow user-created templates to be injected, you can also insert a Emoji list, or even #mentions, which can insert live-views into if that user is currently online, etc.
Since this functionality is both really powerful and easy to implement it in theory, I wrote a really ultra-tiny and bug-free template generator that you can extend to infinity (372 bytes). It's also safe for users, as it only uses function lists you pre-allow for your users.
Grab the source here and use it in your projects if you would like...
https://github.com/onexdata/nano-var-template

Binding some Vue code to existing, server-side generated HTML

I have an old-style, multiple-page website, with a multiple steps checkout process. This all works with JS disabled, and it is critical that it keeps doing so.
The checkout form has no JS at all, at the moment, and I'd like to improve it progressively (eg. dynamically showing or hiding fields, doing live validation, etc...).
I have already wrapped the entire website with an #app div, and I mounted a Vue instance to it. Then I created a few components which work correctly (but are not critical, so if JS is disabled then the whole thing keeps working and the components are just empty).
Now I have a long checkout form which is generated server-side (say: <form id='address-form'>).
The best course would be to put it into a component (say <checkout-form>) and use it. I can't do this, because 1) the form is generated server-side 2) it needs to work without JS.
Ideally, I would love to create a component with no template, and attach it to the existing HTML.
Is this even possible?
Edit: continuing to dig the Internet, I found this tutorial. This is exactly my problem, but if this is the only way to do it, then I will revert to JQuery :) Manually duplicating the entire HTML (one server side, the other in Vue) is definitely not a good idea.

Server Side Rendering Vue with ASP.NET Core 2

I'm trying to understand the usage and limitations of server side rendering with vuejs when using aspnet core.
I used this starter kit for aspnet core and vuejs to setup a simple vue site, which is running based on the code here: https://github.com/selaromdotnet/aspnet-vue-ssr-test/tree/master
I then modified the project to update the aspnet-prerendering and added vue-server-renderer, compiling a hodgepodge of sources to cobble together this update: https://github.com/selaromdotnet/aspnet-vue-ssr-test/tree/ssr
If I run this project, the site appears to load fine, and if I turn off the javascript in the browser, I can see that it does appear that the server-side rendering executed and populated the html result:
however, because JavaScript is disabled, the content isn't moved into the dom as it looks like it is trying to...
My understanding of server-side rendering is that it would populate the html entirely and serve a completed page to the user, so that even if JS was disabled, they'd at least be able to see the page (specifically for SEO purposes). Am I incorrect?
Now I believe modern search engines will execute simple scripts like this to get the content, but I still don't want a blank page rendered if js is disabled...
Is this a limitation of server-side rendering, or perhaps specifically ssr with vue and/or aspnet core?
or am I just missing a step somewhere?
Edit: more information
I looked at the source code for what I believe is the method that prerenders the section here: https://github.com/aspnet/JavaScriptServices/blob/dev/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs
The line
output.Content.SetHtmlContent(result.Html);
has a null value for result.Html. However, when I manually edit this value to put a test value, it also doesn't render to the output html, and the app div tag is still empty...
If I'm doing something wrong to populate the result.Html value with the expected output, that's one thing, and I would appreciate some help in doing that, especially since the output html appears to be found, since it's in the script that immediately follows...
However, even if I were to populate it, it appears it's being skipped, as evidenced by me manually changing the value. is this a bug in the code or am I doing somethigng wrong, or perhaps both?
As you correctly noticed, for your project, result.Html inside the tag helper is null. So that line cannot be the location where the output is being generated. Since the HTML output from your prerendering script also does not include a script tag, it is clear that something has to generate that. The only other line that could possible do this is the following from the PrerenderTagHelper:
output.PostElement.SetHtmlContent($"<script>{globalsScript}</script>");
That would fit the observed output, so we should figure out where the globalsScript comes from.
If you look at the PrerenderTagHelper implementation, you can see that it will call Prerenderer.RenderToString which returns a RenderToStringResult. This result object is deserialized from JSON after calling your Node script.
So there are two properties of interest here: Html, and Globals. The former is responsible for containing the HTML output that finally gets rendered inside the tag helper. The latter is a JSON object containing additional global variables that should be set for the client side. These are what will be rendered inside that script tag.
If you look at the rendered HTML from your project, you can see that there are two globals: window.html and window.__INITIAL_STATE__. So these two are set somewhere in your code, although html shouldn’t be a global.
The culprit is the renderOnServer.js file:
vue_renderer.renderToString(context, (err, _html) => {
if (err) { reject(err.message) }
resolve({
globals: {
html: _html,
__INITIAL_STATE__: context.state
}
})
})
As you can see, this will resolve the result containing just a globals object with both html and __INITIAL_STATE__ properties. That’s what gets rendered inside of the script tag.
But what you want to do instead is have html not as part of globals but on the layer above, so that it gets deserialized into the RenderToStringResult.Html property:
resolve({
html: _html,
globals: {
__INITIAL_STATE__: context.state
}
})
If you do it like that, your project will properly perform server-side rendering, without requiring JavaScript for the initial view.