From bad31ae944f074dab12f7a6d1362775d8f2b18dd Mon Sep 17 00:00:00 2001
From: Andreas Gohr <andi@splitbrain.org>
Date: Sat, 29 Oct 2005 02:26:52 +0200
Subject: [PATCH] JavaScript refactoring

This patch addes a first go on a central javascript and CSS dispatcher
which builds a single script from all needed scripts, does optimizing
and caching.

darcs-hash:20051029002652-7ad00-7558b569c2bf65f5e41820644580d97c62edd0d6.gz
---
 .../cases/lib/exe/jscss_js_compress.test.php  |  68 ++++
 inc/init.php                                  |   6 +-
 inc/template.php                              |  40 ++-
 lib/exe/jscss.php                             | 293 ++++++++++++++++++
 lib/scripts/ajax.js                           |  35 ++-
 lib/scripts/domLib.js                         |  38 +--
 lib/scripts/edit.js                           |  42 +--
 lib/scripts/events.js                         |  62 ++++
 lib/scripts/script.js                         |  33 +-
 lib/scripts/spellcheck.js                     |  42 +--
 lib/scripts/tw-sack.js                        | 243 +++++++--------
 11 files changed, 677 insertions(+), 225 deletions(-)
 create mode 100644 _test/cases/lib/exe/jscss_js_compress.test.php
 create mode 100644 lib/exe/jscss.php
 create mode 100644 lib/scripts/events.js

diff --git a/_test/cases/lib/exe/jscss_js_compress.test.php b/_test/cases/lib/exe/jscss_js_compress.test.php
new file mode 100644
index 000000000..3d9a8b627
--- /dev/null
+++ b/_test/cases/lib/exe/jscss_js_compress.test.php
@@ -0,0 +1,68 @@
+<?php
+
+require_once DOKU_INC.'lib/exe/jscss.php';
+
+
+class jscss_js_compress_test extends UnitTestCase {
+
+    function test_mlcom1(){
+        $text = '/**
+                  * A multi
+                  * line *test*
+                  * check
+                  */';
+        $this->assertEqual(js_compress($text), '');
+    }
+
+    function test_mlcom2(){
+        $text = 'var foo=6;/* another comment */';
+        $this->assertEqual(js_compress($text), 'var foo=6;');
+    }
+
+    function test_slcom1(){
+        $text = '// an comment';
+        $this->assertEqual(js_compress($text), '');
+    }
+
+    function test_slcom2(){
+        $text = 'var foo=6;// another comment ';
+        $this->assertEqual(js_compress($text), 'var foo=6;');
+    }
+
+    function test_slcom3(){
+        $text = 'var foo=6;// another comment / or something with // comments ';
+        $this->assertEqual(js_compress($text), 'var foo=6;');
+    }
+
+    function test_regex1(){
+        $text = 'foo.split( /[a-Z\/]*/ );';
+        $this->assertEqual(js_compress($text), 'foo.split(/[a-Z\/]*/);');
+    }
+
+    function test_dquot1(){
+        $text = 'var foo="Now what \'do we//get /*here*/ ?";';
+        $this->assertEqual(js_compress($text), $text);
+    }
+
+    function test_squot1(){
+        $text = "var foo='Now what \"do we//get /*here*/ ?';";
+        $this->assertEqual(js_compress($text), $text);
+    }
+
+    function test_nl1(){
+        $text = "var foo=6;\nvar baz=7;";
+        $this->assertEqual(js_compress($text), 'var foo=6;var baz=7;');
+    }
+
+    function test_lws1(){
+        $text = "  \t  var foo=6;";
+        $this->assertEqual(js_compress($text), 'var foo=6;');
+    }
+
+    function test_tws1(){
+        $text = "var foo=6;  \t  ";
+        $this->assertEqual(js_compress($text), 'var foo=6;');
+    }
+}
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
diff --git a/inc/init.php b/inc/init.php
index 108ed615c..41363f63d 100644
--- a/inc/init.php
+++ b/inc/init.php
@@ -46,8 +46,10 @@
   @ini_set('arg_separator.output', '&amp;');
 
   // init session
-  session_name("DokuWiki");
-  if (!headers_sent()) session_start();
+  if (!headers_sent() && !defined(NOSESSION)){
+    session_name("DokuWiki");
+    session_start();
+  }
 
   // kill magic quotes
   if (get_magic_quotes_gpc()) {
diff --git a/inc/template.php b/inc/template.php
index 0deeff32d..7401c3e62 100644
--- a/inc/template.php
+++ b/inc/template.php
@@ -195,7 +195,9 @@ function tpl_metaheaders(){
     ptln('<meta name="robots" content="noindex,nofollow" />',$it);
   }
 
-  // include some JavaScript language strings
+/*
+
+  // include some JavaScript language strings #FIXME still needed?
   ptln('<script language="javascript" type="text/javascript" charset="utf-8">',$it);
   ptln("  var alertText   = '".str_replace('\\\\n','\\n',addslashes($lang['qb_alert']))."'",$it);
   ptln("  var notSavedYet = '".str_replace('\\\\n','\\n',addslashes($lang['notsavedyet']))."'",$it);
@@ -204,6 +206,8 @@ function tpl_metaheaders(){
   ptln('</script>',$it);
  
   // load the default JavaScript files
+  ptln('<script language="javascript" type="text/javascript" charset="utf-8" src="'.
+       DOKU_BASE.'lib/scripts/events.js"></script>',$it);
   ptln('<script language="javascript" type="text/javascript" charset="utf-8" src="'.
        DOKU_BASE.'lib/scripts/script.js"></script>',$it);
   ptln('<script language="javascript" type="text/javascript" charset="utf-8" src="'.
@@ -218,13 +222,16 @@ function tpl_metaheaders(){
   ptln('<script language="javascript" type="text/javascript" charset="utf-8" src="'.
        DOKU_BASE.'lib/scripts/domTT.js"></script>',$it);
 
-
+  ptln('<script language="javascript" type="text/javascript" charset="utf-8">',$it);
+  ptln("addEvent(window,'load',function(){ajax_qsearch.init('qsearch_in','qsearch_out');});",$it);
+  ptln("addEvent(window,'load',function(){addEvent(document,'click',closePopups);});",$it);
+  ptln('</script>',$it);
 
   // editing functions
   if($ACT=='edit' || $ACT=='preview'){
     // add size control
     ptln('<script language="javascript" type="text/javascript" charset="utf-8">',$it);
-    ptln("addEvent(window,'onload',function(){initSizeCtl('sizectl','wikitext')});",$it+2);
+    ptln("addEvent(window,'load',function(){initSizeCtl('sizectl','wikitext')});",$it+2);
     ptln('</script>',$it);
 
     if($INFO['writable']){
@@ -243,21 +250,21 @@ function tpl_metaheaders(){
       // add toolbar
       require_once(DOKU_INC.'inc/toolbar.php');
       toolbar_JSdefines('toolbar');
-      ptln("addEvent(window,'onload',function(){initToolbar('toolbar','wikitext',toolbar);});",$it+2);
+      ptln("addEvent(window,'load',function(){initToolbar('toolbar','wikitext',toolbar);});",$it+2);
 
       // add pageleave check
-      ptln("addEvent(window,'onload',function(){initChangeCheck('".
+      ptln("addEvent(window,'load',function(){initChangeCheck('".
            str_replace('\\\\n','\\n',addslashes($lang['notsavedyet']))."');});",$it);
 
       // add lock timer
-      ptln("addEvent(window,'onload',function(){init_locktimer(".
+      ptln("addEvent(window,'load',function(){init_locktimer(".
            ($conf['locktime']-60).",'".
            str_replace('\\\\n','\\n',addslashes($lang['willexpire']))."');});",$it);
 
       // add spellchecker
       if($conf['spellchecker']){
         //init here
-        ptln("addEvent(window,'onload',function(){ ajax_spell.init('".
+        ptln("addEvent(window,'load',function(){ ajax_spell.init('".
                                        $lang['spell_start']."','".
                                        $lang['spell_stop']."','".
                                        $lang['spell_wait']."','".
@@ -268,6 +275,14 @@ function tpl_metaheaders(){
       ptln('</script>',$it);
     }
   }
+*/
+
+  $js_edit  = ($ACT=='edit' || $ACT=='preview') ? 1 : 0;
+  $js_write = ($INFO['writable']) ? 1 : 0;
+
+  ptln('<script language="javascript" type="text/javascript" charset="utf-8" src="'.
+       DOKU_BASE.'lib/exe/jscss.php?type=js&amp;edit='.$js_edit.'&amp;write='.$js_write.'"></script>',$it);
+
 
   // plugin stylesheets and Scripts
   plugin_printCSSJS();
@@ -533,20 +548,17 @@ function tpl_actionlink($type,$pre='',$suf=''){
  *
  * @author Andreas Gohr <andi@splitbrain.org>
  */
-function tpl_searchform(){
+function tpl_searchform($withajax=true){
   global $lang;
   global $ACT;
   
   print '<form action="'.wl().'" accept-charset="utf-8" class="search" name="search">';
   print '<input type="hidden" name="do" value="search" />';
   print '<input type="text" ';
-  
-  if ($ACT == 'search')
-    print 'value="'.htmlspecialchars($_REQUEST['id']).'" ';
-    
-  print 'id="qsearch_in" accesskey="f" name="id" class="edit" onkeyup="ajax_qsearch.call(\'qsearch_in\',\'qsearch_out\')" />';
+  if ($ACT == 'search') print 'value="'.htmlspecialchars($_REQUEST['id']).'" ';
+  print 'id="qsearch_in" accesskey="f" name="id" class="edit" />';
   print '<input type="submit" value="'.$lang['btn_search'].'" class="button" />';
-  print '<div id="qsearch_out" class="ajax_qsearch" onclick="this.style.display=\'none\'"></div>';
+  print '<div id="qsearch_out" class="ajax_qsearch JSpopup"></div>';
   print '</form>';
 }
 
diff --git a/lib/exe/jscss.php b/lib/exe/jscss.php
new file mode 100644
index 000000000..33d67eece
--- /dev/null
+++ b/lib/exe/jscss.php
@@ -0,0 +1,293 @@
+<?php
+/**
+ * DokuWiki JavaScript and CSS creator
+ *
+ * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author     Andreas Gohr <andi@splitbrain.org>
+ */
+
+if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
+define('NOSESSION',true); // we do not use a session or authentication here (better caching)
+require_once(DOKU_INC.'inc/init.php');
+require_once(DOKU_INC.'inc/pageutils.php');
+require_once(DOKU_INC.'inc/io.php');
+
+// Main (don't run when UNIT test)
+if(!defined('SIMPLE_TEST')){
+    if($_REQUEST['type'] == 'css'){
+        css_out();
+    }else{
+        header('Content-Type: text/javascript; charset=utf-8');
+        js_out();
+    }
+}
+
+
+// ---------------------- functions ------------------------------
+
+/**
+ * Output all needed JavaScript
+ *
+ * @todo   Add Whitespace and Comment Compression
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function js_out(){
+    global $conf;
+    global $lang;
+    $edit  = (bool) $_REQUEST['edit'];   // edit or preview mode?
+    $write = (bool) $_REQUEST['write'];  // writable?
+
+    // The generated script depends on some dynamic options
+    $cache = getCacheName($conf['lang'].$edit.$write,$ext='.js'); 
+
+    // Array of needed files
+    $files = array(
+                DOKU_INC.'lib/scripts/events.js',
+                DOKU_INC.'lib/scripts/script.js',
+                DOKU_INC.'lib/scripts/tw-sack.js',
+                DOKU_INC.'lib/scripts/ajax.js',
+                DOKU_INC.'lib/scripts/domLib.js',
+                DOKU_INC.'lib/scripts/domTT.js',
+             );
+    if($edit && $write){
+        $files[] = DOKU_INC.'lib/scripts/edit.js';
+        if($conf['spellchecker']){
+            $files[] = DOKU_INC.'lib/scripts/spellcheck.js';
+        }
+    }
+
+    // FIXME load plugin scripts
+
+    // check cache age here
+    if(js_cacheok($cache,$files)){
+        readfile($cache);
+        return;
+    }
+
+    // start output buffering and build the script
+    ob_start();
+
+    // add some translation strings and global variables
+    print "var alertText   = '".str_replace('\\\\n','\\n',addslashes($lang['qb_alert']))."';";
+    print "var notSavedYet = '".str_replace('\\\\n','\\n',addslashes($lang['notsavedyet']))."';";
+    print "var DOKU_BASE   = '".DOKU_BASE."';";
+
+    // load files
+    foreach($files as $file){
+        readfile($file);
+    }
+
+    // init stuff
+    js_runonstart("ajax_qsearch.init('qsearch_in','qsearch_out')");
+    js_runonstart("addEvent(document,'click',closePopups)");
+
+    if($edit){
+        // size controls
+        js_runonstart("initSizeCtl('sizectl','wikitext')");
+
+        if($write){
+            require_once(DOKU_INC.'inc/toolbar.php');
+            toolbar_JSdefines('toolbar');
+            js_runonstart("initToolbar('toolbar','wikitext',toolbar)");
+
+            // add pageleave check
+            js_runonstart("initChangeCheck('".js_escape($lang['notsavedyet'])."')");
+
+            // add lock timer
+            js_runonstart("init_locktimer(".($conf['locktime']-60).",'".js_escape($lang['willexpire'])."')");
+
+            // load spell checker
+            if($conf['spellchecker']){
+                js_runonstart("ajax_spell.init('".
+                               js_escape($lang['spell_start'])."','".
+                               js_escape($lang['spell_stop'])."','".
+                               js_escape($lang['spell_wait'])."','".
+                               js_escape($lang['spell_noerr'])."','".
+                               js_escape($lang['spell_nosug'])."','".
+                               js_escape($lang['spell_change'])."')");
+            }
+        }
+    }
+
+
+    // load user script
+    if(@file_exists(DOKU_INC.'conf/userscript.js')){
+      readfile(DOKU_INC.'conf/userscript.js');
+    }
+
+    // end output buffering and get contents
+    $js = ob_get_contents();
+    ob_end_clean();
+
+    // compress whitespace and comments
+    $js = js_compress($js);
+
+    // save cache file
+    io_saveFile($cache,$js);
+
+    // finally send output
+    print $js;
+}
+
+/**
+ * Checks if a JavaScript Cache file still is valid
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function js_cacheok($cache,$files){
+    $ctime = @filemtime($cache);
+    if(!$ctime) return false; //There is no cache
+
+    // some additional files to check
+    $files[] = DOKU_INC.'conf/dokuwiki.conf';
+    $files[] = DOKU_INC.'conf/local.conf';
+    $files[] = DOKU_INC.'conf/userscript.js';
+
+    // now walk the files
+    foreach($files as $file){
+        if(@filemtime($file) > $ctime){
+            return false;
+        }
+    }
+    return true;
+}
+
+/**
+ * Escapes a String to be embedded in a JavaScript call, keeps \n
+ * as newline
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function js_escape($string){
+    return str_replace('\\\\n','\\n',addslashes($string));
+}
+
+/**
+ * Adds the given JavaScript code to the window.onload() event
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ */
+function js_runonstart($func){
+    print "addEvent(window,'load',function(){ $func; });";
+}
+
+//http://modp.com/release/jsstrip/jsstrip.py
+function js_compress($s){
+    $i = 0;
+    $line = 0;
+    $s .= "\n";
+    $len = strlen($s);
+
+    // items that don't need spaces next to them
+    $chars = '^&|!+\-*\/%=:;,{}()<>% \t\n\r';
+
+    ob_start();
+    while($i < $len){
+        $ch = $s{$i};
+
+        // multiline comments
+        if($ch == '/' && $s{$i+1} == '*'){
+            $endC = strpos($s,'*/',$i+2);
+            if($endC === false) trigger_error('Found invalid /*..*/ comment', E_USER_ERROR);
+            $i = $endC + 2;
+            continue;
+        }
+
+        // singleline
+        if($ch == '/' && $s{$i+1} == '/'){
+            $endC = strpos($s,"\n",$i+2);
+            if($endC === false) trigger_error('Invalid comment', E_USER_ERROR);
+            $i = $endC;
+            continue;
+        }
+
+        // tricky.  might be an RE
+        if($ch == '/'){
+            // rewind, skip white space
+            $j = 1;
+            while($s{$i-$j} == ' '){
+                $j = $j + 1;
+            }
+            if( ($s{$i-$j} == '=') || ($s{$i-$j} == '(') ){
+                // yes, this is an re
+                // now move forward and find the end of it
+                $j = 1;
+                while($s{$i+$j} != '/'){
+                    while( ($s{$i+$j} != '\\') && ($s{$i+$j} != '/')){
+                        $j = $j + 1;
+                    }
+                    if($s{$i+$j} == '\\') $j = $j + 2;
+                }
+                echo substr($s,$i,$j+1);
+                $i = $i + $j + 1;
+                continue;
+            }
+        }
+
+        // double quote strings
+        if($ch == '"'){
+            $j = 1;
+            while( $s{$i+$j} != '"' ){
+                while( ($s{$i+$j} != '\\') && ($s{$i+$j} != '"') ){
+                    $j = $j + 1;
+                }
+                if($s{$i+$j} == '\\') $j = $j + 2;
+            }
+            echo substr($s,$i,$j+1);
+            $i = $i + $j + 1;
+            continue;
+        }
+
+        // single quote strings
+        if($ch == "'"){
+            $j = 1;
+            while( $s{$i+$j} != "'" ){
+                while( ($s{$i+$j} != '\\') && ($s{$i+$j} != "'") ){
+                    $j = $j + 1;
+                }
+                if ($s{$i+$j} == '\\') $j = $j + 2;
+            }
+            echo substr($s,$i,$j+1);
+            $i = $i + $j + 1;
+            continue;
+        }
+
+        // newlines
+        if($ch == "\n" || $ch == "\r"){
+            $i = $i+1;
+            continue;
+        }
+
+        // leading spaces
+        if( ( $ch == ' ' ||
+              $ch == "\n" ||
+              $ch == "\t" ) &&
+            !preg_match('/['.$chars.']/',$s{$i+1}) ){
+            $i = $i+1;
+            continue;
+        }
+
+        // trailing spaces
+        if( ( $ch == ' ' ||
+              $ch == "\n" ||
+              $ch == "\t" ) &&
+            !preg_match('/['.$chars.']/',$s{$i-1}) ){
+            $i = $i+1;
+            continue;
+        }
+
+        // other chars
+        echo $ch;
+        $i = $i + 1;
+    }
+
+
+    $out = ob_get_contents();
+    ob_end_clean();
+    return $out;
+}
+
+//http://csstidy.sourceforge.net/download.php
+
+//Setup VIM: ex: et ts=4 enc=utf-8 :
+?>
diff --git a/lib/scripts/ajax.js b/lib/scripts/ajax.js
index 0a4183463..c0323f09e 100644
--- a/lib/scripts/ajax.js
+++ b/lib/scripts/ajax.js
@@ -23,39 +23,46 @@ ajax_qsearch.sack.AjaxFailedAlert = '';
 ajax_qsearch.sack.encodeURIString = false;
 
 ajax_qsearch.init = function(inID,outID){
-  if(ajax_qsearch.inObj == null)
-    ajax_qsearch.inObj  = document.getElementById(inID);
-  if(ajax_qsearch.outObj == null)
-    ajax_qsearch.outObj = document.getElementById(outID);
-}
+  ajax_qsearch.inObj  = document.getElementById(inID);
+  ajax_qsearch.outObj = document.getElementById(outID);
+
+  // objects found?
+  if(ajax_qsearch.inObj === null){ return; }
+  if(ajax_qsearch.outObj === null){ return; }
+
+  // attach eventhandler to search field
+	addEvent(ajax_qsearch.inObj,'keyup',ajax_qsearch.call);
+
+  // attach eventhandler to output field
+  addEvent(ajax_qsearch.outObj,'click',function(){ ajax_qsearch.outObj.style.display='none'; });
+};
 
 ajax_qsearch.clear = function(){
   ajax_qsearch.outObj.style.display = 'none';
   ajax_qsearch.outObj.innerHTML = '';
-  if(ajax_qsearch.timer != null){
+  if(ajax_qsearch.timer !== null){
     window.clearTimeout(ajax_qsearch.timer);
     ajax_qsearch.timer = null;
   }
-}
+};
 
 ajax_qsearch.exec = function(){
 	ajax_qsearch.clear();
   var value = ajax_qsearch.inObj.value;
-  if(value == '') return;
+  if(value === ''){ return; }
   ajax_qsearch.sack.runAJAX('call=qsearch&q='+encodeURI(value));
-}
+};
 
 ajax_qsearch.sack.onCompletion = function(){
 	var data = ajax_qsearch.sack.response;
-  if(data == '') return;
+  if(data === ''){ return; }
 
   ajax_qsearch.outObj.innerHTML = data;
   ajax_qsearch.outObj.style.display = 'block';
-}
+};
 
-ajax_qsearch.call = function(inID,outID){
-  ajax_qsearch.init(inID,outID);
+ajax_qsearch.call = function(){
   ajax_qsearch.clear();
   ajax_qsearch.timer = window.setTimeout("ajax_qsearch.exec()",500);
-}
+};
 
diff --git a/lib/scripts/domLib.js b/lib/scripts/domLib.js
index 9dd3af463..e46c6fecf 100644
--- a/lib/scripts/domLib.js
+++ b/lib/scripts/domLib.js
@@ -65,7 +65,7 @@ var domLib_isIE6up = domLib_isIE55up && !domLib_isIE55;
 var domLib_standardsMode = (document.compatMode && document.compatMode == 'CSS1Compat');
 var domLib_useLibrary = (domLib_isOpera7up || domLib_isKHTML || domLib_isIE5up || domLib_isGecko || domLib_isMacIE || document.defaultView);
 // fixed in Konq3.2
-var domLib_hasBrokenTimeout = (domLib_isMacIE || (domLib_isKonq && domLib_userAgent.match(/konqueror\/3.([2-9])/) == null));
+var domLib_hasBrokenTimeout = (domLib_isMacIE || (domLib_isKonq && domLib_userAgent.match(/konqueror\/3.([2-9])/) === null));
 var domLib_canFade = (domLib_isGecko || domLib_isIE || domLib_isSafari || domLib_isOpera);
 var domLib_canDrawOverSelect = (domLib_isMac || domLib_isOpera || domLib_isGecko);
 var domLib_canDrawOverFlash = (domLib_isMac || domLib_isWin);
@@ -109,7 +109,7 @@ function domLib_clone(obj)
 		var value = obj[i];
 		try
 		{
-			if (value != null && typeof(value) == 'object' && value != window && !value.nodeType)
+			if (value !== null && typeof(value) == 'object' && value != window && !value.nodeType)
 			{
 				copy[i] = domLib_clone(value);
 			}
@@ -153,7 +153,7 @@ function Hash()
 Hash.prototype.get = function(in_key)
 {
 	return this.elementData[in_key];
-}
+};
 
 Hash.prototype.set = function(in_key, in_value)
 {
@@ -168,11 +168,12 @@ Hash.prototype.set = function(in_key, in_value)
 			}
 		}
 
-		return this.elementData[in_key] = in_value;
+		this.elementData[in_key] = in_value;
+    return this.elementData[in_key];
 	}
 
 	return false;
-}
+};
 
 Hash.prototype.remove = function(in_key)
 {
@@ -190,17 +191,17 @@ Hash.prototype.remove = function(in_key)
 	}
 
 	return tmp_value;
-}
+};
 
 Hash.prototype.size = function()
 {
 	return this.length;
-}
+};
 
 Hash.prototype.has = function(in_key)
 {
 	return typeof(this.elementData[in_key]) != 'undefined';
-}
+};
 
 Hash.prototype.find = function(in_obj)
 {
@@ -211,7 +212,7 @@ Hash.prototype.find = function(in_obj)
 			return tmp_key;
 		}
 	}
-}
+};
 
 Hash.prototype.merge = function(in_hash)
 {
@@ -228,7 +229,7 @@ Hash.prototype.merge = function(in_hash)
 
 		this.elementData[tmp_key] = in_hash.elementData[tmp_key];
 	}
-}
+};
 
 Hash.prototype.compare = function(in_hash)
 {
@@ -246,7 +247,7 @@ Hash.prototype.compare = function(in_hash)
 	}
 	
 	return true;
-}
+};
 
 // }}}
 // {{{ domLib_isDescendantOf()
@@ -341,7 +342,7 @@ function domLib_detectCollisions(in_object, in_recover, in_useCache)
 
 		return;
 	}
-	else if (domLib_collisionElements.length == 0)
+	else if (domLib_collisionElements.length === 0)
 	{
 		return;
 	}
@@ -349,9 +350,9 @@ function domLib_detectCollisions(in_object, in_recover, in_useCache)
 	// okay, we have a tip, so hunt and destroy
 	var objectOffsets = domLib_getOffsets(in_object);
 
-	for (var cnt = 0; cnt < domLib_collisionElements.length; cnt++)
+	for (cnt = 0; cnt < domLib_collisionElements.length; cnt++)
 	{
-		var thisElement = domLib_collisionElements[cnt];
+		thisElement = domLib_collisionElements[cnt];
 
 		// if collision element is in active element, move on
 		// WARNING: is this too costly?
@@ -442,8 +443,7 @@ function domLib_getOffsets(in_object)
 		'bottom',	offsetTop + originalHeight,
 		'leftCenter',	offsetLeft + originalWidth/2,
 		'topCenter',	offsetTop + originalHeight/2,
-		'radius',	Math.max(originalWidth, originalHeight) 
-	);
+		'radius',	Math.max(originalWidth, originalHeight)	);
 }
 
 // }}}
@@ -461,7 +461,7 @@ function domLib_setTimeout(in_function, in_timeout, in_args)
 		// timeout event is disabled
 		return;
 	}
-	else if (in_timeout == 0)
+	else if (in_timeout === 0)
 	{
 		in_function(in_args);
 		return 0;
@@ -500,7 +500,7 @@ function domLib_clearTimeout(in_id)
 	{
 		if (domLib_timeoutStates.has(in_id))
 		{
-			clearTimeout(domLib_timeoutStates.get(in_id).get('timeoutId'))
+			clearTimeout(domLib_timeoutStates.get(in_id).get('timeoutId'));
 			domLib_timeoutStates.remove(in_id);
 		}
 	}
@@ -631,7 +631,7 @@ function domLib_getComputedStyle(in_obj, in_property)
 	// getComputedStyle() is broken in konqueror, so let's go for the style object
 	else if (domLib_isKonq)
 	{
-		var humpBackProp = in_property.replace(/-(.)/, function (a, b) { return b.toUpperCase(); });
+		humpBackProp = in_property.replace(/-(.)/, function (a, b) { return b.toUpperCase(); });
 		return eval('in_obj.style.' + in_property);
 	}
 	else
diff --git a/lib/scripts/edit.js b/lib/scripts/edit.js
index 43e6843b0..fd4cb8d0b 100644
--- a/lib/scripts/edit.js
+++ b/lib/scripts/edit.js
@@ -59,7 +59,7 @@ function createPicker(id,list,icobase,edid){
     for(var key in list){
         var btn = document.createElement('button');
 
-        btn.className = 'pickerbutton'
+        btn.className = 'pickerbutton';
 
         // associative array?
         if(isNaN(key)){
@@ -69,16 +69,14 @@ function createPicker(id,list,icobase,edid){
             btn.appendChild(ico);
             eval("btn.onclick = function(){pickerInsert('"+id+"','"+
                                   jsEscape(key)+"','"+
-                                  jsEscape(edid)
-                                +"');return false;}");
+                                  jsEscape(edid)+"');return false;}");
         }else{
             var txt = document.createTextNode(list[key]);
             btn.title     = list[key];
             btn.appendChild(txt);
             eval("btn.onclick = function(){pickerInsert('"+id+"','"+
                                   jsEscape(list[key])+"','"+
-                                  jsEscape(edid)
-                                +"');return false;}");
+                                  jsEscape(edid)+"');return false;}");
         }
 
         picker.appendChild(btn);
@@ -127,7 +125,7 @@ function showPicker(pickerid,btn){
  * @author Andreas Gohr <andi@splitbrain.org>
  */
 function initToolbar(tbid,edid,tb){
-		if(!document.getElementById) return;
+		if(!document.getElementById){ return; }
     var toolbar = document.getElementById(tbid);
     var cnt = tb.length;
     for(i=0; i<cnt; i++){
@@ -141,7 +139,7 @@ function initToolbar(tbid,edid,tb){
         switch(tb[i]['type']){
             case 'format':
                 var sample = tb[i]['title'];
-                if(tb[i]['sample']) sample = tb[i]['sample'];
+                if(tb[i]['sample']){ sample = tb[i]['sample']; }
       
                 eval("btn.onclick = function(){insertTags('"+
                                         jsEscape(edid)+"','"+
@@ -199,7 +197,7 @@ function insertTags(edid,tagOpen, tagClose, sampleText) {
     txtarea.focus();
  
     // This has change
-    text = theSelection;
+    var text = theSelection;
     if(theSelection.charAt(theSelection.length - 1) == " "){// exclude ending space char, if any
       theSelection = theSelection.substring(0, theSelection.length - 1);
       r = document.selection.createRange();
@@ -215,10 +213,10 @@ function insertTags(edid,tagOpen, tagClose, sampleText) {
     r.select();
   // Mozilla
   } else if(txtarea.selectionStart || txtarea.selectionStart == '0') {
-    var replaced = false;
+    replaced = false;
     var startPos = txtarea.selectionStart;
     var endPos   = txtarea.selectionEnd;
-    if(endPos - startPos) replaced = true;
+    if(endPos - startPos){ replaced = true; }
     var scrollTop=txtarea.scrollTop;
     var myText = (txtarea.value).substring(startPos, endPos);
     if(!myText) { myText=sampleText;}
@@ -248,7 +246,7 @@ function insertTags(edid,tagOpen, tagClose, sampleText) {
     var re2=new RegExp("\\$2","g");
     copy_alertText=copy_alertText.replace(re1,sampleText);
     copy_alertText=copy_alertText.replace(re2,tagOpen+sampleText+tagClose);
-    var text;
+
     if (sampleText) {
       text=prompt(copy_alertText);
     } else {
@@ -266,7 +264,9 @@ function insertTags(edid,tagOpen, tagClose, sampleText) {
 
   }
   // reposition cursor if possible
-  if (txtarea.createTextRange) txtarea.caretPos = document.selection.createRange().duplicate();
+  if (txtarea.createTextRange){
+    txtarea.caretPos = document.selection.createRange().duplicate();
+  }
 }
 
 /*
@@ -280,7 +280,7 @@ function insertAtCarret(edid,value){
   //IE support
   if (document.selection) {
     field.focus();
-    if(opener == null){
+    if(opener === null){
       sel = document.selection.createRange();
     }else{
       sel = opener.document.selection.createRange();
@@ -291,9 +291,9 @@ function insertAtCarret(edid,value){
     var startPos  = field.selectionStart;
     var endPos    = field.selectionEnd;
     var scrollTop = field.scrollTop;
-    field.value = field.value.substring(0, startPos)
-                  + value
-                  + field.value.substring(endPos, field.value.length);
+    field.value = field.value.substring(0, startPos) +
+                  value +
+                  field.value.substring(endPos, field.value.length);
 
     field.focus();
     var cPos=startPos+(value.length);
@@ -304,7 +304,9 @@ function insertAtCarret(edid,value){
     field.value += "\n"+value;
   }
   // reposition cursor if possible
-  if (field.createTextRange) field.caretPos = document.selection.createRange().duplicate();
+  if (field.createTextRange){
+    field.caretPos = document.selection.createRange().duplicate();
+  }
 }
 
 
@@ -331,7 +333,7 @@ function changeCheck(msg){
  * Sets focus to the editbox as well
  */
 function initChangeCheck(msg){
-		if(!document.getElementById) return;
+		if(!document.getElementById){ return false; }
 		// add change check for links
 		var links = document.getElementsByTagName('a');
 		for(var i=0; i < links.length; i++){
@@ -361,7 +363,7 @@ function initChangeCheck(msg){
 		edit_text.onchange = function(){
 				textChanged = true; //global var
 				summaryCheck();
-		}
+		};
 		edit_text.onkeyup  = summaryCheck;
 		var summary = document.getElementById('summary');
 		summary.onchange = summaryCheck;
@@ -378,7 +380,7 @@ function initChangeCheck(msg){
  */
 function summaryCheck(){
     var sum = document.getElementById('summary');
-    if(sum.value == ''){
+    if(sum.value === ''){
       	sum.className='missing';
     }else{
       	sum.className='edit';
diff --git a/lib/scripts/events.js b/lib/scripts/events.js
new file mode 100644
index 000000000..f6360219d
--- /dev/null
+++ b/lib/scripts/events.js
@@ -0,0 +1,62 @@
+// written by Dean Edwards, 2005
+// with input from Tino Zijdel
+
+// http://dean.edwards.name/weblog/2005/10/add-event/
+
+function addEvent(element, type, handler) {
+	// assign each event handler a unique ID
+	if (!handler.$$guid) handler.$$guid = addEvent.guid++;
+	// create a hash table of event types for the element
+	if (!element.events) element.events = {};
+	// create a hash table of event handlers for each element/event pair
+	var handlers = element.events[type];
+	if (!handlers) {
+		handlers = element.events[type] = {};
+		// store the existing event handler (if there is one)
+		if (element["on" + type]) {
+			handlers[0] = element["on" + type];
+		}
+	}
+	// store the event handler in the hash table
+	handlers[handler.$$guid] = handler;
+	// assign a global event handler to do all the work
+	element["on" + type] = handleEvent;
+};
+// a counter used to create unique IDs
+addEvent.guid = 1;
+
+function removeEvent(element, type, handler) {
+	// delete the event handler from the hash table
+	if (element.events && element.events[type]) {
+		delete element.events[type][handler.$$guid];
+	}
+};
+
+function handleEvent(event) {
+	var returnValue = true;
+	// grab the event object (IE uses a global event object)
+	event = event || fixEvent(window.event);
+	// get a reference to the hash table of event handlers
+	var handlers = this.events[event.type];
+	// execute each event handler
+	for (var i in handlers) {
+		this.$$handleEvent = handlers[i];
+		if (this.$$handleEvent(event) === false) {
+			returnValue = false;
+		}
+	}
+	return returnValue;
+};
+
+function fixEvent(event) {
+	// add W3C standard event methods
+	event.preventDefault = fixEvent.preventDefault;
+	event.stopPropagation = fixEvent.stopPropagation;
+	return event;
+};
+fixEvent.preventDefault = function() {
+	this.returnValue = false;
+};
+fixEvent.stopPropagation = function() {
+	this.cancelBubble = true;
+};
\ No newline at end of file
diff --git a/lib/scripts/script.js b/lib/scripts/script.js
index 44409c712..180f2dcd5 100644
--- a/lib/scripts/script.js
+++ b/lib/scripts/script.js
@@ -16,19 +16,6 @@ if (clientPC.indexOf('opera')!=-1) {
     var is_opera_seven = (window.opera && document.childNodes);
 }
 
-//http://simon.incutio.com/archive/2004/05/26/addLoadEvent#comment2
-function addEvent(oTarget, sType, fpDest) {
-  var oOldEvent = oTarget[sType];
-  if (typeof oOldEvent != "function") {
-    oTarget[sType] = fpDest;
-  } else {
-    oTarget[sType] = function(e) {
-      oOldEvent(e);
-      fpDest(e);
-    }
-  }
-}
-
 /**
  * Get the X offset of the top left corner of the given object
  *
@@ -293,7 +280,7 @@ function fnt(id, e, evt) {
     }
     
     // activate the tooltip
-    domTT_activate(e, evt, 'content', footnote, 'type', 'velcro', 'id', 'insitu-fn'+id, 'styleClass', 'insitu-footnote', 'maxWidth', document.body.offsetWidth*0.4);
+    domTT_activate(e, evt, 'content', footnote, 'type', 'velcro', 'id', 'insitu-fn'+id, 'styleClass', 'insitu-footnote JSpopup', 'maxWidth', document.body.offsetWidth*0.4);
     currentFootnote = id;    
 }
 
@@ -318,8 +305,8 @@ function initSizeCtl(ctlid,edid){
     var s = document.createElement('img');
     l.src = DOKU_BASE+'lib/images/larger.gif';
     s.src = DOKU_BASE+'lib/images/smaller.gif';
-    l.onclick = function(){sizeCtl(edid,100);}
-    s.onclick = function(){sizeCtl(edid,-100);}
+		addEvent(l,'click',function(){sizeCtl(edid,100);});
+		addEvent(s,'click',function(){sizeCtl(edid,-100);});
     ctl.appendChild(l);
     ctl.appendChild(s);
 }
@@ -338,3 +325,17 @@ function sizeCtl(edid,val){
   now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000); //expire in a year
   setCookie('DokuWikisizeCtl',textarea.style.height,now);
 }
+
+/**
+ * Handler to close all open Popups
+ */
+function closePopups(){
+  if(!document.getElementById) return;
+
+  var divs = document.getElementsByTagName('div');
+  for(var i=0; i < divs.length; i++){
+    if(divs[i].className.indexOf('JSpopup') != -1){
+			divs[i].style.display = 'none';
+    }
+  }
+}
diff --git a/lib/scripts/spellcheck.js b/lib/scripts/spellcheck.js
index 308b3dd1f..ceb1cd074 100644
--- a/lib/scripts/spellcheck.js
+++ b/lib/scripts/spellcheck.js
@@ -47,6 +47,9 @@
  *
  *   findPosX()
  *   findPosY()
+ *
+ * Defined in events.js:
+ *
  *   addEvent()
  *
  * Defined in edit.js:
@@ -103,12 +106,12 @@ function ajax_spell_class(){
    */
   this.init = function(txtStart,txtStop,txtRun,txtNoErr,txtNoSug,txtChange){
      // don't run twice
-    if (this.inited) return;
+    if (this.inited){ return; }
     this.inited = true;
 
     // check for AJAX availability
     var ajax = new sack(this.handler);
-    if(ajax.failed) return;
+    if(ajax.failed){ return; }
 
     // get Elements
     this.textboxObj = document.getElementById('wikitext');
@@ -138,7 +141,7 @@ function ajax_spell_class(){
     ajax.runAJAX('call=utf8test&data='+encodeURIComponent('ü'));
 
     // second part of initialisation is in initReady() function
-  }
+  };
 
   /**
    * Eventhandler for click objects anywhere on the document
@@ -159,7 +162,7 @@ function ajax_spell_class(){
     if (target.id != ajax_spell.suggestObj.id){
       ajax_spell.suggestObj.style.display = "none";
     }
-  }
+  };
 
   /**
    * Changes the Spellchecker link according to the given mode
@@ -193,7 +196,7 @@ function ajax_spell_class(){
         ajax_spell.imageObj.src = DOKU_BASE+'lib/images/toolbar/spellcheck.png';
         break;
     }
-  }
+  };
 
   /**
    * Replaces a word identified by id with its correction given in word
@@ -205,7 +208,7 @@ function ajax_spell_class(){
     obj.innerHTML = decodeURIComponent(word);
     obj.style.color = "#005500";
     this.suggestObj.style.display = "none";
-  } 
+  };
 
   /**
    * Opens a prompt to let the user change the word her self
@@ -218,7 +221,7 @@ function ajax_spell_class(){
     if(word){
       this.correct(id,encodeURIComponent(word));
     }
-  }
+  };
 
   /**
    * Displays the suggestions for a misspelled word
@@ -228,7 +231,7 @@ function ajax_spell_class(){
    */
   this.suggest = function(){
     var args = this.suggest.arguments;
-    if(!args[0]) return;
+    if(!args[0]){ return; }
     var id   = args[0];
 
     // set position of the popup
@@ -237,10 +240,11 @@ function ajax_spell_class(){
     var y = findPosY('spell_error'+id);
 
     // handle scrolling 
+    var scrollPos;
     if(is_opera){
-      var scrollPos = 0; //FIXME how to do this without browser sniffing?
+      scrollPos = 0; //FIXME how to do this without browser sniffing?
     }else{
-      var scrollPos = this.showboxObj.scrollTop;
+      scrollPos = this.showboxObj.scrollTop;
     }
 
     this.suggestObj.style.left = x+'px';
@@ -265,7 +269,7 @@ function ajax_spell_class(){
 
     this.suggestObj.innerHTML = text;
     this.suggestObj.style.display = "block";
-  }
+  };
 
   // --- Callbacks ---
 
@@ -284,14 +288,14 @@ function ajax_spell_class(){
     }
 
     // register click event
-    addEvent(document,'onclick',ajax_spell.docClick);
+    addEvent(document,'click',ajax_spell.docClick);
 
     // register focus event
-    addEvent(ajax_spell.textboxObj,'onfocus',ajax_spell.setState);
+    addEvent(ajax_spell.textboxObj,'focus',ajax_spell.setState);
 
     // get started
     ajax_spell.setState('start');    
-  }
+  };
 
   /**
    * Callback. Called after finishing spellcheck.
@@ -329,7 +333,7 @@ function ajax_spell_class(){
       ajax_spell.editbarObj.style.visibility = 'visible';
       ajax_spell.setState('noerr');
     }
-  }
+  };
 
   /**
    * Callback. Gets called by resume() - switches back to edit mode
@@ -358,7 +362,7 @@ function ajax_spell_class(){
     ajax_spell.editbarObj.style.visibility = 'visible';
     ajax_spell.showboxObj.innerHTML     = '';
     ajax_spell.setState('start');
-  }
+  };
 
   // --- Callers ---
 
@@ -376,7 +380,7 @@ function ajax_spell_class(){
     ajax.onCompletion    = this.start;
     ajax.runAJAX('call=check&utf8='+ajax_spell.utf8ok+
                  '&data='+encodeURIComponent(ajax_spell.textboxObj.value));
-  }
+  };
 
   /**
    * Rewrites the HTML back to text again using an AJAX request
@@ -386,7 +390,7 @@ function ajax_spell_class(){
   this.resume = function(){
     ajax_spell.setState('run');
     var text = ajax_spell.showboxObj.innerHTML;
-    if(text != ''){
+    if(text !== ''){
       var ajax = new sack(ajax_spell.handler);
       ajax.AjaxFailedAlert = '';
       ajax.encodeURIString = false;
@@ -394,7 +398,7 @@ function ajax_spell_class(){
       ajax.runAJAX('call=resume&utf8='+ajax_spell.utf8ok+
                    '&data='+encodeURIComponent(text));
     }
-  }
+  };
 
 }
 
diff --git a/lib/scripts/tw-sack.js b/lib/scripts/tw-sack.js
index d608a76b7..0c7e81bf1 100644
--- a/lib/scripts/tw-sack.js
+++ b/lib/scripts/tw-sack.js
@@ -1,133 +1,134 @@
 /* Simple AJAX Code-Kit (SACK) */
-/* ©2005 Gregory Wild-Smith */
+/* ©2005 Gregory Wild-Smith */
 /* www.twilightuniverse.com */
 /* Software licenced under a modified X11 licence, see documentation or authors website for more details */
 
 function sack(file){
-	this.AjaxFailedAlert = "Your browser does not support the enhanced functionality of this website, and therefore you will have an experience that differs from the intended one.\n";
-	this.requestFile = file;
-	this.method = "POST";
-	this.URLString = "";
-	this.encodeURIString = true;
-	this.execute = false;
+  this.AjaxFailedAlert = "Your browser does not support the enhanced functionality of this website, and therefore you will have an experience that differs from the intended one.\n";
+  this.requestFile = file;
+  this.method = "POST";
+  this.URLString = "";
+  this.encodeURIString = true;
+  this.execute = false;
 
-	this.onLoading = function() { };
-	this.onLoaded = function() { };
-	this.onInteractive = function() { };
-	this.onCompletion = function() { };
+  this.onLoading = function() { };
+  this.onLoaded = function() { };
+  this.onInteractive = function() { };
+  this.onCompletion = function() { };
 
-	this.createAJAX = function() {
-		try {
-			this.xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
-		} catch (e) {
-			try {
-				this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
-			} catch (err) {
-				this.xmlhttp = null;
-			}
-		}
-		if(!this.xmlhttp && typeof XMLHttpRequest != "undefined")
-			this.xmlhttp = new XMLHttpRequest();
-		if (!this.xmlhttp){
-			this.failed = true; 
-		}
-	};
-	
-	this.setVar = function(name, value){
-		if (this.URLString.length < 3){
-			this.URLString = name + "=" + value;
-		} else {
-			this.URLString += "&" + name + "=" + value;
-		}
-	}
-	
-	this.encVar = function(name, value){
-		var varString = encodeURIComponent(name) + "=" + encodeURIComponent(value);
-	return varString;
-	}
-	
-	this.encodeURLString = function(string){
-		varArray = string.split('&');
-		for (i = 0; i < varArray.length; i++){
-			urlVars = varArray[i].split('=');
-			if (urlVars[0].indexOf('amp;') != -1){
-				urlVars[0] = urlVars[0].substring(4);
-			}
-			varArray[i] = this.encVar(urlVars[0],urlVars[1]);
-		}
-	return varArray.join('&');
-	}
-	
-	this.runResponse = function(){
-		eval(this.response);
-	}
-	
-	this.runAJAX = function(urlstring){
-		this.responseStatus = new Array(2);
-		if(this.failed && this.AjaxFailedAlert){ 
-			alert(this.AjaxFailedAlert); 
-		} else {
-			if (urlstring){ 
-				if (this.URLString.length){
-					this.URLString = this.URLString + "&" + urlstring; 
-				} else {
-					this.URLString = urlstring; 
-				}
-			}
-			if (this.encodeURIString){
-				var timeval = new Date().getTime(); 
-				this.URLString = this.encodeURLString(this.URLString);
-				this.setVar("rndval", timeval);
-			}
-			if (this.element) { this.elementObj = document.getElementById(this.element); }
-			if (this.xmlhttp) {
-				var self = this;
-				if (this.method == "GET") {
-					var totalurlstring = this.requestFile + "?" + this.URLString;
-					this.xmlhttp.open(this.method, totalurlstring, true);
-				} else {
-					this.xmlhttp.open(this.method, this.requestFile, true);
-				}
-				if (this.method == "POST"){
-					try {
+  this.createAJAX = function() {
+    try {
+      this.xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
+    } catch (e) {
+      try {
+        this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
+      } catch (err) {
+        this.xmlhttp = null;
+      }
+    }
+    if(!this.xmlhttp && typeof XMLHttpRequest != "undefined"){
+      this.xmlhttp = new XMLHttpRequest();
+    }
+    if (!this.xmlhttp){
+      this.failed = true; 
+    }
+  };
+  
+  this.setVar = function(name, value){
+    if (this.URLString.length < 3){
+      this.URLString = name + "=" + value;
+    } else {
+      this.URLString += "&" + name + "=" + value;
+    }
+  };
+  
+  this.encVar = function(name, value){
+    var varString = encodeURIComponent(name) + "=" + encodeURIComponent(value);
+  return varString;
+  };
+  
+  this.encodeURLString = function(string){
+    varArray = string.split('&');
+    for (i = 0; i < varArray.length; i++){
+      urlVars = varArray[i].split('=');
+      if (urlVars[0].indexOf('amp;') != -1){
+        urlVars[0] = urlVars[0].substring(4);
+      }
+      varArray[i] = this.encVar(urlVars[0],urlVars[1]);
+    }
+  return varArray.join('&');
+  };
+  
+  this.runResponse = function(){
+    eval(this.response);
+  };
+  
+  this.runAJAX = function(urlstring){
+    this.responseStatus = new Array(2);
+    if(this.failed && this.AjaxFailedAlert){ 
+      alert(this.AjaxFailedAlert); 
+    } else {
+      if (urlstring){ 
+        if (this.URLString.length){
+          this.URLString = this.URLString + "&" + urlstring; 
+        } else {
+          this.URLString = urlstring; 
+        }
+      }
+      if (this.encodeURIString){
+        var timeval = new Date().getTime(); 
+        this.URLString = this.encodeURLString(this.URLString);
+        this.setVar("rndval", timeval);
+      }
+      if (this.element) { this.elementObj = document.getElementById(this.element); }
+      if (this.xmlhttp) {
+        var self = this;
+        if (this.method == "GET") {
+          var totalurlstring = this.requestFile + "?" + this.URLString;
+          this.xmlhttp.open(this.method, totalurlstring, true);
+        } else {
+          this.xmlhttp.open(this.method, this.requestFile, true);
+        }
+        if (this.method == "POST"){
+          try {
              this.xmlhttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=UTF-8');
-					} catch (e) {}
+          } catch (e) {}
         } 
 
-				this.xmlhttp.onreadystatechange = function() {
-					switch (self.xmlhttp.readyState){
-						case 1:
-							self.onLoading();
-						break;
-						case 2:
-							self.onLoaded();
-						break;
-						case 3:
-							self.onInteractive();
-						break;
-						case 4:
-							self.response = self.xmlhttp.responseText;
-							self.responseXML = self.xmlhttp.responseXML;
-							self.responseStatus[0] = self.xmlhttp.status;
-							self.responseStatus[1] = self.xmlhttp.statusText;
-							self.onCompletion();
-							if(self.execute){ self.runResponse(); }
-							if (self.elementObj) {
-								var elemNodeName = self.elementObj.nodeName;
-								elemNodeName.toLowerCase();
-								if (elemNodeName == "input" || elemNodeName == "select" || elemNodeName == "option" || elemNodeName == "textarea"){
-									self.elementObj.value = self.response;
-								} else {
-									self.elementObj.innerHTML = self.response;
-								}
-							}
-							self.URLString = "";
-						break;
-					}
-				};
-				this.xmlhttp.send(this.URLString);
-			}
-		}
-	};
+        this.xmlhttp.onreadystatechange = function() {
+          switch (self.xmlhttp.readyState){
+            case 1:
+              self.onLoading();
+            break;
+            case 2:
+              self.onLoaded();
+            break;
+            case 3:
+              self.onInteractive();
+            break;
+            case 4:
+              self.response = self.xmlhttp.responseText;
+              self.responseXML = self.xmlhttp.responseXML;
+              self.responseStatus[0] = self.xmlhttp.status;
+              self.responseStatus[1] = self.xmlhttp.statusText;
+              self.onCompletion();
+              if(self.execute){ self.runResponse(); }
+              if (self.elementObj) {
+                var elemNodeName = self.elementObj.nodeName;
+                elemNodeName.toLowerCase();
+                if (elemNodeName == "input" || elemNodeName == "select" || elemNodeName == "option" || elemNodeName == "textarea"){
+                  self.elementObj.value = self.response;
+                } else {
+                  self.elementObj.innerHTML = self.response;
+                }
+              }
+              self.URLString = "";
+            break;
+          }
+        };
+        this.xmlhttp.send(this.URLString);
+      }
+    }
+  };
 this.createAJAX();
 }
-- 
GitLab