I have a table on which there's an Edit button for every record at the last column.
My goal is to have an editable form on a modal for the record on which the user pressed the Edit button.
In order to accomplish that, I've created a Partial View which i want to be loaded on the modal, but after tons of tries, i cannot get it working. The JS created to compose the partial view URL and loading into the modal seems to have no effect and it's raising the following error:
VM364 ESa31501901:361 Uncaught ReferenceError: ESa31501901 is not
defined
at HTMLAnchorElement.onclick
Note: ESa31501901 is the first parameter passed into the JS function.
This is my intention:
a) Edit() : This method will return all records.
b) EditClientFeature(string ClientID, string WorkProcessID): This method will return a partial view containing the record of a particular client. This method is called when we start editing a client record. The client record is displayed in modal (popup).
c) EditClientFeature(ClientFeatureViewModel model): This method will update the client record.
ClientFeature ViewModel
public class ClientFeatureViewModel
{
public string ClientID { get; set; }
public string WorkProcessID { get; set; }
public int? Certification { get; set; }
public bool? TrackingActive { get; set; }
public string ClientCode { get; set; }
public string ContractNo { get; set; }
public string ProductCode { get; set; }
}
Edit.cshtml
[...]
<tbody>
#foreach (var feature in Model.ClientFeatures)
{
<tr>
<td style="text-align:center"><strong>#feature.WorkProcessId</strong>/td>
<td style="text-align:center">#feature.Certificate</td>
<td style="text-align:center">#feature.TrackingActive</td>
<td style="text-align:center">#feature.ClientCode</td>
<td style="text-align:center">#feature.ContractNo</td>
<td style="text-align:center">#feature.ProductCode</td>
<td>
<i class="glyphicon glyphicon-pencil"></i>
</td>
</tr>
}
</tbody>
[...]
<div class="modal fade" id="ModalClientFeatures">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
×
<h3 class="modal-title">Edit</h3>
</div>
<div class="modal-body" id="ModalBodyDiv">
<!-- Here's where i want to show the partial view-->
</div>
</div>
</div>
</div>
[...]
<script>
function EditCF(ClientID, WorkProcessID) {
var url = "/Admin/EditClientFeature?ClientID=" + ClientID + "?WorkProcessID=" + WorkProcessID;
$("#ModalBodyDiv").load(url, function () {
$("#ModalClientFeatures").modal("show");
})
}
</script>
ClientFeaturepartialView.cshtml
#model Project.Models.ClientFeatureViewModel
<script src="~/Scripts/jquery-3.3.1.js"></script>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<form id="myForm">
<div class="form-horizontal">
<h4>ClientFeatureViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ClientID)
<div class="form-group">
#Html.LabelFor(model => model.WorkProcessID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.WorkProcessID, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.WorkProcessID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Certification, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Certification, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Certification, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TrackingActive, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
#Html.EditorFor(model => model.TrackingActive)
#Html.ValidationMessageFor(model => model.TrackingActive, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ClientCode, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ClientCode, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ClientCode, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ContractNo, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ContractNo, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ContractNo, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ProductCode, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProductCode, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProductCode, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
<a href="#" id="btnSubmit" class="btn btn-success btn-block">
<span>Update</span>
</a>
</div>
</div>
</div>
</form>
}
Controller
public ActionResult EditClientFeature(string ClientID, string WorkProcessID)
{
PMEntities db = new EntityConn().Db;
ClientFeatures ClientFeature = db.ClientFeatures.Where(cf => cf.ClientId == ClientID && cf.WorkProcessId == WorkProcessID).SingleOrDefault();
if (ClientFeature != null) {
ClientFeatureViewModel model = new ClientFeatureViewModel
{
ClientID = ClientFeature.ClientId,
WorkProcessID = ClientFeature.WorkProcessId,
Certification = ClientFeature.Certificate,
TrackingActive = ClientFeature.TrackingActive,
ClientCode = ClientFeature.ClientCode,
ContractNo = ClientFeature.ContractNo,
ProductCode = ClientFeature.ProductCode,
};
return PartialView("ClientFeaturePartialView", model);
}
else { return View("Error"); }
}
[HttpPost]
public ActionResult EditClientFeature(ClientFeatureViewModel model)
{
try
{
PMEntities db = new EntityConn().Db;
if (model.ClientID != null)
{
//update
ClientFeatures ClientFeature = db.ClientFeatures.Where(cf => cf.ClientId == model.ClientID && cf.WorkProcessId == model.WorkProcessID).SingleOrDefault();
ClientFeature.Certificate = model.Certification;
ClientFeature.ClientCode = model.ClientCode;
ClientFeature.ContractNo = model.ContractNo;
ClientFeature.ProductCode = model.ProductCode;
ClientFeature.TrackingActive = model.TrackingActive;
db.SaveChanges();
}
}
catch (Exception ex)
{
throw ex;
}
}
I think your onclick function parameter value is treated as a variable, not a string.
Solution 1:
Try below to pass as string: (Not tested though!)
onclick="EditCF(\'' + #Model.Piva + '\', \'' + #feature.WorkProcessId+ '\')"
Solution 2:
It's best to Attach a click handler after adding a class for your link. And use HTML5 data attributes for storing your value client side.
<i class="glyphicon glyphicon-pencil"></i>
$('.editClient).on('click', function() {
var clientID = $(this).data('ClientID');
var workProcessId = $(this).data('WorkProcessId');
var url = "/Admin/EditClientFeature?ClientID=" + clientID
+ "?WorkProcessID=" + workProcessID;
$("#ModalBodyDiv").load(url, function () {
$("#ModalClientFeatures").modal("show");
})
});
Reference
Hope this helps.
Related
I am trying to populate/fill a dropdown but I can't seem to get the values I need.
The "Employee Name" dropdown does not get populated while the "Platform Group" one does.
For "Employee Name" I use POST because this method is called by another VIEW in the software so I'd rather not change it.
I tried everything I could but the values will not show in the console or in the UI.
What am I missing?
Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace FXM.BO.ViewModels
{
public class NewAffiliateViewModel
{
public NewAffiliateViewModel()
{
}
public int Id { get; set; }
public string AffiliateName { get; set; }
public string Email { get; set; }
public int Employee { get; set; }
public int MT4Group { get; set; }
}
}
View
#model FXM.BO.ViewModels.NewAffiliateViewModel
#{
ViewBag.Title = "CreateAffiliate";
Layout = "~/Views/Shared/_LoggedInLayout.cshtml";
}
<h2>Create Affiliate</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4 class="hidden">NewAffiliateViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.AffiliateName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.AffiliateName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.AffiliateName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Email, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Email, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Email, "", new { #class = "text-danger" })
</div>
</div>
#*<div class="form-group">
#Html.LabelFor(model => model.Employee, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Employee, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Employee, "", new { #class = "text-danger" })
</div>
</div>*#
#*<div class="form-group">
#Html.LabelFor(model => model.MT4Group, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.MT4Group, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.MT4Group, "", new { #class = "text-danger" })
</div>
</div>*#
<div class="form-group">
<label class="control-label col-md-2">
#FXM.BO.Strings.T("employeeName") <span class="required">
#***#
</span>
</label>
#*<div class="col-md-4 fieldPositionCol4 select2-bootstrap-prepend">*#
<div class="col-md-10 ">
#Html.DropDownListFor(x => x.Employee, new SelectList(new Dictionary<string, string>(), "Key", "Value"), #FXM.BO.Strings.T("ddl_select_option"), new { #class = "form-control select2-allow-clear", #placeholder = "Select Symbols" })
<span class="help-block">
#FXM.BO.Strings.T("lbl_employee_description")
</span>
</div>
<img id="tpLoader" src="~/Content/images/ajax-loader.gif" class="hidden" />
</div>
<div class="form-group">
<label class="control-label col-md-2">
#FXM.BO.Strings.T("lbl_Bo_TradAcc_PlatformGroup") <span class="required">
#***#
</span>
</label>
#*<div class="col-md-4 fieldPositionCol4 select2-bootstrap-prepend">*#
<div class="col-md-10 ">
#Html.DropDownListFor(x => x.MT4Group, new SelectList(new Dictionary<string, string>(), "Key", "Value"), #FXM.BO.Strings.T("ddl_select_option2"), new { #class = "form-control select2-allow-clear", #placeholder = "Select Symbols" })
<span class="help-block">
#FXM.BO.Strings.T("platform_group_description")
</span>
</div>
<img id="tpLoader" src="~/Content/images/ajax-loader.gif" class="hidden" />
</div>
#*Create button*#
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="button" value="Create" class="btn btn-default" id="create-affiliate-button" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
<script type="text/javascript" src="/Scripts/CustomScripts/Common/form-helpers.js"></script>
#*<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>*#
<script type="text/javascript">
// document.ready function
$(function () {
refreshGroups();
// selector has to be . for a class name and # for an ID
$('#create-affiliate-button').click(function (e) {
//e.preventDefault(); // prevent form from reloading page
console.log("blahblahblah");
var b = $("form").serialize();
//var a = $("form").serializeArray();
console.log("formvalues", b);
$.ajax({
url: "/en/AjaxUI/CreateAffiliate",
type: "GET",
dataType: "json",
data: b,
},
//error: function (jqXHR, exception) {
// failMessage();
//}
});
});
});
function refreshGroups() {
var pltf = "MT4_LIVE";
var out = $('#MT4Group');
if (pltf != null && pltf !== "") {
$.ajax({
url: '/' + window.currentLangCulture + '/BOLiveForms/GetPlatformGroupByFilter',
data: {
platform: pltf, currency: "", withId : true
},
type: 'GET',
beforeSend: function () {
$('#tpLoader').show();
},
complete: function () {
$('#tpLoader').hide();
},
success: function (data) {
populateDropDown(out, data);
//$('#recomandedGroup').show();
}
});
} else {
out.empty();
out.append($('<option></option>').val('').html(window.defaultSelection));
}
}
//GetEmployeesExcept - Method that populates the Dropdown for EmployeeName
$(function() {
$.validator.addMethod("regxPhone", function(value, element, regexpr) {
return regexpr.test(value);
}),
$.validator.addMethod("regxEmail", function(value) {
return /^([\w!.%+\-])+##([\w\-])+(?:\.[\w\-]+)+$/.test(value);
}),
$.validator.addMethod('validEmail', function(value, element, param) {
return valiEmailAjax($("#Email").val());
});
$('#create-affiliate-button').select2({
theme: "bootstrap",
ajax: {
url: "/en/BOEmployeeAjax/GetEmployeesExcept",
type: "POST",
dataType: "json",
data: function(params) {
return {
emplId: 0,
t: params.term
}
},
processResults: function(data, params) {
return { results: data }
}
},
placeholder: window.selectAnotherSupervisor,
escapeMarkup: function(markup) { return markup; },
minimumInputLength: 0
});
</script>
Method in the Controller (for Employee Name)
[HttpPost]
[OutputCache(CacheProfile = "Cache10mn", Location = OutputCacheLocation.Client, NoStore = true)]
public JsonResult GetEmployeesExcept(int emplId, string t, bool searchByUserId=false)
{
t = string.IsNullOrWhiteSpace(t) ? String.Empty : t;
t = string.IsNullOrWhiteSpace(t) ? String.Empty : t;
if (!searchByUserId)
{
return Json(CrmServices.GetAllEmployees()
.Where(w => w.Id != emplId && string.Format("{1}{0}", w.UserDetails.Fullname, w.Id).ToLower().Contains(t.ToLower()))
.Select(s => new { id = s.Id, text = string.Format("{1} : {0}", s.UserDetails.Fullname, s.Id) }));
}
else
{
return Json(CrmServices.GetAllEmployees()
.Where(w => w.UserDetails != null && int.Parse(w.UserDetails.UserID) != emplId && string.Format("{1}{0}", w.UserDetails.Fullname, w.UserDetails.UserID).ToLower().Contains(t.ToLower()))
.Select(s => new { id = s.UserDetails.UserID, text = string.Format("{1} : {0}", s.UserDetails.Fullname, s.UserDetails.UserID) }));
}
}
method in the Controller (for "Platform Group")
public JsonResult CreateAffiliate(NewAffiliateViewModel newAffiliateViewModel)
{
try
{
var res = BackOfficeServices.BoAddAffiliateUser(newAffiliateViewModel);
//SystemServices.ResendEmail(userId);
return Json("success");
//return Json(null, JsonRequestBehavior.DenyGet);
}
catch (Exception e)
{
throw e;
}
}
Also, when I uncomment the code for the ajax request for "Employee Name", the "Platform group" stops being populated. When I comment it back, the "Platform group" is populated just fine.
Thank you for any response.
You need to search for : "Select2"
Here is site https://select2.org/
Read the guid, its easy
UPDATE 1:
Here is EXAMPLE of code, that you need, man.
I have a view in one of my controller which have list of items. When user clicks each item, browser must navigate to page which brings details about this item.
Controller for Details accepts values through post method and have complex input object as its input.
Here is sample method for to navigate to details page using GET method to send values:
function openDetailsPage(commodityID, commodityName) {
dateFrom = convertNumbers2English('#(((FilterViewModel)ViewBag.ViewModel).dateValue_1)');
dateTo = convertNumbers2English('#(((FilterViewModel)ViewBag.ViewModel).dateValue_2)');
dateFrom = changeDateSeparator(dateFrom);
dateTo = changeDateSeparator(dateTo);
if (dateTo == null || dateTo == undefined)
dateTo = "0";
if (dateFrom == null || dateFrom == undefined)
dateFrom = "0";
#{
string reportTypes = "0";
if (((FilterViewModel)ViewBag.ViewModel).purchaseReportTypes != null)
{
reportTypes = String.Join(",", ((FilterViewModel)ViewBag.ViewModel).purchaseReportTypes);
}
}
alert('#reportTypes');
var url = '#Url.Action("ReportDetailed","BuyReport",new {
commodityType =(((FilterViewModel)ViewBag.ViewModel).commodityType),
commodityName="dummyCommodityName",
department=((FilterViewModel)ViewBag.ViewModel).department,
repository=((FilterViewModel)ViewBag.ViewModel).repository,
commodity ="dummyCommodityID",
purchaseReportTypes=reportTypes,
dateValue_1="dummyDate1",
dateValue_2="dummyDate2"
})';
alert(url);
#*var url = '#Url.Action("ReportDetailed","BuyReport",
new RouteValueDictionary
{
{"commodityType",((FilterViewModel)ViewBag.ViewModel).commodityType},
{"commodityName","dummyCommodityName" },
{"department",((FilterViewModel)ViewBag.ViewModel).department },
{"repository",((FilterViewModel)ViewBag.ViewModel).repository },
{"commodity","dummyCommodityID"},
{"purchaseReportTypes",((FilterViewModel)ViewBag.ViewModel).purchaseReportTypes },
{"dateValue_1",((FilterViewModel)ViewBag.ViewModel).dateValue_1 },
{ "dateValue_2",((FilterViewModel)ViewBag.ViewModel).dateValue_2 }
})';*#
url = url.replace("dummyCommodityID", commodityID);
url = url.replace("dummyCommodityName", commodityName);
url = url.replace("dummyDate1", dateFrom);
url = url.replace("dummyDate2", dateTo);
alert(url);
openLink(url);
}
I have some difficulties with this type of routing for values:
Input object is complex so route would be so complex. E.g. /BuyReport/ReportDetailed?commodityType=0&commodityName=dummyCommodityName&department=1&repository=2&commodity=dummyCommodityID&dateValue_1=dummyDate1&dateValue_2=dummyDate2 or /BuyReport/ReportDetailed/0/itemName/1/2/1/123/
Any special characters in get parameters such as / will break routing
I cannot pass stuff like arrays so I should convert them before sending
So I'm looking for a method to send parameters using 'Post' method like what form submit button does with below constraints:
I have no forms in my view
I want to post values to controller and page must navigate to details view
Each item in first page, have different row and different ID so I think creating a form for each row is not reasonable.
I want to know are there any ways to implement Post parameters according to my requirements? I would not care if it would be a mixture of C#, JS and jQuery.
More Details:
Here is a sample row in my list page which calls openDetailsPage js function:
<a onclick="openDetailsPage(#item.CommodityId,'#Html.DisplayFor(modelItem => item.CommodityName)')">
<div class="ios-content-box px-4 py-1 mb-3 ios-hover-box">
<div class="row font-12 my-2 ios-divider-line">
<div class="col-6 f-w-600 color-orange text-right">#Html.DisplayFor(modelItem => item.CommodityName)</div>
<div class="col-6 text-left"> <i class="fas fa-chevron-left fa-fw color-orange "></i></div>
</div>
<div class="row font-12 my-2 ios-divider-line">
<div class="col-6 text-gray-600 text-right">type</div>
<div class="col-6 text-gray-600 text-left">#Html.DisplayFor(modelItem => item.TypesName)</div>
</div>
<div class="row font-12 my-2 ios-divider-line">
<div class="col-6 text-gray-600 text-right">Code</div>
<div class="col-6 text-gray-600 text-left">#Html.DisplayFor(modelItem => item.CommodityCode)</div>
</div>
<div class="row font-12 my-2 ios-divider-line">
<div class="col-6 text-gray-600 text-right">Barcode</div>
<div class="col-6 text-gray-600 text-left">#Html.DisplayFor(modelItem => item.CommodityBarcode)</div>
</div>
<div class="row font-12 my-2 ios-divider-line">
<div class="col-6 text-gray-600 text-right">Unit Price</div>
<div class="col-6 text-gray-600 text-left">#Html.DisplayFor(modelItem => item.UnitPrice)</div>
</div>
<div class="row font-12 my-2 ios-divider-line">
<div class="col-6 text-gray-600 text-right">Total Price</div>
<div class="col-6 text-gray-600 text-left">#Html.DisplayFor(modelItem => item.SumPrice)</div>
</div>
</div>
</a>
Currently my controller is as below:
[Route("BuyReport/ReportDetailed/{commodityType}/{commodityName}/{department}/{repository}/{commodity}/{purchaseReportTypes}/{dateValue_1}/{dateValue_2}")]
public async Task<ActionResult> ReportDetailed(
string commodityType,
string commodityName,
string department,
string repository,
string commodity,
string purchaseReportTypes,
string dateValue_1,
string dateValue_2
)
{
}
But I want to change it to something like this:
[HttpPost]
public async Task<ActionResult> ReportDetailed(DetailedViewModel detailedviewmodel){
string commodity = detailedviewmodel.commodity;
string commoditytype = detailedviewmodel.commoditytype;
string department = detailedviewmodel.department;
string purchasereporttypes = detailedviewmodel.purchasereporttypes;
string repository = detailedviewmodel.repository;
string startdate = detailedviewmodel.datevalue_1;
string enddate = detailedviewmodel.datevalue_2;
string commdoityname = detailedviewmodel.commodityname;
}
Where DetailedViewModel is defined as below:
public class DetailedViewModel
{
public string commodityType { get; set; }
public string commodityName { get; set; }
public string department { get; set; }
public string repository { get; set; }
public string commodity { get; set; }
public string[] purchaseReportTypes { get; set; }
public string dateValue_1 { get; set; }//start date
public string dateValue_2 { get; set; }//end date
}
This is not the right way to meet your purpose. Your code looks vulnerable for exploiters too. Don't use solutions which break the normal web application behavior.
Instead, send the parameters to the corresponding controller method and then make an internal redirection with model passing (controller side). If your data is stored in database just send CommodityId and find details in controller side instead of sending entire details as form (HTTPPOST). In this way, you have a well designed project without unwanted crashes which come from breaking the behaviors and your code looks simple and clear as you want.
One quick simple solution is to post via Ajax:
Let's imagine this as your controller:
[HttpGet]
public ActionResult ReportDetailed()
{
return View();
}
[HttpPost]
public JsonResult ReportDetailed(DetailedViewModel detailedviewmodel)
{
var status = "error";
var message = "";
try
{
string commodity = detailedviewmodel.commodity;
string commoditytype = detailedviewmodel.commodityType;
string department = detailedviewmodel.department;
List<string> purchasereporttypes = detailedviewmodel.purchaseReportTypes;
string repository = detailedviewmodel.repository;
string startdate = detailedviewmodel.dateValue_2;
string enddate = detailedviewmodel.dateValue_1;
string commdoityname = detailedviewmodel.commodityName;
// your code here ...
status = "success";
return Json(new { status, detailedviewmodel } , JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
message = ex.Message;
return Json(new { status, message }, JsonRequestBehavior.AllowGet);
}
}
Assuming you have defined DetailedViewModel inside the Models folder:
public class DetailedViewModel
{
public string commodityType { get; set; }
public string commodityName { get; set; }
public string department { get; set; }
public string repository { get; set; }
public string commodity { get; set; }
public List<string> purchaseReportTypes { get; set; }
public string dateValue_1 { get; set; }//start date
public string dateValue_2 { get; set; }//end date
}
In your View, I copy the whole Html and Javascript for you, just grab it and tweak it to your needs:
#model Your_Proj_Namespace.Models.DetailedViewModel
#{
ViewBag.Title = "ReportDetailed";
}
<h2>ReportDetailed</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>DetailedViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.commodityType, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.commodityType, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.commodityType, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.commodityName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.commodityName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.commodityName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.department, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.department, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.department, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<label>purchaseReportTypes: 3 inputs for example</label>
<div class="col-md-10">
<input type="text" name="purchaseReportTypes[0]" class="form-control inputPurchaseReportTypes " value="" />
<input type="text" name="purchaseReportTypes[1]" class="form-control inputPurchaseReportTypes " value="" />
<input type="text" name="purchaseReportTypes[2]" class="form-control inputPurchaseReportTypes " value="" />
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.repository, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.repository, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.repository, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.commodity, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.commodity, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.commodity, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.dateValue_1, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.dateValue_1, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.dateValue_1, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.dateValue_2, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.dateValue_2, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.dateValue_2, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" id="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
#section scripts{
<script>
$('#submit').click(function (e) {
e.preventDefault();
alert('form submitted');
var list = [];
$('.inputPurchaseReportTypes').each( function (key, value) {
list.push($(this).val());
});
const DATA = {
commodityType: $('#commodityType').val(),
commodityName: $('#commodityName').val(),
department: $('#department').val(),
repository: $('#repository').val(),
commodity: $('#commodity').val(),
purchaseReportTypes: list,
dateValue_1: $('#dateValue_1').val(),
dateValue_2: $('#dateValue_2').val(),
};
console.log(DATA);
$.ajax({
url: '/YourControllerName/ReportDetailed',
type: "POST",
contentType: "application/json",
dataType: "json",
data: JSON.stringify(DATA),
success: function (result) {
alert('success');
console.log(result);
// your code here
}
});
});
</script>
}
If you prefer not to use Ajax, comment the javascript code above (all the code inside <script>), to post the form directly.
I built and debugged the above code. Feel free to test it.
Hope this helped.
Finally, You might find the following links useful:
https://www.tutorialsteacher.com/mvc/model-binding-in-asp.net-mvc
This link clearly shows how to handle the list of Authors inside a book viewmodel, similar to yours: http://codebuckets.com/2016/09/07/asp-net-mvc-and-binding-complex-objects-magic/
https://stackoverflow.com/a/16326290/4687359
I agree with the solution by #A. Nadjar
one more note
Use HttpGet if you want user to share the url and show same data as he see it to another user ,
if not? use HttpPost with one Object parameter,
because maybe there's a Nullable parameters the user won't search by so the url will be like this
BuyReport/ReportDetailed/dummyCommodityName/null/null/null/dummyCommodityID/null/2/0
or don't use this custom route [Route("BuyReport/ReportDetailed/{commodityType}/{commodityName}/{department}/{repository}/{commodity}/{purchaseReportTypes}/{dateValue_1}/{dateValue_2}")]
so he can use the Query String and pass only the keys => values he want
I'm using Entity Framework 7 with ASP.NET MVC 5.
I have some forms that look like this. Clicking on one of the "new" buttons brings up a Bootstrap modal that looks like this. Submitting the modal form adds a new entity to the database before appending its name and primary key to the selectlist.
This works, but if the user changes their mind, the item(s) created via the modal (location in this case) stick around forever. So ideally none of the child items would be created until the main form is finished. While the example only has two simple fields, other data models have more than half a dozen, which may include complex fields of their own (but preventing that wouldn't be a horrible restriction).
So what's the best way to do this, nested divs serialized by JavaScript? Nested divs would also make it easy to allow reordering by the user, which is the end goal.
Does ASP.NET have a better way to handle this?
This feels hacky, but it works.
Using BeginCollectionItem, you can have the modal add hidden input elements to the DOM.
I wrote an action method that returns JSON with the modelstate (valid/invalid) and errors or partial HTML for invalid and valid submissions, respectively. Based on this, JavaScript either adds the errors to the summary or adds the requisite label and hidden inputs to the initial form.
Then have the main form's viewmodel contain an ICollection of your data model, called Contacts in below code, and ASP.NET handles the data binding with no troubles.
Example:
_CollectionItem.cshtml (partial HTML added to main form after valid submission)
#model Project.Models.ContactCreateViewModel
<li>
<div class="collection-item">
#using (Html.BeginCollectionItem("Contacts"))
{
<span class="item-name">#Model.LastName, #Model.FirstName</span> <span class="btn btn-danger delete-item">Delete</span>
#Html.HiddenFor(model => model.FirstName)
#Html.HiddenFor(model => model.LastName)
#Html.HiddenFor(model => model.PhoneNumber)
#Html.HiddenFor(model => model.PhoneExt)
#Html.HiddenFor(model => model.Email)
}
</div>
</li>
_CreateModal.cshtml (partial used for the body of the modal)
#model Project.Models.ContactCreateViewModel
<div class="modal-header">
<button type="button" class="close btn-modal-close" data-dismiss="modal"><i class="fas fa-times"></i></button>
<h4 class="modal-title">New Contact</h4>
</div>
<div class="modal-body">
#using (Html.BeginForm("CreateModal", "Contacts", FormMethod.Post, new { id = "new-contact-form" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummaryPlaceholder()
#* First Name *#
<div class="form-group">
#Html.LabelFor(model => model.FirstName, htmlAttributes: new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
</div>
#* Last Name *#
<div class="form-group">
#Html.LabelFor(model => model.LastName, htmlAttributes: new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.EditorFor(model => model.LastName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.LastName, "", new { #class = "text-danger" })
</div>
</div>
#* Phone Number *#
<div class="form-group">
#Html.LabelFor(model => model.PhoneNumber, htmlAttributes: new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.EditorFor(model => model.PhoneNumber, new { htmlAttributes = new { #class = "form-control phone" } })
#Html.ValidationMessageFor(model => model.PhoneNumber, "", new { #class = "text-danger" })
</div>
</div>
#* Phone Ext *#
<div class="form-group">
#Html.LabelFor(model => model.PhoneExt, htmlAttributes: new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.EditorFor(model => model.PhoneExt, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PhoneExt, "", new { #class = "text-danger" })
</div>
</div>
#* Email *#
<div class="form-group">
#Html.LabelFor(model => model.Email, htmlAttributes: new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.EditorFor(model => model.Email, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Email, "", new { #class = "text-danger" })
</div>
</div>
#* SUBMIT *#
<div class="form-group">
<div class="col-md-offset-4 col-md-8">
<input type="submit" value="Create" class="btn btn-success" />
</div>
</div>
</div>
}
</div>
#Scripts.Render("~/Scripts/Custom/ajax-add-collection-item.js")
<script>
$(function () {
ajaxAddCollectionItem("new-contact-form", "contacts", function () {
alertify.success("Added new contact");
})
});
</script>
ajax-add-collection-item.js (capture modal form submission, add _CollectionItem.cshtml to main form)
// Posts form and adds collection item to ul
function ajaxAddCollectionItem(formId, listId, onSuccess = function () { }) {
let $form = $("#" + formId);
$form.submit(function (event) {
event.preventDefault();
$.ajax({
method: "POST",
url: $form.attr("action"),
data: $form.serialize(),
success: function (data) {
let successful = data["success"];
// If form is valid, close modal and append new entry to list
if (successful) {
$("#" + listId).append(data["html"]);
$(".delete-item").click(function (event) {
$(this).closest("li").remove();
});
$(".btn-modal-close").trigger("click");
onSuccess();
}
// If form is not valid, display error messages
else {
displayValidationErrors(data["errors"]);
}
},
error: function (error) {
alert("Dynamic content load failed.");
console.error("Ajax call failed for form: " + $form);
}
});
});
// Populate validation summary
function displayValidationErrors(errors) {
let $ul = $('div.validation-summary-valid.text-danger > ul');
$ul.empty();
$.each(errors, function (i, errorMessage) {
$ul.append('<li>' + errorMessage + '</li>');
});
}
}
ContactsController.cs
public class ContactsController
{
// GET: Contacts/CreateModal
public ActionResult CreateModal()
{
return PartialView("_CreateModal");
}
// POST: Contacts/CreateModal
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> CreateModal(ContactCreateViewModel viewModel)
{
// If form is valid and email does not already exist, send HTML for collection item,
// otherwise send modelstate errors
if (ModelState.IsValid)
{
User user = await UserManager.FindByEmailAsync(viewModel.Email);
if (user == null)
// RenderPartialView returns partial HTML as a string,
// see https://weblog.west-wind.com/posts/2012/May/30/Rendering-ASPNET-MVC-Views-to-String
return Json(new { success = true, html = RenderPartialView("_CollectionItem", viewModel) });
else
ModelState.AddModelError("Email", "Email already exists.");
}
return Json(new { success = false, errors = GetModelStateErrors() });
}
// Actually in base controller class
protected string[] GetModelStateErrors()
{
return ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage).ToArray();
}
}
I have simple form a dropdown list and a textbox. When the page is loaded textbox is disabled. If 'Other' is selected from dropdown list, then textbox is enabled and I add 'required' rule to textbox as I don't want user to enter something in the text box. When 'Other' is selected and if Create button is clicked, I can see validation error message but I cannot remove that message after user has typed something in the empty textbox. Here is how it looks:
Here is my model:
using System.ComponentModel.DataAnnotations;
namespace validatetextbox.Models
{
public class Manager
{
public int Id { get; set; }
[Required]
public string City { get; set; }
[Display(Name = "Other City")]
[DisplayFormat(ConvertEmptyStringToNull = false)]
public string OtherCity { get; set; }
}
}
My Controller method is
// GET: Managers/Create
public ActionResult Create()
{
//populate Gender dropdown
List<SelectListItem> c = new List<SelectListItem>();
c.Add(new SelectListItem { Text = "-- Please Select --", Value = "" });
c.Add(new SelectListItem { Text = "New York", Value = "New York" });
c.Add(new SelectListItem { Text = "Miami", Value = "Miami" });
c.Add(new SelectListItem { Text = "Other", Value = "Other" });
ViewBag.Cities = c;
return View();
}
My view is as follows:
#model validatetextbox.Models.Manager
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Manager</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.City, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.City, ViewBag.Cities as List<SelectListItem>, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.City, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.OtherCity, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.OtherCity, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.OtherCity, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
$(document).ready(function () {
$('#OtherCity').prop('disabled', true);
$('#City').change(function () {
if ($('#City option:selected').text() != 'Other') {
$("#OtherCity").rules("remove", "required");
$('#OtherCity').prop('disabled', true);
} else {
$('#OtherCity').prop('disabled', false)
$("#OtherCity").rules("add", "required");
}
});
});
</script>
}
To keep your existing code - you can remove the rules from the text box on document ready. Also - side note - keep quotation marks consistent.
<script type="text/javascript">
$(document).ready(function () {
$('#OtherCity').prop('disabled', true);
// Remove any rules
$('#OtherCity').rules("remove");
$('#City').change(function () {
if ($('#City option:selected').text() != 'Other') {
//Also tidy up `"` to `'`
$('#OtherCity').rules("remove", "required");
$('#OtherCity').prop('disabled', true);
} else {
$('#OtherCity').prop('disabled', false)
//Also tidy up `"` to `'`
$('#OtherCity').rules("add", "required");
}
});
});
</script>
Another thing you could try is - add the class optional to the editor - the label will display and the visibility and validation requirement will be controlled by the JS:
<div class="form-group">
#Html.LabelFor(model => model.OtherCity, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10 optional">
#Html.EditorFor(model => model.OtherCity, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.OtherCity, "", new { #class = "text-danger" })
</div>
</div>
$(document).ready(function () {
// This will hide the field
$('.optional').hide();
// This makes the value optional
$('#OtherCity').attr("data-val", "false");
$('#City').change(function () {
if ($('#City option:selected').text() == 'Other') {
// Show the text editor
$('.optional').show();
// This makes the value required
$('#OtherCity').attr("data-val", "true");
} else {
$('.optional').hide();
$('#OtherCity').attr("data-val", "false");
}
});
});
So I am learning to use Ajax in my mvc application. I am trying to get my login to work without having to change the page as my login form will be within the navbar with many other ajax forms. I could really use some help as I am not sure how to bind it to the loginmodel either because it never hits my controller. Thanks in advance for any tips you have.
View
<section id ="loginForm">
#using (Ajax.BeginForm("Login", "Account",
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
LoadingElementId = "ajax-loader",
UpdateTargetId = "loginSection",
}, new { #class = "form-horizontal" }))
{
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.Email, new { #class = "col-sm-3 control-label" })
<div class="col-sm-9">
#Html.TextBoxFor(m => m.Email, new { #class = "input-sm form-control" })
#Html.ValidationMessageFor(m => m.Email, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Password, new { #class = "col-sm-3 control-label" })
<div class="col-sm-9">
#Html.PasswordFor(m => m.Password, new { #class = "input-sm form-control" })
#Html.ValidationMessageFor(m => m.Password, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox pull-right">
#Html.CheckBoxFor(m => m.RememberMe)
#Html.LabelFor(m => m.RememberMe)
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<button class="btn btn-default pull-right" type="submit"><i class="fa fa-unlock-alt"></i> Sign in</button>
<i id="ajax-loader" class="fa fa-spinner fa-pulse" style="display:none"></i>
</div>
</div>
}
</section>
Controller
//AccountController
[HttpPost]
[AllowAnonymous]
public async Task<ActionResult> Login(LoginViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return View("Lockout");
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return View("Lockout");
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
Model
public class LoginViewModel
{
[Required]
[Display(Name = "Email")]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
So I was able to solve my problem by noticing my post was actually going to Home/Login instead of Account/login even though i gave the string "Account" to ajax. the following fixed my issue.
new {controller="Account"},
instead of
"Account"
within
#using (Ajax.BeginForm("Login", "Account",
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
LoadingElementId = "ajax-loader",
UpdateTargetId = "loginSection",
}, new { #class = "form-horizontal" }))
{
...
}