{----------------------------------------------------------------------------- threepenny-gui Example: 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 #-} import Prelude hiding (lookup) import Control.Monad (void) import Data.List (isPrefixOf) import qualified Data.Map as Map import qualified Graphics.UI.Threepenny as UI 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 ((firstname, lastname), tDataItem) <- dataItem bSelectionDataItem -- GUI layout element listBox # set (attr "size") "10" # set style [("width","200px")] let uiDataItem = grid [[string "First Name:", element firstname] ,[string "Last Name:" , element lastname]] let glue = string " " getBody window #+ [grid [[row [string "Filter prefix:", element filterEntry], glue] ,[element listBox, uiDataItem] ,[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 = rumors $ tDataItem 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 ("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 = maybe False (const True) <$> bSelection element deleteBtn # sink UI.enabled bDisplayItem element firstname # sink UI.enabled bDisplayItem element lastname # 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 ------------------------------------------------------------------------------} type DataItem = (String, String) showDataItem (firstname, lastname) = lastname ++ ", " ++ firstname -- | Data item widget, consisting of two text entries dataItem :: Behavior (Maybe DataItem) -> UI ((Element, Element), Tidings DataItem) dataItem bItem = do entry1 <- UI.entry $ fst . maybe ("","") id <$> bItem entry2 <- UI.entry $ snd . maybe ("","") id <$> bItem return ( (getElement entry1, getElement entry2) , (,) <$> UI.userText entry1 <*> UI.userText entry2 )