Introduction to Web Programming in F# with Giraffe — Part 3

Introduction

In the last post, we created a simple API for managing a Todo list. In this post, we are going to start our journey into HTML views with the Giraffe View Engine.

If you haven't already done so, read the previous posts in this series.

 

Getting Started

We are going to use the project we created for the last post and add to it.

 

Running the Sample Code

In the Terminal, type the following to run the project:

dotnet run

Go to your browser and type in the following Url:

https://localhost:5001

You should see some text.

Now try the following Uri and you should see some Json returned:

https://localhost:5001/api

Our Task

We are going to create a simple HTML view of a Todo list. In this post, we will concentrate on getting things on the screen. We will wire in to the backend in the next post.

Rather than rely on my HTML/CSS skills, we are going to start with a pre-built sample: The ToDo list example from w3schools:

https://www.w3schools.com/howto/howto_js_todolist.asp

Configuration

Add a new folder to the project called WebRoot and add a couple of files to the folder: main.js and main.css.

Open the .fsproj file and add the following snippet:

PreserveNewest

The final step to enable these files to be used is to edit the main function:

 

[] let main _ = let contentRoot = Directory.GetCurrentDirectory() let webRoot = Path.Combine(contentRoot, "WebRoot") Host.CreateDefaultBuilder() .ConfigureWebHostDefaults(fun webHost -> webHost .UseWebRoot(webRoot) .Configure(configureApp) .ConfigureServices(configureServices) |> ignore) .Build() .Run() 0

We have added a couple of lines to tell the code where the WebRoot folder is and have passed that path to a helper function UseWebRoot.

That's the end of the configuration but we need to copy the code from the w3schools site for the css and javascript to our files.

/* Include the padding and border in an element's total width and height */ * { box-sizing: border-box; } /* Remove margins and padding from the list */ ul { margin: 0; padding: 0; } /* Style the list items */ ul li { cursor: pointer; position: relative; padding: 12px 8px 12px 40px; background: #eee; font-size: 18px; transition: 0.2s; /* make the list items unselectable */ -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } /* Set all odd list items to a different color (zebra-stripes) */ ul li:nth-child(odd) { background: #f9f9f9; } /* Darker background-color on hover */ ul li:hover { background: #ddd; } /* When clicked on, add a background color and strike out text */ ul li.checked { background: #888; color: #fff; text-decoration: line-through; } /* Add a "checked" mark when clicked on */ ul li.checked::before { content: ''; position: absolute; border-color: #fff; border-style: solid; border-width: 0 2px 2px 0; top: 10px; left: 16px; transform: rotate(45deg); height: 15px; width: 7px; } /* Style the close button */ .close { position: absolute; right: 0; top: 0; padding: 12px 16px 12px 16px; } .close:hover { background-color: #f44336; color: white; } /* Style the header */ .header { background-color: #f44336; padding: 30px 40px; color: white; text-align: center; } /* Clear floats after the header */ .header:after { content: ""; display: table; clear: both; } /* Style the input */ input { margin: 0; border: none; border-radius: 0; width: 75%; padding: 10px; float: left; font-size: 16px; } /* Style the "Add" button */ .addBtn { padding: 10px; width: 25%; background: #d9d9d9; color: #555; float: left; text-align: center; font-size: 16px; cursor: pointer; transition: 0.3s; border-radius: 0; } .addBtn:hover { background-color: #bbb; } // Create a "close" button and append it to each list item var myNodelist = document.getElementsByTagName("LI"); var i; for (i = 0; i < myNodelist.length; i++) { var span = document.createElement("SPAN"); var txt = document.createTextNode("\u00D7"); span.className = "close"; span.appendChild(txt); myNodelist[i].appendChild(span); } // Click on a close button to hide the current list item var close = document.getElementsByClassName("close"); var i; for (i = 0; i < close.length; i++) { close[i].onclick = function() { var div = this.parentElement; div.style.display = "none"; } } // Add a "checked" symbol when clicking on a list item var list = document.querySelector('ul'); list.addEventListener('click', function(ev) { if (ev.target.tagName === 'LI') { ev.target.classList.toggle('checked'); } }, false); // Create a new list item when clicking on the "Add" button function newElement() { var li = document.createElement("li"); var inputValue = document.getElementById("myInput").value; var t = document.createTextNode(inputValue); li.appendChild(t); if (inputValue === '') { alert("You must write something!"); } else { document.getElementById("myUL").appendChild(li); } document.getElementById("myInput").value = ""; var span = document.createElement("SPAN"); var txt = document.createTextNode("\u00D7"); span.className = "close"; span.appendChild(txt); li.appendChild(span); for (i = 0; i < close.length; i++) { close[i].onclick = function() { var div = this.parentElement; div.style.display = "none"; } } }

Converting HTML To Giraffe ViewEngine

If you've never seen a DSL like the Giraffe View Engine, it is going to be a bit of a shock but bear with it because it really makes sense!

Let's start with a basic HTML page:

 

My Title

If we convert it to Giraffe View Engine format, we get:

// string -> XmlNode list -> XmlNode let createPage msg content = html [] [ head [] [ title [] [ Text msg ] link [ _rel "stylesheet"; _href "main.css" ] ] body [] content ]

Most tags have two lists, one for styling and one for content. Some tags, like input, only take the style list. The Giraffe ViewEngine is a Domain Specific Language (DSL) that generates XHTML from F#.

To create the original HTML view, we would call:

createPage "My Title" []

This approach is very different to most view engines which rely on search and replace but are primarily still HTML. The primary advantage of the Giraffe View Engine approach is that you get full type safety when generating views and can use the full power of the F# language.

ToDo Snippet

Our ToDo HTML snippet below has to be converted into Giraffe ViewEngine code:

My To Do List

Add

  • Hit the gym
  • Pay bills
  • Meet George
  • Buy eggs
  • Read a book
  • Organize office

Let's create a new function to generate our ToDo HTML snippet:

let todoView = createPage "My ToDo App" [ div [ _id "myDIV"; _class "header" ] [ h2 [] [ Text "My To Do List" ] input [ _type "text"; _id "myInput"; _placeholder "Title..." ] span [ _class "addBtn"; _onclick "newElement()" ] [ Text "Add" ] ] ul [ _id "myUL" ] [ li [] [ Text "Hit the gym" ] li [ _class "checked" ] [ Text "Pay bills" ] li [] [ Text "Meet George" ] li [] [ Text "Buy eggs" ] li [] [ Text "Read a book" ] li [] [ Text "Organize office" ] ] script [ _src "main.js"; _type "text/javascript" ] [] ]

We are nearly ready to run: We only need to tell the router about our new handler. Change the first route in webApp from:

GET >=> route "/" >=> htmlView indexHandler to: GET >=> route "/" >=> htmlView todoView

If you now run the app using dotnet run and click on the link (mine goes to https://localhost:5001), you will see the ToDo app.

Next Stage

Rather than hard code the list of ToDos in the view generation, we can load it in on the fly:

let todoList = [ { Id = Guid.NewGuid(); Description = "Hit the gym"; Created = DateTime.UtcNow; IsCompleted = false } { Id = Guid.NewGuid(); Description = "Pay bills"; Created = DateTime.UtcNow; IsCompleted = true } { Id = Guid.NewGuid(); Description = "Meet George"; Created = DateTime.UtcNow; IsCompleted = false } { Id = Guid.NewGuid(); Description = "Buy eggs"; Created = DateTime.UtcNow; IsCompleted = false } { Id = Guid.NewGuid(); Description = "Read a book"; Created = DateTime.UtcNow; IsCompleted = false } { Id = Guid.NewGuid(); Description = "Organize office"; Created = DateTime.UtcNow; IsCompleted = true } ] // Todo -> XmlNode let showListItem (todo:Todo) = let style = if todo.IsCompleted then [ _class "checked" ] else [] li style [ Text todo.Description ] let todoView = createPage "My ToDo App" [ div [ _id "myDIV"; _class "header" ] [ h2 [] [ Text "My ToDo List" ] input [ _type "text"; _id "myInput"; _placeholder "Title..." ] span [ _class "addBtn"; _onclick "newElement()" ] [ Text "Add" ] ] ul [ _id "myUL" ] [ for todo in todoList do showListItem todo ] script [ _src "main.js"; _type "text/javascript" ] [] ]

We created a simple list of ToDos, a small helper function to style the list item, and used a list comprehension to generate the items displayed in the list.

The code from this post can be found here:

https://gist.github.com/ianrussellsoftwarepark/13569e9a930086d69082481dabebb1f8

I encourage you to have a look at the documentation and source code for the Giraffe View Engine.

https://github.com/giraffe-fsharp/Giraffe.ViewEngine#documentation

In the next post, we will investigate tying the frontend and backend together.

If you like the Giraffe View Engine, but don't like writing JavaScript, you should investigate the SAFE Stack where everything is F#.

https://safe-stack.github.io/

Summary

I hope that you found this introduction to HTML views in F# with Giraffe and the Giraffe View Engine useful and interesting. We have only scratched the surface of what is possible with Giraffe for creating web pages.

In the next post we will start to extend the functionality we covered in this post to wire up the frontend and backend.

If you have any comments on this series of posts or suggestions for new ones, send me a tweet (@ijrussell) and let me know.

Blog 3/10/21

Introduction to Web Programming in F# with Giraffe – Part 1

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 3/11/21

Introduction to Web Programming in F# with Giraffe – Part 2

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 9/15/22

Introduction to Functional Programming in F# – Part 3

Dive into F# data structures and pattern matching. Simplify code and enhance functionality with these powerful features.

Blog 9/13/22

Introduction to Functional Programming in F# – Part 2

Explore functions, types, and modules in F#. Enhance your skills with practical examples and insights in this detailed guide.

Blog 10/1/22

Introduction to Functional Programming in F# – Part 4

Unlock F# collections and pipelines. Manage data efficiently and streamline your functional programming workflow with these powerful tools.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 6

Learn error handling in F# with option types. Improve code reliability using F#'s powerful error-handling techniques.

Blog 10/11/22

Introduction to Functional Programming in F# – Part 5

Master F# asynchronous workflows and parallelism. Enhance application performance with advanced functional programming techniques.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 7

Explore LINQ and query expressions in F#. Simplify data manipulation and enhance your functional programming skills with this guide.

Blog 3/22/23

Introduction to Functional Programming in F# – Part 8

Discover Units of Measure and Type Providers in F#. Enhance data management and type safety in your applications with these powerful tools.

Blog 3/22/23

Introduction to Functional Programming in F# – Part 9

Explore Active Patterns and Computation Expressions in F#. Enhance code clarity and functionality with these advanced techniques.

Blog 5/17/23

Introduction to Functional Programming in F# – Part 10

Discover Agents and Mailboxes in F#. Build responsive applications using these powerful concurrency tools in functional programming.

Blog 7/12/23

Introduction to Functional Programming in F# – Part 11

Learn type inference and generic functions in F#. Boost efficiency and flexibility in your code with these essential programming concepts.

Blog 8/8/23

Introduction to Functional Programming in F# – Part 12

Explore reflection and meta-programming in F#. Learn how to dynamically manipulate code and enhance flexibility with advanced techniques.

Blog 5/18/22

Introduction to Functional Programming in F#

Dive into functional programming with F# in our introductory series. Learn how to solve real business problems using F#'s functional programming features. This first part covers setting up your environment, basic F# syntax, and implementing a simple use case. Perfect for developers looking to enhance their skills in functional programming.

Blog 11/30/22

Introduction to Partial Function Application in F#

Partial Function Application is one of the core functional programming concepts that everyone should understand as it is widely used in most F# codebases.In this post I will introduce you to the grace and power of partial application. We will start with tupled arguments that most devs will recognise and then move onto curried arguments that allow us to use partial application.

Blog

Intro to Functional Programming in F# – Table of Content

Start learning functional programming in F# through practical examples. This blog series covers function composition, collections, recursion, computation expressions, and more.

Blog 11/24/23

Part 3: How to Analyze a Database File with GPT-3.5

In this blog, we'll explore the proper usage of data analysis with ChatGPT and how you can analyze and visualize data from a SQLite database to help you make the most of your data.

Blog

In-depth introduction to flexbox

Master CSS Flexbox: learn properties like flex-grow, flex-shrink, and alignment to create responsive, efficient layouts without complex hacks or extra code.

Blog

Part 3: TIMETOACT Logistics Hackathon

Extend logistics simulations with a speed model: predict travel times using historical data, Python, and polynomial regression for realistic traffic insights.

Referenz

Introduction of Jira to Hamburger Hochbahn

The Hamburger Hochbahn AG controls the development of its new mobility platform "Switchh" via the Atlassian project management tool Jira – introduced, administered and hosted by the TIMETOACT GROUP.

Bleiben Sie mit dem TIMETOACT GROUP Newsletter auf dem Laufenden!