Giter Club home page Giter Club logo

componentbuilder's Introduction

ComponentBuilder

The best automation framework for RCL(Razor Component Library) to help you easy and quickly building your own Razor Component Library

中文介绍 | Quick Start | Document

Latest Version

v4.x supports .net6 .net7

v5.x supports .net8

✨ Features

  • OOP mindset creating component
  • Attribute first, easy define CSS from parameters
  • Easy to associate with components via Attributes
  • Cusomization CSS and attributes of component by coding logic
  • Support Pre-definition for components with simular parameters
  • New lifecycle definition of Component with interceptor design pattern
  • Renderer pipeline pattern to regonize dynamic render of components

🌈 Quick Start

Only change ComponentBase to BlazorComponetBase for derived component class

  • Sample to create a button component with C# class:
[HtmlTag("button")] //define HTML element tag
[CssClass("btn")] //define component necessary CSS class
public class Button : BlazorComponentBase, IHasChildContent, IHasOnClick
{
	[Parameter][CssClass("active")]public bool Active { get; set; } 	
	[Parameter][CssClass("btn-")]public Color? Color { get; set; } 
	[Parameter]public RenderFragment? ChildContent { get; set; }
	[Parameter][HtmlData("tooltip")]public string? Tooltip { get; set; }
	[Parameter][HtmlAttribute("onclick")]public EventCallback<ClickEventArgs> OnClick { get; set; }
	[Parameter][HtmlAttribute]public string? Title { get; set; }
}

public enum Color
{
	Primary,
	Secondary,
	[CssClass("info")]Information,
}

OR you also can define most part of automation features in razor file:

@inherits BlazorComponentBase

<!--Bind GetAttributes() for @attributes to getting automation features-->

<button @attributes="@GetAttributes()"> 
	@ChildContent
</button>

@code{
	[CssClass("btn")]
	public Button()
	{
	}

	[Parameter][CssClass("active")]public bool Active { get; set; } 	
	[Parameter][CssClass("btn-")]public Color? Color { get; set; } 
	[Parameter]public RenderFragment? ChildContent { get; set; } 
	[Parameter][HtmlData("tooltip")]public string? Tooltip { get; set; }
	[Parameter][HtmlAttribute("onclick")]public EventCallback<ClickEventArgs> OnClick { get; set; } 
	[Parameter][HtmlAttribute]public string? Title { get; set; }
	
	public enum Color
	{
		Primary,
		Secondary,
		[CssClass("info")]Information,
	}
}
  • Use component
<!--razor-->
<Button Color="Color.Primary">Submit</Button>
<!--html-->
<button class="btn btn-primary">Submit</button>

<!--razor-->
<Button Active Tooltip="active button" Color="Color.Information" Title="click me">Active Button</Button>
<!--html-->
<button class="btn btn-info active" data-tooltip="active button" title="click me">Active Button</button>

ℹ️ Logical CSS/Style/Attributes

  • Using different logic for groups creates code with OOP mindset
protected override void BuildCssClass(ICssClassBuilder builder)
{
	if(builder.Contains("annotation-enter"))
	{
		builder.Remove("annotation-exist");
	}
	else
	{
		builder.Append("annotation-enter").Append("annotation-exist");
	}
}

protected override void BuildStyle(IStyleBuilder builder)
{
	if(Height.HasValue)
	{
		builder.Append($"height:{Height}px");
	}
}

protected override void BuildAttributes(IDictionary<string, object> attributes)
{	
	if(attrbutes.ContainKey("data-toggle"))
	{
		attributes["data-toggle"] = "collapse";
	}
}

🚸 Parent/Child component

Easy to create parent/child pair components using ParentComponentAttribute and ChildComponentAttribute<TParent>

  • For List component class
[ParentComponent] //auto creating the cascading parameter for current 
[HtmlTag("ul")]
public class List : BlazorComponentBase, IHasChildContent
{

}
  • For ListItem component class
[ChildComponent<List>] //Strong association with List
[HtmlTag("li")]
public class ListItem : BlazorComponentBase, IHasChildContent
{
	[CascadingParameter]public List? CascadedList { get; set; }//Auto getting the instance of cascading parameter

	[Parameter] public RenderFragment? ChildContent { get; set; }
}

Use in blazor

<List>
	<ListItem>...</ListItem>
</List>

<ListItem /> <!--throw exception because ListItem must be the child component of List coponent-->

In .razor file

In razor file component, you should create cascading parameter by yourself

List.razor:

<ul @attributes="@GetAttributes()">
	<CascadingValue Value="this">
		@ChildContent
	</CascadingValue>
</ul>

ListItem.razor:

<li @attributes="GetAttributes()">@ChildContent</li>

@code{
	[ChildComponent<List>]
	public ListItem()
	{
	}

	[CascadingParameter] public List? CascadedList { get; set; }

	[Parameter] public RenderFragment? ChildContent { get; set; }
}

😄 Other extensions

  • Extensions for RenderTreeBuilder

It's very useful for dynamic component creating using OOP mindset

builder.CreateElement(0, "div","any text", new { @class="main" });		
//<div class="main">any text</div>

builder.CreateComponent<MyComponent>(attributes: new { Visible = true }); 
//<MyComponent Visible />

builder.CreateCascadingValue<T>(value); 
//<CascadingValue Value="this"></CascadingValue>
  • FluentRenderTreeBuilder

Write RenderTreeBuilder as fluent API

//import namespace
using ComponentBuilder.FluentRenderTree;

builder.Element("p", "default-class")		// create <p> element with default class
		.Class("hover", Hoverable)			// append class if Hoverable parameter is true
		.Attribute("disabled", Disabled)	// add HTML attribute if Disabled is true
		.Data("trigger", "string")			// add data-trigger="string" HTML attribute if String parameter not empty
		.Callback<MouseEventArgs>("onmouseover", this, e => MyHandler())	// add event named 'onmouseover' with a event handler code
		.Content("content text")			// add inner text for this element
	.Close()

//HTML element generate like:
<p class="default-class hover" data-trigger="string" disabled>content text</p>

// normally in razor file:
<p class="default-class @(Hoverable?"hover":"")" disabled="@Disabled" data-trigger="string" @onmouseover="@(e => MyHandler())">content text</p>


builder.Component<MyComponent>()
		.Parameter(m => m.Disabled, true)
		.Parameter(m => Size, 5)
		.ChildContent("My name is hello world")
	.Close();
  • Create dynamic Class/Style/Callback
//import namespace
using ComponentBuilder.JSInterop

//create dynamic css class string
HtmlHelper.Class.Append("class1").Append("disabled", Disabled).ToString();

//create dynamic style string
HtmlHelper.Style.Append($"width:{Width}px").Append($"height:{Height}px", Height.HasValue).ToString();

//create dynamic EventCallback
HtmlHelper.Callback.Create(this, ()=>{ //action for callback });
  • ComponentBuilder.JSInterop

Interactive with C# and JS

export function sayHello(){
	//...
}

export function getClient(){
	//..
	return name;
}
@inject IJSRuntime JS

var module = JS.ImportAsync("./module.js");	//Import js module

await module.Module.InvokeVoidAsync("sayHello");
var name = await module.Module.InvokeAsync<string>("getClient");

⚔️ Interceptors

You can intercept the lifecycle of component

  • Define an interceptor
public class LogInterceptor : ComponentInterceptorBase
{
	private readonly ILogger<LogInterceptor> _logger;
	public LogInterceptor(ILogger<LogInterceptor> logger)
	{
		_logger = logger;
	}

	//Run in SetParameterAsync method is called
	public override void InterceptSetParameters(IBlazorComponent component, ParameterView parameters)
	{
		foreach(var item in parameters)
		{
			_logger.LogDebug($"Key:{item.Name}, Value:{item.Value}");
		}
	}
}
  • Register interceptor
builder.Services.AddComponentBuilder(configure => {
	configure.Interceptors.Add<LogInterceptor>();
})

BlazorComponentBase Lifecycle

Why interceptors?

Follow SOLID pricipal when designing a component. So you no need break the lifecycle or using override any protected method such as OnParameterSet to create new HTML attribute whatever you want.

♻️ Renderer Pipeline

Recognize special case to render specified component

public class NavLinkComponentRender : IComponentRender
{
	public bool Render(IBlazorComponent component, RenderTreeBuilder builder)
	{
		if ( component is IHasNavLink navLink )
		{
			builder.OpenComponent<NavLink>(0);
			builder.AddAttribute(1, nameof(NavLink.Match), navLink.Match);
			builder.AddAttribute(2, nameof(NavLink.ActiveClass), navLink.ActiveCssClass);
			builder.AddAttribute(3, nameof(NavLink.ChildContent), navLink.ChildContent);
			builder.AddMultipleAttributes(4, component.GetAttributes());
			builder.CloseComponent();
			return false;
		}
		return true;
	}
}
  • Register renderer in configuration
builder.Services.AddComponentBuilder(configure => {
	configure.Renderers.Add<NavLinkComponentRenderer>();
});

📘 Installation Guide

  • Install from Nuget.org
Install-Package ComponentBuilder
  • Register service
builder.Services.AddComponentBuilder();

//configure costomization such as Interceptors
builder.Services.AddComponentBuilder(configure => {
	//...
})

Read document for more informations

📝 Component Library Solution Template

Use ComponentBuilder.Templates to generate a razor component library solution and online demo site

dotnet new install ComponentBuilder.Templates
dotnet new blazor-sln -n {YourRazorLibraryName}

More information see templates

componentbuilder's People

Contributors

teacher-zhou avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

componentbuilder's Issues

任意参数有一个扩展方法 GetCssClass 可以获取 CssClassAttribute 的值

有时候某个参数需要在自定义的代码中使用到 css 的名称,比如 CreateElement 时,因此可以这样

[Parameter][CssClass("my-class")]public bool HasName { get; set; }
//...
builder.CreateElement(0,"div", attributes: new { @class = HasName.GetCssClass() });

问题

如果定义了 CssClassAttribute 的参数,会被应用到组件中,作为 class 的一部分,但有可能这个 class 是组件不需要的

解决方案

定义 [CssClass(Disabled = true)] 禁用到组件的 class 里,但 变量.GetCssClass() 依然可以直接使用

示例代码:

[Parameter][CssClass("my-class", Disabled = true)]public bool HasName { get; set; }
//...
builder.CreateElement(0,"div", attributes: new { @class = HasName.GetCssClass() });

[Idea]Abstraction component for service component

Support abstraction component to create service component, so that we just can change provider of component library with same service component

Example of component using in your application

<Button>My Button</Button>
//Use component provider
builder.Services.AddBootstrap5();

Definition of component

public class BootstrapButton : Button
{
   protected override void BuildRenderTree(RenderTreeBuilder builder)
   {
      //...
   }
}

or

[ServiceComponent(typeof(Button))]
public class BootstrapButton : BlazorComponentBase
{
   protected override void BuildRenderTree(RenderTreeBuilder builder)
   {
      //...
   }
}

[Error]Cannot use AdditionaAttributes for @attributes in element

Build failed with this code in razor file

@inherits BlazorComponentBase
<div class="@(GetCssClassString())" @attributes="AdditionalAttributes">
//...
</div>

Error as below:

Argument 1: cannot convert from 'object' to 'System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object>>'

支持 DynamicParent 父组件

如果使用 ParentComponentAttribute 指定名称是静态的,如果出现嵌套父子组件,就无法分辨上一层的父组件

希望可以随机命名 ParentComponent 的名称,即 CasecadingValueName

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.