View component not rendering properly through AJAX - c#

I've built a site where users can alter various types of data through multiple view components. When I call these view components on initial page load everything works fine. However when I try refreshing a view component through an ajax call, any lists in those view components repeat a single element multiple times, and my jquery functions stop working.
Here is my main view:
#model ContractModel
#using Portal.ViewComponents
#{
ViewData["Title"] = "Contract View";
}
...
<div id="ResultModel_Scope_Facplan">
#await Component.InvokeAsync("ContractPart", new { contractPartName = "Model_Scope_Facplan", projectName = Model.ProjectName })
</div>
My ajax:
#section Scripts {
<script>
$.ajaxSetup({ cache: false });
$(".PartialButton").click(function (event) {
event.preventDefault();
var ElementID = event.target.id.split("-");
var contractPart = ElementID[0];
var contractAction = ElementID[1];
var actionValue = ElementID[2];
console.log($("#Form" + contractPart + actionValue).serialize() + "&ProjectName=#(Model.ProjectName)&contractAction=" + contractAction);
console.log(ElementID);
$.ajax({
url: "#(Url.Action(""))/" + contractPart,
type: "POST",
data: $("#Form" + contractPart + actionValue).serialize()+"&ProjectName=#(Model.ProjectName)&contractAction=" + contractAction,
success: function (data) {
//Fill div with results
alert(data);
//debugger;
$("#Result" + contractPart).html(data);
},
error: failedSearch(contractPart)
});
});
function failedSearch(ElementID) {
// alert(ElementID + " failed!");
// $("#Result"+ElementID).html = "There was a problem in the search. Please try again later.";
}
</script>
}
My actual view component invoke is pretty simple (just sends the right data to the right view).
public IViewComponentResult Invoke(string contractPartName, string projectName)
{
return View(contractPartName, FindContractPart(contractPartName, projectName));
}
And the view component template:
#model IEnumerable<Model_Scope_Facplan>
#if (#Model != null)
{
<div class="row">
Care Provider Payer
</div>
#for (int i = 0; i < Model.Count(); i++)
{
<form id="FormModel_Scope_Facplan#(i)" action="">
<div class="form-horizontal">
<div class="col-md-3">
<input asp-for=#Model.ElementAt(i).CareProviderID class="form-control" />
</div>
<div class="col-md-3">
<input asp-for=#Model.ElementAt(i).PayerID class="form-control" />
</div>
<span class="glyphicon glyphicon-minus DeleteItem PartialButton" style="color:blue;" id="Model_Scope_Facplan-Delete-#(i)"></span>
</div>
</form>
<br/>
}
<form id="FormModel_Scope_Facplan#(Model.Count())" action="">
<div class="form-horizontal">
<div class="col-md-3">
<input name=CareProviderID class="form-control" />
</div>
<div class="col-md-3">
<input name=PayerID class="form-control" />
</div>
<span class="glyphicon glyphicon-plus AddItem PartialButton" style="color:blue;" id="Model_Scope_Facplan-Create-#(Model.Count())"></span>
</div>
</form>
}
The viewcomponent is returning the correct view template, and in debugging I can see that the ViewComponentResult.ViewData.Model includes the appropriate entries in the list. However the page updated after the ajax call includes only a single element over and over. What am I doing wrong?

Seems like my for loop in the view was breaking - I'm guessing the ElementAt(i) function didn't work after the ajax call for some reason. I ended up switching to a foreach loop and now everything works.

Related

Send button value with Ajax to Controller in .net core

I'm going crazy trying to figure this one out.
First, I'm using entity framework and models etc. So, in my view I'm looping through all my objects, every object has unique properties - one of these properties i want to send to my controller, which does some things, and then reloads the page.
My controller class is as follows:
public IActionResult Index()
{
return View(_context.Modules.ToList());
}
[HttpPost]
public IActionResult LoadModule(string packageName)
{
//... do some things here
return RedirectToAction("Index");
}
My view is as follows:
<div class="container">
<div class="row">
#foreach (var m in Model)
{
<div class='col-sm-4'>
<div class="card border-secondary mb-3">
<div class="card-body">
<div class="row">
<button class="btn btn-outline-primary" id="SendValue"
type="submit" value="#m.IntegrationPoint">
Execute Package
</button>
</div>
</div>
</div>
<br />
</div>
}
</div>
</div>
And my Jquery/Ajax:
<script>
$(document).ready(function() {
$("#SendValue").click(function(){
var val = $(this).val();
$.ajax({
type:"POST",
url: '/ExampleModuleController/LoadModule',
dataType: 'json',
data:{String:val}
})
.fail(function(){alert("fail")})
.done(function(){alert("success")})
});
});
</script>
A link to fiddle for easier view of html.
What am i doing wrong here? It doesn't even seem to register my button click or anything.
Add below code to your view
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Change the url: '/ExampleModuleController/LoadModule',into url: '#Url.Action("LoadModule", "ExampleModule")?IntegrationPoint=' + val ,
Also , in Controller, change the parameter name to string IntegrationPointin LoadModule action.
Note: Because the .fail(function(){alert("fail")}) in the front, so it will alert fail message.
result:

Razor pages jQuery function validation issue

I have a form in Razor Pages .NET Core and inside this form I have a dropdown. This dropdown takes it's values (strings) from a jQuery function like this:
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script>
$(function () {
$("#StartDate").on("change", function () {
var time = $(this).val();
$("#select").empty();
$("#select").append("<option value=''>select </option>");
$.getJSON(`?handler=Time&time=${time}`, (data) => {
$.each(data, function (i, item) {
*$("#select").append("<option value='" + "'>" + item.hours + "</option>");*/
$("#select").append($("<option>").val(item.hours).text(item.hours));
});
});
});
});
</script>
<form method="post" id="myForm">
<div class="form-group">
<h6><label class="col-form-label">Time</label></h6>
<select id="select" asp-for="Time" class="form-control"></select>
<span class="text-danger" asp-validation-for="Time"></span>
</div>
</form>
Backend:
[Required(ErrorMessage = "Field cannot be empty!")]
[BindProperty]
public string Time { get;set; }
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
}
My issue is that after I submit my form, the ModelState.IsValid check always fails.
After some research, I found out that the reason is in my jQuery function because the added values are not validated.
I tried adding this line to my function, but it did not help:
$.validator.unobtrusive.parse("#myForm");
Right now, what it happens is that if I select a value from the dropdown, the ModelState won't be valid and returns the page
In my code:
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="StartDate" class="control-label"></label>
<input asp-for="StartDate" class="form-control" min="#DateTime.Now.ToString("yyyy-MM-ddThh:mm")" max="2050-06-01T00:00" />
<span asp-validation-for="StartDate" class="text-danger"></span>
</div>
<select id="select" asp-for="Time" class="form-control"></select>
<span class="text-danger" asp-validation-for="Time"></span>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script>
$(function () {
$("#StartDate").on("change", function () {
var time = $(this).val();
$("#select").empty();
$("#select").append("<option value=''>select </option>");
$.getJSON(`?handler=Time&time=${time}`, (data) => {
$.each(data, function (i, item) {
*$("#select").append("<option value='" + "'>" + item.hours + "</option>");*/
$("#select").append($("<option>").val(item.hours).text(item.hours));
});
});
});
});
When I select a value from the dropdown,it will pass the validate,You can make an endpoint in your OnPost action.Below is my test,you can see which column failed the verification.

view in mvc can not render html for the second time

when I try to render HTML in view for the second time the HTML not change and nothing happens it still as it aims calling the same view two times when the modal is empty and when I fill modal with data and then try to render it
this is the view
<section class="col-12 m-b-60">
<h2 class="m-b-20">Deduction</h2>
<form class="form-inline">
<div class="row">
<div class="col-6">
<div class="input-group">
<label class="input-text">
<span class="label">Employee ID</span>
<input type="text" placeholder="Employee ID ..."id="employeid">
</label>
<a class="btn btn-default" onclick="veiwemployee()">
Get
</a>
</div>
</div>
<div class="col-6">
<div class="input-group">
</div>
</div>
</div>
</form>
</section>
<section class="col-12 m-b-20">
`#if (Model != null)`
{
#await Html.PartialAsync("/Views/Home/View.cshtml", Model);
}
</section>
The action
public IActionResult EmployeeDeduction(int employeeID = 0)
{
Deduction deduction = new Deduction() ;
if (employeeID == 0) { }
else
{
ModelState.Clear();
deduction = _conn.GetEmployeByEmployeeID(employeeID);
}
return View("/Views/view.cshtml",deduction);
}
The Js function
function veiwemployee() {
if ($("#employeid").val() == "") {
$("#employeid").style.borderColor = "red";
}
else {
$.ajax({
type: 'POST',
url: '/Home/EmployeeDeduction?employeeID=' + $("#employeid").val(),
async: false,
dataType: 'json',
success: function (resp) {
}
});
}
}
this tag does not have closing.
<input type="text" placeholder="Employee ID ..." id="employeid">
"/" missing

How to validate multiple Ajax loaded Partial Views?

I have a View, Contact, that loads n number of Caller partial views, m number of Child partial views, and one CallNote partial view all loaded via Ajax once the document is ready.
I'm able to add and remove Callers and Children too, so these numbers are not static.
Contact.cshtml, with some stuff removed:
#using Birth_To_Five.ViewModels
#model CallDetailViewModel
<div class="container">
<ul class="nav nav-tabs">
<li class="active">Call Detail</li>
#* Other tabs not shown here *#
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="tab-1">
#using (Html.BeginForm("SubmitCallDetailsAsync", "Home", FormMethod.Post))
{
<div class="well">
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.CallThreadViewModel.Id)
<span style="color: red">
#Html.ValidationSummary()
</span>
#* Call Details *#
<div class="row">
<fieldset>
<legend>Call Details</legend>
</fieldset>
</div>
<div class="row">
<div class="form-group">
#Html.LabelFor(m => m.EnteredByEmail, new { #class = "control-label" })
#Html.ValidationMessageFor(m => m.EnteredByEmail, "", new { #class = "text-danger" })
#Html.TextBoxFor(m => m.EnteredByEmail, new { #class = "form-control", placeholder = "Who took the call" })
</div>
#* Other stuff *#
</div>
#* Caller Details *#
<div class="row">
<fieldset>
<legend>Callers</legend>
</fieldset>
</div>
#* Render each existing caller. Each caller gets their own well to create a visual separation between them. *#
#foreach (var callerViewModel in Model.CallerViewModels)
{
<div class="progress" id="callerLoadingBar-#callerViewModel.Id" data-callerid="#callerViewModel.Id" data-calldetailid="#Model.Id">
<div class="progress-bar progress-bar-striped active" role="progressbar" style="width: 100%">Loading Caller...</div>
</div>
}
<div id="newCaller"></div>
<div class="row">
#* Button to search for and add a caller *#
</div>
#* Children Details *#
<div class="row">
<fieldset>
<legend>Children</legend>
</fieldset>
</div>
#* Render each existing child. Each child gets their own well to create a visual separation between them. *#
#foreach (var childViewModel in Model.ChildViewModels)
{
<div class="progress" id="childLoadingBar-#childViewModel.Id" data-childid="#childViewModel.Id" data-calldetailid="#Model.Id">
<div class="progress-bar progress-bar-striped active" role="progressbar" style="width: 100%">Loading Child...</div>
</div>
}
<div id="newChild"></div>
<div class="row">
#* Button to search for and add a child *#
</div>
<div class="progress" id="callNoteLoadingBar">
<div class="progress-bar progress-bar-striped active" role="progressbar" style="width: 100%">Loading Call Note...</div>
</div>
</div>
<div class="row">
<div class="form-group">
<button class="btn btn-danger" type="reset">Reset</button>
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit">Submit</button>
</div>
</div>
}
</div>
</div>
</div>
#section scripts
{
#Scripts.Render("~/bundles/jqueryval")
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
#Scripts.Render("~/bundles/calldetailscripts")
}
Snippet from my JS script, callDetailFunctions:
$(document).ready(function () {
getCallNote('#callNoteLoadingBar', $('#Id').val());
getAllCallers();
getAllChildren();
});
// function getAllWhatever(){ Foreach loading bar, addCaller/Child/CallNotePartialView(..., ..., ..., etc.); }
function addWhateverPartialView(divToReplace, thingIWantId, callDetailId) {
$.ajax({
url: '/Home/GetWhateverPartialViewAsync',
data: {
thingIWantId,
callDetailId
},
type: "GET",
error: function (xmlHttpRequest, textStatus, errorThrown) {
alert("Request: " + xmlHttpRequest.toString() + "\n\nStatus: " + textStatus + "\n\nError: " + errorThrown);
},
success: function (data) {
$(divToReplace).replaceWith(data);
}
});
}
Here in my HomeController I have the SubmitCallDetailsAsync method:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SubmitCallDetailsAsync(CallDetailViewModel callDetailViewModel)
{
using (var unitOfWork = new UnitOfWork(ApplicationDbContext))
{
// Call Details
var callDetailServices = new CallDetailServices();
await callDetailServices.AddOrUpdateCallDetailFromCallDetailViewModelAsync(callDetailViewModel, ModelState, unitOfWork);
// Callers
var callerServices = new CallerServices();
await callerServices.AddOrUpdateCallersFromCallDetailsViewModelAsync(callDetailViewModel, ModelState, unitOfWork);
// Children
var childServices = new ChildServices();
await childServices.AddOrUpdateChildrenFromCallDetailsViewModelAsync(callDetailViewModel, ModelState, unitOfWork);
// Call Note
var callNoteServices = new CallNoteServices();
await callNoteServices.AddOrUpdateCallNoteFromCallDetailsViewModelAsync(callDetailViewModel, ModelState, unitOfWork);
// Check the model state (returns true if it's good, false otherwise.
// Also spits out some debug text for me to tell me what broke the Model)
if (!UtilityServices.CheckModelState(ModelState))
{
callDetailViewModel.DirectionChoices =
await unitOfWork.DirectionChoiceRepo.GetAllAsSelectListItemsAsNoTrackingAsync();
return View("Contact", callDetailViewModel);
}
await unitOfWork.CompleteAsync();
}
return RedirectToAction("Index");
}
The gist of what's happen is that I have a loading bar as a placeholder for each Caller, Child, and the Call Note and then when the document loads I go and get those on $(document).ready()
My problem is that when I submit Contact.cshtml and hit a model validation error I get sent back to my Contact page, which reloads all the Callers, Children, and the Call Note, thus losing all changes.
What should/can I do to handle this scenario?
With deloopkat's comment, I was able to get this working (for the most part)
I changed my Contact page to use Ajax.BeginForm() and added a partialCallDetail id to the section I want to replace with a Partial View result:
#using (Ajax.BeginForm("SubmitCallDetailsAsync", "Home", new AjaxOptions() {HttpMethod = "POST", UpdateTargetId = "partialCallDetail", OnSuccess = "onSuccess"})) #* <----------- Note the UpdateTargetId *#
{
<div class="well">
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.CallThreadViewModel.Id)
<span style="color: red">
#Html.ValidationSummary()
</span>
#* Call Details *#
<div id="partialCallDetail"> #* <------------------ This whole div gets replaced by the Submit function when the Model Validation fails *#
#* All of the same stuff as before in my original post *#
</div>
</div>
<div class="row">
<div class="form-group">
<button class="btn btn-danger" type="reset">Reset</button>
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit">Submit</button>
</div>
</div>
}
#section scripts
{
<script>
function onSuccess(data) {
///<summary>
/// When the Ajax form is submitted, this function gets called with the return data.
/// Determine if it contains a redirectUrl and go there if it does
///</summary>
if (data.redirectUrl !== undefined) {
window.location.replace(data.redirectUrl);
}
}
</script>
}
I created a separate Partial View, _PartialCallDetail, that renders each Caller, Child, and CallNotes PartailView on the spot rather than calling a function via Ajax on $(document).ready()
...
#* Render each existing caller. Each caller gets thier own well to create a visual seperation between them. *#
#foreach (var callerViewModel in Model.CallerViewModels)
{
Html.RenderPartial("_PartialCallerInfo", callerViewModel);
}
...etc.
I then changed my Submit function to this:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SubmitCallDetailsAsync(CallDetailViewModel callDetailViewModel)
{
using (var unitOfWork = new UnitOfWork(ApplicationDbContext))
{
// Call Details
var callDetailServices = new CallDetailServices();
await callDetailServices.AddOrUpdateCallDetailFromCallDetailViewModelAsync(callDetailViewModel, ModelState, unitOfWork);
// Callers
var callerServices = new CallerServices();
await callerServices.AddOrUpdateCallersFromCallDetailsViewModelAsync(callDetailViewModel, ModelState, unitOfWork);
// Children
var childServices = new ChildServices();
await childServices.AddOrUpdateChildrenFromCallDetailsViewModelAsync(callDetailViewModel, ModelState, unitOfWork);
// Call Note
var callNoteServices = new CallNoteServices();
await callNoteServices.AddOrUpdateCallNoteFromCallDetailsViewModelAsync(callDetailViewModel, ModelState, unitOfWork);
// Check the model state
if (!UtilityServices.CheckModelState(ModelState))
{
// Setup all drop downs
callDetailViewModel.DirectionChoices =
await unitOfWork.DirectionChoiceRepo.GetAllAsSelectListItemsAsNoTrackingAsync();
foreach (var callerViewModel in callDetailViewModel.CallerViewModels)
{
await callerServices.SetupSelectListItemsAsync(callerViewModel, unitOfWork);
}
foreach (var childViewModel in callDetailViewModel.ChildViewModels)
{
childViewModel.SexChoices = await unitOfWork.SexChoiceRepo.GetAllAsSelectListItemsAsNoTrackingAsync();
}
// Return the ViewModel with Validation messages
if (Request.IsAjaxRequest()) return PartialView("_PartialCallDetail", callDetailViewModel);
return View("Contact", callDetailViewModel);
}
await unitOfWork.CompleteAsync();
}
return Json(new { redirectUrl = Url.Action("Index", "Home", null) });
}
Now when there is a Model Validation error, I send back my _PartialCallDetail view, which updates the Contact page with the existing data and activates the #Html.ValidationMessageFor(...)s
I think it's important to note that this isn't perfect in it's current state:
I now have two Views, Contact and _PartialCallDetail, that I need to update if I make a design change in the future.
The #Html.ValidationSummary() I have in my View is only for non-Ajax content, so the User's have to sift through the View to see what's wrong.
And a couple other specific-to-my-code issues too, but I'll leave those out.
But I feel pretty good that this is a step in the right direction

Embed Html `alert()` inside C# method call to display alert window MVC

I am trying to add Html code inside a #functions {} codeblock where if it matches some condition it will alert the user.
this is what I have so far, and I am getting the error CS0103: The name 'alert' does not exist in the current context
this is the code piece that is throwing the error.
#functions{
void doSomething(){
if(ViewBag.token != null){
#alert("#ViewBag.token");
}
}
}
Is there a way to embed alert() inside a C# code-block within .cshtml
this is the entire code which this function is in
#using System.Web.Mvc
#using System.Web.Mvc.Html
#using System
#using System.Web.UI
#model Dependency_Injection_MEF_MVC.Models.Payment
#section Scripts{
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript">
Stripe.setPublishableKey('pk_test_6pRNASCoBOKtIshFeQd4XMUh');
</script>
<script type="text/javascript">
$(function () {
var $form = $('#payment-form');
$form.submit(function (event) {
// Disable the submit button to prevent repeated clicks:
$form.find('.submit').prop('disabled', true);
// Request a token from Stripe:
Stripe.card.createToken($form, stripeResponseHandler);
// Prevent the form from being submitted:
return false;
});
});
function stripeResponseHandler(status, response) {
// Grab the form:
var $form = $('#payment-form');
if (response.error) { // Problem!
// Show the errors on the form:
$form.find('.payment-errors').text(response.error.message);
$form.find('.submit').prop('disabled', false); // Re-enable submission
} else { // Token was created!
// Get the token ID:
var token = response.id;
ViewBag.token = token;
// Insert the token ID into the form so it gets submitted to the server:
$form.append($('<input type="hidden" name="Token">').val(token));
// Submit the form:
$form.get(0).submit();
}
};
</script>
}
<div class="row">
<div class="col-md-12 form-column">
<div class="form-container">
<form asp-controller="home" asp-action="processpayment" method="POST" id="payment-form">
<span class="payment-errors"></span>
<div class="form-group">
<h3>Membership Amount: USD XX</h3>
</div>
<div class="form-group">
<label for="cardNumber">Card Number</label>
<input class="form-control form-input" id="cardNumber" type="text" size="20" data-stripe="number" style= "width:250px;height:25px;font-size:120%">
</div>
<div class="form-group">
<label>Expiration (MM/YY)</label>
<div>
<input class="form-control form-input date-input" type="text" size="2" data-stripe="exp_month" style= "width:250px;height:25px;font-size:120%">
<input class="form-control form-input date-input" type="text" size="2" data-stripe="exp_year" style= "width:250px;height:25px;font-size:120%">
</div>
</div>
<div class="form-group">
<label for="cvc">CVC</label>
<input class="form-control form-input" id="cvc" type="text" size="4" data-stripe="cvc" style= "width:250px;height:25px;font-size:120%">
</div>
<input class="btn btn-default" onclick="doSomething()" id="submit" value="Submit Payment">
</form>
</div>
</div>
</div>
#functions{
void doSomething(){
if(ViewBag.token != null){
alert("#ViewBag.token");
}
}
}
Functions are intended to be completely server-side. But that script can be easily embeddable; if you move that to the page where you want it called, just do:
#if(ViewBag.token != null){
<script>
alert("#ViewBag.token");
</script>
}
And this will get rendered if the token exists. Functions aren't needed for this; this could be inside of a #helper though.

Categories

Resources