-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Alternative fluent syntax #54
Comments
If the Html.div [
Mui.appBar()
.position.absolute
.children([
Mui.typography()
.variant.h6
.color.primary
.text("Foo")
])
] But I still think that duplicating the base properties would the easiest to do. I don't think you need to duplicate every single property because a lot of the times you won't use them, just duplicate the ones that are most likely to be used: |
Continuing discussion of inheritance in Shmew/Feliz.MaterialUI#20 since this issue is only about the alternative syntax, which is a consideration in its own right separate from my struggles with inheritance (sorry if that wasn't clear). |
I think the fluent syntax has another drawback, for example if i want to create an extensible control, i can use yield! to append new props. Or I can create union cases for limited props and do reduce to get the base props and yield it at anywhere I want. But with fluent syntax i can only append props in the end. let myButton props =
Html.button [
prop.Classes [ ... ]
prop.Style [....]
yield! props
] |
@albertwoo I'm not enturely sure what you mean by
But there's no problem adding support for arbitrary props using the fluent syntax: let myButton props =
Html.button
.classes([...])
.style([...])
.custom(props) where
|
In any case, for extensible components with the fluent syntax, you probably wouldn't pass props like that. You'd do something like this: let myButton () =
Html.button
.classes([...])
.style([...]) And then use it like this: myButton()
.text("foo") |
I kind of like the props list style. In my code I did something like: type ISimpleFieldProp = interface end
[<RequireQualifiedAccess>]
type SimpleFieldProp =
| Label of string
| Errors of string list
| OuterClasses of string list
| LabelClasses of string list
| ErrorClasses of string list
| FieldView of ReactElement
| OuterAttrs of IHTMLProp list
interface ISimpleFieldProp
let simpleField (props: ISimpleFieldProp list) =
let props = props |> List.choose (function :? SimpleFieldProp as x -> Some x | _ -> None)
let label = props |> UnionProps.tryLast (function SimpleFieldProp.Label x -> Some x | _ -> None)
let errorClasses = props |> UnionProps.tryLast (function SimpleFieldProp.ErrorClasses x -> Some x | _ -> None) |> Option.defaultValue []
let outerClasses = props |> UnionProps.concat (function SimpleFieldProp.OuterClasses x -> Some x | _ -> None)
let labelClasses = props |> UnionProps.concat (function SimpleFieldProp.LabelClasses x -> Some x | _ -> None)
div </> [
yield! props |> UnionProps.concat (function SimpleFieldProp.OuterAttrs x -> Some x | _ -> None)
match outerClasses with
| [] -> Style [ Margin "5px"; Padding "5px" ]
| cs -> Classes cs
Children [
if label.IsSome then fieldLabel labelClasses label.Value
match props > UnionProps.tryLast (function SimpleFieldProp.FieldView x -> Some x | _ -> None) with
| Some x -> x
| None -> ()
yield!
props
|> UnionProps.concat (function SimpleFieldProp.Errors x -> Some x | _ -> None)
|> fieldError errorClasses
]
] With let simpleField1 props =
simpleField [
SimpleFieldProp.OuterClasses [ ... ]
]
let simpleField2 props =
simpleField1 [
SimpleFieldProp.OuterClasses [ ... ]
] and all the OuterClasses can merge together. Of course if merge together is not I want I can just use UnionProps.tryLast.
|
Thank you for the detailed example. I must admit that it has me confused. For example, you don't seem to be using Feliz at all, but normal Fable.React. So I'm having a hard time understanding how it's relevant to this discussion. Also, merging prop values for the same props from several "inherited"/composed classes seems to me like an unnecessarily messy way to do extensibility given that in React/JSX based syntax (AFAIK) a component can have only one occurrence of any given prop. It seems like what you are trying to do, is to defining a custom component that supports a handful of optional, custom props, some of which are props and some of which are child elements. In Feliz (as it is now, without any fluent syntax), just off the top of my head, I might solve it like this (partial implementation; hopefully the rest of the props should be clear): open Feliz
type Html with
member simpleField(
?label: string,
?errors: string list,
?labelClasses: string list,
?outerClasses: string list
?props: IReactProperty list) =
// Unsure if this is correct, since I don't know what UnionProps.concat actually
// does, but I'm sure you get the gist
let lbClasses = labelClasses |> Option.defaultValue []
Html.div [
yield! props |> Option.defaultValue []
outerClasses |> Option.map prop.classes |> Option.defaultValue (prop.style [ ... ])
prop.children [
match label with Some lb -> fieldLabel lbClasses lb | None -> ()
...
]
] This also allows you to easily make any of the props required, if you desire. As regards the fluent syntax under discussion, it would work the same way. The usage of the example above would be: Html.simpleField(
label = "foo",
outerClasses = ["bar"]
) Note also that creating custom React components (e.g. function components with Fable.React's |
Yes, I am not using Feliz (I tried but there is no SSR support yet ). But I just want to explain my thoughts about prop list style. type Html with
member simpleField2(
?label: string,
?errors: string list,
?labelClasses: string list,
?outerClasses: string list
?props: IReactProperty list) =
simpleField(label, errors, labelClasses, [ "bar"; yield! outerClasses ], props) Appreciate you guys works!!! |
:) Possible workaround: type SimpleFieldProps = {
Label: string option
Errors: String list
// ...
} with
static member Create(?label: string, ?errors: string list, .. ) = { .. }
type Html with
member simpleField (props: SimpleFieldProps) = // ...
member simpleField2 (props: SimpleFieldProps) = // ... Usage: Html.simpleField (SimpleFieldProps.create(..)) |
:) Thanks for the advise. This way can help but if I have a lot of props like in Fabulous Xamarin, it would be too much work. That is also why I also use Fabulous.SimpleElements (Thanks @Zaid-Ajaj again :)) |
Regarding the However, a drawback then is that since lists can only contain one type, and there's no automatic upcasting inside a list, then we need an explicit upcast (e.g. to A possible workaround to the above is to not have lists of child elements, but use There also needs to be an API for conditional props. In the current Feliz (and Fable.React) DSL we can selectively We could have a wrapping .prop1("foo")
.conditional(false, fun p -> p.prop2("bar"))
.prop3("baz") But it's not the prettiest API I have seen. (Not too bad, but the current conditional |
If I was writing the DSL in C#, this is the syntax I would have wanted to write for sure but since we have F# and all of it's nice list syntax, I very much rather use the current one instead of this |
Gotcha. The only thing I really like about this syntax is that it's the most discoverable I have seen so far. Otherwise it seems to have too many drawbacks compared to the current one. |
How do you feel about a syntax like this? (I drafted this quickly; there may be lots of room for improvement - for example, there may be a way to eliminate the
.reactElement
at the end of each component when more careful thought is given to this.)Benefits: Supports inheritance, and puts a final end to any discoverability issues still left in Feliz: No more hunting for the correct prop type; you're just dotting through and seeing what's available. Also supports overloaded props and enum props just like Feliz; we have basically just replaced property lists with "fluent builders".
Drawbacks: I don't know which impacts this has for bundle size or performance (see quick and dirty implementation below). Also it's "unusual" as far as Fable goes (no prop lists). Not that the latter matters by itself. Update: See more challenges here: #54 (comment)
I'm not saying we should necessarily do this; I just wanted to get this out of my head and post it for discussion.
For the record, below is the implementation of the above syntax. I have not made any attempts to inline or erase stuff. And there's a bit of type trickery which would likely cause OO fanatics to spin in their graves (what with subtypes inheriting base types parametrized by the subtype and all).
The text was updated successfully, but these errors were encountered: