namespace ShoppingCart open IntelliFactory.Html open IntelliFactory.WebSharper open IntelliFactory.WebSharper.Sitelets type Action = | Home module EventHub = type Item = { ProductName : string Title: string Price: float } type CartItem = { Item: Item Quantity: int Order: int } type CartEvent = | AddItem of Item * int | AddOneItem of string | RemoveOneItem of string | RemoveItem of string | EmptyCart [] let ShoppingCartEvents = new Event () module Server = type CartFamily = { Title : string Products : CartProduct list } and CartProduct = { Title : string ProductName : string ImageSrc : string Price : float } /// Computes the families and the products inside them that are /// available for download. [] let ComputeFamiliesAndProducts () : CartFamily list = let T imageSrc (title, id, price) = { Title = title ProductName = id ImageSrc = imageSrc Price = price } let laptop prod = T "/images/laptop.png" prod let desktop prod = T "/images/desktop.png" prod let netbook prod = T "/images/netbook.png" prod [ { Title = "Laptops" Products = [ laptop ("Toshiba", "id1", 1299.0) laptop ("HP", "id2", 1499.0) laptop ("Dell", "id3", 1499.0) laptop ("Acer", "id4", 1499.0) ] } { Title = "Desktops" Products = [ desktop ("Gamer 1", "id11", 699.0) desktop ("Gamer 2", "id12", 799.0) desktop ("Office", "id13", 599.0) desktop ("Server", "id14", 1299.0) ] } { Title = "Netbooks" Products = [ netbook ("Entry", "id21", 799.0) netbook ("Medium", "id22", 899.0) netbook ("Cool", "id23", 699.0) netbook ("Speed-King", "id24", 999.0) ] } ] [] module Internals = let private Items = ref Map.empty let private Counter = ref 0 let GetItemsInCart () = async { return !Items } let NextCounter () = async { Counter := !Counter + 1 return !Counter } let SetItemsInCart items = Items := items [] module Client = open IntelliFactory.WebSharper.Html module WebControls = let Button label = Input [Attr.Type "button"; Attr.Value label; Width (label.Length*15 |> string)] /// Sends payment. [] let SendPayment items = () /// Displays all item categories and their items for sale. [] let ItemsToBuy () = let families = Server.ComputeFamiliesAndProducts () Div [Id "shopping-cart"] -< [ families |> List.map (fun family -> Div [Attr.Class "family"] -< [ H1 [Text family.Title] family.Products |> List.map (fun product -> let input = Input [Attr.Type "text"; Attr.Value "1"] Div [Attr.Class "product"] -< [ Img [Alt product.Title; Src product.ImageSrc] Div [ H1 [Text product.Title] P [ Code [Text ("$" + string product.Price)] ] -< [ Text " / item" ] P [ Text "Quantity:" ] -< [ input ] WebControls.Button "Add to cart" |>! OnClick (fun e args -> EventHub.CartEvent.AddItem ( { ProductName=product.ProductName Title=product.Title Price=product.Price }, (int input.Value)) |> EventHub.ShoppingCartEvents.Trigger ) ] ] ) |> fun products -> products @ [ Div [Attr.Style "clear:both;"] ] |> Div ] ) |> Div ] /// Displays the shopping cart. [] let ShoppingCart () = let contents = Div [] let updateCart () = async { let! itemsInCart = Internals.GetItemsInCart () contents.Clear () Div [Id "shopping-cart-contents"] -< [ H1 [Text "Shopping Cart"] Table [ itemsInCart |> Map.toList |> List.map snd |> List.sortBy (fun cartItem -> cartItem.Order) |> List.fold (fun (i, acc, sum) cartItem -> let alt = if i%2 = 1 then "alt " else "" (i+1, acc @ [ TR [Attr.Class alt] -< [ TD [Attr.Class "col1"] -< [ WebControls.Button "-" // What happens when we click "-"? |>! OnClick (fun e args -> cartItem.Item.ProductName |> EventHub.CartEvent.RemoveOneItem |> EventHub.ShoppingCartEvents.Trigger ) Div [Attr.Class "count"] -< [cartItem.Quantity |> string |> Text] WebControls.Button "+" // What happens when we click "+"? |>! OnClick (fun e args -> cartItem.Item.ProductName |> EventHub.CartEvent.AddOneItem |> EventHub.ShoppingCartEvents.Trigger ) WebControls.Button "X" // What happens when we click "Remove"? |>! OnClick (fun e args -> cartItem.Item.ProductName |> EventHub.CartEvent.RemoveItem |> EventHub.ShoppingCartEvents.Trigger ) Div [Attr.Class "title"] -< [Text cartItem.Item.Title] ] TD [Attr.Class "col2"] -< [ Code [ Text (string cartItem.Item.Price) ] ] ] ], sum + (float cartItem.Quantity)*cartItem.Item.Price) ) (0, [], 0.) |> fun (_, rows, sum) -> rows |> fun rows -> rows @ [ TR [ Hr [] ] TR [ TD [Text "Total:"] TD [Code [sum |> string |> Text]] ] ] |> TBody ] Div [Attr.Style "height:20px"] -< [] Div [ WebControls.Button "Checkout" |>! OnClick (fun e args -> JavaScript.Alert "Checkout!" // Process payment itemsInCart |> SendPayment // Empty shopping cart EventHub.CartEvent.EmptyCart |> EventHub.ShoppingCartEvents.Trigger ) WebControls.Button "Empty cart" |>! OnClick (fun e args -> EventHub.CartEvent.EmptyCart |> EventHub.ShoppingCartEvents.Trigger ) ] ] |> contents.Append } |> Async.Start EventHub.ShoppingCartEvents.Publish.Add (fun ce -> async { let! itemsInCart = Internals.GetItemsInCart () let! next = Internals.NextCounter () let SET items = items |> Internals.SetItemsInCart let _ = match ce with | EventHub.CartEvent.AddItem (item, qty) -> let cartItem = if Map.containsKey item.ProductName itemsInCart then let cartItem = itemsInCart.[item.ProductName] { cartItem with Quantity = cartItem.Quantity+qty } else { Item = item; Quantity = qty; Order = next } itemsInCart |> Map.add item.ProductName cartItem |> SET | EventHub.CartEvent.RemoveOneItem productName -> if Map.containsKey productName itemsInCart then let cartItem = itemsInCart.[productName] if cartItem.Quantity <= 1 then itemsInCart |> Map.remove productName else itemsInCart |> Map.add productName { cartItem with Quantity = cartItem.Quantity-1 } |> SET | EventHub.CartEvent.AddOneItem productName -> if Map.containsKey productName itemsInCart then let cartItem = itemsInCart.[productName] itemsInCart |> Map.add productName { cartItem with Quantity = cartItem.Quantity+1 } |> SET | EventHub.CartEvent.RemoveItem productName -> itemsInCart |> Map.remove productName |> SET | EventHub.CartEvent.EmptyCart -> Map.empty |> SET do updateCart () } |> Async.Start ) updateCart () contents module Controls = [] type ShoppingCart() = inherit Web.Control() [] override __.Body = Client.ShoppingCart() :> _ [] type ItemsToBuy() = inherit Web.Control() [] override __.Body = Client.ItemsToBuy() :> _ module Skin = open System.Web type Page = { Title : string Items : list ShoppingCart : list } let MainTemplate = Content.Template("~/Main.html") .With("title", fun x -> x.Title) .With("items", fun x -> x.Items) .With("shoppingcart", fun x -> x.ShoppingCart) let WithTemplate title items shoppingcart : Content = Content.WithTemplate MainTemplate <| fun context -> { Title = title Items = items context ShoppingCart = shoppingcart context } module Site = let ( => ) text url = A [HRef url] -< [Text text] let HomePage = Skin.WithTemplate "ShoppingCart" <| fun ctx -> [ Div [new Controls.ItemsToBuy()] ] <| fun ctx -> [ Div [new Controls.ShoppingCart()] ] let Main = Sitelet.Sum [ Sitelet.Content "/" Home HomePage ] [] type Website() = interface IWebsite with member this.Sitelet = Site.Main member this.Actions = [Home] type Global() = inherit System.Web.HttpApplication() member g.Application_Start(sender: obj, args: System.EventArgs) = () [)>] do ()