Reputation: 21
Seeing some odd behavior in a Blazor (server) application that I am working on. New to this technology, and not that great at web development to begin with, but here I sit.
The problem is that for some reason, and from debugging no code that I am deliberately firing, Blazor is removing/adding HTML elements on my page without calling back to the server C# code as far as I can tell. It's only doing it for 1 of 3 html select elements, which are coded exactly the same on the page. This particular select element seems to be firing filtering logic that I have in C#, but for the life of me I cannot figure out how it's doing it. And even if it is, it's not exactly correct in what it's doing as it relates to my C# logic.
What is being removed are the span/buttons rendered in the @foreach shown below, but ONLY when clicking something in the "Status Codes" select element. It only happens after the first filtering that renders content in the @foreach the first time. After that, changing the 'Status Code' select begins to alter the HTML client side, but for just that one select element. Not the other two. Notice I currently have the ApplyFilter commented out in C#, it only fires when clicking the 'Search' button. The other 2 select elements behave exactly as expected, which is that nothing happens with the @foreach until I physically click the 'Search' button.
I've put a break point on the subtree change in the browser dev tools for that @foreach block, and what I'm describing is what's happening in that event and the breakpoint being hit. The 1st and 3rd select elements never cause the subtree to change, the 2nd one does. The subtree change break indicates that blazor.server.js is calling 'removeChildElement'.
I currently have no javascript client side, other than what blazor might be doing. So this is pure Blazor server side, HTML/C# w/ the bits of Razor-ish scripting to render the page.
Thanks in advance for any thoughts on how to debug this further, or figure out what I've done wrong here...which may be PLENTY in how I've written this app.
<ul class="nav flex-column">
<li class="nav-item px-3">
<select class="form-control" @onchange="OnChangeFilterSite">
<option value="all" selected>Select Site :</option>
@foreach (string item in RtuServiceData.Sites) {
if (filterSite != null && filterSite == @item) {
<option selected value="@item">@item</option>
}
else {
<option value="@item">@item</option>
}
}
</select>
</li>
<li class="nav-item px-3">
<select class="form-control" @onchange="OnChangeFilterStatusCode">
<option value="all" selected>RTU Status :</option>
@foreach (AcceptedStatus item in RtuServiceData.StatusCodes) {
if (filterStatusCode != null && filterStatusCode == item.Code) {
<option selected value="@item.Code">@item.Description</option>
}
else {
<option value="@item.Code">@item.Description</option>
}
}
</select>
</li>
<li class="nav-item px-3">
<select class="form-control" @onchange="OnChangeFilterMatchingOption">
<option value="all" selected>Matching Option :</option>
@foreach (string item in RtuServiceData.MatchingOptions) {
if (filterMatchingOption != null && filterMatchingOption == @item) {
<option selected value="@item">@item</option>
}
else {
<option value="@item">@item</option>
}
}
</select>
</li>
<li class="nav-item px-3">
<label class="text-white"><b>Filter :</b></label>
<input type="text" class="form-control" @onchange="OnChangeFilterName"/>
<br>
<button class="btn btn-dark" @onclick="@(e => _ApplyFilter())">Search</button>
<br><br>
</li>
<li class="nav-item px-3">
<div style="height: 300px; overflow: auto; padding-right: 15px">
@foreach (RtuComposite item in RtuServiceData.RTUs_Filtered) {
<span class="btn btn-block @item.ACCEPTED_STATUS_BUTTON_CLASS" @onclick="@(e => _SetRtu(@item.Proposed.ID))">@item.Proposed.RTU</span>
}
</div>
</li>
</ul>
And here is the C# code that handles the various @onchange events.
private string filterSite;
public void OnChangeFilterSite(ChangeEventArgs e)
{
filterSite = e.Value.ToString();
//_ApplyFilter();
}
private string filterStatusCode;
public void OnChangeFilterStatusCode(ChangeEventArgs e)
{
filterStatusCode = e.Value.ToString();
//_ApplyFilter();
}
private string filterMatchingOption;
public void OnChangeFilterMatchingOption(ChangeEventArgs e)
{
filterMatchingOption = e.Value.ToString();
//_ApplyFilter();
}
private string filterName;
public void OnChangeFilterName(ChangeEventArgs e)
{
filterName = e.Value.ToString();
//_ApplyFilter();
}
Upvotes: 1
Views: 1063
Reputation: 21
Sorry, by 'button' I meant the DIV at the very bottom of the HTML that contains the SPAN elements using the bootstrap btn class for styling. Full code for the sidebar portion of the app.
<div>
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="rtu">
<span class="oi oi-home" aria-hidden="true"></span> RTU
@*   <span class="spinner-border" role="status"></span> *@
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="analogs">
<span class="oi oi-list-rich" aria-hidden="true"></span> Analogs
@*   <span class="spinner-border" role="status"></span> *@
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="digitals">
<span class="oi oi-list-rich" aria-hidden="true"></span> Digitals
@*   <span class="spinner-border" role="status"></span> *@
</NavLink>
</li>
</ul>
<br>
<ul class="nav flex-column">
<li class="nav-item px-3">
<select class="form-control" @bind="@filterSite">
<option value="all" selected>Select Site :</option>
@foreach (string item in RtuServiceData.Sites) {
if (filterSite != null && filterSite == @item) {
<option @key="@item" selected value="@item">@item</option>
}
else {
<option @key="@item" value="@item">@item</option>
}
}
</select>
</li>
<li class="nav-item px-3">
<select class="form-control" @bind="@filterStatusCode">
<option value="all" selected>RTU Status :</option>
@foreach (AcceptedStatus item in RtuServiceData.StatusCodes) {
if (filterStatusCode != null && filterStatusCode == item.Code) {
<option @key="@item.Code" selected value="@item.Code">@item.Description</option>
}
else {
<option @key="@item.Code" value="@item.Code">@item.Description</option>
}
}
</select>
</li>
<li class="nav-item px-3">
<select class="form-control" @bind="@filterMatchingOption">
<option value="all" selected>Matching Option :</option>
@foreach (string item in RtuServiceData.MatchingOptions) {
if (filterMatchingOption != null && filterMatchingOption == @item) {
<option @key="@item" selected value="@item">@item</option>
}
else {
<option @key="@item" value="@item">@item</option>
}
}
</select>
</li>
<li class="nav-item px-3">
<label class="text-white"><b>Filter :</b></label>
<input type="text" class="form-control" @bind="@filterName"/>
<br>
<button class="btn btn-dark" @onclick="@(e => _ApplyFilter())">Search</button>
<br><br>
</li>
<li class="nav-item px-3">
<div style="height: 300px; overflow: auto; padding-right: 15px">
@foreach (RtuComposite item in RtuServiceData.RTUs_Filtered) {
<span @key="@item.Proposed.ID" class="btn btn-block @item.ACCEPTED_STATUS_BUTTON_CLASS" @onclick="@(e => _SetRtu(@item.Proposed.ID))">@item.Proposed.RTU</span>
}
</div>
</li>
</ul>
@code {
private string filterSite;
@* public void OnChangeFilterSite(ChangeEventArgs e)
{
filterSite = e.Value.ToString();
//_ApplyFilter();
} *@
private string filterStatusCode;
@* public void OnChangeFilterStatusCode(ChangeEventArgs e)
{
filterStatusCode = e.Value.ToString();
//_ApplyFilter();
} *@
private string filterMatchingOption;
@* public void OnChangeFilterMatchingOption(ChangeEventArgs e)
{
filterMatchingOption = e.Value.ToString();
//_ApplyFilter();
} *@
private string filterName;
@* public void OnChangeFilterName(ChangeEventArgs e)
{
filterName = e.Value.ToString();
//_ApplyFilter();
} *@
protected override async Task OnInitializedAsync()
{
await Task.Run(_Load);
}
protected void _Load() {
RtuServiceData.RTUs = RtuService._GetRtuList();
RtuServiceData.RTUs_Filtered = RtuServiceData.RTUs;
}
private void _ApplyFilter() {
RtuServiceData.RTUs_Filtered = RtuServiceData.RTUs; //get the full list from the singleton
//only filter on site when it's one of the values in the list
if (!string.IsNullOrEmpty(filterSite) && RtuServiceData.Sites.Contains(filterSite)) {
RtuServiceData.RTUs_Filtered = RtuServiceData.RTUs_Filtered.Where(r => r.Proposed.SITE == filterSite).ToList();
}
//only filter on status code when it's one of the values in the list
if (!string.IsNullOrEmpty(filterStatusCode) && RtuServiceData.StatusCodes.Select(x => x.Code).Contains(filterStatusCode)) {
RtuServiceData.RTUs_Filtered = RtuServiceData.RTUs_Filtered.Where(r => r.Accepted.ACCEPTED_STATUS == filterStatusCode);
}
//only filter on matching option when it's one of the values in the list
switch (filterMatchingOption) {
case "100% Match":
RtuServiceData.RTUs_Filtered = RtuServiceData.RTUs_Filtered.Where(r => r.Proposed.MATCH_PERCENT == 100);
break;
case "< 100% Match":
RtuServiceData.RTUs_Filtered = RtuServiceData.RTUs_Filtered.Where(r => r.Proposed.MATCH_PERCENT > 0 && r.Proposed.MATCH_PERCENT < 100);
break;
case "No Match":
RtuServiceData.RTUs_Filtered = RtuServiceData.RTUs_Filtered.Where(r => r.Proposed.MATCH_PERCENT == 0);
break;
default: // default: provides the same functionality as checking filterSite and filterStatusCode against legit values above
break;
}
//only filter on name when it's populated
if (!string.IsNullOrEmpty(filterName)) {
RtuServiceData.RTUs_Filtered = RtuServiceData.RTUs_Filtered.Where(r => r.Proposed.RTU.ToUpper().StartsWith(filterName.ToUpper()));
}
}
private void _SetRtu(int id) {
RtuServiceData.SelectedRtu = null;
NavigationManager.NavigateTo("/");
RtuServiceData.SelectedRtu = RtuService._GetRtu(id);
NavigationManager.NavigateTo("rtu");
}
}
Upvotes: 0
Reputation: 21
Thanks. Just commented out the Site and Matching selects, same behavior. First selection of the Status Code, and going back and forth between options, does nothing. Clicking 'Search' filters as expected then on first 'Search' click. Then, if I switch back to the 'all' item in the Status Code list, all of the buttons disappear. Clicking on additional options filters correctly. This is so strange.
Revised code below...
<ul class="nav flex-column">
<li class="nav-item px-3">
<select class="form-control" @bind="@filterSite">
<option value="all" selected>Select Site :</option>
@foreach (string item in RtuServiceData.Sites) {
if (filterSite != null && filterSite == @item) {
<option @key="@item" selected value="@item">@item</option>
}
else {
<option @key="@item" value="@item">@item</option>
}
}
</select>
</li>
<li class="nav-item px-3">
<select class="form-control" @bind="@filterStatusCode">
<option value="all" selected>RTU Status :</option>
@foreach (AcceptedStatus item in RtuServiceData.StatusCodes) {
if (filterStatusCode != null && filterStatusCode == item.Code) {
<option @key="@item.Code" selected value="@item.Code">@item.Description</option>
}
else {
<option @key="@item.Code" value="@item.Code">@item.Description</option>
}
}
</select>
</li>
<li class="nav-item px-3">
<select class="form-control" @bind="@filterMatchingOption">
<option value="all" selected>Matching Option :</option>
@foreach (string item in RtuServiceData.MatchingOptions) {
if (filterMatchingOption != null && filterMatchingOption == @item) {
<option @key="@item" selected value="@item">@item</option>
}
else {
<option @key="@item" value="@item">@item</option>
}
}
</select>
</li>
<li class="nav-item px-3">
<label class="text-white"><b>Filter :</b></label>
<input type="text" class="form-control" @bind="@filterName"/>
<br>
<button class="btn btn-dark" @onclick="@(e => _ApplyFilter())">Search</button>
<br><br>
</li>
<li class="nav-item px-3">
<div style="height: 300px; overflow: auto; padding-right: 15px">
@foreach (RtuComposite item in RtuServiceData.RTUs_Filtered) {
<span @key="@item.Proposed.ID" class="btn btn-block @item.ACCEPTED_STATUS_BUTTON_CLASS" @onclick="@(e => _SetRtu(@item.Proposed.ID))">@item.Proposed.RTU</span>
}
</div>
</li>
</ul>
private string filterSite;
@* public void OnChangeFilterSite(ChangeEventArgs e)
{
filterSite = e.Value.ToString();
//_ApplyFilter();
} *@
private string filterStatusCode;
@* public void OnChangeFilterStatusCode(ChangeEventArgs e)
{
filterStatusCode = e.Value.ToString();
//_ApplyFilter();
} *@
private string filterMatchingOption;
@* public void OnChangeFilterMatchingOption(ChangeEventArgs e)
{
filterMatchingOption = e.Value.ToString();
//_ApplyFilter();
} *@
private string filterName;
@* public void OnChangeFilterName(ChangeEventArgs e)
{
filterName = e.Value.ToString();
//_ApplyFilter();
} *@
Upvotes: 1
Reputation: 43
It is a good idea to bind @key="something unique" to components and elements in loops. It helps Blazor know what has changed. Put it on the span as it is in a loop
Upvotes: 1