Asynchronous updates.
authorrich <rich>
Mon, 20 Dec 2004 11:57:27 +0000 (11:57 +0000)
committerrich <rich>
Mon, 20 Dec 2004 11:57:27 +0000 (11:57 +0000)
html/_js/editor.js
scripts/preview.ml
templates/edit.html

index d4dc063..1bf5b82 100644 (file)
@@ -1,51 +1,59 @@
 /* Javascript for OCAMLWIKI.
  * Copyright (C) 2004 Merjis Ltd.
- * $Id: editor.js,v 1.5 2004/11/14 14:23:27 rich Exp $
+ * $Id: editor.js,v 1.6 2004/12/20 11:57:27 rich Exp $
  */
 
-// Delay in milliseconds before updating.
-// We will adjust this during editing to take into account the actual
-// average round trip time measured during update_preview_now.
+// Delay in milliseconds between updates.
 var delay = 1000;
 
-// This is used to measure the average round trip time.
-var rtt_sum = 0;
-var rtt_n = 0;
-var rtt = 0;                   // Actual average RTT measured (ms).
-
-function update_preview (content_id, preview_id)
+// This function is called when the content has changed (eg. from a keypress).
+function content_changed (content_id, preview_id)
 {
-  // Updating is quite expensive, so only update after a period of apparent
-  // inactivity.
   var preview = document.getElementById (preview_id);
-  if (preview.timer) clearTimeout (preview.timer);
-  preview.timer =
-    setTimeout ("update_preview_now ('" + content_id + "', '" +
-               preview_id + "')", delay);
+
+  // Set a flag to indicate that the content has changed.
+  preview.updated = true;
+
+  // If there's not an asynchronous preview in progress at the moment,
+  // then start one.
+  start_update (content_id, preview_id, preview);
 }
 
-function update_preview_now (content_id, preview_id)
+function start_update (content_id, preview_id, preview)
 {
-  // Remove the timer.
-  var preview = document.getElementById (preview_id);
-  if (preview.timer) preview.timer = null;
+  if (!preview) preview = document.getElementById (preview_id);
+
+  // Do nothing if a preview is already in progress.
+  if (preview.in_progress) return;
 
   // Get the Wiki-markup content from the content textarea.
   var content = document.getElementById (content_id).value;
 
-  // Time how long it takes to send the document to the server.
-  var start_time = (new Date()).getTime();
-
   // Send the Wiki-markup to the server to be turned into XHTML.
   var http = document.all ?
     new ActiveXObject ('Microsoft.XMLHTTP') : new XMLHttpRequest ();
   if (http)
     {
-      http.open ('POST', '/_bin/preview.cmo', false);
+      // Set a handler for state changes.
+      http.onreadystatechange = function () {
+       preview_state_change (content_id, preview_id, preview, http);
+      };
+      // 'true' argument means we want to do this asynchronously.
+      http.open ('POST', '/_bin/preview.cmo', true);
       http.setRequestHeader ('Content-Type',
                             'application/x-www-form-urlencoded');
       http.send ('content=' + encodeURIComponent (content));
 
+      // A preview is "in progress".
+      preview.in_progress = true;
+      preview.updated = false;
+    }
+}
+
+function preview_state_change (content_id, preview_id, preview, http)
+{
+  if (http.readyState == 4) {  // If state is "loaded".
+    if (http.status == 200) {  // Request status is 200 OK.
       var xhtml = http.responseText;
 
       // Next line fails with my copy of IE if the text contains a
@@ -54,14 +62,38 @@ function update_preview_now (content_id, preview_id)
       preview.innerHTML = xhtml;
     }
 
-  // Finish timer and recompute RTT.
-  var end_time = (new Date()).getTime();
-  rtt_sum += end_time - start_time;
-  rtt_n++;
-  rtt = rtt_sum / rtt_n;
+    // A preview is not in progress now.
+    preview.in_progress = false;
+
+    // Have there been further updates since this preview started?  If so
+    // then we need to start another preview.
+    if (preview.updated) start_update (content_id, preview_id, preview);
+  }
+}
 
-  // Recompute the next delay period.
-  delay = rtt * 2;
+// Update the preview field synchronously.
+function synch_update (content_id, preview_id)
+{
+  var preview = document.getElementById (preview_id);
+
+  // Get the Wiki-markup content from the content textarea.
+  var content = document.getElementById (content_id).value;
+
+  // Send the Wiki-markup to the server to be turned into XHTML.
+  var http = document.all ?
+    new ActiveXObject ('Microsoft.XMLHTTP') : new XMLHttpRequest ();
+  if (http)
+    {
+      http.open ('POST', '/_bin/preview.cmo', false);
+      http.setRequestHeader ('Content-Type',
+                            'application/x-www-form-urlencoded');
+      http.send ('content=' + encodeURIComponent (content));
+
+      if (http.readyState == 4 && http.status == 200) {
+       var xhtml = http.responseText;
+       preview.innerHTML = xhtml;
+      }
+    }
 }
 
 // Initialise the edit_buttons for a section.
@@ -149,7 +181,7 @@ function link (content_id, preview_id)
   };
   var warning = "Select an area of text first.";
   replace_selection (content_id, fn, warning);
-  update_preview_now (content_id, preview_id);
+  start_update (content_id, preview_id);
 }
 
 function bold (content_id, preview_id)
@@ -162,7 +194,7 @@ function bold (content_id, preview_id)
   };
   var warning = "Select an area of text first.";
   replace_selection (content_id, fn, warning);
-  update_preview_now (content_id, preview_id);
+  start_update (content_id, preview_id);
 }
 
 function italic (content_id, preview_id)
@@ -175,7 +207,7 @@ function italic (content_id, preview_id)
   };
   var warning = "Select an area of text first.";
   replace_selection (content_id, fn, warning);
-  update_preview_now (content_id, preview_id);
+  start_update (content_id, preview_id);
 }
 
 function strikeout (content_id, preview_id)
@@ -188,7 +220,7 @@ function strikeout (content_id, preview_id)
   };
   var warning = "Select an area of text first.";
   replace_selection (content_id, fn, warning);
-  update_preview_now (content_id, preview_id);
+  start_update (content_id, preview_id);
 }
 
 function bullet (content_id, preview_id)
@@ -201,7 +233,7 @@ function bullet (content_id, preview_id)
   };
   var warning = "Select some whole lines of text first.";
   replace_selection (content_id, fn, warning);
-  update_preview_now (content_id, preview_id);
+  start_update (content_id, preview_id);
 }
 
 function numbered (content_id, preview_id)
@@ -214,5 +246,5 @@ function numbered (content_id, preview_id)
   };
   var warning = "Select some whole lines of text first.";
   replace_selection (content_id, fn, warning);
-  update_preview_now (content_id, preview_id);
+  start_update (content_id, preview_id);
 }
index 10e8a4a..df0da8d 100644 (file)
@@ -1,7 +1,7 @@
 (* COCANWIKI - a wiki written in Objective CAML.
  * Written by Richard W.M. Jones <rich@merjis.com>.
  * Copyright (C) 2004 Merjis Ltd.
- * $Id: preview.ml,v 1.6 2004/10/23 12:00:24 rich Exp $
+ * $Id: preview.ml,v 1.7 2004/12/20 11:57:28 rich Exp $
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -29,6 +29,12 @@ open Printf
 
 open Cocanwiki
 
+(* This script is called very frequently indeed during editing.  It should
+ * therefore be as short and fast as possible.  There is a case for
+ * special-casing the register_script code to avoid doing as much
+ * start-up SQL as possible.
+ *)
+
 let run r (q : cgi) (dbh : Dbi.connection) hostid _ _ =
   let content = q#param "content" in
   let xhtml = Wikilib.xhtml_of_content dbh hostid content in
index 98edc7d..c620201 100644 (file)
@@ -62,10 +62,10 @@ Redirect to (if given, page contents are ignored):
 </div>
 <input class="heading" name="sectionname_::ordering::" value="::sectionname_html_tag::" size="40"/><br/>
 <div class="edit_buttons" id="edit_buttons_::ordering::"></div>
-<textarea id="content_::ordering::" name="content_::ordering::" rows="14" cols="80" onkeypress="update_preview ('content_::ordering::', 'preview_::ordering::')">::content_html_textarea::</textarea>
+<textarea id="content_::ordering::" name="content_::ordering::" rows="14" cols="80" onkeypress="content_changed ('content_::ordering::', 'preview_::ordering::')">::content_html_textarea::</textarea>
 <div class="preview" id="preview_::ordering::"><noscript>(If you had Javascript, you would see a preview of your edits here)</noscript></div>
 <script type="text/javascript"><!--
-update_preview_now ('content_::ordering::', 'preview_::ordering::');
+synch_update ('content_::ordering::', 'preview_::ordering::');
 init_edit_buttons ('edit_buttons_::ordering::', 'content_::ordering::', 'preview_::ordering::');
 //--></script>
 <abbr class="css_id" title="Assign a stylesheet ID to this block of text to enable further styling">CSS id</abbr>: <input class="css_id" name="divname_::ordering::" value="::divname_html_tag::" size="8"/>