-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #54 from AngelMunoz/doc-updates
Documentation updatates
- Loading branch information
Showing
5 changed files
with
819 additions
and
136 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
<div id="my-id" class="my-class">Hello, World!</div> | ||
``` | ||
|
||
> 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 | ||
[<CustomOperation("stack")>] | ||
member inline _.stack([<InlineIfLambda>] comb: CombineKeyValue) = | ||
comb | ||
&&& css { | ||
height "100%" | ||
displayFlex | ||
flexDirectionColumn | ||
alignItemsStretch | ||
overflowHidden | ||
} | ||
[<CustomOperation("strench")>] | ||
member inline _.strench([<InlineIfLambda>] 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<MudPaper>() | ||
.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 | ||
[<CustomOperation("HeaderAndRow")>] | ||
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)))) | ||
[<CustomOperation("withDefaultSettings")>] | ||
member inline _.withDefaultSettings([<InlineIfLambda>] 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") | ||
} | ||
``` | ||
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<unit>` | ||
|
||
```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<string> e.Value |> printfn "New Value: '%s'" | ||
) | ||
} | ||
input { | ||
type' "number" | ||
placeholder "Change Number" | ||
oninput(fun e -> | ||
unbox<string> 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 |
Oops, something went wrong.