Commit 368387cb authored by Bas Lijnse's avatar Bas Lijnse

Did a full cleanup of the communication parts of the iData library. It still...

Did a full cleanup of the communication parts of the iData library. It still needs some tweaking, but is ready to be alpha tested during iTask development.

Changes include:
 - Removal of dirty low level parsing in the EncodeDecode library
 - Removal of embedded javascript fragments scattered across the clean code
 - Centralization of all javascript functionality into one javascript library that is alway available
 - Addition of a method for serving shared static resources (images, javascript, css etc.) from a central
   location. (Libraries/iData/Resources/)
 - Moving of flag macros that have to be set during different compile runs to a separate module (iDataCompileOptions).
- Change to the type of doHtmlClient to allow for switching between client and server compilation without changing any sources.

 And many small tweaks and cleanups everywhere in the codebase


git-svn-id: https://svn.cs.ru.nl/repos/iTask-system/trunk@143 63da3aa8-80fd-4f01-9db8-e6ea747a3da2
parent 6a6784ba
...@@ -25,22 +25,20 @@ import iDataFormData ...@@ -25,22 +25,20 @@ import iDataFormData
| UpdS String // new piece of text | UpdS String // new piece of text
encodeTriplet :: !Triplet -> String // encoding of triplets encodeTriplet :: !Triplet -> String // encoding of triplets
encodeInputId :: !Triplet -> String
encodeString :: !String -> String // encoding of string encodeString :: !String -> String // encoding of string
decodeString :: !String -> *String decodeString :: !String -> *String
urlEncode :: !String -> String urlEncode :: !String -> String
urlDecode :: !String -> *String urlDecode :: !String -> *String
// Form submission handling // Form submission handling
callClean :: !(Script -> ElementEvents) !Mode !String !Lifespan !Bool -> [ElementEvents] callClean :: !(Script -> ElementEvents) !Mode !String !Lifespan !Bool -> [ElementEvents]
submitscript :: BodyTag
initscript :: BodyTag
globalstateform :: !Value !Value -> BodyTag
// serializing, de-serializing of iData states to strings stored in the html page // serializing, de-serializing of iData states to strings stored in the html page
EncodeHtmlStates :: ![HtmlState] -> String EncodeHtmlStates :: ![HtmlState] -> String
DecodeHtmlStatesAndUpdate :: (Maybe [(String, String)]) -> (![HtmlState],!Triplets,!String) // hidden state stored in Client + triplets DecodeHtmlStatesAndUpdate :: [(String, String)] -> (![HtmlState],!Triplets,!String) // hidden state stored in Client + triplets
// serializing, de-serializing of iData state stored in files // serializing, de-serializing of iData state stored in files
...@@ -50,12 +48,10 @@ deleteStateFile :: !String !*NWorld -> *NWorld ...@@ -50,12 +48,10 @@ deleteStateFile :: !String !*NWorld -> *NWorld
// constants that maybe useful // constants that maybe useful
traceHtmlInput :: !(Maybe [(String, String)]) -> BodyTag // for debugging showing the information received from browser traceHtmlInput :: [(String, String)] -> BodyTag // for debugging showing the information received from browser
trace_to_file :: !String !*World -> *World // for storing debug information to file trace_to_file :: !String !*World -> *World // for storing debug information to file
globalFormName :== "CleanForm" // name of hidden Html form in which iData state information is stored
updateInpName :== "UD" // marks update information
globalInpName :== "GS" // marks global state information globalInpName :== "GS" // marks global state information
selectorInpName :== "CS_" // marks constructor update selectorInpName :== "CS_" // marks constructor update
focusInpName :== "FS" // marks the focus of the cursor at the time the form was sent focusInpName :== "FS" // marks the focus of the cursor at the time the form was sent
...@@ -8,6 +8,7 @@ import iDataTrivial, iDataFormData, StdBimap ...@@ -8,6 +8,7 @@ import iDataTrivial, iDataFormData, StdBimap
import GenPrint, GenParse import GenPrint, GenParse
import dynamic_string import dynamic_string
import EstherBackend import EstherBackend
import HttpTextUtil
//import sapldebug //import sapldebug
derive gParse UpdValue, (,,), (,) derive gParse UpdValue, (,,), (,)
...@@ -19,102 +20,18 @@ derive gPrint UpdValue, (,,), (,) ...@@ -19,102 +20,18 @@ derive gPrint UpdValue, (,,), (,)
// script for transmitting name and value of changed input // script for transmitting name and value of changed input
callClean :: !(Script -> ElementEvents) !Mode !String !Lifespan !Bool -> [ElementEvents] callClean :: !(Script -> ElementEvents) !Mode !String !Lifespan !Bool -> [ElementEvents]
callClean onSomething Edit _ lsp submit = [onSomething (SScript ("toclean(this," <+++ isOnClient lsp <+++ "," <+++ (doSubmit submit) <+++ ")"))] callClean event mode elemid lsp action
callClean onSomething Submit myid lsp submit = [onSomething (SScript ("toclean2(" <+++ myid <+++ "," <+++ isOnClient lsp <+++ ",true)"))] | isMember mode [Edit, Submit] = [event (SScript ("toClean(this,'" +++ elemid +++ "'," +++ isAction action +++ "," +++ isSubmit mode +++ "," +++ isOnClient lsp +++ ")"))]
callClean onSomething _ _ _ _ = [] | otherwise = []
where
onSomething True = OnClick isAction True = "true"
onSomething False = OnChange isAction False = "false"
isOnClient Client = "true"
isOnClient _ = "false"
doSubmit True = "true"
doSubmit False = "false"
// Initializes a page with javascript after it has been loaded
// It is currently just used to set the focus back to the place it was before a submit was done
initscript :: BodyTag
initscript
= BodyTag
[ Script [] (SScript
( "var cleanUpdated = false; " +++
"function cleanInit() { " +++
" resetFocus();" +++
" attachFocusHandler(\"input\"); " +++
" attachFocusHandler(\"select\"); " +++
" attachFocusHandler(\"textarea\"); " +++
"} " +++
"function sendToClean() {" +++
" if(cleanUpdated) {" +++
" document." +++ globalFormName +++ "." +++ focusInpName +++ ".value=this.name;" +++
" document." +++ globalFormName +++ ".submit();" +++
" }" +++
"} " +++
"function resetFocus() {" +++
" if(document.getElementById('" +++ focusInpName +++ "').value != '') {" +++
" var inp = document.getElementById(document.getElementById('"+++ focusInpName +++ "').value); " +++
" if (inp != undefined) { inp.focus(); } " +++
" }" +++
"} " +++
"function attachFocusHandler(tagname) {" +++
" elems = document.getElementsByTagName(tagname);" +++
" for(var i = 0; i < elems.length; i++) {" +++
" elems[i].onfocus = sendToClean;" +++
" }" +++
"}"
))]
submitscript :: BodyTag
submitscript
= BodyTag
[ Script [] (SScript
( " function toclean(inp,onclient,submit) {" +++
" document." +++ globalFormName +++ "." +++ updateInpName +++ ".value=inp.name+\"=\"+inp.value; " +++
" document." +++ globalFormName +++ "." +++ focusInpName +++ ".value=inp.name;\n;" +++
" if(submit) {" +++
" document." +++ globalFormName +++ ".submit();" +++
" } else {" +++
" cleanUpdated = true;" +++
" }" +++
" }"
))
, Script [] (SScript
( " function toclean2(form,onclient,submit)" +++
" { " +++
"form.hidden.value=" +++ "document." +++ globalFormName +++ "." +++ globalInpName +++ ".value;" +++
"form.submit();" +++
"}"
))
]
// form that contains global state and empty input form for storing updated input
globalstateform :: !Value !Value -> BodyTag
globalstateform globalstate focus
= Form [ Frm_Name globalFormName
, Frm_Method Post
, Frm_Enctype "multipart/form-data" // what to do to enable large data ??
]
[ Input [ Inp_Name updateInpName
, Inp_Type Inp_Hidden
] ""
, Input [ Inp_Name globalInpName
, Inp_Type Inp_Hidden
, Inp_Value globalstate
] ""
, Input [ Inp_Name focusInpName
, Inp_Type Inp_Hidden
, Inp_Value focus
, `Inp_Std [Std_Id focusInpName]
] ""
]
isSubmit Submit = "true"
isSubmit _ = "false"
isOnClient Client = "true"
isOnClient _ = "false"
isSelector name = name%(0,size selectorInpName - 1) == selectorInpName isSelector name = name%(0,size selectorInpName - 1) == selectorInpName
getSelector name = decodeString (name%(size selectorInpName,size name - 1)) getSelector name = decodeString (name%(size selectorInpName,size name - 1))
...@@ -188,54 +105,26 @@ where ...@@ -188,54 +105,26 @@ where
// reconstruct HtmlState out of the information obtained from browser // reconstruct HtmlState out of the information obtained from browser
DecodeHtmlStatesAndUpdate :: (Maybe [(String, String)]) -> (![HtmlState],!Triplets, !String) DecodeHtmlStatesAndUpdate :: [(String, String)] -> (![HtmlState],!Triplets,!String)
DecodeHtmlStatesAndUpdate args DecodeHtmlStatesAndUpdate args
# (_,triplets,state,focus) = DecodeArguments args # (_,triplets,state,focus) = DecodeArguments args
= ([states \\states=:(id,_,_,nstate) <- DecodeHtmlStates state | id <> "" || nstate <> ""],triplets, focus) // to be sure that no rubbish is passed on = ([states \\states=:(id,_,_,nstate) <- DecodeHtmlStates state | id <> "" || nstate <> ""],triplets, focus) // to be sure that no rubbish is passed on
// Parse and decode low level information obtained from server // Decode posted form information obtained from http layer
// In case of using a php script and external server: DecodeArguments :: [(String, String)] -> (!String,!Triplets,!String,!String)
DecodeArguments args
DecodeArguments :: (Maybe [(String, String)]) -> (!String,!Triplets,!String, !String) # state = http_getValue "GS" args ""
DecodeArguments (Just args) # focus = http_getValue "FS" args ""
# nargs = length args # tripargs = [decodeNameValue (n,v) \\ (n,v) <- args | not (isMember n ["GS","FS"])]
| nargs == 0 = ("clean",[],"","") # triplets = ordertriplets [(triplet,value) \\ (mbtriplet,value) <- tripargs, Just triplet <- [decodeTriplet mbtriplet]] [] // order is important, first the structure than the values ...
| nargs == 1 = DecodeCleanServerArguments (foldl (+++) "" [name +++ "=" +++ value +++ ";" \\ (name,value) <- args]) # triplets = determineChanged triplets
//TODO: Get focus from form when CleanServer is not used = ("clean", triplets, state, focus)
# tripargs = reverse args // state hidden in last field, rest are triplets
# (state,tripargs) = (urlDecode (snd (hd tripargs)),tl tripargs) // decode state, get triplets highest positions first
# alltriplets = ordertriplets [(triplet,string) \\ (mbtriplet,string) <- map decodeNameValue tripargs, Just triplet <- [parseString mbtriplet]] []
= ("clean",determineChanged alltriplets ,state,"") // order is important, first the structure than the values ...
where where
DecodeCleanServerArguments :: !String -> (!String,!Triplets,!String, !String) // executable, id + update , new , state, focus
DecodeCleanServerArguments args fromJustTriplet :: (Maybe Triplet) -> Triplet
# input = [c \\ c <-: args | not (isControl c) ] // get rid of communication noise fromJustTriplet Nothing = ("",0,UpdI 0)
# (thisexe,input) = mscan '\"' input // get rid of garbage fromJustTriplet (Just triplet) = triplet
# input = skipping ['UD\"'] input
# (triplet, input) = mscan '=' input // should give triplet
# (found,index) = FindSubstr ['--'] input
# (new, input) = splitAt index input // should give triplet value
# (_,input) = mscan '=' input
# input = skipping ['\"GS\"'] input
# (found,index) = FindSubstr ['---'] input
# (state, input) = splitAt index input
# state = if found (take index state) ['']
# (_,input) = mscan '=' input
# input = skipping ['\"FS\"'] input
# (found,index) = FindSubstr ['--'] input
# focus = if found (take index input) ['']
# striplet = toString triplet
= if (striplet == "")
("clean", [], toString state, toString focus)
(if (isSelector striplet)
("clean", [(fromJust` (decodeChars new) (parseString (decodeChars new)), "")], toString state, toString focus)
("clean", [(fromJust` (decodeChars triplet) (parseString (decodeChars triplet)) , toString new)], toString state, toString focus))
fromJust` _ (Just value) = value
fromJust` string Nothing = ("",0,UpdI 0)
ordertriplets [] accu = accu ordertriplets [] accu = accu
ordertriplets [x=:((id,_,_),_):xs] accu ordertriplets [x=:((id,_,_),_):xs] accu
# (thisgroup,other) = ([x:filter (\((tid,_,_),_) -> tid == id) xs],filter (\((tid,_,_),_) -> tid <> id) xs) # (thisgroup,other) = ([x:filter (\((tid,_,_),_) -> tid == id) xs],filter (\((tid,_,_),_) -> tid <> id) xs)
...@@ -246,29 +135,25 @@ where ...@@ -246,29 +135,25 @@ where
++ [x] ++ ++ [x] ++
qsort [y \\ y=:((_,posy,_),_) <- xs | posy < posx] qsort [y \\ y=:((_,posy,_),_) <- xs | posy < posx]
determineChanged :: [TripletUpdate] -> [TripletUpdate]
determineChanged triplets = filter updated triplets determineChanged triplets = filter updated triplets
where where
updated ((_,_,UpdC c1),c2) = c1 <> c2 updated ((_,_,UpdC c1),c2) = c1 <> c2
updated ((_,_,UpdI i),s) = i <> toInt s updated ((_,_,UpdI i),s) = i <> toInt s
updated ((_,_,UpdR r),s) = r <> toReal s updated ((_,_,UpdR r),s) = r <> toReal s
updated ((_,_,UpdB True),"False") = True updated ((_,_,UpdB True),"False") = True
updated ((_,_,UpdB False),"True") = True updated ((_,_,UpdB False),"True") = True
updated ((_,_,UpdB b1),b2) = False updated ((_,_,UpdB b1),b2) = False
updated ((_,_,UpdS s1),s2) = s1 <> s2 updated ((_,_,UpdS s1),s2) = s1 <> s2
decodeNameValue :: !(!String,!String) -> (!String,!String) decodeNameValue :: !(!String,!String) -> (!String,!String)
decodeNameValue (encname,encvalue) decodeNameValue (name,value)
= decodeNameValue` (urlDecode encname,urlDecode encvalue) | isSelector name = (value, getSelector name)
where | otherwise = (name, value)
decodeNameValue` (name,value)
| name == "hidden" = (name,value)
| isSelector name = (decodeString value, getSelector name)
| otherwise = (decodeString name, value)
// traceHtmlInput utility used to see what kind of rubbish is received from client // traceHtmlInput utility used to see what kind of rubbish is received from client
traceHtmlInput :: [(String, String)] -> BodyTag
traceHtmlInput :: !(Maybe [(String, String)]) -> BodyTag traceHtmlInput args
traceHtmlInput args=:(Just input)
= BodyTag [ Br, B [] "State values received from client when application started:", Br, = BodyTag [ Br, B [] "State values received from client when application started:", Br,
STable [] [ [B [] "Triplets:",Br] STable [] [ [B [] "Triplets:",Br]
, showTriplet triplets , showTriplet triplets
...@@ -279,7 +164,7 @@ traceHtmlInput args=:(Just input) ...@@ -279,7 +164,7 @@ traceHtmlInput args=:(Just input)
] ]
, Br , Br
, B [] "Undecoded information from client received:", Br, Br , B [] "Undecoded information from client received:", Br, Br
, BodyTag (foldl (++) [] [[B [] "name = ", Txt (fst (decodeNameValue (name,value))),Br,B [] "value = ", Txt (snd (decodeNameValue (name,value))),Br] \\ (name,value) <- input]) , BodyTag (foldl (++) [] [[B [] "name = ", Txt (fst (decodeNameValue (name,value))),Br,B [] "value = ", Txt (snd (decodeNameValue (name,value))),Br] \\ (name,value) <- args])
] ]
where where
...@@ -364,6 +249,10 @@ encodeTriplet triplet = encodeInfo triplet ...@@ -364,6 +249,10 @@ encodeTriplet triplet = encodeInfo triplet
decodeTriplet :: !String -> Maybe Triplet // decoding of triplets decodeTriplet :: !String -> Maybe Triplet // decoding of triplets
decodeTriplet triplet = decodeInfo triplet decodeTriplet triplet = decodeInfo triplet
// encodes only the formid and counter to use as identifier in html pages
encodeInputId :: !Triplet -> String
encodeInputId (formid, cntr, updval) = encodeInfo (formid, cntr)
// utility functions based on low level encoding - decoding // utility functions based on low level encoding - decoding
encodeInfo :: !a -> String | gPrint{|*|} a encodeInfo :: !a -> String | gPrint{|*|} a
......
...@@ -37,6 +37,9 @@ derive gHpr Int, Real, Bool, String, Char, [] ...@@ -37,6 +37,9 @@ derive gHpr Int, Real, Bool, String, Char, []
print_to_stdout :: !a !*HtmlStream -> *HtmlStream | gHpr{|*|} a print_to_stdout :: !a !*HtmlStream -> *HtmlStream | gHpr{|*|} a
// instance of toString for an html stream.
instance toString [# String !]
// handy utility print routines // handy utility print routines
print :: !String -> FoF print :: !String -> FoF
......
...@@ -44,6 +44,31 @@ where ...@@ -44,6 +44,31 @@ where
myfold file [x:xs] = myfold (gHlist file x) xs myfold file [x:xs] = myfold (gHlist file x) xs
myfold file [] = file myfold file [] = file
// instance of toString for an html stream.
instance toString [# String !]
where
toString stream
# n_chars = count_chars stream 0
= copy_strings stream n_chars (createArray n_chars '\0')
where
count_chars [|] n = n
count_chars [|s:l] n = count_chars l (n+size s)
copy_strings [|e:l] i s
# size_e = size e
# i = i-size_e
= copy_strings l i (copy_chars e 0 i size_e s)
copy_strings [|] 0 s
= s
copy_chars :: !{#Char} !Int !Int !Int !*{#Char} -> *{#Char}
copy_chars s_s s_i d_i n d_s
| s_i<n
# d_s = {d_s & [d_i]=s_s.[s_i]}
= copy_chars s_s (s_i+1) (d_i+1) n d_s
= d_s
// utility print functions based on gHpr // utility print functions based on gHpr
print :: !String -> FoF print :: !String -> FoF
......
body {
background-image: url('../img/clean-background.jpg');
font-family: Arial, Helvetica, sans-serif;
font-style: normal;
font-weight: normal;
font-size: 14pt;
color: white;
}
form {
display: inline;
}
h1 {
text-align: center;
border-bottom: 1px solid;
}
.EditBox {
font-family: Arial, Helvetica, sans-serif;
font-size: 12pt;
}
.DisplayBox {
font-family: Arial, Helvetica, sans-serif;
font-style: normal;
font-weight: normal;
font-size: 12pt;
color: white;
background-color: #32729D;
}
.Label {
color: yellow;
font-weight: bold;
}
.MainLabel {
color: red;
font-weight: bold;
}
.HighLight {
color: aqua;
font-weight: bold;
}
.LowLight {
color: aqua;
}
.Trace {
color: silver;
font-weight: bold;
}
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
// Script to handle iTask's client architecture.
// It handles everything using Ajax and Sapl
// MJP + JMJ + BL 2007-2008
// version 1.1
// The following symbols are used by Clean and Sapl to separate the different types of information;
var Sapl_ToServer_Separator = "#0#"; // separator between boolean value and page update response
var State_FormList_Separator = "##;"; // separator between state info and list of form info
var FormName_Content_Separator = "###"; // separator between name of form and the contents of a form element
var FormElem_Separator = "#;;"; // separator between form elements in form list
// Global variables
var xmlHttp = undefined; // The xml/http object
var saplApplet = undefined; // The sapl applet
var use_ajax = false; // Is ajax enabled
var use_sapl = false; // Is sapl enabled
var lock = false; // asynchronous submitting of information is not allowed
var viasaple = false;
//Attach the client-side initialization handler
window.onload = loadPage;
function loadPage() {
//Reset focus if neccessary
resetFocus();
//Attach focus handler to all inputs
attachFocusHandlers();
//Detect and initialize sapl
saplApplet = document.getElementById("SA");
if(saplApplet != undefined) {
saplApplet.readsaplfromserver(getAppName());
use_sapl = true;
} else {
use_sapl = false;
}
//Detect and initialize ajax
ajaxoption = document.getElementById("OPT-ajax");
if(ajaxoption != undefined) {
if(ajaxoption.innerHTML == "true") {
//Initialize the xml/httml object
initAjax();
//Load the start page
ajaxInitPage();
use_ajax = true;
} else {
use_ajax = false;
}
} else {
use_ajax = false;
}
}
function initAjax() {
//Initialize the xml/httml object
try { // Firefox, Opera 8.0+, Safari
xmlHttp = new XMLHttpRequest();
} catch (e) {
try { // Internet Explorer
xmlHttp=new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");
}
}
}
//Resets the focus to the previously selected input
function resetFocus() {
var focusid = getFocus();
if(focusid != "")
{
var inp = document.getElementById(focusid);
if (inp != undefined)
{
inp.focus();
}
}
}
//Attaches the setFocus handler to each input
function attachFocusHandlers() {
var tagnames = ["input","select","textarea"];
for(var i = 0; i < tagnames.length; i++) {
var elems = document.getElementsByTagName(tagnames[i]);
for(var j = 0; j < elems.length; j++) {
elems[j].onfocus = setFocus
}
}
}
//Getters for the state data in the page
function getAppName () {
return document.getElementById("AN").innerHTML;
}
function getGlobalState () {
return document.getElementById("GS").innerHTML;
}
function getFocus() {
return document.getElementById("FS").innerHTML;
}
//Setters for the state data in the page
function setGlobalState (state) {
document.getElementById("GS").innerHTML = state;
}
function setFocus() {
document.getElementById("FS").innerHTML = this.id;
}
//Catches a submit of a form
function catchSubmit(form) {
sendForm(form.id, false); //Send the form to the server (caught submits are never handled on the client)
return false;
}
//Attaches focus and state information to a form before sending it to clean.
function addState(form) {
var state = document.createElement('input');