2

I have the following GUI for a simple inventory management application:

enter image description here

My problem is... The <tr> element's @Url.Action(...) is preventing the delete (trash) button from popping up a bootstrap modal. The page navigates away to the row items "Details" page. However, it appears the modal would popup if it didn't navigate away.

Table Row Markup:

<tr onclick="location.href = '@(Url.Action("Action", "Controller", new { id = item.ItemID }))'" class="inventory-row">
    ... <!-- Item Info -->
</tr>

Delete Button Markup:

@Html.ActionLink("<i class='fas fa-trash-alt'></i>", "#", null, new { @class = "btn btn-danger", @data_toggle = "modal", @data_target = "#DeleteConfirmationModal" })

Tried/Alt Delete Button Markup:

<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#DeleteConfirmationModal">
    <i class="fas fa-pencil-alt"></i>
</button>

My goal is to popup a simple "Are you sure you want to delete item?" modal.

How can I get my delete button to popup the bootstrap modal while keeping the functionality that clicking on a table row goes to the items "Details" page?

I'd prefer to use razor syntax to accomplish this but you know the saying... at the end of the day I just need something that will work.

In the event it helps... this question is how I got the edit (pencil) button to function properly and this question is how I get the modal to pop up when the page doesn't navigate away.

EDIT:

I believe the edit (pencil) button only works because it navigates to the items edit page before the <tr> element has a chance to navigate to the details page. The delete (trash) button brings up a modal nested on the same page therefore allowing the <tr> element to navigate to the details page.

Do I need to reevaluate my interface design? or is it possible to have all but the last <td> element clickable effectively removing the conflict from the buttons?

EDIT 2:

Here is a link to the markup for the table: link

EDIT 3:

Delete method in controller:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
    try
    {
        Item.RemoveItem(id);
        return RedirectToAction("Index");
    }
    catch
    {
        return View();
    }
}
johnny 5
  • 19,893
  • 50
  • 121
  • 195
Greg Hess Jr
  • 122
  • 2
  • 12
  • 1
    Do you need to navigate to the Details page if the user clicks anywhere on the row or only if they click on the pencil button? – StaticBeagle May 15 '18 at 14:41
  • 1
    I need to navigate to the details page if they click anywhere on the row. The pencil button takes them to the items edit page. – Greg Hess Jr May 15 '18 at 14:41

3 Answers3

1

Since the trash button is inside the tr, one thing you can try is to handle the click event of the trash button itself and stop the event from propagating up. I'm assuming you are using bootstrap so the following solution is using jQuery. This should work for a button or ActionLink but usually when you use an <a>, you want to use that anchor's href as the placeholder for the data-target. I decided to use a button. So in you html

<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#DeleteConfirmationModal">
    <i class="fas fa-trash-alt"></i>
</button>

In your javascript:

$(document).ready(function() {
  $(".btn.btn-danger").click(function(e) {
    e.stopPropagation()
    let target = $(this).data('target')
    $(target).modal('show')
  })
})

Here is a demo:
https://www.bootply.com/paKZLt8L94

How to implement this in MVC
Add an anti-forgery token to your view (Replace ControllerName below with your Controller)

// The purpose of this form is to create a Anti-forgery token
// needed by the controller. Insert this anywhere in your html
@using (Html.BeginForm("DeleteConfirmed", "ControllerName", FormMethod.Post, new { id = "delete-forklift-form" }))
{
    @Html.AntiForgeryToken()
    <input type="hidden" id="forklift-Id" name="id" value="" /> 
}

Add the modal markup somewhere in your view (at the end is ok)

<!-- Modal -->
<div class="modal fade" id="DeleteConfirmationModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">×</span>
        </button>
      </div>
      <div class="modal-body">
        Are you sure you want to delete this item?
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        <button id="delete-confirm" type="button" class="btn btn-primary">Delete</button>
      </div>
    </div>
  </div>
</div>

Add the item ID as an data-attribute to your buttons:

// We need this ID to be passed to the delete action
// This is the foreach statement in you view from the pastebin link
@foreach(var item in Model.ForkliftStockInventoryList)
{
    // ... rest of the stuff
    <button type="button" class="btn btn-danger" data-fid="@item.ForkliftID" data-toggle="modal" data-target="#DeleteConfirmationModal">
        <i class="fas fa-trash-alt"></i>
    </button>  
}

Wrap your JavaScript in a Scripts block
Put the modified JavaScript code at the end of your view:

@section Scripts{
    <script>
        $(document).ready(function () {
            // handler for the trash button
            $(".btn.btn-danger").click(function (e) {

                e.stopPropagation()
                let target = $(this).data('target')
                let forkliftId = $(this).data('fid')
                // update the forklift to delete in the hidden field
                $('#forklift-Id').val(forkliftId)
                $(target).modal('show')
            })

            // handler for the delete confirmation on the modal
            $('#delete-confirm').click(function () {
                $('#delete-forklift-form').submit()
            })
        })
    </script>
}

In your _Layout view you are rendering jQuery after is being used that's why none of the jQuery stuff was working. Push @RenderSection("scripts", required: false) to the end so that all the scripts in the layout get rendered before the scripts in each view. It's very important that you wrap the <script>...</script> in the Forklift index view in a Scripts block (see above)

Change

 @RenderSection("scripts", required: false)
    @*<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>*@
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script> 

To

 <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
    @RenderSection("scripts", required: false)
// RenderSection scripts at the end
StaticBeagle
  • 5,070
  • 2
  • 23
  • 34
  • I appreciate the help. This seemed like the most promising answer to me. Especially when I saw `e.stopPropagation()` was a thing but I still couldn't get this to work. It looks like it's still trying to open the modal but it navigates away. I am using bootstrap-4. – Greg Hess Jr May 16 '18 at 16:26
  • @GregHess hmm, interesting. I've added a demo with a mockup of what your html might look like. Take a look at the demo and see if I'm missing something. – StaticBeagle May 16 '18 at 16:57
  • Looking at your demo I did notice I made a huge mistake putting the modal code into the `` element. However, even moving the modal outside the table I'm still faced with the same problem. Here is a [pastebin link](https://pastebin.com/LgufJ44K) for my code I'm so confused as to what I might be doing wrong since your demo clearly worked. – Greg Hess Jr May 16 '18 at 17:37
  • I'm sorry in that paste bin ignore the extra space on line 68 between the classes... I added the space thinking it was possibly needed. I tried with and without the space. – Greg Hess Jr May 16 '18 at 17:45
  • @GregHess Do you have a Delete view in your views and two delete actions in your controller? Issuing the delete request via JavaScript, while not impossible, is a bit more complicated than using the default scaffolding provided by MVC. I'm asking all this to try figure out what your current setup looks like. – StaticBeagle May 17 '18 at 12:46
  • I do not have a view for delete. The idea was that the modal serves the purpose for confirmation. The controller has a single delete method that deletes the inventory record from the database and redirects to the "Index" action for displaying the full list of items as seen in the image of my original post. I have added my controller method to the bottom of my question for reference. – Greg Hess Jr May 17 '18 at 13:22
  • @GregHess I've added a mini how-to using MVC. Ignore everything before the edit. Try it out and let's see if it works. – StaticBeagle May 17 '18 at 18:34
  • @pool pro made [this](http://codesandbox.io/s/52kjq29jzl) which replicates my problem. It works for me in chrome but not edge. I have users that heavily rely on edge any ideas? – Greg Hess Jr May 18 '18 at 01:09
  • @GregHess that's a really good start but I'm afraid it won't work "as is" in your mvc project. The bootstrap modal doesn't really do anything when you press the "confirm/delete" button so you have to do some javascript trickery and submit a fake form to your controller. The section scripts stuff he mentions is good use that. Feel free to try the last stuff I added, I think it should do the trick. – StaticBeagle May 18 '18 at 01:27
  • Your right I just thought I'd be able to implement the post to the controller from the modal delete button myself. I'm really trying to get it right but I'm still experiencing the same problem. I've gone over your how-to multiple time. Maybe I'm still missing something. I put my entire project on a GitHub repo [View](https://github.com/GregHess/Bootstrap-4-Modal-Problem/blob/master/MaterialSolutions/MaterialSolutions/Views/Forklift/Index.cshtml) [Controller](https://github.com/GregHess/Bootstrap-4-Modal-Problem/blob/master/MaterialSolutions/MaterialSolutions/Controllers/ForkliftController.cs) – Greg Hess Jr May 18 '18 at 12:12
  • 1
    @GregHess the problem was in you layout. You were including jQuery after it was being used. All the changes you have to make to get the thing to work start after **Wrap your JavaScript in a Scripts block** in my answer. I noticed you are using `$("#DeleteForkliftButton")`, don't use this, use `$(".btn.btn-danger")` as provided in the answer. This should put a lid on this issue. Also please feel free to delete the repo once you patch the issue. – StaticBeagle May 18 '18 at 19:51
  • You are the man thank you so much for pointing that out to me. I literally threw the references in at the beginning/end of the project so fast to upgrade bootstrap from 3.3.7 (from the project scaffold) to 4 and was going to sort them out later not realizing this problem. Do you have a tip jar? I'd love to compensate you for your time. I can't thank you enough. – Greg Hess Jr May 18 '18 at 20:22
  • @GregHess glad I could help. One friendly suggestion is to familiarize yourself with what the `Scripts.Render`, `@Scripts` stuff is doing, ideally you want to be using that (at least in the layout view) instead of including the script directly. The app looks good. Good luck with the rest of the development. – StaticBeagle May 18 '18 at 21:09
0

This markup would work but you need to provide that modal ID:

<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#DeleteConfirmationModal">
    <i class="fas fa-pencil-alt"></i>
</button>

I'm not sure if you missed the ID by mistake...

EDIT:

I see, I think it's because you're using <button> and I've had the same issue.

Try this markup:

<a href="#" class="btn btn-danger" data-toggle="modal" data-target="#DeleteConfirmationModal">
    <i class="fas fa-pencil-alt"></i>
</a>

EDIT:

Oh I see what you mean, yes it's your <tr> event that fires. Add this to your button click event: event.stopPropagation();

Documentation: https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation

Billy
  • 81
  • 2
  • 10
  • I apologize... I missed it in typing the question but the ID is not missing from my actual code. This does bring up the modal but the page still navigates away to the items "Details" page from the `` markup. – Greg Hess Jr May 14 '18 at 16:25
  • I tried that as well. I think that regardless of where I click on the row it will trigger to go to the details page. The only reason the edit button works is because it navigates to the items edit page before it has a chance to navigate to the items detail page. With my delete button I'm trying to pop up a confirmation modal on the same page therefore the `` element navigates away. If that makes sense. I've also tried javascript to disable default link behavior but still no success. I may just have to change my design. For now I've just moved those buttons to the items details page. – Greg Hess Jr May 15 '18 at 15:05
0

So looking at you code, Since you have an edit button you should ad a view button and remove the action link from the <tr> tag and move it to a button. this will stop the page from navigating away with the onclick event on the whole row. with the 3 buttons on the right users can know how to click rather than making the whole row clickable.

EDIT:

So i have set up the following as if it was MVC with jQuery loading after the footer, Had issues with loading the script in the page as it would error on the buttons with multiple rows. I have never been a fan of e.stopPropagation() as it has always caused other issues on pages but seams on this single page with no other jQuery being called it should work. here is a working example.

    <html>

    <head>
        <title>Parcel Sandbox</title>
        <meta charset="UTF-8" />
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
     crossorigin="anonymous">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp"
     crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css">
    </head>

    <body>
        <table>
            <tbody>
                <tr onclick="location.href = 'https://www.google.com'" class="inventory-row">
                    <td>col 1</td>
                    <td>col 2</td>
                    <td>col 3</td>
                    <td>col 4</td>
                    <td>
                        <button type="button" class="btn btn-warning" data-toggle="modal" data-target="#DeleteConfirmationModal">
            <i class="fa fa-trash"></i>
          </button>
                    </td>
                    <td>
                        <button type="button" class="btn btn-danger" data-toggle="modal" data-target="#DeleteConfirmationModal">
            <i class="fa fa-trash"></i>
          </button>
                    </td>
                </tr>
                <tr onclick="location.href = 'https://www.google.com'" class="inventory-row">
                    <td>col 1</td>
                    <td>col 2</td>
                    <td>col 3</td>
                    <td>col 4</td>
                    <td>
                        <button type="button" class="btn btn-warning" data-toggle="modal" data-target="#DeleteConfirmationModal">
            <i class="fa fa-trash"></i>
          </button>
                    </td>
                    <td>
                        <button type="button" class="btn btn-danger" data-toggle="modal" data-target="#DeleteConfirmationModal">
            <i class="fa fa-trash"></i>
          </button>
                    </td>
                </tr>
            </tbody>
        </table>

        <!-- Modal -->
        <div class="modal fade" id="DeleteConfirmationModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
            <div class="modal-dialog" role="document">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
              <span aria-hidden="true">×</span>
            </button>
                    </div>
                    <div class="modal-body">
                        TEST TEXT...
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                        <button type="button" class="btn btn-primary">Save changes</button>
                    </div>
                </div>
            </div>


    <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.3.1.min.js"></script>
            <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
             crossorigin="anonymous"></script>
            <script src="/src/index.js"></script>
    </body>

    </html>

For the script: place the script in its own file, here i have it in index.js. Include this in layout under jquery script load:

@RenderSection("Scripts",false/*required*/)

Include the file on the view:

@section Scripts
{
  <script src="@Url.Content("~/Scripts/index.js")"></script>
}

index.js:

$(".btn-warning").click(function (e) {
  e.stopPropagation()
  let target = $(this).data('target')
  $(target).modal('show')
})
$(".btn-danger").click(function (e) {
  e.stopPropagation()
  let target = $(this).data('target')
  $(target).modal('show')
})
pool pro
  • 2,084
  • 12
  • 21
  • I could but the constraint I had in my question is to allow the user to click on the row instead of having a view button. – Greg Hess Jr May 15 '18 at 14:45
  • The row element will take priority over the element as the element is nested within the element. It is like trying to touch a button in a sealed glass jar. the only way around this is to make the ID clickable so you have control over what elements are clickable. i hope this makes sense. – pool pro May 16 '18 at 17:25
  • Another ting you can do is to remove buttons al together and place your delete button on the Item view page. so this way when they click the row they can edit and save, or delete record. – pool pro May 16 '18 at 17:27
  • I'm unsure what your referring to by making the ID clickable but I understand what you mean about the nested elements I just find it weird the edit (pencil) button works. Right now I do have the buttons in the details page so they can just click on the row and then click to edit/delete/etc. It would just be super convenient to have it all in one spot. – Greg Hess Jr May 16 '18 at 17:42
  • does the Clickable row take the user to the same URL as the edit button? – pool pro May 16 '18 at 17:49
  • No, the edit button will go to a view with a form for editing the item. Clicking on the row will go to a view that just displays the details of the item. – Greg Hess Jr May 16 '18 at 17:51
  • And what i meant is to move the link to the ID or Name element.causing a hyperlink on the text, you can still have hover effects on the element. – pool pro May 16 '18 at 17:51
  • Good point, I will just make the text clickable if I can't get it to work the original way I intended. – Greg Hess Jr May 16 '18 at 17:53
  • Can you share the whole table code. Wondering if we can use indexing here – pool pro May 16 '18 at 17:54
  • Absolutely, here is a [pastebin link](https://pastebin.com/vCv4500D) I'll add this link to the end of my question. Let me know if you need anything else. – Greg Hess Jr May 16 '18 at 17:56
  • Move the ,script tag below the button, and replace .btn.btn-danger with .btn-danger – pool pro May 16 '18 at 18:07
  • Just tried it but the page still navigates away with the modal only popping up for a second :(... Regardless of whether or not it's ever gonna work I wanted to say I genuinely appreciate the time and effort you put into helping me. – Greg Hess Jr May 16 '18 at 18:16
  • what version of jQuery are you using? – pool pro May 17 '18 at 04:21
  • Ok i have this working with 2 buttons per row. I had used jQuery 2.2.4 as i had issues with 3.3.1. Also had issues with having the script in the html. So it is installed in a ls file. here is the code link. https://codesandbox.io/s/r5m9w5ryvn – pool pro May 17 '18 at 04:45
  • I don't see any code in your demo other than what looks like default scaffolding for that platform... but to answer your previous question I am using jQuery 3.3.1 – Greg Hess Jr May 17 '18 at 13:28
  • Sorry, never saved, just redid it with jQuery 3.3.1, https://codesandbox.io/s/52kjq29jzl – pool pro May 18 '18 at 00:41
  • This works in chrome I now realize my code does too... but my users heavily rely on edge any ideas? – Greg Hess Jr May 18 '18 at 00:53
  • You can not use the online editors to view this in EDGE as it will not allow the iframe, i have run the same example built in Visual Studio and works in all browsers. – pool pro May 18 '18 at 01:31
  • I have also updated the HTML, so just build the files the same way in notepad++ and open the html with EDGE. – pool pro May 18 '18 at 01:32