I have a Core 2 MVC web app which I am trying to add some Ajax into.
I have a Create view which loads a standard web form. What makes this a bit different to what I have learned/done so far is that I would like to add some semi-dynamic content to the page.
The ViewModel for the page is a bunch of simple properties, but part of it is also a nested collection.
I am trying to dynamically add values into the nested collection via a modal, and then reload the partial view to reflect what I have added without causing a page reload - I only want to reload the partial view and close the Modal.
I can see the JS is actioning the request and firing the call to the controller and getting a 200 response but it is not closing the modal or reloading the partial view with the data from the Modal.
There is something I am definitely not getting here. Can anyone advise?
ViewModel
public class RaceViewModel
{
public Race RaceData { get; set; }
public ObservableCollection<CurrencyDetails> Currencies { get; set; }
public CurrencyDetails BaseCurrency { get; set; }
public CurrencyDetails RaceCurrency { get; set; }
public ObservableCollection<RaceOptions> RaceOptionData { get; set; }
[DisplayFormat(DataFormatString = "{0:0.00")]
public decimal BaseFee { get; set; }
}
Main view (Irrelevant sections omitted)
#using TechsportiseOnline.Helpers
#model TechsportiseOnline.ViewModels.RaceViewModel
#{
ViewData["Title"] = "Create";
}
<h2>Create a new race</h2>
<div class="modal fade bd-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true" id="optionsmodal">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h2 class="modal-title">New Race Option</h2>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label value="Name" class="control-label">Option Name</label>
<br /><small class="text-muted">The name of this Race Option</small>
<input name="Name" placeholder="Your 10k" type="text" class="form-control" aria-label="Name">
#*<span asp-validation-for="RaceData.Name" class="text-danger"></span>*#
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label name="RaceDistance" value="Race Distance" class="control-label">Distance</label>
<br /><small class="text-muted">Choose a race distance, used for Age Grading </small>
<select name="RaceDistance" class="form-control">
<option value="" selected>--select--</option>
<option value="M1">1 mile</option>
<option value="KM5">5 km</option>
<option value="KM6">6 km</option>
<option value="M4">4 miles</option>
<option value="KM8">8 km</option>
<option value="M5">5 miles</option>
<option value="KM10">10 km</option>
<option value="KM12">12 km</option>
<option value="KM15">15 km</option>
<option value="M10">10 miles</option>
<option value="KM20">20 km</option>
<option value="Half">Half Marathon</option>
<option value="KM25">25 km</option>
<option value="KM30">30 km</option>
<option value="Marathon">Marathon</option>
<option value="KM50">50 km</option>
<option value="M50">50 miles</option>
<option value="KM100">100 km</option>
<option value="KM150">150 km</option>
<option value="M100">100 miles</option>
<option value="KM200">200 km</option>
<option value="Other">Other</option>
</select><br />
#*<span asp-validation-for="RaceData.RaceDistance" class="text-danger"></span>*#
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label name="MaxEntries" value="Maximum Entries" class="control-label">Maximum Entries</label>
<br /><small class="text-muted">The maximum capacity of the race</small>
<input name="MaxEntries" class="form-control" />
#*<span asp-validation-for="RaceData.MaxEntries" class="text-danger"></span>*#
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label name="Start Time" value="Start Time" class="control-label">Race Start Time</label>
<br /><small class="text-muted">Start time in HH:MM</small>
<input name="Start Time" value="19:00" asp-format="{0:hh:mm}" class="form-control" type="time" />
#*<span asp-validation-for="RaceData.RaceStartTime" class="text-danger"></span>*#
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label value="Entry Price" class="control-label">Entry Price</label>
<br /><small class="text-muted">The price of the normal race entry</small>
<input name="Entry Price" type="text" class="form-control" aria-label="Amount" placeholder="10.00" asp-format="{0:0.00}">
#*<span asp-validation-for="RaceData.EntryPrice" class="text-danger"></span>*#
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label class="control-label">Affiliation Discount</label>
<br /><small class="text-muted">Value of discount for being an affiliated runner</small>
<input name="AffiliatedDiscountValue" type="text" class="form-control" aria-label="Amount" value="2.00" asp-format="{0:0.00}">
#*<span asp-validation-for="AffiliatedDiscountValue" class="text-danger"></span>*#
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="addoption" >Add</button>
</div>
</div>
</div>
</div>
<form asp-action="Create" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="panel panel-default">
<div class="panel-heading">Race Options</div>
<div class="panel-body">
<div id="container">
Create your different Race Options here, if you want to have more than one distance/race in the same event. You must have at least 1 Race Option.
<div id="dvRaceOptionsResults">
#{Html.RenderPartial("RaceOptions", Model);}
</div>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target=".bd-example-modal-lg">Add Race Option</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script type="text/javascript">
$("#addoption").click(function(){
$.ajax({
url: '#Url.Action("GetRaceOptions", "Races")',
type: 'POST',
success: function(data) {
if (data) { // check if data is defined
$("#dvRaceOptionsResults").html(data);
}
}
});
});
</script>
}
Partial View
#model TechsportiseOnline.ViewModels.RaceViewModel
<table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
#if (Model.RaceOptionData != null)
{
foreach (var option in Model.RaceOptionData)
{
<tr>
<td>#option.Name</td>
</tr>
}
}
</tbody>
</table>
Controller Method
[HttpPost]
public async Task<IActionResult> GetRaceOptions(RaceViewModel race)
{
var viewModel = race;
return PartialView("RaceOptions", viewModel);
}
This is your VideModel right?
public class RaceViewModel
{
...
}
From what I understand, your AJAX request is getting routed to method GetRaceOptions which receives a RaceViewModel parameter. Unfortunately, your AJAX POST Request is not sending the required RaceViewModel object. You have to pass it in the data body.
Make sure your JS data object has the same property name as the ViewModel so Model Binding will work properly.
Example:
data: {BaseFee: 842350, property2: 'abc'}; //and so on
$.ajax({
url: '#Url.Action("GetRaceOptions", "Races")',
type: 'POST',
data: data,
dataType : 'html', //expect html back
success: function(data) {
if (data) { // check if data is defined
$("#dvRaceOptionsResults").html(data);
}
}
You are calling ajax without data: parameter. So you are not posting data to your controller. Than controller is trying to return the partial view with empty viewModel.
If you want to save newly added options you will need to do something like this.
create data object and send it as data parameter in ajax.
modify the controller to save new options and return old + new options
[HttpPost]
public async Task<IActionResult> GetRaceOptions(RaceViewModel race)
{
//save options from RaceViewModel
//get old options + newly added options
var viewModel = old + newly added data
return PartialView("RaceOptions", viewModel);
}
You need to close modal in the success part of ajax
success: function(data) {
if (data) { // check if data is defined
$("#dvRaceOptionsResults").html(data);
$('#modalId').modal('hide');
}
Related
Maybe I'm looking at this the wrong way, maybe the fact that the browser is showing the view as a pop up doesn't change the fact that for MVC this is just a different View with it's own ViewModel. Since this is a form inside a form in a way, maybe there are better ways to do the following;
The initial view (CreateTimeslot.cshtml) is a form with properties of a workshop to be scheduled. name, description,.., And a speaker multiselect list:
#model MDFrontend.Models.Timeslots.TimeslotManagementViewModel
<div class="form-group">
<label asp-for="StartTime" class="control-label"></label>
<input asp-for="StartTime" type="datetime" class="form-control" value="#startTime" />
<span asp-validation-for="StartTime" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="EndTime" class="control-label"></label>
<input asp-for="EndTime" type="datetime" class="form-control" value="#startTime.Add(Model.WorkshopDuration)" readonly />
<span asp-validation-for="EndTime" class="text-danger"></span>
</div>
<!--------------------------------------------------------------SpeakerBox---------------------------------------------------------------------->
<div class="form-group">
<label asp-for="Speakers" class="control-label"></label>
<select asp-for="SpeakerIDs" id="speakerBox" name="SpeakerIDs[]" asp-items="#Model.Speakers" multiple="multiple">
</select>
<span asp-validation-for="SpeakerIDs" class="text-danger"></span> <!--This is not showing, asp-validation-summary does-->
</div>
<button onclick="showFormInPopup('#Url.Action("CreateSpeaker","SpeakerAdmin",Model,Context.Request.Scheme)',
'New Speaker')" type="button" id="btnAddSpeaker" class="btn btn-primary"> #Localizer["Add_Speaker"]
</button>
I'm passing the TimeslotManagementViewModel in the Url.Action method, which I'm mapping to the CreateSpeakerViewModel
public class CreateSpeakerViewModel
{
public string Firstname { get; set; }
public string Lastname { get; set; }
public TimeslotManagementViewModel oldModel { get; set; }
}
This view is shown as a popup:
<!-------------------------------------------Hidden Add Speaker Popup--------------------------------------------------->
<div class="modal" tabindex="-1" role="dialog" id="form-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="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">
</div>
</div>
</div>
</div>
<!-------------------------------------------Hidden Add Speaker Popup--------------------------------------------------->
The body of this CreateSpeaker View:
#model MDFrontend.Models.Speakers.CreateSpeakerViewModel
<form asp-action="CreateSpeaker" asp-controller="SpeakerAdmin">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Firstname" class="control-label"></label>
<input asp-for="Firstname" class="form-control" />
<span asp-validation-for="Firstname" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Lastname" class="control-label"></label>
<input asp-for="Lastname" class="form-control" />
<span asp-validation-for="Lastname" class="text-danger"></span>
</div>
<div class="form-group" >
<input type="submit" value="Save" asp-action="CreateSpeaker" asp-controller="SpeakerAdmin"
formmethod="post" class="btn btn-primary" />
</div>
</form>
The CreateSpeaker Post method is receiving this simple form, adding them to the db, redirecting to the initial CreateTimeslot view with an updated multiselect.
[HttpPost]
[Authorize(Roles = RoleConstant.Admin)]
public async Task<IActionResult> CreateSpeaker(CreateSpeakerViewModel vm,
TimeslotManagementViewModel model)
{
//The idea would be to have TimeslotManagementViewModel model posted back with the set values by the user before "AddSpeaker" was clicked.
}
However with this solution the previous set inputs are lost to the user.
So atm I'm thinking either to use map every property of the TimeslotManagementViewModel to the CreateSpeakerViewModel to pass them between controllers or maybe an Ajax call could work or drop the popup all togheter. Or better yet someone here knows a better option?
Many thanks!
After some trail and error realized the best, maybe only solution comes by using some simple Javascript.
function SaveSpeaker(e) {
var firstname = $('#txtFirstname').val();
var lastname = $('#txtLastname').val();
$.ajax({
url: '/SpeakerAdmin/CreateSpeaker',
dataType: 'Json',
type: "POST",
data: { firstname: firstname, lastname: lastname },
success: function (data, status, jqXHR) {
console.log('success');
if (data.valFirstname == false) {
$('#valFirstname').text('Firstname not complete');
}
if (data.valLastname == false) {
$('#valLastname').val('Lastname not complete')
}
if (data.valSpeaker == true) {
$('#form-modal').modal('hide');
$('#valSpeaker').text('Speaker added');
$('#speakerBox').append(new Option(firstname + " " + lastname, data.speakerID))
}
},
error: function (jqXHR, status, err) {
},
complete: function (jqXHR, status) {
}
});
So instead of posting the form just using an Ajax call to post to the controller and return Json to populate the new select option.
because I need to create my user info in many different tables with many different Models and views I used this code in the address page but as shown in my remarks I lost the user info in the post function My question is why and what to do????? by the way when I copied the User1 difenation from my OnGet function to the OnPost function this code work perfectly as explained in my comment but I still want to understand why a public property lose the information please read my comments
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using RazorPagesUI.Models;
namespace RazorPagesUI.Pages.Forms
{
partial class AddAddressModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public AddAddressModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
[BindProperty(SupportsGet = true)]
public string Mail { get; set; }
public IEnumerable<SelectListItem>? Country { get; set; }
[BindProperty]
public AddressModel? Address { get; set; }
public string SelectedString { get; set; }
public UserModel User1 { get; set; }=new UserModel();
public void OnGet()
{
List<string> TagIds = Mail.Split(',').ToList();
Int32.TryParse(TagIds[0], out int j);
User1.Id = j;
User1.Email = TagIds[1];
User1.FirstName = TagIds[2];
User1.LastName = TagIds[3];
User1.Password = TagIds[4]
Country = new SelectListItem[]
{
new SelectListItem ("Canada", "Canada"),
new SelectListItem ("Egypt", "Egypt"),
new SelectListItem ( "Usa", "Usa")
};
}
public IActionResult OnPost()
{
//when I get to here User1 is null
Address.Country = Request.Form["country"];
if (ModelState.IsValid == false)
{
return Page();
}
//I need to insert my user info to my user table but User1 is null
//here I insert Address info
return RedirectToPage("/index", new{ Name = User1.Firstname);//User1
becomes Null
}
}
}
cshtml file As asked to include in my post
#page
#using RazorPagesUI.Models
#model RazorPagesUI.Pages.Forms.AddAddressModel
#{
ViewData["Title"] = "Add Address";
}
<b>Adderres for: #Model.User1.FirstName #Model.User1.LastName</b>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
<div class="text-center">
<h1>Add Address</h1>
</div>
<form method="post">
<div class="container-fluid">
<div class="p-1">
<div class="text-center">
<select name = "country" asp-items="#Model.Country">
</select>
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.State" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.City"
/>
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.StreetNumber"
placeholder="Street #" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.StreetName"
placeholder="Street Name" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.AppNumber"
placeholder="App#" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.ZipCode" />
</div>
</div>
<div class="p-1">
<div class="text-center">
<input type="tel" asp-for="Address.Phone" />
</div>
</div>
<div class="p-1">
<div class="text-center">
<input type="tel" asp-for="Address.CellPhone" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<button type="submit">Submit</button>
</div>
</div>
</div>
</form>
Firstly,you need to pass User1.FirstName when form post,so that you can get User1.FirstNamein OnPost handler.
form(add hidden input with User1.FirstName):
<form method="post">
<div class="container-fluid">
<div class="p-1">
<div class="text-center">
<select name = "country" asp-items="#Model.Country">
</select>
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.State" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.City"
/>
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.StreetNumber"
placeholder="Street #" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.StreetName"
placeholder="Street Name" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.AppNumber"
placeholder="App#" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="text" asp-for="Address.ZipCode" />
</div>
</div>
<div class="p-1">
<div class="text-center">
<input type="tel" asp-for="Address.Phone" />
</div>
</div>
<div class="p-1">
<div class="text-center">
<input type="tel" asp-for="Address.CellPhone" />
</div>
</div>
<div class="text-center">
<div class="p-1">
<input type="hidden" asp-for="User1.FirstName" />
<button type="submit">Submit</button>
</div>
</div>
</div>
</form>
cshtml.cs(If you want to bind the data to User1,you need to use [BindProperty],so that you can use User1.Firstname in OnPost handler):
[BindProperty]
public UserModel User1 { get; set; } = new UserModel();
You have to show your cshtml file i.e. the front end of the Razor page for a more clear description of your problem. But in general, I'm seeing that you are trying to bind a property called Country of a complex object called Address of type AddressModel In this case the name of the input/select in your cshtml file should reflect the complex path to the target Country property of the Address object. It should be something like this <select name="Address.Country" asp-items="Model.Country"></select> Notice the name of the select element Address.Country i.e. it reflects the full path to the target property. More information on complex model binding in razor pages here https://www.learnrazorpages.com/razor-pages/model-binding If you manage to bind the property of the complex object correctly this line of code Address.Country = Request.Form["country"]; becomes redundant. The value of Address.Country should be populated automatically.
I'll only include the relevant properties of the model and the related lines for simplicity:
Model: Department.cs
Property: public virtual Staff HOD { get; set; }
ControllerMethod:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Add([Bind] Department newDepartment)
{
await _departmentRepository.AddDepartmentAsync(newDepartment);
await _departmentRepository.SaveTransactionAsync(true);
return Redirect("Details" + "/" + newDepartment.Id);
}
View:
#model Department
#inject IStaffRepository staffRepository
#{
// select list
var listOfEmployees = new List<Staff>();
foreach (var staff in await staffRepository.GetAllStaffAsync())
{
listOfEmployees.Add(staff);
}
var selectListHOD = new SelectList(listOfEmployees, "EmployeeId", "Name"); //populates the option element with the correct Employee Id
}
<form class="form-group" method="post">
<div class="d-flex flex-column">
<div class="input-group" style="margin-bottom: 1%">
#Html.DropDownListFor(department => department.HOD, selectListHOD, htmlAttributes: new { #class="form-control" })
</div>
<div class="input-group">
<input class="btn btn-primary" type="submit" value="Save" />
</div>
</div>
</form>
Result:
(I have not included the other properties seen here in the question, but trust me, the properties of the parameter to the controller get correctly populated with them, except for the HOD property)
Source:
<form class="form-group" method="post">
<div class="d-flex flex-column">
<div class="input-group" style="margin-bottom: 1%">
<input class="form-control" id="Id" name="Id" placeholder="Id" type="text" value="" /> </div>
<div class="input-group" style="margin-bottom: 1%">
<input class="form-control" id="Name" name="Name" placeholder="Department" type="text" value="" /> </div>
<div class="input-group" style="margin-bottom: 1%">
<input class="form-control" id="Description" name="Description" placeholder="Description" type="text" value="" /> </div>
<div class="input-group" style="margin-bottom: 1%">
<select class="form-control" id="HOD" name="HOD">
<option value="EMP01">Employee 1</option>
<option value="EMP02">Employee 2</option>
</select>
</div>
<div class="input-group">
<input class="btn btn-primary" type="submit" value="Save" /> </div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8AIwSWkSO0hJqMxy-oQKoeG35LTDmI2N1XHDZL9qeaxRxc17TyZbT2z9Iq0GMPkRyE7HnaX1r4ZSIs0bQATYB7w_A_HZDBXGETMmpdSqlMXZCmf7cH9ECzrNGz0Wuu9zHkE50yI92vPY-GxNPG-pRhs" />
</form>
My guess is that the value being supplied to the controller seems to be wrong. How would I pass the actual object instead of the ID?
I tried using this instead of EmployeeId, but it throws Object Reference not found exception.
You cannot bind an object with <select></select>.You can try to bind Hod.EmployeeId with <select></select>.And add a hidden input to bind Hod.Name.When selected value changes,change the value of hidden input.Here is a demo:
<form class="form-group" method="post">
<div class="d-flex flex-column">
<div class="input-group" style="margin-bottom: 1%">
#Html.DropDownListFor(department => department.HOD.EmployeeId, selectListHOD, htmlAttributes: new { #class = "form-control",#onChange= "AddName()" })
<input hidden asp-for="HOD.Name" />
</div>
<div class="input-group">
<input class="btn btn-primary" type="submit" value="Save" />
</div>
</div>
</form>
#section scripts
{
<script>
$(function () {
AddName();
})
function AddName() {
$("#HOD_Name").val($("#HOD_EmployeeId option:selected").text());
}
</script>
}
result:
Code searchView and PartialResultView
SearchView
#model Shared.Model.Search.GLSearch
#{
ViewData["Title"] = "Search GL";
}
<!-- start page title -->
<div class="row">
<div class="col-12">
<div class="page-title-box">
<div class="page-title-right">
<ol class="breadcrumb m-0">
<li class="breadcrumb-item">UBold</li>
<li class="breadcrumb-item">Forms</li>
<li class="breadcrumb-item active">Elements</li>
</ol>
</div>
<h4 class="page-title">Search Customer</h4>
</div>
</div>
</div>
<!-- end page title -->
<form asp-action="" asp-controller="" method="post">
<div class="row">
<div class="col-lg-12">
<div class="card-box">
<div class="form-row">
<div class="form-group col-md-2">
<label asp-for="Name" class="col-form-label"></label>
<input asp-for="Name" type="text" class="form-control" />
</div>
<div class="form-group col-md-2">
<label asp-for="Code" class="col-form-label"></label>
<input asp-for="Code" type="text" class="form-control" />
</div>
<div class="form-group col-md-3">
<label asp-for="GLSectionId" class="col-form-label">Section </label>
<select asp-for="GLSectionId" asp-items="#(new SelectList(Model.glSections,"Id","Name"))" class="form-control">
<option value="">Choose</option>
</select>
</div>
<div class="form-group col-md-3">
<label asp-for="GLGroupId" class="col-form-label">Group</label>
<select asp-for="GLGroupId" asp-items="#(new SelectList(Model.glGroups,"Id","Name"))" class="form-control">
<option value="">Choose</option>
</select>
</div>
<button type="button" id="search" class="btn btn-primary waves-effect waves-light">Search</button>
</div>
</div> <!-- end card-box -->
</div> <!-- end col -->
</div> <!-- end row -->
</form>
<div id="view-all"></div>
Search_PartiaView
#model PagedResult<Shared.Model.Masters.GLMaster.GLViewModel>
#{
}
#if (Model == null || Model.RowCount == 0)
{
<p>No results found</p>
}
else
{
<div class="col-lg-12">
<div class="card-box">
<h4 class="header-title">Customers</h4>
<p class="sub-header">
</p>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th data-priority="1">#</th>
<th data-priority="3">Name</th>
<th data-priority="6">Code</th>
<th data-priority="6">Section</th>
<th data-priority="6">Group</th>
<th data-priority="6">
<a onclick="showInPopup('#Url.Action("AddOrEditGL","GLMaster",new {area = "Masters"},Context.Request.Scheme)','New GL')" class="btn btn-success text-white"><i class="fas fa-random"></i> New GL</a>
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Results)
{
<tr>
<th scope="row">#item.Id</th>
<td>#item.Name</td>
<td>#item.Code</td>
<td>#item.GLSection</td>
<td>#item.GLGroup</td>
<td>
<div>
<a onclick="showInPopup('#Url.Action("AddOrEditGL","GLMaster",new { area= "Masters",id = item.Id},Context.Request.Scheme)','Update GL')" class="btn btn-info text-white"><i class="fas fa-pencil-alt"></i> Edit</a>
<form asp-area="Masters" asp-action="DeleteGL" asp-route-id="#item.Id" onsubmit="return jQueryAjaxDelete(this)" class="d-inline">
<input type="submit" value="Delete" class="btn btn-danger" />
</form>
</div>
</td>
</tr>
}
</tbody>
</table>
</div> <!-- end table-responsive-->
</div> <!-- end card-box -->
</div> <!-- end col -->
<!-- Responsive Table js -->
}
Partial View (AddEditGL)
#model Shared.Model.Masters.GLMaster.GLModel
#{
Layout = null;
ViewData["Title"] = "Add Customer";
}
<div class="row">
<div class="col-lg-12">
<div class="card-box">
<form asp-action="AddOrEditGL" asp-controller="GLMaster" asp-area="Masters" asp-route-id="#Model.Id" onsubmit="return jQueryAjaxPost(this);">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="#Model.Id" />
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" type="text" class="form-control">
<span asp-validation-for="Name" class="text-danger"></span>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label asp-for="NameLang" class="control-label"></label>
<input asp-for="NameLang" type="text" class="form-control">
<span asp-validation-for="NameLang" class="text-danger"></span>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-6 offset-md-3">
<input type="submit" value="Submit" class="btn btn-primary btn-block" />
</div>
</div>
</form>
</div> <!-- end card-box -->
</div> <!-- end col -->
</div> <!-- end row -->
I have View with Partial view (is for results in table) . When i click Edit button in Search_PartiaView
i need to open popup (Partial View (AddEditGL))
and data should be loaded ajax and submit the button after update.. I need to use jquery unobtrusive validation in popup and also without refresh the page ..Please let me know hw to do..Thanks
EDIT
I Have implemented similar to this Ajax crud popup
I Have Main view and Partial view. Also AddOrEdit View for Add/edit Master.
My Current solution works.. But inmy main view i have filter based on 2 filds.
After add/edit grid load all the result but if filter applied i also need to filter the grid ..
My Javascript code Here:
jQueryAjaxPost = form => {
try {
$.ajax({
type: 'POST',
url: form.action,
data: new FormData(form),
contentType: false,
processData: false,
success: function (res) {
if (res.isValid) {
$('#view-all').html(res.html) --- here actually data coming all without filter
$('#form-modal .modal-body.p-4').html('');
$('#form-modal .modal-title').html('');
$('#form-modal').modal('hide');
showAll(4, 1); --- it is the javascript fuction call to call the
api again
}
else
$('#form-modal .modal-body.p-4').html(res.html);
},
error: function (err) {
console.log(err)
}
})
//to prevent default form submit event
return false;
} catch (ex) {
console.log(ex)
}
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddOrEditGL(int id,GLModel glModel)
{
if (ModelState.IsValid)
{
var mappedGL = _mapper.Map<GLDTO>(glModel);
//Insert
if (id == 0)
{
await _glService.CreateGL(mappedGL);
}
//Update
else
{
await _glService.UpdateGL(mappedGL);
//Call Update
}
// How do i filter the based on Main view form controls
return Json(new { isValid = true, html = Helper.RenderRazorViewToString(this, "_GLViewAll", null) });
}
return Json(new { isValid = false, html = Helper.RenderRazorViewToString(this, "AddOrEditGL", glModel) });
}
my Current solution call the api again ( 2 server calls) one for update and another for call update table .. i need to do the same in single call ..Please help to do?
Note: I dont need complete solution , I only need to how to get the AddOrEditGL Controller post method Main view form control text fieds text to filter in DB
If you want to update/add and show the searched data in one request,The quick way is to copy the SearchGLPartial code to the AddOrEditGL function and pass the pageSize,pageIndex,name,code,GLSectionId and GlGroupId by ajax:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddOrEditGL(int id,GLModel glModel,string name,string code,string GLSectionId,string GlGroupId...)
{
if (ModelState.IsValid)
{
var mappedGL = _mapper.Map<GLDTO>(glModel);
//Insert or
//Update
//copy the SearchGLPartial code here and return view with data
}
return Json(new { isValid = false, html = Helper.RenderRazorViewToString(this, "AddOrEditGL", glModel) });
}
If you just do not want to remove showViewAll() jquery,I think you could set session for the filter condition when you first search the data in SearchGLPartial action.Then in your AddOrEdit action,get the session and set the correct url.Finally,you could get the url in ajax success response:
public IActionResult SearchGLPartial(string name,string code,string GLSectionId,string GLGroupId)
{
HttpContext.Session.SetString("name", name);
HttpContext.Session.SetString("code", code);
HttpContext.Session.SetString("GLSectionId", GLSectionId);
HttpContext.Session.SetString("GLGroupId", GLGroupId);
var data = Gllist.Where(a => a.Name.Contains(name) & a.Code.Contains(code)).FirstOrDefault();//filter by yourself
return PartialView("_Search", data);
}
AddOrEdit:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddOrEditGL(int id,GLModel glModel)
{
if (ModelState.IsValid)
{
//Insert
//Update
var name = HttpContext.Session.GetString("name");
//other session data...
return Json(new { isValid = true, url="Home/Index?name="+name+"&code="+xxx+"&...."});
}
return Json(new { isValid = false, html = Helper.RenderRazorViewToString(this, "AddOrEditGL", glModel) });
}
Then your ajax:
success: function (res) {
if (res.isValid) {
window.location.href = response.url;
}
else
$('#form-modal .modal-body.p-4').html(res.html);
},
I recommend using the jQuery Unobtrusive AJAX library. Its very easy to use:
It can fetch partial views and place them in a container of your choice using the
data-ajax-update="#panel"
Click here
<div id="panel"></div>
This is an example of a controller action that would return the modal:
public IActionResult GetEditModal() => Partial("ViewName");
Then when the modal is placed in your container, using the data-ajax-success attribute a callback method is called, parse the form using the jQuery Unobtrusive Validation like this:
function SuccessCallback() {
//You can also use the keyword "this" instead of getting the form with jquery
$.validator.unobtrusive.parse($(this));
//or
$.validator.unobtrusive.parse($form);
}
Very useful resources : Link - jQuery Unobtrusive AJAX - GitHub
Update
What i did in previous projects is to include an empty edit modal (a modal with an empty body) then using the library and the data-ajax-update i would replace the body of that modal every time the user pressed the edit button on a table item like this:
<div class="modal fade" id="eModal" data-keyboard="false" data-backdrop="static">
<div class="modal-dialog modal-lg">
<div class="modal-content id="modalContentE">
<div class="modal-header">
<h4 class="modal-title"><i class="nav-icon fas fa-edit"></i> Edit Data</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true"><i class="fas fa-times-circle text-white"></i></span>
</button>
</div>
//New data goes here
</div>
</div>
</div>
And your add Modal would be a regular full modal and each time the user use it to submit data, clear the form.
I am creating a loop of objects/forms that will enable a user to update data for each object in a partial view. The unique data properly displays in the loop of forms but when I try to submit to the action, the model data that passes to the action is blank and doesn't pull the data from the form I'm submitting from.
In debugging, object data is successfully passed to each var item in the loop.
#foreach (var item in ViewData["CtaList"] as
IEnumerable)
Ex. There are 5 individual forms that are created by the loop each with their own Save and Delete button. If I click Save on the 3rd form in the loop, it goes to the action but the MedInfoModel model for that individual object is blank.
View
#{
ViewData["Title"] = "Edit";
ViewData["hidePluginCSS"] = "yes";
#model POR.Common.MedInfoModel;
}
#foreach (var item in ViewData["CtaList"] as IEnumerable<POR.Common.CtaListModel>)
{
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Edit CTA</h3>
</div>
<div id="partialPlaceHolder">
#using (Html.BeginForm("SaveCtaInfo/" + item.CtaId, "Med", FormMethod.Post, new { #Id = item.CtaId, #class = "content-form form-horizontal" }))
{
<div class="panel-body">
<form class="content-form form-horizontal">
<div class="form-group justify-content-end">
<label for="inputEmail" autocomplete="false" class="col-md-9 control-label">Select Type</label>
#Html.DropDownListFor(m => item.CtaType, Model.CTATypeDropdown, new { #class = "form-control"})
</div>
<div class="form-group justify-content-end">
<label for="inputEmail" autocomplete="false" class="col-md-9 control-label">Select Priority</label>
#Html.DropDownListFor(m => item.CtaOrder, Model.CTAOrderDropdown, new { #class = "form-control" })
</div>
<div class="row mt-2">
<div class="col-md-12 r">
<button class="btn btn-raised btn-danger r" name="ctaSave" value="ctaDelete">Delete</button>
<button class="btn btn-raised btn-success r" name="ctaSave" value="ctaSave">Save</button>
</div>
</div>
</form>
</div>
}
</div>
</div>
}
Controller:
[HttpPost]
public ActionResult SaveCtaInfo(MedInfoModel model, int id)
{
string _sSubmit = Request.Form["ctaSave"].ToString();
if (_sSubmit == "ctaSave")
{
// Code for cta save
model.CtaActionType = "UPDATE";
Helper.SQLSPCrudModel(ConnectionString, "storedProcedure", model);
}
else if (_sSubmit == "ctaDelete")
{
// code for cta delete
model.CtaActionType = "DELETE";
Helper.SQLSPCrudModel(ConnectionString, "storedProcedure", model);
}
return RedirectToAction("Edit/" + id);
}
Rendered Form HTML:
<form action="/Med/SaveCtaInfo%2F1000" class="content-form form-horizontal" id="1000" method="post"> <div class="panel-body">
<div class="form-group justify-content-end">
<label for="inputEmail" autocomplete="false" class="col-md-9 control-label">Select Type</label>
<select class="form-control" id="item_0__CtaType" name="item[0].CtaType"><option value="PDF">PDF</option>
<option selected="selected" value="Call">Call</option>
<option value="Menu">Menu</option>
<option value="Video">Video</option>
</select>
</div>
<div class="form-group justify-content-end">
<label for="inputEmail" autocomplete="false" class="col-md-9 control-label">Select Priority</label>
<select class="form-control" data-val="true" data-val-required="The CtaOrder field is required." id="item_0__CtaOrder" name="item[0].CtaOrder"><option value="0">0</option>
<option selected="selected" value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
</select>
</div>
<div class="row mt-2">
<div class="col-md-12 r">
<button class="btn btn-raised btn-danger r" name="ctaSave" value="ctaDelete">Delete</button>
<button class="btn btn-raised btn-success r" name="ctaSave" value="ctaSave">Save<div class="ripple-container"></div></button>
</div>
</div>
</div></form>
EDIT:
I just noticed however that the form data is passing through via chrome dev tools as:
item[0].CtaType: Call
item[0].CtaOrder: 1
item[0].CtaLink:
Where in a succesfull passing of the model in another action is passing through as:
CtaType: Call
CtaOrder: 1
CtaLink:
Does the action need to be set up differently since it is no longer the parent model of the page?
The answer was to simply replace the following in the controller in original code I posted:
public ActionResult SaveCtaInfo(MedInfoModel model, int id)
with
public ActionResult SaveCtaInfo(MedInfoModel item, int id)
Thanks for your help mxmissile. Sorry for wasting your time.