Hello,
By default, ASP.NET MVC ListBoxFor() helper does not have support for optgroup tag. To achieve this functionality, we either use jQuery or create a custom html helper. The example below demonstrates using the latter and is a VB.NET version of what we have developed in C#.
First, we define our ViewModel class for the countries with states and selected states.
VB.NET Code:
Public Class CountriesStatesViewModel
Public Property SelectedStates() As IEnumerable(Of String)
Public Property CountriesAndStates() As Dictionary(Of String, IEnumerable(Of SelectListItem))
End Class
Next, we create the custom ListBoxFor() helper that supports optgroup tag.
VB.NET Code:
Public Module HtmlExtensions
<Extension()>
Public Function ListBoxFor(Of TModel, TProperty)(htmlHelper As HtmlHelper(Of TModel), expression As Expression(Of Func(Of TModel, TProperty)), _
selectList As Dictionary(Of String, IEnumerable(Of SelectListItem)), Optional htmlAttributes As Object = Nothing) As IHtmlString
Dim selectTag = New TagBuilder("select")
selectTag.Attributes.Add("name", ExpressionHelper.GetExpressionText(expression))
If htmlAttributes IsNot Nothing Then
Dim routeValues As New RouteValueDictionary(htmlAttributes)
If Not routeValues.ContainsKey(("size").ToLower()) Then
selectTag.Attributes.Add("size", selectList.Sum(Function(x) x.Value.Count()).ToString())
End If
For Each item In routeValues
selectTag.Attributes.Add(item.Key, item.Value.ToString())
Next
Else
selectTag.Attributes.Add("size", selectList.Sum(Function(x) x.Value.Count()).ToString())
End If
Dim optgroups = New StringBuilder()
For Each kvp In selectList
Dim optgroup = New TagBuilder("optgroup")
optgroup.Attributes.Add("label", kvp.Key)
Dim options = New StringBuilder()
For Each item In kvp.Value
Dim optionTag = New TagBuilder("option")
optionTag.Attributes.Add("value", item.Value)
optionTag.SetInnerText(item.Text)
If item.Selected Then
optionTag.Attributes.Add("selected", "selected")
End If
options.Append(optionTag.ToString(TagRenderMode.Normal))
Next
optgroup.InnerHtml = options.ToString()
optgroups.Append(optgroup.ToString(TagRenderMode.Normal))
Next
selectTag.InnerHtml = optgroups.ToString()
Return MvcHtmlString.Create(selectTag.ToString(TagRenderMode.Normal))
End Function
End Module
In our controller, we add two action result methods. The Index action adds data to our model and then pass it to our view for rendering. The SaveEntry action retrieves the selected states chosen by the user.
VB.NET Code:
Function Index() As ActionResult
Dim model As New CountriesStatesViewModel
Dim items As New Dictionary(Of String, IEnumerable(Of SelectListItem))
model.CountriesAndStates = New Dictionary(Of String, IEnumerable(Of SelectListItem))
items.Add("US", New List(Of SelectListItem)() From { _
New SelectListItem() With {.Text = "Arizona", .Value = "001", .Selected = False},
New SelectListItem() With {.Text = "Montana", .Value = "002", .Selected = False}
})
items.Add("AU", New List(Of SelectListItem)() From { _
New SelectListItem() With {.Text = "Queensland", .Value = "003", .Selected = False},
New SelectListItem() With {.Text = "Victoria", .Value = "004", .Selected = False}
})
items.Add("BR", New List(Of SelectListItem)() From { _
New SelectListItem() With {.Text = "Bahia", .Value = "005", .Selected = False},
New SelectListItem() With {.Text = "Minas Gerais", .Value = "006", .Selected = False}
})
model.CountriesAndStates = items
Return View(model)
End Function
<HttpPost>
Function SaveEntry(SelectedStates As IEnumerable(Of String)) As ActionResult
'get selected states
If SelectedStates IsNot Nothing Then
If SelectedStates.Count() > 0 Then
TempData("list") = SelectedStates.ToList() 'replace with your own code
Return RedirectToAction("SelectionSuccess") 'replace with your own code
End If
End If
Return RedirectToAction("Index") 'replace with your own code
End Function
And in our view (Index.vbhtml), make sure to reference the ViewModel class and Helper.
HTML Code:
@ModelType ASPMVCListBoxForHelper.CountriesStatesViewModel
@Imports ASPMVCListBoxForHelper.Helpers
<div>
@Using Html.BeginForm("SaveEntry", "Home", FormMethod.Post)
@Html.ListBoxFor(Function(t) t.SelectedStates, Model.CountriesAndStates, New With {.Multiple = "Multiple", .Size = Model.CountriesAndStates.Sum(Function(x) x.Value.Count())})
@<br />
@<input name="Save" type="submit" value="Save" />
End Using
</div>
Screenshot
Source code: VB.NET Version|C# Version
That's it.. :-)