Reputation: 341
Since I want to design some reusable Blazor components, I hope they could have a feature like this:
Suppose I have a custom component "MyComponent", I could add any CSS attribute to it when I am using it:
<MyComponent Class="custom-css1 custom-css2">
some child content...
</MyComponent>
While in MyComponent, I usually fix some common CSS attributes to the top wapper just like this:
<div class="fixed-css1 fixed-css2">
some child content...
</div>
That means I have to combine two parts of the CSS attributes together to make the final HTML like:
<div class="fixed-css1 fixed-css2 custom-css1 custom-css2">
some child content...
</div>
So I guess I should have this patern:
<div class="@Classes">
some child content...
</div>
@functions
{
[Parameter]
private string Class { get; set; } = "";
private string fixedClass = "fixed-css1 fixed-css2";
private string Classes
{
get
{
return $"{fixedClass} {Class}";
}
}
}
To reduce redundant code, I could make a base class that have a protected Class property and every component inherent from it, but I still can't avoid writing the same combine code in each one. I hope there is some solution to add those custom CSS directly in my base class, I guess I could achieve this by override BuildRenderTree method from ComponentBase clss:
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);
}
But unfortunately, I have tried all my ways to build manually but have no idea to get it done. I don't know how to get elements of my HTML ("div" for example) and add additional CSS attributes to it.
All these are about doing a feature as Vue could easily do. In Vue code, we could certainly add any attribute to a component and pass them down to the first element in the component.
Could anybody help me complete this goal or give me some suggestion?
Upvotes: 15
Views: 15995
Reputation: 2491
Your pattern is good, but can be simplified a little.
<div class="fixed-css1 fixed-css2 @Class">
some child content...
</div>
@code
{
[Parameter]
public string Class { get; set; } = "";
}
@Class
will print the content of the variable, i.e. the classes attached to your component, and it can do so at the end of an existing string.
Upvotes: 0
Reputation: 20489
Assumptions:
class
... not Class
, Classes
, Css
, CssClass
or whatever!ChildComponent.razor
:
<div @attributes=_additionalAttributes></div>
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IReadOnlyDictionary<string, object> AdditionalAttributes { get; set; }
= new Dictionary<string, object>();
private IReadOnlyDictionary<string, object>? _additionalAttributes;
protected override void OnParametersSet()
{
base.OnParametersSet();
var parentClasses = AdditionalAttributes.GetValueOrDefault("class", "");
var classes = $"foo {parentClasses}".Trim();
_additionalAttributes =
AdditionalAttributes.Where(x => x.Key != "class")
.Append(KeyValuePair.Create("class", (object)classes))
.ToDictionary(x => x.Key, x => x.Value);
}
}
ParentComponent.razor
is now clean, with the normal name class
:
<ChildComponent class="bar baz" />
@code {
}
Which renders:
<div class="foo bar baz"></div>
Upvotes: 2
Reputation: 1
You can do this in two ways.
The first way, you can use the dictionary to pass from parent to child like below. I have added the required
attribute in this example.
Child Component:
<input id="firstName" @attributes="InputAttributes" />
@code {
[Parameter]
public Dictionary<string, object> InputAttributes { get; set; } =
new Dictionary<string, object>()
{
{ "placeholder", "Child Component Placeholder" }
};
}
Parent Component:
<ChildComponent InputAttributes="attributesFromParent">
</ChildComponent>
@code {
public Dictionary<string, object> attributesFromParent { get; set; } =
new Dictionary<string, object>()
{
{ "required", "required" },
{ "placeholder", "Parent Component Placeholder" }
};
}
The second way, you can do by setting CaptureUnmatchedValues
to true
. and directly passing the attribute like maxlength
from parent to child.
Child Component:
<input id="firstName" @attributes="InputAttributes" />
@code {
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> InputAttributes { get; set; } =
new Dictionary<string, object>()
{
{ "placeholder", "Child Component Placeholder" }
};
}
Parent Component:
<ChildComponent InputAttributes="attributesFromParent" maxlength="15">
</ChildComponent>
@code {
public Dictionary<string, object> attributesFromParent { get; set; } =
new Dictionary<string, object>()
{
{ "required", "required" },
{ "placeholder", "Parent Component Placeholder" }
};
}
Reference: https://youtu.be/gClG243kn1o and https://www.pragimtech.com/blog/blazor/blazor-arbitrary-attributes/
Upvotes: 2
Reputation: 236
try this, i have used it and it works and is simple
<div class="fixed-css1 fixed-css2 @(Class)">
some child content...
</div>
I'm not sure if this is a bad practice,
It stops itellisense from working correctly in the class attribute,
but that is easily workable just add it last and at the end of the attribute
if you need to make changes with intellisense add a "" before the @(class), make your changes and then remove the ""
it may leave a space in the class string if the Class parameter is not set on the component
eg <div class="fixed-css1 fixed-css2 ">
(space at end)
Upvotes: 3
Reputation: 4624
I think your approach is fine, it just needs a little abstraction around it to make it readable and easily managed across multiple components.
That's why I created this simple helper function library. It's exactly what you are doing in your code, but offers an API for consistency.
https://www.nuget.org/packages/BlazorComponentUtilities/
Upvotes: 9
Reputation: 45586
As far as I know, right now, Blazor does not offer a built-in way to handle CSS, and Blazor best practices and patterns are not yet available, so you can handle CSS in whatever manner you found fit, including JSInterop.
Below is a link to a library I believe can be very useful to you: https://github.com/chanan/BlazorStyled
Hope this helps...
Upvotes: 3