MVC controller action launched from modal crashes losing jQuery context - c#

I am having a problem that is similar to these two StackOverflow posts here and here. However my situation is just different enough that I'm not able to make the correction needed.
Like those two post I seem to be losing context as the browser does the post controller action.
The scenario is is follows:
Page Loads
User clicks add button.
Modal dialog loads partial view with fields needed to add new object.
Form is completed and user clicks save.
Controller action for add completes (add record just fine) then redirects to the controller action for step 1. The thought was this would close the modal and return user to page in step 1.
The problem is of course that the redirect results in a $ (jquery) is undefined error.
Step 1 controller action:
public ActionResult Configure()
{
....code...
return View("~/FolderPath.../Configure.cshtml", model);
}
Step 2 jQuery to launch modal:
$("#new").click(function (e)
{
e.preventDefault();
var window = $("#window").kendoWindow(
{
content: {
url: "#Html.Raw(Url.Action("CreateEditRecord", "Controller"))",
data: { .... }
}
});
window.data('kendoWindow').center();
});
Step 3 & 4 Modal submit:
$("#save").click(function (e)
{
e.preventDefault();
...some validation stuff here.....
$("#formCreateEditRecord").submit();
});
Step 5 (These are the different methods I have tried and they all lose jQuery context)
public ActionResult CreateEditRecord(NewRecordModel model)
{
...add new record to db etc.....
//return RedirectToAction("Configure");
//return Configure();
//return PartialView("~/FolderPath/CreateEditRecord.cshtml", model);
//tried this last one to return to modal window just trying to figure things out.
}
Edit
#SSA & #Ryios. Your explanations were very clear and do solve the problem but if you could bear with me a few seconds more....
I understand the partial view doesn't have the full weight (scripting tags etc.) of the full page. What I am still unclear of is WHY does it even go back to the partial view? My post controller action in step 5 redirects to configure action which is a full view so I would have expected the page to load the full view.
Does the MVC view engine still render the partial view even just for a few milliseconds before changing and reloading the full page again?

I think 2 steps more,
Manually submit the form.
Return a JsonResult from action CreateEditRecord after a database operation.
//In controller action
public JsonResult CreateEditRecord(NewRecordModel model)
{
// Do your database stuff here....
//Then return Success or Failure based on your database result.
return new JsonResult { Data = new { Result = "Success/Failure" } };
}
Then when you call a submit form.
$("#save").click(function (e)
{
e.preventDefault();
...some validation stuff here.....
$("#formCreateEditRecord").submit(
function(e){
e.preventDefault(); //As we will manually submit the form
$.ajax({
type: "POST",
url: "#Html.Raw(Url.Action("CreateEditRecord", "Controller"))",
data: $(this).serialize(),
success: function(data) {
//here we check if database called resulted in Success/Failure
if(data.Result === "Success")
{
//Write your code to close the dialog.
}else
{
//Show error message or whatever.
}
}
})
});
}

"#Html.Raw(Url.Action("CreateEditRecord", "Controller"))"
That is rendering a partial view. Partial views being what they are, typically don't have the full page weight, e.g. they don't have all your script tags.
When it loads as the content for the modal dialog it (itself) does not have jquery because you don't have script tags to load jQuery on the partial view.
So either, add jquery to your partial view, or refer to jQuery on the parent window.
"Note: I'm not sure if this next code stub will work or not as jQuery might be attached to the dom on the parent window and not find any elements on your Modal Window. I know there is a way to do it though, you just might have to mess with your jQuery code a little bit.
$ = window.parent.$;
$("#save").click(function (e)
{
e.preventDefault();
...some validation stuff here.....
$("#formCreateEditRecord").submit();
});
With Ajax, partial views work because they are used to update portions of the original page. However, with a modal dialog it's an entirely new page (in a sub window).
If you were using an InPage Modal Dialog solution like jQuery UI Dialogs, then you would have jquery available from your original page.
I suggest designing a base template for your partial views, something like
<div class="modal-dialog">
<div class="title">#this.ViewBag.ModalTitle</div>
<div class="body">
#RenderBody()
</div>
</div>
<!--Load Scripts/Styles Here-->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
#RenderSection("scripts", false)
Then you can create partial views that use it, like
#{
Layout = "~/Views/Partials/Dialogs/BaseDialog.cshtml";
}
<p>This is a test dialog</p>
#section scripts
{
<script src="anotherscripttoaddhere" />
}

Related

Handling multiple forms on a page

I have a page that is mostly informational data (not an input form, overall). And it has a title that can be edited.
Clicking the Edit Title button pops up a modal window where the user can edit the title and hit submit.
Because this form is a very small part of the page, and because I may need other small forms on the same page, I just created a small handler in the controller to handle this one submit.
[HttpPost]
public ActionResult PageTitle(string title)
{
// ... Save new title to the database ...
// I have no PageTitle page, so just redirect back to the source page
return RedirectToAction("Index");
}
This works okay, but I don't like the extra redirect here. A redirect back to the client causes another round trip back to the server.
Is there a more efficient way to handle small forms on a page, where you don't want to resubmit every input element on the page each time?
I don't think there is a convenient way how to avoid the redirection after a post request (without using Ajax).
You can of course return any View:
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult ProcessForm1(FormOneData data)
{
// process form
return View("Index");
}
[HttpPost]
public ActionResult ProcessForm2(FormTwoData data)
{
// process form
return View("Index");
}
So the user will get the same page but the problem with this solution is that the url will be different.
You can have one function for GET request, another for POST requests, both with the same URL. But than you would have to figure out how one action method would handle more forms - doable but terrible for maintenance.
And this can get even more complicated if those forms can appear on more than one page.
But actually, it solves one thing. With the redirection you avoid the re-POST dialog that user gets if they press F5 (and sometimes submit the form again by mistake).
As #TiesonT. points out, post/redirect/get is a standard pattern. No, it doesn't seem like the most efficient. But without doing AJAX or something a little more elaborate, there is really no way to postback to a specialized handler and then refresh the page without a redirect.
Was hoping I overlooked something simple but it looks like I did not.
What about sending form data via Ajax as shown below?
#model Models.YourModel
$('form').submit(function (event) {
event.preventDefault();
//Send the values of all form controls within the <form> tags including the token
var formdata = $('#frmRegister').serialize();
$.ajax({
type: "POST",
url: '#Url.Action("ProcessForm", "Controller")',
cache: false,
dataType: "json",
data: formdata,
error: function (response) {
$("#error_message").html(data);
},
success: function (data, textStatus, XMLHttpRequest) {
$('#result').html(data);
}
});
});
You can also use Partial Views in order to use multiple forms or fragment on the same View.

MVC AJAX Form will not submit after a different AJAX post is called

This is an odd issue, and I'm sure it's something simple, but I couldn't find another post with the same issue. I have a MVC View with an AJAX.BeginForm() that posts to my controller. Also, I have a search box (input) above the form that when the Enter key is pressed, it performs a JQuery ajax post to my controller (GetData) to auto-populate some of the form fields before submitting it. What's strange, is if the search field is used, it successfully fills the form fields with data, but the form's submit button stops working (form does NOT submit at all)! Now, if I do NOT use the search field (which performs the ajax POST) and manually fill in the form fields, then hit submit, it correctly submits to my controller.
The GetData (field pre-filler action) returns a JSONified model object which I read into the form objects.
The form is in a PartialView with the master ViewModel as it's model. The search box is in the _Layout page with no model on the razor view.
Code below is simplified to the relevant chunks (let me know if I omitted something important):
_Layout.cshtml:
<body>
<div id="bodyFrame">
#RenderBody()
</div>
<footer>
<span>Project Number:</span><input id="projectSearch" type="text" />
<div id="inputFrame">
#{ Html.RenderPartial("InputPartialView"); }
</div>
</footer>
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("~/bundles/bootstrap")
#Scripts.Render("~/bundles/scripts")
#RenderSection("scripts", required: false)
</body>
InputPartialView.cshtml:
#using (Ajax.BeginForm("AddEntry", new AjaxOptions() { HttpMethod = "POST", UpdateTargetId = "bodyFrame", InsertionMode = InsertionMode.Replace }))
{
/*Table with numerous HTML helper input fields for my model*/
<input type="submit" value="Submit" />
}
Main.js (method to pre-fill form data on Enter key press in search box):
$('#projectSearch').keyup(function (event) {
if (event.keyCode == 13) {
$.ajax({
url: '/Home/GetData',
dataType: 'json',
type: 'POST',
data: {
project: $(this).val()
},
success: function (data) {
$('#tb_Client').val(data.Client);
/*Other field values filled the same way*/
},
fail: function () {
alert('fail');
}
});
}
});
controller actions:
[HttpPost]
public JsonResult GetData(string search)
{
var result = new SubModel(); //This is a submodel of the main viewmodel
/*Pull in data via SQL into result*/
return Json(result);
}
[HtppPost]
public ActionResult AddEntry(ReportLogViewModel model)
{
/*Only works if search function is not called*/
return PartialView("Index", ViewModel.Generate());
}
So to summarize: The search function (GetData) always works, the form only works when the search function is not used (once it is used, the form does not submit to the controller), no errors occur in the browser console. Normal usage would be:
Type query in search input, press enter
GetData action is called, data is retrieved and returned via $.ajax() and then the form fields are populated with the returned data
User correct/amends any autocompleted data, fills in manual data, then hits Submit button
AddEntry action is called to enter the form data into the database, then return a newly generated partialview with the newly entered record.
Thanks in advance for any help!
UPDATE 1: It seems commenting out the lines of code in the main.js ajax success command allows the form to submit normally (albeit without the data the GetData method received). In this case removing "$('#tb_Client').val(data.Client);" allows the form to submit. This doesn't solve my problem but further pinpoints the problem to these lines of code in main.js.
UPDATE 2: The problem indeed was one of the javascript/jquery value setters on one of my inputs. Something was wrong with the JSON object. I commented them out one by one till I found the culprit then went back to my controller/viewmodel to the method that set the values and corrected the problem.
This is more appropriate for a comment but I need more reputation to reply to people on this site for some reason.
With your update, it makes me think that you're using the #tb_Client ID in more than one place but I can't confirm that without seeing the rest of the program.
Alternatively, instead of submitting the data encapsulated into a single object, you can send variables individually:
data : { variable : 'variable',
variable2 : 'variable2'},

Load partial view after main view

I have several partial views that have been added to my main view on my MVC site. One of the partial views takes a long time to load however, as it can take a while to retrieve the data for it. Is it possible to load the main view and start loading the partial views, but if the view is still loading then display the rest in the meantime, with the final one showing once it has finished?
On a similar note, while a partial view is loading (or being refreshed) how can I get some kind of "Loading" screen to show over the view area so that the user knows that something is happening?
Thanks
Try the below codes
$.ajax(
{
url: '/Controller/Action',
data: { Id: '1' }, //input parameters to action
beforeSend: function () {
$('#div-result').show();
var img = '<img src="../../Images/loading.ico" />';
$('#div-result').html(img);
},
success: function (data) {
// $('#div-result') -> div in the main view
$('#div-result').html(data);
}
});
As Alexey says, simply add content element(s) where you need the partial views
<div id="partialViewHolder" style="display:none">Ajax content goes here after initial page loads, but it is hidden until then</div>
And then in your javascript, add something like (assuming you have already loaded jquery into the DOM):
<script type="text/javascript">
$(window).load(function () {
$( "#partialViewHolder" ).load( "#Url.Content("Your PartialView Controller Method Goes Here")" );
$( "#partialViewHolder" ).show();
}
</script>
Note that you need to use $(window).load() instead of $(document).ready() as a trigger to allow the page to render while the partial view is loading.
As I understand, you want to get partial view async. You can use Ajax to draw partial view, for example, by using jQuery ajax to get data from server and draw it, when ajax request is done. So your main view will be loading much faster.

Using jQuery with partial views in asp.net mvc 4

I have a View with the following layout
The parent View is composed of several PartialViews as ilustrated in the picture. One of which is a list where each item has a corresponding Edit button which loads the item into another PartialView, but this is loaded via ajax into a modal dialog-bootstrap. This part works fine.
The problem I have is that no script or jquery event related to the controls of this modal gets executed. For example datepicker widget is never displayed, can not capture the change event of the dropdown, or capture the submit event for the form or the click event of Submit button.
All the scripts are placed in the main View. for example this is the event handler for the modal submit:
$(function () {
$('#myModal form').on('submit', function () {
console.log("okk");
clearErrors();
$.post($(this).attr('action'), $(this).serialize(), function (data, status) {
$('#myModal').modal('hide');
$("#details").html(data);
}).error(function (error, status, a, b) {
$('.modal-body p.body').html(error.responseText);
});
return false;
});
});
In my _Layout.cshtm I have included the necessary scripts (I think):
#Scripts.Render("~/js")
#Scripts.Render("~/bundles/globalization")
#RenderSection("scripts", required: false)
</div>
</body>
where "~/js" is:
bundles.Add(new ScriptBundle("~/js").Include(
"~/Scripts/jquery-{version}.js",
"~/Scripts/jquery-migrate-{version}.js",
"~/Scripts/bootstrap.js",
"~/Scripts/bootstrap-datepicker.js",
"~/Scripts/jquery.validate.js",
"~/scripts/jquery.validate.unobtrusive.js",
"~/Scripts/jquery.validate.unobtrusive-custom-for-bootstrap.js",
"~/Scripts/locales/bootstrap-datepicker.es.js"
));
What could be the problem with my scripts and jQuery for this dialog ? Indications from similar questions in the site have not worked for me so far.
I have tried to express as clearly as possible if something is not understood the code or the illustrations I'm all ears. Thank you
As Partial View is loaded via ajax you need to initialize datepicker after the html is rendered on page so you need to put datepicker initialization script in success function callback:
$.post($(this).attr('action'), $(this).serialize(), function (data, status) {
$('#myModal').modal('hide');
$("#details").html(data);
$("#textBoxID").datepicker(); // initialize datepicker for partial view element here
})
For events you can write delegated events to make them work,choose the closest element of the partial view, i am using container of partial in which you are appending partial view html:
For click event:
$("#details").on("click","#someButtonId",function(){
// write event code here
})
For more detials of on() you can see HERE
If they are being loaded in by ajax you may need to give more context to your selectors.
$(document).on('submit', '#myModal form', function () { ...

Modal editing in MVC

I've been searching for a way to update data using a modal pop-up.
Right now I'm using devexpress, because we're already using other devexpress controls (but this could change if a jquery library would be easier!!)
I'm stuck with the validation aspect. Frankly, the entire process seems quite hard for what I'm trying to achieve.
Anyways, let me describe the process that I've currently worked out:
-The Index page contains an overview of the different elements that can be updated. Using a HtmlExtension, I was able to create a devexpress popup which loads the edit page when you open the popup. => #Html.PopupControl().WithText("Edit").PopupGoesTo(Url.Action("EditPopup", etc etc)
-The edit page -which is just a partial view- works just fine.
I've created a little test page, which contains 1 textbox, which takes in a decimal.
I want to submit the form using ajax (because frankly, I have no idea how I can show the validation if I do a full post back, since I need to be able to create the popup and bind the data to it AND trigger the validation errors).
<script type="text/javascript">
function EndPopUpUpdate(message) {
if (message.url) {
window.locatin.href = url;
}
$("#submitButtonPopUp, #loadingPopUp").toggle();
}
function BeginPopUpUpdate() {
$("#submitButtonPopUp, #loadingPopUp").toggle();
}
</script>
using (Ajax.BeginForm("Edit", "xxx", new AjaxOptions { UpdateTargetId = "PopUpDiv", HttpMethod = "Post", OnBegin = "BeginPopUpUpdate", OnComplete = "EndPopUpUpdate"}, new { id = "PopUpForm" }))
{
<div id="PopUpDiv">
#Html.Partial("EditPopup", new xxxViewModel())
</div>
}
I was able to achieve validation when I postback by manually rehooking the jquery events (because these don't get hooked since the page gets loaded dynamicly)
function ReconnectValidation() {
$("#PopUpForm").ready(function () {
$.validator.unobtrusive.parse("#PopUpForm");
});
$("#submitButton").click(function (e) {
var form = $("#PopUpForm");
if (!form.valid()) {
e.preventDefault();
}
});
}
So this handles my client side validation, which works.
Now, my actual problem! Server side validation.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([ModelBinder(typeof(CommandModelBinder))] UpdateCommand command)
{
if (!ModelState.IsValid)
{
//using the command pattern
var handlerResult = HandlerLocator.GetQueryHandler<IGetOverviewHandler>().Execute(..);
return PartialView("EditPopUp", handlerResult.ViewModel);
}
HandlerLocator.GetCommandHandler<UpdateCommand>().Handle(command);
var returnLink = Url.Action("Index", new {..});
return Json(new { url = returnLink }, JsonRequestBehavior.AllowGet);
}
I've written a CustomModelBinder, which does nothing but look for the properties in my command object (my return model if you will) and looks in the formcollection if it can find a matching object with the same name. Then it tries to convert it and it binds a ModelError to my ModelState if it fails.
So, now we have a ModelState which is either valid or is invalid.
If it's valid, I want to redirect to the Index (so my overview can update). I've read that I should handle this in the client side, because the ajax.BeginForm is going to replace the
"PopUpDiv"-div with the result (which just creates the same page within my page).
Here is the onComplete event:
function EndPopUpUpdate(message) {
if (message.url) {
window.locatin.href = url;
}
$("#submitButtonPopUp, #loadingPopUp").toggle();
}
The problem is, that I don't receive a json message, but I receive a PartialView. This means I can't access the message.url..because that's not what I recieve :/
So that is problemo number 1
If the object is not valid, I want to return a partialview with the model and give an error to the user. When I return the partialview, it just replaces the current view but it doesn't show any validation errors..
That is problem number 2 :)
Also, if you know a better way to solve this problem, please don't hesitate to respond (because this method just seems really convoluted for what it does -or should do-)
Sorry for the lengthy post, but I hope everything is clear.
Thanks for your help & time!
I've used the dialog plugin from jQuery UI () before, which I've found works well. I normally get the links to open up in an iframe within the popup, which avoids the problem you describe where the jQuery validation events don't get hooked up because the page gets loaded dynamically - both client side and server side validation should work as normal, just within this iframe.
The nice thing about this technique is that you can generate action links as normal within your index page, just add a class of popup to them e.g.
#Html.ActionLink("Edit", "Edit", new { id = Model.Id }, new { #class = "popup" })
Then, you can get these links to open in a dialog iframe with jQuery like:
$("a.popup").click(function(e) {
e.preventDefault();
$("<iframe />").attr("src", $(this).attr("href") + "?popup=true").dialog({ show: "fadeIn", modal: true, width: 300, height: 300});
});
This basically looks for popup links, cancels the default behaviour (page navigation) and opens that URL in a iframe within a popup, adding a querystring to identify that the page is within a popup. The reason for this querystring and knowing its a popup allows you to load a different layout page within the view, maybe through an action filter e.g.:
public class Popup : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Result != null
&& filterContext.Result is ViewResult
&& filterContext.RequestContext.HttpContext.Request["popup"] == "true")
(filterContext.Result as ViewResult).MasterName = "~/Views/Shared/_PopupLayout.cshtml";
}
}
This means you can easily apply this attribute to classes where you want action methods to apply. This method also means if you change your mind about the implementation in the future (ie removing the popups) then you can easily remove the jQuery that cancels the clicks and your app will continue to function as a normal MVC app with separate pages, and all the navigation/validation etc will 'just work'.
I hope that all makes sense and helps.

Categories