From 5e6a0ac2719e915be0c09f8535f473a73d5bde76 Mon Sep 17 00:00:00 2001 From: Loic Denuziere Date: Thu, 23 Jul 2020 10:43:05 +0200 Subject: [PATCH] #168: Move Bolero.Html to a separate assembly with inlines --- Bolero.sln | 15 + build.fsx | 18 +- src/Bolero.Html/Bolero.Html.fsproj | 14 + src/Bolero.Html/Html.fs | 2339 ++++++++++++++++++ src/Bolero.Html/paket.references | 1 + src/Bolero/Bolero.fsproj | 1 - src/Bolero/Components.fs | 1 + src/Bolero/Node.fs | 6 + src/Bolero/paket.template | 2 + tests/Client/Client.fsproj | 1 + tests/Remoting.Client/Remoting.Client.fsproj | 1 + tests/Unit.Client/Unit.Client.fsproj | 1 + 12 files changed, 2390 insertions(+), 10 deletions(-) create mode 100644 src/Bolero.Html/Bolero.Html.fsproj create mode 100644 src/Bolero.Html/Html.fs create mode 100644 src/Bolero.Html/paket.references diff --git a/Bolero.sln b/Bolero.sln index 75d9e77d..2cff0c96 100644 --- a/Bolero.sln +++ b/Bolero.sln @@ -37,6 +37,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Bolero.Templating.Provider" EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Unit.Client", "tests\Unit.Client\Unit.Client.fsproj", "{61B550E1-6348-4A5E-BFB0-6A0220522EBF}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Bolero.Html", "src\Bolero.Html\Bolero.Html.fsproj", "{47381EA9-6744-4BE8-9C78-E3BD22EA3B3D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -167,6 +169,18 @@ Global {61B550E1-6348-4A5E-BFB0-6A0220522EBF}.Release|x64.Build.0 = Release|Any CPU {61B550E1-6348-4A5E-BFB0-6A0220522EBF}.Release|x86.ActiveCfg = Release|Any CPU {61B550E1-6348-4A5E-BFB0-6A0220522EBF}.Release|x86.Build.0 = Release|Any CPU + {47381EA9-6744-4BE8-9C78-E3BD22EA3B3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47381EA9-6744-4BE8-9C78-E3BD22EA3B3D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47381EA9-6744-4BE8-9C78-E3BD22EA3B3D}.Debug|x64.ActiveCfg = Debug|Any CPU + {47381EA9-6744-4BE8-9C78-E3BD22EA3B3D}.Debug|x64.Build.0 = Debug|Any CPU + {47381EA9-6744-4BE8-9C78-E3BD22EA3B3D}.Debug|x86.ActiveCfg = Debug|Any CPU + {47381EA9-6744-4BE8-9C78-E3BD22EA3B3D}.Debug|x86.Build.0 = Debug|Any CPU + {47381EA9-6744-4BE8-9C78-E3BD22EA3B3D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47381EA9-6744-4BE8-9C78-E3BD22EA3B3D}.Release|Any CPU.Build.0 = Release|Any CPU + {47381EA9-6744-4BE8-9C78-E3BD22EA3B3D}.Release|x64.ActiveCfg = Release|Any CPU + {47381EA9-6744-4BE8-9C78-E3BD22EA3B3D}.Release|x64.Build.0 = Release|Any CPU + {47381EA9-6744-4BE8-9C78-E3BD22EA3B3D}.Release|x86.ActiveCfg = Release|Any CPU + {47381EA9-6744-4BE8-9C78-E3BD22EA3B3D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -182,6 +196,7 @@ Global {328CE686-3CB0-4AC7-9779-866ABE310DF6} = {8BE98B06-91C5-4948-B5AA-F80C67849031} {770469C8-531A-4317-9BCE-DFF61E79B944} = {8BE98B06-91C5-4948-B5AA-F80C67849031} {61B550E1-6348-4A5E-BFB0-6A0220522EBF} = {7EB0C3D3-0BD4-4D72-8ACF-A30B71F7C8E8} + {47381EA9-6744-4BE8-9C78-E3BD22EA3B3D} = {8BE98B06-91C5-4948-B5AA-F80C67849031} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5493A436-6524-48E0-874D-CD70C1615AAF} diff --git a/build.fsx b/build.fsx index a4f6e1a9..e2c807d2 100644 --- a/build.fsx +++ b/build.fsx @@ -98,7 +98,7 @@ let runTags filename apply = Target.description "Generate HTML tags and attributes from CSV" Target.create "tags" (fun _ -> - runTags "src/Bolero/Html.fs" ( + runTags "src/Bolero.Html/Html.fs" ( replace (Tags.GetSample().Rows) "TAGS" (fun s tag -> let esc = escapeDashes tag.Name let ident = if tag.NeedsEscape then "``" + esc + "``" else esc @@ -106,7 +106,7 @@ Target.create "tags" (fun _ -> let childrenVal = if tag.CanHaveChildren then "children" else "[]" s.AppendLine(sprintf """/// Create an HTML `<%s>` element.""" tag.Name) .AppendLine( """/// [category: HTML tag names]""") - .AppendLine(sprintf """let %s (attrs: list)%s : Node =""" ident childrenArg) + .AppendLine(sprintf """let inline %s (attrs: list)%s : Node =""" ident childrenArg) .AppendLine(sprintf """ elt "%s" attrs %s""" tag.Name childrenVal) .AppendLine() ) @@ -117,27 +117,27 @@ Target.create "tags" (fun _ -> elif attr.NeedsEscape then "``" + esc + "``" else esc s.AppendLine(sprintf """ /// Create an HTML `%s` attribute.""" attr.Name) - .AppendLine(sprintf """ let %s (v: obj) : Attr = "%s" => v""" ident attr.Name) + .AppendLine(sprintf """ let inline %s (v: obj) : Attr = "%s" => v""" ident attr.Name) .AppendLine() ) >> replace (Events.GetSample().Rows) "EVENTS" (fun s event -> let esc = escapeDashes event.Name s.AppendLine(sprintf """ /// Create a handler for HTML event `%s`.""" event.Name) - .AppendLine(sprintf """ let %s (callback: %sEventArgs -> unit) : Attr =""" esc event.Type) - .AppendLine(sprintf """ event "%s" callback""" esc) + .AppendLine(sprintf """ let inline %s (callback: %sEventArgs -> unit) : Attr =""" esc event.Type) + .AppendLine(sprintf """ attr.callback<%sEventArgs> ("on%s") callback""" event.Type esc) .AppendLine() ) >> replace (Events.GetSample().Rows) "ASYNCEVENTS" (fun s event -> let esc = escapeDashes event.Name s.AppendLine(sprintf """ /// Create an asynchronous handler for HTML event `%s`.""" event.Name) - .AppendLine(sprintf """ let %s (callback: %sEventArgs -> Async) : Attr =""" esc event.Type) - .AppendLine(sprintf """ event "%s" callback""" esc) + .AppendLine(sprintf """ let inline %s (callback: %sEventArgs -> Async) : Attr =""" esc event.Type) + .AppendLine(sprintf """ attr.async.callback<%sEventArgs> "on%s" callback""" event.Type esc) ) >> replace (Events.GetSample().Rows) "TASKEVENTS" (fun s event -> let esc = escapeDashes event.Name s.AppendLine(sprintf """ /// Create an asynchronous handler for HTML event `%s`.""" event.Name) - .AppendLine(sprintf """ let %s (callback: %sEventArgs -> Task) : Attr =""" esc event.Type) - .AppendLine(sprintf """ event "%s" callback""" esc) + .AppendLine(sprintf """ let inline %s (callback: %sEventArgs -> Task) : Attr =""" esc event.Type) + .AppendLine(sprintf """ attr.task.callback<%sEventArgs> "on%s" callback""" event.Type esc) ) ) runTags "src/Bolero.Templating.Provider/Parsing.fs" ( diff --git a/src/Bolero.Html/Bolero.Html.fsproj b/src/Bolero.Html/Bolero.Html.fsproj new file mode 100644 index 00000000..f7037bc3 --- /dev/null +++ b/src/Bolero.Html/Bolero.Html.fsproj @@ -0,0 +1,14 @@ + + + + netstandard2.1 + Bolero + + + + + + + + + \ No newline at end of file diff --git a/src/Bolero.Html/Html.fs b/src/Bolero.Html/Html.fs new file mode 100644 index 00000000..09c90d9a --- /dev/null +++ b/src/Bolero.Html/Html.fs @@ -0,0 +1,2339 @@ +// $begin{copyright} +// +// This file is part of Bolero +// +// Copyright (c) 2018 IntelliFactory and contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you +// may not use this file except in compliance with the License. You may +// obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +// $end{copyright} + +/// Create HTML elements, attributes and event handlers. +/// [category: HTML] +module Bolero.Html + +open System.Threading.Tasks +open System.Globalization + +open System +open Microsoft.AspNetCore.Components +open Microsoft.AspNetCore.Components.Web + +/// Create an HTML text node. +/// [category: HTML elements] +let inline text str = Text str + +/// Create an HTML text node using formatting. +/// [category: HTML elements] +let inline textf format = Printf.kprintf text format + +/// Create an HTML element. +/// [category: HTML elements] +let inline elt name attrs children = Node.Elt(name, attrs, children) + +/// Concatenate HTML fragments. +/// [category: HTML elements] +let inline concat nodes = Concat nodes + +/// Create an HTML attribute. +/// [category: HTML elements] +let inline (=>) name value = Attr(name, box value) + +/// Create a conditional fragment. `matching` must be either a boolean or an F# union. +/// If it's a union, `mkNode` must only match on the case. +/// [category: HTML elements] +let inline cond<'T> (matching: 'T) (mkNode: 'T -> Node) = + if typeof<'T> = typeof then + Node.Cond(unbox matching, mkNode matching) + else + Node.Match(typeof<'T>, matching, mkNode matching) + +/// Create a fragment that concatenates nodes for each item in a sequence. +/// [category: HTML elements] +let inline forEach<'T> (items: seq<'T>) (mkNode: 'T -> Node) = + Node.ForEach [for n in items -> mkNode n] + +/// Create a node from a Blazor RenderFragment. +let inline fragment (frag: RenderFragment) = + Node.Fragment frag + +/// Create a fragment from a Blazor component. +/// [category: Components] +let inline comp<'T when 'T :> IComponent> attrs children = + Node.BlazorComponent<'T>(attrs, children) + +/// Create a fragment from an Elmish component. +/// [category: Components] +let inline ecomp<'T, 'model, 'msg when 'T :> ElmishComponent<'model, 'msg>> + (attrs: list) (model: 'model) (dispatch: Elmish.Dispatch<'msg>) = + comp<'T> (attrs @ ["Model" => model; "Dispatch" => dispatch]) [] + +/// Create a fragment with a lazily rendered view function. +/// [category: Components] +let inline lazyComp (viewFunction: 'model -> Node) (model: 'model) = + let viewFunction' : 'model -> Elmish.Dispatch<'msg> -> Node = fun m _ -> viewFunction m + comp> ["Model" => model; "ViewFunction" => viewFunction'; ][] + +/// Create a fragment with a lazily rendered view function and a custom equality. +/// [category: Components] +let inline lazyCompWith (equal: 'model -> 'model -> bool) (viewFunction: 'model -> Node) (model: 'model) = + let viewFunction' : 'model -> Elmish.Dispatch<'msg> -> Node = fun m _ -> viewFunction m + comp> ["Model" => model; "ViewFunction" => viewFunction'; "Equal" => equal; ] [] + +/// Create a fragment with a lazily rendered view function. +/// [category: Components] +let inline lazyComp2 (viewFunction: 'model -> Elmish.Dispatch<'msg> -> Node) (model: 'model) (dispatch: Elmish.Dispatch<'msg>) = + comp> ["Model" => model; "Dispatch" => dispatch; "ViewFunction" => viewFunction; ] [] + +/// Create a fragment with a lazily rendered view function and a custom equality. +/// [category: Components] +let inline lazyComp2With (equal: 'model -> 'model -> bool) (viewFunction: 'model -> Elmish.Dispatch<'msg> -> Node) (model: 'model) (dispatch: Elmish.Dispatch<'msg>) = + comp> ["Model" => model; "Dispatch" => dispatch; "ViewFunction" => viewFunction; "Equal" => equal; ] [] + +/// Create a fragment with a lazily rendered view function. +/// [category: Components] +let inline lazyComp3 (viewFunction: ('model1 * 'model2') -> Elmish.Dispatch<'msg> -> Node) (model1: 'model1) (model2: 'model2) (dispatch: Elmish.Dispatch<'msg>) = + comp> ["Model" => (model1, model2); "Dispatch" => dispatch; "ViewFunction" => viewFunction; ] [] + +/// Create a fragment with a lazily rendered view function and a custom equality. +/// [category: Components] +let inline lazyComp3With (equal: ('model1 * 'model2) -> ('model1 * 'model2) -> bool) (viewFunction: ('model1 * 'model2') -> Elmish.Dispatch<'msg> -> Node) (model1: 'model1) (model2: 'model2) (dispatch: Elmish.Dispatch<'msg>) = + comp> ["Model" => (model1, model2); "Dispatch" => dispatch; "ViewFunction" => viewFunction; "Equal" => equal; ] [] + +/// Create a fragment with a lazily rendered view function and custom equality on model field. +/// [category: Components] +let inline lazyCompBy (equal: 'model -> 'a) (viewFunction: 'model -> Node) (model: 'model) = + let equal' model1 model2 = (equal model1) = (equal model2) + lazyCompWith equal' viewFunction model + +/// Create a fragment with a lazily rendered view function and custom equality on model field. +/// [category: Components] +let inline lazyComp2By (equal: 'model -> 'a) (viewFunction: 'model -> Elmish.Dispatch<'msg> -> Node) (model: 'model) (dispatch: Elmish.Dispatch<'msg>) = + let equal' model1 model2 = (equal model1) = (equal model2) + lazyComp2With equal' viewFunction model dispatch + +/// Create a fragment with a lazily rendered view function and custom equality on model field. +/// [category: Components] +let inline lazyComp3By (equal: ('model1 * 'model2) -> 'a) (viewFunction: ('model1 * 'model2) -> Elmish.Dispatch<'msg> -> Node) (model1: 'model1) (model2: 'model2) (dispatch: Elmish.Dispatch<'msg>) = + let equal' (model11, model12) (model21, model22) = (equal (model11, model12)) = (equal (model21, model22)) + lazyComp3With equal' viewFunction model1 model2 dispatch + +/// Create a navigation link which toggles its `active` class +/// based on whether the current URI matches its `href`. +/// [category: Components] +let inline navLink (``match``: Routing.NavLinkMatch) attrs children = + comp (("Match" => ``match``) :: attrs) children + +// BEGIN TAGS +/// Create an HTML `` element. +/// [category: HTML tag names] +let inline a (attrs: list) (children: list) : Node = + elt "a" attrs children + +/// Create an HTML `` element. +/// [category: HTML tag names] +let inline abbr (attrs: list) (children: list) : Node = + elt "abbr" attrs children + +/// Create an HTML `` element. +/// [category: HTML tag names] +let inline acronym (attrs: list) (children: list) : Node = + elt "acronym" attrs children + +/// Create an HTML `
` element. +/// [category: HTML tag names] +let inline address (attrs: list) (children: list) : Node = + elt "address" attrs children + +/// Create an HTML `` element. +/// [category: HTML tag names] +let inline applet (attrs: list) (children: list) : Node = + elt "applet" attrs children + +/// Create an HTML `` element. +/// [category: HTML tag names] +let inline area (attrs: list) : Node = + elt "area" attrs [] + +/// Create an HTML `
` element. +/// [category: HTML tag names] +let inline article (attrs: list) (children: list) : Node = + elt "article" attrs children + +/// Create an HTML `