summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Ziv Scully <ziv@mit.edu>2014-11-24 20:47:38 -0500
committerGravatar Ziv Scully <ziv@mit.edu>2014-11-24 20:47:38 -0500
commitb59e6e96601c09bd97a4cce881c9b9f8bf8816a3 (patch)
treec2714306eb85c1e0d144089bbc3f6ed51e58cdda
parenta413fc1a42bf0fbee27c2f22cc8e9ca9b17b9edf (diff)
parentf3e50f123c33c26038b601475eeaa619526ad7ab (diff)
Merge.
-rw-r--r--doc/manual.tex4
-rw-r--r--include/urweb/urweb_cpp.h3
-rw-r--r--lib/js/urweb.js67
-rw-r--r--lib/ur/basis.urs59
-rw-r--r--src/c/http.c2
-rw-r--r--src/c/urweb.c13
-rw-r--r--src/monoize.sml125
-rw-r--r--src/urweb.grm9
-rw-r--r--src/urweb.lex8
-rw-r--r--tests/ctextbox.urp1
-rw-r--r--tests/html5_cforms.ur56
-rw-r--r--tests/html5_forms.ur45
-rw-r--r--tests/html5_forms.urs1
13 files changed, 300 insertions, 93 deletions
diff --git a/doc/manual.tex b/doc/manual.tex
index 0550d081..5935ccbf 100644
--- a/doc/manual.tex
+++ b/doc/manual.tex
@@ -2348,13 +2348,15 @@ $$\begin{array}{rrcll}
&&& \texttt{<}g\texttt{>}l^*\texttt{</}x\texttt{>} & \textrm{tag with children} \\
&&& \{e\} & \textrm{computed XML fragment} \\
&&& \{[e]\} & \textrm{injection of an Ur expression, via the $\mt{Top}.\mt{txt}$ function} \\
- \textrm{Tag} & g &::=& h \; (x = v)^* \\
+ \textrm{Tag} & g &::=& h \; (x [= v])^* \\
\textrm{Tag head} & h &::=& x & \textrm{tag name} \\
&&& h\{c\} & \textrm{constructor parameter} \\
\textrm{Attribute value} & v &::=& \ell & \textrm{literal value} \\
&&& \{e\} & \textrm{computed value} \\
\end{array}$$
+When the optional $= v$ is omitted in an XML attribute, the attribute is assigned value $\mt{True}$ in Ur/Web, and it is rendered to HTML merely as including the attribute name without a value. If such a Boolean attribute is manually set to value $\mt{False}$, then it is omitted altogether in generating HTML.
+
Further, there is a special convenience and compatibility form for setting CSS classes of tags. If a \cd{class} attribute has a value that is a string literal, the literal is parsed in the usual HTML way and replaced with calls to appropriate Ur/Web combinators. Any dashes in the text are replaced with underscores to determine Ur identifiers. The same desugaring can be accessed in a normal expression context by calling the pseudo-function \cd{CLASS} on a string literal.
Similar support is provided for \cd{style} attributes. Normal CSS syntax may be used in string literals that are \cd{style} attribute values, and the desugaring may be accessed elsewhere with the pseudo-function \cd{STYLE}.
diff --git a/include/urweb/urweb_cpp.h b/include/urweb/urweb_cpp.h
index 39dc0bc0..be7be442 100644
--- a/include/urweb/urweb_cpp.h
+++ b/include/urweb/urweb_cpp.h
@@ -397,4 +397,7 @@ uw_Basis_string uw_Basis_blessData(struct uw_context *, uw_Basis_string);
extern const char uw_begin_xhtml[], uw_begin_html5[];
+int uw_remoteSock(struct uw_context *);
+void uw_set_remoteSock(struct uw_context *, int sock);
+
#endif
diff --git a/lib/js/urweb.js b/lib/js/urweb.js
index 5cc49fec..342dc943 100644
--- a/lib/js/urweb.js
+++ b/lib/js/urweb.js
@@ -1038,30 +1038,79 @@ function input(x, s, recreate, type, name) {
return x;
}
-function inp(s, name) {
+function inpt(type, s, name) {
if (suspendScripts)
return;
var x = input(document.createElement("input"), s,
- function(x) { return function(v) { if (x.value != v) x.value = v; }; }, "text", name);
+ function(x) { return function(v) { if (x.value != v) x.value = v; }; }, type, name);
x.value = s.data;
x.onkeyup = x.oninput = x.onchange = x.onpropertychange = function() { sv(s, x.value) };
return x;
}
+function inp(s, name) {
+ return inpt("text", s, name);
+}
+
function password(s, name) {
- if (suspendScripts)
- return;
+ return inpt("password", s, name);
+}
- var x = input(document.createElement("input"), s,
- function(x) { return function(v) { if (x.value != v) x.value = v; }; }, "password", name);
- x.value = s.data;
- x.onkeyup = x.oninput = x.onchange = x.onpropertychange = function() { sv(s, x.value) };
+function email(s, name) {
+ return inpt("email", s, name);
+}
- return x;
+function search(s, name) {
+ return inpt("search", s, name);
+}
+
+function url(s, name) {
+ return inpt("url", s, name);
+}
+
+function tel(s, name) {
+ return inpt("tel", s, name);
+}
+
+function color(s, name) {
+ return inpt("color", s, name);
}
+function number(s, name) {
+ return inpt("number", s, name);
+}
+
+function range(s, name) {
+ return inpt("range", s, name);
+}
+
+function date(s, name) {
+ return inpt("date", s, name);
+}
+
+function datetime(s, name) {
+ return inpt("datetime", s, name);
+}
+
+function datetime_local(s, name) {
+ return inpt("datetime-local", s, name);
+}
+
+function month(s, name) {
+ return inpt("month", s, name);
+}
+
+function week(s, name) {
+ return inpt("week", s, name);
+}
+
+function time(s, name) {
+ return inpt("time", s, name);
+}
+
+
function selectValue(x) {
if (x.options.length == 0)
return "";
diff --git a/lib/ur/basis.urs b/lib/ur/basis.urs
index 5d0a0c8a..326563d6 100644
--- a/lib/ur/basis.urs
+++ b/lib/ur/basis.urs
@@ -948,15 +948,40 @@ con formTag = fn (ty :: Type) (inner :: {Unit}) (attrs :: {Type}) =>
-> [[Form] ~ ctx] =>
nm :: Name -> unit
-> tag attrs ([Form] ++ ctx) inner [] [nm = ty]
+
+con inputAttrs = [Required = bool, Autofocus = bool]
+
+
val hidden : formTag string [] [Data = data_attr, Id = string, Value = string]
val textbox : formTag string [] ([Value = string, Size = int, Placeholder = string, Source = source string, Onchange = transaction unit,
- Ontext = transaction unit] ++ boxAttrs)
-val password : formTag string [] ([Value = string, Size = int, Placeholder = string, Onchange = transaction unit] ++ boxAttrs)
+ Ontext = transaction unit] ++ boxAttrs ++ inputAttrs)
+val password : formTag string [] ([Value = string, Size = int, Placeholder = string, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs)
val textarea : formTag string [] ([Rows = int, Cols = int, Onchange = transaction unit,
- Ontext = transaction unit] ++ boxAttrs)
+ Ontext = transaction unit] ++ boxAttrs ++ inputAttrs)
val checkbox : formTag bool [] ([Checked = bool, Onchange = transaction unit] ++ boxAttrs)
+(* HTML5 widgets galore! *)
+
+type textWidget = formTag string [] ([Value = string, Size = int, Placeholder = string, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs)
+
+val email : textWidget
+val search : textWidget
+val url_ : textWidget
+val tel : textWidget
+val color : textWidget
+
+val number : formTag float [] ([Value = float, Min = float, Max = float, Step = float, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs)
+val range : formTag float [] ([Value = float, Min = float, Max = float, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs)
+val date : formTag string [] ([Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs)
+val datetime : formTag string [] ([Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs)
+val datetime_local : formTag string [] ([Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs)
+val month : formTag string [] ([Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs)
+val week : formTag string [] ([Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs)
+val timeInput : formTag string [] ([Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs)
+
+
+
type file
val fileName : file -> option string
val fileMimeType : file -> string
@@ -1011,19 +1036,35 @@ con cformTag = fn (attrs :: {Type}) (inner :: {Unit}) =>
-> [[Body] ~ ctx] => [[Body] ~ inner] =>
unit -> tag attrs ([Body] ++ ctx) ([Body] ++ inner) [] []
-val ctextbox : cformTag ([Value = string, Size = int, Source = source string, Placeholder = string, Onchange = transaction unit,
- Ontext = transaction unit] ++ boxAttrs) []
-val cpassword : cformTag ([Value = string, Size = int, Source = source string, Placeholder = string, Onchange = transaction unit,
- Ontext = transaction unit] ++ boxAttrs) []
+type ctext = cformTag ([Value = string, Size = int, Source = source string, Placeholder = string,
+ Onchange = transaction unit, Ontext = transaction unit] ++ boxAttrs ++ inputAttrs) []
+
+val ctextbox : ctext
+val cpassword : ctext
+val cemail : ctext
+val csearch : ctext
+val curl : ctext
+val ctel : ctext
+val ccolor : ctext
+
+val cnumber : cformTag ([Source = source float, Value = float, Min = float, Max = float, Step = float, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) []
+val crange : cformTag ([Source = source float, Value = float, Min = float, Max = float, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) []
+val cdate : cformTag ([Source = source string, Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) []
+val cdatetime : cformTag ([Source = source string, Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) []
+val cdatetime_local : cformTag ([Source = source string, Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) []
+val cmonth : cformTag ([Source = source string, Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) []
+val cweek : cformTag ([Source = source string, Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) []
+val ctime : cformTag ([Source = source string, Value = string, Min = string, Max = string, Size = int, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) []
+
val button : cformTag ([Value = string] ++ boxAttrs) []
-val ccheckbox : cformTag ([Value = bool, Size = int, Source = source bool, Onchange = transaction unit] ++ boxAttrs) []
+val ccheckbox : cformTag ([Value = bool, Size = int, Source = source bool, Onchange = transaction unit] ++ boxAttrs ++ inputAttrs) []
val cselect : cformTag ([Source = source string, Onchange = transaction unit] ++ boxAttrs) [Cselect]
val coption : unit -> tag [Value = string, Selected = bool] [Cselect, Body] [] [] []
val ctextarea : cformTag ([Value = string, Rows = int, Cols = int, Source = source string, Onchange = transaction unit,
- Ontext = transaction unit] ++ boxAttrs) []
+ Ontext = transaction unit] ++ boxAttrs ++ inputAttrs) []
(*** Tables *)
diff --git a/src/c/http.c b/src/c/http.c
index 9651a216..e6c7b1af 100644
--- a/src/c/http.c
+++ b/src/c/http.c
@@ -89,6 +89,8 @@ static void *worker(void *data) {
sock = uw_dequeue();
}
+ uw_set_remoteSock(ctx, sock);
+
qprintf("Handling connection with thread #%d.\n", me);
while (1) {
diff --git a/src/c/urweb.c b/src/c/urweb.c
index 8ecef7c5..4cd347b2 100644
--- a/src/c/urweb.c
+++ b/src/c/urweb.c
@@ -479,6 +479,8 @@ struct uw_context {
// For caching.
char *recording;
+
+ int remoteSock;
};
size_t uw_headers_max = SIZE_MAX;
@@ -564,6 +566,8 @@ uw_context uw_init(int id, uw_loggers *lg) {
ctx->recording = 0;
+ ctx->remoteSock = -1;
+
return ctx;
}
@@ -651,6 +655,7 @@ void uw_reset_keep_error_message(uw_context ctx) {
ctx->amInitializing = 0;
ctx->usedSig = 0;
ctx->needsResig = 0;
+ ctx->remoteSock = -1;
}
void uw_reset_keep_request(uw_context ctx) {
@@ -4471,3 +4476,11 @@ uw_Basis_string uw_Basis_blessData(uw_context ctx, uw_Basis_string s) {
return s;
}
+
+int uw_remoteSock(uw_context ctx) {
+ return ctx->remoteSock;
+}
+
+void uw_set_remoteSock(uw_context ctx, int sock) {
+ ctx->remoteSock = sock;
+}
diff --git a/src/monoize.sml b/src/monoize.sml
index d609a67d..2d225813 100644
--- a/src/monoize.sml
+++ b/src/monoize.sml
@@ -3289,7 +3289,7 @@ fun monoExp (env, st, fm) (all as (e, loc)) =
val (style, fm) = monoExp (env, st, fm) style
val (dynStyle, fm) = monoExp (env, st, fm) dynStyle
- val dynamics = ["dyn", "ctextbox", "cpassword", "ccheckbox", "cselect", "coption", "ctextarea", "active", "script"]
+ val dynamics = ["dyn", "ctextbox", "cpassword", "ccheckbox", "cselect", "coption", "ctextarea", "active", "script", "cemail", "csearch", "curl", "ctel", "ccolor"]
fun isSome (e, _) =
case e of
@@ -3589,6 +3589,29 @@ fun monoExp (env, st, fm) (all as (e, loc)) =
else
"span"
+ fun cinput (fallback, dynamic) =
+ case List.find (fn ("Source", _, _) => true | _ => false) attrs of
+ NONE =>
+ let
+ val (ts, fm) = tagStart "input"
+ in
+ ((L'.EStrcat (ts,
+ strH (" type=\"" ^ fallback ^ "\" />")),
+ loc), fm)
+ end
+ | SOME (_, src, _) =>
+ let
+ val sc = strcat [str (dynamic ^ "(exec("),
+ (L'.EJavaScript (L'.Script, src), loc),
+ str "))"]
+ val sc = setAttrs sc
+ in
+ (strcat [str "<script type=\"text/javascript\">",
+ sc,
+ str "</script>"],
+ fm)
+ end
+
val baseAll as (base, fm) =
case tag of
"body" => let
@@ -3669,6 +3692,19 @@ fun monoExp (env, st, fm) (all as (e, loc)) =
| _ => (Print.prefaces "Targs" (map (fn t => ("T", CorePrint.p_con env t)) targs);
raise Fail "No name passed to textbox tag"))
| "password" => input "password"
+ | "email" => input "email"
+ | "search" => input "search"
+ | "url_" => input "url"
+ | "tel" => input "tel"
+ | "color" => input "color"
+ | "number" => input "number"
+ | "range" => input "range"
+ | "date" => input "date"
+ | "datetime" => input "datetime"
+ | "datetime_local" => input "datetime-local"
+ | "month" => input "month"
+ | "week" => input "week"
+ | "timeInput" => input "time"
| "textarea" =>
(case targs of
[_, (L.CName name, _)] =>
@@ -3719,75 +3755,24 @@ fun monoExp (env, st, fm) (all as (e, loc)) =
| _ => (Print.prefaces "Targs" (map (fn t => ("T", CorePrint.p_con env t)) targs);
raise Fail "No name passed to lselect tag"))
- | "ctextbox" =>
- (case List.find (fn ("Source", _, _) => true | _ => false) attrs of
- NONE =>
- let
- val (ts, fm) = tagStart "input"
- in
- ((L'.EStrcat (ts,
- strH " type=\"text\" />"),
- loc), fm)
- end
- | SOME (_, src, _) =>
- let
- val sc = strcat [str "inp(exec(",
- (L'.EJavaScript (L'.Script, src), loc),
- str "))"]
- val sc = setAttrs sc
- in
- (strcat [str "<script type=\"text/javascript\">",
- sc,
- str "</script>"],
- fm)
- end)
-
- | "cpassword" =>
- (case List.find (fn ("Source", _, _) => true | _ => false) attrs of
- NONE =>
- let
- val (ts, fm) = tagStart "input"
- in
- ((L'.EStrcat (ts,
- strH " type=\"password\" />"),
- loc), fm)
- end
- | SOME (_, src, _) =>
- let
- val sc = strcat [str "password(exec(",
- (L'.EJavaScript (L'.Script, src), loc),
- str "))"]
- val sc = setAttrs sc
- in
- (strcat [str "<script type=\"text/javascript\">",
- sc,
- str "</script>"],
- fm)
- end)
-
- | "ccheckbox" =>
- (case List.find (fn ("Source", _, _) => true | _ => false) attrs of
- NONE =>
- let
- val (ts, fm) = tagStart "input type=\"checkbox\""
- in
- ((L'.EStrcat (ts,
- strH " />"),
- loc), fm)
- end
- | SOME (_, src, _) =>
- let
- val sc = strcat [str "chk(exec(",
- (L'.EJavaScript (L'.Script, src), loc),
- str "))"]
- val sc = setAttrs sc
- in
- (strcat [str "<script type=\"text/javascript\">",
- sc,
- str "</script>"],
- fm)
- end)
-
+ | "ctextbox" => cinput ("text", "inp")
+ | "cpassword" => cinput ("password", "password")
+ | "cemail" => cinput ("email", "email")
+ | "csearch" => cinput ("search", "search")
+ | "curl" => cinput ("url", "url")
+ | "ctel" => cinput ("tel", "tel")
+ | "ccolor" => cinput ("color", "color")
+
+ | "cnumber" => cinput ("number", "number")
+ | "crange" => cinput ("range", "range")
+ | "cdate" => cinput ("date", "date")
+ | "cdatetime" => cinput ("datetime", "datetime")
+ | "cdatetime_local" => cinput ("datetime-local", "datetime_local")
+ | "cmonth" => cinput ("month", "month")
+ | "cweek" => cinput ("week", "week")
+ | "ctime" => cinput ("time", "time")
+
+ | "ccheckbox" => cinput ("checkbox", "chk")
| "cselect" =>
(case List.find (fn ("Source", _, _) => true | _ => false) attrs of
NONE =>
diff --git a/src/urweb.grm b/src/urweb.grm
index edac345f..995d1329 100644
--- a/src/urweb.grm
+++ b/src/urweb.grm
@@ -221,6 +221,9 @@ val inDml = ref false
fun tagIn bt =
case bt of
"table" => "tabl"
+ | "url" => "url_"
+ | "datetime-local" => "datetime_local"
+ | "cdatetime-local" => "cdatetime_local"
| _ => bt
datatype prop_kind = Delete | Update
@@ -1747,6 +1750,12 @@ attr : SYMBOL EQ attrv (case SYMBOL of
else
attrv)
end)
+ | SYMBOL (let
+ val loc = s (SYMBOLleft, SYMBOLright)
+ in
+ Normal ((CName (makeAttr SYMBOL), loc),
+ (EVar (["Basis"], "True", Infer), loc))
+ end)
attrv : INT (EPrim (Prim.Int INT), s (INTleft, INTright))
| FLOAT (EPrim (Prim.Float FLOAT), s (FLOATleft, FLOATright))
diff --git a/src/urweb.lex b/src/urweb.lex
index 15ae448e..0d316ed2 100644
--- a/src/urweb.lex
+++ b/src/urweb.lex
@@ -277,19 +277,19 @@ xint = x[0-9a-fA-F][0-9a-fA-F];
continue ())
end);
-<INITIAL> "<" {id} "/>"=>(let
+<INITIAL> "<" {xmlid} "/>"=>(let
val tag = String.substring (yytext, 1, size yytext - 3)
in
Tokens.XML_BEGIN_END (tag, yypos, yypos + size yytext)
end);
-<INITIAL> "<" {id} ">"=> (let
+<INITIAL> "<" {xmlid} ">"=> (let
val tag = String.substring (yytext, 1, size yytext - 2)
in
YYBEGIN XML;
xmlTag := tag :: (!xmlTag);
Tokens.XML_BEGIN (tag, yypos, yypos + size yytext)
end);
-<XML> "</" {id} ">" => (let
+<XML> "</" {xmlid} ">" => (let
val id = String.substring (yytext, 2, size yytext - 3)
in
case !xmlTag of
@@ -304,7 +304,7 @@ xint = x[0-9a-fA-F][0-9a-fA-F];
Tokens.END_TAG (id, yypos, yypos + size yytext)
end);
-<XML> "<" {id} => (YYBEGIN XMLTAG;
+<XML> "<" {xmlid} => (YYBEGIN XMLTAG;
Tokens.BEGIN_TAG (String.extract (yytext, 1, NONE),
yypos, yypos + size yytext));
diff --git a/tests/ctextbox.urp b/tests/ctextbox.urp
index d5cb5e9f..5c6c5df8 100644
--- a/tests/ctextbox.urp
+++ b/tests/ctextbox.urp
@@ -1,4 +1,5 @@
debug
allow url http://localhost/*
+rewrite url Ctextbox/*
ctextbox
diff --git a/tests/html5_cforms.ur b/tests/html5_cforms.ur
new file mode 100644
index 00000000..be07d07e
--- /dev/null
+++ b/tests/html5_cforms.ur
@@ -0,0 +1,56 @@
+fun dn [a] (_ : show a) (x : source a) : xbody = <xml>
+ <dyn signal={v <- signal x; return (txt v)}/>
+</xml>
+
+fun main () : transaction page =
+ a <- source "";
+ b <- source True;
+ c <- source "a@b";
+ d <- source "";
+ e <- source "";
+ f <- source "";
+ g <- source 1.0;
+ h <- source 1.0;
+ i <- source "#CCCCCC";
+ j <- source "2014/11/16";
+ k <- source "2014/11/16 12:30:45";
+ l <- source "2014/11/16 12:30:45";
+ m <- source "2014/11";
+ n <- source "2014-W7";
+ o <- source "12:30:45";
+
+ return <xml><body>
+ <ctextbox source={a}/>
+ <ccheckbox source={b}/>
+ <cemail source={c}/>
+ <curl source={d}/>
+ <ctel source={e}/>
+ <csearch source={f}/>
+ <cnumber source={g} min={-10.0} max={10.0} step={0.5}/>
+ <crange source={h} min={-10.0} max={10.0}/>
+ <ccolor source={i}/>
+ <cdate source={j}/>
+ <cdatetime source={k}/>
+ <cdatetime-local source={l}/>
+ <cmonth source={m}/>
+ <cweek source={n}/>
+ <ctime source={o}/>
+
+ <hr/>
+
+ {dn a};
+ {dn b};
+ {dn c};
+ {dn d};
+ {dn e};
+ {dn f};
+ {dn g};
+ {dn h};
+ {dn i};
+ {dn j};
+ {dn k};
+ {dn l};
+ {dn m};
+ {dn n};
+ {dn o}
+ </body></xml>
diff --git a/tests/html5_forms.ur b/tests/html5_forms.ur
new file mode 100644
index 00000000..507ea3cf
--- /dev/null
+++ b/tests/html5_forms.ur
@@ -0,0 +1,45 @@
+fun handler r = return <xml><body>
+ A: {[r.A]}<br/>
+ B: {[r.B]}<br/>
+ C: {[r.C]}<br/>
+ D: {[r.D]}<br/>
+ E: {[r.E]}<br/>
+ F: {[r.F]}<br/>
+ G: {[r.G]}<br/>
+ H: {[r.H]}<br/>
+ I: {[r.I]}<br/>
+ J: {[r.J]}<br/>
+ K: {[r.K]}<br/>
+ L: {[r.L]}<br/>
+ M: {[r.M]}<br/>
+ N: {[r.N]}<br/>
+ O: {[r.O]}<br/>
+ P: {[r.P]}<br/>
+</body></xml>
+
+fun main () =
+ return <xml><body>
+ <form>
+ <textbox{#A} required placeholder="bobby"/>
+ <textbox{#B} placeholder="soggy" autofocus/>
+ <checkbox{#C}/>
+ <email{#D}/>
+ <url{#E}/>
+ <tel{#F}/>
+ <search{#G}/>
+
+ <hr/>
+
+ <color{#H}/>
+ <number{#I} min={17.0} max={32.8} value={20.6} step={2.5}/>
+ <range{#J} min={17.0} max={32.8} value={20.6}/>
+ <date{#K}/>
+ <datetime{#L}/>
+ <datetime-local{#M}/>
+ <month{#N}/>
+ <week{#O}/>
+ <timeInput{#P}/>
+
+ <submit action={handler}/>
+ </form>
+ </body></xml>
diff --git a/tests/html5_forms.urs b/tests/html5_forms.urs
new file mode 100644
index 00000000..6ac44e0b
--- /dev/null
+++ b/tests/html5_forms.urs
@@ -0,0 +1 @@
+val main : unit -> transaction page