-
Notifications
You must be signed in to change notification settings - Fork 5
Other Features
Draft version!
Roads features which have not been discussed in other chapters.
- Sawhorse Configuration
- Composable Form Parts
- Making Requests Bookmarkable
- Returning other Mime Types or HTML with a different charset
- Handling POST/GET parameters with multiple values
- JavaScript
- JSON
- Ajax
- CSS
- Reading and Setting Cookies
- Controlling URL Expiration
- Logging
- How to configure Roads’ webserver.
- Take a look at
sawhorse/server/Configuration.oz:
...
Config = config(port:8080
requestTimeout: 300
keepAliveTimeout: 15
acceptTimeout: 2*60*1000
processingTimeout: 10
documentRoot: "x-ozlib://wmeyer/sawhorse/public_html"
directoryIndex: "index.html"
serverName: "localhost"
serverAlias: nil
typesConfig: "x-ozlib://wmeyer/sawhorse/mime.types"
defaultType: mimeType(text plain)
serverAdmin: "admin@localhost"
logDir: "x-ozlib://wmeyer/sawhorse/sawhorse-log"
accessLogFile: "http-access.log"
accessLogLevel: trace
errorLogFile: stdout
errorLogLevel: trace
pluginDir: "x-ozlib://wmeyer/sawhorse/plugins"
)
Every option can be set like this: {Roads.setSawhorseOption port 80}.
You may also change the code directly and recompile if you want different defaults.
- “Formlets in Links decouple user interface from data, a vital form of abstraction supported by very few web frameworks.” (An idiom’s guide to formlets [PDF])
- Roads: composibility of form parts is a natural result of
bindandvalidateattributes and dataflow variables. - Example from the paper above:
“Say you want to present the user with an HTML form for entering
a pair of dates. In your initial design, a date is represented as a pair
of pulldown menus, one to select a month and one to select a day.
Later, you choose to replace each date by a single text field, for
entering a date as text.”
- In Roads you can implement this simply by virtue of procedural abstraction.
Example (extract from /roads/examples/DateExample.oz) which lets the user enter a date by textual input and displays it back to them:
...
fun {EnterDateSimple S}
Date
in
form({TextualDate 2009 2020 Date}
input(type:submit value:"Submit date")
method:post
action:fun {$ _}
p("You entered " # Date.year # "-" # Date.month # "-" # Date.day # ".")
end
)
end
%% Res will contain the date after successfull submission.
fun {TextualDate FirstYear LastYear Res}
D M
in
'div'(input(type:text id:day
validate:int_in(1 31) bind:D)
input(type:text id:month
validate:int_in(1 12) bind:M)
input(type:text id:year
validate:int_in(FirstYear LastYear)
bind:proc {$ Y}
Res = date(day:D month:M year:Y)
end
)
)
end
...
The abstraction works because bind can be set to a procedure. In this way we are able to compose the overall result from the various inputs.
It is now very easy to replace the call to TextualInput with a call to a different function which returns the HTML code for entering a date by pulldown menus (helper functions omitted):
...
%% Enter a date with popup menus.
fun {SelectDate FirstYear LastYear Res}
D M
in
'div'({Selector 1 31 ?D}
{Selector 1 12 ?M}
{Selector FirstYear LastYear
proc {$ Y}
Res = date(day:D month:M year:Y)
end
}
)
end
...
The composibility stems from the fact that we don’t need to use any explicit names to access input elements (avoids the danger of name clash) and that we can refer to the result of a group of input elements even before this result is known.
- If you use function values for
hreforactionattributes, the resulting link will not be bookmarkable. It is only valid for the current user and only during the current session. - To make bookmarkable URLs, you need to
- export the function, i.e. make it public
- use a normal URL in
hreforaction, either by providing a string or using aurl(...)record (see below) - in forms: use
getinstead ofput
- such bookmarkable links are not guarded against CSRF attacks; so they should not have side effect
- a
url(...)record represents an URL to a Roads functions. Examples: -
url(app:poll 'functor':admin function:makeAdmin)representspoll/admin/makeAdmin. -
url('functor':admin function:makeAdmin)represents<current application>/admin/makeAdmin. -
url(function:makeAdmin)represents<current application>/<current functor>/makeAdmin. -
url(function:makeAdmin extra:"?id=v%al"):<current application>/<current functor>/makeAdmin?id=v%al. -
url(function:makeAdmin params:unit(id:"v%al")):<current application>/<current functor>/makeAdmin?id=v%25al(param values are percent-encoded)
In all examples so long, we returned records that represent HTML. But you may also return:
- virtual strings; these will, by default, be served as HTML (wrt. the HTTP content type header) with charset IS0 8859-1, which is the default charset both for HTTP and Mozart/Oz.
-
redirect(Code URL)orredirect(Code url(...)); creates a redirect response; for exampleredirect(303 url(function:makeAdmin)) - a
response(...)record; in this case, Roads will only add cookies and otherwise send the response unmodified to the client.
You may also customize the mime type and charset that is used for functions that return HTML or virtual strings.
- Export a value like
MimeType = mimeType(text plain)at the functor or application level. Changing this only makes sense for virtual strings, as the HTML generated by Roads is always HTML 4.01. - Export a value like
Charset = "UTF-8"at the functor or application level. You have to make sure that your functions actually return text with this encoding manually. For HTML, you have to use a charset that is compatible to ASCII, i.e. anything from the ISO 8859 family or UTF-8.
If you expect a parameter to occur multiple times (e.g. in form submission with a multiple-selection element), use:
bind:list(Var)Var = {S.getParamAsList param}validate:list(Validator)
Note that this will validate successfully even if NO or just one parameter value is available.
For a complete example, see /roads/examples/MultipleSelection.oz.
- The best approach is probably to serve JavaScript code as separate files which are referenced in the HTML code.
- But you may also write JavaScript code as strings in Oz code.
- The full Roads installations comes with is a package “javascript” which offers some additional features:
Check your Javascript code with JSLint. JSLint in implemented in JavaScript. Roads uses either the “Windows Scripting Host” or “Rhino” (a JavaScript engine implemented in Java) to execute JSLint. WSH is usually installed by default. To install Rhino, apt-get install rhino should do the trick.
Example (/roads/examples/Ajax.oz):
functor Ajax
...
require
Javascript at 'x-ozlib://wmeyer/javascript/Javascript.ozf'
prepare
JavaScriptCode =
{Javascript.checked %% takes a list of lines
[
"function onSelectChange() {"
" var selected = $(\"#selector option:selected\").val();"
" if(selected !== undefined && selected !== \"dummy\") {"
" $(\"#content\").load(\"info\", {type:selected});"
" }"
"}"
""
"$(document).ready(function() {"
" $(\"#selector\").change(onSelectChange);"
" });"
]}
define
...
Note that we do the JavaScript check in the prepare section, so this will happen at compile time. If a problem is detected, compilation will abort with an error message.
Writing Javascript In Oz: This feature is rather a gimmick than a really useful tool ;-) Anyway it looks like this (/roads/examples/Ajax2.oz):
functor Ajax
export
Select
Info
require
Javascript at 'x-ozlib://wmeyer/javascript/Javascript.ozf'
prepare
JavaScriptCode =
{Javascript.convertAndCheck
[
function onSelectChange(nil)
[
var selected '=' '$'("#selector option:selected")#val(nil)';'
'if'(selected '!==' undefined '&&' selected '!==' "dummy")
[
'$'("#content")#load("info" ',' object(type:selected))';'
]
]
jQuery(document)#ready(
function(nil)
[
'$'("#selector")#change(onSelectChange)';'
])';'
]
}
It should be possible to figure out the syntax by comparing this example with the previous one.
Writing JavaScript like this might be useful if you construct the code programmatically. But be warned: embedding user input in JavaScript code is extremely dangerous (XSS attacks!).
Embedding JavaScript in HTML: Finally, you may embed the JavaScript-in-Oz code directly in the HTML structure (/roads/examples/Ajax3.oz):
fun {Select Session}
html(
head(
title("Ajax example")
%% load jQuery from Google
script(type:"text/javascript"
src:"http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"
)
script(type:"text/javascript"
javascript(
function onSelectChange(nil)
[
var selected '=' '$'("#selector option:selected")#val(nil)';'
'if'(selected '!==' undefined '&&' selected '!==' "dummy")
[
'$'("#content")#load("info" ',' object(type:selected))';'
]
]
jQuery(document)#ready(
function(nil)
[
'$'("#selector")#change(onSelectChange)';'
])';'
)
)
)
body(
h2("Simple Ajax Example")
...
)
end
- Library to convert Oz values to JSON (with UTF-8 encoding):
/jzon/JSON.oz.
Example (extract from /roads/examples/JSON.oz):
...
functor Info
import
JSON at 'x-ozlib://wmeyer/jzon/JSON.ozf'
export
'':Info
MimeType
define
MimeType = mimeType(application json)
fun {Info S}
{S.validateParameters [type]}
{JSON.encode
case {S.getParam type}.original
of "sapiens" then object('from':200000 to:"now")
[] "neanderthal" then object('from':400000 to:28000)
[] "heidelberg" then object('from':600000 to:200000)
[] "erectus" then object('from':1800000 to:40000)
else null
end
}
end
end
...
- no special support for Ajax
- but also no obstacles if you want to use it
- Simple example:
roads/examples/Ajax.oz, http://localhost:8080/select
CSS code can be represented by Oz records and embedded in HTML.
Example:
style(type:"text/css"
css('div'#content
'border-width':".2em"
'border-style':solid
'border-color':"#900"))
Take a look at sawhorse/common/Css.oz to find out more about the syntax.
-
{Session.hasCookie Key}: check whether a certain cookie was sent with the current request -
{Session.getCookie Key}: get the value of a cookie as a string -
{Session.getCookieExt Key}: get the cookie as a value likecookie(value:Val path:"/" domain:nil version:0). “path”, “domain” and “version” will only be there if they were in the original cookie header. -
{Session.setCookie Key Cookie}: set a cookie to be send with the current response. “Cookie” may simply be a (virtual) string value. In this case, the cookie will have a default path that limits it to the current application. Cookie may also be a value likecookie(value:"String" path:"/" httpOnly:unit otherAttribute:"foobar").
- By default, all URLs are valid for the duration of a session (except for bookmarkable URLs).
- It is possible to expire URLs explicitly after a defined period of time or after a period of inactivity.
-
{Session.expireLinksAfter Milliseconds}expires all URLs that are created from now on in the current function or in directly following functions, after a period of time. -
{Session.expireLinksAfterInactivity Milliseconds}does the same, but only if none of the links have been used for a period of time. -
C = {Session.createContext}: starts a context. You can explicitly expire all links collected in that context with{C expire}.
Functions Session.logTrace and Session.logError can be used to write log messages in the error log file (Sawhorse option errorLogFile). By default, these messages go to stdout.
Messages are filtered by the application-level option logLevel.
Previous: Application Development Next: Future Development