{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DeriveGeneric #-}
{-----------------------------------------------------------------------------
Example from threepenny-gui extended with Editors:
Small database with CRUD operations and filtering.
To keep things simple, the list box is rebuild every time
that the database is updated. This is perfectly fine for rapid prototyping.
A more sophisticated approach would use incremental updates.
------------------------------------------------------------------------------}
{-# LANGUAGE RecursiveDo #-}
module CRUD (main) where
import Prelude hiding (lookup)
import Control.Monad (void)
import Data.Biapplicative
import Data.List (isPrefixOf)
import Data.Maybe
import qualified Data.Map as Map
import Generics.SOP.TH
import qualified Graphics.UI.Threepenny as UI
import Graphics.UI.Threepenny.Editors hiding (create)
import qualified Graphics.UI.Threepenny.Editors as Editors
import Graphics.UI.Threepenny.Core hiding (delete)
{-----------------------------------------------------------------------------
Main
------------------------------------------------------------------------------}
main :: IO ()
main = startGUI defaultConfig setup
setup :: Window -> UI ()
setup window = void $ mdo
return window # set title "CRUD Example (Simple)"
-- GUI elements
createBtn <- UI.button #+ [string "Create"]
deleteBtn <- UI.button #+ [string "Delete"]
listBox <- UI.listBox bListBoxItems bSelection bDisplayDataItem
filterEntry <- UI.entry bFilterString
dataItem <- Editors.create editor (fromMaybe (DataItem "" "") <$> bSelectionDataItem)
-- GUI layout
element listBox # set (attr "size") "10" # set style [("width","200px")]
let glue = string " "
getBody window #+ [grid
[[row [string "Filter prefix:", element filterEntry], glue]
,[element listBox, render dataItem]
,[row [element createBtn, element deleteBtn], glue]
]]
-- events and behaviors
bFilterString <- stepper "" . rumors $ UI.userText filterEntry
let tFilter = isPrefixOf <$> UI.userText filterEntry
bFilter = facts tFilter
eFilter = rumors tFilter
let eSelection = rumors $ UI.userSelection listBox
eDataItemIn = edited dataItem
eCreate = UI.click createBtn
eDelete = UI.click deleteBtn
-- database
-- bDatabase :: Behavior (Database DataItem)
let update' mkey x = flip update x <$> mkey
bDatabase <- accumB emptydb $ concatenate <$> unions
[ create (DataItem "Emil" "Example") <$ eCreate
, filterJust $ update' <$> bSelection <@> eDataItemIn
, delete <$> filterJust (bSelection <@ eDelete)
]
-- selection
-- bSelection :: Behavior (Maybe DatabaseKey)
bSelection <- stepper Nothing $ head <$> unions
[ eSelection
, Nothing <$ eDelete
, Just . nextKey <$> bDatabase <@ eCreate
, (\b s p -> b >>= \a -> if p (s a) then Just a else Nothing)
<$> bSelection <*> bShowDataItem <@> eFilter
]
let bLookup :: Behavior (DatabaseKey -> Maybe DataItem)
bLookup = flip lookup <$> bDatabase
bShowDataItem :: Behavior (DatabaseKey -> String)
bShowDataItem = (maybe "" showDataItem .) <$> bLookup
bDisplayDataItem = (UI.string .) <$> bShowDataItem
bListBoxItems :: Behavior [DatabaseKey]
bListBoxItems = (\p show -> filter (p. show) . keys)
<$> bFilter <*> bShowDataItem <*> bDatabase
bSelectionDataItem :: Behavior (Maybe DataItem)
bSelectionDataItem = (=<<) <$> bLookup <*> bSelection
-- automatically enable / disable editing
let
bDisplayItem :: Behavior Bool
bDisplayItem = isJust <$> bSelection
element deleteBtn # sink UI.enabled bDisplayItem
element (firstName $ widgetControl dataItem) # sink UI.enabled bDisplayItem
element (lastName $ widgetControl dataItem) # sink UI.enabled bDisplayItem
{-----------------------------------------------------------------------------
Database Model
------------------------------------------------------------------------------}
type DatabaseKey = Int
data Database a = Database { nextKey :: !Int, db :: Map.Map DatabaseKey a }
emptydb = Database 0 Map.empty
keys = Map.keys . db
create x (Database newkey db) = Database (newkey+1) $ Map.insert newkey x db
update key x (Database newkey db) = Database newkey $ Map.insert key x db
delete key (Database newkey db) = Database newkey $ Map.delete key db
lookup key (Database _ db) = Map.lookup key db
{-----------------------------------------------------------------------------
Data items that are stored in the data base
------------------------------------------------------------------------------}
data DataItemDual (usage :: Usage) = DataItem
{ firstName, lastName :: Field usage String
}
showDataItem :: DataItem -> String
showDataItem DataItem{..} = lastName ++ ", " ++ firstName
type DataItem = DataItemDual Value
type DataItemEditor = DataItemDual Edit
instance Editable DataItem where
type EditorWidget DataItem = DataItemEditor
editor = bipure DataItem DataItem
<<*>> edit firstName editor
<<*>> edit lastName editor
deriveGeneric ''DataItemDual
instance Renderable DataItemEditor where
render = renderGeneric