I have a DIV (tenant-reference-photos) that holds a partial view that display photos. Beside each photo is a delete button. When it's clicked, I want the photo to be removed from the list and only the DIV to be updated by Ajax.
I'm using an Ajax.ActionLink for the delete action:
<div>
#Ajax.ActionLink("Delete", "DeleteReference",
new { id = photo.ImageId },
new AjaxOptions { InsertionMode = InsertionMode.Replace, HttpMethod = "POST",
UpdateTargetId = "tenant-reference-photos" },
new { #class = "btn btn-primary" })
</div>
Controller actions:
[HttpGet]
public ActionResult DeleteReference(int id)
{
return View(refPhotoRepository.Find(id));
}
[HttpPost, ActionName("DeleteReference")]
public ActionResult DeleteReferenceConfirmed(int id)
{
try
{
refPhotoRepository.Delete(id);
refPhotoRepository.Save();
return PartialView("_TenantReferencePhotosPartial");
}
catch (Exception e)
{
// handle exception
}
return View();
}
When I click delete, the action fires and record is deleted from the database. The problem is with return PartialView("_TenantReferencePhotosPartial");. When the action tries to return the partial, I get a NullReferenceException at #if (Model.ReferencePhotos.Count == 0).
_TenantReferencePhotosPartials.cshtml
<div>
#if (Model.ReferencePhotos.Count == 0)
{
<h3>You haven't uploaded any references!
#Ajax.ActionLink("Upload now?",
"TenantUploadReference",
new AjaxOptions
{
UpdateTargetId = "tenant-reference-photos",
InsertionMode = InsertionMode.Replace,
HttpMethod = "GET",
LoadingElementId = "ajax-loader"
})</h3>
}
else
{
foreach (var photo in Model.ReferencePhotos)
{
<ul class="thumbnails">
<li class="span8">
<a href="#" class="thumbnail">
<img src="#Url.Action("GetReference", "Tenants", new { id = photo.ImageId })" alt="#photo.Name") />
</a>
</li>
<li class="span4 btn-group btn-group-vertical">
Edit
#Ajax.ActionLink("Delete", "DeleteReference",
new { id = photo.ImageId },
new AjaxOptions { InsertionMode = InsertionMode.Replace, HttpMethod = "POST", UpdateTargetId = "tenant-reference-photos" },
new { #class = "btn btn-primary" })
</li>
</ul>
}
}
</div>
Even if there are several photos in the collection and one is deleted, the exception is still thrown at the line mentioned previously. Some help would be appreciated.
How do I resolve the above error?
I've had some trouble with jQuery $ not being defined in partials even
though it's there in the source
I believe this is in connection with your problem. When I encounter this on a partial view, what always works for me is to include it in a script section. So on your _Layout.cshtml where you write your scripts you can add this:
// Do this after you referenced jquery
#RenderSection("scripts", required: false)
<script>
// some script references
</script>
Then on your view you do this:
#section scripts {
$(function() {
<script>
function onDeleteReferenceSuccess(data, status, xhr) {
if (data.error) { /* handle error */ }
else {
window.location.reload(true);
}
}
</script>
});
}
Once above is resolved, how do I update the only the DIV?
Since you deleted the item I would assume you would want to delete the div - that represents the item.
If my assumption is correct then why not give it an Id so you can delete it afterwards in your else statement:
else {
// instead of this -> window.location.reload(true);
$("#id_of_the_div").remove();
}
UPDATE:
Your updated question throws off my original answer. But let me include the updated answer here first (and then clean-up afterwards). So the reason you are having a null reference error is because you are not returning the model:
refPhotoRepository.Delete(id);
refPhotoRepository.Save();
// build the model here and pass it to the partial view
// this will most probably involve querying your data store again
var model = goBuildTheModel();
return PartialView("_TenantReferencePhotosPartial", model;
Related
I have a small view inside of an HTML.Action that lives inside my _Layout. I am trying to send the ProjectId of the selected project to the Create Ticket View. My modal view looks like this.
Here is the controller method for the modal view:
[AllowAnonymous] // Change this to only submitters
public ActionResult NavRoleItems()
{
ViewBag.ProjectId = new SelectList(db.Projects, "Id", "Name");
return View();
}
Modal View
#using (Html.BeginForm("Create", "Tickets", FormMethod.Get))
{
#Html.AntiForgeryToken()
<div class="modal-body">
#Html.DropDownListFor(p => p.ProjectId, null, new { #class = "form-control"})
</div>
<div class="modal-footer">
<input type="submit" value="Add Issue" />
</div>
}
And I want to send any way I can really, but Ideally I want to send it as the projId variable below. Im in school and this is the first time I have played with Formmethod.Get.
Controller View
public ActionResult Create(string projId)
{
TicketCreateViewModel model = new TicketCreateViewModel();
var userId = User.Identity.GetUserId();
var user = db.Users.Find(userId);
model.OwnerUserId = userId;
model.OwnerUser = user;
model.ProjectId = projId;
model.AssignedToUserId = new SelectList(db.Users, "Id", "FirstName");
ViewBag.TicketPriorityId = new SelectList(db.TicketPriorities, "Id", "Name");
ViewBag.TicketStatusId = new SelectList(db.TicketStatuses, "Id", "Name");
ViewBag.TicketTypeId = new SelectList(db.TicketTypes, "Id", "Name");
return View();
}
Every way that I have tried it, html hidden and as a param in the begin form, doesn't work because it sees the #Model.ProjectId as null when it loads. As it should bc it is null. But how can I tell it to send it to the Create Tickets controller after the user selects and hits submit?
First Change This in your view
#Html.DropDownListFor(model => model.ProjectId, new SelectList(ViewBag.ProjectId , "Value", "Text"), "...", htmlAttributes: new { #class = "form-control" })
Change your controller variable name by ProjectId
It might Work fine
Another Way
You can add onclick in your button
function submitForm()
{
$.ajax(
{
url: 'Url',
type: "GET",
contentType: "application/json",
data: JSON.stringify({projId: $('ProjectId').val()}),
success: function(objStatus) {},
error: function(xhr, status, error)
{
if(status === "timeout")
{
alert(msg_timeout);
}
else
{
alert(msg_error);
}
},
});
}
why you set null the feeder argument of dropDownListFor ?
set with ViewBag.ProjectId content instead of null
Or
you can fill the viewbag with projects data and in view
do like this :
Controller
[AllowAnonymous]
public ActionResult NavRoleItems()
{
ViewBag.Projects = db.Projects;
return View();
}
View
add a model top of the page that it has ProjectId property
#Html.DropDownListFor(x => x.ProjectId, new SelectList(ViewBag.Projects, "Id", "Name"))
change Create action argument to type that insert top of the view
I am loading partial view with Jquery UI dialog. In partialview post submit I want capture results in updateSuccess function. But function was never called and results was showing on page.
Parent page
<script type="text/javascript">
function display_dialog() {
$('#my-dialog').dialog({
resizable: false,
modal: true,
show: "blind",
hide: "blind",
open: function (event, ui) {
$(this).load("/ContactSubmission/Index" );
}
});
function updateSuccess(data) {
if (data.Success == true) {
alert("Inside Update");
//now we can close the dialog
$('#my-dialog').dialog( "close" );
//twitter type notification
$('#commonMessage').html("Update Complete");
$('#commonMessage').delay(400).slideDown(400).delay(3000).slideUp(400);
} else {
$("#update-message").html(data.ErrorMessage);
$("#update-message").show();
}
}
</script>
controller
public ActionResult Index()
{
var contact = new Contact
{
Countries = Context.GetCountries()
};
return PartialView(contact);
}
[HttpPost]
public JsonResult Index(Contact contact)
{
if (ModelState.IsValid)
{
if (contact != null)
{
//Some business logic is here
var result = new { Success = "True", Message = "No Error" };
return Json(result, JsonRequestBehavior.DenyGet);
}
else{
var result = new { Success = "False", Message = "Contact is Null" };
return Json(result, JsonRequestBehavior.DenyGet);
}
}
else
{
var result = new { Success = "False", Message = "Invalid state" };
return Json(result, JsonRequestBehavior.DenyGet);
}
}
Partial view
#using (Ajax.BeginForm("Index", "ContactSubmission", new AjaxOptions{
InsertionMode = InsertionMode.Replace, HttpMethod = "POST",
OnSuccess = "updateSuccess" },new { enctype = "multipart/form-data",
#class = "form-horizontal", role = "form" }))
{
// Html code is here
<div class="form-group">
<div class="col-sm-4 col-md-offset-5">
<button type="submit" class="btn btn-primary">Submit</button>
<button type="button" class="btn btn-default" id="cancel">Cancel
</button>
</div>
</div>
}
after submit is showing like this on browser
How can I handle the issue and how to call function after submit results?
You need to reference jquery Unobtrusive otherwise the view will change.
http://www.nuget.org/packages/jQuery.Ajax.Unobtrusive/
This has caught me out before and as a result I find it much easier to write my own ajax calls in jQuery so I dont need this reference.
Good Luck
I am trying to create a sample MVC4 webpage with partialViews
on my parent page ,eg., Index.cshtml page I am displaying a partialView page which will allow the user to view/update profile photo
When the index page loads ,I need this partial page to show up the photo if photo is available
once the page is loaded ,when the user uploads a new photo,I need only the partialView page to do an ajax postback and show up the new photo .
I am able to load the page with photo fetched from DB,
I am able to Save new photo to db by clicking "#btnPhotoUpload" button.
But after saving the photo ,the partialview is not getting refreshed automatically.Please help me how to get my partialview page to refesh and display the updated photo.
Here is my index page ie., "Index.cshtml"
#model MvcSamples.Models.ViewModels.UserInfoViewModel
#{
ViewBag.Title = "Ajax Partial Postback demo";
ViewBag.UserId = 1;
}
<h2>PersonalInfo example</h2>
<div id="photoForm">
#Html.Partial("_UserPhoto")
</div>
<div id="OtherDetails">
#Html.Partial("_UserDetails")
</div>
Here is my PartialView, i.e. _UserPhoto.cshtml
#model MvcSamples.Models.ViewModels.UserInfoViewModel
#using (Ajax.BeginForm("SaveProfilePhoto", "Example", new { id = "1" }, new AjaxOptions { UpdateTargetId = "photoForm", OnSuccess = "onSuccess" }, new { encType = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<a>
<img id="imgPhoto" width="100px" height="100px"/>
<label for="photo">Photo:</label>
<input type="file" name="photo" id="photo" />
<input id="btnPhotoUpload" type="button" value="Apply" />
</a>
<script type="text/javascript">
$(document).ready(function () {
$("#imgPhoto").attr('src', "#Url.Action("GetProfileImage", "Example", new { id = ViewBag.UserId })");
$("#btnPhotoUpload").click(function (event) {
//on-click code goes in here.
event.preventDefault();
SavePhotoToDb();
});
function SavePhotoToDb() {
var json;
var data;
$.ajax({
type: "POST",
url: "/Example/SaveProfilePhoto",
data: new FormData($("#form0").get(0)),
dataType: "html",
contentType: false,
processData: false,
success: saveItemCompleted(data),
error: saveItemFailed
});
}
function saveItemCompleted(data) {
$("#photoForm").html(data);
}
function saveItemFailed(request, status, error) {
}
});
</script>
}
Here is my controller ExampleController:
namespace MvcSamples.Controllers
{
public class ExampleController : Controller
{
IUserDetails usr = new UserDetails();
// GET: /Example/
[HttpGet]
public ActionResult Index()
{
//usr.GetProfilePhoto(WebSecurity.GetUserId(User.Identity.Name));
if (!string.IsNullOrWhiteSpace(User.Identity.Name))
{
ViewBag.UserId = WebSecurity.GetUserId(User.Identity.Name);
}
UserInfoViewModel model = new UserInfoViewModel();
model.GenderList = usr.FillGenderTypesDropDownList();
return View(model);
}
[HttpPost]
public ActionResult SaveProfilePhoto(HttpPostedFileBase photo, UserInfoViewModel model)
{
string path = #"C:\Temp\";
if (photo != null)
{
model.UserId = 1;//WebSecurity.GetUserId(User.Identity.Name);
ViewBag.UserId = model.UserId;
var binary = new byte[photo.ContentLength];
photo.InputStream.Read(binary, 0, photo.ContentLength);
UserPicModel upModel = new UserPicModel();
upModel.UserPhoto = binary;
upModel.UserId = model.UserId;
usr.InsertProfilePhoto(upModel);
}
return PartialView("_UserPhoto", model);
}
public FileResult GetProfileImage(int id)
{
byte[] barrImg = usr.GetProfilePhoto(id);
return File(barrImg, "image/png");
}
}
}
Update:
As #David Tansey suggested ,I added code to refresh image inside SaveCompleted(data).
function RefreshImage() {
$("#imgPhoto").attr('src', function () {
// the datetime portion appended to the url avoids caching issues
// and ensures that a fresh image will be loaded every time
var d = new Date();
return this.src + '?' + d.getTime();
});
}
But the above code is refreshing the image only after I click the upload button twice .
Actually I need this to refresh the image immediately after the $("#btnPhotoUpload").click. Any suggestions?
I also tried disabling cache at the controller but no luck:
[OutputCacheAttribute(VaryByParam = "*", Duration = 0, NoStore = true)]
I am pretty sure the problem is that the browser is caching the image file and does not 'perceive' the need to bring it across the wire again after you upload a new one.
Look at the following post for a description of how to attach a dummy (yet dynamic) query string value to prevent the caching from occuring. I think this approach will solve your problem.
asp.net mvc jquery filling image
Hope that helps.
I have a form inside a partial view, it works but in case a server side validation error is raised it displays only the partial view. So I decided to use ajax for the submission (actually it makes sense as it is inserting a contact and there's a list in the main view).
Thing is, with the posted code if one of these errors comes it's properly displayed in the view (I'd need to make the partial visible again, but that's another thing), but if there's no error it'll display the list only in the partial view. I can do the other way round, displaying properly when there's no error but then not achieving the proper displaying of validation errors.
I would like to understand what's the best approach, or at least which are the possibilities: maybe change the code in the controller or do some kind of check in the success callback...
I edit what I had before, because when no error I should return the list, not the whole view as I posted before, but anyway I still have the doubt on how to tell one from another as both are succesfull calls to the post action
Thanks
The view is this one
#model ContactListViewModel
#{
ViewBag.Title = " My Contacts"
}
<div id="ContactList">
<h2>My Contacts</h2>
<hr />
<div id="addContainer">
#{ Html.RenderAction("AddContact"); }
</div>
<div id="editContainer" data-amp-url="#Url.Action("Edit", "Contacts")" class="initiallyHidden"></div>
#foreach (var group in Model.Contacts)
{
<div class="PlanContacts">
<div class="PlanName">#group.Key</div>
#foreach (var contact in group.Values)
{
<div class="Preview">
#Html.DisplayFor(m => contact, "Contact")
</div>
}
</div>
}
</div>
#section PageJavascript
{
<script src="~/Scripts/AMPContacts.js"></script>
}
The controller post action
[HttpPost]
public ActionResult AddContact(AddContactViewModel viewModel)
{
var partyId = (int) Session["PartyId"];
if (ModelState.IsValid)
{
_contactsManager.AddContact(viewModel, partyId);
// Here I should return the updated list
}
var newViewModel = _createBuilder.Rebuild(viewModel, partyId);
return PartialView("_AddContact", newViewModel);
}
And the ajax submission code inside the longer AMPContact.js
$('#addForm').submit(function (e) {
e.preventDefault();
var addContainer = $(document.getElementById('addContainer'));
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function(result) {
addContainer.html(result);
}
});
});
I understand this answer it's far from being good but maybe it's useful for somebody in terms of narrowing a possible search. As it would ended up being huge I rather post this update as an answer to my own question, not sure if it complies with good manners.
The explained one wasn't the only problem I faced, hard times as well with the reset of the form and specially the successive error submissions (once I got an error trying to submit it with errors again), so I ended up making a mess out of different solutions for different problems. Hopefully I will be able to clean it up
Thanks
In the view I use now
<div id="myContacts">
<h2>My Contacts</h2>
<hr />
<div id="addContainer">
<div class="toggler">
Add Contact
</div>
<div id="addToggling" class="initiallyHidden">
#{ Html.RenderAction("AddContact"); }
</div>
</div>
<div id="editContainer" data-amp-url="#Url.Action("Edit", "Contacts")" class="initiallyHidden"></div>
<div id="list">
#{ Html.RenderPartial("_ContactList", Model); }
</div>
In the .js
$('#addContainer').on('submit', '#addForm', ajaxCall);
function ajaxCall(e) {
e.preventDefault();
var addToggling = $(document.getElementById('addToggling'));
var contactList = $(document.getElementById('contactList'));
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result) {
if (result.passedValidation == true) {
// Json is returned with flag, we get the list from the server and update the list element
$.get(result.action, function (partial) {
contactList.html(partial);
});
// Add some effects and clear the form
$(document).scrollTop(0);
setTimeout(function () {
addToggling.slideUp(300, resetAddForm);
}, 500);
setTimeout(function () {
contactList.effect("highlight", {}, 3000);
}, 1000);
}
else {
// The form partial view is returned and displayed in the same element when there are validation errors
$(document).scrollTop(0);
addToggling.html(result);
$.validator.unobtrusive.parse('#addForm');
}
}
});
}
function resetAddForm() {
var addForm = $(document.getElementById('addForm'));
// Hhide the error messages
addForm.find("span.field-validation-error").hide();
addForm.find("div.validation-summary-errors").hide();
// Removes the class associated to errors
addForm[0].reset();
// Clear the inputs
addForm.find('input:text, input:password, input:file, select, textarea').val('');
addForm.find('input:radio, input:checkbox').removeAttr('checked').removeAttr('selected');
}
Controller with the existing action method slightly changed and a new one
public ActionResult ContactList()
{
var partyId = (int)Session["PartyId"];
var viewModel = _displayBuilder.Build(partyId);
return PartialView("_ContactList", viewModel);
}
[HttpGet]
public ActionResult AddContact()
{
var partyId = (int) Session["PartyId"];
var viewModel = _createBuilder.Build(partyId);
return PartialView("_AddContact", viewModel);
}
[HttpPost]
public ActionResult AddContact(AddContactViewModel viewModel)
{
var partyId = (int) Session["PartyId"];
if (ModelState.IsValid)
{
_contactsManager.AddContact(viewModel, partyId);
if (Request.IsAjaxRequest())
return Json(new { passedValidation = true, action = Url.Action("ContactList")});
return RedirectToAction("Index");
}
var newViewModel = _createBuilder.Rebuild(viewModel, partyId);
return PartialView("_AddContact", newViewModel);
}
In the code below, I have a form with some informations about an employee. I'd like feel in the password and post. If the password is correct the user is redirected to /Dashboard that it's ok.
When the password is wrong, I'd like show the error (_ErrorPartial) in MyError div and still see the form. I'd like see the form and the message in the div (MyError). With the code below, when the password is wrong, I see the error message but on a blank page and not a page with the layout + the form + the error message
Any idea ?
Thanks,
My page :
#model MyApp.EmployeeModel
<script type="text/javascript">
$(document).ready(function () {
$(".").click(function () {
$.ajax({
url: '/Account/Login',
type: "Post",
data: $('#MyForm').serialize(),
success: function (result) {
alert('Im here');
$("#MyError").html(result);
//Here is code to detect error or not but removed
}
});
});
});
</script>
<div id="MyError"></div>
#using (Html.BeginForm("Login", "Account", FormMethod.Post, new { id = "MyForm" }))
{
#Html.AntiForgeryToken()
<div>
#Html.LabelFor( m => m.FirstName)
<div>
#Html.HiddenFor(model => model.Username)
#Html.LabelFor( m => m.Password)
#Html.PasswordFor(m => m.Password)
<input type="submit" class="jqClick" />
</div>
</div>
}
Controller :
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(EmployeeModel employeeModel)
{
var Errors = new List<string>();
var employee = .... //Get employee from DB
if (employee == null)
{
Errors.Add("Employee trouble");
return PartialView("_ErrorPartial", Errors.AsEnumerable());
}
return RedirectToAction("Index", "Dashboard");
}
My _ErrorPartial view :
#model IEnumerable<string>
<div id="DisplayError">
#{ var error = string.Empty; }
#foreach (var err in Model){
error += err + "<br>";
}
#Html.Raw(error)
</div>
You have to make some changes to your javascript code. First of all, replace jQuery selector $(".") with specific one, like $(".jqClick"). Second of all, never hardcode urls like /Account/Login in your code, use UrlHelper for that. And the third thing is, you have to prevent a default behavior of the submit button. See the changes:
$(document).ready(function () {
$(".jqClick").click(function (e) {
e.preventDefault();
$.ajax({
url: '#Url.Action("Login", "Account")',
type: "Post",
data: $('#MyForm').serialize(),
success: function (result) {
alert('Im here');
if (result.redirectUrl)
window.location.href = result.redirectUrl;
else
$("#MyError").html(result);
//Here is code to detect error or not but removed
}
});
});
});
Also you need to change the Login action a little in order to make a redirect if a login is success:
public ActionResult Login(EmployeeModel employeeModel)
{
var Errors = new List<string>();
var employee = .... //Get employee from DB
if (employee == null)
{
Errors.Add("Employee trouble");
return PartialView("_ErrorPartial", Errors.AsEnumerable());
}
return Json(new { redirectUrl = Url.Action("Index", "Dashboard") });
}
The probable error that you throw from the server side, won't appear in success function. Instead you should have below
error:function(response)
{
//your code here
}
Simply throw exception from the controller.