Reputation: 87
I need help with dynamically building/generating multistep component with rendertreebuilder.
I have a two components: MultistepComponent (parent) and MultiStepNavigation (child). I also have a component builder class to build these components.
Rendering components does not work, I get an error:
Object reference not set to an instance of an object.
That is because the MultiStepComponent property in MultiStepNavigation is empty. Using a component in razor page does not give this error.
I will be posting a lot of code, but you don't have to read every detail. I just want to give all the information.
MultiStepComponent.razor
<CascadingValue Value="this">
<div id="@Id">
<ul class="nav nav-pills nav-justified">
@foreach (var step in Steps)
{
<li id="step-@(StepsIndex(step) + 1)" class="nav-item">
<a class="nav-link @((ActiveStep == step) ? "active" : "")" href="javascript: void(0)"
@onclick="@(e=> SetActive(step))">@step.Name</a>
</li>
}
</ul>
<div id="container-fluid">
<div class="navigatingBtns">
<button class="btn btn-primary btn-lg" type="button"
disabled="@(ActiveStepIndex == 0)" @onclick="GoBack">
Previous
</button>
<button class="btn btn-primary btn-lg"
type="@(IsLastStep ? "submit" : "button")" @onclick="GoNext">
@(IsLastStep ? "Submit" : "Next")
</button>
</div>
@ChildContent
</div>
</div>
</CascadingValue>
MultiStepComponent.razor.cs
public partial class MultiStepComponent {
protected internal List<MultiStepNavigation> Steps = new List<MultiStepNavigation>();
[Parameter]
public string Id { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public MultiStepNavigation? ActiveStep { get; set; }
[Parameter]
public int ActiveStepIndex { get; set; }
public bool IsLastStep { get; set; }
protected internal void GoBack()
{
if (ActiveStepIndex > 0)
{
SetActive(Steps[ActiveStepIndex - 1]);
}
}
protected internal void GoNext()
{
if (ActiveStepIndex < Steps.Count - 1)
{
SetActive(Steps[(Steps.IndexOf(ActiveStep) + 1)]);
}
}
protected internal void SetActive(MultiStepNavigation step)
{
ActiveStepIndex = StepsIndex(step);
if (ActiveStepIndex == Steps.Count - 1)
{
IsLastStep = true;
}
else
{
IsLastStep = false;
}
}
public int StepsIndex(MultiStepNavigation step) => StepsIndexInternal(step);
protected int StepsIndexInternal(MultiStepNavigation step)
{
return Steps.IndexOf(step);
}
protected internal void AddStep(MultiStepNavigation step)
{
Steps.Add(step);
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
SetActive(Steps[0]);
StateHasChanged();
}
}
}
MultiStepNavigation.razor
@if (MultiStepComponent.ActiveStep == this) {
<div id="step-@(MultiStepComponent.StepsIndex(this) + 1)">
@ChildContent
</div>
}
MultiStepNavigation.razor.cs
public partial class MultiStepNavigation
{
[CascadingParameter]
protected internal MultiStepComponent MultiStepComponent { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; } = default!;
[Parameter]
public string Name { get; set; } = "";
protected override void OnInitialized()
{
if(MultiStepComponent != null)
{
MultiStepComponent.AddStep(this);
}
}
}
code to build componnts:
renderFragment = b =>
{
b.OpenComponent<MultiStepComponent>(0);
b.AddAttribute(1, "id", "MultiStepContainer");
b.OpenComponent<MultiStepNavigation>(2);
b.AddAttribute(3, "Name", "First Step");
b.CloseComponent();
b.CloseComponent();
};
Making a component on razor page myself does not give an error. It looks like this:
<MultiStepComponent Id="MultiStepContainer">
<MultiStepNavigation Name="First Step"></MultiStepNavigation>
</MultiStepComponent>
What did I do wrong??
Upvotes: 0
Views: 1699
Reputation: 11
You have to create a new builder if you want to add attributes on the child component :
renderFragment = b1 =>
{
b1.OpenComponent<MultiStepComponent>(0);
b1.AddAttribute(1, "id", "MultiStepContainer");
b1.AddAttribute(2, "ChildContent", (RenderFragment)((b2 =>
{
b2.OpenComponent<MultiStepNavigation>(3);
b2.AddAttribute(4, "Name", "First Step");
b2.CloseComponent();
}));
b1.CloseComponent();
};
Notice that a component can only have one childContent, so if the component have 2 childs components you have to code a builder2 and builder3 inside the same ChildContent,
Upvotes: 1
Reputation: 30046
You code doesn't take account of the fact that MultiStepNavigation
is child content of MultiStepComponent
.
Look in /obj/debug/net5.0/razor/..., find your razor version of your component and look at the code.
You probably need to do something like this:
private RenderFragment _MultiStepNavigationFragment => b =>
{
b.OpenComponent<MultiStepNavigation>(0);
b.AddAttribute(1, "Name", "First Step");
b.AddAttribute(2, "ChildContent", this.ChildContent);
b.CloseComponent();
};
private RenderFragment _MultiStepComponentFragment => b =>
{
b.OpenComponent<MultiStepComponent>(0);
b.AddAttribute(1, "id", "MultiStepContainer");
b.AddAttribute(2, "ChildContent", this._MultiStepNavigationFragment);
b.CloseComponent();
};
Upvotes: 4