Modal editing in MVC - c#

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.

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 controller action launched from modal crashes losing jQuery context

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" />
}

Is it possible to kick off a javascript function after a partial view renders in MVC Asp.net?

Let me preface this question with the fact that I am very new to MVC.
I have an instance where I am rendering a devexpress grid in a partial view.
#Html.Partial("MyGridPartial", Model)
I need to kick off a javascript function at the moment that the model has been populated via this partial view render. I attempted to do this via this. :
settings.ClientSideEvents.EndCallback
I can get to this point, but at that time I do not have the model itself populated so it does no good. I was wondering if anyone is aware of a generic way of kicking/attaching to a partial view render in order to jump into some clientside javascript code.
If it's a PartialView you're rendering in a View on the serverthen Dave's method would work best. Simply wire-up your code to the DOM ready event.
$(document).ready(function(){
//Javascript logic to fire goes here
});
or if you prever the shorthand version...
$(function(){
//Javascript logic to fire goes here
});
If you're rendering a partial view that is being loaded via Ajax then the same method will work. jQuery will run javascript in the html passed back to the client via Ajax once it's attached to the DOM if I recall correctly (feel free to test this I'm just going by memory about it firing once attached to the DOM, but I believe this is a feature of the load() method), assuming the javascript you want to run is in the response. If it's in the parent page sending the Ajax request then you're best bet is to hook it up to the complete event. (I'm populating the parameter on the client side here)
$("#wrapperAwaitingContent").load("/Grids/MyGridPartial", {id: null /*parameters*/}, function(text, status, xhr){
//Javascript logic to fire goes here
});
For me the url used in the .load() call is resolved using the UrlHelper on the server
$("#wrapperAwaitingContent").load("#Url.Action("MyGridPartial", "Grids")", {id: null /*parameters*/}, function(text, status, xhr){
//Javascript logic to fire goes here
});
You also have the option of doing something similar to this using Unobtrusive Ajax. (I'm populating the parameter on the server side here)
#Ajax.ActionLink("Load Data", "MyGridPartial", "Grids", new { id = null/*parameters*/ }, new AjaxOptions() { UpdateTargetId = "wrapperAwaitingContent", OnComplete="onCompleteMethodName" })
There are more properties you can set for the AjaxOptions other than the element to receive the HTML and the method to call when it's finished but I find I'll reuse functions defined in a shared javascript file and populate them only if they are not already populated from there, something like this...
$("a[data-ajax='true']").each(function () {
var ajaxUpdate = $(this).closest("data-ajax-container");
$(this).attr("data-ajax-update", $(this).attr("data-ajax-update") ? $(this).attr("data-ajax-update") : ajaxUpdate);
$(this).attr("data-ajax-mode", $(this).attr("data-ajax-mode") ? $(this).attr("data-ajax-mode") : "replace");
$(this).attr("data-ajax-success", $(this).attr("data-ajax-success") ? $(this).attr("data-ajax-success") : "AjaxSuccess");
$(this).attr("data-ajax-complete", $(this).attr("data-ajax-complete") ? $(this).attr("data-ajax-complete") : "AjaxComplete");
$(this).attr("data-ajax-failure", $(this).attr("data-ajax-error") ? $(this).attr("data-ajax-error") : "AjaxError");
});
If you are rendering this partial as part of the normal flow of a View being rendered, the answer is NO.
Reason for this is the Partial is converted into a string before the parent View is even rendered. At that point, none of your markup has been seen by the browser, no jscript has been read.
If, on the other hand, you rendered the partial in your JQuery Ready function:
$(document).ready(function() {
I think you would need to use an Action Partial (Partial that gets called by an action method). Action Partials can be called within your JQuery Ready function by referencing the url (restfully):
$('#divMyGridPartial').load('/Grids/MyGridPartial/{id}');
and any follow up jscript/jquery functions can be called within the ready series.
The other advantage of an Action Partial, the Model is formed within the action method and can be created contextually to what you need (ideally hinging off an id passed).

Lazy loading Telerik window content

I am working in ASP.NET MVC 3 and using Telerik. I have an empty telerik window. My goal is to only ask server for content when the user clicks a button, however I have no idea how this could be done. I imagine that to accomplish this I would need to store a reference to the telerik window in question. But how do i do this? plz help. This is how far I have got:
#{
Html.Telerik().Window()
.Name("ImportDialog")
.ClientEvents(events => events.OnOpen("DialogOpen"))
.Visible(false)
.Title("Import users")
.Draggable(false)
.Resizable(resizing => resizing.Enabled(false))
.Modal(true)
.Width(400)
.Height(400)
.Render();
}
I do want do do somethin in DialogOpen function, or alternatevly replace that client side function with a server side funciton....
You should use the client API of the Telerik Window and more specifically the ajaxRequest method.
So for example when the button is clicked you should get the client object and call the ajaxRequest method which will make the Window retrieve its content from that MVC action method.
e.g.
function DialogOpen(){
var myWin= $('#ImportDialog').data('tWindow');
myWin.ajaxRequest("Controller/Action", { name: "John",location:"Boston"});
}
I found one type of answer, however i am not sure yet if it is the best.
In the javascript function DialogOpen I send an ajax request to an url(also known as an Action of a Controller in MVC), the I put the result in the content of the dialog window, like so:
function DialogOpen ()
{
$.ajax({
type: "POST",
url: "Controller/Action",
data: "name=John&location=Boston",
success: function (htmlContent)
{
$('#ImportDialogContent').html(htmlContent);
},
error: function ()
{
$('#ImportDialogContent').html("<p>Could not import user data at this time</p>");
}
});
}
P.S.: I gave an ID to content area of telerik Window(ImportDialogContent)!

MVC 4 with jQuery Mobile page seems to be caching and controller method not being called

EDIT
Figured it out.. $.ajaxSetup({ cache: false }); wasn't working because i had data-ajax set to turned off on the form and therefore i wasn't able to set no cache ajax on it.. just for completeness if anybody knows how to get this done WHILE dada-ajax is set to false then please post so here
Something else that I just tried and it worked was to simply add data-ajax="false" to any link that you want a page refresh on. Meaning that if I have data-ajax="false" on a link it will always refresh the page before showing it!
For example the link I had a problem with was
Add a new weekly update
and the problem was that for some reason that page was caching and always showing the cached page.. So one of the easy fixes was to add data-ajax="false" to it and that forced a reload of the page everytime
Add a new weekly update
````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
Question:
I have a options menu which brings up a form which also has a cancel and submit button.
Once the form is submitted there is certain validation that runs and if something is missing it returns to the form with some validation text.
Now if I click the cancel button at anytime i should be brought back to the options menu and if I click on the same button that brings up the form i should see a brand new clean form and this works fine if I do it before the validation.
The problem is that if I submit a non valid form which returns with the error validation messages and THEN press cancel it seems that the page becomes cached or something similar because from that point on anytime I click on the form options menu button the same form shows up each with the validation errors and data. I put a break point in the method that returns the form View() and they are never hit so for some reason it skips the entire method which creates a new form and somehow just shows the old page.
The cancel button is the following
Cancel
Does anybody know what is happening? is it being cached somehow when it returns to the same page with the validation errors??
** EDIT **
I Tried adding [OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")] in front of the controller by to no avail..
I also now added
$.ajaxSetup({ cache: false });
to the top of my $(document).ready(function () but that also does not seem to be doing anything, do I just put it there or do I have to call it somehow?
I checked System.Web.HttpContext.Current.Cache and the page doesn't show up there.
Have you tried setting the output cache options on the controller action like
[OutputCache(Duration=600,VaryByParam="id")]
You might also want to try making sure jquery is not caching the request as well. You can globally turn off jquery ajax caching using the information here: How to set cache: false in jQuery.get call
You can try using $.mobile.changePage() to transition to the page, it allows you to set some options, one of which is reloadPage.
reloadPage (boolean, default: false) Forces a reload of a page, even if it is already in the DOM of the page container. Used only when
the 'to' argument of changePage() is a URL.
Source: http://jquerymobile.com/demos/1.1.1/docs/api/methods.html
You could work this into your link with something like:
<script>
function changeMyPage(url) {
$.mobile.changePage(url, { reloadPage : true });
}
</script>
Cancel
jquery Mobile pulls multiple pseudo-pages into the DOM at one time and normally deletes (.removes()) a pseudo-page after you've navigated away from it. It however sounds like that's not happening so you may need to use my above code (or something similar) to force a refresh of the page.
You need to clear your ModelState before hand.
This should work:
if (ModelState.IsValid)
{
//saving
if (result > 0)
{
**ModelState.Clear();**
return View(new CategoryViewModel());
}
}
How to Clear model after submit the data in database in MVC3

Categories