Basically I am trying to create a mix of a Templated Component and a layout. I want a page layout that I can reuse, and still pass required parameters to. I think a way to do this may be to take advantage of an abstract base class.
This could be solved by my third option if I wanted to do it all through render tree building, but I do not want to do that. I'm okay with the base being built by a render tree (i.e. Option 3), but not the component that inherits from it (i.e. MyGrid.razor
)
What I want to do:
MyGrid.razor
@page "/mygrid"
@inherits GridPage @*or "layout" or whatever works*@
<Grid>
<Column>
<Column>
</Grid>
@code {
protected overrides string Title => "My Grid"
}
GridPage.cs
<header>@Title</header>
<div>@ChildContent</div>
@code {
[Parameter] public string Title { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
}
Solutions I Have Tried
- Layout
GridLayout
<header>@Title</header>
<div>@Body</div>
@code {
[CascadingParameter]
[Parameter] public string Title { get; set; }
}
Issue is that you have to remember this param is available and it is not required to impliment
- Template
<GridPageTemplate Title="My Title">
<Grid>
<Column>
<Column>
</Grid>
</GridPageTemplate>
Issue here is that I would have to wrap my component with the template (not that big of deal, but would rather inherit) and I still don't know which params are required to impliment.
- Abstract Base Class
public abstract class GridPage : ComponentBase
{
protected abstract string Title { get; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenRegion(0);
builder.OpenElement(1, "header");
builder.AddContent(2, Title);
builder.CloseElement();
builder.CloseRegion();
builder.OpenElement(3, "div")
builder.OpenRegion(10);
this.BuildRenderTree(builder);
builder.CloseRegion();
builder.Closelement()
}
}
Issue is this one doesn't seem to work. I actually thought it would cause it would just take in the component and continue the base render (this.BuildRenderTree(builder);
).
Desired Outcome
Say you have two grids.. a Students
grid and Classes
grid. I would want a different grid (and title) for each of them. I want to customize the title and columns for each basically. They would have a common layout markup (what I call GridPage
), but MyGrid
(either Students
or Classes
) would be different.
Students.razor
@page "/students"
@inherits GridPage
<Grid RowType=Student>
<Column For="StudentName">
<Column For="StudentAge">
<Column For="StudentHomeRoom">
</Grid>
@code {
protected overrides string Title => "Students"
}
Classes.razor
@page "/classes"
@inherits GridPage
<Grid RowType=Class>
<Column For="ClassName">
<Column For="ClassTime">
</Grid>
@code {
protected overrides string Title => "Classes"
}
They would both output
<header>Title_here</header>
<div>Grid_markup_here</div>
CodePudding user response:
Create the abstract base class with common properties required for logic and then extend it where you actually have to render.
AbstractBaseClass.razor.cs
public class AbstractBaseClass<T> : ComponentBase
{
[Parameter]
public string Title {get;set;}
[Parameter]
public T TypedObject {get;set;}
[Parameter]
public RenderFragment<T> TypedComponent {get;set;}
[Parameter]
public RenderFragment CommonUi {get;set;}
}
In one implementation:
IntType.razor
@inherits AbstractBaseClass<int>
@CommonUi
// or any other markup logic if any
@TypedComponent(TypedObject)
StringType.razor
@inherits AbstractBaseClass<string>
@CommonUi
// or any other markup logic if any
@TypedComponent(TypedObject)
Then use it with the hard implementation
<StringType TypedObject="StackOverflow" >
<CommonUi> /* Component common to both*/ </CommonUi>
</StringType>
<IntType TypedObject="2" >
<CommonUi> /* Component common to both*/ </CommonUi>
</IntType>
CodePudding user response:
Blazor offers a composition model. Your pages will still have to be routable components. Why not go with the flow:
MyGrid.razor
<GridPage Title="Title">
<Grid>
<Column>
<Column>
</Grid>
</GridPage>
@code {
string Title => "My Grid"
}
You already have the GridPage template:
<header>@Title</header>
<div>@ChildContent</div>
@code {
[Parameter] public string Title { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
}
Your attempts to turn this inside-out will at least require a lot of effort.
CodePudding user response:
Component inheritance works for inheriting functionality, but not for content. As soon as you define some razor content BuildRenderTree
gets overridden.
You need to think Components.
GridPage.razor
is a component and looks like this:
<header>@Title</header>
<div>@ChildContent</div>
@code {
[Parameter] public string Title { get; set; } = "Silly sod, you didn't define a header";
[Parameter] public RenderFragment ChildContent { get; set; }
}
and then MyGrid.Razor
is a RouteView.
@page "/MyGrid"
<GridPage Title="My Grid Page">
<Column>
<Column>
... more markup
</GridPage>
CodePudding user response:
Not sure what you want...Anyhow here's my input:
Warning:
- "As soon as you define some razor content BuildRenderTree gets overridden" ATTRIBUTION: MrC aka Shaun Curtis
- Avoid using unnecessary layers of components. It's very expensive and make your application slower.
I'm of the opinion that your design should be as follows:
A Grid
component A Column
component And perhaps GridLayout
, which is really superfluous :
Here's how I would use the Data Grid on the Index page: Index.razor
<Grid>
<Column />
<Column />
</Grid>
You could, of course, do something like this:
GridLayout.razor
<Grid>
<Column />
<Column />
</Grid>
And in the Index page instantiate the GridLayout component like this:
Index.razor
<GridLayout />