diff --git a/WebSharper.UI.Next.Formlets/Formlets.fs b/WebSharper.UI.Next.Formlets/Formlets.fs index e76977a..32ad140 100644 --- a/WebSharper.UI.Next.Formlets/Formlets.fs +++ b/WebSharper.UI.Next.Formlets/Formlets.fs @@ -288,7 +288,7 @@ module Formlet = let m = ListModel.Create fst [] let v = m.View - |> View.ConvertBy m.Key snd + |> View.MapSeqCachedBy m.Key snd |> View.Map Array.ofSeq { View = @@ -404,6 +404,115 @@ module Formlet = let Do = Builder() + module private ValidationView = + + open WebSharper.UI.Next.Html + + let validationMessage = function + | Success _ -> Doc.Empty + | Failure msg -> + let m = msg |> List.fold (fun a s -> a + "\n" + s) "" + text m + + let inline respondResult (v : View>) (s, f) = + View.Map ( + function + | Success _ -> s + | Failure _ -> f + ) v + + let toggleHidden (v : View>) = + respondResult v ("hidden", "visible") + + let toggleError (v : View>) = + respondResult v ("", "errorFormlet") + + let validationTooltipView res doc = + table [ + tr [ + tdAttr [attr.``class`` "tooltip"] [ + doc + divAttr [attr.classDyn <| toggleHidden res] [ + spanAttr + [attr.``class`` "tooltiptext"] + [Doc.BindView validationMessage res] + ] + ] + td [ + divAttr [attr.classDyn <| toggleHidden res] [ + divAttr [attr.``class`` "errorIcon"] [] + ] + ] + ] + ] :> Doc + + let validationMessageView res doc = + table [ + tr [ + tdAttr [attr.classDyn <| toggleError res] [ + doc + ] + td [] + ] + trAttr [attr.classDyn <| toggleHidden res] [ + tdAttr [attr.``class`` "errorPanel"] [ + Doc.BindView validationMessage res + ] + td [] + ] + ] :> Doc + + let wrapIcon f v ls = + Layout.OfList ls + |> fun l -> + match l.Shape with + | Item x -> {l with Shape = Item (f v x)} + | _ -> l |> Layout.Wrap (f v) + |> fun x -> [x] + + let withValidation f (flX : Formlet<'T>) = + let flx = flX.Data () + Formlet (fun () -> + { + View = flx.View + Layout = wrapIcon f flx.View flx.Layout + }) + + let addTrigger l (sb : Submitter<_>) (x : Doc) = + match x with + | :? Elt as e -> + let itm = Item (DocExtensions.OnChange + (e, (fun _ _ -> sb.Trigger ()))) + [{l with Shape = itm}] + | _ -> [l] + + let addSubmitter sb flx = + match flx.Layout with + | [l] -> + match l.Shape with + | Item x -> addTrigger l sb x + | _ -> [l] + | l -> l + + module V = ValidationView + + let WithValidationIcon (flX : Formlet<'T>) = + V.withValidation V.validationTooltipView flX + + let WithValidationMessage (flX : Formlet<'T>) = + V.withValidation V.validationMessageView flX + + let ValidateOnChange init (flX : Formlet<'T>) = + let flx = flX.Data() + let sb = Submitter.Create flx.View (Success init) + Formlet (fun () -> + { + View = sb.View + Layout = V.addSubmitter sb flx + } + ) + + [] module Pervasives = @@ -507,3 +616,4 @@ module Validation = let IsMatch regex msg flX = let re = RegExp(regex) Is re.Test msg flX + diff --git a/WebSharper.UI.Next.Formlets/styles/Formlet.css b/WebSharper.UI.Next.Formlets/styles/Formlet.css index 7c6ded9..a072d0d 100644 --- a/WebSharper.UI.Next.Formlets/styles/Formlet.css +++ b/WebSharper.UI.Next.Formlets/styles/Formlet.css @@ -107,10 +107,12 @@ .formlet select, .formlet textarea { + font-size: 14px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; padding : 4px; margin : 5px; - border:solid 1px #aacfe4; - width:250px; + border: solid 1px #aacfe4; + width: 250px; display: block; } .formlet select @@ -190,12 +192,14 @@ .formlet .validIcon, .formlet .removeIcon, .formlet .addIcon, .formlet .infoIcon , .formlet .errorIcon { + position: absolute; display : block; width : 24px; height : 24px; padding : 0; margin:2px; margin-left : 5px; + margin-top : -12px; background-repeat : no-repeat; background-position : center; cursor : pointer; @@ -225,3 +229,59 @@ { background-image :url("InfoIcon.png"); } + +@keyframes fadein { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes fadeout { + from { opacity: 0; } + to { opacity: 1; } +} + +.formlet .visible { + opacity: 1; + animation: fadeout 0.3s ease-in; +} + +.formlet .hidden { + opacity: 0; + animation: fadein 0.3s ease-out; +} + +/* Tooltip container */ +.formlet .tooltip { + position: relative; + display: inline-block; + opacity: 1; +} + +/* Tooltip text */ +.formlet .tooltip .tooltiptext { + bottom: 110%; + left: 50%; + width: 240px; + margin-left: -120px; + background-color: dimgray; + color: #fff; + text-align: center; + padding: 5px 0; + border-radius: 6px; + position: absolute; + z-index: 1; + opacity: inherit; +} + +/* Tooltip arrow */ +.formlet .tooltip .tooltiptext::after { + content: " "; + position: absolute; + top: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: dimgray transparent transparent transparent; + opacity: inherit; +}