Skip to content

Commit fc50ff9

Browse files
authored
Merge pull request #54 from AngelMunoz/doc-updates
Documentation updatates
2 parents f54fcc3 + 4633abc commit fc50ff9

File tree

5 files changed

+819
-136
lines changed

5 files changed

+819
-136
lines changed

Docs/11 DOM/README.md

Lines changed: 139 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,160 +1,169 @@
11
# DOM
22

3-
The most important thing for **Fun.Blazor** is the DSL to build the DOM.
4-
Even before V2, computation expression style DSL is already supported.
3+
Fun.Blazor provides a friendly way to write HTML for web applications. It uses [F# Computation Expressions] to generate a simple yet performant DSL.
54

6-
It is very easy to build and compose the DOM:
5+
```fsharp
6+
let hello name =
7+
div {
8+
id "my-id"
9+
class' "my-class"
10+
$"Hello, {name}"
11+
}
12+
13+
hello "World!"
14+
```
15+
16+
Calling that function would produce a markup like
17+
18+
```html
19+
<div id="my-id" class="my-class">Hello, World!</div>
20+
```
21+
22+
> 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`
23+
24+
> Note: Attributes must be placed before any other elements, like strings or other nodes
25+
26+
## Control Flow
27+
28+
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.
29+
30+
### If/Else
731

832
```fsharp
933
10-
let fragment1 = div { "F=ma" }
11-
12-
let composed =
13-
div {
14-
style { height 500 }
15-
p { "This is the way!" }
16-
// because below stuff will display something according some conditions,
17-
// 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.
18-
region {
19-
if conditionIsTrue then
20-
fragment1
21-
p { "E = MCC" }
22-
}
34+
let element isVisible =
35+
div {
36+
$"The element is: "
37+
region {
38+
if isVisible then
39+
"Visible"
40+
else
41+
"Not Visible"
2342
}
43+
}
2444
```
2545

26-
Also, you can build a shared attributes/style fragment to compose:
46+
### Match
2747

2848
```fsharp
29-
let commonStyle =
30-
css {
31-
cursorPointer
32-
color "green"
33-
}
3449
35-
let commonAttr =
36-
domAttr {
37-
data 123
50+
let element kind =
51+
div {
52+
$"The element is: "
53+
region {
54+
match kind with
55+
| Fantastic -> "Fantastic"
56+
| Average -> "Average"
57+
| WellItsSomething -> "Wel... it is something"
3858
}
59+
}
60+
```
61+
62+
### Lists
3963

40-
let sharedButtonAttrs =
41-
button {
42-
style {
43-
commonStyle
44-
// css priority/override is controlled by browser.
45-
// For "color", "red" will be used.
46-
color "red"
47-
}
48-
data 456
49-
// attribute priority is controlled by blazor core.
50-
// Currently, only the first added attribute will be used when you are trying to add the same attribute.
51-
// That is why I put commonAttr lower than "data 456",
52-
// so the 456 will be used even in commonAttr "data" is 123.
53-
commonAttr
54-
asAttrRenderFragment // Here is the thing to make the magic happen
64+
To render lists you can use `for item in items do`
65+
66+
```fsharp
67+
ul {
68+
h3 { "Some title." }
69+
for item in 0..10 do
70+
li {
71+
key item
72+
$"Item: {item}"
5573
}
5674
57-
let demo =
58-
div {
59-
p { "Below we will have a cool button" }
60-
button {
61-
onclick ignore
62-
sharedButtonAttrs
63-
"Cool"
64-
}
65-
}
66-
```
75+
}
6776
68-
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.
77+
```
6978

70-
For CSS, you can do:
79+
Or also if you have an existing list of nodes you can use the `childContent` operation.
7180

7281
```fsharp
73-
type StyleBuilder with
74-
75-
[<CustomOperation("stack")>]
76-
member inline _.stack([<InlineIfLambda>] comb: CombineKeyValue) =
77-
comb
78-
&&& css {
79-
height "100%"
80-
displayFlex
81-
flexDirectionColumn
82-
alignItemsStretch
83-
overflowHidden
84-
}
85-
86-
[<CustomOperation("strench")>]
87-
member inline _.strench([<InlineIfLambda>] comb: CombineKeyValue) =
88-
comb
89-
&&& css {
90-
flex 1
91-
height "100%"
92-
width "100%"
93-
positionRelative
94-
overflowXHidden
95-
overflowYAuto
96-
}
97-
98-
99-
let demo =
100-
div {
101-
style { stack; backgroundColor "blue" }
102-
div { "Header" }
103-
div {
104-
style { strench; backgroundColor "green" }
105-
}
106-
}
82+
ul {
83+
childContent listOfNodes
84+
}
10785
```
10886

109-
For DOM element/component, you can do:
87+
> 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.
11088
111-
Without any bindings you can:
89+
## Attributes
90+
91+
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.
11292

11393
```fsharp
114-
let demo =
115-
html.blazor (ComponentAttrBuilder<MudPaper>()
116-
.Add((fun x -> x.Elevation), 10)
117-
.Add((fun x -> x.Outlined), true)
118-
)
94+
section {
95+
"my-attribute", "value"
96+
}
11997
```
12098

121-
With auto generated bindings:
99+
### Shared attributes
100+
101+
If you'd like to share attributes between different elements you can use the `domAttr`
122102

123103
```fsharp
124-
open Fun.Blazor
125-
open Fun.Blazor.Operators
126-
open MudBlazor
127-
128-
type MudTable'<'T> with
129-
130-
[<CustomOperation("HeaderAndRow")>]
131-
member this.HeaderAndRow(render: AttrRenderFragment, mappers: (NodeRenderFragment * ('T -> NodeRenderFragment)) seq) =
132-
let headers = mappers |> Seq.map fst
133-
let render = this.HeaderContent(render, html.fragment headers)
134-
this.RowTemplate(render, (fun row -> html.inject (row, (fun () -> mappers |> Seq.map (snd >> fun fn -> fn row) |> html.fragment))))
135-
136-
[<CustomOperation("withDefaultSettings")>]
137-
member inline _.withDefaultSettings([<InlineIfLambda>] render: AttrRenderFragment) =
138-
render
139-
==> MudTable'() {
140-
Hover true
141-
FixedHeader true
142-
HorizontalScrollbar true
143-
Breakpoint Breakpoint.None
144-
asAttrRenderFragment // with this feature we can have a better coding experience
145-
}
146-
147-
let demo =
148-
MudTable'() {
149-
Height "100%"
150-
Items items
151-
HeaderAndRow [
152-
MudTh'() { "Name" },
153-
fun item -> MudTd'() { item.Name }
154-
155-
MudTh'() { "Age" },
156-
fun item -> MudTd'() { item.Age }
157-
]
158-
withDefaultSettings
104+
module SharedAttrs =
105+
let classAndData =
106+
domAttr {
107+
class' "has-data"
108+
data("my-data", "123")
159109
}
160-
```
110+
111+
let someNode() =
112+
div {
113+
SharedAttrs.classAndData
114+
"Some Node"
115+
}
116+
117+
let otherNode() =
118+
div {
119+
SharedAttrs.classAndData
120+
"Other Node"
121+
}
122+
123+
```
124+
125+
## Events
126+
127+
Events conform to the standard HTML event names, so you will find them in any element as usual.
128+
handlers can be async or sync depending on your usage but they're often defined as `EventArgs -> unit` or `EventArgs -> Task<unit>`
129+
130+
```fsharp
131+
button {
132+
onclick(fun e -> printfn "clicked")
133+
"Click Me"
134+
}
135+
136+
button {
137+
onclick(fun e -> task {
138+
do! Async.Sleep 1000
139+
printfn "clicked"
140+
})
141+
"Click Me Task"
142+
}
143+
```
144+
145+
For inputs remember that events provide values as strings, so you have to unbox them
146+
147+
```fsharp
148+
input {
149+
placeholder "Write Something"
150+
oninput(fun e ->
151+
unbox<string> e.Value |> printfn "New Value: '%s'"
152+
)
153+
}
154+
155+
input {
156+
type' "number"
157+
placeholder "Change Number"
158+
oninput(fun e ->
159+
unbox<string> e.Value |> int |> printfn "New Value: '%i'"
160+
)
161+
}
162+
```
163+
164+
> 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.
165+
166+
[F# Computation Expressions]: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions
167+
[Adaptive Data]: https://github.com/fsprojects/FSharp.Data.Adaptive
168+
[Working With Blazor]: ./Advanced-features/Working-With-Blazor
169+
[Adaptive Forms]: ./Advanced-features/Adaptive/Form

0 commit comments

Comments
 (0)