I am creating the survey application for which I am creating the controls dynamically(Like checkbox,radio-button,textbox etc).
Each question will have the controls depending upon the control type assigned to the question and on question type the answer choices(checkbox, radio button) will be rendered.
On Next/Previous navigation I am storing current page answers in the database. While navigating the page I am doing ajax call for database saving and my UI/controls are NOT in the form.
I have created ViewModel based on my LMS_SurveyQuestions and LMS_SurveyQuestionOptionChoice table.
So, while creating the UI in view in for loop, I have directly assigned SurveyQuestionOptionChoiceID as Control ID while creating the AnswerChoice controls and stored the same in the table SurveyUserAnswer table.
Model
public class LMS_TraineeSurveyPaginationViewModel
{
public List<LMS_SurveyQuestions> SurveyQuestions { get; set; }
public List<LMS_SurveyQuestionOptionChoice> SurveyQuestionOptionChoice { get; set; }
public SurveyPager Pager { get; set; }
}
and this is how I rendered the view
#foreach (var item in Model.SurveyQuestions)
{
foreach (var data in Model.SurveyQuestionOptionChoice.Where(x => x.SurveyQuestionID == item.SurveyQuestionID).ToList())
{
if (item.QuestionTypeID == QuestionType.RadioButton)
{
<li style="list-style:none;">
<input type="radio" name="rb" id="#data.SurveyQuestionOptionChoiceID" />
<span>#data.OptionChoice</span>
</li>
}
else if (item.QuestionTypeID == QuestionType.CheckBox)
{
<li style="list-style:none;">
<input type="checkbox" id="#data.SurveyQuestionOptionChoiceID" name="#data.SurveyQuestionOptionChoiceID" " />
<span>#data.OptionChoice</span>
</li>
}
}
}
and while saving the answer into database I have created the JSON/JS array as model for SurveyUserAnswer and saved it into database as follows. Below is example for radio button
function SaveValues() {
var surveyQuestion = #Html.Raw(Json.Serialize(Model.SurveyQuestions.ToArray()));
var surveyQuestionOptionChoide = #Html.Raw(Json.Serialize(Model.SurveyQuestionOptionChoice.ToArray()));
for (item in surveyQuestion) {
var surveyQuestionID=surveyQuestionViewModel[item].SurveyQuestionID;
var filteredData = surveyQuestionOptionChoide.filter(function(filteredItem) {
return (filteredItem.SurveyQuestionID==surveyQuestionID);
});
for (optionChoice in filteredData) {
if(surveyQuestion[item].QuestionTypeID=='#QuestionType.RadioButton') {
if (($('#'+SurveyQuestionOptionChoiceID).prop("checked"))) {
surveyUserAnswer.push({ SurveyUserAnswerID: filteredData[optionChoice].SurveyUserAnswerID==null?0:filteredData[optionChoice].SurveyUserAnswerID,
SurveyQuestionOptionChoiceID: SurveyQuestionOptionChoiceID,SurveyUserID:'#ViewBag.SurveyUserID',AnswerText:null,
MarksObtained:filteredData[optionChoice].Marks,WhenCreated:'#DateTime.UtcNow',WhoCreated:'#tenant.UserID'});
}
}
}
}
$.post('#Url.Action("GetTraineeSurvey", "Survey")', {SurveyID:surveyID,page:page, surveyUserAnswer: surveyUserAnswer,PrevBranchQuestionPage:currentPage,IsBranchQuestionAvailable:IsBranchQuestionAvailable }, function (data) {
$('#surveyModalContent').html('');
$('#surveyModalContent').html(data);
$("#surveyModal").modal('show');
}).fail(function() {
alert( "error in GetTraineeSurvey" );
}).success(function() { });
}
So, my question is how can I validate dynamically created controls in this scenario ?
You can use Unobtrusive jQuery validation basic on data attributes on controls.
Read more about that on this LINK.
Related
I work on an ASP.NET MVC project that I need some help with. I need to be able to create x number of textboxes when the user click "add textbox". When the user enter the page a viewmodel is loaded. This viewmodel need to handle the x number of textboxes that the user create when he is on the page so that when the page is posted these textboxes are part of the model. The model should look something like this..
public class PlanViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public List<EventViewModel> EventList { get; set; } // this should be the list of textboxes that the user "create" by clicking add new
}
public class EventViewModel
{
public string Name { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string Description { get; set; }
}
I'm kinda lost on how to do this so any help is appreciated.
UPDATE
I've added this javascript that add textboxes client side..
<script type="text/javascript">
function GetDynamicTextBox(value) {
return('<input type="text" name="events[0].Key" value="box1" /><input type="text" name="events[0].Value.StartDate" value="box2"/><button type="button" class="btn btn-sm btn-primary" onclick="RemoveTextBox(this)"><i class="fa fa-angle-right"></i> Remove</button>');
}
function AddTextBox() {
var div = document.createElement('DIV');
div.innerHTML = GetDynamicTextBox("");
document.getElementById("divcontent").appendChild(div);
}
function RemoveTextBox(div) {
document.getElementById("divcontent").removeChild(div.parentNode);
}
</script>
<div id="divcontent" class="form-group">
<button type="button" class="btn btn-sm btn-primary" onclick="AddTextBox()"><i class="fa fa-angle-right"></i> Add</button>
</div>
I think I only need to add unique id's for the textboxes like this...
events[0].Key
events[1].Key
events[2].Key
and so on..
But I don't know how. Anyone knows?
You can add a list of String, like this
public String[] MyTextFields
and then create HTML using Javascript, like this:
<input name="myTextFields[0]"></input>
<input name="myTextFields[1]"></input>
In Razor view:
#for (var i = 0; i < Model.EventList.Count; i++)
{
#Html.EditorFor(x => Model.EventList[i].Name)
}
To set the name attribute of all edited elements in javascript, this is to be called on page load, and any time the collection changes (item is added or removed):
var children = document.getElementById("myDIV").children; // TODO: here supposing children are the input elements, may be different on your page (they may be nested in a different way)
for (i = 0; i < children.length; i++)
{
var el = children[i];
el.name = 'EventList[' + i + '].Name'; // TODO: look into source code of the generated page for the actual format of existing elements and then modify the mask accordingly
el.id = 'EventList[' + i + '].Name';
}
If it is ok to have JS dependency than I suggest to use light Knockout library. It will help you to create/edit/delete your inputs. Check example in JS fiddle.
Use HTML to adjust your view. Tag data-bind lets you to bind to data and events
<button data-bind="click: addInput">Add</button>
<div data-bind="foreach: inputs">
<input data-bind="value: text"/><br />
</div>
<button data-bind="click: proceed">Proceed</button>
<!-- Use Knockout JS library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
Then small JS script which handles adding new input and processing data on click.
function InputData(text) {
let self = this;
self.text = text;
}
function InputViewModel() {
let self = this;
// Your array of HTML inputs
self.inputs = ko.observableArray([new InputData("Default value")]);
self.output = ko.observable();
self.addInput = function() {
// Dynamically adds new input on user click button "Add"
self.inputs.push(new InputData(""));
};
self.proceed = function() {
// Process all input with their values
for (var i = 0; i < self.inputs().length; i++) {
console.log(self.inputs()[i].text);
}
}
}
// Bind our JS to HTML view
ko.applyBindings(new InputViewModel());
I have a project to make an online shop between users (post a product, buy, etc.) using a database. In this project I have a view called "ShoppingCart":
#model IEnumerable<MyFirstProject.Models.Product>
#{
ViewBag.Title = "ShoppingCart";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Your Shopping Cart</h2>
#if (Model == null)
{
<div style="float:left">Your cart is empty.</div>
<div>
Total payment: 0
</div>
}
else
{
decimal tPrice = 0;
<div>
<table style="float:left">
#foreach (var product in Model)
{
tPrice = tPrice + product.Price;
{ Html.RenderPartial("ProductLine", product);}
}
</table>
</div>
<div>
Total payment: #tPrice
</div>
}
It receives a list of products which the user decided to buy and displays them (not the important part). I need to add a button which will send the list to an action result in the "ShoppingController":
[HttpPost]
public ActionResult ShoppingCart(List<Product> bought)
{
if (ModelState.IsValid)
{
foreach (var listP in bought.ToList())
{
foreach (var databaseP in db.Products.ToList())
{
if (listP.ProductID == databaseP.ProductID)
{
databaseP.State = 1;
db.SaveChanges();
break;
}
}
}
return RedirectToAction("Index");
}
else
{
return View(bought);
}
}
"State" indicates if the product was bought or not (0=not bought, 1=bought), db is the database
If you wan't to post any data from a view to an action method, you should keep that data in form elements and keep that in a form. Since you want to post a collection of items, You may use Editor Templates.
Let's start by creating a view model.
public class ShoppingCartViewModel
{
public decimal TotalPrice { set; get; }
public List<Product> CartItems { set; get; }
}
public class Product
{
public int Id { set; get; }
public string Name { set; get; }
}
Now in your GET action, you will create an object of the ShoppingCartViewModel, load the CartItems property and send to the view.
public ActionResult Index()
{
var cart = new ShoppingCartViewModel
{
CartItems = new List<Product>
{
new Product { Id = 1, Name = "Iphone" },
new Product { Id = 3, Name = "MacBookPro" }
},
TotalPrice = 3234.95
};
return View(cart);
}
Now i will create an EditorTemplate. To do that, Go to your ~/Views/YourControllerName folder, and Create a directory called EditorTemplates and add a view with name Product.cshtml
The name of the file should match with the name of the type.
Open this new view and add the below code.
#model YourNamespace.Product
<div>
<h4>#Model.Name</h4>
#Html.HiddenFor(s=>s.Id)
</div>
You can keep the display however you want. But the important thing is, We need to keep a form field for the productId. We are keeping that in a hidden field here.
Now let's go back to our main view. We need to make this view strongly typed to our ShoppingCartViewModel. We will use the EditorFor html helper method in this view to call our editor template
#model ReplaceYourNamespaceHere.ShoppingCartViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.CartItems)
<p>Total : #Model.TotalPrice</p>
<input type="submit" />
}
And in your HttpPost action method, We will have a paramer of type ShoppingCartViewModel. When the form is submitted, MVC Model binder will map the posted form values to an object of ShoppingCartViewModel.
[HttpPost]
public ActionResult Index(ShoppingCartViewModel model)
{
foreach (var item in model.CartItems)
{
var productId = item.Id;
// to do : Use productId and do something
}
return RedirectToAction("OrderSucessful");
}
You can iterate through the CartItems collection and get the Id of the Products and do whatever you want.
If you wan't to allow the user to edit the items (using a check box) in this page, Take a look at this answer. It is basically same, but you add a boolean property to Product class and use that for rendering a checkbox.
I am new to MVC (I am moving over from the dark side of traditional ASP.Net) and I know that SO is more of a "why doesn't this work" but, being new to MVC, I just wanted to ask how something is achieved - I don't really have any code or markup because I don't know how at the moment.
Right, using an analogous example... I have a form that has a drop-down of a list of "Widgets" (have that working, thanks to SO) ... and then there are other fields (Length/Height/Width) which have "default" values.
When the form displays, the Drop-Down is shown but the form fields of L/H/W are empty/disabled until the user selects one from the DDL.
Now, in clasic ASP.Net world, you would do a PostBack on the "onselectedindexchange" and that would look at the item selected, then update the L/H/W fields with values from the "master widget entry" version.
As MVC does not have post back... how is this achieved?
In Asp.Net MVC, There is no postback behaviour like you had in the web forms when a control value is changed. You can still post the form and in the action method, you may read the selected value(posted value(s)) and load the values for your text boxes and render the page again. This is complete form posting. But there are better ways to do this using ajax so user won't experience the complete page reload.
What you do is, When user changes the dropdown, get the selected item value and make a call to your server to get the data you want to show in the input fields and set those.
Create a viewmodel for your page.
public class CreateViewModel
{
public int Width { set; get; }
public int Height{ set; get; }
public List<SelectListItem> Widgets{ set; get; }
public int? SelectedWidget { set; get; }
}
Now in the GET action, We will create an object of this, Initialize the Widgets property and send to the view
public ActionResult Create()
{
var vm=new CreateViewModel();
//Hard coded for demo. You may replace with data form db.
vm.Widgets = new List<SelectListItem>
{
new SelectListItem {Value = "1", Text = "Weather"},
new SelectListItem {Value = "2", Text = "Messages"}
};
return View(vm);
}
And your create view which is strongly typed to CreateViewModel
#model ReplaceWithYourNamespaceHere.CreateViewModel
#using(Html.BeginForm())
{
#Html.DropDownListFor(s => s.SelectedWidget, Model.Widgets, "Select");
<div id = "editablePane" >
#Html.TextBoxFor(s =>s. Width,new { #class ="myEditable", disabled="disabled"})
#Html.TextBoxFor(s =>s. Height,new { #class ="myEditable", disabled="disabled"})
</div>
}
The above code will render html markup for the SELECT element and 2 input text fields for Width and Height. ( Do a "view source" on the page and see)
Now we will have some jQuery code which listens to the change event of the SELECT element and reads the selected item value, Makes an ajax call to server to get the Height and Width for the selected widget.
<script type="text/javascript">
$(function(){
$("#SelectedWidget").change(function() {
var t = $(this).val();
if (t !== "") {
$.post("#Url.Action("GetDefault", "Home")?val=" + t, function(res) {
if (res.Success === "true") {
//enable the text boxes and set the value
$("#Width").prop('disabled', false).val(res.Data.Width);
$("#Height").prop('disabled', false).val(res.Data.Height);
} else {
alert("Error getting data!");
}
});
} else {
//Let's clear the values and disable :)
$("input.editableItems").val('').prop('disabled', true);
}
});
});
</script>
We need to make sure that we have an action method called GetDetault inside the HomeController to handle the ajax call.
[HttpPost]
public ActionResult GetDefault(int? val)
{
if (val != null)
{
//Values are hard coded for demo. you may replae with values
// coming from your db/service based on the passed in value ( val.Value)
return Json(new { Success="true",Data = new { Width = 234, Height = 345}});
}
return Json(new { Success = "false" });
}
Make a Controller "Action" that return "Json" data.
Make Ajax call "onchange" of dropdown to that "Action".
On ajax "response" (json) u will get values then set those values to
fields from json response.
This is the way to update field values.
Shyju made a brilliant post in 2015 but I had to update it to make it work for MVC 5. I worked with one of my progammers (I'm an IT manager) to create this. You need to create a class to represent the dropdown and the Height and Width.
public class AjaxText
{
public int Width { set; get; }
public int Height { set; get; }
public List<SelectListItem> Widgets { set; get; }
public int? SelectedWidget { set; get; }
}
In my HomeController.cs, the GET action will create an object of this, initialize the Widgets property and send to the view.
public IActionResult AjaxText()
{
//Hard coded for demo. You may replace with data form db.
AjaxText vm = new AjaxText();
vm.Widgets = new List<SelectListItem>
{
new SelectListItem {Value = "1", Text = "Weather"},
new SelectListItem {Value = "2", Text = "Messages"}
};
return View(vm);
}
And your create view will render html markup for the SELECT element and 2 input text fields for Width and Height. ( Do a "view source" on the page and see)
#model AjaxText
#{
ViewData["Title"] = "AjaxText";
}
<h1>#ViewData["Title"]</h1>
#using(Html.BeginForm())
{
#Html.DropDownListFor(s => s.SelectedWidget, Model.Widgets, "Select");
<div id = "editablePane" >
#Html.TextBoxFor(s =>s. Width,new { #class ="myEditable", disabled="disabled"})
#Html.TextBoxFor(s =>s. Height,new { #class ="myEditable", disabled="disabled"})
</div>
Now we will have some code which listens to the change event of the SELECT element and reads the selected item value, makes an ajax call to server to get the Height and Width for the selected widget. I added some alerts to help you debug.
<script type="text/javascript">
$(function(){
$("#SelectedWidget").change(function() {
var t = $(this).val();
if (t !== "")
{
$.ajax({
type: 'POST',
datatype: 'json',
url: '/Home/GetDefault?val=' + t,
success: function (bbb) {
alert(t);
alert(bbb.success);
alert(bbb.info.height);
$("#Width").prop('disabled', false).val(res.Data.Width);
$("#Height").prop('disabled', false).val(res.Data.Height);
},
error: function (msg) {
alert("error");
}
});
} else {
//Let's clear the values and disable :)
$("input.editableItems").val('').prop('disabled', true);
}
});
});
</script>
And in my home controller, the Post is done almost the same as how Shyju did it, but success doesn't have quotes around true and false. And you don't have to use the word data... info or whatever will work too. But keep it lowercase to maintain your sanity.
[HttpPost]
public JsonResult GetDefault(int? val)
{
if (val != null)
{
//Values are hard coded for demo. you may replae with values
// coming from your db/service based on the passed in value ( val.Value)
return Json(new { success = true, info = new { width = 234, height = 345 } });
}
return Json(new { Success = false });
}
I'm sure there are better ways to do this. This is what worked for us. Cheers and enjoy your coding experience! :)
How would you add #Html.ValidationMessageFor() for each item in a collection? Say,
public class FooVm
{
// some property
public ICollection<BarVm> Bars { get; set; }
}
public class BarVm
{
// some property
[Range(1, int.Max, ErrorMessage = "Must be greater than 1")
public float? Fox { get; set; }
}
Then in a view
#model namespace.here.FooVm
<div class="container"></div>
Populate
<script>
$(function() {
var i = 0;
var populate = function() {
var strBuilder = '<input type="text" name="Bars[i].Fox" />';
$(".container").append(strBuilder);
return false;
};
$(".trigger").click(populate);
});
</script>
It's all working. But how can I add the validation in every textbox? I'm using ASP.NET MVC 4 still practicing. I'm also utilizing unobtrusive validation for client validation. Any you-should-do-something-like-this suggestions or tips, sample code would be great. Thanks.
Actually, using Javascript to populate a View is not the way MVC should be used. Instead, you can render all textboxes like this:
First the code for the class:
public class FooVm
{
// some property
public List<BarVm> Bars { get; set; }
public FooVm()
{
// Make sure the collection exists to prevent NullReferenceException
this.Bars = new List<BarVm>();
}
}
public class BarVm
{
// some property
[Range( 1, Int32.MaxValue, ErrorMessage = "Must be greater than 1" )]
public float? Fox { get; set; }
}
Now the code for the View:
#model WebApplication2.Models.FooVm
<h2>Sample View</h2>
#using ( Html.BeginForm( "YourAction", "YourController" ) )
{
<div class="container">
#for ( int i = 0; i < Model.Bars.Count; i++ )
{
#Html.TextBoxFor( m => m.Bars[i].Fox )
#Html.ValidationMessageFor( m => m.Bars[i].Fox );
}
</div>
}
This will render the necessary tags - and of course the validationmessage-bits. However, it's also possible to combine all error messages in one place by using
#Html.ValidationSummary()
If you really want to display the stuff only after clicking a button, consider using a partial view and loading that one. That's a much better approach than trying to create all necessary tags and attributes for validation using javascript.
Regards,
Frank
I have one Model Having two virtual Properties i.e
public virtual IEnumerable MediumIds { get; set; }
public virtual IEnumerable AnsLanguageIds { get; set; }
I have Used ViewBag To Populate Them i.e
ViewBag.MediumIds = db.ExamMediums.Where(x => x.ExamId == _ExamId).Select(x => x.Medium);
ViewBag.AnsLanguageIds = new SelectList(db.AnswerLanguages.ToList(), "AnswerLanguageId", "AnsLanguage");
And My View Is
#foreach (var item in
ViewBag.MediumIds)
{
<input id="MediumIds" name="MediumIds" value="#item.MediumId" type="checkbox" /><strong>
#item.Medium1 </strong>
#Html.DropDownList("AnsLanguageIds")
<br />
}
I want The functionality like when the checkbox is selected than only the the dropdown should be enabled else it should be disabled and also i want that for which medium which anslanguage is selected
Your answer will be appreciated.
You can make the drop down lists disabled by default by passing new { disabled = "disabled" } as the DropDownList() method's htmlAttributes argument. This JQuery should toggle the select's disabled state when each checkbox is checked:
$(function() {
$("input#MediumIds").click(function() {
var checkbox = $(this);
var dropDownlist = checkbox.sibling("select:first");
dropDownlist.attr("disabled", checkbox.is(":checked") ? "" : "disabled");
});
});
...I'm not sure what you mean by "i want that for which medium which anslanguage is selected"?