Home > Software engineering >  Razor app: I want html button shows jQuery UI confirm dialog before execute C# method to delete DB r
Razor app: I want html button shows jQuery UI confirm dialog before execute C# method to delete DB r

Time:09-18

I'm learning web dev with a Razor app. I have a Page, PeopleIndex where a table shows a people list and after each row there is, 1st an <a ...> to Edit person data, and 2nd a <input ...> from where I would like to call the jQuery UI confirm dialog (which I already have the code in the .cshtml), from where if I click "Yes" button that person registry should be deleted, this is, call the Delete() method that is in the .cshtml.cs file. I clear out that this procedure is not a submit action, I say this because all what I found on the Internet related to my problem was about "form method = POST and type=submit" and that all this should be done in another Page, that I want to just do it in my person listing Page. But, if I'm wrong, I will listen to ideas. I attach both files of my PeopleIndex Razor Page:

************* PeopleIndex.cshtml *****************

@page
@model WebAppPersonas.Pages.PersonasIndexModel
@{
    Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head title="Lista de personas">
    <link rel="stylesheet" href="~/lib/jquery-ui-1.12.1.custom/jquery-ui.min.css" />
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" />
    <script src="~/lib/jquery-ui-1.12.1.custom/external/jquery/jquery.js"></script>
    <script src="~/lib/jquery-ui-1.12.1.custom/jquery-ui.min.js"></script>
    <script>
        $(document).ready(function () {
            // Confirmation Dialog
            $('#confirmDialog').dialog({
                autoOpen: false,
                width: 500,
                height: auto,
                modal: true,
                resizable: false,
                buttons: {
                    "Yes": function () {
                        $(".ui-dialog-buttonpane button:contains('Si')").button("disable");
                        $(".ui-dialog-buttonpane button:contains('No')").button("disable");
                        call Delete() C# method from .cs file // neither know what goes here
                        $(this).dialog("close");
                    },
                    "No": function () {
                        $(this).dialog("close");
                    }
                }
            });

            $('#deleteReg').click(function (e) {
                e.preventDefault();
                $('#confirmDialog').dialog('open');
            });
        });
    </script>
</head>
<body>
    <a asp-page="./InsertUpdate" class="btn btn-primary"> Add person </a>
    <h2> People LIst </h2>
    <table class="table">
        <thead>
            <tr>
                <th> @Html.DisplayNameFor(Model => Model.people[0].Id) </th>
                <th> @Html.DisplayNameFor(Model => Model.people[0].Name) </th>
                <th> @Html.DisplayNameFor(Model => Model.people[0].Age) </th>
                <th> @Html.DisplayNameFor(Model => Model.people[0].Email) </th>
            </tr>
        </thead>
        <tbody>
            @foreach (var item in Model.people)
            {
                <tr>
                    <td> @Html.DisplayFor(modelItem => item.Id) </td>
                    <td> @Html.DisplayFor(modelItem => item.Name) </td>
                    <td> @Html.DisplayFor(modelItem => item.Age) </td>
                    <td> @Html.DisplayFor(modelItem => item.Email) </td>
                    <td> <a asp-page="./InsertUpdate" asp-route-id="@item.Id"> Update </a> | <input type="button" value="Delete" onclick="I don't know what goes here"/> </td>
                </tr>
            }
        </tbody>
    </table>

    <div id="confirmDialog" title="Confirm delete">
        <p> You are about to delete a registry ¿Are you sure?</p>
    </div>
</body>
</html>

********** PeopleIndex.cshtml.cs *****************

    public void OnGet()
    {
        people = dataAccess.GetPeople();
    }

    public ActionResult Delete(int? id) 
    {
        dataAccess.DeletePerson(id.Value);
        return Redirect("./PeopleIndex");
    }

I had to cut off the header of the .cshtml.cs file because the site didn't allow me to format that code and then because of that the site didn't allow me to post the question. I hope now it allows me to post the question. This is very complicated. I think it should be easier the duty of formatting code in different languages, but well, ... this is what there is ... Thank you in advance.

EDIT*** Hi, Michael I edited the files with the code you gave me. But I put a breakpoint in:

            $('.deleteButton').click(function (e) {
                e.preventDefault();
                const id = $(e.target).data("id");
                $('#confirmDialog').data("id", id);
                $('#confirmDialog').dialog('open');
            });

And it doesn't enter that code, which comes from the button here:

    <tbody>
        @foreach (var item in Model.people)
        {
            <tr>
                <td> @Html.DisplayFor(modelItem => item.Id) </td>
                <td> @Html.DisplayFor(modelItem => item.Name) </td>
                <td> @Html.DisplayFor(modelItem => item.Age) </td>
                <td> @Html.DisplayFor(modelItem => item.Email) </td>
                <td> <a asp-page="./InsertUpdate" asp-route-id="@item.Id"> Update </a> | <button class="deleteButton" data-id="@item.Id"> Delete </button> </td>
            </tr>
        }
    </tbody>

So it is not working by now, but at least we now know where is the problem. I attach again the affected files with current updates, I don't put the .cs because it's the only that is Ok. 1st, the .cshtml.cs (Now we know that when clicking deleteButton it doesn't enter the JS function. I tried changing class for name but the same.)

@page
@model WebAppPersonas.Pages.PeopleIndexModel
@section Head
{
    <link rel="stylesheet" href="~/lib/jquery-ui-1.12.1.custom/jquery-ui.min.css" />
    @*<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
        <link rel="stylesheet" href="~/css/site.css" />
        <script src="~/lib/jquery-ui-1.12.1.custom/external/jquery/jquery.js"></script>*@
    <script src="~/lib/jquery-ui-1.12.1.custom/jquery-ui.min.js"></script>
    <script>
        $(document).ready(function () {
            // Confirmation Dialog
            $('#confirmDialog').dialog({
                autoOpen: false,
                width: 500,
                height: auto,
                modal: true,
                resizable: false,
                buttons: {
                    "Yes": function () {
                        $(".ui-dialog-buttonpane button:contains('Yes')").button("disable");
                        $(".ui-dialog-buttonpane button:contains('No')").button("disable");
                        DeletePerson($(this).data("id"));
                        $(this).dialog("close");
                    },
                    "No": function () {
                        $(this).dialog("close");
                    }
                }
            });

            $('.deleteButton').click(function (e) {
                e.preventDefault();
                const id = $(e.target).data("id");
                $('#confirmDialog').data("id", id);
                $('#confirmDialog').dialog('open');
            });
        });
    </script>
    <script>
        const token = document.getElementsByName('__RequestVerificationToken')[0].nodeValue;
        function DeletePerson(id) {
            const formData = new FormData();
            formData.append("id", id);
            fetch(window.location.href, {
                method: 'DELETE',
                headers: { 'XSRF-TOKEN': token },
                body: formData
            })
                .then(result => { window.location.reload(); })
                .catch(error => alert("Error sending DELETE request."))
        }
    </script>
}
<div id="confirmDialog" title="Confirm delete">
    <p> You are about to delete a registry. Are you sure? </p>
</div>

<a asp-page="./InsertUpdate" class="btn btn-primary"> Add person </a>
<h2> List of People </h2>
<table class="table">
    <thead>
        <tr>
            <th> @Html.DisplayNameFor(Model => Model.people[0].Id) </th>
            <th> @Html.DisplayNameFor(Model => Model.people[0].Name) </th>
            <th> @Html.DisplayNameFor(Model => Model.people[0].Age) </th>
            <th> @Html.DisplayNameFor(Model => Model.people[0].Email) </th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.people)
        {
            <tr>
                <td> @Html.DisplayFor(modelItem => item.Id) </td>
                <td> @Html.DisplayFor(modelItem => item.Name) </td>
                <td> @Html.DisplayFor(modelItem => item.Age) </td>
                <td> @Html.DisplayFor(modelItem => item.Email) </td>
                <td> <a asp-page="./InsertUpdate" asp-route-id="@item.Id"> Update </a> | <button class="deleteButton" data-id="@item.Id"> Delete </button> </td>
            </tr>
        }
    </tbody>
</table>
@Html.AntiForgeryToken()

2nd, the _Layout.cshtml

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - WebAppPersonas</title>
    @*<link rel="stylesheet" href="~/lib/jquery-ui-1.12.1.custom/jquery-ui.min.css" />*@
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" />
    <script src="~/lib/jquery-ui-1.12.1.custom/external/jquery/jquery.js"></script>
    @*<script src="~/lib/jquery-ui-1.12.1.custom/jquery-ui.min.js"></script>*@
    @RenderSection("Head", false)
</head>
<body>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">WebAppPersonas</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - WebAppPersonas - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    @*<script src="~/lib/jquery/dist/jquery.min.js"></script>*@
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

Blockquote

CodePudding user response:

Razor pages are a server side technology. You can't call C# functions in javascript. Instead you can make a request. This request will be assigned to a method in your Razor page using the routing middleware.

To illustrate the problems that this implies we have a simple PageModel:

public class Person
{
    public int Id { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
}

public class PersonsModel : PageModel
{
    private static List<Person> _persons = new List<Person>
    {
        new Person{Id = 1, Firstname = "FN1", Lastname = "LN1"},
        new Person{Id = 2, Firstname = "FN2", Lastname = "LN2"},
        new Person{Id = 3, Firstname = "FN3", Lastname = "LN3"},
        new Person{Id = 4, Firstname = "FN4", Lastname = "LN4"}
    };

    public List<Person> Persons => _persons;

    public void OnGet()
    {
    }

    // Called from fetch (JavaScript)
    public IActionResult OnDelete(int id)
    {
        var person = _persons.Find(p => p.Id == id);
        if (person == null) { return NotFound(); // HTTP 404 triggers .catch() in fetch }
        _persons.Remove(person);
        return new NoContentResult();  // HTTP 204
    }
}

As you can see, OnDelete returns no redirect, because the server response is not interpreted by the browser. It is up to you to respond to this return value.

In our Razor view we have a list of persons and a delete button for each of them.

@page
@model RazorDemo.Pages.PersonsModel
@{
}
<ul>
    @foreach (var p in Model.Persons)
    {
        <li>@p.Id - @p.Firstname @p.Lastname <button onclick="deletePerson(@p.Id)">Delete</button></li>
    }
</ul>

@*Generate a hidden field with the RequestVerificationToken.*@
@Html.AntiForgeryToken()

<script>
    const token = document.getElementsByName('__RequestVerificationToken')[0].value;

    // Send a DELETE request as multipart/form-data (an ordinary HTML form)
    function deletePerson(id) {
        // Crates a HTML form with one parameter (id)
        const formData = new FormData();
        formData.append("id", id);

        fetch(window.location.href, {
            method: 'DELETE',
            headers: { 'XSRF-TOKEN': token },
            body: formData
        })
            .catch(error => alert("Error sending DELETE request."));
    }
</script>

A server generated HTML form contains a hidden input element with an anti forgery token (request verification token, more information at www.learnrazorpages.com). We have to create this element manually by using @Html.AntiForgeryToken().

The JavaScript gets the value of this token. After that we are creating the payload of an ordinary HTML form, because we want to use standard model binding and validation at server side.

Now we need to do some configuration, because we are sending the anti forgery token in the request header.

public void ConfigureServices(IServiceCollection services)
{
    // Other services
    services.AddAntiforgery(o => o.HeaderName = "xsrf-token");
}

Now you can delete a person, but your UI dosen't refresh. Only after reloading the deleted person disappears. Now you come to a point where you need a JavaScript MVVM framework instead of JQuery. A small JavaScript framework is Knockout. A JavaScript MVVM framework generates the html content based on json values from your server. You can send your list of persons to the client as JSON and store it in an array. When you delete a person, you can delete that person in that array and the templates engine will update your view automatically.

Razor pages supports JSON results and different handlers, so you don't need a separate controller for that.

// GET Persons?handler=AllPersons
public IActionResult OnGetAllPersons()
{
    return new JsonResult(Persons);
}

Using jQuery UI

To include the jQuery UI references you can define a section in your main layout (_Layout.cshtml). The default template uses bootstrap and jQuery. To get jQuery UI to work, you have to reference to the bundled version of jQuery which comes with jQuery UI. We also define a section in the head element, which can be used by the razor page.

_Layout.cshtml

<!DOCTYPE html>
<html lang="en">
<head>
    <!-- Other references (bootstrap css, ...) -->
    <script src="~/lib/jquery-ui-1.12.1.custom/external/jquery/jquery.js"></script>
    @RenderSection("Head", false)
</head>
<body>
    <!-- Your layout with RenderBody() -->
    <!-- NO REFERENCE TO JQUERY! -->
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

Important: Delete (or comment out) the reference to lib/jquery/dist/jquery.min.js at the end of the body.

Now we can add a dialog to the Persons page:

@page
@model RazorDemo.Pages.PersonsModel
@section Head
{
    <link rel="stylesheet" href="~/lib/jquery-ui-1.12.1.custom/jquery-ui.min.css" />

    <script src="~/lib/jquery-ui-1.12.1.custom/jquery-ui.min.js"></script>
    <script>
        $(document).ready(function () {
            // Confirmation Dialog
            $('#confirmDialog').dialog({
                autoOpen: false,
                width: 500,
                height: "auto",
                modal: true,
                resizable: false,
                buttons: {
                    "Yes": function () {
                        $(".ui-dialog-buttonpane button:contains('Si')").button("disable");
                        $(".ui-dialog-buttonpane button:contains('No')").button("disable");
                        // Call deletePerson with data-id of the dialog div.
                        deletePerson($(this).data("id"));
                        $(this).dialog("close");
                    },
                    "No": function () {
                        $(this).dialog("close");
                    }
                }
            });

            $('.deleteButton').click(function (e) {
                e.preventDefault();
                // We have a generic event handler for all buttons.
                // So we have to look at the event source and read the data-id attribute.
                const id = $(e.target).data("id");
                // Now we create a data attribute for <div id="confirmDialog">
                $('#confirmDialog').data("id", id);
                $('#confirmDialog').dialog('open');
            });
        });

        function deletePerson(id) {
            const token = document.getElementsByName('__RequestVerificationToken')[0].value;
            const formData = new FormData();
            formData.append("id", id);

            fetch(window.location.href, {
                method: 'DELETE',
                headers: { 'XSRF-TOKEN': token },
                body: formData
            })
                .then(result => { window.location.reload(); })  // Reloading the page. This is not AJAX, but it will do the job.
                .catch(error => alert("Error sending DELETE request."));
        }
    </script>
}

<div id="confirmDialog" title="Confirm delete">
    <p> You are about to delete a registry. Are you sure?</p>
</div>

<ul>
    @foreach (var p in Model.Persons)
    {
        <li>@p.Id - @p.Firstname @p.Lastname <button class="deleteButton" data-id="@p.Id">Delete</button></li>
    }
</ul>

@*Generate a hidden field with the RequestVerificationToken.*@
@Html.AntiForgeryToken()

Instead of onClick you can use a generic event handler (defined with .click() in jQuery). Therefore the buttons have a data-id attribute. Inside the event handler we retrieve this id with $(e.target).data("id") and assign a data-id attribute dynamically to the dialog div.

  • Related