------------------------------------------------------------------------------
--- Library for creating web applications from ui descriptions
--- based on the HTML library from PAKCS
---
--- The installation of a cgi script written with this library
--- can be done by the command
---
--- curry makecgi -m main -o /home/user/public_html/prog.cgi prog
---
--- where prog
is the name of the Curry program with
--- the cgi script, /home/user/public_html/prog.cgi
is
--- the desired location of the
--- compiled cgi script, and main
is a Curry expression
--- like runGUI "Titel" uiwidget
.
---
--- @author Christof Kluss
--- @version July 2013
------------------------------------------------------------------------------
module UI2HTML (
UIWidget, UIRef,
Command(..),Ref,Handler(..),
Widget(Widget),
Event(..),WidgetKind(..),
CanvasItem(..),
-- for styles
StyleClass(..),Position(..),Direction(..),
Style(..),BorderStyle(..),FontStyle(..),Color(..),
--
addStyle,addStyles,setStyles,
addHandler,addHandlers,setHandlers,
setRef,getRef,
-- IO Actions
UI2HTML.runUI,UI2HTML.exitUI,
UI2HTML.getValue,UI2HTML.setValue,UI2HTML.updateValue,UI2HTML.appendValue,
UI2HTML.changeStyles,UI2HTML.setHandler,UI2HTML.setDisabled,
UI2HTML.addCanvas,UI2HTML.setVisible,
UI2HTML.showPopup,UI2HTML.showMessage,
-- Widgets
colS,col,rowS,row,matrixS,matrix,
entry,entryS,label,labelS,
UI.button,buttonS,simpleButton,simpleButtonS,
checkButton,checkButtonS,simpleCheckButton,simpleCheckButtonS,
canvas,canvasS,
textEdit,textEditS,
scale,scaleS,message,messageS,
menuBar,menuBarS,menu,menuS,menuSeparator,menuSeparatorS,
menuItem,menuItemS,
listBox,listBoxS,
UI.selection,UI.selectionInitial,UI.selectionInitialS,
UI.radio_main,UI.radio_other,
--
showBorderStyle,showColor,showPos,
--
UI2HTML.UIEnv,
Reconfigure(..), -- exported for GUI2HTML
ui2hexps,
ajaxForm,ajaxForm2,
setConfig,seeText,
--
setErrorBg,
cgiRef2Ref,ref2cgiRef,
nextHtmlForm,
includeHtmlForm,
onClickSpicy,
changeChilds
) where
import HTML
import IOExts
import Maybe
import Json
import System
import UI
import SpicyWeb
--- The URL of the directory where the include files (Action.js,
--- Throbber.gif, Throbber.png, ajaxrequest.js, default.css, prototype.js)
--- are stored.
includeURL :: String
includeURL = "http://www.informatik.uni-kiel.de/~pakcs/UI/"
jsbibsspicy :: [String]
jsbibsspicy = ["prototype.js","Action.js"]
jsbibs :: [String]
jsbibs = ["ajaxrequest.js"]
css :: [String]
css = ["default.css"]
data State = State (CgiRef -> String,
([(Ref CgiRef, Maybe String,[Reconfigure])],
[([(String,String)],[HtmlExp])]))
--- The (abstract) data type for representing environments
data UIEnv = UIEnv (IORef State)
type UIWidget = UI.Widget CgiRef (UIEnv -> IO()) (Doc (Ref CgiRef))
type UIRef = Ref CgiRef
-- Data type for describing configurations that are applied
data Reconfigure
= List [String]
| Disabled Bool
| Visible Bool
| Style [StyleClass]
| Pos (Int,Int)
| ErrorBg Bool
conf2str :: Reconfigure -> (String,Json)
conf2str val = case val of
--Value str -> ("text", String str)
Disabled b -> ("disabled",Bool b)
Visible b -> ("visible",Bool b)
Style styles -> ("style",String (styleClasses2String styles))
List strs -> ("list",Array (map String strs))
Pos (x,y) -> ("pos",Array [Int x,Int y])
ErrorBg b -> ("errorbg",Bool b)
x -> ("error",String (show x))
state2json :: ([(Ref CgiRef,Maybe (String),[Reconfigure])],_) -> Json
state2json (xs,_) = Array (map toJson xs)
where
toJson (ref,mbval,confs) =
Object [("id",String (idOfRef ref)),
("changes", Object ((map conf2str confs) ++
(maybe []
(\val -> [("value",String val)])
mbval)))]
execCmdAndRespond :: (UIEnv -> IO()) -> IORef State -> IO HtmlForm
execCmdAndRespond cmd stateref = do
cmd (UIEnv stateref)
State (_,state) <- readIORef stateref
let ps = snd state
writeIORef stateref (State (const "",([],[])))
return $ AjaxAnswer (state2json state) ps
-------------------------------------------------------------------------------
ref2cgiRef :: UIRef -> CgiRef
ref2cgiRef (Ref ref) = ref
cgiRef2Ref :: CgiRef -> Ref CgiRef
cgiRef2Ref ref = Ref ref
-- little modifications on the UI widgets for HTML output
widgetUI2widgetHTML :: UIWidget -> UIWidget
widgetUI2widgetHTML (UI.Widget name mbcont mbref handlers styles ws) =
case name of
Matrix wss -> Widget (Matrix (map (map widgetUI2widgetHTML) wss))
mbcont mbref handlers styles []
MenuBar -> ((UI.Widget name mbcont mbref handlers styles
(map widgetUI2widgetHTML entrys))
`addStyles` [Class [Width 8]])
where
entrys = if null ws then ws else
let (UI.Widget name' mbcontent' _ _ _ ws') = head ws in
case name' of
Menu -> (UI.Widget (Name "option") mbcontent' Nothing []
[Class [Font Bold,Font Italic]] []):ws' ++ tail ws
_ -> ws
Menu -> UI.Widget name mbcont mbref handlers styles
(map widgetUI2widgetHTML ws)
Scale min max ->
UI.Widget (Name "select") mbcont mbref handlers styles
(map (\x -> UI.Widget (ListBoxItem (show x) False)
(Just (show x)) Nothing [] [] [])
[min .. max])
CheckButton checked ->
row [UI.Widget (CheckButton checked) mbcont mbref handlers styles [],
label (fromMaybe "" mbcont)]
ListBox size list sel ->
UI.Widget (ListBox size list sel) mbcont mbref handlers styles
(selOption list 0)
where
selOption [] _ = []
selOption (v:vs) i =
(UI.Widget (ListBoxItem (show i) (if i==sel then True else False))
(Just v) Nothing [] [] []) : selOption vs (i+1)
_ -> UI.Widget name mbcont mbref handlers styles
(map widgetUI2widgetHTML ws)
-- translates the kind of a widget into corresponding html tag
showWidgetKind :: WidgetKind _ _ _ -> String
showWidgetKind kind = case kind of
Matrix _ -> "span"
Col -> "div"
Row -> "div"
Label -> "div" -- span is no block element
Button -> "input"
Entry -> "input"
Menu -> "optgroup"
MenuSeparator -> "option"
MenuBar -> "select"
MenuItem -> "option"
(Canvas _ _) -> "canvas"
(TextEdit _ _) -> "textarea"
(CheckButton _) -> "input"
(Scale _ _) -> "scale"
(ListBox _ _ _) -> "select"
ListBoxItem _ _ -> "option"
(Name str) -> str
--(Selection _ _) -> "select"
--(SelectionItem _ _) -> "option"
(RadioButton _ ) -> "input"
Link -> "a"
_ -> "error in UI2HTML.showWidgetKind"
event2js :: Event -> String
event2js event = case event of
FocusOut -> "onblur"
FocusIn -> "onfocus"
MouseButton1 -> "onclick"
MouseButton2 -> "onclick"
MouseButton3 -> "onclick"
KeyPress -> "onkeyup"
Return -> "onkeypress"
Change -> "onchange"
DoubleClick -> "ondbclick"
Click -> "onclick"
DefaultEvent -> "default"
_ -> "error in UI2HTML.event2js"
event2str :: WidgetKind _ _ _ -> Event -> String
event2str wkind event = case event of
DefaultEvent -> case wkind of
Scale _ _ -> "onchange"
Entry -> "onkeypress" -- Return
Name "select" -> "onchange"
_ -> "onclick"
_ -> event2js event
event2jsfctname :: WidgetKind _ _ _ -> Event -> String
event2jsfctname kind event = case event of
Return -> "handleKeypress"
KeyPress -> "ajaxRequest"
DefaultEvent ->
case kind of
Entry -> "handleKeypress"
_ -> "ajaxRequest"
_ -> "ajaxRequest"
name2attrs :: WidgetKind _ _ _ -> [(String,String)]
name2attrs name = case name of
TextEdit rows cols -> [("rows",show rows),("cols",show cols)]
Canvas h w -> [("height",show h),("width",show w)]
Button -> [("type","button")]
Entry -> [("type","text")]
ListBox size _ _ -> [("size",show size)]
--SelectionItem _ b -> if b then [("selected","yes")] else []
ListBoxItem _ b -> if b then [("selected","yes")] else []
CheckButton checked -> [("type","checkbox")]
++ if checked then [("checked","checked")] else []
RadioButton checked -> [("type","radio")] ++
(if checked then [("checked","yes")] else [])
MenuBar ->
[("onchange",
"ajaxRequest(event,window,this.options[this.selectedIndex].value); "
++ "this.selectedIndex=0;")]
_ -> []
content2attrs :: Maybe (String) -> WidgetKind _ _ _ -> [(String,String)]
content2attrs mbcontent name = case mbcontent of
Nothing -> []
Just label -> case name of
Menu -> [("label",htmlQuote label)]
Label -> []
TextEdit _ _ -> []
MenuItem -> []
MenuSeparator -> []
Link -> [("href","#")]
ListBoxItem val _ -> [("value",htmlQuote val)]
_ -> [("value",htmlQuote label)]
-- converts a Widget to a HtmlExp
widget2hexp :: Maybe (IORef State) -> Widget CgiRef (UIEnv -> IO ()) _ ->
[HtmlExp]
widget2hexp mbstateref (UI.Widget kind mbcontent mbref handlers styles widgets) =
handlerhexps ++ [case mbref of
Nothing -> hexp
Just ref -> case kind of
RadioButton False -> hexp -- only the main radio button has a reference
_ -> HtmlCRef hexp (ref2cgiRef ref)]
where
hexp1 =
HtmlStruct
(showWidgetKind kind) -- tag name
-- set the tag attributes
((name2attrs kind) -- type attribute
++ (content2attrs mbcontent kind) -- value attribute
++ case mbref of -- name and id attribute
Nothing -> []
Just ref1 -> case kind of
-- span has no name attribute
Label -> [("id",rid)]
Link -> [("id",rid)]
Row -> [("id",rid)]
Col -> [("id",rid)]
-- id attr is unique,
-- so groups of radio buttons cannot have same id
RadioButton _ -> [("name",rid)]
_ -> [("id",rid),("name",rid)]
where rid = idOfRef ref1
++ if null styles -- style attribute
then [] else styleClasses2Attrs styles)
childhexps -- child html expressions
xs = case kind of
--Col -> map (\ w -> block (widget2hexp mbstateref w)) widgets
Col -> [table' (map (\ w -> [widget2hexp mbstateref w]) widgets)]
Row -> [table' [map (\ w -> widget2hexp mbstateref w) widgets]]
Matrix wss ->
[table' (map (\ ws -> map (\w -> widget2hexp mbstateref w) ws) wss)
`addAttrs` (if null styles then [] else styleClasses2Attrs styles)]
_ -> concatMap (widget2hexp mbstateref) widgets
-- some special handling for widgets without child widgets
-- the content of these widgets are in their textnodes
-- e.g. a widget of kind TextEdit:
childhexps = case kind of
Canvas _ _ -> [htxt "canvas tag not supported"]
Label -> [htxtcont] -- content
TextEdit _ _ -> [htxtcont] --