diff options
author | Adam Chlipala <adam@chlipala.net> | 2012-03-14 10:10:56 -0400 |
---|---|---|
committer | Adam Chlipala <adam@chlipala.net> | 2012-03-14 10:10:56 -0400 |
commit | e90f7afc607a0af326c87ebb184b7eacfee8b92b (patch) | |
tree | 30712b447483786217c20367a7f29491305fe2db | |
parent | 3b4137c893c21d68a1dbecdcb3acd865dd32057d (diff) |
New JavaScript FFI function: setInnerHTML
-rw-r--r-- | doc/manual.tex | 2 | ||||
-rw-r--r-- | lib/js/urweb.js | 32 | ||||
-rw-r--r-- | tests/ffi.urs | 1 | ||||
-rw-r--r-- | tests/setInner.js | 3 | ||||
-rw-r--r-- | tests/setInner.ur | 9 | ||||
-rw-r--r-- | tests/setInner.urp | 7 |
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 |