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/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 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 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 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 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 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 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 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 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 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 8/10/22

So, I wrote a book

Join me as I share the story of writing a book on F#. Discover the challenges, insights, and triumphs along the way.

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 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 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 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 9/17/21

How to gather data from Miro

Learn how to gather data from Miro boards with this step-by-step guide. Streamline your data collection for deeper insights.

Blog 1/29/20

Tracing IO in .NET Core

Learn how we leverage OpenTelemetry for efficient tracing of IO operations in .NET Core applications, enhancing performance and monitoring.

Blog 3/17/22

Using NLP libraries for post-processing

Learn how to analyse sticky notes in miro from event stormings and how this analysis can be carried out with the help of the spaCy library.