{"id":27057916,"url":"https://github.com/mrange/client.formlet","last_synced_at":"2025-07-30T01:13:36.033Z","repository":{"id":22096562,"uuid":"25426420","full_name":"mrange/Client.Formlet","owner":"mrange","description":null,"archived":false,"fork":false,"pushed_at":"2015-01-06T23:00:31.000Z","size":647,"stargazers_count":16,"open_issues_count":1,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-07-26T16:08:23.884Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mrange.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-10-19T12:32:48.000Z","updated_at":"2025-02-09T09:45:26.000Z","dependencies_parsed_at":"2022-08-19T07:32:11.320Z","dependency_job_id":null,"html_url":"https://github.com/mrange/Client.Formlet","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mrange/Client.Formlet","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrange%2FClient.Formlet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrange%2FClient.Formlet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrange%2FClient.Formlet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrange%2FClient.Formlet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrange","download_url":"https://codeload.github.com/mrange/Client.Formlet/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrange%2FClient.Formlet/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267791131,"owners_count":24144895,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-29T02:00:12.549Z","response_time":2574,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-04-05T11:34:00.488Z","updated_at":"2025-07-30T01:13:35.993Z","avatar_url":"https://github.com/mrange.png","language":"F#","funding_links":[],"categories":[],"sub_categories":[],"readme":"FSharp.Client.Formlet\n=====================\n\nSee my blog post on why I consider Formlets to a very important idea: https://mrange.wordpress.com/2014/10/26/building-forms-fast-using-formlets/\n\nThe major inspiration for this library comes from WebSharper (https://github.com/intellifactory/websharper).\n\nWebSharper demonstrated how powerful a, so called, Formlet can be to represent an interactive form.\n\nWebSharper impressed so much that we think Formlets are the current best way to represent interactive forms in F#.\n\nFormlets are awesome because:\n* They are functionally idiomatic, that means they feel right in a functional language such as F#\n* They are endlessly composable. That means any formlet can be combined with any other formlet to produce a new formlet.\n* Using formlets it's trivial to create highly interactive forms.\n\nExample\n-------\n\n```fsharp\n// Defines how to input personal information\nlet person =\n    formlet {\n        let! firstName  = LabeledText       \"First name\"    \"John\"\n        let! lastName   = LabeledText       \"Last name\"     \"Doe\"\n        let! birthDate  = LabeledDateTime   \"Birth date\"    None\n        return firstName, lastName, birthDate\n    }\n    |\u003e Enhance.WithLegend \"Person\"\n\n// Defines how to address information\nlet address =\n    formlet {\n        let! street = LabeledText       \"Street\"    \"\"\n        let! zip    = LabeledText       \"Zip\"       \"\"\n        return street, zip\n    }\n    |\u003e Enhance.WithLegend \"Address info\"\n\n// Combines the personal and address info formlets and adds an error summary\n//  Two formlets combined results in a new formlet which in turn can be combined\n//  And so on...\nlet f =\n    formlet {\n        let! firstName, lastName, birthDate = person\n        let! address                        = address\n        return firstName, lastName, address,\n    }\n    |\u003e Enhance.WithErrorSummary\n\n```\n\nThis defines a form like below:\n\u003cimg src=\"media/simple_formlet.JPG\" /\u003e\n\n\nWhile this is rather static what's really great with Formlets is the ease\nhow you can build Forms that react to different input. The simplest way would\nbe an if expression\n\n```fsharp\nlet f =\n    formlet {\n        let! country    = LabeledText \"Country\" \"SWEDEN\"\n        let! orgNo      = LabeledText \"Org no\"  \"\"\n        let! mva        =\n            // In Norway a company needs to have an MVA number in addition to the OrgNo\n            if country = \"NORWAY\" then\n                LabeledText \"MVA\" \"\"\n            else\n                Formlet.Return \"N/A\"\n\n        return country, orgNo, mva\n    }\n```\n\nIf country is \"NORWAY\" the MVA input textbox is shown, otherwise not.\n\nA slightly more interesting example involves the Option input:\n\n```fsharp\nlet empty =\n    formlet {\n        return \"\", None\n    }\n\nlet sweden =\n    formlet {\n        let! orgNo = LabeledText \"Org no\" \"\"\n        return orgNo, None\n    }\n\nlet norway =\n    formlet {\n        let! orgNo  = LabeledText \"Org no\" \"\"\n        let! mva    = LabeledText \"MVA\" \"\"\n        return orgNo, Some mva\n    }\n\nlet companyInfo =\n    let options = LabeledOption \"Country\" empty [|\"Sweden\", sweden; \"Norway\", norway|]\n    formlet {\n        let! name       = LabeledText   \"Name\"      \"\"\n        // The user selects an option that evaluates into a formlet\n        let! country    = options\n        // This invokes the selected formlet presenting the user a different\n        // form depending on the selected option\n        // This is just mind-boggling cool and succinct\n        let! orgNo, mva = country\n        return name, orgNo, mva\n    }\n    |\u003e Enhance.WithLegend \"Company info\"\n```\n\n\n\nExtensibility\n-------------\n\nIt's very important to have an extensibility story and FSharp.Client.Formlet is designed to not be locked into to a special platform.\n\nObviously the various adaptations are tied to a platform but the core is agnostic.\n\nIn addition, it shall be possible for a user of FSharp.Client.Formlet to extend with custom formlets that combines like the other formlets.\n\nIt's preferable if creating a formlet isn't overly hard.\n\nExample of the basic text input formlet\n---------------------------------------\n\n```fsharp\n/// The visual element of the text input formlet\ntype InputTextElement(initialText : string) as this =\n    inherit TextBox()\n\n    let mutable text                = initialText\n\n    do\n        this.Text   \u003c- initialText\n        this.Margin \u003c- DefaultMargin\n\n    member val ChangeNotifier = EmptyChangeNotification with get, set\n\n    override this.OnLostFocus(e) =\n        base.OnLostFocus(e)\n\n        if text \u003c\u003e this.Text then\n            text \u003c- this.Text\n\n            this.ChangeNotifier ()\n\n/// The text input Formlet\nlet Text initialText : Formlet\u003cFormletContext, UIElement, string\u003e =\n    let eval (fc,cl,ft : FormletTree\u003cUIElement\u003e) =\n        let e =\n            match ft with\n            | Element (:? InputTextElement as e) -\u003e e\n            | _ -\u003e\n                InputTextElement(initialText)\n        e.ChangeNotifier \u003c- cl\n        (FormletResult.Success e.Text), Element (e :\u003e UIElement)\n\n    FormletMonad.New eval\n\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrange%2Fclient.formlet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrange%2Fclient.formlet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrange%2Fclient.formlet/lists"}