From 2320650a4b6f7967ce7481454160ab4639961a9a Mon Sep 17 00:00:00 2001 From: Jonas Juselius Date: Wed, 30 Mar 2016 17:40:23 +0200 Subject: [PATCH 1/5] Add WithValidationIcon, with or without tooltips. --- WebSharper.UI.Next.Formlets/Formlets.fs | 69 +++++++++++++++++++ .../styles/Formlet.css | 65 +++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/WebSharper.UI.Next.Formlets/Formlets.fs b/WebSharper.UI.Next.Formlets/Formlets.fs index e76977a..c74ac67 100644 --- a/WebSharper.UI.Next.Formlets/Formlets.fs +++ b/WebSharper.UI.Next.Formlets/Formlets.fs @@ -404,6 +404,74 @@ 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 toggleValidationView (v : View>) = + View.Map ( + function + | Success _ -> "hidden" + | Failure _ -> "visible" + ) v + + let validationLayout doc validity = + table [ + tr [ + td [doc] + td [validity] + ] + ] :> Doc + + let validationViewWithTooltip res doc = + divAttr [attr.classDyn <| toggleValidationView res] [ + divAttr [attr.``class`` "tooltip"] [ + divAttr [attr.``class`` "errorIcon"] [] + spanAttr + [attr.``class`` "tooltiptext"] + [Doc.BindView validationMessage res] + ] + ] + |> validationLayout doc + + let validationViewIcon res doc = + divAttr [attr.classDyn <| toggleValidationView res] [ + divAttr [attr.``class`` "errorIcon"] [] + ] + |> validationLayout 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>) = + flX + |> fun flx -> + Formlet (fun () -> + let flx = flx.Data () + { + View = flx.View + Layout = wrapIcon f flx.View flx.Layout + }) + + module V = ValidationView + + let WithValidationIconOnly (flX : Formlet<'T>) = + V.withValidation V.validationViewIcon flX + + let WithValidationIcon (flX : Formlet<'T>) = + V.withValidation V.validationViewWithTooltip flX + [] module Pervasives = @@ -507,3 +575,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..4a8a887 100644 --- a/WebSharper.UI.Next.Formlets/styles/Formlet.css +++ b/WebSharper.UI.Next.Formlets/styles/Formlet.css @@ -225,3 +225,68 @@ { 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: absolute; + margin-top: -12px; + display: inline-block; + opacity: inherit; +} + +/* Tooltip text */ +.formlet .tooltip .tooltiptext { + top: -5px; + left: 105%; + width: 200px; + background-color: dimgray; + color: #fff; + text-align: center; + padding: 5px 0; + border-radius: 6px; + position: absolute; + z-index: 1; + opacity: inherit; + /*transition: opacity 1s;*/ +} +/* Tooltip arrow */ +.formlet .tooltip .tooltiptext::after { + content: " "; + position: absolute; + top: 50%; + right: 100%; /* To the left of the tooltip */ + margin-top: -5px; + border-width: 5px; + border-style: solid; + border-color: transparent dimgray transparent transparent; + opacity: inherit; + /*transition: opacity 1s;*/ +} + +.tooltip:focus .tooltiptext { + opacity: 1; +} + +/*.tooltip:hover .tooltiptext { + opacity: 1; +}*/ \ No newline at end of file From 9e942b9ace3eff1abe87beb0c77080338f51b698 Mon Sep 17 00:00:00 2001 From: Jonas Juselius Date: Thu, 31 Mar 2016 09:14:23 +0200 Subject: [PATCH 2/5] Change ConvertBy to MapSeqCachedBy to quench deprecarion warning. --- WebSharper.UI.Next.Formlets/Formlets.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebSharper.UI.Next.Formlets/Formlets.fs b/WebSharper.UI.Next.Formlets/Formlets.fs index c74ac67..387365a 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 = From fb45618c1652c4792a8b35ad32b3682a596df0dd Mon Sep 17 00:00:00 2001 From: Jonas Juselius Date: Thu, 31 Mar 2016 14:42:55 +0200 Subject: [PATCH 3/5] Validation tooltip on top. --- WebSharper.UI.Next.Formlets/Formlets.fs | 37 +++++++----------- .../styles/Formlet.css | 39 ++++++++----------- 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/WebSharper.UI.Next.Formlets/Formlets.fs b/WebSharper.UI.Next.Formlets/Formlets.fs index 387365a..e3387a2 100644 --- a/WebSharper.UI.Next.Formlets/Formlets.fs +++ b/WebSharper.UI.Next.Formlets/Formlets.fs @@ -421,31 +421,25 @@ module Formlet = | Failure _ -> "visible" ) v - let validationLayout doc validity = + let validationViewWithTooltip res doc = table [ tr [ - td [doc] - td [validity] + tdAttr [attr.``class`` "tooltip"] [ + doc + divAttr [attr.classDyn <| toggleValidationView res] [ + spanAttr + [attr.``class`` "tooltiptext"] + [Doc.BindView validationMessage res] + ] + ] + td [ + divAttr [attr.classDyn <| toggleValidationView res] [ + divAttr [attr.``class`` "errorIcon"] [] + ] + ] ] ] :> Doc - let validationViewWithTooltip res doc = - divAttr [attr.classDyn <| toggleValidationView res] [ - divAttr [attr.``class`` "tooltip"] [ - divAttr [attr.``class`` "errorIcon"] [] - spanAttr - [attr.``class`` "tooltiptext"] - [Doc.BindView validationMessage res] - ] - ] - |> validationLayout doc - - let validationViewIcon res doc = - divAttr [attr.classDyn <| toggleValidationView res] [ - divAttr [attr.``class`` "errorIcon"] [] - ] - |> validationLayout doc - let wrapIcon f v ls = Layout.OfList ls |> fun l -> @@ -466,9 +460,6 @@ module Formlet = module V = ValidationView - let WithValidationIconOnly (flX : Formlet<'T>) = - V.withValidation V.validationViewIcon flX - let WithValidationIcon (flX : Formlet<'T>) = V.withValidation V.validationViewWithTooltip flX diff --git a/WebSharper.UI.Next.Formlets/styles/Formlet.css b/WebSharper.UI.Next.Formlets/styles/Formlet.css index 4a8a887..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; @@ -248,17 +252,17 @@ /* Tooltip container */ .formlet .tooltip { - position: absolute; - margin-top: -12px; + position: relative; display: inline-block; - opacity: inherit; + opacity: 1; } /* Tooltip text */ .formlet .tooltip .tooltiptext { - top: -5px; - left: 105%; - width: 200px; + bottom: 110%; + left: 50%; + width: 240px; + margin-left: -120px; background-color: dimgray; color: #fff; text-align: center; @@ -267,26 +271,17 @@ position: absolute; z-index: 1; opacity: inherit; - /*transition: opacity 1s;*/ } + /* Tooltip arrow */ .formlet .tooltip .tooltiptext::after { content: " "; position: absolute; - top: 50%; - right: 100%; /* To the left of the tooltip */ - margin-top: -5px; + top: 100%; + left: 50%; + margin-left: -5px; border-width: 5px; border-style: solid; - border-color: transparent dimgray transparent transparent; + border-color: dimgray transparent transparent transparent; opacity: inherit; - /*transition: opacity 1s;*/ } - -.tooltip:focus .tooltiptext { - opacity: 1; -} - -/*.tooltip:hover .tooltiptext { - opacity: 1; -}*/ \ No newline at end of file From 0844f9244fbd1a78c43031d847ae268aeef66a4c Mon Sep 17 00:00:00 2001 From: Jonas Juselius Date: Thu, 31 Mar 2016 15:10:52 +0200 Subject: [PATCH 4/5] Add WithValidationMessage combinator. Have the option to select between error icon + tooltip, or errorFormlet and errorPanel. --- WebSharper.UI.Next.Formlets/Formlets.fs | 39 ++++++++++++++++++++----- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/WebSharper.UI.Next.Formlets/Formlets.fs b/WebSharper.UI.Next.Formlets/Formlets.fs index e3387a2..2defc97 100644 --- a/WebSharper.UI.Next.Formlets/Formlets.fs +++ b/WebSharper.UI.Next.Formlets/Formlets.fs @@ -414,32 +414,54 @@ module Formlet = let m = msg |> List.fold (fun a s -> a + "\n" + s) "" text m - let toggleValidationView (v : View>) = + let inline respondResult (v : View>) (s, f) = View.Map ( function - | Success _ -> "hidden" - | Failure _ -> "visible" + | Success _ -> s + | Failure _ -> f ) v - let validationViewWithTooltip res doc = + 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 <| toggleValidationView res] [ + divAttr [attr.classDyn <| toggleHidden res] [ spanAttr [attr.``class`` "tooltiptext"] [Doc.BindView validationMessage res] ] ] td [ - divAttr [attr.classDyn <| toggleValidationView res] [ + 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 -> @@ -461,7 +483,10 @@ module Formlet = module V = ValidationView let WithValidationIcon (flX : Formlet<'T>) = - V.withValidation V.validationViewWithTooltip flX + V.withValidation V.validationTooltipView flX + + let WithValidationMessage (flX : Formlet<'T>) = + V.withValidation V.validationMessageView flX [] module Pervasives = From cab8facf68f5bad0651ceb9d639db0bb13e0f2b0 Mon Sep 17 00:00:00 2001 From: Jonas Juselius Date: Wed, 6 Apr 2016 10:16:03 +0200 Subject: [PATCH 5/5] Add ValidateOnChange combinator to delay validation errors. The ValidateOnChange combinator delays validation until the OnChange event of the input element fires. Form validation with reactive validation status indicators can become annoying when the validation triggers too early. For example, a user starts typing an email address, and the validator immediately shows an error until the validator clears. --- WebSharper.UI.Next.Formlets/Formlets.fs | 41 ++++++++++++++++++++----- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/WebSharper.UI.Next.Formlets/Formlets.fs b/WebSharper.UI.Next.Formlets/Formlets.fs index 2defc97..32ad140 100644 --- a/WebSharper.UI.Next.Formlets/Formlets.fs +++ b/WebSharper.UI.Next.Formlets/Formlets.fs @@ -471,14 +471,28 @@ module Formlet = |> fun x -> [x] let withValidation f (flX : Formlet<'T>) = - flX - |> fun flx -> - Formlet (fun () -> - let flx = flx.Data () - { - View = flx.View - Layout = wrapIcon f flx.View flx.Layout - }) + 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 @@ -488,6 +502,17 @@ module Formlet = 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 =