{"action":"created","issue":{"url":"https://api.github.com/repos/intellifactory/websharper.ui.next/issues/34","labels_url":"https://api.github.com/repos/intellifactory/websharper.ui.next/issues/34/labels{/name}","comments_url":"https://api.github.com/repos/intellifactory/websharper.ui.next/issues/34/comments","events_url":"https://api.github.com/repos/intellifactory/websharper.ui.next/issues/34/events","html_url":"https://github.com/intellifactory/websharper.ui.next/issues/34","id":105191328,"number":34,"title":"Many issues with Doc.FloatInput and Doc.IntInput","user":{"login":"Bananas-Are-Yellow","id":5648989,"avatar_url":"https://avatars.githubusercontent.com/u/5648989?v=3","gravatar_id":"","url":"https://api.github.com/users/Bananas-Are-Yellow","html_url":"https://github.com/Bananas-Are-Yellow","followers_url":"https://api.github.com/users/Bananas-Are-Yellow/followers","following_url":"https://api.github.com/users/Bananas-Are-Yellow/following{/other_user}","gists_url":"https://api.github.com/users/Bananas-Are-Yellow/gists{/gist_id}","starred_url":"https://api.github.com/users/Bananas-Are-Yellow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Bananas-Are-Yellow/subscriptions","organizations_url":"https://api.github.com/users/Bananas-Are-Yellow/orgs","repos_url":"https://api.github.com/users/Bananas-Are-Yellow/repos","events_url":"https://api.github.com/users/Bananas-Are-Yellow/events{/privacy}","received_events_url":"https://api.github.com/users/Bananas-Are-Yellow/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":8,"created_at":"2015-09-07T10:28:25Z","updated_at":"2015-09-09T14:50:09Z","closed_at":null,"body":"The most obvious problem is with `Doc.FloatInput`. You can't enter a float value, e.g. \"1.5\", because as soon as you type \"1.\", the text is immediately replaced by \"1\". This is because \"1.\" is successfully parsed and the var is updated, but then because the var was updated, the value is immediately formatted (using `string x`) and this simplifies the text to \"1\".\r\n\r\nIn fact, there are several usability problems with `Doc.FloatInput` and `Doc.IntInput`. Here are the requirements for numeric input as I see them:\r\n\r\n- Whatever you type, the code should never change that text. If I enter \" 007.00\", that's what I should see. It should never be simplified. This is true for both `float` and `int` input.\r\n\r\n- Empty input should be supported. If a dialog requires the user to enter a value, and there is no natural default value, the input field should initially be empty. The field can become empty again if the user deletes the text. And if the user just enters some spaces, this is empty too (and should not be simplified).\r\n\r\n- A dialog with an **OK** or **Apply** button needs to know if the input field is currently invalid. The code as it stands today sets the var to be the last valid value, but the input field may have been changed since then and may now be invalid.\r\n\r\n- Values need to be parsed correctly. Currently `Doc.FloatInput` uses `parseFloat`, and `Doc.IntInput` uses `parseInt`, but these are not the right functions to used (see [here](http://stackoverflow.com/a/13676265/2987720) and [here](http://stackoverflow.com/a/17106702/2987720)).\r\n\r\nHere is my code for addressing these requirements.\r\n\r\nFirst a helper function:\r\n\r\n```fsharp\r\n[<JavaScript>]\r\nmodule String =\r\n    /// The string is zero or more spaces.\r\n    let blank (text: string) = text |> Seq.forall ((=) ' ')\r\n```\r\n\r\nNow the parsing:\r\n\r\n```fsharp\r\n[<Inline \"Number($x)\">]\r\nlet number x = X<float>\r\n\r\nlet (|ValidFloat|InvalidFloat|BlankFloat|) (text: string) =\r\n    if String.blank text then BlankFloat\r\n    else\r\n        let x = number text\r\n        if JS.IsNaN x then InvalidFloat else ValidFloat x\r\n\r\nlet (|ValidInt|InvalidInt|BlankInt|) text =\r\n    match text with\r\n    | _ when text |> Seq.exists ((=) '.') -> InvalidInt\r\n    | ValidFloat x -> ValidInt (int x)\r\n    | InvalidFloat -> InvalidInt\r\n    | BlankFloat -> BlankInt\r\n```\r\n\r\nThe var that we want to bind to the input field is of type `FormattedText<float>` or `FormattedText<int>`.\r\n\r\n```fsharp\r\ntype FormattedText<'t> =\r\n    | ValidText of text: string * value: 't\r\n    | InvalidText of text: string\r\n    | BlankText of spaces: string\r\n```\r\n\r\nHere is the code to connect the input field to the var:\r\n\r\n```fsharp\r\nlet text = function\r\n    | ValidText (text, _) -> text\r\n    | InvalidText text -> text\r\n    | BlankText text -> text\r\n\r\nlet formattedInput attrs tryParse (var: IRef<_>) =\r\n    inputAttr [\r\n        Attr.Concat attrs\r\n        attr.typ \"text\"\r\n        Attr.CustomValue var text (tryParse >> Some)\r\n        Attr.DynamicClass \"invalid-format\" var.View (function InvalidText _ -> true | _ -> false)\r\n    ] []\r\n```\r\n\r\nI like to show invalid input by changing the background color of the input field, which is what the `Attr.DynamicClass` is doing. This is optional of course, and this attribute could be added by the caller instead, as part of the `attrs` argument.\r\n\r\nFinally here are my alternatives for `Doc.FloatInput` and `Doc.IntInput`. The `filter` function allows you to constrain the value, e.g. to require a positive number.\r\n\r\n```fsharp\r\nlet floatInput attrs filter (var: IRef<_>) =\r\n    let tryParse text =\r\n        match text with\r\n        | ValidFloat x when filter x -> ValidText (text, x)\r\n        | BlankFloat -> BlankText text\r\n        | _ -> InvalidText text\r\n    formattedInput attrs tryParse var\r\n\r\nlet intInput attrs filter (var: IRef<_>) =\r\n    let tryParse text =\r\n        match text with\r\n        | ValidInt x when filter x -> ValidText (text, x)\r\n        | BlankInt -> BlankText text\r\n        | _ -> InvalidText text\r\n    formattedInput attrs tryParse var\r\n```\r\n\r\nNote that since `tryParse` is supplied to `formattedInput`, we can support other situations, such as parsing math expressions, e.g. \"1.4 + 2/3\". And since `FormattedText<'t>` is generic, other types could be supported, e.g. dates.\r\n\r\nHere is an example usage. My application is CAD for mechanical design, and here I want the user to enter a setback distance for a chamfer, which will be applied to some edges:\r\n\r\n```fsharp\r\nlet event f = fun _ _ -> f ()\r\n\r\nlet chamfer =\r\n    let setback = Var.Create (BlankText \"\")\r\n\r\n    let submit = Submitter.Create (View.Map Some setback.View) None\r\n    submit.View\r\n    |> View.Sink (function\r\n        | Some (ValidText (_, setback)) -> JS.Alert (string setback)\r\n        | _ -> ())\r\n\r\n    let input = floatInput [attr.placeholder \"distance\"] (fun x -> x >= 0.) setback\r\n    Template.ChamferDialog.Doc (\r\n        Setback = [input],\r\n        OnApply = event submit.Trigger)\r\n```\r\n"},"comment":{"url":"https://api.github.com/repos/intellifactory/websharper.ui.next/issues/comments/138935373","html_url":"https://github.com/intellifactory/websharper.ui.next/issues/34#issuecomment-138935373","issue_url":"https://api.github.com/repos/intellifactory/websharper.ui.next/issues/34","id":138935373,"user":{"login":"granicz","id":6954755,"avatar_url":"https://avatars.githubusercontent.com/u/6954755?v=3","gravatar_id":"","url":"https://api.github.com/users/granicz","html_url":"https://github.com/granicz","followers_url":"https://api.github.com/users/granicz/followers","following_url":"https://api.github.com/users/granicz/following{/other_user}","gists_url":"https://api.github.com/users/granicz/gists{/gist_id}","starred_url":"https://api.github.com/users/granicz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/granicz/subscriptions","organizations_url":"https://api.github.com/users/granicz/orgs","repos_url":"https://api.github.com/users/granicz/repos","events_url":"https://api.github.com/users/granicz/events{/privacy}","received_events_url":"https://api.github.com/users/granicz/received_events","type":"User","site_admin":false},"created_at":"2015-09-09T14:50:09Z","updated_at":"2015-09-09T14:50:09Z","body":"So to summarize this discussion so far, given that the right shim can be found for IE9, we would rename/add a `Doc.IntInputUnchecked`/`Doc.IntInput` and their float variants, and these would use `type=\"number\"`.  Is there any better alternative or a further enhancement you can suggest?"}}