Ajax post in MVC 3 with multiple-form View
Asked Answered
D

1

2

I deleted my prior question dealing with the subject as the context has changed a bit and the problem is new.

I have a single view now, with a single model. The view has 2 forms. Each form submits to its own action, each action uses a subset of the model data, uses the data to perform a search. The search results then are added to a property of the model and need to be rendered back to the view. I'm able to accomplish this all via a standard html post but this now needs to be an ajax post.

Here is my ViewModel:

public sealed class SearchUsersViewModel {

    [Display(Name = "Last Name")]
    public string LastName { get; set; }

    [Display(Name = "Username")]
    public string Username { get; set; }

    [Display(Name = "Email Address")]
    public string EmailAddress { get; set; }

    [Display(Name = "Entity Type")]
    public byte EntityTypeID { get; set; }

    [Display(Name = "Search Name")]
    public string SearchField { get; set; }

    public IEnumerable<SelectListDTO> EntityTypes { get; set; }

    public IEnumerable<UserEditViewModel> Users { get; set; }

    [Display(Name = "Total Rows")]
    public int TotalRowCount { get; internal set; }

    public SearchUsersViewModel() {
        EntityTypes = LookupEntityTypeService.Instance.SelectList;
        Users = new List<UserEditViewModel>();
    }
}

Here is my View:

@model SearchUsersViewModel

<div>
    <div class="SearchByUserDataTable">
        @using (Html.BeginForm("FilterByUserData", "Admin", FormMethod.Post, new {model = Model})) {
            @Html.ValidationSummary(true)
            <table cellpadding="0" cellspacing="0" border="0">
                <tr style="height: 30px;">
                    <td class="Header01">
                        User Search:
                    </td>
                </tr>
            </table>
            <table cellpadding="0" cellspacing="0" border="0">
                <tr style="height: 20px;">
                    <td class="Header02">
                        Search By User Information:
                    </td>
                </tr>
            </table>
            <table cellpadding="0" cellspacing="0" border="0">
                <tr style="height: 1px;">
                    <td class="ContentDividerHoriz_425"></td>
                </tr>
            </table>
            <table id="searchByUserDataTable" cellpadding="0" cellspacing="0" border="0">
                <tr style="height: 26px;">
                    <td class="leftColumn">
                        @Html.LabelFor(model => model.LastName):
                    </td>
                    <td class="rightColumn">
                        @Html.TextBoxFor(model => model.LastName, new { id = "lastNameSearchField", @class = "TextField_220" })
                    </td>
                </tr>
                <tr style="height: 26px;">
                    <td class="leftColumn">
                        @Html.LabelFor(model => model.Username):
                    </td>
                    <td class="rightColumn">
                        @Html.TextBoxFor(model => model.Username, new { id = "userNameSearchField", @class = "TextField_220" })
                    </td>
                </tr>
                <tr style="height: 26px;">
                    <td class="leftColumn">
                        @Html.LabelFor(model => model.EmailAddress):
                    </td>
                    <td class="rightColumn">
                        @Html.TextBoxFor(model => model.EmailAddress, new { id = "emailAddressSearchField", @class = "TextField_220" })
                    </td>
                </tr>
            </table>
            <table cellpadding="0" cellspacing="0" border="0">
                <tr>
                    <td id="filterByUserError" style="width: 375px;"></td>
                    <td align="right" style="width: 50px; padding-right: 75px;">
                        <div>
                            <input id="filterByUserButton" type="submit" value="Search" />
                        </div>
                    </td>
                </tr>
            </table>
        }
    </div>

    <div class="SearchByEntityDataTable">
        @using (Html.BeginForm("FilterByEntityData", "Admin", FormMethod.Post, new { model = Model })) { 
            <table cellpadding="0" cellspacing="0" border="0">
                <tr style="height: 28px;">
                    <td style="width: 425px;"></td>
                </tr>
            </table>
            <table cellpadding="0" cellspacing="0" border="0">
                <tr style="height: 20px;">
                    <td class="Header02">
                        Search By Entity Information:
                    </td>
                </tr>
            </table>
            <table cellpadding="0" cellspacing="0" border="0">
                <tr style="height: 1px;">
                    <td class="ContentDividerHoriz_425"></td>
                </tr>
            </table>
            <table id="searchByEntityDataTable" cellpadding="0" cellspacing="0" border="0">
                <tr style="height: 26px;">
                    <td class="leftColumn">
                        @Html.LabelFor(model => model.EntityTypeID):
                    </td>
                    <td class="rightColumn">
                        @Html.DropDownListFor(model => model.EntityTypeID, new SelectList(Model.EntityTypes, "ID", "Name"), new { id = "entityTypeDropDown", @class = "DropDown_220" })
                    </td>
                </tr>
                <tr style="height: 26px;">
                    <td class="leftColumn">
                        @Html.LabelFor(model => model.SearchField, new { id = "entityTypeSearchLabel"}):
                    </td>
                    <td class="rightColumn">
                        @Html.TextBoxFor(model => model.SearchField, new { id = "entityTypeSearchField", @class = "ui-widget TextField_220" })
                    </td>
                </tr>
                <tr style="height: 26px;">
                    <td class="leftColumn"></td>
                    <td class="rightColumn"></td>
                </tr>
            </table>
            <table cellpadding="0" cellspacing="0" border="0">
                <tr>
                    <td id="filterByEntityError" style="width: 375px;"></td>
                    <td align="right" style="width: 50px; padding-right: 75px;">
                        <div>
                            <input type="submit" value="Search" />
                        </div>
                    </td>
                </tr>
            </table>
        }
    </div>

    <div id="searchResults">
        @(Html.Telerik().Grid(Model.Users)
            .Name("Users").TableHtmlAttributes(new { style = "width: 870px;"})
            .Columns(columns => {
                columns.Bound(o => o.EntityTypeName).Title("Entity Type");
                columns.Bound(o => o.FirstName).Title("First Name");
                columns.Bound(o => o.LastName).Title("Last Name");
                columns.Bound(o => o.Username).Title("Username");
                columns.Template(
                    @<text>
                        <a href="mailto:@item.EmailAddress" target="blank">@item.EmailAddress</a>
                    </text>).Title("Email").HtmlAttributes(new { style = "text-align: center" }).HeaderHtmlAttributes(new { style = "text-align: center" });
                columns.Template(
                    @<text>
                        <div class="ActionIcon_ResendInvitationOn" title="Resend Invitation" onclick="location.href='@Url.Action("ResendInvitation", "Admin", new { entityTypeID = item.EntityTypeID, emailAddress = item.EmailAddress })'"></div> 
                        @{
                            if (item.IsApproved) {
                                <div class="ActionIcon_AccountStatusOn" title="Disable Account" onclick="setApprovalStatus('@item.Username', false);"></div> 
                            }
                            else {
                                <div class="ActionIcon_AccountStatusOn" title="Enable Account" onclick="setApprovalStatus('@item.Username', true);"></div> 
                            }
                        }
                        @{
                            if (item.IsLockedOut) {
                                <div class="ActionIcon_ResetPasswordOn" title="Unlock Account" onclick="unlockAccount('@item.Username')"></div> 
                            }
                            <div class="ActionIcon_ResetPasswordOn" title="Reset Password"  onclick="resetPassword('@item.Username')"></div> 
                        }
                        <div class="ActionIcon_EditOn" title="Edit User" onclick="location.href='@Url.Action("Edit", "Admin", new { id = item.MembershipID, username = item.Username })'"></div>
                    </text>).Title("Actions");
                columns.Bound(o => o.RowNumber).Hidden(true);
                columns.Bound(o => o.MembershipID).Hidden(true);
                columns.Bound(o => o.EntityTypeID).Hidden(true);
            })
            .Pageable(paging => paging.PageSize(10))
            .Sortable()
        )
        <span>Total Rows: </span> @Html.DisplayFor(model => Model.TotalRowCount)

        <p>
            @Html.ActionLink("Create New User", "Create", "Invitation")
        </p>
    </div>
</div>

I have used this view with this line as the BeginForm:

@using (Html.BeginForm("FilterByUserData", "Admin", FormMethod.Post, new {model = Model})) 

And using this:

 @using (Html.BeginForm())

with this Ajax script

$('#filterByUserButton').click(function () {
    $.ajax({
        url: '/Admin/FilterByUserData',
        type: 'POST',
        success: function (result) {
            alert(result);
        }
    });
});

With the first method using the HTML post it works fine but I need to use the ajax functionality so the 2nd method is my trouble

1) Using the ajax call the model being posted to the action contains all Null values in the text fields instead of the values I'm entering on the form. The model posts fine using the HTML method

2) Even if I can get the model to post correctly so that I have values to search with, how do I return the data to the grid and how do I show model errors when the validation fails instead of sending the result to the UpdateTargetID?

3) Being able to have both forms submit to the controller and have the same results back to the view, a search result

It seems I can only have 1 UpdateTarget - which is fine if you never have errors...but the behavior is bad, I need to show the validation errors on the field just the same as if this was a synchronous call. So the error result coming from the action would need to have it's own target.

I came up with this Ajax call that works fine with actions that don't need to return data, just a success or error message. Each gets loaded into its own div.

$(function () {
    $('form').submit(function () {
        if ($(this).valid()) {
            $.ajax({
                url: this.action,
                type: this.method,
                data: $(this).serialize(),
                success: function (result) {
                    if (result.toString().indexOf("Success") == -1) {
                        $('#successDiv').hide();
                        $('#errorDiv').html('');
                        $('#errorDiv').fadeIn(100).append($('<ul />').append(result));
                    }
                    else {
                        $('#errorDiv').hide();
                        $('#successDiv').fadeIn(1000).html(result).fadeOut(6000);
                    }
                }
            });
        }
        return false;
    });
});

These are the actions that process the search (The FilterByUserData action is different as I was trying to work out how to get a proper result back, I don't yet have it returning data properly.

    public ActionResult Search() {
        var model = new SearchUsersViewModel();
        return View(model);
    }

    [HttpPost]
    public ActionResult FilterByUserData(SearchUsersViewModel model) {
        var result = string.Empty;
        if (model.LastName != null || model.Username != null || model.EmailAddress != null) {
            var listOfMatchingUsers = SearchUserService.SearchByUserData(model.LastName, model.Username, model.EmailAddress);
            model = PrepareResultsModel(listOfMatchingUsers, model);
        }
        else {
            ModelState.AddModelError("", "Last Name, Username or Email Address must be entered for search");
        }
        if (ModelState.IsValid)
            result = "Success: Thanks for your submission: " + model.Username;
        else {
            result = ModelState.SelectMany(item => item.Value.Errors).Aggregate(result, (current, error) => current + error.ErrorMessage);
        }
        return Content(result, "text/html");
    }

    [HttpPost]
    public ActionResult FilterByEntityData(SearchUsersViewModel model) {
        if(model.EntityTypeID > 0 &&  model.SearchField != null) {
            var listOfMatchingUsers = SearchUserService.SearchByEntityData(model.EntityTypeID, model.SearchField);
            model = PrepareResultsModel(listOfMatchingUsers, model);
        }
        else {
            var entityType = string.Empty;
            if(model.EntityTypeID == 2) entityType = "Lender";
            if (model.EntityTypeID == 3) entityType = "School";
            ModelState.AddModelError("", entityType + " name must be entered for search");
        }
        return View("Search", model);
    }

    private SearchUsersViewModel PrepareResultsModel(ICollection<SearchUserResultsDTO> listOfMatchingUsers, SearchUsersViewModel model) {
        if (listOfMatchingUsers.Count != 0) {
            model.Users = listOfMatchingUsers.Select(item => new UserEditViewModel(item)).ToList();
            model.TotalRowCount = model.Users.Count();
        }
        return model;
    }

How can I get the model for the ajax post to make it to my action? and how do I get a proper model back so that my grid has the results? and if there is an error how can I get that shown in a similar manner to validationsummary?

Update: for @Shyju I tried what you suggested trying to incorporate your example into my code but the values of the properties of the model are still null and are not being set. This is what I did in my ajax function, note that it is different than your form.post method:

$('#filterByUserButton').click(function () {
    $.ajax({
        url: '/Admin/FilterByUserData',
        type: 'POST',
        data: {LastName : $('#LastName').val(), Username : $('#Username').val(), EmailAddress : $('#EmailAddress').val()},
        success: function (result) {
            processResult(result);
        }
    });
});

I also tried this:

$('form').submit(function() {
    alert("submitting");
    $.post('@Url.Action("FilterByUserData","Admin")', { LastName: $("#LastName").val(), UserName: $("#Username").val(), EmailAddress: $("#EmailAddress").val() },
        function(data) {
            alert(data);
    });
});

The page flashes and the first alert is it but it never hits the 2nd alert and never hits the controller so it looks like it's not even truly posting. I also tried this but it also does not hit the controller action:

$('#filterByUserButton').click(function () {
    alert("submitting");
    $.ajax({
        url: '/Admin/FilterByUserData',
        type: 'POST',
        data: {LastName : $('#LastName').val(), Username : $('#Username').val(), EmailAddress : $('#EmailAddress').val()},
        success: function (result) {
            alert(result);
            processResult(result);
        }
    });
});

I also tried the above with the data: line omitted and it will hit my controller action but again, the model being sent has null values in the properties. The model isn't null, just the property values. I do have values in the dropdown list properties of the model, the EntityTypes still show 4 values. So there's something about posting that form but the form values are not making it into the model properties.

Update 2

@Shyju I'm using your ajax code like so:

$(function () {
    $('#SearchByUserForm').submit(function () {
        $.post('@Url.Action("FilterByUserData", "Admin")', { LastName: $("#LastName").val(), UserName: $("#UserName").val(), EmailAddress: $("#EmailAddress").val() },
        function (data) {
            alert(data);
        });
        return false;
    });
});

I can step thru this after hitting the submit button but it fails to hit the post action of my controller. I looked at the HTML rendered for the form and this is what I see.. which is incorrect:

<form action="/Admin/Search/SearchByUserForm" method="post">         

As you can see the POST action here is wrong. The Controller is correct but the rest is wrong. It should be posting to /Admin/FilterByUserData. I'm sure this is just a syntactical mess that I'm in and I'm just not sure what I'm doing wrong. In a standard form I would do this:

@using (Html.BeginForm("FilterByUserData", "Admin", FormMethod.Post, new { model = Model })) { 

Which works correctly. But I can't combine this form signature with the URL action post from your Ajax code. I tried just leaving the form stark with only the ID but I get the problem I stated above:

@using (Html.BeginForm(new {@id = "SearchByUserForm"})) {

I think this problem comes about from the fact that my view is rendered by hitting the Search action of my controller, so the URL is /Admin/Search but the forms on the view need to post to /Admin/FilterByUserData and /Admin/FilterByEntityData. Like I said above if I explicitly create the Html.BeginFrom with the right signature then it works,, but it's just not Ajax

Update 3

@Shyju

I got it working, the problem was definitely the Form signature, I modified it and I can hit my controller action successfully and also the data is being passed in! Thanks for all your help!

This is what got it working:

@using (Html.BeginForm(new {id = "SearchByUserForm", @controller = "Admin", @action = "FilterByUserData"})) {
Dryad answered 3/4, 2012 at 19:5 Comment(0)
S
2

Use the same name as of your property name (of your viewmodel which is the parameter of your action method) when you pass data in your jquery post. you will get the object filled with data there.

('form').submit(function () {
    $.post('@Url.Action("Logon","Account")', { LastName : $("#username").val(), 
                     UserName:   $("#password").val() },
                     EmailAddress:   $("#password").val() },  function (data) {

    //process the result and update the grid

});

EDIT : Since the OP said, it is not working, I created a sample project from scratch to verify this and it is working fine. Here is my View looks like

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>

<h2>Index</h2>
<form id="frm1" action="Search">
LastName <input type="text" id="LastName" /><br />
UserName <input type="text" id="UserName" /><br />
Email Address <input type="text" id="EmailAddress" />
<input type="submit" value="go" />
</form>
<script type="text/javascript">
    $(function () {
        $('#frm1').submit(function () {
            $.post('@Url.Action("Search", "Home")', { LastName: $("#LastName").val(),
                UserName: $("#UserName").val(),
                EmailAddress: $("#EmailAddress").val()
            }, function (data) {

                alert(data)
            });
            return false;
        });
    });
</script>

And my action method which receives the post call is

public ActionResult Search(SearchUsersViewModel objVM)
{
   return View();
}

And here is the screenshots of results

Client

enter image description here

Controller Action method

enter image description here

You can even send the form to the controller action using the jquery serialize() method as well.

http://api.jquery.com/serialize/

Simoniac answered 3/4, 2012 at 19:31 Comment(10)
I tried to incorporate your suggestion but it does not work, see my update at the end of the questionDryad
Thanks for taking the time to help and expand on your answer. unfortunately it's still not working for me. My view and forms and routes to post to are a bit more complex than your example. I updated my question at the end incorporating your code and explained why it's failing for me.Dryad
the only thing I don't get is that it looks like the whole page is doing a refresh, the whole screen does the flicker/flash during the submit and re-renders with the updated screen. This doesn't feel ajaxy. But that's because the return on the action is return View() instead of Content or some sort of JSON result. So it looks like getting a clean asynchronous post and response isn't quite there yetDryad
@CDSmith: You can return a view with a table where you have your search results in that. If the page is reloading that means you are not stopping the page to do further form submission. Did you do a return false ?Simoniac
I think I see what you mean, maybe my results should be a partial view and hold the grid portion of my view? If so should that partial have a model defined within it? I'm not sure how to accomplish that one, unfamiliar territory. Where should I return false at? Also, despite that the code works... the alert(data) function never gets called ... looks like that piece never gets hit.Dryad
@CDSmith You can return a partial view from your action method. Make that view as a strongly typed view which is ties to a viewmodel to represent your search result ( Ex: SearchResultViewmodel). you can load the data (response from the ajax call to a div by $("#yourdivId").html(data), instead of alerting.Simoniac
I'm tyring that right now and ... it's acting weird.. 1) it's running the controller action twice and now it's not submitting my data fields, null again. And the return now renders the partial on a new page with no formatting... I've seen this problem before. Just can't recall how I fixed it. I'm going to create a new question with the new problem :-)Dryad
@CDSmith: I think there is some other problem in your page. without seeing it i can not tell what it is. Probably , you should try to d isolate your problem and get it fixed.Simoniac
Yes definetely agree! Thanks for your help. I think it will work once I figure out why data isn't being submitted any longer. I'll do another search thru SO on partials not rendering correctly and isolate my data issue. If I need to start another question if not finding an answer after that I will.Dryad
let us continue this discussion in chatDryad

© 2022 - 2024 — McMap. All rights reserved.