HTML.DropDownList values from multiple sources? - asp.net

In ASP.NET MVC, is it possible to fill the list of values of a Html.DropDownList from multiple data sources along with multiple manually entered values?
Basically, I envision it being formated like the below using something along the lines of OPTGROUP:
**Group 1**
Manual Item 1
Manual Item 2
**Group 2**
DS1 Item 1
DS1 Item 2
**Group 3**
DS2 Item 1
DS2 Item 2
I've thought about using a view on the DB and getting the data from that, however, I've not really the faintest how to lay it out like above using helpers and to pass the data to it from multiple sources.
Thanks for any help in advance.

As always start with a model (actually start with a unit test but no time for this here):
public class MyModel
{
public string SelectedItem { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
}
Then a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var items1 = new[]
{
new { Value = "1", Text = "Manual Item 1" },
new { Value = "2", Text = "Manual Item 2" },
};
// TODO: Go fetch those from your repo1
var items2 = new[]
{
new { Value = "3", Text = "DS1 Item 1" },
new { Value = "4", Text = "DS1 Item 2" },
};
// TODO: Go fetch those from your repo2
var items3 = new[]
{
new { Value = "5", Text = "DS2 Item 1" },
new { Value = "6", Text = "DS2 Item 2" },
};
var items = items1.Concat(items2).Concat(items3);
var model = new MyModel
{
Items = new SelectList(items, "Value", "Text")
};
return View(model);
}
}
And finally a strongly typed view to the model:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyApp.Models.MyModel>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<%= Html.DropDownListFor(x => x.SelectedItem, Model.Items) %>
</asp:Content>
You will probably define an intermediary type to avoid the anonymous types that I've used for brevity.
Remark: If your original question was about using an OPTGROUP then ignore my answer and make your intention clear so that you can get a more adapted answer.

It seems it would be easier for you to write your own helper. The basic syntax to do that is this:
// The class can be named anything, but must be static and accessible
public static class HtmlHelperExtensions
{
// The method name is what you want to call on Html,
// in this case Html.CoolSelectList(arguments...)
//
// The method has to be static, and the first argument should be of the type
// you're extending (in this case HtmlHelper, which is the type of the
// Html property on your view). The first argument must be prefixed with the
// "this" keyword, to indicate it's an extension method.
//
// All the following arguments will be arguments that you supply when calling
public static string CoolSelectList(this HtmlHelper helper,
IEnumerable<IEnumerable<CoolThingThatWeMakeAListOf>> groups)
{
// I chose an argument of type IEnumerable<IEnumerable<T>>, since that
// allows you to create each group of item on its own (i.e. get them from
// various data sources) and then add all of them to a list of groups
// that you supply as argument. It is then easy to keep track of which
// items belong to which groups, etc.
// Returned from the extension method is a string you have built, that
// constitutes the html you want to output on your view. I usually use
// the TagBuilder class to build the html.
return "this is what will be on the page";
}
}

Many solutions exist for you problem. One would be the one that Tomas described, another is a Controller Action that returns PartialView, which contains the code to render the input and option tags, another solution would be to have the Controller Action populate the ViewData with a SelectList or have the SelectList as a strong type for your View/ViewUserControl (Partial).

Related

ASP.NET MVC: How to send the ids of the DOM Body elements on the client's browser TO the controller when navigating from the view

I am working on an ASP.NET MVC app (ASP.NET NOT ASP.NET Core).
When a View is rendered, the user can click on some buttons on the page to collapse or show divs associated with each button. The div changes its class depending on whether it is collapsed or shown. I am using bootstrap attributes for this, and it works fine.
Now I have a "Save" button on the page. When the user clicks on this button, I need to retrieve the ids and classes of the divs, and pass them TO the Controller (in an array/collection/dictionary whatever).
Is there a way/method in ASP.NET to send to the Controller the attributes (ids, classes, etc) of the DOM elements on the client's browser ?
Thanks
If you want to send some attributes of DOM to Controller, I have a way.
HTML:
<div id="demo-1" class="chosendiv other-className" data-code ="abc">Lorem Ipsum</div>
<div id="demo-2" class="chosendiv other-className" data-code ="xyz">Lorem Ipsum</div>
<div id="demo-3" class="other-className" data-code ="mnt">Lorem Ipsum</div>
<button id="btn-save" onclick="Save()">SAVE</button>
Javascript
<script>
function Save(){
var cds = document.getElementsByClassName('chosendiv');
var finder = [];
if(cds != null){
for(i = 0; i< cds.length; i++){
finder.push({
ID: cds[i].getAttribute('id'),
ClassName: cds[i].getAttribute('class'),
Code: cds[i].getAttribute('data-code')
})
}
}
//
// Send finder to Controller. You can use Ajax...
// A simple ajax call:
//
$.ajax({
url: '/Home/YourAction',
type: 'GET', //<---- you can use POST method.
data:{
myDiv: JSON.stringify(finder)
},
success: function(response){
// Your code
}
})
}
</script>
Your Controller
public class HomeController: Controller
{
public HomeController(){}
[HttpGet]
public void YourAction(string myDiv)
{
//A lot of ways for converting string to Object, such as: creating new class for model, ...
// I use Dictionary Class
List<Dictionary<string, string>> temp = new List<Dictionary<string, string>>();
if(!string.IsNullOrEmpty(myDiv))
{
try
{
temp = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(myDiv);
}
catch { // Do something if it catches error. }
}
// Get a element (at index) from temp if temp.Count()>0
// var id = temp.ElementAt(index)["ID"];
// var className = temp.ElementAt(index)["ClassName"];
// var code = temp.ElementAt(index)["Code"];
//
//Your code
//
}
//......
}
It would be great if my answer could solve your problem.
Based on the answer provided by #Gia Khang
I made few changes in order to avoid the issue of the length of the URL exceeding the maximum limit.
Instead of adding the element's classes to an array using JS, I add them to a string :
function Save() {
var cds = document.getElementsByClassName('chosendiv');
// I use as string instead of an array
var finder = "";
if(cds != null){
for(i = 0; i< cds.length; i++){
finder = finder + "id=" + cds[i].getAttribute('id') + "class=" + cds[i].getAttribute('class') + "data-code=" +cds[i].getAttribute('data-code')
}
}
// Send finder to Controller. You can use Ajax...
// A simple ajax call:
var myURL = "/{Controller}/{Action}"
$.ajax({
url: myURL,
type: "POST",
data: { ids:finder },
success: function (response) {
}
})
}
In the Controller Action I add a parameter named "ids" (this must be the same name as the identifier of the data object in the post request)and I extract the id, class, and data value from the ids string by a method in one of my Models classes (sorry I work with VB.NET not with C# and it will take me a lot of time to convert the code to C#. I use the Split method in VB to split the ids string several times: a first one by using "id=" as delimiter, then spiting each element in the resulting array by the second delimiter "class=", etc. I add the resulting elements to a collection)
The Controller Action looks like this:
public class HomeController: Controller
{
public HomeController(){}
[HttpPost]
public void YourAction(string ids)
{
Models.myClass.splitStringMethod(ids)
Return View()
}
}

Do not post SelectList with Knockout

I'm running an ASP.net MVC4 application with Knockout.
I have a generic script that posts my Knockout Forms.
I need to optimize the data sent to the server, because when i post my Knockout ViewModel, SelectList with all items are posted too!
Example Server ViewModel :
Public Class FooViewModel
Public Property Bar As String
Public Property Products As List(Of SelectListItem)
End Class
The JS code to convert my Knockout ViewModel to JSON
var data = ko.toJSON(viewModel);
data variable contains all products items and that's not very optimized.
I found this code (which work) :
viewModel.toJSON = function () {
var copy = ko.toJS(this);
// remove any unneeded properties
delete copy.Products;
return copy;
}
But I need a generic solution ... ! And here I don't see how i can make it generic ...
A quick and dirty solution would be to add a suffix on every array properties like "_NoPost" and then loop and delete every property that has this suffix, but it smells ... bad :/
Any thoughts ?
The one option is to separate your form data from your lookup data like the following. This will allow you to get hold of only your form data when you need to post it to the server.
Public Class FormViewModel
Public Property Bar As String
End Class
Public Class FooViewModel
Public Property FormData As FormViewModel
Public Property Products As List(Of SelectListItem)
End Class
Which will allow you to
var data = ko.toJSON(viewModel);
$post(url, data.FormData, function(d){...});
In your HTML you will also have to include the FormData as part of the variable i.e.
<input data-bind="value: FormData.Bar">
EDIT
Based on your feedback you can use the following function to construct a "clean" object for you. The idea is to pass in the original JSON object as well as a mapping object which will indicate which of the properties should be excluded/left behind:
function MapJson(obj, map) {
if (obj == undefined)
return;
map = map || {};
var ret = {};
for (var prop in obj) {
if (map[prop] != undefined && map[prop] == false)
continue;
if (typeof obj[prop] !== "object")
ret[prop] = obj[prop];
else {
if (map.constructor == Array) {
ret[prop] = MapJson(obj[prop], map[0]);
}
else
ret[prop] = MapJson(obj[prop], map[prop]);
}
}
return ret;
}
You can then use it like this - by setting the property's value to false it will be excluded from the data. The sample shows how to block both an array within a child object as well as an array within an array:
var obj = {
Name: "John Doe",
Vehicle: {
Details: {
Make: "Mazda",
Model: 2010
},
Registration: "ABC123",
ServiceDates: ["01 Jan", "23 Feb", "13 March"]
},
WeekDays: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
Children: [{ Name: "Mary", Age: 4, Hobbies: ["Soccer", "Chess", "Swim"] }, { Name: "Jane", Age: 2, Hobbies: ["Tennis", "Movies", "Reading"] }]
};
var map = {
Vehicle: {
ServiceDates: false
},
Children: [{
Hobbies: false,
}]
};
MapJson(obj, map);
Hope it helps.
EDIT 2
Herewith a working sample based on the data you posted in your comment.
var vm = {
"Type":"PropertyTax",
"Label":"d",
"StartDate":"2015-01-01T00:00:00",
"EndDate":"2015-12-31T00:00:00",
"Value":0,
"RegularizationMonth":0,
"TotalConsumption":null,
"UnitPrice":null,
"Active":true,"Products":[{"Selected":false,"Text":"XXX 39","Value":"28"},{"Selected":false,"Text":"ZZZ","Value":"38"}],"ChargeProducts":[{"ProductID":"28","Products":[{"Selected":false,"Text":"XXX 39","Value":"28"},{"Selected":false,"Text":"XXX 41","Value":"8"}]}],
"map":{"Products":false,"ChargeProducts":[{"Products":false}]}
};
var result = MapJson(vm, vm.map);
console.log("Result: ", result);
If you use KO.Mapping, you can choose certain pieces to ignore.
var mapping = { 'ignore': ["SomeFieldProperty"] };
ko.mapping.fromJS(data, mapping, viewModel);
This section in the documentation lists all the ways you can manipulate the bindings coming and going, for ko.mapping:
http://knockoutjs.com/documentation/plugins-mapping.html
Scroll down to the bottom for the bits on ignore as well as topics covering how you can manage bindings and debinding.

Html.DropDownList default selection troubleshoot

I have a selection of names, drawn from my database, to which I prepend the option "None" in my Controller:
var rtrnStaff = (from st in db.PrmTbl_Staffs
join sal in db.PrmTbl_Salutations on st.SalutationID equals sal.ID
where st.Active == true
select new { st.ID, Name = sal.Desc + ". " + st.Name });
List<SelectListItem> staff = new SelectList(rtrnStaff, "ID", "Name").ToList();
staff.Insert(0, (new SelectListItem { Text = "None", Value = "0" }));
ViewData["Staff"] = staff;
I then present this list as a dropdown multiple times in my View, where each time I feed it a variable containing the ID of the desired default option:
#Html.DropDownList(thisSelectID, (IEnumerable<SelectListItem>)ViewData["Staff"], thisStaffID)
Using breakpoints, I can see that these variables are being filled correctly (eg. "3", "2", etc.), but in every case the option shown is the first one: "None". Where is my error?
When you use DropDownList you don't have any way to set the selected value but in the list itself. None of the overloads allows to do it.
So, if you only have one list, it's unavoidable to have all the controls with the same selected item.
Most times it's much better, and make the code much more clear to use a view model class for your view, and use DropDownListFor.
When you use DropDowListFor you set the default property value in the property of the view model, and you don't have to do anything special to set the selected value.
I.e. create a class like this:
public class MyViewModel
{
public List<SelectListItem> Staff { get; set; }
public int FirstId { get; set; }
public int SecondId { get; set; }
}
Then, in your view,
#model MyViewModel
...
#Html.DropDownListFor(m => m.FirstId, Model.Staff);
#Html.DropDownListFor(m => m.SecondId, Model.Staff);
Create an instance of MyViewModel, intialize the list and the Ids, and pass it to the View
MyViewModel model = new MyViewModel
{
// Initialize here
};
...
return View("ViewName", model);
There are additional advantages: you can use the same view model class as the parameter for the POST action method, and the properties are automatically bound to the corresponding properties, you have Intellisense and a typed view is safer to implement.

Control isn't getting the Selected value from DropDownListFor

I got the following Model:
public class ViewBloqueioNotaFiscal
{
public ViewComboStatus ComboStatus = new ViewComboStatus();
public class ViewComboStatus
{
public int? IdStatusSelecionado { get; set; }
public IEnumerable<SelectListItem> ComboStatus { get; set; }
}
}
The following controller method:
public ViewBloqueioNotaFiscal.ViewComboStatus geraComboStatus(int? statusSelecionado)
{
ViewBloqueioNotaFiscal.ViewComboStatus combo = new ViewBloqueioNotaFiscal.ViewComboStatus
{
IdStatusSelecionado = statusSelecionado,
ComboStatus = new[]{
new SelectListItem { Value = 1, Text = "Op1"},
new SelectListItem { Value = 2, Text = "Op2"}
}
};
return combo;
}
And my aspx is like:
<%: Html.DropDownListFor(x => x.ComboStatus.IdStatusSelecionado, Model.ComboStatus.ComboStatus) %>
Its getting perfectly displayed for selection but when I submit my form, my post method from controller gets the model perfectly with the values except for this combo that Im recieving null value into the model. As its the first one that I try, I think that something is wrong.
Could you guys check that for me? If you have any better solution for this I d like to know too.
thanks for the help !
You are not binding to the correct property of your view model. You are binding to some complex object (ComboStatus) which doesn't make sense.
You should bind the drop down list to the IdStatusSelecionado property:
<%: Html.DropDownListFor(
x => x.ComboStatus.IdStatusSelecionado,
Model.ComboStatus.ComboStatus
) %>
A strongly typed DropDownListFor helper requires at least 2 things on your view model:
A scalar property (int, decimal, string, ...) which will be used to bind to
A collection of value/text pairs.
If the collection of value/text pairs contains an item whose value is equal to the scalar property you used as first argument, this item will be preselected. For example if you wanted to preselect the second item in your example you would set IdStatusSelecionado=2 on your view model.
Side note: Model.ComboStatus.ComboStatus looks terrible. Please rename.

Asp.Net Gridview - One Column is List<string> - Want to Show Only The Last Item

I have an Asp.Net GridView. One of the Columns is a List, but I only want to show the Last Item in the list. How can I do this?
List<string> Column1
I am binding the Gridview to a business object:
public Class GridObject
{
List<string> Column1 { get; set; }
}
EDIT
This worked, but is it the best solution:
<%# ((List<string>)Eval("Column1"))[((List<string>)Eval("Column1")).Count - 1] %>
I would add a property to the object you are binding to, and use that property instead of the list property in your binding.
public Class GridObject
{
List<string> Column1 { get; set; }
public string Column1LastValue
{
get
{ // return Column1.Last(); if linq is available
return Column1[Column1.Count-1];
}
}
}
Edit: Adding a presentation wrapper allows you to unit test what will be displayed. You are doing a translation in the view, which is OK, but since you technically have some logic happening to translate your business object to something proper for display, you would likely want to unit test that translation. Then, any formatting you want to apply to any of your business object fields is wrapped in a testable class, rather than hidden on the untestable view. Here is a sample of how this could be done:
public class GridObjectView
{
private GridObject _gridObject;
public GridObjectView(GridObject gridObject)
{
_gridObject = gridObject;
}
public string Column1
{
get
{
return _gridObject.Column1.Last();
}
}
}
Then to do the databinding, you could do this:
List<GridObject> data = GetGridData();
grid.DataSource = data.Select(g => new GridObjectView(g));
grid.DataBind();
Your best bet is to create a template column and use an inline script to retrieve the value from the list:
<%= ((List<string>)DataBinder.Eval("Column1"))[((List<string>)DataBinder.Eval("Column1")).Count] %>
Or you could store the result in the text of a label or a literal.
Hope that helps

Resources