changeset 1702:06791667937e

New JavaScript FFI function: setInnerHTML
author Adam Chlipala <adam@chlipala.net>
date Wed, 14 Mar 2012 10:10:56 -0400
parents 6a4461757829
children 6f2f74cc4ead
files doc/manual.tex lib/js/urweb.js tests/ffi.urs tests/setInner.js tests/setInner.ur tests/setInner.urp
diffstat 6 files changed, 54 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/doc/manual.tex	Wed Mar 14 09:06:23 2012 -0400
+++ b/doc/manual.tex	Wed Mar 14 10:10:56 2012 -0400
@@ -2384,6 +2384,8 @@
 
 \item The behavior of the \cd{<dyn>} pseudo-tag may be mimicked by following the right convention in a piece of HTML source code with a type like $\mt{xbody}$.  Such a piece of source code may be encoded with a JavaScript string.  To insert a dynamic section, include a \cd{<script>} tag whose content is just a call \cd{dyn(pnode, s)}.  The argument \cd{pnode} specifies what the relevant enclosing parent tag is.  Use value \cd{"tr"} when the immediate parent is \cd{<tr>}, use \cd{"table"} when the immediate parent is \cd{<table>}, and use \cd{"span"} otherwise.  The argument \cd{s} is a string-valued signal giving the HTML code to be inserted at this point.  As with the usual \cd{<dyn>} tag, that HTML subtree is automatically updated as the value of \cd{s} changes.
 
+\item There is only one supported method of taking HTML values generated in Ur/Web code and adding them to the DOM in FFI JavaScript code: call \cd{setInnerHTML(node, html)} to add HTML content \cd{html} within DOM node \cd{node}.  Merely running \cd{node.innerHTML = html} is not guaranteed to get the job done, though programmers familiar with JavaScript will probably find it useful to think of \cd{setInnerHTML} as having this effect.  The unusual idiom is required because Ur/Web uses a nonstandard representation of HTML, to support infinite nesting of code that may generate code that may generate code that....  The \cd{node} value must already be in the DOM tree at the point when \cd{setInnerHTML} is called, because some plumbing must be set up to interact sensibly with \cd{<dyn>} tags. 
+
 \item It is possible to use the more standard ``IDs and mutation'' style of JavaScript coding, though that style is unidiomatic for Ur/Web and should be avoided wherever possible.  Recall the abstract type $\mt{id}$ and its constructor $\mt{fresh}$, which can be used to generate new unique IDs in Ur/Web code.  Values of this type are represented as strings in JavaScript, and a function \cd{fresh()} is available to generate new unique IDs.  Application-specific ID generation schemes may cause bad interactions with Ur/Web code that also generates IDs, so the recommended approach is to produce IDs only via calls to \cd{fresh()}.  FFI code shouldn't depend on the ID generation scheme (on either server side or client side), but it is safe to include these IDs in tag attributes (in either server-side or client-side code) and manipulate the associated DOM nodes in the standard way (in client-side code).  Be forewarned that this kind of imperative DOM manipulation may confuse the Ur/Web runtime system and interfere with proper behavior of tags like \cd{<dyn>}!
 \end{itemize}
 
--- a/lib/js/urweb.js	Wed Mar 14 09:06:23 2012 -0400
+++ b/lib/js/urweb.js	Wed Mar 14 10:10:56 2012 -0400
@@ -836,6 +836,38 @@
     populate(x);
 }
 
+function setInnerHTML(node, html) {
+    var x;
+
+    if (node.previousSibling && node.previousSibling.closures != undefined) {
+        x = node.previousSibling;
+
+        for (var ls = x.closures; ls; ls = ls.next)
+            freeClosure(ls.data);
+
+        if (node.getElementsByTagName) {
+            var arr = node.getElementsByTagName("script");
+            for (var i = 0; i < arr.length; ++i)
+                killScript(arr[i]);
+        }
+    } else {
+        x = document.createElement("script");
+        x.dead = false;
+        x.sources = null;
+
+        if (node.parentNode)
+            node.parentNode.insertBefore(x, node);
+        else
+            whine("setInnerHTML: node is not already in the DOM tree");
+    }
+
+    var cls = {v : null};
+    var html = flatten(cls, html);
+    x.closures = cls.v;
+    node.innerHTML = html;
+    runScripts(node);
+}
+
 function input(x, s, recreate, type, name) {
     if (name) x.name = name;
     if (type) x.type = type;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/ffi.urs	Wed Mar 14 10:10:56 2012 -0400
@@ -0,0 +1,1 @@
+val setIt : id -> xbody -> transaction unit
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/setInner.js	Wed Mar 14 10:10:56 2012 -0400
@@ -0,0 +1,3 @@
+function setIt(id, html) {
+    setInnerHTML(document.getElementById(id), html);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/setInner.ur	Wed Mar 14 10:10:56 2012 -0400
@@ -0,0 +1,9 @@
+fun main () : transaction page =
+    x <- fresh;
+    s <- source 0;
+    q <- source "";
+    return <xml><body>
+      <span id={x}/>
+      <button onclick={v <- get q; set q (v ^ "!"); Ffi.setIt x <xml><dyn signal={n <- signal s; return <xml>n = {[n]}</xml>}/>{[v]}</xml>}/>
+      <button onclick={n <- get s; set s (n + 1)}/>
+    </body></xml>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/setInner.urp	Wed Mar 14 10:10:56 2012 -0400
@@ -0,0 +1,7 @@
+rewrite all SetInner/*
+script http://localhost/setInner.js
+jsFunc Ffi.setIt=setIt
+benignEffectful Ffi.setIt
+ffi ffi
+
+setInner