SillyJumper
SillyJumper

Reputation: 341

How can I randomly add CSS attributes to Blazor component from parent layer as Vue did?

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

Answers (6)

Nick Udell
Nick Udell

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

lonix
lonix

Reputation: 20489

Assumptions:

  • you want to add classes in the parent
  • you want to add classes in the child
  • you want all classes to be combined in the final markup
  • you want to use 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

Vivek Nuna
Vivek Nuna

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

Scott
Scott

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

Ed Charbeneau
Ed Charbeneau

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

enet
enet

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

Related Questions