Blazored / Modal

A powerful and customizable modal implementation for Blazor applications.
https://blazored.github.io/Modal/
MIT License
773 stars 185 forks source link

Modal dialog doesn't appear without additional CSS Tweaks (Blazor / .NET Core 3.1) #84

Closed VorTechS closed 4 years ago

VorTechS commented 4 years ago

After implementing the instructions as is using the NuGet package, despite the component I wanted to display in the dialog being initialized, no Blazored.Modal dialog was displayed.

It was however added to the DOM.

The following additional CSS was needed for the dialog to display:

.blazored-modal-container {
    position: fixed;
    left: 0;
    top: 0;
}
chrissainty commented 4 years ago

Hi @VorTechS,

Could you provide some code showing what you've done so far?

You shouldn't need to provide any additional CSS for the modal to function.

VorTechS commented 4 years ago

Hi @chrissainty,

Thanks for the reply!

I followed the instructions on the GitHub page (after the instructions on your old Blog post were clearly out-of-date) .

So Startup.cs:ConfigureServices:

services.AddBlazoredModal();

Modified Imports file with:

@using Blazored
@using Blazored.Modal
@using Blazored.Modal.Services
@using BlazorInputFile

...and modified the _Hosts file with:

<link href="_content/Blazored.Modal/blazored-modal.css" rel="stylesheet" />

The modal is placed as per the instructions in MainLayout.cs:

@inherits LayoutComponentBase
@using Microsoft.AspNetCore.Components.Authorization

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        <AuthorizeView>
            <NotAuthorized>
                <ul class="nav flex-row">
                    <li class="nav-item px-4">
                        <span class="oi oi-person mr-1">

                        </span>
                        <a href="Identity/Account/Login">Log in</a>
                    </li>
                </ul>
            </NotAuthorized>
            <Authorized>
                <ul class="nav flex-row">
                    <li class="nav-item px-4">
                        <span class="oi oi-person mr-1">

                        </span>
                        <a href="Identity/Account/Manage"><span>@context.User.Identity.Name</span></a>
                    </li>
                </ul>

                <ul class="nav flex-row">
                    <li class="nav-item px-4">
                        <span class="oi oi-account-logout mr-1">

                        </span>
                        <a href="Identity/Account/Logout"><span>Log Out</span></a>
                    </li>
                </ul>
            </Authorized>
        </AuthorizeView>
    </div>

    <div class="content px-4">
        @Body
    </div>

    <BlazoredModal />
</div>

@code {
}

Then, I have a data-bound page, which houses a toolbar (custom Razor component), which comprises the following code:

@inject IModalService Modal
@using Blazored.Modal
@using Curazor.Helpers;

@if (NextAvailableStatuses != null)
{
    <div class="row mt-3 mb-3">
        <div class="btn-toolbar d-inline">
            <span class="align-middle mr-2">Change Task Status to:</span>

            @foreach (var nextStatus in NextAvailableStatuses)
            {
                <button type="button" class="btn btn-primary d-inline align-middle mr-2" @onclick="() => { ShowNextStatusDialog(nextStatus.TaskStatusId); }">@nextStatus.Name</button>
            }
        </div>
    </div>
}

@code {

    private List<TaskItemStatus> NextAvailableStatuses = null;
    private TaskItemStatus NextStatusSelected = null;

    [Parameter]
    public int TaskId { get; set; }

    [Parameter]
    public int? TaskStatus { get; set; }

    protected override void OnInitialized()
    {
        base.OnInitialized();

        if (TaskStatus != null)
        {
            NextAvailableStatuses = StatusHelper.GetNextStatuses((int)this.TaskStatus).Result;
        }
    }

    void CloseNextStatusDialog()
    {

        this.StateHasChanged();
    }

    void ShowNextStatusDialog(int statusSelected)
    {
        NextStatusSelected = NextAvailableStatuses?.FirstOrDefault(item => item.TaskStatusId == statusSelected);

        var modalParameters = new ModalParameters();
        modalParameters.Add("StatusId", statusSelected);
        modalParameters.Add("TaskId", this.TaskId);

        Modal.Show<TaskStatusUpdate>($"Update task status to: {NextStatusSelected.Name}", modalParameters);

        this.StateHasChanged();

    }
}

and the TaskStatusUpdate component having the following:

@inject IModalService ModalService
@using Blazored.Modal
@using Curazor;
@using Curazor.Helpers;
@using System.IO;
@using Curazor.Data;
@using MimeMapping;

<div class="row">
    <div class="col-12">
        <p>You are about to update this task to the new status of @NewStatus.Name. </p>
        <p> Add any comments in the box below.</p>
    </div>
</div>

<div class="row mt-2">
    <div class="col-12"><textarea class="form-control" id="taskComment" rows="7" /></div>
</div>

@if (NewStatus.AllowFileUpload)
{
    <p class="mt-3">You can also add any additional supporting files.</p>
    <div class="row">
        <div class="drag-drop-zone col-5">
            <InputFile multiple OnChange="OnFileUploaded"></InputFile>
            @status
        </div>

        @if (NewAttachments != null && NewAttachments.Any())
        {
            <div class="col-7">

                <table class="table table-sm text-sm-center">
                    <thead>
                        <tr class="table-item-header">
                            <td>Name</td>
                            <td>File type</td>
                            <td>Status</td>
                            <td></td>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach (var newAttachment in NewAttachments)
                        {
                        <tr class="table-item-row">
                            <td class="text-sm-left">
                                <span class="text-uppercase">@newAttachment.Filename</span>
                            </td>
                            <td>@newAttachment.FileContentType</td>

                            <td class="text-sm-center">
                                @if (newAttachment.FileSize == 0)
                                {
                                    if (newAttachment.Filename == CurrentFileLoading)
                                    {
                                        <span class="text-sm-left">Loading...</span>
                                    }
                                    else
                                    {
                                        <span class="text-sm-left">Queued...</span>
                                    }
                                }
                                else
                                {
                                    @if (newAttachment.FileSize != -1)
                                    {
                                        <span class="text-sm-left">Uploaded!</span>
                                    }
                                    else
                                    {
                                        <span class="text-sm-left alert-danger">Rejected</span>
                                    }
                                }
                            </td>

                            <td>
                                @if (newAttachment.FileSize != 0)
                                {
                                    <ul><li class="oi oi-circle-x delete-attachment" @onclick="() => { DeleteUploadedAttachment(newAttachment.Filename); }"></li></ul>
                                }
                            </td>
                        </tr>
                        }

                    </tbody>
                </table>
            </div>
        }
    </div>
}

<div class="row d-inline-block mt-3 ml-2 w-100">
    <button @onclick="@UpdateTask" class="btn btn-primary float-right ml-2">Update</button>
    <button @onclick="@CancelDialog" class="btn btn-secondary float-right">Cancel</button>
</div>

@code {

    const string DefaultStatus = "Drop one or more files here to upload, or click to choose";
    const int MaxFileSize = 20 * 1024 * 1024; // 20MB
    const long MaxChunkRead = 5 * 1024 * 1024; // 1MB

    string status = DefaultStatus;

    [CascadingParameter]
    ModalParameters Parameters { get; set; }

    public int TaskId { get; set; }
    public int StatusId { get; set; }

    private TaskItemStatus NewStatus = null;

    IFileListEntry[] UploadedFiles = null;
    List<TaskAttachment> NewAttachments = null;
    private string CurrentFileLoading = null;

    protected override void OnInitialized()
    {
        TaskId = this.Parameters.Get<int>("TaskId");
        StatusId = this.Parameters.Get<int>("StatusId");

        NewStatus = StatusHelper.GetTaskStatuses().Result?.FirstOrDefault(item => item.TaskStatusId == StatusId);

        base.OnInitialized();
    }

    void UpdateTask()
    {

        ModalService.Close(ModalResult.Ok<int>(TaskId));
    }

    void CancelDialog()
    {
        ModalService.Cancel();
    }

    async void OnFileUploaded(IFileListEntry[] files)
    {
        UploadedFiles = files;

        if (NewAttachments == null)
        {
            NewAttachments = new List<TaskAttachment>();
        }

        foreach (var file in files)
        {
            if (NewAttachments.Count(item => item.Filename.ToLower() == file.Name.ToLower()) == 0)
            {
                NewAttachments.Add(new TaskAttachment()
                {
                    Filename = file.Name,
                    FileContentType = MimeMapping.MimeUtility.GetMimeMapping(file.Name)
                });
            }
        }

        foreach (var file in files)
        {
            if (file.Size <= MaxFileSize)
            {
                using (var reader = new BinaryReader(file.Data))
                {
                    CurrentFileLoading = file.Name;

                    long bytesRead = 0;
                    long fileSize = file.Size;
                    List<byte> currentFileBytes = new List<byte>();

                    do
                    {
                        long remaining = fileSize - bytesRead;
                        long bytesToRead = remaining;

                        if (remaining > MaxChunkRead)
                        {
                            bytesToRead = MaxChunkRead;
                        }

                        try
                        {
                            byte[] buffer = new byte[bytesToRead];

                            await file.Data.ReadAsync(buffer, 0, (int)bytesToRead);
                            bytesRead += buffer.Length;

                            currentFileBytes.AddRange(buffer.ToList());
                        }
                        catch (Exception)
                        {

                            throw;
                        }

                    } while (bytesRead < fileSize);

                    NewAttachments.FirstOrDefault(item => item.Filename == file.Name).FileData = currentFileBytes.ToArray();
                    NewAttachments.FirstOrDefault(item => item.Filename == file.Name).FileSize = currentFileBytes.Count;
                    this.StateHasChanged();
                }
            }
            else
            {
                NewAttachments.FirstOrDefault(item => item.Filename == file.Name).FileSize = -1;
            }
        }
    }

    void DeleteUploadedAttachment(string fileName)
    {
        var matchedUpload = NewAttachments.FirstOrDefault(item => item.Filename == fileName);

        if (matchedUpload != null)
        {
            NewAttachments.Remove(matchedUpload);
            this.StateHasChanged();
        }
    }
}

(You can ignore everything essentially in this file as originally it was just a basic form with a standard header element in, and these other things were added after this issue was raised).

So it's all pretty 'straight forward' stuff.

I mostly use bootstrap CSS, except where I need to add additional things for layout / colour difference.

The net result, as I say, is all the code fires and the DOM gets updated correctly. It's just the CSS tweaks I've made that appears to be missing.

There's nothing secret about this project I'm working on (it's an internal project for evaluating Blazor and migrating an old ASP application), although it requires our database behind. So I'd be happy to do a code-share (subject to any configuration alterations inside VS 2019) so you can see it for yourself any time you like.

chrissainty commented 4 years ago

Thanks for the detailed information @VorTechS, much appreciated.

As you say, it's all pretty straight forward stuff. Looking at the CSS you added I'm wondering if some other CSS is overriding the styles from Blazored Modal. Looking at the styles you've had to add:

.blazored-modal-container {
    position: fixed;
    left: 0;
    top: 0;
}

The .blazored-modal-container class has the position: fixed by default. If you've had to re-state that then possibly something is interfering with it somehow? What do you think?

VorTechS commented 4 years ago

I just removed the CSS I added, and you're right, it does have the position: fixed attribute, but it does not have a location of 0, 0 which puts it in the right place.

Dev tools screenshot from Firefox:

https://1drv.ms/u/s!AvRDtKpR9Rqyn8loex6-pcs6VO5Yiw

chrissainty commented 4 years ago

You shouldn't need to use top: 0 and left: 0. The modal is centred by default using flexbox. My gut feeling here is there is some other styling interfering with the modal defaults.

The image you linked isn't working for some reason, it just goes to a 404 page. But I wonder if you could send me a screenshot of the styles for the modal container element, this one <div class="blazored-modal-container">, from the dev tools.

larsk2009 commented 4 years ago

This URL works for me

VorTechS commented 4 years ago

Sorry, I guess I got my markdown wrong for the image Url, thanks @larsk2009.

@chrissainty The screenshot (via the link larsk provided) contains what you are looking for.

chrissainty commented 4 years ago

@VorTechS So nothing looks out of place. I did notice you were using Firefox and I usually use the new Edge. So I wondered if it was some browser issue, but I've just run the sample app in FF and everything works as expected.

I'm a bit stumped on this one, to be honest. All I can think is that there is some styling in your project that is having an effect somehow but without going through all the code I wouldn't be able to tell.

As you have a solution which works for you I think we might just have to chalk this one up as a project-specific issue. What do you think?

VorTechS commented 4 years ago

@chrissainty The issue appears to be irrespective of browser. The same happens in Edge (although not tested under Canary as it's not installed on this machine), and Chrome too.

As I have a workaround, I have no complaints if you want to close it. Clearly no-one else has the issue or silently fixed it without mentioning it!

Perhaps though it's worth a small note on the README to mention that if anyone runs into problems like I did, to add the tweaks … just in case it starts becoming an issue...?

chrissainty commented 4 years ago

Perhaps though it's worth a small note on the README to mention that if anyone runs into problems like I did, to add the tweaks … just in case it starts becoming an issue...?

I think I will leave it for now and see if it comes up again. If someone else starts having the same issue then there might be something I need to fix.

Thank you for helping investigate this one.

subgurim commented 4 years ago

Exactly the same is happening to me, and the left:0, top:0 thing worked for me.

Trying to find the problem, I've removed every external CSS and just left a loren ipsum div:

image

chrissainty commented 4 years ago

It's very odd, I'm not sure what to say here, to be honest.

larsk2009 commented 4 years ago

I am pretty sure I have figured out the problem. I was having this problem in the beginning as well.

Looking at @VorTechS code, I think the <BlazorModal /> is in the wrong place. It should be at the root of the document, and not inside a <div> like it is in his code. If I move the <BlazoredModal /> to the position used by @VorTechS in the official sample, I can replicate this problem which can indeed be fixed by adding the css mentioned. So I think this can easiliy be fixed by anyone by placing the <BlazoredModal> in the correct location.

The modal is placed as per the instructions in MainLayout.cs:

@inherits LayoutComponentBase
@using Microsoft.AspNetCore.Components.Authorization

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        <AuthorizeView>
            <NotAuthorized>
                <ul class="nav flex-row">
                    <li class="nav-item px-4">
                        <span class="oi oi-person mr-1">

                        </span>
                        <a href="Identity/Account/Login">Log in</a>
                    </li>
                </ul>
            </NotAuthorized>
            <Authorized>
                <ul class="nav flex-row">
                    <li class="nav-item px-4">
                        <span class="oi oi-person mr-1">

                        </span>
                        <a href="Identity/Account/Manage"><span>@context.User.Identity.Name</span></a>
                    </li>
                </ul>

                <ul class="nav flex-row">
                    <li class="nav-item px-4">
                        <span class="oi oi-account-logout mr-1">

                        </span>
                        <a href="Identity/Account/Logout"><span>Log Out</span></a>
                    </li>
                </ul>
            </Authorized>
        </AuthorizeView>
    </div>

    <div class="content px-4">
        @Body
    </div>

    <BlazoredModal />
</div>

@code {
}

And see this from the official samples:

@inherits LayoutComponentBase

<BlazoredModal />

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        <a href="https://docs.microsoft.com/en-us/aspnet/" target="_blank">About</a>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>
chrissainty commented 4 years ago

That's a great spot @larsk2009!

@VorTechS Could you try moving your modal out of the div it's in and see if it resolves the issue?

VorTechS commented 4 years ago

Good spot @larsk2009 - yes, indeed moving it out of that containing div does solve the problem!

chrisknebel commented 4 years ago

Okay, here's another data point for you @chrissainty : I had the same problem, but my 'BlazoredModal' component was NOT inside of a DIV. What it was, though, was at the end of the MainLayout.razor, after all of the DIVs. Just moving it to the top of the MainLayout.razor fixed the problem. Weird, huh? I hope this helps you narrow down the problem, but it's quite obviously not due to the 'BlazoredModal' not being in the root of the HTML document.

chrissainty commented 4 years ago

Thanks for the information @chrisknebel