How to highlight list item inside a nested list in MudBlazor? - mudblazor

I want to be able to highlight a <MudListItem> inside a <NestedList> using the property SelectedValue
This sample code works like a charm
<MudList Clickable="true" SelectedValue="3">
<MudListItem Value="1">Item 1</MudListItem>
<MudListItem Value="2">Item 2</MudListItem>
<MudListItem Value="3">Item 3</MudListItem> <-- cool! it is highlighted
</MudList>
However, in this markup, the selected value doesn't get highlighted
<MudList Clickable="true" SelectedValue="3">
<MudListItem Text="GroupOne" InitiallyExpanded="true">
<NestedList>
<MudListItem Value="1">Item 1</MudListItem>
<MudListItem Value="2">Item 2</MudListItem>
</NestedList>
</MudListItem>
<MudListItem Text="GroupTwo" InitiallyExpanded="true">
<NestedList>
<MudListItem Value="3">Item 3</MudListItem> <-- nope, doesn't get highlighted
<MudListItem Value="4">Item 4</MudListItem>
</NestedList>
</MudListItem>
</MudList>

It seems that the MudList instance isn't able to see the items due to them being wrapped in a NestedList.
As a workaround, you can try highlighting an item programmatically, based on the SelectedValue, by assigning a SelectedItem value, when the component is first rendered.
Example:
https://try.mudblazor.com/snippet/cOQQaWvOqBoBxsAf
<MudList #ref=#list Clickable="true" SelectedValue="3">
<MudListItem Text="GroupOne" InitiallyExpanded="true">
<NestedList>
<MudListItem #ref=#items[0] Value="1">Item 1</MudListItem>
<MudListItem #ref=#items[1] Value="2">Item 2</MudListItem>
</NestedList>
</MudListItem>
<MudListItem Text="GroupTwo" InitiallyExpanded="true">
<NestedList>
<MudListItem #ref=#items[2] Value="3">Item 3</MudListItem>
<MudListItem #ref=#items[3] Value="4">Item 4</MudListItem>
</NestedList>
</MudListItem>
</MudList>
#code {
private MudList list;
private MudListItem[] items = new MudListItem[4];
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
var itemToSelect = items.FirstOrDefault(x => x.Value.Equals(list.SelectedValue));
if (itemToSelect != null)
{
list.SelectedItem = itemToSelect;
StateHasChanged();
}
}
}
}

Related

Blazor select dropdown set active value by code

I am dynamicaly filling the values of a dropdown list based of the content of a List. when a item in the dropdown is selected an option to remove this item is shown. When the item is removed, it is first removed from the list and then the dropdown list is rebuilt, this is where I run in to problems. Instead of returning the dropdown to its default value when it is rebuilt the value just below the removed one is shown as selected (this happens whitout the #onchange value being triggered). How can i make the dropdown list return to its default value when it is being rebuilt?
here is some code, Razor code:
<select class="form-control" #onchange="selectedValue">
<option value="">select one</option>
#foreach (var mod in values)
{
<option value="#mod.Id">#mod.Name</option>
}
</select>
the class named Items that is populationg the list:
public class Items
{
public string Name { get; set; }
public int Id { get; set; }
public Items(string name, int id)
{
Name = name;
Id = id;
}
}
the list itself (before it is populated):
public List<Items> itm = new List<Items>();
the function thats called onchange:
public string SelectedValue = "";
public void selectedValue(ChangeEventArgs selectEvent)
{
SelectedValue = selectEvent.Value.ToString();
}
so to sum it up, this is what's happening:
the list is pupolated with Items.
the select list is built of the items in the list using a #foreach loop (se razor code).
an item is selected from the dropdown, this runs the selectedValue() function and changes the value ud the SelectedValue string.
the selected item is deleted from the list of items
the dropdown is rebuilt using the modefied list
the selected item is now set to the one directly below the deleted item, this is happening without onchange being run. this is the problem
Now the selected value of the dropdown don't correspond to the one of the SelectedValue string.
this can probably be solved by setting the element to its default option/value, but i can't figure out how to do this.
How can I change the selected value to be the default option (in my case the "select one" option with value "") of my dropdown list?
I was having a similar issue. I solved it by putting an if statement in the loop that generates the option elements and from there conditionally add the selected attribute, i.e.;
<select #onchange="listChanged">
#foreach (var item in PageModel.Lists)
{
if(item.Id == currListId)
{
<option value="#item.Id" selected>#item.Name</option>
}
else
{
<option value="#item.Id">#item.Name</option>
}
}
</select>
Sorry for not taking the time to adapt my code to your situation, but you've already solved your problem. This is for anyone else that comes across this problem (prolly me next time i need to do something like this, lol).
How can I change the selected value to be the default option (in my case the "select one" option with value "") of my dropdown list?
I'm afraid you can do it only with JSInterop as follows:
Generally speaking, the selectedIndex property on the select element is set to 0 after you select an option in the select element. This is done in the selectedValue method...
This is a complete working code snippet. Run it and test it...
#page "/"
<select #ref="myselect" id="myselect" class="form-control"
#onchange="selectedValue">
<option selected value="-1">select one</option>
#foreach (var item in items)
{
<option value="#item.ID">#item.Name</option>
}
</select>
<p>#SelectedValue</p>
#code {
[Inject] IJSRuntime JSRuntime { get; set; }
private ElementReference myselect;
List<Item> items = Enumerable.Range(1, 10).Select(i => new Item {
Name = $"Name {i.ToString()}", ID = i }).ToList();
public string SelectedValue = "";
public void selectedValue(ChangeEventArgs args)
{
SelectedValue = args.Value.ToString();
var item = items.Single(item => item.ID == Convert.ToInt32(SelectedValue));
items.Remove(item);
JSRuntime.InvokeVoidAsync("exampleJsFunctions.selectElement", myselect);
}
public class Item
{
public string Name { get; set; }
public int ID { get; set; }
}
}
Put the following JS code in the _Host.cshtml file:
<script src="_framework/blazor.server.js"></script>
<script>
window.exampleJsFunctions =
{
selectElement: function (element) {
element.selectedIndex = 0;
}
};
</script>
A similar problem confronted me in a .NET MAUI Blazor project. In the project I'm working on a view model manages most of the form behavior. In addition, CommunityToolkit.Mvvm is used to manage the property changed management. So this answer will base itself on that configuration adjusted for using bindings. It also follows answer given by #MikeT.
The assumption is that another element is effecting the selection decision. In my case if there is only one element in values to choose from, that element would become selected automatically. Otherwise, the user makes a selection.
<select class="form-control" #Bind="vm.selectedValueId"
disabled="#(vm.IsValuesDataLoaded == false)">
<option value="">select one</option>
#foreach (var mod in values)
{
#if(#mod.Id == #vm.selectedValueId)
{
<option value="#mod.Id" selected>#mod.Name</option>
}
else
{
<option value="#mod.Id">#mod.Name</option>
}
}
</select>
#code{
ValueViewModel vm; //instantiated inside OnInitialized
// more ...
}
As the view model is handling construction of the element list in values, all of the activity takes place there. So this section of view model code would be --
partial void OnSomeOtherPreliminaryPropertyChanged(string? value)
{
// code for processing this event
// condition test could also be for an entry previously selected
if(values.Count == 1)
{
SelectedValueId = whatever_Value_Should_Be_Default_For_Selection;
OnModuleIdChanged(selectedValueId);
}
IsValuesDataLoaded = true;
}
[ObservableProperty]
private int selectedValueId;
[ObservableProperty]
private string selectedValue;
//OnSelected... is called when selectedValueId changes. In this case
// the code is changing the selection
partial void OnSelectedValueIdChanged(int? value)
{
SelectedValue = Values[value].Name
}
public bool IsValuesDataLoaded //controls the enabled state of the select element
{
get;
private set;
} = false;

XPage datatable and pager issue

On my XPage, I have a series of search filters (State, First Name and Last Name). The search button triggers the population of the datatable which is bound to a Managed Bean.
The datatable initially gets populated with rows of data. However when I click on Next (in the pager), instead of loading the next 5 rows, it returns no more data. I KNOW the datatable is getting the right number of rows because if I change the Repeat limit in the datatable to 10, they all display. Also, the number of pages in the pager is 2 (which is the correct number).
Any ideas what could be causing this?
Thanks,
Dan
Code for the datatable:
<xp:dataTable rows="5" id="studentTable" var="currentStudent"
style="width:400.0px" value="#{studentlist.students}">
<xp:column id="firstnameColumn" style="font-weight:bold">
<xp:this.facets>
<xp:span xp:key="header">
<xp:span style="font-weight:bold">
First Name
</xp:span>
</xp:span>
</xp:this.facets>
<xp:text escape="true" id="firstnameField"
value="#{currentStudent.firstname}">
</xp:text>
</xp:column>
<xp:column id="column1">
<xp:text escape="true" id="middleinitialField"
value="#{currentStudent.middleName}">
</xp:text>
<xp:this.facets>
<xp:span xp:key="header">
<xp:span style="font-weight:bold">
Middle Name
</xp:span>
</xp:span>
</xp:this.facets>
</xp:column>
<xp:column id="lastnameColumn" style="font-weight:bold">
<xp:this.facets>
<xp:span xp:key="header">
<xp:span style="font-weight:bold">
Last Name
</xp:span>
</xp:span>
</xp:this.facets>
<xp:text escape="true" id="lastnameField"
value="#{currentStudent.lastname}">
</xp:text>
</xp:column>
<xp:column id="idColumn">
<xp:this.facets>
<xp:span xp:key="header">
<xp:span style="font-weight:bold">ID</xp:span>
</xp:span>
</xp:this.facets>
<xp:text escape="true" id="computedField1"
value="#{currentStudent.id}">
</xp:text>
</xp:column>
<xp:this.facets>
<xp:pager layout="Previous Group Next" xp:key="header"
id="pager1" for="studentTable" partialRefresh="true">
</xp:pager>
</xp:this.facets></xp:dataTable>
Code behind the button:
var state=getComponentValue('state');
var firstName=document1.getItemValueString("firstName");
var lastName=document1.getItemValueString("lastName");
if(state == "--" || firstName == "" || lastName == "")
{
//do nothing
}
else{
studentlist.setConnDB("jdbc:sqlserver://XX.XX.X.XX:1433;DatabaseName=dan_test");
studentlist.setConnUserName("test");
studentlist.setConnPassword("Password1");
studentlist.setSQLQuery("SELECT FirstName,MiddleName,LastName,ID FROM TestStudents WHERE FirstName Like '"+firstName+"%' AND LastName Like '"+lastName+"%' AND State = '"+state+"' ORDER BY LastName ASC");
I'm making some assumptions here without seeing your MBean code, but hope to point you in the right direction nonetheless...
Ensure you are restoring the datamodel within the studenLists bean during invocations of the getStudents() call. I see you have value="#{studentlist.students}" in the XSP fragment above... that should map to a getStudents() method on the bean returning a DataModel of Student objects or the like... presumably yes? So, a couple of things to bear in mind here... ensure the bean is in at least viewScope (preferably) and that you "restore the wrapped data" in the getStudents() method based on a non-transient buffer (eg: ArrayList that is holding the current set of filtered "student" objects - and make sure that the buffer of Student objects is serializable - critical for "Disk Persistence"!
If you use this approach, the Pager will always be "paging" against an up-to-date restored datamodel for each subsequent paging request posted against the current view - the page index etc being internally managed by the DataModel object.
eg:
1. No restoration:
...
private transient DataModel studentSearchResults;
private transient List searchResults;
...
public DataModel getStudents() {
if (null == studentSearchResults) {
studentSearchResults = new ListDataModel();
}
return studentSearchResults;
}
versus:
2. With restoration:
...
private transient DataModel studentSearchResults;
private List searchResults;
...
public DataModel getStudents() {
if (null == studentSearchResults) {
studentSearchResults= new ListDataModel();
if(null != searchResults){
studentSearchResults.setWrappedData(searchResults);
}
}
return studentSearchResults;
}
Do you also lose the data if you add another button that just partially refreshes the dataTable? That should identify if it's a problem with managed bean properties being reset (in which case the contents of the dataTable would disappear) or a problem with paging (in which case they wouldn't).

Spring 3: Select value to enum value mapping

I have a very simple scenario to handle. An enum is created to represent a set of options for select control. The select control needs to have a prompt mapped to '-' as the prompt value. The corresponding enum does not have this dash. When page is submitted with select control still sitting at the prompt, exception is thrown. How do you handle such cases?
Page:
<select id="filterUserAccessLevel" name="filterUserAccessLevel">
<option value="-">Select Value</option>
<option value="DEPOSITOR">Depositor</option>
<option value="READER">Reader</option>
<option value="AUTHOR">Author</option>
<option value="EDITOR">Editor</option>
<option value="ADMINISTRATOR">Administrator</option>
</select>
<input type="submit" name="resetFilter" value="<spring:message code="common.filterResetButtonLabel" />" />
UserAccessLevel enum:
public enum UserAccessLevel {
DEPOSITOR("DEPOSITOR"),
READER("READER"),
AUTHOR("AUTHOR"),
EDITOR("EDITOR"),
ADMINISTRATOR("ADMINISTRATOR");
private String code;
private UserAccessLevel(String code) {
this.code=code;
}
public String getCode() {
return this.code;
}
}
Controller:
#RequestMapping(value="/userIndex/", method=RequestMethod.POST, params="resetFilter")
public void resetFilter(#ModelAttribute("userIndexBean") UserIndexBean bean, Model model) {
System.out.println("resetFilter()");
bean.resetFilterSection();
loadBean(1, bean, model);
}
Exception:
Field error in object 'userIndexBean' on field 'filterUserAccessLevel': rejected value [-];
Why is necessary an option mapped to "-"? Can't it be just an empty String?
In this case, I think that the simplest solution is:
<option value="">Select Value</option>
.
#RequestMapping("userIndex")
public void resetFilter(#RequestParam(required = false) UserAccessLevel filterUserAccessLevel) {
...
}

ASP.NET MVC2 validation not working with drop down list in IE <8

I have a form with a dropdownlist rendered using Html.DropDownListFor(...). The view model field that corresponds with the dropdown list has a [Required(...)] attribute attached to it. This works fine on my local machine, but as soon as I publish to our development server, the drop down lists keep displaying the required error message, even when a value is selected in the list. This only happens in IE - Firefox submits just fine.
Any thoughts?
Relevant code
View:
<ol class="form">
<li>
<%= Html.LabelFor(x => x.ContactTitle) %>
<%= Html.DropDownListFor(x=>x.ContactTitle, Model.GetTitleOptions()) %>
<%= Html.ValidationMessageFor(x => x.ContactTitle) %>
</li>
<!-- more fields... -->
</ol>
View Model:
[Required(ErrorMessage = "Title is required")]
[DisplayName("Title")]
public string ContactTitle { get; set; }
// ...
public SelectList GetTitleOptions()
{
return new SelectList(new string[]
{
"","Dr.", "Mr.", "Ms.", "Mrs.", "Miss"
});
}
It's all pretty basic stuff... I'm at a loss.
Edit: Just discovered this bug is limited to IE 8 compatibility view (and maybe prior versions). IE 8 in standards mode works as expected...
Chalk this one up to stupidity. The code in the example produces output similar to the following:
<select>
<option></option>
<option>Dr.</option>
<option>Mr.</option>
<option>Ms.</option>
<option>Mrs.</option>
<option>Miss</option>
</select>
And the relevant MVC validation function (when a RequiredAttribute is applied to a property that corresponds to a drop down list) is:
Sys.Mvc.RequiredValidator._validateSelectInput = function Sys_Mvc_RequiredValidator$_validateSelectInput(optionElements) {
/// <param name="optionElements" type="DOMElementCollection">
/// </param>
/// <returns type="Object"></returns>
for (var i = 0; i < optionElements.length; i++) {
var element = optionElements[i];
if (element.selected) {
if (!Sys.Mvc._validationUtil.stringIsNullOrEmpty(element.value)) {
return true;
}
}
}
return false;
}
Notice the function checks element.value. In the case of the html above, the value attribute is empty because there is no value attribute on the option elements. Therefore, the validation function returns false and the error occurs. This only appears to happen in IE <8, presumably because other browsers by default assign an option element's text to the value attribute if none is specified.
The solution was to modify the way I was returning the select list items from which the drop down list was built like so:
public IEnumerable<SelectListItem> GetTitleOptions()
{
return BuildSelectListItems(new string[]
{
"","Dr.", "Mr.", "Ms.", "Mrs.", "Miss"
});
}
private List<SelectListItem> BuildSelectListItems(IEnumerable<string> values) {
return (from v in values
select new SelectListItem()
{
Text = v,
Value = v
}).ToList();
}
This results in the much more predictable HTML output:
<select>
<option value=""></option>
<option value="Dr.">Dr.</option>
<option value="Mr.">Mr.</option>
<option value="Ms.">Ms.</option>
<option value="Mrs.">Mrs.</option>
<option value="Miss">Miss</option>
</select>
which of course the function validates properly.

Conditional Item Templates with RadComboBox

I have a RadComboBox that I am using to display department name and abbreviations. I am using an Item Template with a LinqDataSource to make each item appear as:
DeptAbbr - (DeptName)
Here is the code I am using to do this and it works fine:
<telerik:RadComboBox ID="rcbDepartments" runat="server" AppendDataBoundItems="True"
OnInit="rcbDepartments_Init" DataTextField="DepartmentAbbr" AutoPostBack="True"
DataSourceID="ldsDepartments" DataValueField="DepartmentID" HighlightTemplatedItems="true"
NoWrap="true" Width="250px">
<ItemTemplate>
<div>
<b>
<%# Eval("DepartmentAbbr")%></b><%# Eval("DepartmentName", " - ({0})") %>
</div>
</ItemTemplate>
</telerik:RadComboBox>
My question is this. I want to add an initial item in the list that is for "All Departments" and is the default item. I can do this easily, but the problem I'm having is that because I am not storing an "All Departments" entry in the database, the templating shows a blank space at the beginning of the items list when you pull down the combo box. I'm trying to find out if there is any way to template all but first item in the list?
Note: I have also tried do a conditional in the Eval like this:
<b><%# (Eval("DepartmentAbbr") != null) ? Eval("DepartmentAbbr") : "All Departments" %></b><%# Eval("DepartmentName", " - ({0})") %>
But it only evaluates on the items that are databound and not the initial item which I am sticking in manually. In other words, if I change the above statement to be:
<b><%# (Eval("DepartmentAbbr") == null) ? Eval("DepartmentAbbr") : "All Departments" %></b><%# Eval("DepartmentName", " - ({0})") %>
Then I just get a list with one blank item at the top and the rest reading "All Departments".
My work around for this problem has been to do some funky selection stuff with LINQ server side, but that has forced me to get rid of all templating and html formatting.
You can define the 'All Departments' RadComboBoxItem as a static item in the <Items> collection. Since you have enabled the AppendDataBoundItems property, you don't want to bind to your data source until after the control has already bound the static items; otherwise you'll get the blank space you are seeing when expanding the combo box. Also, use DataBinder.Eval(Container, "Text") to render the DepartmentAbbr field. Since you have set this field as the DataTextField for the control, that value will always render. If not, you'll get the empty space again when the control binds to the static item because it doesn't know what DepartmentAbbr is; it only has a Text field. Here's an example to get you going:
<telerik:RadComboBox ID="RadComboBox1" runat="server"
AppendDataBoundItems="True"
DataTextField="Abbr"
AutoPostBack="True"
DataValueField="DeptID"
HighlightTemplatedItems="true"
NoWrap="true"
Width="250px">
<Items>
<telerik:RadComboBoxItem runat="server" Text="All Departments" />
</Items>
<ItemTemplate>
<div>
<b><%# DataBinder.Eval(Container, "Text")%></b><%# Eval("Name", " - ({0})") %>
</div>
</ItemTemplate>
</telerik:RadComboBox>
public partial class _Default : System.Web.UI.Page
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
RadComboBox1.Load += new EventHandler(RadComboBox1_Load);
}
protected void RadComboBox1_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Ensure the static items are already bound before assigning
// new data to the DataSource property
RadComboBox1.DataBind();
var departments = new[] {
new { DeptID = 1, Abbr = "ACME", Name = "ACME Corporation" },
new { DeptID = 2, Abbr = "MSFT", Name = "Microsoft Corporation" },
new { DeptID = 3, Abbr = "GOOG", Name = "Google, Inc" }
};
RadComboBox1.DataSource = departments;
RadComboBox1.DataBind();
}
}
}
Hope that helps!

Resources