Skip to content

Commit 7237a60

Browse files
authored
Implement onAfterRenderTasksQueue for show/hide on HxModal analogous to HxOffcanvas (#1000)
1 parent 5671003 commit 7237a60

File tree

2 files changed

+57
-41
lines changed

2 files changed

+57
-41
lines changed

Havit.Blazor.Components.Web.Bootstrap/Modals/HxModal.razor.cs

+42-36
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,10 @@ static HxModal()
208208

209209

210210
private bool _opened = false; // indicates whether the modal is open
211-
private bool _shouldOpenModal = false; // indicates whether the modal is going to be opened
212211
private DotNetObjectReference<HxModal> _dotnetObjectReference;
213212
private ElementReference _modalElement;
214213
private IJSObjectReference _jsModule;
214+
private Queue<Func<Task>> _onAfterRenderTasksQueue = new();
215215
private bool _disposed;
216216

217217
public HxModal()
@@ -224,20 +224,52 @@ public HxModal()
224224
/// </summary>
225225
public Task ShowAsync()
226226
{
227+
if (!_opened)
228+
{
229+
_onAfterRenderTasksQueue.Enqueue(async () =>
230+
{
231+
// Running JS interop is postponed to OnAfterRenderAsync to ensure modalElement is set
232+
// and correct order of commands (Show/Hide) is preserved
233+
_jsModule ??= await JSRuntime.ImportHavitBlazorBootstrapModuleAsync(nameof(HxModal));
234+
if (_disposed)
235+
{
236+
return;
237+
}
238+
await _jsModule.InvokeVoidAsync("show", _modalElement, _dotnetObjectReference, CloseOnEscapeEffective, OnHiding.HasDelegate);
239+
});
240+
}
227241
_opened = true; // mark modal as opened
228-
_shouldOpenModal = true; // mark modal to be shown in OnAfterRender
229242

230-
StateHasChanged(); // ensures render modal HTML
243+
StateHasChanged(); // ensures rendering modal HTML
231244

232245
return Task.CompletedTask;
233246
}
234247

235248
/// <summary>
236249
/// Closes the modal.
237250
/// </summary>
238-
public async Task HideAsync()
251+
public Task HideAsync()
239252
{
240-
await _jsModule.InvokeVoidAsync("hide", _modalElement);
253+
if (!_opened)
254+
{
255+
// this might be a minor PERF benefit, if it turns out to be causing troubles, we can remove this or make it configurable through optional method parameter
256+
return Task.CompletedTask;
257+
}
258+
259+
_onAfterRenderTasksQueue.Enqueue(async () =>
260+
{
261+
// Running JS interop is postponed to OnAfterRenderAsync to ensure modalElement is set
262+
// and correct order of commands (Show/Hide) is preserved
263+
_jsModule ??= await JSRuntime.ImportHavitBlazorBootstrapModuleAsync(nameof(HxModal));
264+
if (_disposed)
265+
{
266+
return;
267+
}
268+
await _jsModule.InvokeVoidAsync("hide", _modalElement);
269+
});
270+
StateHasChanged(); // enforce rendering
271+
272+
return Task.CompletedTask;
241273
}
242274

243275
/// <summary>
@@ -268,26 +300,15 @@ public async Task HandleModalHidden()
268300
[JSInvokable("HxModal_HandleModalShown")]
269301
public async Task HandleModalShown()
270302
{
303+
_opened = true;
271304
await InvokeOnShownAsync();
272305
}
273306

274307
protected override async Task OnAfterRenderAsync(bool firstRender)
275308
{
276-
await base.OnAfterRenderAsync(firstRender);
277-
278-
if (_shouldOpenModal)
309+
while (_onAfterRenderTasksQueue.TryDequeue(out var task))
279310
{
280-
// do not run show in every render
281-
// the line must be prior to JSRuntime (because BuildRenderTree/OnAfterRender[Async] is called twice; in the bad order of lines the JSRuntime would be also called twice).
282-
_shouldOpenModal = false;
283-
284-
// Running JS interop is postponed to OnAfterAsync to ensure modalElement is set.
285-
_jsModule ??= await JSRuntime.ImportHavitBlazorBootstrapModuleAsync(nameof(HxModal));
286-
if (_disposed)
287-
{
288-
return;
289-
}
290-
await _jsModule.InvokeVoidAsync("show", _modalElement, _dotnetObjectReference, CloseOnEscapeEffective, OnHiding.HasDelegate);
311+
await task();
291312
}
292313
}
293314

@@ -363,26 +384,11 @@ protected virtual async ValueTask DisposeAsyncCore()
363384

364385
if (_jsModule != null)
365386
{
366-
// We need to remove backdrop when leaving "page" when HxModal is shown (opened).
367-
if (_opened)
368-
{
369-
try
370-
{
371-
await _jsModule.InvokeVoidAsync("dispose", _modalElement);
372-
}
373-
catch (JSDisconnectedException)
374-
{
375-
// NOOP
376-
}
377-
catch (TaskCanceledException)
378-
{
379-
// NOOP
380-
}
381-
}
382-
383387
try
384388
{
389+
await _jsModule.InvokeVoidAsync("dispose", _modalElement, _opened);
385390
await _jsModule.DisposeAsync();
391+
386392
}
387393
catch (JSDisconnectedException)
388394
{

Havit.Blazor.Components.Web.Bootstrap/wwwroot/HxModal.js

+15-5
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function hide(element) {
3131

3232
function handleModalShown(event) {
3333
event.target.hxModalDotnetObjectReference.invokeMethodAsync('HxModal_HandleModalShown');
34-
};
34+
}
3535

3636
async function handleModalHide(event) {
3737
const modalInstance = bootstrap.Modal.getInstance(event.target);
@@ -49,21 +49,21 @@ async function handleModalHide(event) {
4949
event.target.hxModalHiding = true;
5050
modalInstance.hide();
5151
}
52-
};
52+
}
5353

5454
function handleModalHidden(event) {
5555
event.target.hxModalHiding = false;
5656

5757
if (event.target.hxModalDisposing) {
5858
// fix for #110 where the dispose() gets called while the offcanvas is still in hiding-transition
59-
dispose(event.target);
59+
dispose(event.target, false);
6060
return;
6161
}
6262

6363
event.target.hxModalDotnetObjectReference.invokeMethodAsync('HxModal_HandleModalHidden');
64-
};
64+
}
6565

66-
export function dispose(element) {
66+
export function dispose(element, opened) {
6767
if (!element) {
6868
return;
6969
}
@@ -75,6 +75,16 @@ export function dispose(element) {
7575
return;
7676
}
7777

78+
if (opened) {
79+
// #110 Scrolling not working when modal is removed (even if disposed is called)
80+
// Compensates https://github.com/twbs/bootstrap/issues/36397,
81+
// where the o.dispose() does not reset the ScrollBarHelper() and the scrolling remains deactivated.
82+
// The dispose() is re-called from hidden.bs.modal event handler.
83+
// Remove when the issue is fixed.
84+
hide(element);
85+
return;
86+
}
87+
7888
element.removeEventListener('hide.bs.modal', handleModalHide);
7989
element.removeEventListener('hidden.bs.modal', handleModalHidden);
8090
element.removeEventListener('shown.bs.modal', handleModalShown);

0 commit comments

Comments
 (0)