changeset 1556:e1f5d9c4cc20

An abstract type of IDs
author Adam Chlipala <adam@chlipala.net>
date Sat, 03 Sep 2011 12:51:05 -0400
parents d5c961c709f9
children 4a13e1b73641
files doc/manual.tex include/urweb.h lib/js/urweb.js lib/ur/basis.urs src/c/urweb.c src/monoize.sml src/settings.sml tests/nextid.ur
diffstat 8 files changed, 56 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/doc/manual.tex	Sat Sep 03 11:48:12 2011 -0400
+++ b/doc/manual.tex	Sat Sep 03 12:51:05 2011 -0400
@@ -1983,6 +1983,17 @@
  \mt{val} \; \mt{onMouseup} : \mt{transaction} \; \mt{unit} \to \mt{transaction} \; \mt{unit}
 \end{array}$$
 
+\subsubsection{Node IDs}
+
+There is an abstract type of node IDs that may be assigned to \cd{id} attributes of most HTML tags.
+
+$$\begin{array}{l}
+  \mt{type} \; \mt{id} \\
+  \mt{val} \; \mt{fresh} : \mt{transaction} \; \mt{id}
+\end{array}$$
+
+The \cd{fresh} function is allowed on both server and client, but there is no other way to create IDs, which includes lack of a way to force an ID to match a particular string.  The only semantic importance of IDs within Ur/Web is in uses of the HTML \cd{<label>} tag.  IDs play a much more central role in mainstream JavaScript programming, but Ur/Web uses a very different model to enable changes to particular nodes of a page tree, as the next manual subsection explains.  IDs may still be useful in interfacing with JavaScript code (for instance, through Ur/Web's FFI).
+
 \subsubsection{Functional-Reactive Page Generation}
 
 Most approaches to ``AJAX''-style coding involve imperative manipulation of the DOM tree representing an HTML document's structure.  Ur/Web follows the \emph{functional-reactive} approach instead.  Programs may allocate mutable \emph{sources} of arbitrary types, and an HTML page is effectively a pure function over the latest values of the sources.  The page is not mutated directly, but rather it changes automatically as the sources are mutated.
--- a/include/urweb.h	Sat Sep 03 11:48:12 2011 -0400
+++ b/include/urweb.h	Sat Sep 03 12:51:05 2011 -0400
@@ -346,4 +346,6 @@
 
 void uw_cutErrorLocation(char *);
 
+uw_Basis_string uw_Basis_fresh(uw_context);
+
 #endif
--- a/lib/js/urweb.js	Sat Sep 03 11:48:12 2011 -0400
+++ b/lib/js/urweb.js	Sat Sep 03 12:51:05 2011 -0400
@@ -1374,5 +1374,14 @@
     return u;
 }
 
+
+// ID generation
+
+var nextId = 0;
+
+function fresh() {
+    return (--nextId).toString();
+}
+
 // App-specific code
 
--- a/lib/ur/basis.urs	Sat Sep 03 11:48:12 2011 -0400
+++ b/lib/ur/basis.urs	Sat Sep 03 12:51:05 2011 -0400
@@ -668,12 +668,15 @@
 val effectfulUrl : (option queryString -> transaction page) -> url
 val redirect : t ::: Type -> url -> transaction t
 
+type id
+val fresh : transaction id
+
 val dyn : ctx ::: {Unit} -> use ::: {Type} -> bind ::: {Type} -> [ctx ~ body] => unit
           -> tag [Signal = signal (xml (body ++ ctx) use bind)] (body ++ ctx) [] use bind
 
 val head : unit -> tag [] html head [] []
 val title : unit -> tag [] head [] [] []
-val link : unit -> tag [Id = string, Rel = string, Typ = string, Href = url, Media = string] head [] [] []
+val link : unit -> tag [Id = id, Rel = string, Typ = string, Href = url, Media = string] head [] [] []
 
 val body : unit -> tag [Onload = transaction unit, Onresize = transaction unit, Onunload = transaction unit]
                        html body [] []
@@ -686,7 +689,7 @@
                            -> [[Body] ~ ctx] =>
                                  unit -> tag attrs ([Body] ++ ctx) [] [] []
 
-val br : bodyTagStandalone [Id = int]
+val br : bodyTagStandalone [Id = id]
 
 con focusEvents = [Onblur = transaction unit, Onfocus = transaction unit]
 con mouseEvents = [Onclick = transaction unit, Ondblclick = transaction unit,
@@ -701,8 +704,8 @@
 con boxEvents = focusEvents ++ mouseEvents ++ keyEvents ++ resizeEvents
 con tableEvents = focusEvents ++ mouseEvents ++ keyEvents
 
-con boxAttrs = [Id = string, Title = string] ++ boxEvents
-con tableAttrs = [Id = string, Title = string] ++ tableEvents
+con boxAttrs = [Id = id, Title = string] ++ boxEvents
+con tableAttrs = [Id = id, Title = string] ++ tableEvents
 
 val span : bodyTag boxAttrs
 val div : bodyTag boxAttrs
@@ -789,7 +792,7 @@
 val postData : postBody -> string
 
 con radio = [Body, Radio]
-val radio : formTag string radio [Id = string]
+val radio : formTag string radio [Id = id]
 val radioOption : unit -> tag ([Value = string, Checked = bool] ++ boxAttrs) radio [] [] []
 
 con select = [Select]
--- a/src/c/urweb.c	Sat Sep 03 11:48:12 2011 -0400
+++ b/src/c/urweb.c	Sat Sep 03 12:51:05 2011 -0400
@@ -463,6 +463,8 @@
   uw_Basis_postBody postBody;
   uw_Basis_string queryString;
 
+  unsigned nextId;
+
   char error_message[ERROR_BUF_LEN];
 };
 
@@ -532,6 +534,8 @@
 
   ctx->queryString = NULL;
 
+  ctx->nextId = 0;
+
   return ctx;
 }
 
@@ -608,6 +612,7 @@
   ctx->used_transactionals = 0;
   ctx->script_header = "";
   ctx->queryString = NULL;
+  ctx->nextId = 0;
 }
 
 void uw_reset_keep_request(uw_context ctx) {
@@ -3947,3 +3952,7 @@
 
   memmove(s, s2+2, strlen(s2+2)+1);
 }
+
+uw_Basis_string uw_Basis_fresh(uw_context ctx) {
+  return uw_Basis_htmlifyInt(ctx, ctx->nextId++);
+}
--- a/src/monoize.sml	Sat Sep 03 11:48:12 2011 -0400
+++ b/src/monoize.sml	Sat Sep 03 12:51:05 2011 -0400
@@ -219,6 +219,7 @@
                   | L.CApp ((L.CApp ((L.CFfi ("Basis", "xhtml"), _), _), _), _) =>
                     (L'.TFfi ("Basis", "string"), loc)
                   | L.CFfi ("Basis", "css_class") => (L'.TFfi ("Basis", "string"), loc)
+                  | L.CFfi ("Basis", "id") => (L'.TFfi ("Basis", "string"), loc)
 
                   | L.CApp ((L.CFfi ("Basis", "serialized"), _), _) =>
                     (L'.TFfi ("Basis", "string"), loc)
--- a/src/settings.sml	Sat Sep 03 11:48:12 2011 -0400
+++ b/src/settings.sml	Sat Sep 03 12:51:05 2011 -0400
@@ -155,7 +155,8 @@
                         "onKeypress",
                         "onKeyup",
                         "onMousedown",
-                        "onMouseup"]
+                        "onMouseup",
+                        "fresh"]
 
 val benign = ref benignBase
 fun setBenignEffectful ls = benign := S.addList (benignBase, ls)
@@ -278,7 +279,9 @@
                           ("onKeypress", "uw_onKeypress"),
                           ("onKeyup", "uw_onKeyup"),
                           ("onMousedown", "uw_onMousedown"),
-                          ("onMouseup", "uw_onMouseup")]
+                          ("onMouseup", "uw_onMouseup"),
+
+                          ("fresh", "fresh")]
 val jsFuncs = ref jsFuncsBase
 fun setJsFuncs ls = jsFuncs := foldl (fn ((k, v), m) => M.insert (m, k, v)) jsFuncsBase ls
 fun jsFunc x = M.find (!jsFuncs, x)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/nextid.ur	Sat Sep 03 12:51:05 2011 -0400
@@ -0,0 +1,11 @@
+fun main () : transaction page =
+    id1 <- fresh;
+    id2 <- fresh;
+    id3 <- fresh;
+    idS <- source id3;
+    return <xml><body>
+      <span id={id1}>Hi</span> <span id={id2}>there!</span><br/><br/>
+      <dyn signal={idS <- signal idS; return <xml><span id={idS}>Whoa-hoa!</span></xml>}/>
+      <button onclick={id <- fresh; set idS id}/>
+      Source: <dyn signal={idS <- signal idS; return (txt (<xml><span id={idS}>Whoa-hoa!</span></xml> : xbody))}/>
+    </body></xml>