HTML markup
It's time to replace plain text placeholders in containers with meaningful content.
First, define h2
in View
module to output HTML header of level 2:
let h2 s = tag "h2" [] (text s)
Full name: CDocument.h2
and replace text
with a new h2
in each of the 4 containers.
We'd like the "/store" route to output hyperlinks to all genres in our Music Store.
Let's add a helper function in Path
module, that will be responsible for formatting HTTP urls with a key-value parameter:
let withParam (key,value) path = sprintf "%s?%s=%s" path key value
Full name: CDocument.withParam
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
The withParam
function takes a tuple (key,value)
as its first argument, path
as the second and returns a properly formatted url.
A tuple (or a pair) is a widely used structure in F#. It allows us to group two values into one in an easy manner.
Syntax for creating a tuple is as follows: (item1, item2)
- this might look like a standard parameter passing in many other languages including C#.
Follow this link to learn more about tuples.
Add also a string key for the url parameter "/store/browse" in Path.Store
module:
module Store = let overview = "/store" let browse = "/store/browse"
from CDocument
Full name: CDocument.Store.overview
Full name: CDocument.Store.browse
We'll use it in App
module:
let browse = request (fun r -> match r.queryParam Path.Store.browseKey with
Full name: CDocument.browse
Now, add the following for working with the unordered list (ul
) and list item (li
) elements in HTML:
let ul xml = tag "ul" [] (flatten xml) let li = tag "li" []
Full name: CDocument.ul
Full name: CDocument.li
flatten
takes a list of Xml
and "flattens" it into a single Xml
object model.
The actual container for store
can now look like the following:
let store genres = [ h2 "Browse Genres" p [ text (sprintf "Select from %d genres:" (List.length genres)) ] ul [ for g in genres -> li (aHref (Path.Store.browse |> Path.withParam (Path.Store.browseKey, g)) (text g)) ] ]
Full name: CDocument.store
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
module List
from Microsoft.FSharp.Collections
--------------------
type List<'T> =
| ( [] )
| ( :: ) of Head: 'T * Tail: 'T list
interface IEnumerable
interface IEnumerable<'T>
member GetSlice : startIndex:int option * endIndex:int option -> 'T list
member Head : 'T
member IsEmpty : bool
member Item : index:int -> 'T with get
member Length : int
member Tail : 'T list
static member Cons : head:'T * tail:'T list -> 'T list
static member Empty : 'T list
Full name: Microsoft.FSharp.Collections.List<_>
Full name: Microsoft.FSharp.Collections.List.length
Things worth commenting in the above snippet:
store
now takes a list of genres (again the type is inferred by the compiler)- the
[ for g in genres -> ... ]
syntax is known as "list comprehension". Here we map every genre string fromgenres
to a list item aHref
inside list item points to thePath.Store.browse
url with "genre" parameter - we use thePath.withParam
function defined earlier
To use View.store
from App
module, let's simply pass a hardcoded list for genres
like following:
path Path.Store.overview >=> html (View.store ["Rock"; "Disco"; "Pop"])
GitHub commit: 4116ee213ae519ad745f5882ba9eb62e1d4fea4f
Files changed:
- App.fs (modified)
- Path.fs (modified)
- View.fs (modified)