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:

View.fs

   6: 
let h2 s = tag "h2" [] [Text s]

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:

Path.fs

   5: 
let withParam (key,value) path = sprintf "%s?%s=%s" path key value

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:

Path.fs

  14: 
let browseKey = "genre"

We'll use it in App module:

App.fs

  13: 
  14: 
  15: 
  16: 
  17: 
let browse = request (fun r -> match r.queryParam Path.Store.browseKey with | Choice1Of2 genre -> html (View.browse genre) | Choice2Of2 msg -> BAD_REQUEST msg)

Now, add the following for working with the unordered list (ul) and list item (li) elements in HTML:

View.fs

   7: 
   8: 
let ul nodes = tag "ul" [] nodes let li = tag "li" []

The actual container for store can now look like the following:

View.fs

  14: 
  15: 
  16: 
  17: 
  18: 
  19: 
  20: 
  21: 
  22: 
  23: 
  24: 
  25: 
  26: 
let store genres = [ h2 "Browse Genres" p [] [ Text (sprintf "Select from %d genres:" (List.length genres)) ] ul [ for genre in genres -> let url = Path.Store.browse |> Path.withParam (Path.Store.browseKey, genre) li [ a url [] [ Text genre ] ] ] ]

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 from genres to a list item
  • aHref inside list item points to the Path.Store.browse url with "genre" parameter - we use the Path.withParam function defined earlier

To use View.store from App module, let's simply pass a hardcoded list for genres like following:

App.fs

  22: 
path Path.Store.overview >=> html (View.store ["Rock"; "Disco"; "Pop"])
type IntPath = PrintfFormat<(int -> string),unit,string,string,int>

Full name: SuaveMusicStore.Path.IntPath
Multiple items
type PrintfFormat<'Printer,'State,'Residue,'Result> =
new : value:string -> PrintfFormat<'Printer,'State,'Residue,'Result>
member Value : string

Full name: Microsoft.FSharp.Core.PrintfFormat<_,_,_,_>

--------------------
type PrintfFormat<'Printer,'State,'Residue,'Result,'Tuple> =
inherit PrintfFormat<'Printer,'State,'Residue,'Result>
new : value:string -> PrintfFormat<'Printer,'State,'Residue,'Result,'Tuple>

Full name: Microsoft.FSharp.Core.PrintfFormat<_,_,_,_,_>

--------------------
new : value:string -> PrintfFormat<'Printer,'State,'Residue,'Result>

--------------------
new : value:string -> PrintfFormat<'Printer,'State,'Residue,'Result,'Tuple>
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
val withParam : key:string * value:string -> path:string -> string

Full name: SuaveMusicStore.Path.withParam
val key : string
val value : string
val path : string
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val home : string

Full name: SuaveMusicStore.Path.home
module Store

from SuaveMusicStore.Path
val overview : string

Full name: SuaveMusicStore.Path.Store.overview
val browse : string

Full name: SuaveMusicStore.Path.Store.browse
val details : IntPath

Full name: SuaveMusicStore.Path.Store.details
val browseKey : string

Full name: SuaveMusicStore.Path.Store.browseKey
namespace Suave
module Html

from Suave
val cssLink : href:string -> Node

Full name: SuaveMusicStore.View.cssLink
val href : string
val link : attr:Attribute list -> Node

Full name: Suave.Html.link
val h2 : s:string -> Node

Full name: SuaveMusicStore.View.h2
val s : string
val tag : tag:string -> attr:Attribute list -> contents:Node list -> Node

Full name: Suave.Html.tag
union case Node.Text: string -> Node
val ul : nodes:Node list -> Node

Full name: SuaveMusicStore.View.ul
val nodes : Node list
val li : (Node list -> Node)

Full name: SuaveMusicStore.View.li
val home : Node list

Full name: SuaveMusicStore.View.home
val store : genres:string list -> Node list

Full name: SuaveMusicStore.View.store
val genres : string list
val p : (Attribute list -> Node list -> Node)

Full name: Suave.Html.p
Multiple items
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<_>
val length : list:'T list -> int

Full name: Microsoft.FSharp.Collections.List.length
val genre : string
val url : string
module Path

from SuaveMusicStore
val a : href:string -> attr:Attribute list -> (Node list -> Node)

Full name: Suave.Html.a
val browse : genre:string -> Node list

Full name: SuaveMusicStore.View.browse
val details : id:int -> Node list

Full name: SuaveMusicStore.View.details
val id : int
val index : container:Node list -> string

Full name: SuaveMusicStore.View.index
val container : Node list
val html : (Attribute list -> Node list -> Node)

Full name: Suave.Html.html
val head : (Attribute list -> Node list -> Node)

Full name: Suave.Html.head
val title : attr:Attribute list -> s:string -> Node

Full name: Suave.Html.title
val body : (Attribute list -> Node list -> Node)

Full name: Suave.Html.body
val div : (Attribute list -> Node list -> Node)

Full name: Suave.Html.div
val htmlToString : node:Node -> string

Full name: Suave.Html.htmlToString
module App

from SuaveMusicStore
module Filters

from Suave
module Operators

from Suave
module RequestErrors

from Suave
module Successful

from Suave
module Web

from Suave
val html : container:Html.Node list -> WebPart

Full name: SuaveMusicStore.App.html
val container : Html.Node list
val OK : body:string -> WebPart

Full name: Suave.Successful.OK
module View

from SuaveMusicStore
val index : container:Html.Node list -> string

Full name: SuaveMusicStore.View.index
val browse : (HttpContext -> Async<HttpContext option>)

Full name: SuaveMusicStore.App.browse
val request : apply:(HttpRequest -> HttpContext -> 'a) -> context:HttpContext -> 'a

Full name: Suave.Http.request
val r : HttpRequest
member HttpRequest.queryParam : key:string -> Choice<string,string>
union case Choice.Choice1Of2: 'T1 -> Choice<'T1,'T2>
val browse : genre:string -> Html.Node list

Full name: SuaveMusicStore.View.browse
union case Choice.Choice2Of2: 'T2 -> Choice<'T1,'T2>
val msg : string
val BAD_REQUEST : body:string -> WebPart

Full name: Suave.RequestErrors.BAD_REQUEST
val webPart : WebPart<HttpContext>

Full name: SuaveMusicStore.App.webPart
val choose : options:WebPart<'a> list -> WebPart<'a>

Full name: Suave.WebPart.choose
val path : pathAfterDomain:string -> WebPart

Full name: Suave.Filters.path
val home : Html.Node list

Full name: SuaveMusicStore.View.home
val store : genres:string list -> Html.Node list

Full name: SuaveMusicStore.View.store
val pathScan : pf:PrintfFormat<'a,'b,'c,'d,'t> -> h:('t -> WebPart) -> WebPart

Full name: Suave.Filters.pathScan
val details : Path.IntPath

Full name: SuaveMusicStore.Path.Store.details
val details : id:int -> Html.Node list

Full name: SuaveMusicStore.View.details
val pathRegex : pathAfterDomainRegex:string -> WebPart

Full name: Suave.Filters.pathRegex
module Files

from Suave
val browseHome : WebPart

Full name: Suave.Files.browseHome
val startWebServer : config:SuaveConfig -> webpart:WebPart -> unit

Full name: Suave.Web.startWebServer
val defaultConfig : SuaveConfig

Full name: Suave.Web.defaultConfig

Show code from this section on GitHub

results matching ""

    No results matching ""