{"id":13658516,"url":"https://github.com/websharper-samples/Counter","last_synced_at":"2025-04-24T11:31:52.767Z","repository":{"id":81964696,"uuid":"127162463","full_name":"websharper-samples/Counter","owner":"websharper-samples","description":"A simple counter with the Model-View-Update pattern","archived":false,"fork":false,"pushed_at":"2020-06-02T22:20:08.000Z","size":60,"stargazers_count":4,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-08-02T05:08:53.492Z","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":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/websharper-samples.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-28T15:39:31.000Z","updated_at":"2024-03-07T23:54:35.000Z","dependencies_parsed_at":null,"dependency_job_id":"38d07aaa-6cb0-48f0-9383-79df42f7ec48","html_url":"https://github.com/websharper-samples/Counter","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/websharper-samples%2FCounter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/websharper-samples%2FCounter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/websharper-samples%2FCounter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/websharper-samples%2FCounter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/websharper-samples","download_url":"https://codeload.github.com/websharper-samples/Counter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223952514,"owners_count":17230901,"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","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":"2024-08-02T05:01:00.256Z","updated_at":"2024-11-10T12:30:20.829Z","avatar_url":"https://github.com/websharper-samples.png","language":"F#","readme":"# Part 2. Model-View-Update (MVU) style apps with WebSharper UI [![Build status](https://ci.appveyor.com/api/projects/status/7g2x8efv5xcty87c?svg=true)](https://ci.appveyor.com/project/IntelliFactory/counter)\n\n\u003e Written by Adam Granicz, IntelliFactory.\n\nIn this tutorial, you will learn about using WebSharper UI to implement a simple Model-View-Update (MVU) application pattern, similar to the [Elm architecture](https://guide.elm-lang.org/architecture/). In subsequent tutorials, you will learn about **enhancing this pattern to a full-scale application development architecture (The WebSharper Architecture)** that has superior performance and sufficient flexibility to implement any type of web application. Be sure to read the [First Steps: Using HTML templates, accessing form values, and wiring events](https://github.com/websharper-samples/LoginWithBulma) tutorial to get a solid grip on some of the WebSharper fundamentals, these will come handy while reading this tutorial.\n\n## What you will learn and a quick recap\n\n 1. **Using a Model-View-Update (MVU)-like approach** with WebSharper UI. As you will see, WebSharper UI can implement a number of different styles of reactive UI programming easily.\n\n### You already learned in the [previous tutorial](https://github.com/websharper-samples/LoginWithBulma) how to:\n\n2. **Create WebSharper SPA projects**\n\n    In the parent folder of your choice, type\n    ```\n    dotnet new websharper-spa -lang f# -n YourApp\n    ```\n 3. **Edit the key files in your SPA project**\n\n    For simple SPAs, these files will be:\n    -   **`wwwroot\\index.html`** - Your main SPA - this is the file you open to run your app\n    -   **`Client.fs`** - The logic for your SPA - this is where your F# code will be\n\n4. **Use HTML templates**\n\n    WebSharper UI provides an advanced templating engine with dynamic code generation both for C# and F#. Always consider using external HTML templates instead of inlined HTML combinators to speed up your developer workflow. Templates allow you to make changes to your presentation layer without having to compile your project.\n    ```fsharp\n    open WebSharper.UI.Templating\n    \n    type MySPA = Template\u003c\"wwwroot/index.html\", ClientLoad.FromDocument\u003e\n\n    MySPA()\n        ...\n        .Bind()\n    ```\n    You can use templates everywhere you need `Doc` values by calling `.Doc()`, or you can use `.Bind()` to apply your template instantiations to your master document (typically your main SPA page.)\n\n5. **Wire events**\n    * In `wwwroot/index.html`, mark input controls with `ws-on[xxx]` for each event `xxx` you want to bind:\n\n        ```html\n       \u003cbutton ws-onclick=\"OnSubmit\"\u003eClick me\u003c/button\u003e\n        ```\n\n6. **Read and write the values of HTML input controls**\n    * In `wwwroot/index.html`, mark input controls with `ws-var=\"xxx\"`:\n\n        ```html\n       \u003cinput ws-var=\"Name\" ws-onkeydown=\"OnEdit\" type=\"text\" /\u003e\n        ```\n\n    * To read form values, in `Client.fs` access as follows:\n        ```fsharp\n        open WebSharper.UI\n        open WebSharper.UI.Client\n        ...\n        MySPA()\n            .OnSubmit(fun e -\u003e ... e.Vars.Name ...)\n        ```\n    * To write/update form values, in `Client.fs` access as follows:\n        ```fsharp\n        open WebSharper.UI.Notation\n        ...\n        MySPA()\n            .OnSubmit(fun e -\u003e\n                e.Vars.Name := \"\"\n            )\n        ```\n\n7. **Use reactive variables**\n    * Create a reactive variable:\n      ```fsharp\n      let v = Var.Create 0\n      ```\n    * Read the value of a reactive variable:\n      ```fsharp\n      v.Value\n      ```\n    * Set the value of a reactive variable:\n      ```fsharp\n      open WebSharper.UI.Notation\n      ...\n      v := !v + 1\n      ```\n\n## Prerequisites\n\nTo get the most out of this tutorial, make sure you have installed:\n\n* .NET Core 2.0+ and ASP.NET Core\n* the  [latest WebSharper templates](http://websharper.com/downloads). This is not strictly required if you clone the GitHub project directly.\n* Visual Studio Code with  [Ionide](http://ionide.io/)  or Visual Studio 2017 \n\n## Model-View-Update (MVU) - the basic Elm architecture\n\n[Elm](https://guide.elm-lang.org) defines a simple pattern for building web applications, founded on the clean separation between:\n\n * **Model** - the state\n * **View** - the presentation (HTML) of the state\n * **Update** - describes how to update the state\n\nThe basic architecture (without effects) uses two key functions:\n\n```fsharp\nval update: 'Msg -\u003e 'Model -\u003e 'Model\nval view: 'Model -\u003e 'View\n```\nwhere `'Msg` values encode various messages that take the model forward. Both `update` and `view` are assumed to be pure, i.e. `update` computes new model values based only on the message, and `view` computes the HTML representation without changing the model (thus both are without side effects), and both produce the same value for the same input.\n\n## Buttons - keeping a counter\n\nNow you will implement the [buttons](https://guide.elm-lang.org/architecture/user_input/buttons.html) example from the Elm documentation by mimicing the above MVU pattern. You can [try the WebSharper UI version online](http://try.websharper.com/snippet/adam.granicz/0000Jr) to see how it works. Here we do:\n\n### a) Create a new SPA called Counter\n\n```\ndotnet new websharper-spa -lang f# -n Counter\n```\n\n### b) `wwwroot\\index.html`\n\nAs a best practice, always consider keeping all of your SPA markup (including templates and subtemplates, if any) in the master HTML file. So replace `wwwroot\\index.html` with:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n     \u003ctitle\u003eCounter\u003c/title\u003e\n     \u003cmeta charset=\"utf-8\" /\u003e\n     \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n     \u003cbutton ws-onclick=\"OnDecrement\"\u003e-\u003c/button\u003e\n     \u003cdiv ws-hole=\"Counter\"\u003e\u003c/div\u003e\n     \u003cbutton ws-onclick=\"OnIncrement\"\u003e+\u003c/button\u003e\n     \u003cscript type=\"text/javascript\" src=\"Content/Counter.min.js\"\u003e\u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\nHere, you added a `click` event handler to the increment/decrement buttons, and a `Counter` placeholder for displaying the current value. (The reference to `Content/Counter.head.js` - a file WebSharper generates for SPAs - was removed, since no JQuery is needed for this app and there are no other dependencies.)\n\n### c) `Client.fs`\n\nThe main application looks quite a lot like the Elm version:\n\n```fsharp\nnamespace Counter\n\nopen WebSharper\nopen WebSharper.UI\nopen WebSharper.UI.Notation\nopen WebSharper.UI.Templating\n\n[\u003cJavaScript\u003e]\nmodule Client =\n    type MySPA = Template\u003c\"wwwroot/index.html\", ClientLoad.FromDocument\u003e\n\n    type Model = int\n\n    type Message =\n        | Increment\n        | Decrement\n\n    let update msg model  =\n        match msg with\n        | Message.Increment -\u003e\n            model+1\n        | Message.Decrement -\u003e\n            model-1\n\n    let init = 0\n\n    let view =\n        let vmodel = Var.Create init\n        let handle msg =\n            let model = update msg vmodel.Value\n            vmodel := model\n        MySPA()\n            .OnIncrement(fun _ -\u003e handle Message.Increment)\n            .OnDecrement(fun _ -\u003e handle Message.Decrement)\n            .Counter(V(string vmodel.V))\n            .Bind()\n        fun model -\u003e\n            vmodel := model\n\n    [\u003cSPAEntryPoint\u003e]\n    let Main () =\n        view init\n```\n\n### The `view` function\n\nYou probably spotted that `view` looks slightly more complicated than just \"take the master HTML file, bind the button event handlers, and reflect the counter as a text label,\" and you are right.\n\nFirst, `view` encodes an **entire \"runtime\"**: it gives means to dispatch messages (`handle`), updates the model upon receiving those messages, and rebinds the changes onto the UI. Yet it manages to do all that in only four lines of code because the real complexity: binding the model to the UI is **done automatically by the UI layer** (via templating, here).\n\nSecond, this is possible because your presentation layer describes reactive content and is expressed in terms of `vmodel` and not on `model`, thus using **`Var\u003cModel\u003e`** values and not `Model` ones. (This distinction will become important in the upcoming tutorials as we work towards establishing a more comprehensive application pattern.)  For instance, it uses `V(string vmodel.V)` to take the current value of the model (`vmodel.V`, an integer), convert it to a string (`string vmodel.V`), and convert the result back to a view (`V(string vmodel.V)`) to bind it the `Counter` placeholder.\n\nThird, `MySPA().xxx(...).Bind()` lights the reactive machinery on the document the first time it runs, as a \"side-effect\". Therefore, you fill in your placeholders and bind your events, and seal things off with `.Bind()` **in the closure of `view`**.  Another way to think about this is that `view` is not constructing the presentation layer, but instead it **sets up and binds the reactive pieces and the main logic** onto that presentation layer (which is supplied by the template.)\n\nAnd last, the main job of `view` ends up simply **updating the reactive model** underneath the bound UI (by simply doing `vmodel := model`), and thus its return value is `unit`. Note, however, that you don't call `view` more than once (unlike in Elm) because the first call already sets everything up, but nothing keeps you from doing it. For instance, you might as well start with:\n\n```fsharp\n[\u003cSPAEntryPoint\u003e]\nlet Main () =\n    view 0\n    view 1\n    view 2\n    view 3\n    view 4\n    view 5\n```\n\n\n## Conclusion\n\nIn this tutorial, you saw how you can use WebSharper UI to build a Model-View-Update (MVU)-like pattern to develop simple web applications. Your model and message type, and your `update` function were exactly as you would expect, while your `view` function had a couple important differences that reflect the capabilities of the underlying WebSharper UI reactive layer.\n\nOne key thing to remember is that WebSharper UI applications **don't require \"diffing\" between two virtual DOM representations** as employed by popular libraries like React, but instead, changes propagate through the dataflow graph constructed from the reactive embeddings and always yield the minimal number of updates right where necessary. This also means that your WebSharper UI applications **don't depend on React or other reactive libraries**.\n\n\u003e Note: you can work with React and React components through direct bindings, if you prefer.\n\nThere is a whole lot more to see, so stay tuned for more.\n\n## Source code and try the app\n\nYou can fork [this SPA project](https://github.com/websharper-samples/Counter) via GitHub. You can also [try out a slightly adapted version](http://try.websharper.com/snippet/adam.granicz/0000Jr) live on Try WebSharper.\n\nHappy coding!\n","funding_links":[],"categories":["Examples"],"sub_categories":["Applications"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebsharper-samples%2FCounter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebsharper-samples%2FCounter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebsharper-samples%2FCounter/lists"}