diff options
author | Ziv Scully <ziv@mit.edu> | 2014-11-24 20:47:38 -0500 |
---|---|---|
committer | Ziv Scully <ziv@mit.edu> | 2014-11-24 20:47:38 -0500 |
commit | b59e6e96601c09bd97a4cce881c9b9f8bf8816a3 (patch) | |
tree | c2714306eb85c1e0d144089bbc3f6ed51e58cdda | |
parent | a413fc1a42bf0fbee27c2f22cc8e9ca9b17b9edf (diff) | |
parent | f3e50f123c33c26038b601475eeaa619526ad7ab (diff) |
Merge.
-rw-r--r-- | doc/manual.tex | 4 | ||||
-rw-r--r-- | include/urweb/urweb_cpp.h | 3 | ||||
-rw-r--r-- | lib/js/urweb.js | 67 | ||||
-rw-r--r-- | lib/ur/basis.urs | 59 | ||||
-rw-r--r-- | src/c/http.c | 2 | ||||
-rw-r--r-- | src/c/urweb.c | 13 | ||||
-rw-r--r-- | src/monoize.sml | 125 | ||||
-rw-r--r-- | src/urweb.grm | 9 | ||||
-rw-r--r-- | src/urweb.lex | 8 | ||||
-rw-r--r-- | tests/ctextbox.urp | 1 | ||||
-rw-r--r-- | tests/html5_cforms.ur | 56 | ||||
-rw-r--r-- | tests/html5_forms.ur | 45 | ||||
-rw-r--r-- | tests/html5_forms.urs | 1 |
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 |