Manage albums
Let's start by adding manage to our View module:
let manage (albums : Db.AlbumDetails list) = [ h2 "Index" table [ yield tr [ for t in ["Artist";"Title";"Genre";"Price"] -> th [ text t ] ] for album in albums -> tr [ for t in [ truncate 25 album.Artist; truncate 25 album.Title; album.Genre; formatDec album.Price ] -> td [ text t ] ] ] ]
Full name: CDocument.manage
Full name: Microsoft.FSharp.Collections.list<_>
Full name: Microsoft.FSharp.Core.Operators.truncate
The view requires a few of new helper functions for table HTML markup:
let table x = tag "table" [] (flatten x) let th x = tag "th" [] (flatten x) let tr x = tag "tr" [] (flatten x) let td x = tag "td" [] (flatten x)
Full name: CDocument.table
Full name: CDocument.th
Full name: CDocument.tr
Full name: CDocument.td
as well as a truncate function that will ensure our cell content doesn't span over a maximum number of characters:
let truncate k (s : string) = if s.Length > k then s.Substring(0, k - 3) + "..." else s
Full name: CDocument.truncate
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
System.String.Substring(startIndex: int, length: int) : string
Remarks:
- our HTML table consists of first row (
tr) containing column headers (th) and a set of rows for each album with cells (td) to display specific values. - we used the
yieldkeyword for the first time. It is required here because we're using it in conjunction with thefor album in albums ->list comprehension syntax inside the same list. The rule of thumb is that whenever you use the list comprehension syntax, then you need theyieldkeyword for any other item not contained in the comprehension syntax. This might seem hard to remember, but don't worry - the compiler is helpful here and will issue a warning if you forget theyieldkeyword. - for the sake of saving a few keystrokes we used a nested list comprehension syntax to output
ths andtds. Again, it's just a matter of taste, and could be also solved by enumerating each element separately
We are going to need to fetch the list of all AlbumDetails from the database.
For this reason, let's create following query in Db module:
let getAlbumsDetails (ctx : DbContext) : AlbumDetails list = ctx.``[dbo].[AlbumDetails]`` |> Seq.toList
Full name: CDocument.getAlbumsDetails
Full name: Microsoft.FSharp.Collections.list<_>
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Seq.toList
Now we're ready to define an actual handler to display the list of albums.
Let's add a new sub-module to Path:
module Admin = let manage = "/admin/manage"
from CDocument
Full name: CDocument.Admin.manage
The Admin sub-module will contain all album management paths or routes if you will.
manage WebPart in App module can be implemented in following way:
let manage = warbler (fun _ -> Db.getContext() |> Db.getAlbumsDetails |> View.manage |> html)
Full name: CDocument.manage
and used in the main choose WebPart:
path Path.Admin.manage >=> manage
Don't forget about the warbler for manage WebPart - we don't use an parameters for this WebPart, so we need to prevent it's eager evaluation.
If you navigate to the "/admin/manage" url in the application now, you should be presented the grid with every album in the store.
GitHub commit: 3c1325de71a6e671f4a9121b1efadbab931d5cae
Files changed:
- App.fs (modified)
- Db.fs (modified)
- Path.fs (modified)
- View.fs (modified)