diff --git a/Docs/11 DOM/README.md b/Docs/11 DOM/README.md index 0b06d5fe..1e54a9ac 100644 --- a/Docs/11 DOM/README.md +++ b/Docs/11 DOM/README.md @@ -1,160 +1,169 @@ # DOM -The most important thing for **Fun.Blazor** is the DSL to build the DOM. -Even before V2, computation expression style DSL is already supported. +Fun.Blazor provides a friendly way to write HTML for web applications. It uses [F# Computation Expressions] to generate a simple yet performant DSL. -It is very easy to build and compose the DOM: +```fsharp +let hello name = + div { + id "my-id" + class' "my-class" + $"Hello, {name}" + } + +hello "World!" +``` + +Calling that function would produce a markup like + +```html +
Hello, World!
+``` + +> Some html attributes are reserved keywords in F#, and that's why `'` is added at the end of those names, in this case `class'` instead of `class` + +> Note: Attributes must be placed before any other elements, like strings or other nodes + +## Control Flow + +Since you're using F# for your markup code, you have all of the F# arsenal at your disposal that includes `match`, `if`, `function` and even lists of elements but to avoid having problems with mismatch between content in the element rendering you should use `fragment` or `region` these are containerless builders that isolate blazor's node count which can be useful for performance as well as keeping the content concistent. + +### If/Else ```fsharp -let fragment1 = div { "F=ma" } - -let composed = - div { - style { height 500 } - p { "This is the way!" } - // because below stuff will display something according some conditions, - // so we need use region to isolate it so it will not impact it's parent element/component's sequence numbers for better diff performance. - region { - if conditionIsTrue then - fragment1 - p { "E = MCC" } - } +let element isVisible = + div { + $"The element is: " + region { + if isVisible then + "Visible" + else + "Not Visible" } + } ``` -Also, you can build a shared attributes/style fragment to compose: +### Match ```fsharp -let commonStyle = - css { - cursorPointer - color "green" - } -let commonAttr = - domAttr { - data 123 +let element kind = + div { + $"The element is: " + region { + match kind with + | Fantastic -> "Fantastic" + | Average -> "Average" + | WellItsSomething -> "Wel... it is something" } + } +``` + +### Lists -let sharedButtonAttrs = - button { - style { - commonStyle - // css priority/override is controlled by browser. - // For "color", "red" will be used. - color "red" - } - data 456 - // attribute priority is controlled by blazor core. - // Currently, only the first added attribute will be used when you are trying to add the same attribute. - // That is why I put commonAttr lower than "data 456", - // so the 456 will be used even in commonAttr "data" is 123. - commonAttr - asAttrRenderFragment // Here is the thing to make the magic happen +To render lists you can use `for item in items do` + +```fsharp +ul { + h3 { "Some title." } + for item in 0..10 do + li { + key item + $"Item: {item}" } -let demo = - div { - p { "Below we will have a cool button" } - button { - onclick ignore - sharedButtonAttrs - "Cool" - } - } -``` +} -You can also create an extension operation method to create something to reuse. And with this way, it is type safer because it attached to a specific type. It is better to use it for global sharing stuff. As for **asAttrRenderFragment**, it is recommended to use it only locally because what it generates is just **AttrRenderFragment** which can be combined anywhere. +``` -For CSS, you can do: +Or also if you have an existing list of nodes you can use the `childContent` operation. ```fsharp -type StyleBuilder with - - [] - member inline _.stack([] comb: CombineKeyValue) = - comb - &&& css { - height "100%" - displayFlex - flexDirectionColumn - alignItemsStretch - overflowHidden - } - - [] - member inline _.strench([] comb: CombineKeyValue) = - comb - &&& css { - flex 1 - height "100%" - width "100%" - positionRelative - overflowXHidden - overflowYAuto - } - - -let demo = - div { - style { stack; backgroundColor "blue" } - div { "Header" } - div { - style { strench; backgroundColor "green" } - } - } +ul { + childContent listOfNodes +} ``` -For DOM element/component, you can do: +> Note: Please note that `key` is very useful to preserve list order between re-renders otherwise you might have unexpected changes in the view when you add/remove items from a list. -Without any bindings you can: +## Attributes + +Fun.Blazor provides out of the box most if not all of the existing HTML attributes in the spec however if you need to set a custom attribute in an element then you can provide the builder with a string tuple. ```fsharp -let demo = - html.blazor (ComponentAttrBuilder() - .Add((fun x -> x.Elevation), 10) - .Add((fun x -> x.Outlined), true) - ) +section { + "my-attribute", "value" +} ``` -With auto generated bindings: +### Shared attributes + +If you'd like to share attributes between different elements you can use the `domAttr` ```fsharp -open Fun.Blazor -open Fun.Blazor.Operators -open MudBlazor - -type MudTable'<'T> with - - [] - member this.HeaderAndRow(render: AttrRenderFragment, mappers: (NodeRenderFragment * ('T -> NodeRenderFragment)) seq) = - let headers = mappers |> Seq.map fst - let render = this.HeaderContent(render, html.fragment headers) - this.RowTemplate(render, (fun row -> html.inject (row, (fun () -> mappers |> Seq.map (snd >> fun fn -> fn row) |> html.fragment)))) - - [] - member inline _.withDefaultSettings([] render: AttrRenderFragment) = - render - ==> MudTable'() { - Hover true - FixedHeader true - HorizontalScrollbar true - Breakpoint Breakpoint.None - asAttrRenderFragment // with this feature we can have a better coding experience - } - -let demo = - MudTable'() { - Height "100%" - Items items - HeaderAndRow [ - MudTh'() { "Name" }, - fun item -> MudTd'() { item.Name } - - MudTh'() { "Age" }, - fun item -> MudTd'() { item.Age } - ] - withDefaultSettings +module SharedAttrs = + let classAndData = + domAttr { + class' "has-data" + data("my-data", "123") } -``` \ No newline at end of file + +let someNode() = + div { + SharedAttrs.classAndData + "Some Node" + } + +let otherNode() = + div { + SharedAttrs.classAndData + "Other Node" + } + +``` + +## Events + +Events conform to the standard HTML event names, so you will find them in any element as usual. +handlers can be async or sync depending on your usage but they're often defined as `EventArgs -> unit` or `EventArgs -> Task` + +```fsharp +button { + onclick(fun e -> printfn "clicked") + "Click Me" +} + +button { + onclick(fun e -> task { + do! Async.Sleep 1000 + printfn "clicked" + }) + "Click Me Task" +} +``` + +For inputs remember that events provide values as strings, so you have to unbox them + +```fsharp +input { + placeholder "Write Something" + oninput(fun e -> + unbox e.Value |> printfn "New Value: '%s'" + ) +} + +input { + type' "number" + placeholder "Change Number" + oninput(fun e -> + unbox e.Value |> int |> printfn "New Value: '%i'" + ) +} +``` + +> Note: If you're realing with forms you should check out [Adaptive Forms] instead. They can work with more structured objects like records and provide validation abilities. + +[F# Computation Expressions]: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions +[Adaptive Data]: https://github.com/fsprojects/FSharp.Data.Adaptive +[Working With Blazor]: ./Advanced-features/Working-With-Blazor +[Adaptive Forms]: ./Advanced-features/Adaptive/Form diff --git a/Docs/12 Style/README.md b/Docs/12 Style/README.md index 1e59ee9a..d1ad9eab 100644 --- a/Docs/12 Style/README.md +++ b/Docs/12 Style/README.md @@ -1,15 +1,196 @@ -# Style +# Styling -This was created by [Fun.Css](https://github.com/slaveOftime/Fun.Css). It can be used to build CSS rules or inline styles. +Adding styles is common in every web application, Fun.Blazor provides out of the box support for inline css and also there's the [Fun.Css] package that provides support for style tags (using classes and rulesets). -Since it uses computation expression, you can customize its operations as well: +## Inline Styles + +Adding inline styles to elements is very straight forward + +```fsharp +div { + style { + color "red" + } +} +``` + +Would produce + +```html +
+``` + +If you like to pursue inline styles you can also share those styles, for that we have to add a reference to [Fun.Css] in our project and then we can start sharing styles: + +```fsharp +module SharedStyles = + let ClickableGreen = + css { + cursorPointer + color "green" + } + +let styledDiv = + div { + style { + SharedStyles.ClickableGreen + fontSize 16 + } + } +``` + +> Note: The normal styling rules still apply, if we supply a `color "red"` attribute in our button, the browser will pick up that instead of our shared style. Keep in mind however that it also means you will have duplicated css properties in inline styles. + +## Style Sheets + +To create style tags you can use the `ruleset` builder, this will produce a css rule that can be applied to any element within the scope of the style tag (e.g. when it loads) + +```fsharp +module Shared = + let Page = + ruleset ".page" { + margin 0 + padding "1em" + displayFlex + flexDirectionColumn + height "100vh" + } + + let Title = + ruleset ".title" { fontSize "calc(10px + 3vmin)" } + + let Text = + ruleset ".text" { fontSize "calc(10px + 1vmin)" } + +``` + +With those rules in mind we can then add a "home page" + +```fsharp +let HomePage = + article { + class' "page home" + h1 { class' "title"; "This is the title" } + + section { + p { class' "text"; "This is some text" } + p { "This is another text" } + } + + styleElt { + ruleset ".home" { + fontSize "medium" + boxShadow "5px 5px 0.5em rgba(0, 0, 0, 0.5)" + } + Shared.Page + Shared.Title + Shared.Text + } + } +``` + +That would produce something like + +```html +
+

This is the title

+
+

This is some text

+

This is another text

+
+ +
+``` + +> Note: Keep in mind that these styles are loaded in style tags along their declaring elements, so if you have styles already loaded you could have rules clashing and overriding depending on the [rule's specificity]. + +## Sharing Inline Styles Globally + +There are some cases where you'd like yo have certain styles available for any element you can also create an extension operation method to create something to reuse. + +We'll add a couple of "stacks" to our styles using flexbox, for this we have to extend the style builder in Fun.Blazor. + +```fsharp +[] +module StyleExtensions = + open Fun.Blazor.Internal + open Fun.Css.Internal + + type StyleBuilder with + + [] + member inline _.VStack([] comb: CombineKeyValue) = + comb + &&& css { + displayFlex + flexDirectionColumn + } + + [] + member inline _.HStack([] comb: CombineKeyValue) = comb &&& css { displayFlex } + + [] + member inline this.EvenHStack([] comb: CombineKeyValue) = + this.HStack comb &&& css { custom "justify-content" "space-evenly" } + +``` + +> Note: `&&&` is a custom operator provided by Fun.Css so we don't have to manually combine those styles ourselves. + +Once we have defined our extension members on the style builder, we can use those in the following way: + +```fsharp +let MyItems = + ul { + // display: flex; flex-direction: column; + style { VStack } + + // content + } +let MyItems2 = + ul { + // display: flex; + style { HStack } + + // content + } +let MyItems3 = + ul { + // display: flex; justify-content: space-evenly; + style { EvenHStack } + // content + } +``` + +### Other Examples: {{InlineStyleDemo}} -Additionally, you can create CSS rules with it: +Using CSS rules: {{CssRuleDemo}} -Even simple keyframes can be built: +Using keyframes: + +{{KeyFramesDemo}} -{{KeyFramesDemo}} \ No newline at end of file +[Fun.Css]: https://github.com/slaveOftime/Fun.Css +[rule's Specificity]: https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity diff --git a/Docs/13 Interactive Nodes/README.md b/Docs/13 Interactive Nodes/README.md new file mode 100644 index 00000000..443d0a92 --- /dev/null +++ b/Docs/13 Interactive Nodes/README.md @@ -0,0 +1,201 @@ +# Interactive Nodes + +> Note: Blazor by default renders the content as static so if you're rendering from the server you won't have any dynamic behavior, for that you need to set server interactivity for the component or use client side blazor (WASM). +> If your project was created with the `fun-wasm` template then you don't have to do anything else for this to work. +> For server interactivity and more information please check the [Working With Blazor] section. + +While much of a website content is static, there are times where you have to match the content based on a value you had previously. + +## Adaptive Data + +Fun.Blazor's way to deal with this is by using [Adaptive Data] which is included out of the box. Adaptive values behave a lot like excel cells, where a change propagates through the rest of the connected cells. + +This concept has also been used within the javascript ecosystem in recent times often named `signals`. + +The `adaptiview` builder will keep track of adaptive values and re-render the component when a change has been detected, this is provides an efficient rendering mechanism while also providing an immutable way to handle state changes in a view. + +```fsharp +module DynamicViews = + open FSharp.Data.Adaptive + open Fun.Blazor + + let adaptiveNode = + adaptiview () { + let! age, setAge = cval(10).WithSetter() + + section { + $"Age: {age}" + br + + input { + type' "number" + value age + onchange (fun event -> unbox event.Value |> int |> setAge) + } + } + } +``` + +## Stores and Observables + +> Note: This requires the [Fun.Blazor.Reactive] package + +### Observables + +> Note: The examples here use the [FSharp.Control.Reactive] package due the functions it provides to help, but it is not required, any `IObservable` works with Fun.Blazor + +If you have observable information around it is quite simple to hook it up with Fun.Blazor by using the `html.watch` API + +```fsharp +module ReactiveViews = + open System + open Fun.Blazor + open FSharp.Control.Reactive + + let obs = Observable.interval (TimeSpan.FromSeconds(1.)) + + article { + + h1 { "My View" } + p { + html.watch(obs, (fun num -> fragment { $"Number: {num}" }), 0) + } + } +``` + +### Stores + +Another popular way to handle state changes in the frontend ecosystem is the usage of stores which are containers that keep data around for you. they are very similar to observables but they provide a simpler API to work with. Stores are also useful to bind dynamic attributes + +```fsharp +module ReactiveViews = + open Fun.Blazor + + let store: IStore = new Store(initialAge) + + let view() = + let storeNode = + html.watch(store, (fun num -> fragment { $"Store: {num}" })) + + article { + p { storeNode } + } +``` + +Both Observables and Stores can be converted into adaptive values to have a uniform data interface to work with + +```fsharp + +module AdaptiveViews = + open System + open Fun.Blazor + open FSharp.Control.Reactive + open FSharp.Data.Adaptive + + let observe = Observable.interval (TimeSpan.FromSeconds(1.)) + let store: IStore = new Store(0) + + let adaptiveObs = AVal.ofObservable 0L ignore observe + let adaptiveStore = AVal.ofObservable 0 ignore store.Observable + + let view() = + fragment { + adaptiview () { + let! fromObs = adaptiveObs + let! fromStore = adaptiveStore + fragment { $"Observable: {fromObs} - Store: {fromStore}" } + } + + button { + onclick (fun _ -> store.Publish(store.Current + 10)) + + "Update Store" + } + } +``` + +## Rendering + +Dynamic views re-render when one of the dependencies change and that means the whole node is rendered so you have to keep in mind that whenver you use `html.watch`, or `adaptiview` any of its dependencies may cause a re-render avoid having large trees of information in those places and stay static when possible for performance. + +### Adaptive + +In the case of adaptive values it is easy to track where something will re-render as it is usually surrounded by `adaptiview` anything inside of `adaptiview` will re-render but the outside world will stay static + +```fsharp +// ❌ Avoid +adaptiview() { + let! content = myAValue + + $"Hello static string" // will re-render + div { "Hello static node" } // will re-render + + p { // will re-render + div { // will re-render + $"Hello dynamic content {content}"// will re-render + } + } +} +``` + +```fsharp +// ✅ Prefer +fragment { + $"Hello static string" // Stays static + div { "Hello static node" } // Stays static + + p { // Stays static + div { // Stays static + adaptiview() { + let! content = myAValue + fragment { $"Hello dynamic content {content}" } // will re-render + } + } + } +} +``` + +### Stores and Observables + +In a similar fashion to adaptive values, whenever you see a `html.watch` call it means that the whole node will re-render when the observable/store value changes. + +```fsharp +// ❌ Avoid +html.watch( + obs, + (fun value -> fragment { + $"Hello static string" // will re-render + div { "Hello static node" } // will re-render + + p { // will re-render + div { // will re-render + $"Hello dynamic content {value}"// will re-render + } + } + }), +0) +``` + +```fsharp +// ✅ Prefer +fragment { + $"Hello static string" // will stay static + div { "Hello static node" } // will stay static + + p { // will stay static + div { // will stay static + "Hello dynamic content " // will stay static + html.watch( + obs, + (fun value -> fragment { $"{value}" }), //will re-render + 0 + ) + } + } +} +``` + +[Adaptive Data]: https://github.com/fsprojects/FSharp.Data.Adaptive +[Working With Blazor]: ./Advanced-features/Working-With-Blazor +[Fun.Blazor.Reactive]: https://github.com/slaveOftime/Fun.Blazor +[FSharp.Control.Reactive]: http://fsprojects.github.io/FSharp.Control.Reactive/index.html diff --git a/Docs/14 Classes and Functions/README.md b/Docs/14 Classes and Functions/README.md new file mode 100644 index 00000000..0dcc645c --- /dev/null +++ b/Docs/14 Classes and Functions/README.md @@ -0,0 +1,160 @@ +# Classes vs Functions + +Fun.Blazor is very flexible and allows you to choose between classes and functions as you need. + +For the most part you can use functions as much as you'd like but there are situations wher classes may come in handy (like writing blazor components for other teams). + +## Functions + +Functions are executed once and they are not re-executed unless the parent re-renders + +```fsharp +let staticGreeting name = + p { $"Hello there, {name}!" } +``` + +This function given the same parameter will always output the same p tag with the same content and it will never change again, the same goes for functions that enclose mutable values, for example the following function will not re-render + +```fsharp +let staticGreeting()= + let mutable counter = 0 + + button { + onclick (fun _ -> counter <- counter + 1) + $"Click Me: {counter}" + } +``` + +This is because the change detection mechanics happen at the component level, that means class inheriting ComponentBase which in the case of Fun.Blazor is `FunComponent`. +However If change the declaration from a function (`let staticGreeting () =`) to a let bound value (`let staticGreeting =`) , and use it in a context which its parent component has re-render triggers (like event handlers in `FunComponent`s, see the below section on classes.), the count will increase and updated. + +However if you want to work with dynamic data in your functions, you can use `Adaptive` values, `Stores`, or `Observables` which you can read further in [Interactive Nodes]. A brief example with adaptive values would be: + +```fsharp + +let staticChild name (currentName: string cval) = + button { + // trigger currentName changes + onclick(fun _ -> currentname.Publish name) + $"Click {name}!" + } + +let staticParent () = + let currentName = cval("Nobody") + fragment { + // doesn't re-render + h1 { $"Initial Name: {currentName.Value}" } + + // render on currentName changes + adaptiview() { + let! currentName = currentName + h1 { $"Hello, {currentName}!" } + } + + staticChild "Peter" currentName + staticChild "Frank" currentName + } +``` + +This reduces the need to have classes for most of the codebase, you can combine dynamic data with Fun.Blazor's DSL to go as functional as you need. + +### Benefits and drawbacks + +The benefits are: + +- Simpler to test +- Easier to reason about +- Composable + +They are often the preferred way to go for most F# users. + +The downsides are: + +- Don't have access to aspnet's default DI container +- Don't play well raw blazor +- They need to be wrapped in components in order for C# consumers to use + +In short the closer you want to get to Blazor, the least functions work nicely with it. + +However, most of the time a function is the simple yet correct choice. + +## Classes + +Blazor by default re-renders in certain conditions like after an event handler is executed, changing parameters/cascading parameters and when calling `this.StateHasChanged()` so it plays well with mutable values even in F#. + +A simple counter example would be the following: + +```fsharp +type Counter() = + inherit FunComponent() + + let mutable counter = 0 + [] + member val Logger: ILogger = Unchecked.defaultof<_> + + override this.Render() = + fragment { + h1 { $"Count: {counter}" } + + button { + onclick (fun _ -> + let newCount = counter + 1 + this.Logger.LogDebug("Old Value: {count} - New Value: {newCount}", counter, newCount) + counter <- newCount + ) + "Increment" + } + } +``` + +In this case we didn't add any dynamic values like Adaptive data, Stores or Observables and our counter is re-rendered and works as expected, blazor applies a diffing algorithm to know that it only needs to re-render the `h1` tag. you can also however use dynamic data if required. + +### Using adaptive data + +If you would like to avoid re-rendering your components at event handlers, you can opt out of it by using `FunBlazorComponent` this base class turns off re-renders for event triggers. + +```fsharp +type Demo() = + // Note this is not the same as *inherit FunComponent()* + inherit FunBlazorComponent() + + let data = cval 0 + + override _.Render() = fragment { + h1 { "Static text" } + adaptiview() { + let! data = data + // only this part will need to be used for computate for diff when data is changed + p { $"x = {data}" } + } + button { + onclick (fun _ -> data.Publish(fun value -> value + 1)) + "Increase" + } + } +``` + +This has the added benefit that updates are more localized and you don't accidentally re-render on event handlers, most of the time it should not be an issue, but if you require to have more control of it, then this is the ideal way to do it. + +### Benefits and drawbacks + +In general classes are good to use within Fun.Blazor and are a safe choice if your project needs them. + +The benefits are: + +- Offer full decorator support `Parameter`, `Cascading Parameter`, `Route`, `Inject` +- Has direct access to the DI container in aspnet +- Consumption is transparent for C# consumers +- Plays well with the blazor framework under the hood. + +The drawbacks are: + +- They require manual bindings to play well with the Fun.Blazor DSL +- Verbose + +In general, classes can be used to orchestrate dependency injection, to use plain blazor features seamlessly, and to keep as close as blazor as possible. + +Functions most of the time can overcome the benefits of classes when you use `html.comp` functions (also called `html.inject`) which you can read more about in [Hooks] and [Dependency Injection](<./Advanced-features/Dependency-injection-(DI)>) + +[Interactive Nodes]: ./Interactive-Nodes +[Hooks]: ./Advanced-features/Hook diff --git a/Docs/40 Advanced/44 Working With Blazor/README.md b/Docs/40 Advanced/44 Working With Blazor/README.md new file mode 100644 index 00000000..76465adf --- /dev/null +++ b/Docs/40 Advanced/44 Working With Blazor/README.md @@ -0,0 +1,132 @@ +# Working with Blazor + +Using Blazor features with almost "raw" blazor elements is possible in Fun.Blazor + +Most of the ways to render Blazor components in Fun.Blazor is with the `html.blazor` API it provides of several overloads you can use to render elements as required. + +### Add Attributes + +To add attributes to a blazor component you can use the `domAttr` builder + +```fsharp +html.blazor ( + domAttr { + class' "my-component" + data ("some-dalue", initialValue) + } +) +``` + +### Add Members + +When you want to bind to particular members in a component you can do so by using the `ComponentAttrBuilder()` builder, this provides a strongly typed access to the component and the properties it has exposed to bind for example: + +```fsharp +html.blazor ( + ComponentAttrBuilder() + .Add((fun x -> x.Value), "Some value") + .Add((fun x -> x.OnValueChange), EventCallback(null, Action (fun v -> printfn $"{v}"))) +) +``` + +> Note: Element attributes are not the same as members, element attributes are a concept tied to HTML (e.g. class styles or inline styles), members are part of the object itself this distinction also applies to javascript in the attributes vs properties situation. + +### Server/Wasm/Auto Rendering + +In .NET8 a few rendering modes were added into blazor you can also use these to ensure where do you need your blazor elements to render + +```fsharp +html.blazor(RenderMode.InteractiveServer) + +html.blazor(RenderMode.InteractiveAuto) + +html.blazor(RenderMode.InteractiveWebAssembly) +``` + +For more information about what are the effects please visit the Microsoft documentation for [Blazor Rendering Modes]. + +## Blazor Components + +If you're working with a place where there are C# and F# teams you may want to work with plain Blazor components. + +Thankfully these are very simple to define as they are just a class like the following: + +```fsharp +type MyComponent() = + inherit FunComponent() // This is required + + [] + member val Logger: ILogger = Unchecked.defaultof<_> with get, set + + [] + member val Value = "" with get, set + + [] + member val OnValueChangeCb: EventCallback = Unchecked.defaultof<_> with get, set + + override this.Render() = + div { + $"Hello {this.Value}!" + + textarea { + type' "text" + value this.Value + + oninput + (fun e -> + let value = unbox e.Value + this.Logger.LogDebug("Value Changed: {OldValue} -> {NewValue}", this.Value, value) + this.OnValueChangeCb.InvokeAsync value + ) + } + } +``` + +You can define `Parameters`, `CascadingParameters`, `EventCallbacks`, also use attributes like `Inject`, `Route`, `StreamRendering`, and most if not all of what you'd expect in your blazor components in the C# counterpart. + +You can use the Fun.Blazor DSL to work with your markup and that includes dynamic content with adaptive data. + +## Blazor Bindings + +Sometimes you'd like to use third party libraries in Fun.Blazor you can write manual bindings or use the [Fun.Blazor.Cli] tool to generate them for you. + +For the manual bindings we can use `ComponentAttrBuilder()` like the following example: + +```fsharp +// This is an existing component it can be defined in C# or F# +type MyComponent with + + static member create(value, onValueChanged) = + html.blazor ( + ComponentAttrBuilder() + .Add((fun x -> x.Value), value) + .Add((fun x -> x.OnValueChangeCb), EventCallback(null, Action onValueChanged)) + ) +``` + +With our `create` extension we can now use it in a seamless way with the rest of our Fun.Blazor markup + +```fsharp +module Home = + open FSharp.Data.Adaptive + open Fun.Blazor + + let view() = + article { + h1 { "This is a title" } + + adaptiview() { + let! value, setValue = cval("").WithSetter() + + MyComponent.create(value, setValue) + + $"the value is: {value.ToUpperInvariant()}" + } + } +``` + +This can be a tedious process however [Fun.Blazor.Cli] provides an automated tool to generate these, please visit the [Code Generation] section for more information on how to generate new bindings. + +[Fun.Blazor.Cli]: ./Tooling/Code-Generation +[Code Generation]: ./Tooling/Code-Generation +[Blazor Rendering Modes]: https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-8.0