summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Adam Chlipala <adam@chlipala.net>2012-03-14 10:10:56 -0400
committerGravatar Adam Chlipala <adam@chlipala.net>2012-03-14 10:10:56 -0400
commite90f7afc607a0af326c87ebb184b7eacfee8b92b (patch)
tree30712b447483786217c20367a7f29491305fe2db
parent3b4137c893c21d68a1dbecdcb3acd865dd32057d (diff)
New JavaScript FFI function: setInnerHTML
-rw-r--r--doc/manual.tex2
-rw-r--r--lib/js/urweb.js32
-rw-r--r--tests/ffi.urs1
-rw-r--r--tests/setInner.js3
-rw-r--r--tests/setInner.ur9
-rw-r--r--tests/setInner.urp7
6 files changed, 54 insertions, 0 deletions
diff --git a/doc/manual.tex b/doc/manual.tex
index 45c5479f..c18a594c 100644
--- a/doc/manual.tex
+++ b/doc/manual.tex
@@ -2384,6 +2384,8 @@ It is possible to write JavaScript FFI code that interacts with the functional-r
\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}
diff --git a/lib/js/urweb.js b/lib/js/urweb.js
index e6c2124f..5a18e17c 100644
--- a/lib/js/urweb.js
+++ b/lib/js/urweb.js
@@ -836,6 +836,38 @@ function dyn(pnode, s) {
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;
diff --git a/tests/ffi.urs b/tests/ffi.urs
new file mode 100644
index 00000000..f5b719fa
--- /dev/null
+++ b/tests/ffi.urs
@@ -0,0 +1 @@
+val setIt : id -> xbody -> transaction unit
diff --git a/tests/setInner.js b/tests/setInner.js
new file mode 100644
index 00000000..4f71ac26
--- /dev/null
+++ b/tests/setInner.js
@@ -0,0 +1,3 @@
+function setIt(id, html) {
+ setInnerHTML(document.getElementById(id), html);
+}
diff --git a/tests/setInner.ur b/tests/setInner.ur
new file mode 100644
index 00000000..3032f8a7
--- /dev/null
+++ b/tests/setInner.ur
@@ -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>
diff --git a/tests/setInner.urp b/tests/setInner.urp
new file mode 100644
index 00000000..cfbc6a50
--- /dev/null
+++ b/tests/setInner.urp
@@ -0,0 +1,7 @@
+rewrite all SetInner/*
+script http://localhost/setInner.js
+jsFunc Ffi.setIt=setIt
+benignEffectful Ffi.setIt
+ffi ffi
+
+setInner