Commit 08614204 authored by Bas Lijnse's avatar Bas Lijnse
Browse files

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
parent 0a4994ff
......@@ -25,22 +25,20 @@ import iDataFormData
| UpdS String // new piece of text
encodeTriplet :: !Triplet -> String // encoding of triplets
encodeInputId :: !Triplet -> String
encodeString :: !String -> String // encoding of string
decodeString :: !String -> *String
urlEncode :: !String -> String
urlDecode :: !String -> *String
// Form submission handling
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
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
......@@ -50,12 +48,10 @@ deleteStateFile :: !String !*NWorld -> *NWorld
// constants that maybe useful
traceHtmlInput :: !(Maybe [(String, String)]) -> BodyTag // for debugging showing the information received from browser
trace_to_file :: !String !*World -> *World // for storing debug information to file
traceHtmlInput :: [(String, String)] -> BodyTag // for debugging showing the information received from browser
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
selectorInpName :== "CS_" // marks constructor update
focusInpName :== "FS" // marks the focus of the cursor at the time the form was sent
......@@ -8,6 +8,7 @@ import iDataTrivial, iDataFormData, StdBimap
import GenPrint, GenParse
import dynamic_string
import EstherBackend
import HttpTextUtil
//import sapldebug
derive gParse UpdValue, (,,), (,)
......@@ -19,102 +20,18 @@ derive gPrint UpdValue, (,,), (,)
// script for transmitting name and value of changed input
callClean :: !(Script -> ElementEvents) !Mode !String !Lifespan !Bool -> [ElementEvents]
callClean onSomething Edit _ lsp submit = [onSomething (SScript ("toclean(this," <+++ isOnClient lsp <+++ "," <+++ (doSubmit submit) <+++ ")"))]
callClean onSomething Submit myid lsp submit = [onSomething (SScript ("toclean2(" <+++ myid <+++ "," <+++ isOnClient lsp <+++ ",true)"))]
callClean onSomething _ _ _ _ = []
onSomething True = OnClick
onSomething False = OnChange
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]
] ""
]
callClean event mode elemid lsp action
| isMember mode [Edit, Submit] = [event (SScript ("toClean(this,'" +++ elemid +++ "'," +++ isAction action +++ "," +++ isSubmit mode +++ "," +++ isOnClient lsp +++ ")"))]
| otherwise = []
where
isAction True = "true"
isAction False = "false"
isSubmit Submit = "true"
isSubmit _ = "false"
isOnClient Client = "true"
isOnClient _ = "false"
isSelector name = name%(0,size selectorInpName - 1) == selectorInpName
getSelector name = decodeString (name%(size selectorInpName,size name - 1))
......@@ -188,54 +105,26 @@ where
// reconstruct HtmlState out of the information obtained from browser
DecodeHtmlStatesAndUpdate :: (Maybe [(String, String)]) -> (![HtmlState],!Triplets, !String)
DecodeHtmlStatesAndUpdate :: [(String, String)] -> (![HtmlState],!Triplets,!String)
DecodeHtmlStatesAndUpdate 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
// Parse and decode low level information obtained from server
// In case of using a php script and external server:
DecodeArguments :: (Maybe [(String, String)]) -> (!String,!Triplets,!String, !String)
DecodeArguments (Just args)
# nargs = length args
| nargs == 0 = ("clean",[],"","")
| nargs == 1 = DecodeCleanServerArguments (foldl (+++) "" [name +++ "=" +++ value +++ ";" \\ (name,value) <- args])
//TODO: Get focus from form when CleanServer is not used
# 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 ...
// Decode posted form information obtained from http layer
DecodeArguments :: [(String, String)] -> (!String,!Triplets,!String,!String)
DecodeArguments args
# state = http_getValue "GS" args ""
# focus = http_getValue "FS" args ""
# tripargs = [decodeNameValue (n,v) \\ (n,v) <- args | not (isMember n ["GS","FS"])]
# triplets = ordertriplets [(triplet,value) \\ (mbtriplet,value) <- tripargs, Just triplet <- [decodeTriplet mbtriplet]] [] // order is important, first the structure than the values ...
# triplets = determineChanged triplets
= ("clean", triplets, state, focus)
where
DecodeCleanServerArguments :: !String -> (!String,!Triplets,!String, !String) // executable, id + update , new , state, focus
DecodeCleanServerArguments args
# input = [c \\ c <-: args | not (isControl c) ] // get rid of communication noise
# (thisexe,input) = mscan '\"' input // get rid of garbage
# 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)
fromJustTriplet :: (Maybe Triplet) -> Triplet
fromJustTriplet Nothing = ("",0,UpdI 0)
fromJustTriplet (Just triplet) = triplet
ordertriplets [] accu = accu
ordertriplets [x=:((id,_,_),_):xs] accu
# (thisgroup,other) = ([x:filter (\((tid,_,_),_) -> tid == id) xs],filter (\((tid,_,_),_) -> tid <> id) xs)
......@@ -246,29 +135,25 @@ where
++ [x] ++
qsort [y \\ y=:((_,posy,_),_) <- xs | posy < posx]
determineChanged :: [TripletUpdate] -> [TripletUpdate]
determineChanged triplets = filter updated triplets
where
updated ((_,_,UpdC c1),c2) = c1 <> c2
updated ((_,_,UpdI i),s) = i <> toInt s
updated ((_,_,UpdR r),s) = r <> toReal s
updated ((_,_,UpdC c1),c2) = c1 <> c2
updated ((_,_,UpdI i),s) = i <> toInt s
updated ((_,_,UpdR r),s) = r <> toReal s
updated ((_,_,UpdB True),"False") = True
updated ((_,_,UpdB False),"True") = True
updated ((_,_,UpdB b1),b2) = False
updated ((_,_,UpdS s1),s2) = s1 <> s2
updated ((_,_,UpdS s1),s2) = s1 <> s2
decodeNameValue :: !(!String,!String) -> (!String,!String)
decodeNameValue (encname,encvalue)
= decodeNameValue` (urlDecode encname,urlDecode encvalue)
where
decodeNameValue` (name,value)
| name == "hidden" = (name,value)
| isSelector name = (decodeString value, getSelector name)
| otherwise = (decodeString name, value)
decodeNameValue (name,value)
| isSelector name = (value, getSelector name)
| otherwise = (name, value)
// traceHtmlInput utility used to see what kind of rubbish is received from client
traceHtmlInput :: !(Maybe [(String, String)]) -> BodyTag
traceHtmlInput args=:(Just input)
traceHtmlInput :: [(String, String)] -> BodyTag
traceHtmlInput args
= BodyTag [ Br, B [] "State values received from client when application started:", Br,
STable [] [ [B [] "Triplets:",Br]
, showTriplet triplets
......@@ -279,7 +164,7 @@ traceHtmlInput args=:(Just input)
]
, 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
......@@ -364,6 +249,10 @@ encodeTriplet triplet = encodeInfo triplet
decodeTriplet :: !String -> Maybe Triplet // decoding of triplets
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
encodeInfo :: !a -> String | gPrint{|*|} a
......
......@@ -37,6 +37,9 @@ derive gHpr Int, Real, Bool, String, Char, []
print_to_stdout :: !a !*HtmlStream -> *HtmlStream | gHpr{|*|} a
// instance of toString for an html stream.
instance toString [# String !]
// handy utility print routines
print :: !String -> FoF
......
......@@ -44,6 +44,31 @@ where
myfold file [x:xs] = myfold (gHlist file x) xs
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
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;
}
// 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');
state.type = 'hidden';
state.name = 'GS';
state.value = getGlobalState();
form.appendChild(state);
var focus = document.createElement('input');
focus.type = 'hidden';
focus.name = 'FS';
focus.value = getFocus();
form.appendChild(focus);
return true;
}
//Sends the information in a form to the server/client handler
function sendForm(formid, onclient) {
var form = document.getElementById(formid);
if(form == undefined) {
return;
}
//Add state and focus information to the form
addState(form);
//If onclient is true, we try to handle the update on the client first
if(onclient && use_sapl) {
if(saplSendForm(form)) { //If handling by sapl succeeds, we are done.
return; //When sapl fails we try sending via ajax or submit.
}
}
//Send the form to the server. Either via ajax or with a submit.
if(use_ajax) {
ajaxSendForm(form);
} else {
submitSendForm(form);
}
}
//Send an update to clean
//elem :: the element that triggered the event
//triplet :: the encoded triplet information
//isaction :: is the change an action. (e.g. a button is pressed)
//issubmit :: is the change part of an iData in Submit mode
//onclient :: Bool - Should the update be handled by the client
function toClean(elem, triplet, isaction, issubmit, onclient) {
if(lock) {
return;
}
var form = getForm(elem);
if(isaction) {
var action = document.createElement('input');
action.type = 'hidden';
action.name = triplet;
action.value = '';