MVC Pass IDs from view to controller from checkbox multiselection - asp.net-mvc

At the moment i have a table with a column fill with checkbox for each ID user. and i want to pass that value from checkbox checked from view to controller to perfom some action in actionresult CreatePlanning.
How can i do it ?
<td data-field="#Html.DisplayNameFor(model => model.Status_Coordinator)">
#Html.DisplayFor(model => item.Status_Coordinator)
<input id="Status_Coordinator" type="checkbox" name="Status_Coordinator" value="true" />
</td>

There are probably other ways to do this, but I have done it like this previously - Create a new Checkbox class like:
public class CheckboxModel
{
//Value of checkbox
public int Value { get; set; }
//description of checkbox
public string Text { get; set; }
//whether the checkbox is selected or not
public bool IsChecked { get; set; }
}
Initialise static list of users, to give you an idea (you probably have to generate it dynamically):
ListOfUserID = new List<CheckboxModel>
{
new CheckboxModel { Value = 1, Text = "User1" },
new CheckboxModel { Value = 2, Text = "User2" },
new CheckboxModel { Value = 3, Text = "User3" }
};
Use this class in the view (for example in a loop):
#Html.CheckBoxFor(m => Model.ListOfUserID[i].IsChecked)#Model.ListOfUserID[i].Text
#Html.HiddenFor(m => Model.ListOfUserID[i].Value)
#Html.HiddenFor(m => Model.ListOfUserID[i].Text)
Then you have the text or value of the checkbox in Controller action when the form is posted.

To select use Checkboxfor
`
Select your Order:
#foreach (var x in Model)
{
#Html.CheckBoxFor(modelItem => x.isSelected, new { #id="test"
,value = (x.Orderumber) })
}
Get Values
To pass value To the controller use JQuery as:
<script>
$(document).ready(function () {
$("#b1").click(function () {
var favorite = [];
$.each($("input[name='x.isSelected']:checked"), function () {
favorite.push($(this).val());
});
alert("Your order are: " + favorite.join(", "));
window.location.replace("https://localhost:44304/SecondPage/?
id="+favorite.join(", "));
});
});
</script>

Related

How to shorten the url when using pagination and filtering with multiple checkboxes

I am using PagedList for server side paging, and also have a textbox in the view for filtering the data, along with checkboxes to determine which fields in my model to filter based on the search text.
My current code is
View model
public class SearchPagingViewModels
{
public IPagedList<AllResolution> Resolutions { get; set; }
public string Keyword { get; set; } // serach text
public bool IsResYearChecked { get; set; } // used to filter the ResolutionYear field
public bool IsResNumChecked { get; set; } // used to filter the ResolutionNumber field
public bool IsResTextChecked { get; set; } // used to filter the ResolutionText field
}
Controller
public ViewResult Index(int? page string keyword, bool? isResYearChecked, bool? isResNumChecked, bool? isResTextChecked)
{
int pageSize = 25;
int pageNumber = (page ?? 1);
bool searchYear = isResYearChecked.GetValueOrDefault();
....
IQueryable<> resolutions = db.AllResolutions;
if (searchKeyword != null)
{
if (searchYear)
{
resolutions = resolutions.Where(x => x.ResolutionYear.Contains(searchKeyword));
}
....
}
resolutions = resolutions.OrderBy(c => c.ResolutionYear).ThenBy(c => c.ResolutionNumber);
SearchPagingViewModels model = new SearchPagingViewModels
{
Keyword = keyword,
IsResYearChecked = searchYear,
....
Resolutions = resolutions.ToPagedList(pageNumber, pageSize)
};
return View(model);
}
View
#model SearchPagingViewModels
....
#using (Html.BeginForm("Index", "Resolutions", FormMethod.Get))
{
#Html.LabelFor(m => m.Keyword)
#Html.TextBoxFor(m => m.Keyword)
#Html.LabelFor(m => m.IsResYearChecked)
#Html.CheckBoxFor(m => m.IsResYearChecked)
// .. ditto for IsResNumChecked etc
<input type="submit" value="search" />
}
<table>
<thead>
....
</thead>
<tbody>
#foreach (var task in Model.Resolutions)
{
// .... build table rows
}
</tbody>
</table>
#Html.PagedListPager(Model.Resolutions, page => Url.Action("Index", new { page, Keyword = Model.Keyword, IsResYearChecked = Model.IsResYearChecked, IsResNumChecked = IsResNumChecked IsResTextChecked = Model.IsResTextChecked }))
While this works, the issue is that the for generates a long and ugly query string, for example
.../Index?Keyword=someText&IsResYearChecked=true&IsResYearChecked=false&IsResNumChecked=false&IsResTextChecked=true&IsResTextChecked=false
And now I want to add additional bool properties for filtering the records making it even worse and potentially exceeding the query string limit.
Is there a way to shorten the URL? Would this be related to routing? Would a new ViewModel be in order to accomplish this?
Your could replace all your bool properties with an enum marked with the [Flags] attribute where each value in the enum represents a property in your model to search.
[Flags]
public enum FilterProperties
{
None = 0,
ResolutionYear = 1,
ResolutionNumber = 2,
ResolutionText = 4,
.... // more properties
}
and the view model will be
public class SearchPagingViewModels
{
public string Keyword { get; set; }
public FilterProperties Filter { get; set; }
public IPagedList<AllResolution> Resolutions { get; set; }
}
The controller method then becomes
public ViewResult Index(int? page string keyword, FilterProperties filter = FilterProperties.None)
{
IQueryable<AllResolution> resolutions = db.AllResolutions;
if (searchKeyword != null)
{
if (filter.HasFlag(FilterProperties.ResolutionYear)
{
resolutions = resolutions.Where(x => x.ResolutionYear.Contains(feyword));
}
// .... more if blocks for other enum values
}
resolutions = resolutions.OrderBy(c => c.ResolutionYear).ThenBy(c => c.ResolutionNumber);
SearchPagingViewModels model = new SearchPagingViewModels
{
Keyword = keyword,
Filter = filter,
Resolutions = resolutions.ToPagedList(pageNumber, pageSize)
};
return View(model);
}
You view will then be
#using (Html.BeginForm("Index", "Resolutions", FormMethod.Get))
{
#Html.LabelFor(m => m.Keyword)
#Html.TextBoxFor(m => m.Keyword)
#Html.ValidationMessageFor(m => m.Keyword)
#Html.HiddenFor(m => m.Filter)
foreach (Enum item in Enum.GetValues(typeof(Tables.Controllers.FilterProperties)))
{
if (item.Equals(Tables.Controllers.FilterProperties.None))
{
continue;
}
<div>
<label>
<input type="checkbox" value="#((int)(object)item)" checked=#Model.Filter.HasFlag(item) />
<span>#item</span>
</label>
</div>
}
<span id="filtererror" class="field-validation-error" hidden >Please select at least one property to search</span>
<input type="submit" value="Search" />
}
<table>
....
</table>
#Html.PagedListPager(Model.Resolutions, page => Url.Action("Index", new { page, Keyword = Model.Keyword, Filter = (int)Model.Filter }))
And then use javascript to andles the forms .submit() event to update the hidden input value for Filter (note I have also assumed that you want at least one checkbox selected if the value of Keyword is not null)
<script>
var checkboxes = $('input:checkbox');
var keyword = $('#Keyword');
$('form').submit(function () {
var filter = 0;
// validate at least one checkbox must be checked if Keyword has a value
if (keyword.val() && checkboxes.filter(':checked').length == 0) {
$('#filtererror').show();
return false;
}
$.each(checkboxes, function () {
if ($(this).is(':checked')) {
filter += Number($(this).val());
}
// disable checkboxes to prevent their value being added to the query string
$(this).prop('disabled', true);
})
$('#Filter').val(filter);
})
checkboxes.click(function () {
if (keyword.val() && checkboxes.filter(':checked').length == 0) {
$('#filtererror').show();
} else {
$('#filtererror').hide();
}
})
</script>
Your URL (based on ResolutionYear and ResolutionText being checked) will now be
.../Index?Keyword=someText&Filter=5
instead of
.../Index?Keyword=someText&IsResYearChecked=true&IsResYearChecked=false&IsResNumChecked=false&IsResTextChecked=true&IsResTextChecked=false

ViewModel not binding when using in a new view

I am able to pass a view model initially on a form on my index.cshtml page to an editor template page. On the index page I have a submit button that post the form results (radio button groups in the editor template) back to the controller and within the HttpPost method its passing this model to a partial view which is displayed in a modal popup. All this does is show the form elements that were selected but it disables the radio buttons to the user. From here the user can either go back (close the window) or confirm the form results. When the user clicks the confirm button it should pass the viewmodel back to the controller to another HttpPost method which will then process the form results and return the final confirmation view. But when I try to pass the viewmodel back to the controller from the modal popup it does not keep the binding. I tried making sure all were binded through Hidden inputs but I must be missing something somewhere. Maybe I am going about this the wrong way. I just need to basically keep the viewmodel binding from the initial post and be able to process that after the user confirms the selection from the modal popup. What would be the best way to accomplish this without having to put a session hack in there?
Index
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { id = "ballotForm" }))
{
#Html.AntiForgeryToken()
#(Html.EditorFor(m => m.BallotViewModel, new ViewDataDictionary(ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = "BallotViewModel"
}
}))
<table class="col-sm-12">
<tr>
<td class="pull-right">
<button type="submit" class="btn btn-primary" data-target="#modal-container" data-toggle="modal">Vote Management Ballot</button>
</td>
</tr>
</table>
}
Controller - Initial Post to Modal Popup
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(HomeViewModel bModel)
{
if (ModelState.IsValid)
{
//set property to identity view
bModel.BallotViewModel[0].IsVoteConfirmationView = true;
return PartialView("ViewVoteConfirmation", bModel);
}
}
Controller - Post after Confirm submit from modal popup
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ConfirmVote(HomeViewModel cModel)
{
//Process form results here but model is null
//Go to Thank You View
return View();
}
ViewVoteConfirmation:
#model Ballot.WebUI.Models.HomeViewModel
<div class="row">
#(Html.EditorFor(m => m.BallotViewModel, new ViewDataDictionary(ViewData) { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "BallotViewModel" } }))
</div>
#using (Html.BeginForm("ConfirmVote", "Home", FormMethod.Post, new { id = "ballotConfirmVoteForm" }))
{
#Html.AntiForgeryToken()
<div class="row">
#Html.EditorFor(m => m.BallotViewModel[0].Proposals, "Proposals", new ViewDataDictionary(ViewData)
{
TemplateInfo = new TemplateInfo
{
HtmlFieldPrefix = "Proposals"
}
})
</div>
<div class="row">
<div class="col-md-4 col-md-offset-4">
<button type="button" class="btn btn-default"
data-dismiss="modal">
Cancel
</button>
<button type="submit" id="approve-btn"
class="btn btn-danger">
Confirm
</button>
</div>
</div>
}
ProposalViewModel:
public class ProposalViewModel
{
public int ProposalItemID { get; set; }
public string ProposalItemTitle { get; set; }
public string Option0_Name { get; set; }
public string Option1_Name { get; set; }
public string Option2_Name { get; set; }
public string Option3_Name { get; set; }
public string PercOfShare { get { return "% of Share"; }}
public bool IsHeader { get; set; }
public int TagOrder { get; set; }
public int SelectedVoteOption { get; set; }
public bool IsVoteConfirmationView { get; set; }
public bool IsCumulative { get; set; }
public int SharePercentage { get; set; }
public List<VoteOptionViewModel> lVoteOptions { get; set; }
}
Proposals:
#model List<Ballot.WebUI.Models.ProposalViewModel>
#for (int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(m => m[i].ProposalItemID)
#Html.HiddenFor(m => m[i].ProposalItemTitle)
#Html.HiddenFor(m => m[i].Option0_Name)
#Html.HiddenFor(m => m[i].Option1_Name)
#Html.HiddenFor(m => m[i].Option2_Name)
#Html.HiddenFor(m => m[i].Option3_Name)
#Html.HiddenFor(m => m[i].PercOfShare)
#Html.HiddenFor(m => m[i].IsHeader)
#Html.HiddenFor(m => m[i].TagOrder)
#Html.HiddenFor(m => m[i].SelectedVoteOption)
#Html.HiddenFor(m => m[i].IsVoteConfirmationView)
#Html.HiddenFor(m => m[i].IsCumulative)
#Html.HiddenFor(m => m[i].lVoteOptions)
#Html.HiddenFor(m => m[i].SharePercentage)
}
jquery script to change the value of the SharePercentage label
$(function () {
//When 'For' is Selected
$('[class$=PercOfShareFor]').on('click', function (e) {
if ($(this).is(':checked')) {
var forMatches1 = 0;
$('[class$=PercOfShareFor]').each(function (i, val) {
if ($(this).is(':checked')) {
//check how many 'For' Vote Options are selected
forMatches1++;
//select the Share Percentage value label in the same row, and change the class to ForSelected (used as selector)
$(this).closest('td').next('td').next('td').find('.SharePercentage')
.removeClass("SharePercentage")
.addClass("SharePercentageForSelected");
//if the Share Percentage class (used as selector) was previously WithholdSelected then change to ForSelected
$(this).closest('td').next('td').next('td').find('.SharePercentageWithholdSelected')
.removeClass("SharePercentageWithholdSelected")
.addClass("SharePercentageForSelected");
}
});
//divide total 'For' Selections by number of Director Proposals
var forPercent1 = 100 / forMatches1;
//format the percentage to display 2 decimal places if not a whole number
var forPercent2 = Math.round(forPercent1 * 100) / 100;
//Update 'For' Percentages
$('[class$=SharePercentageForSelected]').text(forPercent2);
}
});
//When 'Withhold' is Selected after initially selecting 'For'
$('[class$=PercOfShareWithhold]').on('click', function (e) {
if ($(this).is(':checked')) {
var forMatches = 0;
$('[class$=PercOfShareFor]').each(function (i, val) {
if ($(this).is(':checked')) {
//check how many 'For' Vote Options are still selected
forMatches++;
}
});
var withholdMatches = 0;
$('[class$=PercOfShareWithhold]').each(function (i, val) {
if ($(this).is(':checked')) {
//check how many 'Withhold' Vote Options are still selected
withholdMatches++;
//set the class to WithholdSelected
$(this).closest('td').next('td').find('.SharePercentageForSelected')
.removeClass("SharePercentageForSelected")
.addClass("SharePercentageWithholdSelected")
.text("0"); //Set 'Withhold' Percentage back to 0
}
});
//divide total 'For' Selections by number of Director Proposals
var forPercent1 = 100 / forMatches;
//format the percentage to display 2 decimal places if not a whole number
var forPercent2 = Math.round(forPercent1 * 100) / 100;
//Update 'For' Percentages
$('[class$=SharePercentageForSelected]').text(forPercent2);
}
});
});
You can't do what you are trying to through form Posts. The browser sees the return from the Post as a full HTML page (even if you are saying it is a Partial server side). You will need to use some form of javascript to accomplish it or you will need to make your confirmation page an actual page instead of a modal popup.
The basic premise is that you need to trap the submit (or the button click) via javascript then display the modal. You can fill the modal with the results of the first action but you will need to submit the form via ajax instead of a standard form post. Then based on their selection in the modal you can submit the form or not.
There are a variety of resources available already that might help you. Here is one that show how to display a confirmation for a delete action. You could then alter that javascript to load the result of your first action via ajax as in this (admittedly older) article about loading MVC partial views using AJAX or maybe this one about using jQuery dialog for CRUD operations.
The model binding fix was to add a hidden field to the EditorTemplate for BallotViewModel so that when a radio button is selected, not only will the label change values but the hidden field would change values as well.
Editor Template
#Html.HiddenFor(m => Model.Proposals[i].SharePercentage, new { #class = "hdnSharePercentage" })
#Html.LabelFor(m => Model.Proposals[i].lVoteOptions[j].SharePercentage, Model.Proposals[i].lVoteOptions[j].SharePercentage, new { #class = "SharePercentage" })
jQuery
$(this).closest('td').next('td').next('td').find('.hdnSharePercentageWithholdSelected')
.removeClass("hdnSharePercentageWithholdSelected")
.addClass("hdnSharePercentageForSelected");

Passing Model data from View to Controller

I am trying to pass the Model data from a View (and PartialView within the View) back to the Controller upon HttpPost. (Adapted from Pass SelectedValue of DropDownList in Html.BeginForm() in ASP.NEt MVC 3)
Why? I want to show a list of assets each with a DropDownList and number of options. Upon submission of form to read the selected items from DropDownList.
My 2 (simplified) models:
public class Booking
{
public int BookingID { get; set; }
public int StoreID { get; set; }
...
public IEnumerable<AssetShort> Assets { get; set; }
}
and
public class AssetShort
{
public int AssetID { get; set; }
....
public int SelectedAction { get; set; }
public IEnumerable<SelectListItem> ActionList { get; set; }
}
In my Booking Controller > Create I build the List:
public ActionResult Booking(int id)
{
// get myBag which contains a List<Asset>
// booking corresponds to 'id'
var myAssets = new List<AssetShort>();
foreach (var a in myBag.Assets)
{
var b = new AssetShort();
b.AssetID = a.ID;
b.SelectedAction = 0;
b.ActionList = new[]
{
new SelectListItem { Selected = true, Value = "0", Text = "Select..."},
new SelectListItem { Selected = false, Value = "1", Text = "Add"},
new SelectListItem { Selected = false, Value = "2", Text = "Remove"},
new SelectListItem { Selected = false, Value = "3", Text = "Relocate"},
new SelectListItem { Selected = false, Value = "4", Text = "Upgrade"},
new SelectListItem { Selected = false, Value = "5", Text = "Downgrade"}
};
myAssets.Add(b);
};
var model = new BookingRequirementsViewModel
{
BookingID = booking.ID,
StoreID = booking.StoreID,
Assets = myAssets.ToList(),
};
return View(model);
My View:
#model uatlab.ViewModels.BookingRequirementsViewModel
#{
ViewBag.Title = "Booking step 2";
}
<h4>Your booking ref. #Model.BookingID</h4>
#using (Html.BeginForm("Booking2", "Booking", FormMethod.Post))
{
<fieldset>
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.StoreID)
#Html.Partial("_Assets", Model.StoreAssets)
<input type="submit" value="Cancel" class="btn btn-default" />
<input type="submit" value="Next" class="btn btn-default" />
</fieldset>
}
The Partial View includes
#foreach (var item in Model)
{
<tr>
<td>#item.Name</td>
<td>#item.Number</td>
<td>#Html.DropDownListFor(modelItem=>item.SelectedAction, item.ActionList)</td>
</tr>
}
So, all this works fine in the browser and I can select dropdowns for each asset listed but when I submit the only value posted back is the StoreID as it is in a "HiddenFor".
The booking2 controller has the model for a parameter:
public ActionResult Booking2(BookingRequirementsViewModel model)
{
//loop through model.Assets and display SelectedActions
}
Let me make it clear what the problems is - in Booking2 controller the Model is null when viewed in Debug mode and I get error "Object reference not set to an instance of an object."
Any ideas please how to pass back the Model to controller from view?
Regards
Craig
You need to create an EditorTemplate for AssetShort. I also suggest moving ActionList to the BookingRequirementsViewModel so your not regenerating a new SelectList for each AssetShort
The models you have posted aren't making sense. Your controller has var model = new BookingRequirementsViewModel { ..., Assets = myAssets.ToList() }; but in the view you refer to #Html.Partial("_Assets", Model.StoreAssets)? Are these 2 different properties. I will assume that StoreAssets is IEnumerable<AssetShort>
/Views/Shared/EditorTemplates/AssetShort.cshtml
#model AssetShort
<tr>
<td>#Html.DispayFor(m => m.Name)</td>
....
<td>
#Html.DropDownListFor(m => m.SelectedAction, (IEnumerable<SelectListItem>)ViewData["actionList"], "--Please select--")
#Html.ValidationMessageFor(m => m.SelectedAction)
</td>
</tr>
In the main view
#model uatlab.ViewModels.BookingRequirementsViewModel
....
#using (Html.BeginForm()) // Not sure why you post to a method with a different name
{
....
#Html.HiddenFor(m => m.StoreID)
#Html.EditorFor(m => m.StoreAssets, new { actionList = Model.ActionList })
....
}
In the controller
public ActionResult Booking(int id)
{
....
var model = new BookingRequirementsViewModel
{
BookingID = booking.ID,
StoreID = booking.StoreID,
Assets = myBag.Assets.Select(a => new AssetShort()
{
AssetID = a.ID,
SelectedAction = a.SelectedAction, // assign this if you want a selected option, otherwise the "--Please select--" option will be selected
....
})
};
ConfigureViewModel(model); // Assign select list
return View(model);
}
And a separate method to generate the SelectList because it needs to be called in the GET method and again in the POST method if you return the view. Note use the overload of DropDownListFor() to generate the option label (null value) as above, and there is no point setting the Selected property (the value of SelectedAction determines what is selected, not this)
private ConfigureViewModel(BookingRequirementsViewModel model)
{
model.ActionList = new[]
{
new SelectListItem { Value = "1", Text = "Add"},
....
new SelectListItem { Value = "5", Text = "Downgrade"}
};
}
and the POST
public ActionResult Booking(BookingRequirementsViewModel model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model); // Re-assign select list
return View(model);
}
// save and redirect
}
I recommend also making SelectedAction nullable with the [Required] attribute so you get client and server side validation
public class AssetShort
{
public int AssetID { get; set; }
....
[Required]
public int? SelectedAction { get; set; }
}

How to create Static Dropdownlist values in MVC Model

How can I prepare a model for Dropdownlist static values (not retrieved from database) like enum or list in MVC Model so that it could be used many times in a project? I would appreciate if you can give a good article? Thanks.
As always you could start with a view model:
public class MyViewModel
{
public string SelectedValue { get; set; }
public IEnumerable<SelectListItem> Values
{
get
{
return new[]
{
new SelectListItem { Value = "1", Text = "Item 1" },
new SelectListItem { Value = "2", Text = "Item 2" },
new SelectListItem { Value = "3", Text = "Item 3" },
};
}
}
}
then a controller:
public ActionResult Index()
{
var model = new MyViewModel();
return View(model);
}
and finally a view:
#model MyViewModel
#Html.DropDownListFor(x => x.SelectedValue, Model.Values)
For enums you could use some of the many posts out there illustrating custom helpers. Here's one blog post illustrating such helper: http://blogs.msdn.com/b/stuartleeks/archive/2010/05/21/asp-net-mvc-creating-a-dropdownlist-helper-for-enums.aspx
Finally I have found the solution by describing the parameters as hidden input. I am not sure if there is a more elegant method in order to pass the parameters from View to Controller. Thank you so much for your good sample. I also marked as helpful all of your replies. Here is my final code for those who might encounter a similar problem:
ApplicantViewModel:
public class ApplicantViewModel
{
public IEnumerable<Applicant> Applicants { get; set; }
//Codes for Dropdownlist values
public string SelectedValue { get; set; }
public IEnumerable<SelectListItem> Values
{
get
{
return new[]
{
new SelectListItem { Value = "pdf", Text = "Pdf" },
new SelectListItem { Value = "excel", Text = "Excel" },
new SelectListItem { Value = "word", Text = "Word" }
};
}
}
}
ApplicantController:
public ViewResult Reporting()
{
var model = new ApplicantViewModel();
return View(model);
}
public ActionResult RenderReport(string SelectedValue, string name, string fileName, string dataSource, string table, string filter)
{
//Codes for rendering report
...
}
Reporting.cshtml:
#model MyProject.Models.ApplicantViewModel
#using (Html.BeginForm("RenderReport", "Applicant", FormMethod.Post,
new { enctype = "multipart/form-data" }))
{
<div>
#Html.DropDownListFor(model => model.SelectedValue, Model.Values, "-- select an option --")
<input type="hidden" name="name" value="Report1"/>
<input type="hidden" name="fileName" value="image rapor"/>
<input type="hidden" name="dataSource" value="ApplicantDataset"/>
<input type="hidden" name="table" value="ApplicantsView"/>
<input type="hidden" name="filter" value="David"/>
<input type="submit" value="submit" />
</div>
}

Validate JQuery UI modal form within another form in MVC 4

I have a form in MVC 4 which contains several fields and, depending on the value of a combo, I need to open a modal dialog form and load into that one 3 additional fields that will impact against the same entity that I'm creating/editing in the main form.
For this modal dialog I'm using the one from jQuery UI.
Now, what I need to do is to validate (Required) the fields within the modal dialog in order to allow the user to retain the entered values which will be submited later by the main form.
My problem is how to perform the validation of those 3 fields from within the modal form (because they wouldn't be able to submit the main form until dialog is closed).
Any hints or ideas?
Regards,
Cesar.
You could use AJAX to submit the form modal to the server. The modal form will have of course a separate view model associated with it. Let's exemplify:
Main view model:
public class MyViewModel
{
[DisplayName("select a value")]
public string SelectedValue { get; set; }
public IEnumerable<SelectListItem> Values { get; set; }
public string SomeOtherProperty { get; set; }
}
Modal dialog view model:
public class DialogViewModel
{
[Required]
public string Prop1 { get; set; }
[Required]
public string Prop2 { get; set; }
[Required]
public string Prop3 { get; set; }
}
Then you could have a controller containing 4 actions:
public class HomeController : Controller
{
// Renders the main form
public ActionResult Index()
{
var model = new MyViewModel
{
Values = new[]
{
new SelectListItem { Value = "1", Text = "item 1" },
new SelectListItem { Value = "2", Text = "item 2" },
new SelectListItem { Value = "3", Text = "item 3" },
}
};
return View(model);
}
// Processes the submission of the main form
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return Content(
string.Format(
"Thanks for filling out the form. You selected value: \"{0}\" and other property: \"{1}\"",
model.SelectedValue,
model.SomeOtherProperty
)
);
}
// Renders the partial view which will be shown in a modal
public ActionResult Modal(string selectedValue)
{
var model = new DialogViewModel
{
Prop1 = selectedValue
};
return PartialView(model);
}
// Processes the submission of the modal
[HttpPost]
public ActionResult Modal(DialogViewModel model)
{
if (ModelState.IsValid)
{
// validation of the modal view model succeeded =>
// we return a JSON result containing some precalculated value
return Json(new
{
value = string.Format("{0} - {1} - {2}", model.Prop1, model.Prop2, model.Prop3)
});
}
// Validation failed => we need to redisplay the modal form
// and give the user the possibility to fix his errors
return PartialView(model);
}
}
Next you could have a main view (~/Views/Home/Index.cshtml):
#model MyViewModel
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(x => x.SelectedValue)
#Html.DropDownListFor(x => x.SelectedValue, Model.Values, new { id = "ddl" })
</div>
<div>
#Html.LabelFor(x => x.SomeOtherProperty)
#Html.TextBoxFor(x => x.SomeOtherProperty, new { id = "otherProperty" })
#Html.ActionLink(
"click here to open a modal and help you fill the value",
"Modal",
"Home",
null,
new { id = "showModal" }
)
</div>
<button type="submit">OK</button>
}
<div id="modal"></div>
and a partial view to contain the modal form (~/Views/Home/Modal.cshtml):
#model DialogViewModel
#using (Ajax.BeginForm(new AjaxOptions { OnSuccess = "handleModalSubmit" }))
{
<div>
#Html.LabelFor(x => x.Prop1)
#Html.EditorFor(x => x.Prop1)
#Html.ValidationMessageFor(x => x.Prop1)
</div>
<div>
#Html.LabelFor(x => x.Prop2)
#Html.EditorFor(x => x.Prop2)
#Html.ValidationMessageFor(x => x.Prop2)
</div>
<div>
#Html.LabelFor(x => x.Prop3)
#Html.EditorFor(x => x.Prop3)
#Html.ValidationMessageFor(x => x.Prop3)
</div>
<button type="submit">OK</button>
}
OK, now all that's left is write some javascript to make the whole thing alive. We start by making sure that we have included all the required scripts first:
<script src="#Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery-ui-1.8.20.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
and then write our own:
$(function () {
$('#showModal').click(function () {
$.ajax({
url: this.href,
type: 'GET',
cache: false,
data: { selectedValue: $('#ddl').val() },
success: function (result) {
$('#modal').html(result).dialog('open');
}
});
return false;
});
$('#modal').dialog({
autoOpen: false,
modal: true
});
});
function handleModalSubmit(result) {
if (result.value) {
// JSON returned => validation succeeded =>
// close the modal and update some property on the main form
$('#modal').dialog('close');
$('#otherProperty').val(result.value);
} else {
// validation failed => refresh the modal to display the errors
$('#modal').html(result);
}
}

Resources