I have two RenderFragments in blazor component. One is MainContent another one is AuxilaryContent i.e. I place the AuxilaryContent at first followed by MainContent. As of now, Auxilary content rendered succeeded by MainContent because as I placed AuxilaryContent at first.
But my requirement is that I need to render MainContent first, based upon rendering of MainContent, I may render AuxilaryContent or not. But in DOM, AuxilaryContent always lies before MainContent.
Is this possible?
If I am using bool in MainContent, then by using the bool to trigger SecondaryContent means, it requires another StateHasChanged(). It involves unwanted re-rendering of components.
#page "/check"
#AuxilaryContent
#MainContent
#code {
RenderFragment MainContent => (builder) =>
{
//It must be rendered first
};
RenderFragment AuxilaryContent => (builder) =>
{
//It should rendered after MainContent rendering. But in DOM, it always lies before MainContent
};
}
Suppose you have the 2 components, main and aux.
The second aux can be shown or not by the result of the main one ,thats what i understand.
First of all Blazor is a spa (single-page application) it means that everything is in fact in the same page i mean,suppose you start by the index of the page , it contains all the components of the blazor proyect.
The components inside can contain or not others.
For example, suppose a collection of books:
The index page will be more less like that:
#page "/"
<book1_component></book1_component>
<book2_component></book2_component>
<book3_component></book3_component>
Inside of each component you can put another component if u want.
<h1>book1</h1>
<label>title</label> <input type="text" />
<label>author</label> <input type="text" />
<other_component> </other_component>
#code{
//methods
}
The interesting thing about blazor is that you should work with it as you have State-Patron:
You can check here what its a state patron:
https://refactoring.guru/es/design-patterns/state
What i mean is if the state changes, then the behavior of blazor will change too.
Example: Here you can see that if the state of the index component change , there will be one or other components or even none.
#page "/"
<input type="int" max="4" min="1" #bind="state" />
#if( state == 1 )<book1_component></book1_component>
#if( state == 2 )<book2_component></book2_component>
#if( state == 3 )<book3_component></book3_component>
#if(i>4) <label> no books!</label>
#code{
int state;
}
So in your example you should do more less the same , for example, if a condition is true then you show, your component.
So if you want to refresh some component with statehaschange, you can do it in its own .razor page .
#page "/"
<InputCheckbox #bind="state" />
#if( state) <aux_component></aux_component>
else Not loading component.
#code{
bool state;
//operations to change the state
}
Other way ,can be : If you want to keep the logic on only one component, you can use the lifecycle methods:
https://learn.microsoft.com/es-es/aspnet/core/blazor/components/lifecycle?view=aspnetcore-5.0
So if you want to execute some code first put onInitiallize, then go to the next lifecycle method and execute second block by a condition, something like that.
#page "/check"
#AuxilaryContent
#MainContent
#code {
protected override void OnInitiallize(){
//executing the first
RenderFragment MainContent => (builder) =>
{
//It must be rendered first
};
}
protected override void OnParametersSet(){
//main component rendered
RenderFragment AuxilaryContent => (builder) =>
{
//It should rendered after MainContent rendering. But in DOM, it always lies before MainContent
};
}
}
Hope it helps.
I would consider restructuring this component into two nested components: main container controlling the conditional rendering of the aux one.
Related
I am using blazor webclient to display an enum value in the UI. however when I run the client and a value is assigned nothing displays. I have taken this code from the GetName method. however this displays nothing on the page.
my razor component
<div>
<p>Nationality: #Enum.GetName(typeof(Nationality), user.Nationality).ToString()</p>
<p>Favourite Medium: #Enum.GetName(typeof(Medium), user.FavouriteMedium).ToString()</p>
</div>
#code {
[Parameter]
public UserViewModel user {get; set;}
}
when the page is loaded in no error is given and the value is shown as blank. I followed this method from the documentation here.
https://learn.microsoft.com/en-us/dotnet/api/system.enum.getname?view=net-6.0
Edit.
The issue here was that there was a view model conversion point on my API that was not sending the enum value to the user model. because of this a 0 was being passed by the value where the enum values for these objects start at 1. therefore nothing was being displayed by the component where the #code block was inserted into the HTML.
thanks guys!
Not sure what your code is doing, but here's a dirty demo that shows you how to display an enum in a Blazor component.
#page "/"
<h1>Hello, your #nationality</h1>
<div>
<button class="btn btn-dark" #onclick=ChangeNationality>Change Nationality</button>
</div>
#code {
private Nationality nationality = Nationality.French;
private void ChangeNationality()
=> nationality++;
public enum Nationality
{
French,
English,
Welsh,
Portuguese,
Spanish
}
}
I found the issue to be that the enum was getting a value of 0 from the model object when the first value of enum was set to 1. therefore no value was being returned from the GetName() method.
I have an edit form, with multiple MudTabPanels inside.
Problem is, I have LOTS of properties for this class, and we've decided to split into multiple panels, that each contain an edit form with different forms/inputs.
Format is somewhat like this (pseudo-razor-code) :
<MudTabs>
<MudTabPanel Text="Section 1">
<EditForm>
<MudItem>
<EditField Property1>
<EditField Property2>
...
<EditField Property 10>
</EditForm>
</MudTabPanel>
<MudTabPanel Text="Section 2">
<EditForm>
<MudItem>
<EditField Propertyn11>
<EditField Propertyn12>
...
<EditField Property 20>
</EditForm>
</MudTabPanel>
..... lots of other panels here
<MudTabPanel Text="Section N">
<EditForm>
<MudItem>
<EditField Property98>
<EditField Property99>
...
<EditField Property100>
</EditForm>
</MudTabPanel>
</MudTabs>
Problem is :
I have +1000 lines of code just in this razor page!
VS 2022 Preview is struggling to give me a decent performance (on the UI seems to be working fine)but modifying just a property is a pain in the ass in VS.
I was thinking about moving each Panel into a separate component , and transmitting my entity as a Parameter.
But:
1).Right now, because I use all these into a single page razor, on the code page, let's say I have the method DoSomething(), I can use this method on each panel.
Will I need to repeat the DoSomething() on each component, if i'll split them ? Is there a way I can share that method?
2).Do you think this will impact the performance on the UI?
3).Is there any better way of doing this ?
LE: Updated my data binding example
Code behind:
private Article _article;
Example of some bindings in my first tab:
<MudNumericField T="int?" #bind-value="_article.ArticleID">
<MudSelect #bind-Value="_article.UnitPriceIntervals" OffsetY="true" Label="Unit Price Interval" Variant="Variant.Outlined" Margin="Margin.Dense" Dense="true">
#foreach (UnitPriceIntervals? item in (UnitPriceIntervals[])Enum.GetValues(typeof(UnitPriceIntervals)))
{
<MudSelectItem Value="item">#item</MudSelectItem>
}
</MudSelect>
Now, my article properties can also contain references to other data types that are stored in a different SQL table, with possibility to change them, based on a search.
Example :
_article.GeneralText1 = 1234
Why not use a
#foreach (FieldAttributes fsa in cAtribs)
{
<MudTabPanel Text=#fsa.key>
<EditForm>
<MudItem>
#for(int i=0;i<fsa.Value.Count();i++)
{
<EditField #fsa.Value.ElementAt(i) />
}
</MudItem>
</EditForm>
</MudTabPanel>
}
loop, where Dictionary<string, List<string>> cAtribs.
The key of the dictionary is "Section 1", ... "Section N" and the collection has the property names for each section.
you can build cAtribs ahead of time, or dynamically, or even using reflection.
This is more a comment than an answer, but there's not enough spacing to fit it into a comment.
Track which Tab you're in and only load the edit form for the specific tab. That will significantly reduce what needs to be rendered at one time. Something like:
#if (tabNo = 2)
{
// Edit Form 2
}
You don't show your data binding, but make sure you use a view data service to hold your model data.
Consider using a component for each edit form within a Tab?
There are many complications with multi-tab editors/wizards. How are you validating and when? Is you backend one model/data table?
If you want more detail, add a comment and I'll try and put together some demo code later today.
===== Update
First get your data out of your edit component and into a ViewService. Here's a wire framework for one.
using System.Threading.Tasks;
namespace StackOverflow.Answers
{
public class ArticleViewService
{
//set up your data access
// load as Scoped Service - one per user session
public Article Article { get; private set; }
public Task GetArticle()
{
// Your get article code here
return Task.CompletedTask;
}
public Task SaveArticle()
{
// Your save article code here
return Task.CompletedTask;
}
}
}
Next your section edit components. Your data comes from the inject view service and gets updated directly into the same service. No passing data between componnts.
<h3>Section1</h3>
<EditForm EditContext="_editContext">
<InputText #bind-Value="ViewService.Article.Name"></InputText>
// or your mud editor components
.....
</EditForm>
#code {
[Inject] private ArticleViewService ViewService { get; set; }
private EditContext _editContext;
protected override Task OnInitializedAsync()
{
_editContext = new EditContext(ViewService.Article);
return base.OnInitializedAsync();
}
}
Then you Article editor, with MudTabs. This should track the active tab and display only the correct section component. I haven't tested this but it "should" work (I don't use MudBlazor and don't have it installed.)
<MudTabs #bind-ActivePanelIndex="this.panelId">
<MudTabPanel Text="Item One" ID='"pn_one"'>
#if(this.PanelId = 1)
{
\\ Section 1 componwnt
}
</MudTabPanel>
<MudTabPanel Text="Item Two" ID='"pn_two"'>
#if (this.PanelId = 2)
{
\\ Section 2 componwnt
}
</MudTabPanel>
<MudTabPanel Text="Item Three" ID='"pn_three"'>
#if (this.PanelId = 2)
{
\\ Section 3 componwnt
}
</MudTabPanel>
</MudTabs>
#code {
private int PanelId {
get => _panelId;
set => {
if (value != _panelId )
{
_panelId = value;
StateHasChanged();
}
}
}
private int _panelId = 1;
}
I am running into an odd issue using Blazor Webassembly. I have a component that has 2 text boxes. For some reason, if I type anything into the first text box, then try to either tab to the second text box or click in the second text box, I have to do it twice. It's like the first tab or click doesn't register. After playing around with it, I was able to minimally duplicate the issue. The issue seems to happen when I have both a model binding on the control and I use a #ref attribute.
Here is my .cshtml file:
#inject IJSRuntime JsRuntime
#page "/"
<div>
<input class="form-control" id="first-text-box" type="text" #ref="TextBoxControl" #bind="_model.Box1" />
</div>
<div>
<input class="form-control" id="second-text-box" type="text" #bind="_model.Box2" />
</div>
#code {
private ElementReference TextBoxControl { get; set; }
private readonly BoxClass _model = new BoxClass();
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await JsRuntime.InvokeVoidAsync("jsFunctions.focusElement", TextBoxControl);
await base.OnAfterRenderAsync(firstRender);
}
}
Here is the focusElement javascript function:
window.jsFunctions = {
focusElement: function(element) {
element.focus();
}
};
Finally, here is a Fiddle of the issue to see it happen live: https://blazorfiddle.com/s/kph6msiv
Like I said, just type something into the first text box, then try to tab or click to the next text box and you'll see that you'll need to do it twice.
You are focusing every time it renders, which happens when Blazor data binds, which happens when you exit the control.
Instead, only call your JavsScript if firstRender is true
I am trying to import this Javascript function into my Blazor application. The function of the script is simple, add the class c-show into an existing list <li> element that already has two classes. The original javascript in its completion is:
Javascript
document.addEventListener('DOMContentLoaded', (event) => {
document.querySelectorAll('.c-sidebar-nav-item.c-sidebar-nav-dropdown').forEach(dropMenu => {
dropMenu.addEventListener('click', () => dropMenu.classList.toggle('c-show'));
});
})
This script affects this element:
<li class="c-sidebar-nav-item c-sidebar-nav-dropdown">
Adding the c-show making it:
<li class="c-sidebar-nav-item c-sidebar-nav-dropdown">
I am trying to achieve the same thing with Blazor/C# via Interop.
So I have added the following to my element:
<li class="c-sidebar-nav-item c-sidebar-nav-dropdown" #onclick="dropMenu">
And my code section being:
#code {
public async void dropMenu()
{
classList.toggle('c-show')
}
}
But I am not entirely sure on how get the result I am working for since all I know is that I need to adjust the javascript somehow but not sure on how.
You can use JSInterop call from the OnAfterRenderAsync method to initialize your JavaScript object, this can be done only once, and then you can call your JavaScript methods each time the component is rendered.
Note: You need to inject the JSRuntime object in order to execute JSInterop calls.
#page "/"
#inject IJSRuntime jsRuntime
<li id="myid" #ref=MyElementReference class="c-sidebar-nav-item c-sidebar-
nav-dropdown" #onclick="dropMenu">
#code{
// This add an element reference to the li element, which you can pass to
// your JavaScript functions
ElementReference MyElementReference;
// You have to call your JavaScript code after your components have been
// rendered. The OnAfterRenderAsync method is called after the component
// has been rendered, and thus you can put code here to initialize your
// component. This should be when firstRender is true, and multiple calls
// to your JavaScript objects, when firstRender is false.
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// Note: Here you initialize your elements, only once. When the user
// clicks on the li element, you'll call your required method from
// the click event handler dropMenu
if (firstRender)
{
await JSRuntime.InvokeAsync<object>("MyJSMethods.myMethod",
MyElementReference);
}
}
}
public async void dropMenu()
{
await JSRuntime.InvokeAsync<object>("MyJSMethods.myMethod",
MyElementReference);
}
Place your script at the bottom of the _Host.cshtml file, just below
Adjust your element accordingly. Note the code here only display the alert with the id given to the li tag. Instead, you'll have to add the code to change your objects as necessary.
<script src="_framework/blazor.server.js"></script>
<script>
window.MyJSMethods =
{
myMethod: function (element) {
window.alert(element.id);
}
};
</script>
Note that the parameter named element that the function takes is an element object because we defined the argument as ElementReference in Blazor. Of course, you could've passed the element id or class name, etc.
I am learning Blazor Component and I am wondering about StateHasChanged force the component to re-render all itself or only the differences. The intellisense report that
Notifies the component that its state has changed. When applicable, this will cause the component to be re-rendered.
What it means with "this will cause the component to be re-rendered"?
StateHasChanged will cause only the differences to be re-rendered.
To prove that, I've created the following Blazor component that has 2 buttons:
first button generates a list of 10 000 <li> elements, numbered 0 .. 9999
second button modifies the value of the first <li> and calls StateHasChanged()
Here is the complete code:
#page "/BigDataStateHasChanged"
<h3>BigDataStateHasChanged</h3>
<button class="btn btn-primary" #onclick="GenerateBigData">Generate big data</button>
<button class="btn btn-primary" #onclick="ChangeOneRow">Change 1 row</button>
#if(list != null)
{
#foreach(var item in list)
{
<li>#item</li>
}
}
#code {
List<int> list;
const int cMaxNumbers = 10000;
protected void GenerateBigData()
{
list = new List<int>(cMaxNumbers);
for(int i = 0; i < cMaxNumbers; i++)
{
list.Add(i);
}
}
protected void ChangeOneRow()
{
list[0] = 123456;
StateHasChanged();
}
}
I then used the Chrome's development tools to monitor the websockets. On the Network tab, when clicking on the first button, I can see that 1.4 MB was transferred via the websockets to the client:
Then, after clicking the second button that only changes the first element, I can see that only 153 bytes have been transferred:
So, from this, the conclusion is that only the "diff" gets rendered.