2

I have this script in my view file, the purpose of it is to populate a section of the same view, but only a small section of it, which is a HTML div with Bootstrap panel classes:

<script type="text/javascript">
    function GetVehicles() {      
                     $.get('@Context.Request.Scheme://@hostname/@controller/GetVehicles', {id: @Model.Id}, function (response) {
                                            $("#tabOutVehicles").html(response);
                                        });
                                    }

function GetMedInfo() {
    $.get('@Context.Request.Scheme://@hostname/@controller/GetMedInfo', {id: @Model.Id}, function (response) {
           $("#tabOutMedInfo").html(response);

    });
}
</script>

My complete view, which will display the output generated by the script above:

    @{
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
    <script src="~/js/site.js"></script>

        <div class="panel panel-primary">
                                <div class="panel-heading">
                                    <a href="#collapseVehicles" class="btn student-dash-btn" onclick="GetVehicles()" data-toggle="collapse">Vehicles</a>
                                </div>

                                <div id="collapseVehicles" class="panel-collapse collapse">
                                    <div id="tabOutVehicles">
                                    </div>
                                </div>
                            </div>

                            <div class="panel panel-primary">
                                <div class="panel-heading">
                                    <a href="#collapseMedInfo" class="btn student-dash-btn" onclick="GetMedInfo()" data-toggle="collapse"><strong style="color:white">Medical Info</strong></a>
                                </div>

                                <div id="collapseMedInfo" class="panel-collapse collapse">
                                    <div id="tabOutMedInfo">
                                    </div>
                                </div>
                            </div>

<script type="text/javascript">
        function GetVehicles() {      
                         $.get('@Context.Request.Scheme://@hostname/@controller/GetVehicles', {id: @Model.Id}, function (response) {
                                                $("#tabOutVehicles").html(response);
                                            });
                                        }

    function GetMedInfo() {
        $.get('@Context.Request.Scheme://@hostname/@controller/GetMedInfo', {id: @Model.Id}, function (response) {
               $("#tabOutMedInfo").html(response);

        });
    }
    </script>

By clicking on the hyperlinks inside the Bootstrap panel divs, the jQuery method hits my controller action, returns the response, and then puts the response in the applicable div (#tabOutMedInfo / #tabOutVehicles). Both these actions return partial views. Here is what my partial view looks like, both look the same except for the model properties that differ:

@model MyViewModel
    <div class="panel-body">
        <a data-url="@Url.Action("EditMedInfo", "Controller", new { id = Model.Id })" data-toggle="ajax-modal" data-target="#EditMedInfo" class="btn btn-default">Edit</a>

        <div class="form-horizontal">
            <div class="">
                <div class="form-group">
                    <div class="col-md-5 col-sm-5 col-xs-5"><label asp-for="Variable" class="control-label"></label></div>
                    <div class="col-md-7 col-sm-7 col-xs-7">
                        @Html.DisplayFor(item => item.Variable)
                    </div>
                </div>
            </div>
        </div>
    </div>

When the above hyperlink is clicked, it is supposed to execute JavaScript code that loads a modal for editing, which is not happening, instead it does not open the modal. The JavaScript code is located in my site.js file, which is being imported in my main view.

What I've tried:
I moved the script import tag to both my partial views and then removed it from my view, it then causes the JavaScript code to run and display my modal, but this solution only worked for a while.

New problem:
Then a new problem started occurring, if the first partial view is loaded, and you were to load the second partial view without closing the web page, it causes site.js to be loaded twice into the view, once by the first partial view, and a second time by the second partial view. With site.js loaded twice, it somehow causes my post action to be hit twice, resulting in data being inserted twice for one post action.

I then decided to move site.js to my _Layout.cshtml (ideally how it should be) and tried again, this way around caused the partial views to render like normal, but once again, the modals didn't show when clicking on the hyperlinks found in the partial views.

My theory:
The way I understand it, when the jQuery get methods loads the partial views, it prevents the partial view from seeing site.js, even though it was loaded in by my _Layout.cshtml.

What I preferrably don't want to change:
I don't want to get rid of my small get actions, I built it like this to keep my users as much as possible on one page, and calling the get actions seperately reduces their data usage.

Am I loading site.js correctly? Why doesn't my partial views see site.js?

Edit:
This is my _Layout.cshtml

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <environment names="Development">
        @*Other scripts omitted *@
        <script src="~/js/site.js" asp-append-version="true" defer></script>
    </environment>

    <environment names="Staging,Production">
        @*Other scripts omitted *@
        <script src="~/js/site.js" asp-append-version="true" defer></script>
    </environment>
</head>
<body>
    <div id="modal-placeholder"></div>
    @RenderBody()
    @RenderSection("scripts", required: false)
</body>
</html>

And here is the JavaScript code located inside site.js:

$(function () {
    var placeholderElement = $('#modal-placeholder');
    var redirectUrl = "";

    $('a[data-toggle="ajax-modal"]').click(function (event) {
        var url = $(this).data('url');
        redirectUrl = window.location.href;

        $.get(url).done(function (data) {
            placeholderElement.html(data);
            placeholderElement.find('.modal').modal('show');
        });
    });

    placeholderElement.on('click', '[data-save="modal"]', function (event) {
        event.preventDefault();

        var form = $(this).parents('.modal').find('form');
        var actionUrl = form.attr('action');
        var dataToSend = form.serialize();

        $.post(actionUrl, dataToSend).done(function (data) {
            var newBody = $('.modal-body', data);
            placeholderElement.find('.modal-body').replaceWith(newBody);
            var isValid = newBody.find('[name="IsValid"]').val() == 'True';
            if (isValid) {
               placeholderElement.find('.modal').modal('hide');
               window.location.assign(redirectUrl);
               }
            }
        });
    });
});

The JavaScript was written like above to handle server side validation on modals, see the brilliant article here: https://softdevpractice.com/blog/asp-net-core-mvc-ajax-modals/

Jacques
  • 25
  • 7
  • 1
    _When the above hyperlinked is clicked, it is supposed to execute JavaScript code that loads a modal for editing, which is not happening._ What happens instead? What is this JavaScript code? Where is the `#EditMedInfo` modal? – Jasen Sep 19 '18 at 17:45
  • @Jasen, Hi Jasen, please see my edit. – Jacques Sep 19 '18 at 19:15
  • Instead of unbinding/rebinding, you can use [event delegation](http://api.jquery.com/on/). See https://stackoverflow.com/a/40262524/2030565 – Jasen Sep 19 '18 at 20:39
  • Are there cons for unbinding/rebinding? And is that not what is happening in site.js where it says `placeholderElement.on('click', '[data-save="modal"]', function (event) { ... }` – Jacques Sep 19 '18 at 21:16
  • @KamilFolwarczny most likely, that's why I changed the title to keep it in a category and as close as possible to other questions. – Jacques Sep 21 '18 at 10:16

1 Answers1

3

When you 'paste' the HTML for the panel into the page once returned from the ajax call, it breaks all existing event handlers. So you need to re-bind the handler anchor elements. I would wrap your event handler inside a function, and call that function both on initial page load and also in the ajax success handler (after you've pasted the HTML). Assuming you're using jQuery, it would look something like this:

function bindAjaxModalButtons() {
    $('[data-toggle="ajax-modal"]').click(function() {
        // ... your event handler code here
    });
}
$(function() {
    bindAjaxModalButtons(); // attaches event handlers on initial page load
});

Then change your ajax functions like so:

function GetMedInfo() {
    $.get('@Context.Request.Scheme://@hostname/@controller/GetMedInfo', {id: @Model.Id}, function (response) {
        $("#tabOutMedInfo").html(response);
        bindAjaxModalButtons(); // <-- this will attach the event handler to the new buttons    
    });
}

EDIT: now that I can see your JS file, here is what it should look like:

$(function () {
    var placeholderElement = $('#modal-placeholder');
    var redirectUrl = "";

    bindAjaxModalButtons();

    placeholderElement.on('click', '[data-save="modal"]', function (event) {
        event.preventDefault();

        var form = $(this).parents('.modal').find('form');
        var actionUrl = form.attr('action');
        var dataToSend = form.serialize();

        $.post(actionUrl, dataToSend).done(function (data) {
            var newBody = $('.modal-body', data);
            placeholderElement.find('.modal-body').replaceWith(newBody);
            var isValid = newBody.find('[name="IsValid"]').val() == 'True';
            if (isValid) {
               placeholderElement.find('.modal').modal('hide');
               window.location.assign(redirectUrl);
               }
            }
        });
    });
});

function bindAjaxModalButtons() {
    var btns = $('a[data-toggle="ajax-modal"]');
    btns.unbind(); // <-- so that existing buttons don't get double-bound
    btns.click(function (event) {
        var url = $(this).data('url');
        redirectUrl = window.location.href;
        var placeholderElement = $('#modal-placeholder');
        $.get(url).done(function (data) {
            placeholderElement.html(data);
            placeholderElement.find('.modal').modal('show');
        });
    });
}
akerra
  • 427
  • 3
  • 16
  • please see my edit. – Jacques Sep 19 '18 at 19:15
  • The problem still is the same. You are wiring up the event handlers on the document.ready event, which does not fire when a partial view is inserted into the DOM. You need to wire up the handler again after you manipulate the DOM via javascript. I will modify my answer to match your code. – akerra Sep 19 '18 at 19:20
  • This is the correct answer. Do you think it's necessary to change the title of the question or is it clear enough? – Jacques Sep 19 '18 at 20:01
  • It may be worth revising the title to something like 'JavaScript event handlers with dynamically loaded PartialViews', since the root cause of you issue is not really related to how you load the script file. – akerra Sep 20 '18 at 18:27
  • Perfect, the title has been changed. – Jacques Sep 20 '18 at 20:58