MVC DropDownList OnChange to update other form fields - c#

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! :)

Related

Kendo Grid changing depending on DropDownList

Before I start I'll just say that I've looked at other answers before posting and none specifically help me.
I need to create a Kendo UI grid in ASP.NET MVC that changes depending on what the users selects from a DropDownList. I will eventually be using data from a database, but currently I'm trying to learn with random hard-coded data.
I found a tutorial online that shows me how to do it with data from a sample database, but I can't set that up for reasons I cant explain. So I'm trying to adapt the code from that tutorial to work with my controllers and models. This might be set up completely wrong as I'm relatively new to ASP.NET MVC.
So here's the tutorial I'm trying to follow.
This is my controller:
public class LookupValueController : Controller
{
private List<LookupModel> tables = new
List<LookupModel>()
{ new LookupModel() { TableName = "Table1",
Description = "Table 1" },
new LookupModel() { TableName = "Table2",
Description = "Table 2" } };
private List<LookupValueModel> values = new List<LookupValueModel>()
{ new LookupValueModel() { TableName = "Table1", Description = "Value 1", LookupCode = "1" },
new LookupValueModel() { TableName = "Table2", Description = "Value 2", LookupCode = "2"} };
// GET: LookupValue
public ActionResult Index()
{
return View();
}
public ActionResult GetAllTableA()
{
try
{
var table = tables;
return Json(table, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json(ex.Message);
}
}
public ActionResult GetAllTableB()
{
try
{
var value = values;
return Json(value, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json(ex.Message);
}
}
}
Then my 2 models:
public class LookupValueModel
{
public string TableName { get; set; }
public string LookupCode { get; set; }
public string Description { get; set; }
}
public class LookupModel
{
public string TableName { get; set; }
public string Description { get; set; }
}
I've tried just changing the values in the view in the tutorial but it doesn't work, as I believe it isn't as simple as just changing some text.
I'm pretty stuck for how to do this and don't know where to go from here. I know this is a very long winded post with lots of code, but I would really appreciate some help.
Where am I going wrong adapting the tutorial code? What do I have to change to get it to work with hard-coded data?
That's not that hard. What you need to do is to change the DataSource's url for each Action you want. So, depending on what options user selects in the DDL, you change the DataSource. Check this demo.
What you need to change in from the above demo is that your grid's DataSource will call an url instead of a hard-coded json, right? In that url, you change the desired action:
let changeTableData = function changeTableData(option) {
let dataSource = new kendo.data.DataSource({
transport: {
url: "MyApp/" + option
}
});
$("#grid").data("kendoGrid").setDataSource(dataSource);
};
It will read the new url and fetch the data into the grid and updated it.
UPDATE
The transport url ir the url path to your action, e.g.
let url;
if (option == "A") {
url = "#Url.Action("TableA")";
}
else if (option == "B") {
url = "#Url.Action("TableB")";
}
let dataSource = new kendo.data.DataSource({
transport: {
url: url
}
});
1) Remove the grid from this view and create a new partialview and just have the grid located in that.
Now this can be one of two ways. Either an onclick via the drop down list or an onchange. Your choice
function Getdropdown()
{
var id = $("#//dropdownID").val(); //Get the dropdown value
var json = '{dropdownId: ' + id + '}';
$.ajax({
url:'#Url.Action("ViewGrid", "//Controller")',
type:'POST',
data:json,
contentType:'Application/json',
success:function(result){
$("//The Id of of the div you want the partial to be displayed in in the cshtml").html(result);
}
});
}
2) Get the value of the dropdown and pass it to a controller method that calls this new partial view, sending it the ID in a model
public ActionResult ViewGrid(int dropdownId)
{
AModel model = new AModel
{
DropDownID = dropdownId
};
return PartialView("_theGridPartial", model);
}
3) Change your grid to look like this:
#(Html.Kendo().Grid<KendoMvcApp.Models.EmployeeA>()
.Name("EmpGrid")
.Selectable()
.Columns(columns =>
{
columns.Bound(c => c.FirstName);
columns.Bound(c => c.LastName);
})
.DataSource(dataSource => dataSource
.Ajax()
.Read(read => read.Action("GetAllEmployee", "GridDataSource", new {id = Model.DropDownID}))
)
)
4) This is the new Controller read
public ActionResult GetAllEmployee([DataSourceRequest]DataSourceRequest request, int id)
{
try
{
//Have a call that gets the table data based on the id you are passing into here. This id will be the table id you have got from your dropdown list
}
catch (Exception ex)
{
return Json(ex.Message);
}
}
This should allow you to change the table based on the dropdown.

Use simple MVC Html.DropDownList to control visibility of a div, onLoad and on selectChange

I have the following code:
<div class="form-group">
<label class="col-xs-3 control-label">Intermediary Bank Required?:</label>
<div class="col-xs-9">
<p class="form-control-static">#Html.DropDownList("IntermediaryRequired",(SelectList)ViewBag.IntermediaryRequired,"NO", new { #class = "form-control" })</p>
</div>
</div>
IntermediaryRequired is a bool field on my model
I also have this Extension Helper:
public static class BooleanExtensions
{
public static string ToYesNoString(this bool value)
{
return value ? "YES" : "NO";
}
public static string ToDislay(this bool value)
{
return value ? "normal" : "none";
}
public static string ToChar(this bool value)
{
return value ? "1" : "0";
}
}
My aim is to hide/display a <div> in response to the selected value in the DropDownList for two cases:
when the user Manually changes the DropDownList selection
when the form loads with an existing value for the model field IntermediaryRequired
Please how can we achieve this.
You should be able to do this with a little bit of javascript. Listen to the change event of the dropdown, check the value and hide/show the div. do the samething on document.ready (page loaded) as well to work with existing value of the model.
<script type="text/javascript">
$(function(){
//On page load, update the visiblity
var v=$("#IntermediaryRequired").val();
UpdateDivVisibility(v);
//When user changes the dropdown, update visibility
$("#IntermediaryRequired").change(function(e){
var v=$("#IntermediaryRequired").val();
UpdateDivVisibility(v);
});
});
function UpdateDivVisibility(isVisible)
{
if(v=="YES")
{
$("#DivIdtoHide").show();
}
else
{
$("#DivIdtoHide").hide();
}
}
</script>
EDIT : As per the question in the comment
Usually I create a viewmodel like this
public class CreateCustomerVM
{
public string Name { set;get;}
public List<SelectListItem> IntermediaryOptions {set;get;}
public string IntermediaryRequired {set;get;}
public CreateCustomerVM()
{
this.IntermediaryOptions =new List<SelectListItem>()
}
}
and in your GET actions for create
public ActionResult create()
{
var vm = new CreateCustomerVM();
vm.IntermediaryOptions = GetOptions();
return View(vm);
}
private List<SelectListItem> GetOptions()
{
return new List<SelectListItem>
{
new SelectListItem {Value = "0", Text = "No"},
new SelectListItem {Value = "1", Text = "Yes"}
};
}
And your view will be bounded to the viewmodel
#model CreateCustomerVM
#using(Html.Beginform())
{
<div>
<p>Required?</p>
<p>#Html.DropdowListFor(s=>s.IntermediaryRequired,Model.IntermediaryOptions)
<div id="ExtraOptions">
<!-- Your additional UI elements here -->
</div>
<input type="submit" />
</div>
}
In your Form post, you can read the IntermediaryRequired value and convert that to boolean value
[HttpPost]
public ActionResult Create(CreateCustomerVM model)
{
//check model.IntermediaryRequired
// to do : Save and Redirect(PRG pattern)
}
You can do something like this to show/hide the div when the user Manually changes the Drop Down
var yourDiv = $('#yourDiv');
$('#IntermediaryRequired').on('change', function(){
if ($(this).val() == 'YES') {
yourDiv.show();
}
else {
yourDiv.hide();
}
});
And to get the same result on page load you can try
#if (Model.value)
{
<div id="yourDiv">
....
</div>
}
As a side note use p only when you want to add a paragraph, if you just want to add -meaningless- block element, you can use a div. You can read more about semantics here.

Set selected index of dropdown to zero after form submit in ASP.NET MVC

I am bit to new asp.net mvc and using aps.net mvc 5. I have create the below dropdown using html helpers in aps.net mvc. When i submit(post back) the form i want to set the selected index to zero. Here i am using a optionLabel "--select--". I want to set the selected value to that one ("--select--") after post back. How to achieve this. Please help. Thank you.
#Html.DropDownListFor(model => model.TestCategory, new SelectList(#ViewBag.TestCategories, "value", "text"), "-- Select --", new { #class = "form-control input-sm"})
Controller Code
[HttpGet]
public ActionResult Index()
{
var model = new LaboratoryViewModel {
medicaltestlist = new List<MedicalTest>()
};
PopTestCategory();
PopEmptyDropdown();
return View(model);
}
[HttpPost]
public ActionResult Index(LaboratoryViewModel labvm)
{
var test = PopMedicalTests().Where(x => x.TestSerial == Convert.ToInt32(labvm.TestCode)).FirstOrDefault();
if (labvm.medicaltestlist == null)
labvm.medicaltestlist = new List<MedicalTest>();
if(!labvm.medicaltestlist.Any(x=> x.TestSerial == test.TestSerial))
labvm.medicaltestlist.Add(test);
labvm.TestCategory = "";
PopTestCategory();
return View(labvm);
}
public void PopTestCategory()
{
var categorylist = new List<DropDownItem>
{
new DropDownItem{value="Medical",text="Medical"},
new DropDownItem{value="Animal",text="Animal"},
new DropDownItem{value="Food",text="Food"},
new DropDownItem{value="Water",text="Water"}
};
ViewBag.TestCategories = categorylist;
}
public class DropDownItem
{
public int id { get; set; }
public string value { get; set; }
public string text { get; set; }
}
You return the view in you post method so if you selected (say) Animal then that value will be selected when you return the view because the html helpers use the values from ModelState, not the model property. Setting labvm.TestCategory = ""; has no effect. The correct approach is to follow the PRG pattern and redirect to the GET method, however you can make this work by calling ModelState.Clear(); before setting resetting the value of TestCategory although this will clear all ModelState properties and errors and may have other side effects.
Side note: You DropDownItem class seems unnecessary. MVC already has a SelectListItem class designed to work with dropdownlists, and in any case you can replace all the code in your PopEmptyDropdown() method with
ViewBag.TestCategories = new SelectList(new List<string>() { "Medical", "Animal", "Food", "Water" });
and in the view
#Html.DropDownListFor(m => m.TestCategory, (SelectList)#ViewBag.TestCategories, "-- Select --", new { #class = "form-control input-sm"})
If you set the "value" attribute of the top item in the drop down list to something and then pass back a model containing that for the bound property it should work?

Populating sub menu using Jquery

I feel like a pain asking for help ALL the time but I guess that's what here is for.
New to C# MVC and new to JQuery. I'm not entriely sure about the program flow of things when it comes to Jquery and MVC.
There really isn't that much on tutorials. This is what I want to happen. Select a type of animal (dog) and return list of breeds in sub menu.
#using(Html.BeginForm("", "Pictures", FormMethod.Get))
{
Project1.Models.Animal animal = new Project1.Models.Animal();
#:Filter Photos
<span>
Filter by animal
#Html.DropDownList("animal", new List<SelectListItem> {
new SelectListItem {Text="Select Animal", Value=""},
new SelectListItem {Text="Dog", Value="dog"},
new SelectListItem {Text="Cat", Value="cat"},
})
</span>
//This is where I want jQuery to update the dropdownlist
<span>
Filter by breed
#Html.DropDownList("breed", new SelectList(animal.DogBreeds), "Choose a breed")
</span>
//End of wanted dynamic jquery dropdownlist
<span>
<input type="submit" value="Filter" />
</span>
}
Here is the jquery code
<script type="text/javascript">
$(document).ready(function () {
$("#animal").change(function () {
var animalType = $("#animal option:selected").text();
alert($("#animal option:selected").text());
$.ajax({
type: "POST",
url: "Pictures/GetBreed",
data: animal = animalType,
success: function (data) {
$("#breed").html(data);
}
});
});
})
</script>
And what I want populating is a predefined list. This is just a sample of the list of course
public class Animal
{
public List<string> DogBreeds = new List<string>()
{
"Affenpinscher","Afghan Hound","Airedale Terrier","Akita","Alapaha Blue Blood Bulldog",
"Alaskan Malamute","American Bulldog","American Cocker Spaniel","Anatolian Shepherd",
"Australian Cattle Dog","Australian Shepherd"
};
public List<string> CatBreeds = new List<string>()
{
"Abyssinian","Aegean Cat","Australian Mist","American Curl"
};
}
And here is Pictures/GetBreed, I think this is the bit i'm struggling with. I'm not sure of the correct way to return the data. What do I do!? IS this even the correct way?
public ActionResult GetBreed()
{
string animal = Request["animal"];
if (animal == "Dog")
return dog list;
elseif (animal == "Cat")
return cat list;
else
return null;
}
Thanks for any help!
MVC makes it very simply to pass json data between the client and your controller actions, you could take the following approach.
Javascript
You can get the selected animal type with the following:
var animalType = $('#animal').val()
You could use the getJson method as follows, please note you can also use Url.Action to populate the url i.e. #Url.Action("GetBreed"):
$.getJSON("Pictures/GetBreed", { animalType: animalType }, function (animals) {
// clear the select list
var breedSelect = $('#breed');
breedSelect.empty();
$.each(animals, function (index, breed) {
breedSelect.append($('<option/>', {
value: breed.Id,
text: breed.Name
}));
});
To explain what is happening in the above, a json object is passed as the argument i.e. { animalType: animalType }.
The cascading menu is emptied, the json data that is returned from the controller is looped adding an option to the select list.
Model
The above assumes a new model is created with an Id and a Name i.e.
public class Breed
{
public int Id { get; set;}
public string Name { get; set; }
}
Controller
Then change your controller action to return json as follows:
public ActionResult GetBreed(string animalType)
{
var breeds = new List<Breed>();
if (animalType == "Dog")
breeds = GetDogList();
else if (animalType == "Cat")
breeds = GetCatList();
return Json(breeds, JsonRequestBehavior.AllowGet);
}
The GetDogList and GetCatList just need to return a list of the Breed objects i.e.
private List<Breed> GetDogList()
{
var dogs = new List<Breed>();
dogs.Add(new Breed { Id = 1, Name = "Collie" });
....
return dogs;
}

Dynamic Actionlinks

I am using asp.net mvc 4 and I have the following scenario
Cities Places Events
------ ------------------
City 1 |
City 2 |
|
The left navigation (cities) list all of the cities in database. Places and Events also are links to action methods.
<li>#Html.ActionLink("Places", "Places", null, new{id="placeslink"})</li>
<li>#Html.ActionLink("Events", "Events", null, new{id="eventslink"})</li>
I am asynchronously loading Places and Events using the following script (jQuery)
$('#placeslink').click(function (event) {
event.preventDefault();
var url = $(this).attr('href');
$('#content').html(ajax_load).load(url);
});
$('#eventslink').click(function (event) {
event.preventDefault();
var url = $(this).attr('href');
$('#content').html(ajax_load).load(url);
});
Its is working fine and populates all the places (not city specific) and Events on the page from database when Places and Events links are clicked.
Now what I want to achieve is that when user click a city while viewing places, only the places in that city are displayed and if events are selected, the same city link should display the events in that city.
Similary if a city (e.g City 1) is selected and user click places, places in the selected city are displayed and if she clicks Events, events for the selected city are display.
I have the following action methods
public ActionResult Places()
{
if (Request.IsAjaxRequest())
{
....
return PartialView(model);
}
return View();
}
Its quite confusing and I cannot think of a single way how to generate appropriate links for City, places and events and achieve the above mentioned results.
Giving this a try, I would make the view model like this
public class PlacesAndEventsViewModel
{
public string LocationOption { get; set; } //places or events
public List<Place> Places { get; set; }
public List<Event> Events { get; set; }
public int? CityID { get; set; }
}
And my controller
//this is get
public ActionResult ShowLocations()
{
var model = new PlacesAndEventsViewModel();
model.CityID = null; //or any default value
model.LocationOption = "places"; //or any default value
model.Places = new List<Place>(); //or GetAllPlacesFromDB();
//You can do the same for events but I think you need one at a time
return View("ViewPlaces", model);
}
[HttpPost]
public ActionResult ShowLocations(PlacesAndEventsViewModel model)
{
if(model.LocationOption == "places")
{
model.Places = GetAllPlacesByCity(model.CityID);
return View("ViewPlaces", model); //All these could be partial view
}
else if(model.LocationOption == "cities")
{
model.Events = GetAllEventsByCity(model.CityID);
return View("ViewEvents", model); //All these could be partial view
}
else
{
return View("ViewPlaces", model); //All these could be partial view
}
}
You might need to change your Ajax to $.ajax()
$.ajax({
url: '#Url.Action("ShowLocation"),
data: { LocationOption: '#Model.LocationOption', CityID: #Model.CityID }
});

Categories

Resources