Skip to content

Commit

Permalink
Support ofFunction
Browse files Browse the repository at this point in the history
  • Loading branch information
zaaack committed Mar 1, 2018
1 parent ba2f35c commit 88f1b03
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 99 deletions.
34 changes: 32 additions & 2 deletions Samples/SSRSample/src/Client/View.fs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,30 @@ type MyReactComp(initProps: MyProp) as self =
do self.setInitState { text="my state" }

override x.render() =
div [] [ str (sprintf "prop: %s state: %s" x.props.text x.state.text) ]
div []
[ span [] [ str (sprintf "prop: %s state: %s" x.props.text x.state.text) ]
span [] [ ofArray x.children ] ]



type [<Pojo>] FnCompProps = {
text: string
}

let fnComp (props: FnCompProps) =
div []
[ span [] [ str (sprintf "prop: %s" props.text) ] ]

type [<Pojo>] FnCompWithChildrenProps = {
children: React.ReactElement array
text: string
}

let fnCompWithChildren (props: FnCompWithChildrenProps) =
div []
[ span [] [ str (sprintf "prop: %s" props.text) ]
span [] [ ofArray props.children ] ]

let view (model: Model) (dispatch) =
div []
[ h1 [] [ str "SAFE Template" ]
Expand Down Expand Up @@ -146,5 +166,15 @@ let view (model: Model) (dispatch) =
hybridView jsComp jsCompServer { text="I'm rendered by a js Component!" }
]

ofType<MyReactComp, _, _> { text="my prop" } []
div [] [
span [] [ str "Test ofType:" ]
ofType<MyReactComp, _, _> { text="my prop" } [ span [] [ str "I'm rendered by children!"] ]
]

div [] [
span [] [ str "Test ofFunction:" ]
ofFunction fnComp { text = "I'm rendered by Function Component!"} []
ofFunction fnCompWithChildren { text = "I'm rendered by Function Component!"; children=[||]} [ span [] [ str "I'm rendered by children!"] ]
]

]
72 changes: 72 additions & 0 deletions Samples/SSRSample/src/Client/client.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<div data-reactroot="">
<h1>SAFE Template</h1>
<p>The initial counter is fetched from server</p>
<p>Press buttons to manipulate counter:</p>
<button>-</button>
<div>42</div>
<button>+</button>
<p>
<strong>SAFE Template</strong> powered by:
<span>
<a href="https://github.com/giraffe-fsharp/Giraffe">Giraffe</a>,
<a href="http://fable.io">Fable</a>,
<a href="https://fable-elmish.github.io/">Elmish</a>
</span>
</p>
<div>
<span>Test str:</span>
<span>Some String</span>
</div>
<div>
<span>Test ofFloat:</span>
<span>11.11</span>
</div>
<div>
<span>Test ofInt:</span>
<span>22</span>
</div>
<div>
<span>Test html attr:</span>
<span id="someId" data-aa="bb" cc="dd">data-aa</span>
</div>
<div>
<span>Test CSS prop:</span>
<div style="display:block;color:red">Custom CSSProp</div>
</div>
<div>
<span>Test checkbox:</span>
<input type="checkbox" checked="" />
<input type="checkbox" />
<input type="checkbox" checked="" />
<input type="checkbox" />
</div>
<div>
<span>Test value:</span>
<input type="text" value="true" />
<input type="text" value="false" />
<input type="text" value="true" />
<input type="text" value="false" />
</div>
<div>
<span>Test textarea:</span>
<textarea>true</textarea>
<textarea>false</textarea>
<textarea>true</textarea>
<textarea>false</textarea>
</div>
<div>
<span>Test React.Fragment:</span>
<span>child 1</span>
<span>child 2</span>
<span>child 3</span>
<span>child 4</span>
</div>
<div>
<span>Test escape:</span>
<span data-value="&lt;div&gt;&quot;&#x27;&amp;&lt;/div&gt;" style="display:&lt;div&gt;&quot;&#x27;&amp;&lt;/div&gt;">&lt;div&gt;&quot;&#x27;&amp;&lt;/div&gt;</span>
</div>
<div>
<span>Test js component:</span>
<div>loading</div>
</div>
</div>
72 changes: 72 additions & 0 deletions Samples/SSRSample/src/Client/server.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<div data-reactroot="">
<h1>SAFE Template</h1>
<p>The initial counter is fetched from server</p>
<p>Press buttons to manipulate counter:</p>
<button>-</button>
<div>42</div>
<button>+</button>
<p>
<strong>SAFE Template</strong> powered by:
<span>
<a href="https://github.com/giraffe-fsharp/Giraffe">Giraffe</a>,
<a href="http://fable.io">Fable</a>,
<a href="https://fable-elmish.github.io/">Elmish</a>
</span>
</p>
<div>
<span>Test str:</span>
<span>Some String</span>
</div>
<div>
<span>Test ofFloat:</span>
<span>11.11</span>
</div>
<div>
<span>Test ofInt:</span>
<span>22</span>
</div>
<div>
<span>Test html attr:</span>
<span id="someId" data-aa="bb" cc="dd">data-aa</span>
</div>
<div>
<span>Test CSS prop:</span>
<div style="display:block;color:red">Custom CSSProp</div>
</div>
<div>
<span>Test checkbox:</span>
<input type="checkbox" checked></input>
<input type="checkbox"></input>
<input type="checkbox" checked></input>
<input type="checkbox"></input>
</div>
<div>
<span>Test value:</span>
<input type="text" value="true"></input>
<input type="text" value="false"></input>
<input type="text" value="true"></input>
<input type="text" value="false"></input>
</div>
<div>
<span>Test textarea:</span>
<textarea>true</textarea>
<textarea>false</textarea>
<textarea>true</textarea>
<textarea>false</textarea>
</div>
<div>
<span>Test React.Fragment:</span>
<span>child 1</span>
<span>child 2</span>
<span>child 3</span>
<span>child 4</span>
</div>
<div>
<span>Test escape:</span>
<span data-value="&lt;div&gt;&quot&#x27;&amp;&lt;/div&gt;" style="display:&lt;div&gt;&quot&#x27;&amp;&lt;/div&gt;">&lt;div&gt;&quot&#x27;&amp;&lt;/div&gt;</span>
</div>
<div>
<span>Test js component:</span>
<div>loading</div>
</div>
</div>
2 changes: 1 addition & 1 deletion Samples/SSRSample/src/Server/Server.fs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ open System.Diagnostics

let clientPath = Path.Combine("..","Client") |> Path.GetFullPath
let port = 8085us
let assetsBaseUrl = "http://localhost:8080"
let assetsBaseUrl = "http://localhost:8081"

let initState: Model = {
counter = Some 42
Expand Down
141 changes: 99 additions & 42 deletions src/Fable.React/Fable.Helpers.React.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
module Fable.Helpers.React

open System
open System.Reflection
open FSharp.Reflection
open FSharp.Reflection.FSharpReflectionExtensions
open Fable.Core
open Fable.Core.JsInterop
open Fable.Import
Expand Down Expand Up @@ -756,41 +760,61 @@ with interface ReactElement
let createElement(comp: obj, props: obj, [<ParamList>] children: obj) =
HTMLNode.Text "" :> ReactElement

[<StringEnum>]
type ServerElementType =
| Fragment = 1
| Component = 2
| Tag = 3
| [<CompiledName("0")>] Tag
| [<CompiledName("1")>] Fragment
| [<CompiledName("2")>] Component

let isomorphicElement (tag: obj, props: obj, children: ReactElement list, elementType: ServerElementType) =
let [<Literal>] private ChildrenName = "children"

module ServerRenderingInternal =
let inline hybridExec<'I, 'O> (clientFn: 'I -> 'O) (serverFn: 'I -> 'O) (input: 'I) =
#if FABLE_COMPILER
let props =
match elementType with
| ServerElementType.Component -> props
| _ -> keyValueList CaseRules.LowerFirst (props :?> IProp list)
createElement(tag, props, children)
clientFn input
#else
match elementType with
| ServerElementType.Tag ->
HTMLNode.Node (string tag, props :?> IProp seq, children) :> ReactElement
| ServerElementType.Fragment ->
HTMLNode.List children :> ReactElement
| ServerElementType.Component ->
let tag = tag :?> System.Type
let comp = System.Activator.CreateInstance(tag, props)
let render = tag.GetMethod("render")
render.Invoke(comp, null) :?> ReactElement
| _ -> HTMLNode.Text "" :> ReactElement
serverFn input
#endif

/// OBSOLETE: Use `ofType`
[<System.Obsolete("Use ofType")>]
let inline com<'T,[<Pojo>]'P,[<Pojo>]'S when 'T :> Component<'P,'S>> (props: 'P) (children: ReactElement list): ReactElement =
isomorphicElement(typedefof<'T>, props, children, ServerElementType.Component)
#if FABLE_COMPILER
let inline createServerElement (tag: obj, props: obj, children: ReactElement list, elementType: ServerElementType) =
createElement(tag, props, children)
let inline createServerElementByFn (f, props, children) =
createElement(f, props, children)
#else
let createServerElement (tag: obj, props: obj, children: ReactElement list, elementType: ServerElementType) =
match elementType with
| ServerElementType.Tag ->
HTMLNode.Node (string tag, props :?> IProp seq, children) :> ReactElement
| ServerElementType.Fragment ->
HTMLNode.List children :> ReactElement
| ServerElementType.Component ->
let tag = tag :?> System.Type
let comp = System.Activator.CreateInstance(tag, props)
let childrenProp = tag.GetProperty(ChildrenName)
childrenProp.SetValue(comp, children |> Seq.toArray)
let render = tag.GetMethod("render")
render.Invoke(comp, null) :?> ReactElement

/// OBSOLETE: Use `ofFunction`
[<System.Obsolete("Use ofFunction")>]
let inline fn<[<Pojo>]'P> (f: 'P -> ReactElement) (props: 'P) (children: ReactElement list): ReactElement =
createElement(f, props, children)
let createServerElementByFn = fun (f, props, children) ->
let propsType = props.GetType()
let props =
if propsType.GetProperty (ChildrenName) |> isNull then
props
else
let values = ResizeArray<obj> ()
let properties = propsType.GetProperties()
for p in properties do
if p.Name = ChildrenName then
values.Add (children |> Seq.toArray)
else
values.Add (FSharpValue.GetRecordField(props, p))
FSharpValue.MakeRecord(propsType, values.ToArray()) :?> 'P
f props

#endif

open ServerRenderingInternal

/// Instantiate an imported React component
let inline from<[<Pojo>]'P> (com: ComponentClass<'P>) (props: 'P) (children: ReactElement list): ReactElement =
Expand All @@ -799,12 +823,20 @@ let inline from<[<Pojo>]'P> (com: ComponentClass<'P>) (props: 'P) (children: Rea
/// Instantiate a component from a type inheriting React.Component
/// Example: `ofType<MyComponent,_,_> { myProps = 5 } []`
let inline ofType<'T,[<Pojo>]'P,[<Pojo>]'S when 'T :> Component<'P,'S>> (props: 'P) (children: ReactElement list): ReactElement =
#if FABLE_COMPILER
let obj = typedefof<'T>
#else
let obj = typeof<'T>
#endif
isomorphicElement(obj, props, children, ServerElementType.Component)
let inline clientRender () =
createElement(typedefof<'T>, props, children)

let inline serverRender () =
createServerElement(typeof<'T>, props, children, ServerElementType.Component)

hybridExec clientRender serverRender ()



/// OBSOLETE: Use `ofType`
[<System.Obsolete("Use ofType")>]
let inline com<'T,[<Pojo>]'P,[<Pojo>]'S when 'T :> Component<'P,'S>> (props: 'P) (children: ReactElement list): ReactElement =
ofType<'T, 'P, 'S> props children

/// Instantiate a stateless component from a function
/// Example:
Expand All @@ -813,7 +845,13 @@ let inline ofType<'T,[<Pojo>]'P,[<Pojo>]'S when 'T :> Component<'P,'S>> (props:
/// ofFunction Hello { name = "Maxime" } []
/// ```
let inline ofFunction<[<Pojo>]'P> (f: 'P -> ReactElement) (props: 'P) (children: ReactElement list): ReactElement =
createElement(f, props, children)
hybridExec createElement createServerElementByFn (f, props, children)


/// OBSOLETE: Use `ofFunction`
[<System.Obsolete("Use ofFunction")>]
let inline fn<[<Pojo>]'P> (f: 'P -> ReactElement) (props: 'P) (children: ReactElement list): ReactElement =
ofFunction f props children

/// Instantiate an imported React component. The first two arguments must be string literals, "default" can be used for the first one.
/// Example: `ofImport "Map" "leaflet" { x = 10; y = 50 } []`
Expand Down Expand Up @@ -879,22 +917,41 @@ let inline ofArray (els: ReactElement array): ReactElement = HTMLNode.List els :

/// Instantiate a DOM React element
let inline domEl (tag: string) (props: IHTMLProp list) (children: ReactElement list): ReactElement =
// createElement(tag, keyValueList CaseRules.LowerFirst props, children)
isomorphicElement(tag, (props |> Seq.cast<IProp>), children, ServerElementType.Tag)
let inline clientRender (tag, props, children) =
createElement(tag, keyValueList CaseRules.LowerFirst props, children)

let inline serverRender (tag, props, children) =
createServerElement(tag, (props |> Seq.cast<IProp>), children, ServerElementType.Tag)

hybridExec clientRender serverRender (tag, props, children)

/// Instantiate a DOM React element (void)
let inline voidEl (tag: string) (props: IHTMLProp list) : ReactElement =
// createElement(tag, keyValueList CaseRules.LowerFirst props, [])
isomorphicElement(tag, (props |> Seq.cast<IProp>), [], ServerElementType.Tag)
let inline clientRender (tag, props, children) =
createElement(tag, keyValueList CaseRules.LowerFirst props, children)

let inline serverRender (tag, props, children) =
createServerElement(tag, (props |> Seq.cast<IProp>), children, ServerElementType.Tag)

hybridExec clientRender serverRender (tag, props, [])

/// Instantiate an SVG React element
let inline svgEl (tag: string) (props: IProp list) (children: ReactElement list): ReactElement =
// createElement(tag, keyValueList CaseRules.LowerFirst props, children)
isomorphicElement(tag, props, children, ServerElementType.Tag)
let inline clientRender (tag, props, children) =
createElement(tag, keyValueList CaseRules.LowerFirst props, children)
let inline serverRender (tag, props, children) =
createServerElement(tag, (props |> Seq.cast<IProp>), children, ServerElementType.Tag)

hybridExec clientRender serverRender (tag, props, children)

/// Instantiate a React fragment
let inline fragment (props: IFragmentProp list) (children: ReactElement list): ReactElement =
isomorphicElement(typedefof<Fragment>, props |> Seq.cast<IProp>, children, ServerElementType.Fragment)
let inline clientRender () =
createElement(typedefof<Fragment>, keyValueList CaseRules.LowerFirst props, children)
let inline serverRender () =
createServerElement(typedefof<Fragment>, (props |> Seq.cast<IProp>), children, ServerElementType.Fragment)

hybridExec clientRender serverRender ()

// Standard elements
let inline a b c = domEl "a" b c
Expand Down
Loading

0 comments on commit 88f1b03

Please sign in to comment.