I'm currently having difficulty using Ajax to update a partial view without having to refresh the whole page. I'm using MVC and entity framework to scaffold views.
I'll try and include as much as possible to help explain myself:
I have a div which is going to be used to hold a list view of all my comments
<div id="ContainAnnouncementComments"> </div>
This div gets populated using the following:
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
<script src="~/Custom_Scripts/BuildAnnouncement.js"></script>
#Scripts.Render("~/bundles/jqueryval")
$(document).ready(function () {
$.ajax({
url: '/Comments/BuildAnnouncementComment',
data: { AnnouncementId: #Model.AnnouncementId},
success: function (result) {
$('#ContainAnnouncementComments').html(result);
}
});
});
Which calls the BuildAnnouncementComment() method in my controller:
public ActionResult BuildAnnouncementComment(int? AnnouncementId)
{
return PartialView("_AnnouncementComment", db.Comments.ToList().Where(x => x.AnnouncementId == AnnouncementId));
}
I then have a second div container which is used to hold a text box for a user to enter some information which 'should' then update the ContainAnnouncementComments using Ajax replace call:
<div id="ContainAnnouncementCommentCreate">
#using (Ajax.BeginForm("AJAXCreate", "Comments", new { announcementId = Model.AnnouncementId }, new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "ContainAnnouncementComments"
}))
{
<div class="form-group">
#Html.AntiForgeryToken()
<div class="col-md-10">
#Html.EditorFor(model => model.Message, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Message, "", new { #class = "text-danger" })
</div>
</div>
}
</div>
The Ajax method calls the AJAXCreate method in the controller:
public ActionResult AJAXCreate(int announcementId, [Bind(Include = "CommentId, Message")] Comment comment)
{
if (ModelState.IsValid)
{
comment.UserId = User.Identity.GetUserId();
comment.AnnouncementId = announcementId;
db.Comments.Add(comment);
db.SaveChanges();
}
return PartialView("_AnnouncementComment", db.Comments.ToList());
}
From here, when running, I try to create a new comment, but when submitted, instead of the partialView being updated, all that is being displayed is the partialview.
Not sure if I've explained this properly so if i'm missing any information please let me know and i'll update accordingly.
After 3 hours of staring at my code I realised that nothing was actually wrong with the implementation and all that was wrong was I hadn't installed AJAX through the NuGet manager.
Joys.
Related
Jquery on change function not firing in asp.net MVC
Disclaimer: This might seem like a usual issue up front, but please bear with me and read to the end.
I have two files, namely, CreateProject.cshtml and projectMasterNew.js
CreateProject.cshtml
<div class="form-group">
#Html.LabelFor(model => model.Account.AccountName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Account.AccountName, new SelectList(ViewBag.GetAccount, "AccountId", "AccountName"))
#Html.ValidationMessageFor(model => model.Account.AccountName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Account.DMName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.Account.DMName, new SelectList("", "DMId", "DMName"))
#Html.ValidationMessageFor(model => model.Account.DMName, "", new { #class = "text-danger" })
</div>
</div>
<script src="~/Scripts/DMS/projectMasterNew.js"></script>
<script>
var getDmUrl = '#Url.Action("GetDMList", "Project", new { area = "Masters" })';
</script>
projectMasterNew.js
$(document).ready(function () {
alert($("#Account_AccountName").val());
$("#Account_AccountName").on("change", function () {
alert($("#Account_AccountName").val());
var jsonData = { account: $(this).val() };
getJsonCall("Project Creation", getDmUrl, jsonData, function (response) {
var dName = $('#Account_DMName');
alert("Dropdwn update");
});
alert("Success");
}, false);
});
The above jquery code worked until this morning.
Problem:
The script was being accessed at the time of page load, since I got the alert before the function. But the change event did not trigger on when I changed the value in the AccountName dropdown.
I tried calling the change method as follows:
1. $(document).on("change", "#Account_AccountName", function () {
2. $("#Account_AccountName").change(function () {
to no outcome.
So, after a little research, I implemented the below
function DmList() {
alert($("#Account_AccountName").val());
var jsonData = { account: $("#Account_AccountName").val() };
getJsonCall("Project Creation", getDmUrl, jsonData, function (response) {
var dName = $('#Account_DMName');
$.each(response, function (index, item) {
dName.append($('').text(item.DMName).val(item.DMId));
});
alert("Dropdwn update");
});
alert("Success");
}
#Html.DropDownListFor(model => model.Account.AccountName, new SelectList(ViewBag.GetAccount, "AccountId", "AccountName"), new { onchange = "DmList()"})
As you'll notice, the jquery function isn't called on Document Ready. That is because, adding that also threw an error
For those wondering, my BundleConfig.cs has
bundles.Add(new ScriptBundle("~/bundles/jquery").Include("~/Scripts/jquery-{version}.js"));
and my _Layout.cshtml has
#Scripts.Render("~/bundles/jquery")
I would like to know why this was an issue and what can I do to avoid such discrepancies in the future?
Your change event did not fire because you had an extra false as the last parameter of your change event binding. I am not sure why.
Also make sure you do not have any other script errors in the page.
This should work fine.
$(document).ready(function () {
$("#Account_AccountName").on("change", function () {
alert($(this).val());
var jsonData = { account: $(this).val() };
// your get JSON call goes here
alert("Success");
});
});
Also it might be a good idea to define the getDmUrl variable before using it. I recommend using javascript namespacing instead of simply using a global variable.
<script>
var myProj = myProj || {};
myProj.getDmUrl = '#Url.Action("GetDMList", "Project", new { area = "Masters" })';
</script>
<script src="~/Scripts/DMS/projectMasterNew.js"></script>
And in your projectMasterNew.js, use myProj.getDmUrl for your ajax call.
I am trying to create an AutoComplete textbox, while using an EditorTemplate. The problem I am facing is that by using the Html.BeginCollectionItem() extension solution (https://www.nuget.org/packages/BeginCollectionItem/), the Id's of the EditorFor() and TextBoxFor() methods get set dynamically and this breaks my javascript. Next to that, I do not exactly know if this is even possible (and if so, how. Below you will find how far I have come).
In the main view I have a loop to generate a partial view for each item in a collection
for (int i = 0; i < Model.VoedingCollection.Count; i++)
{
#Html.EditorFor(x => x.VoedingCollection[i], "CreateVoedingTemplate")
}
The partial view CreateVoedingTemplate.cshtml uses the Html.BeginCollectionItem() method
#using (Html.BeginCollectionItem("VoedingCollection"))
{
string uniqueId = ViewData.TemplateInfo.HtmlFieldPrefix.Replace('[', '_').Replace(']', '_').ToString();
string searchId = "Search_";
string standaardVoedingId = "StandaardVoeding_";
foreach (KeyValuePair<string, object> item in ViewData)
{
if (item.Key == "Count")
{
searchId = searchId + item.Value.ToString();
standaardVoedingId = standaardVoedingId + item.Value.ToString();
}
}
<div class="form-horizontal">
<div class="form-group" id=#standaardVoedingId>
#Html.LabelFor(model => model.fk_standaardVoedingId, "Voeding naam", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.HiddenFor(model => model.fk_standaardVoedingId)
<input type="text" id='#searchId' placeholder="Search for a product"/>
</div>
</div>
</div>
<script type="text/javascript">
var id = '#uniqueId' + '_fk_standaardVoedingId'.toString();
var search = '#searchId'.toString();
var url = '#Url.RouteUrl("DefaultApi", new { httproute = "", controller = "AgendaApi" })';
$(document.getElementById(search)).autocomplete({
source: function (request, response) {
$.ajax({
url: url,
data: { query: request.term },
dataType: 'json',
type: 'GET',
success: function (data) {
response($.map(data, function (item) {
return {
label: item.standaardVoedingNaam,
value: item.standaardVoedingId
}
}));
}
})
},
select: function (event, ui) {
$(document.getElementById(search)).val(ui.item.label);
//$('#id').val(ui.item.value);
document.getElementById(id).value = ui.item.value;
return false;
},
minLength: 1
});
</script>
}
<link href="~/Content/SearchBox/jquery-ui.css" rel="stylesheet" />
<script src="~/Scripts/SearchBox/jquery-1.9.1.js"></script>
<script src="~/Scripts/SearchBox/jquery-ui.js"></script>
In the script above, I am trying to make a function where the user can type in the name of a standaardVoeding item and then get results, where, after the user selects a standaardVoeding item, the standaardVoedingId property gets set. Then, after submitting the whole form, the controller receives the standaardVoedingId (with all the other info as well)
So I guess Javascript somehow cannot handle the Razor View # code and, next to that, Html.BeginCollectionItem does something fishy because you cannot set the value of its textboxes via code during runtime. Next to that, I have tried doing alert(document.getElementById(*html.begincollectionitemId*)) and it finds the fields fine. But apparently all other methods do not work?
Is there perhaps a better solution to getting this to work?
The BeginCollectionItem() method alters the id and name attributes of the html generated by the inbuilt helpers, in your case, for the hidden input, instead of
<input ... name="fk_standaardVoedingId" .... />
it will generate
<input ... name="VoedingCollection[xxxx].fk_standaardVoedingId" .... />
where xxxx is a Guid.
While it would be possible to use javascript to extract the Guid value from the textbox (assuming that was generated correctly usind #Html.TextBoxFor()) and build the id of the associated hidden input to use as a selector, it is far easier to use class names and relative selectors.
You also need to remove your scripts and css from the partial and put that in the main view (or its layout). Apart from the inline scripts, your duplicating it for each item in your collection.
Your partial needs to be
#using (Html.BeginCollectionItem("VoedingCollection"))
{
<div class="form-horizontal">
<div class="form-group">
#Html.LabelFor(model => model.fk_standaardVoedingId, "Voeding naam", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10 item"> // add class name
#Html.HiddenFor(model => model.fk_standaardVoedingId)
<input type="text" class="search" placeholder="Search for a product"/>
</div>
</div>
</div>
}
Note the class name for the textbox and its container which also includes the hidden input. Then in the main view, the script will be
<script type="text/javascript">
var url = '#Url.RouteUrl("DefaultApi", new { httproute = "", controller = "AgendaApi" })';
// Attach the script to all textboxes
$('.search').autocomplete({
source: function (request, response) {
$.ajax({
url: url,
data: { query: request.term },
dataType: 'json',
type: 'GET',
success: function (data) {
response($.map(data, function (item) {
return {
label: item.standaardVoedingNaam,
value: item.standaardVoedingNaam, // this needs to be the name
id: item.standaardVoedingId // add property for the id
}
}));
}
})
},
select: function (event, ui) {
// Get the associated hidden input
var input = $(this).closest('.item').find('input[type="hidden"]');
// Set the value of the id property
input.val(ui.item.id);
},
minLength: 1
});
</script>
Based on your comments that your are not dynamically adding or removing items in the view, then there is no point in the extra overhead or using the BeginCollectionItem() method. Change the name of your partial to standaardvoeding.cshtml (assuming that's the name of the class) and move it to the /Views/Shared/EditorTemplates folder.
Then in the main view, replace your for loop with
#Html.EditorFor(m => m.VoedingCollection)
which will generate the correct html for each item in the collection. Finally remove the BeginCollectionItem() method from the template so that its just
<div class="form-horizontal">
<div class="form-group">
#Html.LabelFor(m => m.fk_standaardVoedingId, "Voeding naam", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10 item"> // add class name
#Html.HiddenFor(m => m.fk_standaardVoedingId)
<input type="text" class="search" placeholder="Search for a product"/>
</div>
</div>
</div>
I want to display success Alert pop-up or alert message after form submited and action succeded
In this example I want to show "successfully add" :
Create Action :
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(TAUX taux)
{
if (ModelState.IsValid)
{
db.TAUX.Add(taux);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CAT_ID = new SelectList(db.CATEGORIE, "CAT_ID", "LIBELLE", taux.CAT_ID);
ViewBag.C_GARANT = new SelectList(db.GARANTIE, "C_GARANT", "LIB_ABREGE", taux.C_GARANT);
return PartialView("_Create", taux);
}
Index Action :
public ActionResult Index()
{
var taux = db.TAUX.Include(t => t.CATEGORIE).Include(t => t.GARANTIE);
return View(taux.ToList());
}
What should I add to do this ?
It's possible, for sure, but implementation details might change from technologies and app structure.
One of the most common ways for doing this (and the one I'm currently using with great success) is to receive whater-you-need-to-receive and return a JSON object with the status and/or error message, which will be used by some script on your view.
For example:
[Route(""), HttpPost]
public JsonResult DoSomethingOnTheServer(DoSomethingViewModel vm)
{
try
{
if (DoSomething(vm)) return Success();
else return Error("The server wasn't able to do something right now.");
}
catch (Exception err)
{
return Error(err.Message);
}
}
public JsonResult Success() { return Json(new { Success = true, Message = "" }); }
public JsonResult Error(string message) { return Json(new { Success = false, Message = message }); }
This way, you can do something like:
<script>
$.ajax({
url: "/",
method: "POST",
data: getMyData(),
success: function(json) {
if (json.Success) {
alert("Wow, everything was fine!");
} else {
alert(json.Message);
}
},
// This will be trigered whenever your ajax call fails (broken ISP link, etc).
error: function() {
alert("Something went wrong. Maybe the server is offline?");
}
});
</script>
Being honest with you, it's possible to submit the form (regular submit, I mean) and on the page refresh, display whatever you want.
This will, however, force you to load something extra with your view model, like an extra <script> tag or some extra <div> tags and to check for that every time.
From a usability/design point-of-view, it's a no-go. Avoid page refresh whenever possible because they give a feeling for the user that the app is either going away or that he/she is now moving outsinde the app.
Also, from a performance point-of-view, "full round-trips" to the server, consisting of sending the form data and retrieving a whole new page (with the whole layout, scripts, razor rendering/parse and all) is a massive problem just to show "success/fail" for the end-user so, again: avoid it whenever possible.
IMHO, returning either a view or a partial view only to display a message to the end-user is just wasting both yours and your end-users' link.
I used to make Ajax Form and in the OnSuccess fucntion i show message :
#using (Ajax.BeginForm("Edit1", "Availability", new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.InsertBefore, UpdateTargetId = "formAddAvailabilityContainer", OnSuccess = "AddAvailabilitySuccess" }, new { #class = "AjaxForm", #id = "formAvailability_" + Model.Id }))
{
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
#Html.HiddenFor(model => model.UserId)
#Html.EditorFor(model => model.StartTime)
#Html.MyValidationMessageFor(model => model.StartTime)
#Html.EditorFor(model => model.EndTime)
#Html.MyValidationMessageFor(model => model.EndTime)
#Html.EditorFor(model => model.RecurrenceFreq)
#Html.MyValidationMessageFor(model => model.RecurrenceFreq)
#Html.TextBoxFor(model => model.EndsAfterValue, new {id="txtBoxEndsAfterValue" })
#Html.MyValidationMessageFor(model => model.EndsAfterValue)
#Html.EditorFor(model => model.EndsByDate)
<input type="submit" class="btn btn-primary" value="Save" id="btnsubmit" />
<input type="button" class="btn btn-primary btnCancelAvailability" id="0" value="Cancel">
}
<script>
function AddAvailabilitySuccess()
{
alert("Record updated successfully!");
}
</script>
I have a way to do this, using the ViewBag.
In your controller you must add:
if (ModelState.IsValid)
{
db.TAUX.Add(taux);
db.SaveChanges();
ViewBag.SuccessMsg = "successfully added";
return RedirectToAction("Index");
}
Then in the Index view:
#{
var message = ViewBag.SuccessMsg;
}
<script type="text/javascript">
var message = '#message';
if(message){
alert(message);
}
</script>
in the end you use JavaScript mixed with the MVC ViewBag :)
I want to update a partial view when user changes the current value of my drop down list.
With my onchange submit, the partial view does not load in div but replaces the whole page.
How can I fix it?
view:
#using (Ajax.BeginForm("GetEnvironment", new RouteValueDictionary { { "Environment", Model.Environnements } }, new AjaxOptions() { UpdateTargetId = "ExportDiv" }))
{
#Html.DropDownListForEnum(x => x.Environnements, new { onchange = "this.form.submit();" })
}
<div id="ExportDiv">
#Html.Partial("Export")
</div>
Controller :
public PartialViewResult GetEnvironment(ExampleDto model)
{
return PartialView("Export", model);
}
Export.cshtml
It looks like triggering submit on the form causes whole page to be posted instead of AJAX behavior. Try this:
#using (Ajax.BeginForm("GetEnvironment", new RouteValueDictionary { { "Environment", Model.Environnements } }, new AjaxOptions() { UpdateTargetId = "ExportDiv" },new {id = "ajaxForm"))
{
#Html.DropDownListForEnum(x => x.Environnements, new { onchange = "submitAjaxForm()" })
}
<div id="ExportDiv">
#Html.Partial("Export")
</div>
<script type="text/javascript">
$('form#ajaxForm').submit(function(event) {
eval($(this).attr('onsubmit')); return false;
});
window.submitAjaxForm = function(){
$('form#ajaxForm').submit();
}
</script>
You need to include the scripts in thier own section if you are using it.
The result should be the same than what Ufuk suggested, but did you try simply adding return false; after this.form.submit(); ?
I have a view with two partials in it that use ajax to post their forms to actions. The onsuccess callback redirects the user to another url. However I don't want this onsuccess function to be called when the modelstate is not valid. I've tried returning a 400 level error from my controller to trigger the onfailure function but it causes some weird behavior and the validation errors don't display. Here's my code
Action:
[AllowAnonymous]
[DisableCache]
public ActionResult Login(string returnUrl ="/") //This is the view that contains the two other partial views.
{
var path = VirtualPathUtility.ToAbsolute(returnUrl);
var url = new Uri(Request.Url, path).AbsoluteUri;
ViewBag.ReturnUrl = url;
return View();
}
[AllowAnonymous]
public ActionResult _LoginPartial()
{
return PartialView(new LoginModel());
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult _LoginPartial(LoginModel model)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
return PartialView();
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
Response.StatusCode = 400;
return PartialView(model);
}
Login View:
<hgroup class="title">
<h1>#ViewBag.Title.</h1>
</hgroup>
#section CustomScripts {
#if (ViewData.ModelState.IsValid){
<script type ="text/javascript">
function OnSuccess() {
var returnUrl = #Html.Raw(Json.Encode(ViewBag.ReturnUrl))
window.location = returnUrl;
}
function OnFailure() {
alert("Fail");
}
</script>
}
}
<section id="loginForm">
#{
if(!WebSecurity.IsAuthenticated){
<h2>Use a Creative Works account to log in.</h2>
#Html.Action("_LoginPartial")
#Html.ActionLink("Forgot your password?", "ResetPassword")
}
}
</section>
<section id ="RegisterForm">
#{
if(!WebSecurity.IsAuthenticated){
<span>Don't have an account? Make one!</span>
#Html.Action("RegisterPartial", new { returnUrl = ViewBag.ReturnUrl })
}
}
</section>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
_LoginPartial view
#model Cwo.Domain.Entities.LoginModel
#{
ViewBag.Title = "LoginPartial";
}
#using (Ajax.BeginForm("_LoginPartial",
new AjaxOptions(){UpdateTargetId = "loginForm",
InsertionMode = InsertionMode.Replace, OnSuccess = "OnSuccess", OnFailure = "OnFailure"
})) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Log in Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.UserName)
#Html.TextBoxFor(m => m.UserName)
#Html.ValidationMessageFor(m => m.UserName)
</li>
<li>
#Html.LabelFor(m => m.Password)
#Html.PasswordFor(m => m.Password)
#Html.ValidationMessageFor(m => m.Password)
</li>
<li>
#Html.CheckBoxFor(m => m.RememberMe)
#Html.LabelFor(m => m.RememberMe, new { #class = "checkbox" })
</li>
</ol>
<input type="submit" value="Log in" />
</fieldset>
}
The formatting got kinda messed up pasting in here, sorry about that. Instead of an alert("fail") response I want the validation errors to display. If there's a better way than returning a 400 level error from the action please teach me!
I think another way to word you problem is you don't want the contents of the onsuccess function to be executed? if this is the case then move your #if (ViewData.ModelState.IsValid){ ... } to inside the OnSuccess function. if state is invalid that function would have no guts so nothing gets done.
personally I like keeping javascript separate from razor...even more, the javascript should reside in its own js file. in this case you could use razor to set a bool javascript variable to a function or object constructor, which would then do your success work only if variable was true.