changeset 1069:5f92fa5e683a

Refactor how the "AJAX" stuff works.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 24 Jan 2017 17:25:48 +0200
parents 50d005dd22d8
children e23057465ca2
files admajax.js.php admin.js admin.php ajax.js genajax.js majax.inc.php msitegen.inc.php pages/vote.inc.php show.js show.php showajax.js.php usrajax.js.php usrajax.php
diffstat 13 files changed, 1603 insertions(+), 1567 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/admajax.js.php	Tue Jan 24 17:25:48 2017 +0200
@@ -0,0 +1,1013 @@
+<?php
+//
+// FAPWeb - Simple Web-based Demoparty Management System
+// Party administration page AJAX javascript module
+// (C) Copyright 2012-2017 Tecnic Software productions (TNSP)
+//
+$sessionType = "admin";
+header("Content-Type: application/javascript");
+require_once "mconfig.inc.php";
+require_once "msite.inc.php";
+require_once "msession.inc.php";
+
+// Initiate SQL database connection
+if (!stConnectSQLDB())
+{
+  // Error occured, bail out early
+  cmPrintPageFooter();
+  exit;
+}
+
+// Fetch non-"hardcoded" settings from SQL database
+stReloadSettings();
+
+// Check if sessions are enabled
+if (!stChkSetting("admPassword") || !stAdmSessionAuth(FALSE))
+  exit;
+
+echo
+  "\n".
+  "var jsSettingsArgs = [];\n";
+
+foreach (stExecSQL("SELECT * FROM settings_groups") as $group)
+{
+  $args = array();
+  if (($res = stExecSQL("SELECT * FROM settings WHERE vgroup=".$group["id"])) !== false)
+  {
+    foreach ($res as $item)
+    {
+      switch ($item["vtype"])
+      {
+        case VT_STR:
+        case VT_TEXT: $type = 1; break;
+        case VT_INT:  $type = 2; break;
+        case VT_BOOL: $type = 3; break;
+      }
+      $args[] = "\"".$item["key"]."\":".$type;
+    }
+  }
+
+  echo "jsSettingsArgs[".$group["id"]."] = {".implode(",", $args)."};\n";
+}
+
+echo
+  "\n".
+  "function jsUpdateSettings(id)\n".
+  "{\n".
+  "  if (id in jsSettingsArgs)\n".
+  "    jsSendPOSTRequest(\"action=update&type=settings&id=\"+id+\"&\"+jsMakePostArgs(jsSettingsArgs[id], \"st\", \"\"));\n".
+  "  return false;\n".
+  "}\n".
+  "\n";
+
+stCommonAJAX("admajax.php", "admlogout.php");
+?>
+
+var activeAttendee = -1, prevAttendee = -1, activeAttendeeTmp = "";
+var activeEntry = -1, prevEntry = -1, activeEntryTmp = "";
+
+var registeredTabs = Object();
+var activeTabs = Object();
+
+
+//
+// Tab related code
+//
+function jsUpdateTabList(tabset, extra)
+{
+  var tabs = "";
+  var content = "";
+
+  // Update the tab header list for this tabset
+  for (var id in registeredTabs[tabset])
+  {
+    var thead = registeredTabs[tabset][id];
+    tabs += "<a id=\"tabHead"+ tabset + id +
+      "\" href=\"#\" onClick=\"jsSwitchActiveTab('"+tabset+"', '"+id+
+      "')\">"+ thead +"</a> ";
+    
+    content += "<div id=\"tabCont"+ tabset + id +"\"></div>";
+  }
+
+  // Update the DOM elements
+  var item = document.getElementById("tabHeaders"+ tabset);
+  if (item) item.innerHTML = tabs + extra;
+
+  item = document.getElementById("tabContents"+ tabset);
+  if (item) item.innerHTML = content;
+}
+
+
+function jsRegisterTab(tabset, id, name)
+{
+  // Create tabset object "array" if it does not exist
+  if (!registeredTabs[tabset])
+    registeredTabs[tabset] = Object();
+
+  // Register tab
+  registeredTabs[tabset][id] = name;
+}
+
+
+function jsSwitchActiveTab(tabset, tab)
+{
+  // Go through all registered tabs to update their state
+  for (var id in registeredTabs[tabset])
+  {
+    var tabContent = document.getElementById("tabCont"+ tabset + id);
+    var tabHead = document.getElementById("tabHead"+ tabset + id);
+    if (tabContent && tabHead)
+    {
+      tabContent.style.display = (tab == id) ? "block" : "none";
+      tabHead.className = (tab == id) ? "active" : "inactive";
+      if (tab == id)
+      {
+        // Set active tab and refresh contents
+        activeTabs[tabset] = id;
+        setTimeout(function(qtabset, qid) {
+          switch (qtabset)
+          {
+            case "CM": refreshDispatchCM(qid); break;
+            case "CC": refreshDispatchCC(qid); break;
+            case "CS": refreshDispatchCS(qid); break;
+            default: jsMessageBox("Invalid tabset value: '"+ qtabset +"'.");
+          }
+        }, 10, tabset, id);
+        jsCancelUploadCBS();
+      }
+      else
+      {
+        // Clear inactive tabs
+        tabContent.innerHTML = "";
+      }
+    }
+  }
+}
+
+
+//
+// Admin interface specific popups
+//
+function jsHandleAdminPopupKeys(ev)
+{
+  ev = ev || window.event;
+  var key = ev.keyCode ? ev.keyCode : ev.which;
+  if (key == 27)
+  {
+    jsCloseAdminPopup();
+    return false;
+  }
+  else
+    return true;
+}
+
+
+function jsCloseAdminPopup()
+{
+  var nitem = document.getElementById("adminPopup");
+  if (nitem)
+  {
+    document.onkeydown = null;
+
+    nitem.innerHTML = "";
+    nitem.style.display = "none";
+  }
+}
+
+
+function jsOpenAdminPopup(txt)
+{
+  var nitem = document.getElementById("adminPopup");
+  if (nitem)
+  {
+    document.onkeydown = jsHandleAdminPopupKeys;
+
+    nitem.innerHTML = txt;
+    nitem.style.display = "block";
+  }
+}
+
+
+//
+// Generic element refresh
+//
+function jsRefreshItems(id,name,extra)
+{
+  var msuccess = function(txt)
+  {
+    var nitem = document.getElementById(id);
+    if (nitem) nitem.innerHTML = txt;
+  }
+
+  jsSendPOSTRequest("action=get&type="+name+extra, msuccess);
+}
+
+
+function jsDeleteItem(id,prefix,type,func,dsc,dsc2)
+{
+  var msuccess = function(txt)
+  {
+    var item = document.getElementById(prefix+id);
+    item.style.display = "none";
+    setTimeout(func, 50);
+  }
+
+  // Clearly mark the element when asking confirmation
+  var item = document.getElementById(prefix+id);
+  var tmp = item.className;
+  item.className += " deleteWarning";
+
+  // Ask confirmation for deletion
+  var mcb_ok = function (data)
+  {
+    jsSendPOSTRequest("action=delete&type="+type+"&id="+id, msuccess);
+    item.className = tmp;
+  }
+  
+  var mcb_cancel = function (data)
+  {
+    item.className = tmp;
+  }
+  
+  if (dsc)
+    jsConfirmBox("Are you sure you want to delete "+dsc+" #"+id+"?", mcb_ok, mcb_cancel, 0);
+  else
+    jsConfirmBox(dsc2, mcb_ok, mcb_cancel, 0);
+}
+
+
+function jsRefreshPanels(id, tab, args, set_default)
+{
+  var msuccess = function(txt)
+  {
+    var nitem = document.getElementById(tab);
+    if (nitem)
+    {
+      if (nitem.innerHTML == "")
+      {
+        nitem.innerHTML =
+          "<div id=\"tabHeaders"+id+"\" class=\"tabHeadersSub\"></div>" +
+          "<div id=\"tabContents"+id+"\" class=\"tabContentsSub\"></div>";
+      }
+
+      try {
+        var tmp = JSON.parse("{"+ txt +"}");
+        registeredTabs[id] = tmp;
+        jsUpdateTabList(id, "");
+        if (activeTabs[id])
+          jsSwitchActiveTab(id, activeTabs[id]);
+        else
+        if (set_default == "*")
+        {
+          for (var nid in registeredTabs[id])
+          {
+            jsSwitchActiveTab(id, nid);
+            break;
+          }
+        }
+        else
+        if (set_default != "")
+          jsSwitchActiveTab(id, set_default);
+      }
+      catch (err) {
+        jsErrorMessageBox("JSON.parse("+ txt +") failure: "+ err);
+      }
+    }
+  }
+
+  jsSendPOSTRequest("action=get&type="+args, msuccess);
+}
+
+
+function refreshDispatchCC(id)
+{
+  switch (id)
+  {
+    case "News"      : jsRefreshItems("tabContCCNews", "news", ""); break;
+    case "Attendees" : jsRefreshItems("tabContCCAttendees", "attendees", ""); activeAttendee = -1; break;
+    case "Voting"    : jsRefreshItems("tabContCCVoting", "voters", ""); break;
+    case "Compos"    : jsRefreshItems("tabContCCCompos", "compos", ""); break;
+    case "InfoSys"   : jsRefreshItems("tabContCCInfoSys", "infoMain", ""); break;
+    case "Settings"  : jsRefreshPanels("CS", "tabContCCSettings", "settingslist", "*"); break;
+    case "Entries"   : jsRefreshPanels("CM", "tabContCCEntries", "compolist", ""); break;
+    default          : alert("Invalid id: '"+ id +"'.'");
+  }
+}
+
+
+function refreshDispatchCS(id)
+{
+  jsRefreshItems("tabContCS"+ id, "settings", "&id="+ id);
+}
+
+
+function refreshDispatchCM(id)
+{
+  prevEntry = activeEntry = -1;
+  jsRefreshItems("tabContCM"+ id, "entries", "&id="+ id);
+}
+
+
+//
+// Site news management
+//
+function addNews()
+{
+  var args = jsMakePostArgs({"title":1,"text":1,"author":1}, "nn", "");
+
+  var msuccess = function(txt)
+  {
+    setTimeout(function () { refreshDispatchCC("News"); }, 50);
+  }
+
+  if (args != "")
+    jsSendPOSTRequest("action=add&type=news&"+args, msuccess);
+
+  return false;
+}
+
+
+function deleteNews(id)
+{
+  jsDeleteItem(id, "news", "news", function() { refreshDispatchCC("News"); }, "news item");
+}
+
+
+function updateNews(id)
+{
+  var args = jsMakePostArgs({"title":1,"text":1,"author":1}, "ne", id);
+
+  var msuccess = function(txt)
+  {
+    jsRefreshItems("news"+id, "newsitem", "&id="+id);
+  }
+
+  if (args != "")
+    jsSendPOSTRequest("action=update&type=news&id="+id+"&"+args, msuccess);
+}
+
+
+//
+// Visitor/attendee management
+//
+function addAttendee()
+{
+  var args = jsMakePostArgs({"name":1,"groups":1,"oneliner":1,"email":1}, "ne", "x");
+
+  var msuccess = function(txt)
+  {
+    setTimeout(function() { refreshDispatchCC("Attendees"); }, 50);
+  }
+
+  if (args != "")
+    jsSendPOSTRequest("action=add&type=attendees&"+args, msuccess);
+
+  return false;
+}
+
+
+function deleteAttendee(id)
+{
+  jsDeleteItem(id, "attendee", "attendees", function () { refreshDispatchCC("Attendees"); }, "attendee");
+}
+
+
+function updateAttendee(id)
+{
+  var args = jsMakePostArgs({"name":1,"groups":1,"oneliner":1,"email":1,"reghost":1}, "at", id);
+
+  var msuccess = function(txt)
+  {
+    activateAttendee(-1);
+  }
+
+  if (args != "")
+    jsSendPOSTRequest("action=update&type=attendees&id="+id+"&"+args, msuccess);
+}
+
+
+function activateAttendee(id)
+{
+  var msuccess1 = function(txt)
+  {
+    var nitem = document.getElementById("attendee"+prevAttendee);
+    if (nitem)
+    {
+      nitem.innerHTML = txt;
+      nitem.className = activeAttendeeTmp;
+    }
+  }
+
+  var msuccess2 = function(txt)
+  {
+    var nitem = document.getElementById("attendee"+activeAttendee);
+    if (nitem)
+    {
+      nitem.innerHTML = txt;
+      activeAttendeeTmp = nitem.className;
+      nitem.className += " active";
+      activeAttendee = id;
+    }
+  }
+
+  if (activeAttendee != id)
+  {
+    prevAttendee = activeAttendee;
+    activeAttendee = id;
+
+    if (prevAttendee != -1)
+      jsSendPOSTRequest("action=get&type=attendee&id="+prevAttendee+"&edit=0", msuccess1);
+
+    if (activeAttendee != -1)
+      jsSendPOSTRequest("action=get&type=attendee&id="+activeAttendee+"&edit=1", msuccess2);
+  }
+}
+
+
+//
+// Compo management
+//
+function addCompo()
+{
+  var args = jsMakePostArgs({"name":1, "description":1}, "nc", "");
+
+  var msuccess = function(txt)
+  {
+    setTimeout(function () { refreshDispatchCC("Compos"); }, 50);
+  }
+
+  if (args != "")
+    jsSendPOSTRequest("action=add&type=compo&"+args, msuccess);
+  return false;
+}
+
+
+function updateCompoType(id)
+{
+  var msuccess = function(txt)
+  {
+    jsRefreshItems("compo"+id, "compo", "&id="+id);
+  }
+
+  jsSendPOSTRequest("action=update&type=compotype&id="+id+"&ctype="+jsGetValue("cotype"+id+"Sel", 4), msuccess);
+}
+
+
+function updateCompoVoting(id)
+{
+  var msuccess = function(txt)
+  {
+    jsRefreshItems("covoting"+id, "compovoting", "&id="+id);
+  }
+
+  jsSendPOSTRequest("action=update&type=compovoting&id="+id+"&voting="+jsGetValue("covotingbutton"+id, 3), msuccess);
+}
+
+
+function updateCompo(id)
+{
+  var args = jsMakePostArgs({"name":1, "description":1, "notes":1, "visible":3, "voting":3, "show_authors":3, "cpath":1, "preview_type":4}, "co", id, true);
+
+  var msuccess = function(txt)
+  {
+    jsRefreshItems("compo"+id, "compo", "&id="+id);
+  }
+
+  if (args != "")
+    jsSendPOSTRequest("action=update&type=compo&id="+id+"&"+args, msuccess);
+}
+
+
+function deleteCompo(id)
+{
+  jsDeleteItem(id, "compo", "compo", function () { refreshDispatchCC("Compos"); }, 0, 
+    "Are you ABSOLUTELY sure you want to delete compo #"+id+"? "+
+    "This will delete all votes AND entries related to it!");
+}
+
+
+//
+// Entry management
+//
+function refreshEntryCompos()
+{
+  setTimeout(function () { refreshDispatchCC("Entries"); }, 50);
+}
+
+
+function activateEntry(id, force)
+{
+  var msuccess1 = function(txt)
+  {
+    var nitem = document.getElementById("entry"+prevEntry);
+    if (nitem)
+    {
+      nitem.innerHTML = txt;
+      nitem.className = activeEntryTmp;
+    }
+  }
+
+  var msuccess2 = function(txt)
+  {
+    var nitem = document.getElementById("entry"+activeEntry);
+    if (nitem)
+    {
+      nitem.innerHTML = txt;
+      activeEntryTmp = nitem.className;
+      nitem.className += " active";
+      activeEntry = id;
+    }
+  }
+
+  if (activeEntry != id || force)
+  {
+    jsCancelUploadCBS();
+
+    prevEntry = activeEntry;
+    activeEntry = id;
+
+    if (prevEntry != -1)
+      jsSendPOSTRequest("action=get&type=entry&id="+prevEntry+"&edit=0", msuccess1);
+
+    if (activeEntry != -1)
+      jsSendPOSTRequest("action=get&type=entry&id="+activeEntry+"&edit=1", msuccess2);
+  }
+}
+
+
+function addEntry(id)
+{
+  var args = jsMakePostArgs({"name":1, "author":1, "filename":1, "info":1, "notes":1, "evalue":2}, "ne", id, true);
+
+  jsCancelUploadCBS();
+
+  var msuccess = function(txt)
+  {
+    setTimeout(function (q) { refreshDispatchCM(qid); }, 50, id);
+    refreshEntryCompos();
+  }
+
+  if (args != "")
+    jsSendPOSTRequest("action=add&type=entry&compo_id="+id+"&"+args, msuccess);
+
+  return false;
+}
+
+
+function updateEntry(cid, id, edit)
+{
+  var args = jsMakePostArgs({"name":1, "author":1, "filename":1, "info":1, "notes":1, "compo_id":4, "evalue":2, "preview_type":4}, "en", id, true);
+  var has_id = "compo_id" in lastPostArgs;
+  var compo_id = lastPostArgs["compo_id"];
+
+  jsCancelUploadCBS();
+
+  if (!("compo_id" in lastPostArgs))
+  {
+    args += "&compo_id=" + cid;
+    compo_id = cid;
+  }
+
+  var msuccess = function(txt)
+  {
+    if (cid != compo_id)
+    {
+      var nitem = document.getElementById("entry"+ id);
+      if (nitem)
+        nitem.style.display = "none";
+    }
+    else
+      activateEntry(edit ? id : -1, true);
+    refreshEntryCompos();
+  }
+
+  var mcb_ok = function(data)
+  {
+    jsSendPOSTRequest("action=update&type=entry&id="+id+"&edit="+edit+"&"+args, msuccess);
+    refreshEntryCompos();
+  }
+
+  var mcb_cancel = function(data)
+  {
+    activeEntry = -1;
+  }
+
+  if (args != "")
+  {
+    if (cid != compo_id)
+    {
+      jsConfirmBox("Are you sure you want to change entry #"+id+
+        "'s compo from "+cid+" to "+compo_id+"?", mcb_ok, mcb_cancel);
+    }
+    else
+      mcb_ok();
+  }
+}
+
+
+function deleteEntry(cid, id)
+{
+  jsCancelUploadCBS();
+  jsDeleteItem(id, "entry", "entries", function() { refreshDispatchCM(cid); refreshEntryCompos(); }, "entry");
+}
+
+
+//
+// Votekey management
+//
+function voteKeyInfoRefresh()
+{
+  jsRefreshItems("vkeyInfo", "votekey", "info");
+}
+
+
+function voteKeyRefresh(id)
+{
+  var msuccess2 = function(txt)
+  {
+    var nitem = document.getElementById("vkey"+id);
+    if (nitem)
+      nitem.className = txt;
+    voteKeyInfoRefresh();
+  }
+
+  var msuccess1 = function(txt)
+  {
+    var nitem = document.getElementById("vkey"+id);
+    if (nitem)
+      nitem.innerHTML = txt;
+    voteKeyInfoRefresh();
+  }
+
+  jsSendPOSTRequest("action=get&type=votekey&id="+id, msuccess1);
+  jsSendPOSTRequest("action=get&type=votekeyclass&id="+id, msuccess2);
+}
+
+
+function voteKeyUpdate(id, type, args)
+{
+  var msuccess = function(txt)
+  {
+    voteKeyRefresh(id);
+    voteKeyInfoRefresh();
+  }
+
+  jsSendPOSTRequest("action=votekey&type="+type+"&id="+id+"&"+args, msuccess);
+}
+
+
+function voteKeySetActive(id)
+{
+  var args = jsMakePostArgs({"active":3}, "vk", id);
+
+  var mcb_ok = function (data)
+  {
+    voteKeyUpdate(id, "active", args);
+  }
+
+  var mcb_cancel = function (data)
+  {
+    voteKeyRefresh(id);
+  }
+  
+  if (lastPostArgs["active"] == 0)
+    jsConfirmBox("Are you sure you want to deactivate vote key #"+id+"?", mcb_ok, mcb_cancel, 0);
+  else
+    mcb_ok(0);
+}
+
+
+function voteKeyAssign(id, mode)
+{
+  var args = jsMakePostArgs({"key_id":2}, "vk", id);
+
+  var mcb_ok = function (data)
+  {
+    voteKeyUpdate(id, (mode ? "assign" : "clear"), args);
+  }
+  
+  if (mode == 0)
+    jsConfirmBox("Are you sure you want to clear vote key assign #"+id+"?", mcb_ok, 0, 0);
+  else
+    mcb_ok(0);
+}
+
+
+//
+// Misc functions
+//
+function showScreenCmd(str)
+{
+  jsSendPOSTRequest("action=screencmd&cmd="+str);
+}
+
+
+function performSystemCheck()
+{
+  jsSendPOSTRequest("action=check", jsMessageBox);
+}
+
+
+function generateEntryPositions(id, patch)
+{
+  var msuccess = function (data)
+  {
+    refreshDispatchCM(id);
+    refreshCurrEntryData();
+    refreshCurrEntryListData();
+  }
+
+  var mcb_ok = function (data)
+  {
+    if (id == 0)
+      jsSendPOSTRequest("action=randomize&type=all&patch="+patch, msuccess);
+    else
+      jsSendPOSTRequest("action=randomize&type=compo&id="+id+"&patch="+patch, msuccess);
+  }
+  
+  if (patch == 0)
+  {
+    jsConfirmBox("Are you <b>ABSOLUTELY CERTAIN</b> you want to delete and regenerate entry show positions "+
+      (id == 0 ? "for ALL compos" : "for this compo") +"? "+
+      "<b>This will completely annihilate current show position numbers! AND FUCK UP YOUR EXPORTED FILE ORDER (ask ccr to know what that means.)</b>", mcb_ok, 0, 0);
+  }
+  else
+    mcb_ok(0);
+}
+
+
+//
+// Competition control
+//
+function setShowMode(mode)
+{
+  jsSendPOSTRequest("action=ctrl&type=setShowMode&mode="+mode);
+}
+
+
+function refreshCurrEntryData()
+{
+  jsRefreshItems("ctrlCurrCompoData", "infoCurrCompoData", "");
+}
+
+
+function refreshCurrEntryListData()
+{
+  jsRefreshItems("ctrlEntryList", "infoCurrEntryList", "");
+  refreshCurrEntryData();
+}
+
+
+function activateCompo()
+{
+  jsSendPOSTRequest("action=ctrl&type=setCompoID&id="+jsGetValue("ctrlCompoListSel", 4), refreshCurrEntryListData);
+}
+
+
+function setSelectedEntry()
+{
+  jsSendPOSTRequest("action=ctrl&type=setEntry&index="+jsGetValue("ctrlEntryListSel", 4), refreshCurrEntryData);
+}
+
+
+function switchEntry(dir)
+{
+  jsSendPOSTRequest("action=ctrl&type="+ (dir < 0 ? "prevEntry" : "nextEntry"), refreshCurrEntryListData);
+}
+
+
+//
+// Rotation list editing and handling
+//
+function refreshRotationListSel()
+{
+  jsRefreshItems("ctrlRotationListsSel", "infoRotationLists", "");
+}
+
+
+function refreshRotationListEdit(id)
+{
+  jsRefreshItems("ctrlRotationListEdit", "infoRotationListEdit", "&id="+id+"&full=0");
+  refreshRotationListSel();
+}
+
+
+function refreshActiveRotationList()
+{
+  jsRefreshItems("ctrlActiveRotationList", "infoActiveRotationList", "");
+}
+
+
+function setRotateDuration()
+{
+  var duration = jsGetValue("ctrlRotSlideDuration", 2);
+  jsSendPOSTRequest("action=ctrl&type=setRotateDuration&duration="+duration);
+}
+
+
+function setActiveRotationList()
+{
+  var id = jsGetValue("ctrlRotationListsSel", 4);
+  if (id > 0)
+    jsSendPOSTRequest("action=ctrl&type=setActiveRotationList&id="+id, refreshActiveRotationList);
+  else
+    jsErrorMessageBox("No rotation list selected?");
+}
+
+
+function updateRotationList(id)
+{
+  var name = jsGetValue("ctrlEDRotationListName", 1);
+  jsSendPOSTRequest("action=ctrl&type=updateRotationList&id="+id+"&name="+name, refreshRotationListSel);
+}
+
+
+function moveRotationListSlide(list_id, dir)
+{
+  var msuccess = function(txt)
+  {
+    refreshRotationListEdit(list_id);
+  }
+
+  var slide = jsGetValue("ctrlEDRotationListSel", 4);
+  if (slide)
+  {
+    var str = slide.split("_");
+    jsSendPOSTRequest("action=ctrl&type=moveRotationListSlide&list_id="+list_id+
+      "&slide_id="+parseInt(str[0])+"&order_num="+parseInt(str[1])+"&dir="+dir, msuccess);
+  }
+  else
+    jsErrorMessageBox("No slide selected?");
+}
+
+
+function addRotationListSlide(list_id)
+{
+  var msuccess = function(txt)
+  {
+    refreshRotationListEdit(list_id);
+  }
+
+  var slide_id = jsGetValue("ctrlEDDisplaySlidesSel", 4);
+  if (slide_id > 0)
+    jsSendPOSTRequest("action=ctrl&type=addRotationListSlide&list_id="+list_id+"&slide_id="+slide_id, msuccess);
+  else
+    jsErrorMessageBox("No slide selected?");
+}
+
+
+function removeRotationListSlide(list_id)
+{
+  var msuccess = function(txt)
+  {
+    refreshRotationListEdit(list_id);
+  }
+
+  var slide = jsGetValue("ctrlEDRotationListSel", 4);
+  if (slide)
+  {
+    var str = slide.split("_");
+    jsSendPOSTRequest("action=ctrl&type=removeRotationListSlide&list_id="+list_id+
+      "&slide_id="+parseInt(str[0])+"&order_num="+parseInt(str[1]), msuccess);
+  }
+  else
+    jsErrorMessageBox("No slide selected?");
+}
+
+
+function editRotationList()
+{
+  var id = jsGetValue("ctrlRotationListsSel", 4);
+  if (id > 0)
+    jsSendPOSTRequest("action=get&type=infoRotationListEdit&id="+id, jsOpenAdminPopup);
+  else
+    jsErrorMessageBox("No rotation list selected?");
+}
+
+
+function newRotationList()
+{
+  var msuccess = function(txt)
+  {
+    refreshRotationListSel();
+    jsOpenAdminPopup(txt);
+  }
+
+  jsSendPOSTRequest("action=ctrl&type=newRotationList", msuccess);
+}
+
+
+function deleteRotationList()
+{
+  var mcb_ok = function(txt)
+  {
+    jsSendPOSTRequest("action=ctrl&type=deleteRotationList&id="+id, refreshRotationListSel);
+  }
+
+  var id = jsGetValue("ctrlRotationListsSel", 4);
+  if (id > 0)
+  {
+    jsConfirmBox("Are you <b>sure</b> you want to delete rotation list #"+id+"?",
+      mcb_ok, 0, 0);
+  }
+  else
+    jsErrorMessageBox("No rotation list selected?");
+}
+
+
+//
+// Display slide editing and handling
+//
+function refreshDisplaySlideListSel()
+{
+  jsRefreshItems("ctrlDisplaySlidesSel", "infoDisplaySlides", "");
+}
+
+
+function updateDisplaySlide(id)
+{
+  var vtitle = jsGetValue("ctrlDisplaySlideTitle", 1);
+  var vtext = jsGetValue("ctrlDisplaySlideText", 1);
+  jsSendPOSTRequest("action=ctrl&type=updateDisplaySlide&id="+id+"&title="+vtitle+"&text="+vtext, refreshDisplaySlideListSel);
+  jsCloseAdminPopup();
+  return false;
+}
+
+
+function editDisplaySlide()
+{
+  var id = jsGetValue("ctrlDisplaySlidesSel", 4);
+  if (id > 0)
+    jsSendPOSTRequest("action=get&type=infoDisplaySlideEdit&id="+id, jsOpenAdminPopup);
+  else
+    jsErrorMessageBox("No display slide selected?");
+}
+
+
+function copyDisplaySlide()
+{
+  var msuccess = function(txt)
+  {
+    refreshDisplaySlideListSel();
+    jsOpenAdminPopup(txt);
+  }
+
+  var id = jsGetValue("ctrlDisplaySlidesSel", 4);
+  if (id > 0)
+    jsSendPOSTRequest("action=ctrl&type=copyDisplaySlide&id="+id, msuccess);
+  else
+    jsErrorMessageBox("No display slide selected?");
+}
+
+
+function newDisplaySlide()
+{
+  var msuccess = function(txt)
+  {
+    refreshDisplaySlideListSel();
+    jsOpenAdminPopup(txt);
+  }
+
+  jsSendPOSTRequest("action=ctrl&type=newDisplaySlide", msuccess);
+}
+
+
+function deleteDisplaySlide()
+{
+  var msuccess = function(txt)
+  {
+    refreshDisplaySlideListSel();
+    refreshRotationListSel();
+  }
+
+  var mcb_ok = function(txt)
+  {
+    jsSendPOSTRequest("action=ctrl&type=deleteDisplaySlide&id="+id, msuccess);
+  }
+
+  var id = jsGetValue("ctrlDisplaySlidesSel", 4);
+  if (id > 0)
+  {
+    jsConfirmBox("Are you <b>sure</b> you want to delete slide list #"+id+"?",
+      mcb_ok, 0, 0);
+  }
+  else
+    jsErrorMessageBox("No display slide selected?");
+}
+
+
+function activateTempSlide()
+{
+  var id = jsGetValue("ctrlDisplaySlidesSel", 4);
+  var duration = jsGetValue("ctrlTempSlideDuration", 2);
+  if (id > 0)
+    jsSendPOSTRequest("action=ctrl&type=setTempSlide&id="+id+"&duration="+duration, jsMessageBox);
+  else
+    jsErrorMessageBox("No slide selected?");
+}
+
+
+function skipToNextSlide()
+{
+  jsSendPOSTRequest("action=ctrl&type=skipToNextSlide");
+}
--- a/admin.js	Tue Jan 24 16:44:30 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,954 +0,0 @@
-//
-// FAPWeb - Simple Web-based Demoparty Management System
-// Party administration page frontend module
-// (C) Copyright 2012-2015 Tecnic Software productions (TNSP)
-//
-
-var activeAttendee = -1, prevAttendee = -1, activeAttendeeTmp = "";
-var activeEntry = -1, prevEntry = -1, activeEntryTmp = "";
-
-var registeredTabs = Object();
-var activeTabs = Object();
-
-
-//
-// Tab related code
-//
-function jsUpdateTabList(tabset, extra)
-{
-  var tabs = "";
-  var content = "";
-
-  // Update the tab header list for this tabset
-  for (var id in registeredTabs[tabset])
-  {
-    var thead = registeredTabs[tabset][id];
-    tabs += "<a id=\"tabHead"+ tabset + id +
-      "\" href=\"#\" onClick=\"jsSwitchActiveTab('"+tabset+"', '"+id+
-      "')\">"+ thead +"</a> ";
-    
-    content += "<div id=\"tabCont"+ tabset + id +"\"></div>";
-  }
-
-  // Update the DOM elements
-  var item = document.getElementById("tabHeaders"+ tabset);
-  if (item) item.innerHTML = tabs + extra;
-
-  item = document.getElementById("tabContents"+ tabset);
-  if (item) item.innerHTML = content;
-}
-
-
-function jsRegisterTab(tabset, id, name)
-{
-  // Create tabset object "array" if it does not exist
-  if (!registeredTabs[tabset])
-    registeredTabs[tabset] = Object();
-
-  // Register tab
-  registeredTabs[tabset][id] = name;
-}
-
-
-function jsSwitchActiveTab(tabset, tab)
-{
-  // Go through all registered tabs to update their state
-  for (var id in registeredTabs[tabset])
-  {
-    var tabContent = document.getElementById("tabCont"+ tabset + id);
-    var tabHead = document.getElementById("tabHead"+ tabset + id);
-    if (tabContent && tabHead)
-    {
-      tabContent.style.display = (tab == id) ? "block" : "none";
-      tabHead.className = (tab == id) ? "active" : "inactive";
-      if (tab == id)
-      {
-        // Set active tab and refresh contents
-        activeTabs[tabset] = id;
-        setTimeout(function(qtabset, qid) {
-          switch (qtabset)
-          {
-            case "CM": refreshDispatchCM(qid); break;
-            case "CC": refreshDispatchCC(qid); break;
-            case "CS": refreshDispatchCS(qid); break;
-            default: jsMessageBox("Invalid tabset value: '"+ qtabset +"'.");
-          }
-        }, 10, tabset, id);
-        jsCancelUploadCBS();
-      }
-      else
-      {
-        // Clear inactive tabs
-        tabContent.innerHTML = "";
-      }
-    }
-  }
-}
-
-
-//
-// Admin interface specific popups
-//
-function jsHandleAdminPopupKeys(ev)
-{
-  ev = ev || window.event;
-  var key = ev.keyCode ? ev.keyCode : ev.which;
-  if (key == 27)
-  {
-    jsCloseAdminPopup();
-    return false;
-  }
-  else
-    return true;
-}
-
-
-function jsCloseAdminPopup()
-{
-  var nitem = document.getElementById("adminPopup");
-  if (nitem)
-  {
-    document.onkeydown = null;
-
-    nitem.innerHTML = "";
-    nitem.style.display = "none";
-  }
-}
-
-
-function jsOpenAdminPopup(txt)
-{
-  var nitem = document.getElementById("adminPopup");
-  if (nitem)
-  {
-    document.onkeydown = jsHandleAdminPopupKeys;
-
-    nitem.innerHTML = txt;
-    nitem.style.display = "block";
-  }
-}
-
-
-//
-// Generic element refresh
-//
-function jsRefreshItems(id,name,extra)
-{
-  var msuccess = function(txt)
-  {
-    var nitem = document.getElementById(id);
-    if (nitem) nitem.innerHTML = txt;
-  }
-
-  jsSendPOSTRequest("action=get&type="+name+extra, msuccess);
-}
-
-
-function jsDeleteItem(id,prefix,type,func,dsc,dsc2)
-{
-  var msuccess = function(txt)
-  {
-    var item = document.getElementById(prefix+id);
-    item.style.display = "none";
-    setTimeout(func, 50);
-  }
-
-  // Clearly mark the element when asking confirmation
-  var item = document.getElementById(prefix+id);
-  var tmp = item.className;
-  item.className += " deleteWarning";
-
-  // Ask confirmation for deletion
-  var mcb_ok = function (data)
-  {
-    jsSendPOSTRequest("action=delete&type="+type+"&id="+id, msuccess);
-    item.className = tmp;
-  }
-  
-  var mcb_cancel = function (data)
-  {
-    item.className = tmp;
-  }
-  
-  if (dsc)
-    jsConfirmBox("Are you sure you want to delete "+dsc+" #"+id+"?", mcb_ok, mcb_cancel, 0);
-  else
-    jsConfirmBox(dsc2, mcb_ok, mcb_cancel, 0);
-}
-
-
-function jsRefreshPanels(id, tab, args, set_default)
-{
-  var msuccess = function(txt)
-  {
-    var nitem = document.getElementById(tab);
-    if (nitem)
-    {
-      if (nitem.innerHTML == "")
-      {
-        nitem.innerHTML =
-          "<div id=\"tabHeaders"+id+"\" class=\"tabHeadersSub\"></div>" +
-          "<div id=\"tabContents"+id+"\" class=\"tabContentsSub\"></div>";
-      }
-
-      try {
-        var tmp = JSON.parse("{"+ txt +"}");
-        registeredTabs[id] = tmp;
-        jsUpdateTabList(id, "");
-        if (activeTabs[id])
-          jsSwitchActiveTab(id, activeTabs[id]);
-        else
-        if (set_default == "*")
-        {
-          for (var nid in registeredTabs[id])
-          {
-            jsSwitchActiveTab(id, nid);
-            break;
-          }
-        }
-        else
-        if (set_default != "")
-          jsSwitchActiveTab(id, set_default);
-      }
-      catch (err) {
-        jsErrorMessageBox("JSON.parse("+ txt +") failure: "+ err);
-      }
-    }
-  }
-
-  jsSendPOSTRequest("action=get&type="+args, msuccess);
-}
-
-
-function refreshDispatchCC(id)
-{
-  switch (id)
-  {
-    case "News"      : jsRefreshItems("tabContCCNews", "news", ""); break;
-    case "Attendees" : jsRefreshItems("tabContCCAttendees", "attendees", ""); activeAttendee = -1; break;
-    case "Voting"    : jsRefreshItems("tabContCCVoting", "voters", ""); break;
-    case "Compos"    : jsRefreshItems("tabContCCCompos", "compos", ""); break;
-    case "InfoSys"   : jsRefreshItems("tabContCCInfoSys", "infoMain", ""); break;
-    case "Settings"  : jsRefreshPanels("CS", "tabContCCSettings", "settingslist", "*"); break;
-    case "Entries"   : jsRefreshPanels("CM", "tabContCCEntries", "compolist", ""); break;
-    default          : alert("Invalid id: '"+ id +"'.'");
-  }
-}
-
-
-function refreshDispatchCS(id)
-{
-  jsRefreshItems("tabContCS"+ id, "settings", "&id="+ id);
-}
-
-
-function refreshDispatchCM(id)
-{
-  prevEntry = activeEntry = -1;
-  jsRefreshItems("tabContCM"+ id, "entries", "&id="+ id);
-}
-
-
-//
-// Site news management
-//
-function addNews()
-{
-  var args = jsMakePostArgs({"title":1,"text":1,"author":1}, "nn", "");
-
-  var msuccess = function(txt)
-  {
-    setTimeout(function () { refreshDispatchCC("News"); }, 50);
-  }
-
-  if (args != "")
-    jsSendPOSTRequest("action=add&type=news&"+args, msuccess);
-
-  return false;
-}
-
-
-function deleteNews(id)
-{
-  jsDeleteItem(id, "news", "news", function() { refreshDispatchCC("News"); }, "news item");
-}
-
-
-function updateNews(id)
-{
-  var args = jsMakePostArgs({"title":1,"text":1,"author":1}, "ne", id);
-
-  var msuccess = function(txt)
-  {
-    jsRefreshItems("news"+id, "newsitem", "&id="+id);
-  }
-
-  if (args != "")
-    jsSendPOSTRequest("action=update&type=news&id="+id+"&"+args, msuccess);
-}
-
-
-//
-// Visitor/attendee management
-//
-function addAttendee()
-{
-  var args = jsMakePostArgs({"name":1,"groups":1,"oneliner":1,"email":1}, "ne", "x");
-
-  var msuccess = function(txt)
-  {
-    setTimeout(function() { refreshDispatchCC("Attendees"); }, 50);
-  }
-
-  if (args != "")
-    jsSendPOSTRequest("action=add&type=attendees&"+args, msuccess);
-
-  return false;
-}
-
-
-function deleteAttendee(id)
-{
-  jsDeleteItem(id, "attendee", "attendees", function () { refreshDispatchCC("Attendees"); }, "attendee");
-}
-
-
-function updateAttendee(id)
-{
-  var args = jsMakePostArgs({"name":1,"groups":1,"oneliner":1,"email":1,"reghost":1}, "at", id);
-
-  var msuccess = function(txt)
-  {
-    activateAttendee(-1);
-  }
-
-  if (args != "")
-    jsSendPOSTRequest("action=update&type=attendees&id="+id+"&"+args, msuccess);
-}
-
-
-function activateAttendee(id)
-{
-  var msuccess1 = function(txt)
-  {
-    var nitem = document.getElementById("attendee"+prevAttendee);
-    if (nitem)
-    {
-      nitem.innerHTML = txt;
-      nitem.className = activeAttendeeTmp;
-    }
-  }
-
-  var msuccess2 = function(txt)
-  {
-    var nitem = document.getElementById("attendee"+activeAttendee);
-    if (nitem)
-    {
-      nitem.innerHTML = txt;
-      activeAttendeeTmp = nitem.className;
-      nitem.className += " active";
-      activeAttendee = id;
-    }
-  }
-
-  if (activeAttendee != id)
-  {
-    prevAttendee = activeAttendee;
-    activeAttendee = id;
-
-    if (prevAttendee != -1)
-      jsSendPOSTRequest("action=get&type=attendee&id="+prevAttendee+"&edit=0", msuccess1);
-
-    if (activeAttendee != -1)
-      jsSendPOSTRequest("action=get&type=attendee&id="+activeAttendee+"&edit=1", msuccess2);
-  }
-}
-
-
-//
-// Compo management
-//
-function addCompo()
-{
-  var args = jsMakePostArgs({"name":1, "description":1}, "nc", "");
-
-  var msuccess = function(txt)
-  {
-    setTimeout(function () { refreshDispatchCC("Compos"); }, 50);
-  }
-
-  if (args != "")
-    jsSendPOSTRequest("action=add&type=compo&"+args, msuccess);
-  return false;
-}
-
-
-function updateCompoType(id)
-{
-  var msuccess = function(txt)
-  {
-    jsRefreshItems("compo"+id, "compo", "&id="+id);
-  }
-
-  jsSendPOSTRequest("action=update&type=compotype&id="+id+"&ctype="+jsGetValue("cotype"+id+"Sel", 4), msuccess);
-}
-
-
-function updateCompoVoting(id)
-{
-  var msuccess = function(txt)
-  {
-    jsRefreshItems("covoting"+id, "compovoting", "&id="+id);
-  }
-
-  jsSendPOSTRequest("action=update&type=compovoting&id="+id+"&voting="+jsGetValue("covotingbutton"+id, 3), msuccess);
-}
-
-
-function updateCompo(id)
-{
-  var args = jsMakePostArgs({"name":1, "description":1, "notes":1, "visible":3, "voting":3, "show_authors":3, "cpath":1, "preview_type":4}, "co", id, true);
-
-  var msuccess = function(txt)
-  {
-    jsRefreshItems("compo"+id, "compo", "&id="+id);
-  }
-
-  if (args != "")
-    jsSendPOSTRequest("action=update&type=compo&id="+id+"&"+args, msuccess);
-}
-
-
-function deleteCompo(id)
-{
-  jsDeleteItem(id, "compo", "compo", function () { refreshDispatchCC("Compos"); }, 0, 
-    "Are you ABSOLUTELY sure you want to delete compo #"+id+"? "+
-    "This will delete all votes AND entries related to it!");
-}
-
-
-//
-// Entry management
-//
-function refreshEntryCompos()
-{
-  setTimeout(function () { refreshDispatchCC("Entries"); }, 50);
-}
-
-
-function activateEntry(id, force)
-{
-  var msuccess1 = function(txt)
-  {
-    var nitem = document.getElementById("entry"+prevEntry);
-    if (nitem)
-    {
-      nitem.innerHTML = txt;
-      nitem.className = activeEntryTmp;
-    }
-  }
-
-  var msuccess2 = function(txt)
-  {
-    var nitem = document.getElementById("entry"+activeEntry);
-    if (nitem)
-    {
-      nitem.innerHTML = txt;
-      activeEntryTmp = nitem.className;
-      nitem.className += " active";
-      activeEntry = id;
-    }
-  }
-
-  if (activeEntry != id || force)
-  {
-    jsCancelUploadCBS();
-
-    prevEntry = activeEntry;
-    activeEntry = id;
-
-    if (prevEntry != -1)
-      jsSendPOSTRequest("action=get&type=entry&id="+prevEntry+"&edit=0", msuccess1);
-
-    if (activeEntry != -1)
-      jsSendPOSTRequest("action=get&type=entry&id="+activeEntry+"&edit=1", msuccess2);
-  }
-}
-
-
-function addEntry(id)
-{
-  var args = jsMakePostArgs({"name":1, "author":1, "filename":1, "info":1, "notes":1, "evalue":2}, "ne", id, true);
-
-  jsCancelUploadCBS();
-
-  var msuccess = function(txt)
-  {
-    setTimeout(function (q) { refreshDispatchCM(qid); }, 50, id);
-    refreshEntryCompos();
-  }
-
-  if (args != "")
-    jsSendPOSTRequest("action=add&type=entry&compo_id="+id+"&"+args, msuccess);
-
-  return false;
-}
-
-
-function updateEntry(cid, id, edit)
-{
-  var args = jsMakePostArgs({"name":1, "author":1, "filename":1, "info":1, "notes":1, "compo_id":4, "evalue":2, "preview_type":4}, "en", id, true);
-  var has_id = "compo_id" in lastPostArgs;
-  var compo_id = lastPostArgs["compo_id"];
-
-  jsCancelUploadCBS();
-
-  if (!("compo_id" in lastPostArgs))
-  {
-    args += "&compo_id=" + cid;
-    compo_id = cid;
-  }
-
-  var msuccess = function(txt)
-  {
-    if (cid != compo_id)
-    {
-      var nitem = document.getElementById("entry"+ id);
-      if (nitem)
-        nitem.style.display = "none";
-    }
-    else
-      activateEntry(edit ? id : -1, true);
-    refreshEntryCompos();
-  }
-
-  var mcb_ok = function(data)
-  {
-    jsSendPOSTRequest("action=update&type=entry&id="+id+"&edit="+edit+"&"+args, msuccess);
-    refreshEntryCompos();
-  }
-
-  var mcb_cancel = function(data)
-  {
-    activeEntry = -1;
-  }
-
-  if (args != "")
-  {
-    if (cid != compo_id)
-    {
-      jsConfirmBox("Are you sure you want to change entry #"+id+
-        "'s compo from "+cid+" to "+compo_id+"?", mcb_ok, mcb_cancel);
-    }
-    else
-      mcb_ok();
-  }
-}
-
-
-function deleteEntry(cid, id)
-{
-  jsCancelUploadCBS();
-  jsDeleteItem(id, "entry", "entries", function() { refreshDispatchCM(cid); refreshEntryCompos(); }, "entry");
-}
-
-
-//
-// Votekey management
-//
-function voteKeyInfoRefresh()
-{
-  jsRefreshItems("vkeyInfo", "votekey", "info");
-}
-
-
-function voteKeyRefresh(id)
-{
-  var msuccess2 = function(txt)
-  {
-    var nitem = document.getElementById("vkey"+id);
-    if (nitem)
-      nitem.className = txt;
-    voteKeyInfoRefresh();
-  }
-
-  var msuccess1 = function(txt)
-  {
-    var nitem = document.getElementById("vkey"+id);
-    if (nitem)
-      nitem.innerHTML = txt;
-    voteKeyInfoRefresh();
-  }
-
-  jsSendPOSTRequest("action=get&type=votekey&id="+id, msuccess1);
-  jsSendPOSTRequest("action=get&type=votekeyclass&id="+id, msuccess2);
-}
-
-
-function voteKeyUpdate(id, type, args)
-{
-  var msuccess = function(txt)
-  {
-    voteKeyRefresh(id);
-    voteKeyInfoRefresh();
-  }
-
-  jsSendPOSTRequest("action=votekey&type="+type+"&id="+id+"&"+args, msuccess);
-}
-
-
-function voteKeySetActive(id)
-{
-  var args = jsMakePostArgs({"active":3}, "vk", id);
-
-  var mcb_ok = function (data)
-  {
-    voteKeyUpdate(id, "active", args);
-  }
-
-  var mcb_cancel = function (data)
-  {
-    voteKeyRefresh(id);
-  }
-  
-  if (lastPostArgs["active"] == 0)
-    jsConfirmBox("Are you sure you want to deactivate vote key #"+id+"?", mcb_ok, mcb_cancel, 0);
-  else
-    mcb_ok(0);
-}
-
-
-function voteKeyAssign(id, mode)
-{
-  var args = jsMakePostArgs({"key_id":2}, "vk", id);
-
-  var mcb_ok = function (data)
-  {
-    voteKeyUpdate(id, (mode ? "assign" : "clear"), args);
-  }
-  
-  if (mode == 0)
-    jsConfirmBox("Are you sure you want to clear vote key assign #"+id+"?", mcb_ok, 0, 0);
-  else
-    mcb_ok(0);
-}
-
-
-//
-// Misc functions
-//
-function showScreenCmd(str)
-{
-  jsSendPOSTRequest("action=screencmd&cmd="+str);
-}
-
-
-function performSystemCheck()
-{
-  jsSendPOSTRequest("action=check", jsMessageBox);
-}
-
-
-function generateEntryPositions(id, patch)
-{
-  var msuccess = function (data)
-  {
-    refreshDispatchCM(id);
-    refreshCurrEntryData();
-    refreshCurrEntryListData();
-  }
-
-  var mcb_ok = function (data)
-  {
-    if (id == 0)
-      jsSendPOSTRequest("action=randomize&type=all&patch="+patch, msuccess);
-    else
-      jsSendPOSTRequest("action=randomize&type=compo&id="+id+"&patch="+patch, msuccess);
-  }
-  
-  if (patch == 0)
-  {
-    jsConfirmBox("Are you <b>ABSOLUTELY CERTAIN</b> you want to delete and regenerate entry show positions "+
-      (id == 0 ? "for ALL compos" : "for this compo") +"? "+
-      "<b>This will completely annihilate current show position numbers! AND FUCK UP YOUR EXPORTED FILE ORDER (ask ccr to know what that means.)</b>", mcb_ok, 0, 0);
-  }
-  else
-    mcb_ok(0);
-}
-
-
-//
-// Competition control
-//
-function setShowMode(mode)
-{
-  jsSendPOSTRequest("action=ctrl&type=setShowMode&mode="+mode);
-}
-
-
-function refreshCurrEntryData()
-{
-  jsRefreshItems("ctrlCurrCompoData", "infoCurrCompoData", "");
-}
-
-
-function refreshCurrEntryListData()
-{
-  jsRefreshItems("ctrlEntryList", "infoCurrEntryList", "");
-  refreshCurrEntryData();
-}
-
-
-function activateCompo()
-{
-  jsSendPOSTRequest("action=ctrl&type=setCompoID&id="+jsGetValue("ctrlCompoListSel", 4), refreshCurrEntryListData);
-}
-
-
-function setSelectedEntry()
-{
-  jsSendPOSTRequest("action=ctrl&type=setEntry&index="+jsGetValue("ctrlEntryListSel", 4), refreshCurrEntryData);
-}
-
-
-function switchEntry(dir)
-{
-  jsSendPOSTRequest("action=ctrl&type="+ (dir < 0 ? "prevEntry" : "nextEntry"), refreshCurrEntryListData);
-}
-
-
-//
-// Rotation list editing and handling
-//
-function refreshRotationListSel()
-{
-  jsRefreshItems("ctrlRotationListsSel", "infoRotationLists", "");
-}
-
-
-function refreshRotationListEdit(id)
-{
-  jsRefreshItems("ctrlRotationListEdit", "infoRotationListEdit", "&id="+id+"&full=0");
-  refreshRotationListSel();
-}
-
-
-function refreshActiveRotationList()
-{
-  jsRefreshItems("ctrlActiveRotationList", "infoActiveRotationList", "");
-}
-
-
-function setRotateDuration()
-{
-  var duration = jsGetValue("ctrlRotSlideDuration", 2);
-  jsSendPOSTRequest("action=ctrl&type=setRotateDuration&duration="+duration);
-}
-
-
-function setActiveRotationList()
-{
-  var id = jsGetValue("ctrlRotationListsSel", 4);
-  if (id > 0)
-    jsSendPOSTRequest("action=ctrl&type=setActiveRotationList&id="+id, refreshActiveRotationList);
-  else
-    jsErrorMessageBox("No rotation list selected?");
-}
-
-
-function updateRotationList(id)
-{
-  var name = jsGetValue("ctrlEDRotationListName", 1);
-  jsSendPOSTRequest("action=ctrl&type=updateRotationList&id="+id+"&name="+name, refreshRotationListSel);
-}
-
-
-function moveRotationListSlide(list_id, dir)
-{
-  var msuccess = function(txt)
-  {
-    refreshRotationListEdit(list_id);
-  }
-
-  var slide = jsGetValue("ctrlEDRotationListSel", 4);
-  if (slide)
-  {
-    var str = slide.split("_");
-    jsSendPOSTRequest("action=ctrl&type=moveRotationListSlide&list_id="+list_id+
-      "&slide_id="+parseInt(str[0])+"&order_num="+parseInt(str[1])+"&dir="+dir, msuccess);
-  }
-  else
-    jsErrorMessageBox("No slide selected?");
-}
-
-
-function addRotationListSlide(list_id)
-{
-  var msuccess = function(txt)
-  {
-    refreshRotationListEdit(list_id);
-  }
-
-  var slide_id = jsGetValue("ctrlEDDisplaySlidesSel", 4);
-  if (slide_id > 0)
-    jsSendPOSTRequest("action=ctrl&type=addRotationListSlide&list_id="+list_id+"&slide_id="+slide_id, msuccess);
-  else
-    jsErrorMessageBox("No slide selected?");
-}
-
-
-function removeRotationListSlide(list_id)
-{
-  var msuccess = function(txt)
-  {
-    refreshRotationListEdit(list_id);
-  }
-
-  var slide = jsGetValue("ctrlEDRotationListSel", 4);
-  if (slide)
-  {
-    var str = slide.split("_");
-    jsSendPOSTRequest("action=ctrl&type=removeRotationListSlide&list_id="+list_id+
-      "&slide_id="+parseInt(str[0])+"&order_num="+parseInt(str[1]), msuccess);
-  }
-  else
-    jsErrorMessageBox("No slide selected?");
-}
-
-
-function editRotationList()
-{
-  var id = jsGetValue("ctrlRotationListsSel", 4);
-  if (id > 0)
-    jsSendPOSTRequest("action=get&type=infoRotationListEdit&id="+id, jsOpenAdminPopup);
-  else
-    jsErrorMessageBox("No rotation list selected?");
-}
-
-
-function newRotationList()
-{
-  var msuccess = function(txt)
-  {
-    refreshRotationListSel();
-    jsOpenAdminPopup(txt);
-  }
-
-  jsSendPOSTRequest("action=ctrl&type=newRotationList", msuccess);
-}
-
-
-function deleteRotationList()
-{
-  var mcb_ok = function(txt)
-  {
-    jsSendPOSTRequest("action=ctrl&type=deleteRotationList&id="+id, refreshRotationListSel);
-  }
-
-  var id = jsGetValue("ctrlRotationListsSel", 4);
-  if (id > 0)
-  {
-    jsConfirmBox("Are you <b>sure</b> you want to delete rotation list #"+id+"?",
-      mcb_ok, 0, 0);
-  }
-  else
-    jsErrorMessageBox("No rotation list selected?");
-}
-
-
-//
-// Display slide editing and handling
-//
-function refreshDisplaySlideListSel()
-{
-  jsRefreshItems("ctrlDisplaySlidesSel", "infoDisplaySlides", "");
-}
-
-
-function updateDisplaySlide(id)
-{
-  var vtitle = jsGetValue("ctrlDisplaySlideTitle", 1);
-  var vtext = jsGetValue("ctrlDisplaySlideText", 1);
-  jsSendPOSTRequest("action=ctrl&type=updateDisplaySlide&id="+id+"&title="+vtitle+"&text="+vtext, refreshDisplaySlideListSel);
-  jsCloseAdminPopup();
-  return false;
-}
-
-
-function editDisplaySlide()
-{
-  var id = jsGetValue("ctrlDisplaySlidesSel", 4);
-  if (id > 0)
-    jsSendPOSTRequest("action=get&type=infoDisplaySlideEdit&id="+id, jsOpenAdminPopup);
-  else
-    jsErrorMessageBox("No display slide selected?");
-}
-
-
-function copyDisplaySlide()
-{
-  var msuccess = function(txt)
-  {
-    refreshDisplaySlideListSel();
-    jsOpenAdminPopup(txt);
-  }
-
-  var id = jsGetValue("ctrlDisplaySlidesSel", 4);
-  if (id > 0)
-    jsSendPOSTRequest("action=ctrl&type=copyDisplaySlide&id="+id, msuccess);
-  else
-    jsErrorMessageBox("No display slide selected?");
-}
-
-
-function newDisplaySlide()
-{
-  var msuccess = function(txt)
-  {
-    refreshDisplaySlideListSel();
-    jsOpenAdminPopup(txt);
-  }
-
-  jsSendPOSTRequest("action=ctrl&type=newDisplaySlide", msuccess);
-}
-
-
-function deleteDisplaySlide()
-{
-  var msuccess = function(txt)
-  {
-    refreshDisplaySlideListSel();
-    refreshRotationListSel();
-  }
-
-  var mcb_ok = function(txt)
-  {
-    jsSendPOSTRequest("action=ctrl&type=deleteDisplaySlide&id="+id, msuccess);
-  }
-
-  var id = jsGetValue("ctrlDisplaySlidesSel", 4);
-  if (id > 0)
-  {
-    jsConfirmBox("Are you <b>sure</b> you want to delete slide list #"+id+"?",
-      mcb_ok, 0, 0);
-  }
-  else
-    jsErrorMessageBox("No display slide selected?");
-}
-
-
-function activateTempSlide()
-{
-  var id = jsGetValue("ctrlDisplaySlidesSel", 4);
-  var duration = jsGetValue("ctrlTempSlideDuration", 2);
-  if (id > 0)
-    jsSendPOSTRequest("action=ctrl&type=setTempSlide&id="+id+"&duration="+duration, jsMessageBox);
-  else
-    jsErrorMessageBox("No slide selected?");
-}
-
-
-function skipToNextSlide()
-{
-  jsSendPOSTRequest("action=ctrl&type=skipToNextSlide");
-}
--- a/admin.php	Tue Jan 24 16:44:30 2017 +0200
+++ b/admin.php	Tue Jan 24 17:25:48 2017 +0200
@@ -8,48 +8,6 @@
 require_once "mconfig.inc.php";
 require_once "msite.inc.php";
 require_once "msession.inc.php";
-require_once "majax.inc.php";
-
-
-function stCreateSettingsData()
-{
-  echo
-  "<script type=\"text/javascript\">\n".
-  "\n".
-  "var jsSettingsArgs = [];\n";
-
-  foreach (stExecSQL("SELECT * FROM settings_groups") as $group)
-  {
-    $args = array();
-    if (($res = stExecSQL("SELECT * FROM settings WHERE vgroup=".$group["id"])) !== false)
-    {
-      foreach ($res as $item)
-      {
-        switch ($item["vtype"])
-        {
-          case VT_STR:
-          case VT_TEXT: $type = 1; break;
-          case VT_INT:  $type = 2; break;
-          case VT_BOOL: $type = 3; break;
-        }
-        $args[] = "\"".$item["key"]."\":".$type;
-      }
-    }
-
-    echo "jsSettingsArgs[".$group["id"]."] = {".implode(",", $args)."};\n";
-  }
-
-  echo
-  "\n".
-  "function jsUpdateSettings(id)\n".
-  "{\n".
-  "  if (id in jsSettingsArgs)\n".
-  "    jsSendPOSTRequest(\"action=update&type=settings&id=\"+id+\"&\"+jsMakePostArgs(jsSettingsArgs[id], \"st\", \"\"));\n".
-  "  return false;\n".
-  "}\n".
-  "\n".
-  "</script>\n";
-}
 
 
 function stLoginContent()
@@ -74,7 +32,9 @@
 // Start output
 $pageCSS = "css/admin.css";
 cmPrintPageHeader("FAPWeb Administration",
-  "  <meta http-equiv=\"Pragma\" content=\"no-cache\" />\n",
+  "  <meta http-equiv=\"Pragma\" content=\"no-cache\" />\n".
+  "  <script type=\"text/javascript\" src=\"admajax.js.php\"></script>\n".
+  "  <script type=\"text/javascript\" src=\"genajax.js\"></script>\n",
   FALSE);
 
 
@@ -121,8 +81,6 @@
 }
 else
 {
-stCreateSettingsData();
-stCommonAJAX("admajax.php", "admlogout.php");
 ?>
 <script type="text/javascript" src="admin.js"></script>
 
--- a/ajax.js	Tue Jan 24 16:44:30 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,385 +0,0 @@
-//
-// FAPWeb - Simple Web-based Demoparty Management System
-// Common JavaScript / AJAX code
-// (C) Copyright 2012-2015 Tecnic Software productions (TNSP)
-//
-var jsMessageBoxCBCancel = null, jsMessageBoxCBData = null, jsMessageBoxCBOK = null;
-var jsUploadCBS = [];
-
-
-function jsHandleMessageBoxKeys(ev)
-{
-  ev = ev || window.event;
-  var key = ev.keyCode ? ev.keyCode : ev.which;
-  if (key == 27)
-  {
-    jsCloseMessageBox(jsMessageBoxCBCancel, jsMessageBoxCBData);
-    return false;
-  }
-  else
-    return true;
-}
-
-
-function jsSetMessageBoxCBs(cb_ok, cb_cancel, cb_data)
-{
-  jsMessageBoxCBOK = cb_ok;
-  jsMessageBoxCBCancel = cb_cancel;
-  jsMessageBoxCBData = cb_data;
-}
-
-
-function jsCloseMessageBox(callback, cb_data)
-{
-  var nitem = document.getElementById("messageBox");
-  if (nitem)
-  {
-    document.onkeydown = null;
-    jsSetMessageBoxCBs(null, null, null);
-
-    if (nitem.style.display != "none")
-    {
-      nitem.style.display = "none";
-
-      if (callback && typeof(callback) === "function")
-        callback(cb_data);
-    }
-  }
-}
-
-
-function jsMessageBox(msg)
-{
-  var nitem = document.getElementById("messageBox");
-  if (nitem)
-  {
-    nitem.innerHTML = "<div class='messageBoxInner'>"+ msg +
-      "<div class='messageBoxControls'>"+
-      "<input id='msgBoxConfirmClose' type='button' value=' OK '>"+
-      "</div></div>";
-
-    document.onkeydown = jsHandleMessageBoxKeys;
-    jsSetMessageBoxCBs(null, null, null);
-
-    var elem = document.getElementById("msgBoxConfirmClose");
-    elem.onclick = function () { jsCloseMessageBox(0, 0); }
-
-    nitem.style.display = "block";
-  }
-}
-
-
-function jsErrorMessageBox(msg)
-{
-  jsMessageBox("<h1>Error!</h1><div>"+msg+"</div>");
-}
-
-
-function jsTitleMessageBox(title, msg)
-{
-  jsMessageBox("<h1>"+title+"</h1><div>"+msg+"</div>");
-}
-
-
-function jsConfirmBox(msg, cb_ok, cb_cancel, cb_data)
-{
-  var nitem = document.getElementById("messageBox");
-  if (nitem)
-  {
-    nitem.innerHTML = "<div class='messageBoxInner'><h1>Confirmation</h1><p>"+ msg +"</p>"+
-      "<div class='messageBoxControls'>"+
-      "<input id='msgBoxConfirmCancel' type='button' value=' Cancel '>"+
-      "<input id='msgBoxConfirmOK' type='button' value=' OK '>"+
-      "</div></div>";
-
-    document.onkeydown = jsHandleMessageBoxKeys;
-    jsSetMessageBoxCBs(cb_ok, cb_cancel, cb_data);
-
-    var elem = document.getElementById("msgBoxConfirmCancel");
-    elem.onclick = function () { jsCloseMessageBox(cb_cancel, cb_data); }
-
-    elem = document.getElementById("msgBoxConfirmOK");
-    elem.onclick = function () { jsCloseMessageBox(cb_ok, cb_data); }
-    
-    nitem.style.display = "block";
-  }
-}
-
-
-function jsStatusMsg(msg)
-{
-  var nitem = document.getElementById("nstatus");
-  if (nitem) nstatus.innerHTML = msg;
-}
-
-
-function strtrim(str)
-{
-  if (!str || str == null)
-    return "";
-  return str.replace(/^\s+|\s+$/g,'')
-}
-
-
-function strencode(str)
-{
-  return encodeURIComponent(str);
-}
-
-
-function jsCreateXMLRequest()
-{
-  var req;
-  if (window.XMLHttpRequest)
-  {
-    // Modern browsers
-    req = new XMLHttpRequest();
-  }
-  else
-  {
-    // Old IE versions
-    req = new ActiveXObject("Microsoft.XMLHTTP");
-  }
-  return req;
-}
-
-
-//
-// Function for creating AJAX POST request arguments list based
-// on fields and giving them specified types. Also basic check
-// for validity can be performed (e.g. field empty or not)
-//
-var lastPostArgs = Object();
-function jsMakePostArgs(fields, fprefix, fsuffix, nofail)
-{
-  var res = [];
-  lastPostArgs = Object();
-
-  for (var id in fields)
-  {
-    var elname = fprefix + id + fsuffix;
-    switch (fields[id])
-    {
-      case 4:
-        elname += "Sel";
-        break;
-    }
-
-    var elem = document.getElementById(elname);
-    if (!elem && !nofail)
-    {
-      jsErrorMessageBox("No such DOM element '"+ elname +"'.");
-      return "";
-    }
-
-    if (elem)
-    {
-      switch (fields[id])
-      {
-        case 1:
-          var vstr = strtrim(elem.value);
-          res.push(id+"="+strencode(vstr));
-          lastPostArgs[id] = vstr;
-          break;
-
-        case 2:
-          var vint = parseInt(strtrim(elem.value));
-          res.push(id+"="+vint);
-          lastPostArgs[id] = vint;
-          break;
-
-        case 3:
-          res.push(id+"="+(elem.checked ? "1" : "0"));
-          lastPostArgs[id] = elem.checked;
-          break;
-
-        case 4:
-          var vval = (elem.selectedIndex != -1) ? elem.options[elem.selectedIndex].value : -1;
-          res.push(id+"="+vval);
-          lastPostArgs[id] = vval;
-          break;
-
-        default:
-          jsErrorMessageBox("Unsupported field type in "+ elname);
-          return "";
-      }
-    }
-  }
-  return res.join("&");
-}
-
-
-function jsGetValue(elname, eltype)
-{
-  var elem = document.getElementById(elname);
-  if (!elem)
-  {
-    jsErrorMessageBox("No such DOM element '"+ elname +"'.");
-    return "";
-  }
-
-  switch (eltype)
-  {
-    case 1:
-      var vstr = strtrim(elem.value);
-      return strencode(vstr);
-
-    case 2:
-      var vint = parseInt(strtrim(elem.value));
-      return vint;
-
-    case 3:
-      return elem.checked ? "1" : "0";
-
-    case 4:
-      if (elem.selectedIndex != -1)
-        return elem.options[elem.selectedIndex].value;
-      else
-        return null;
-
-    default:
-      jsErrorMessageBox("Unsupported field type in "+ elname);
-      return "";
-  }
-}
-
-
-function jsShowPreviewImage(file)
-{
-  var nitem = document.getElementById("messageBox");
-  if (nitem)
-  {
-    nitem.innerHTML = "<div class='imageBoxInner'>"+
-      "<img src='"+file+"' alt='"+file+"' />"+
-      "</div>";
-
-    var elem = document.getElementById("messageBox");
-    elem.onclick = function () { jsCloseMessageBox(0, 0); }
-
-    nitem.style.display = "block";
-
-    return false;
-  }
-
-  return true;
-}
-
-
-function jsFormatSize(bytes)
-{
-  var suffixes = ["Bytes", "KiB", "MiB"];
-  var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
-  return (bytes / Math.pow(1024, i)).toFixed(1) +' '+ suffixes[i];
-}
-
-
-function jsStartFileUpload(formID, formTarget, fileSelID, fileMaxSize, fileCallback)
-{
-  var formFile = document.getElementById(fileSelID).files[0];
-  if (!formFile || typeof(formFile) !== "object")
-  {
-    jsErrorMessageBox("No file selected to be uploaded.");
-    return;
-  }
-
-  if (formFile.size > fileMaxSize)
-  {
-    jsErrorMessageBox("File size exceeds "+ jsFormatSize(fileMaxSize) +".");
-    return;
-  }
-
-  var filename = formFile.name;
-  var formElem = document.getElementById(formID);
-  if (!formElem)
-  {
-    jsErrorMessageBox("File upload form '"+ formID +"' element not found!");
-    return;
-  }
-
-  var formData = new FormData(formElem);
-  var req = jsCreateXMLRequest();
-  req.upload.addEventListener("progress", function(e)
-  {
-    if (e.lengthComputable)
-    {
-      var complete = Math.round(e.loaded * 100 / e.total);
-      if (complete < 100)
-        jsStatusMsg("Uploaded ["+filename+"] "+ complete.toString() +'%, '+ jsFormatSize(e.loaded));
-      else
-        jsStatusMsg("Upload ["+filename+"] finished ...");
-    }
-  }, false);
-  req.addEventListener("error", function(e)
-  {
-    jsErrorMessageBox("Error occured while uploading "+filename);
-  }, false);
-  req.addEventListener("abort", function(e)
-  {
-    jsStatusMsg("Upload of '"+filename+"' aborted.");
-  }, false);
-
-  req.onreadystatechange = function()
-  {
-    if (req.readyState == 4)
-    {
-      switch (req.status)
-      {
-        case 902:
-          jsStatusMsg(req.statusText);
-          jsMessageBox(req.responseText);
-          break;
-
-        case 903:
-          {
-            var nitem = document.getElementById("messageBox");
-            if (nitem)
-            {
-              nitem.innerHTML = "<div class='messageBoxInner'>"+ req.responseText +
-                "<div class='messageBoxControls'>"+
-                "</div></div>";
-              nitem.style.display = "block";
-            }
-          }
-          break;
-        
-        case 200:
-          if (fileCallback)
-          {
-            var tid = setTimeout(function(qtid)
-            {
-              jsRemoveUploadCB(qtid);
-              setTimeout(fileCallback, 10);
-              //jsTitleMessageBox("File upload", req.responseText);
-            }, 10, qtid);
-            jsUploadCBS.push(tid);
-          }
-          break;
-        
-        default:
-          jsStatusMsg("["+req.status+" - "+req.statusText+"] "+ req.responseText);
-          break;
-      }
-    }
-  }
-
-  req.open("POST", formTarget);
-  req.send(formData);
-}
-
-
-function jsCancelUploadCBS()
-{
-  if (jsUploadCBS.length > 0)
-  {
-    for (var tid in jsUploadCBS)
-      clearTimeout(tid);
-  }
-}
-
-
-function jsRemoveUploadCB(tid)
-{
-  var index = jsUploadCBS.indexOf(tid);
-  if (index >= 0)
-    jsUploadCBS.splice(index, 1);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/genajax.js	Tue Jan 24 17:25:48 2017 +0200
@@ -0,0 +1,385 @@
+//
+// FAPWeb - Simple Web-based Demoparty Management System
+// Common JavaScript / AJAX code
+// (C) Copyright 2012-2015 Tecnic Software productions (TNSP)
+//
+var jsMessageBoxCBCancel = null, jsMessageBoxCBData = null, jsMessageBoxCBOK = null;
+var jsUploadCBS = [];
+
+
+function jsHandleMessageBoxKeys(ev)
+{
+  ev = ev || window.event;
+  var key = ev.keyCode ? ev.keyCode : ev.which;
+  if (key == 27)
+  {
+    jsCloseMessageBox(jsMessageBoxCBCancel, jsMessageBoxCBData);
+    return false;
+  }
+  else
+    return true;
+}
+
+
+function jsSetMessageBoxCBs(cb_ok, cb_cancel, cb_data)
+{
+  jsMessageBoxCBOK = cb_ok;
+  jsMessageBoxCBCancel = cb_cancel;
+  jsMessageBoxCBData = cb_data;
+}
+
+
+function jsCloseMessageBox(callback, cb_data)
+{
+  var nitem = document.getElementById("messageBox");
+  if (nitem)
+  {
+    document.onkeydown = null;
+    jsSetMessageBoxCBs(null, null, null);
+
+    if (nitem.style.display != "none")
+    {
+      nitem.style.display = "none";
+
+      if (callback && typeof(callback) === "function")
+        callback(cb_data);
+    }
+  }
+}
+
+
+function jsMessageBox(msg)
+{
+  var nitem = document.getElementById("messageBox");
+  if (nitem)
+  {
+    nitem.innerHTML = "<div class='messageBoxInner'>"+ msg +
+      "<div class='messageBoxControls'>"+
+      "<input id='msgBoxConfirmClose' type='button' value=' OK '>"+
+      "</div></div>";
+
+    document.onkeydown = jsHandleMessageBoxKeys;
+    jsSetMessageBoxCBs(null, null, null);
+
+    var elem = document.getElementById("msgBoxConfirmClose");
+    elem.onclick = function () { jsCloseMessageBox(0, 0); }
+
+    nitem.style.display = "block";
+  }
+}
+
+
+function jsErrorMessageBox(msg)
+{
+  jsMessageBox("<h1>Error!</h1><div>"+msg+"</div>");
+}
+
+
+function jsTitleMessageBox(title, msg)
+{
+  jsMessageBox("<h1>"+title+"</h1><div>"+msg+"</div>");
+}
+
+
+function jsConfirmBox(msg, cb_ok, cb_cancel, cb_data)
+{
+  var nitem = document.getElementById("messageBox");
+  if (nitem)
+  {
+    nitem.innerHTML = "<div class='messageBoxInner'><h1>Confirmation</h1><p>"+ msg +"</p>"+
+      "<div class='messageBoxControls'>"+
+      "<input id='msgBoxConfirmCancel' type='button' value=' Cancel '>"+
+      "<input id='msgBoxConfirmOK' type='button' value=' OK '>"+
+      "</div></div>";
+
+    document.onkeydown = jsHandleMessageBoxKeys;
+    jsSetMessageBoxCBs(cb_ok, cb_cancel, cb_data);
+
+    var elem = document.getElementById("msgBoxConfirmCancel");
+    elem.onclick = function () { jsCloseMessageBox(cb_cancel, cb_data); }
+
+    elem = document.getElementById("msgBoxConfirmOK");
+    elem.onclick = function () { jsCloseMessageBox(cb_ok, cb_data); }
+    
+    nitem.style.display = "block";
+  }
+}
+
+
+function jsStatusMsg(msg)
+{
+  var nitem = document.getElementById("nstatus");
+  if (nitem) nstatus.innerHTML = msg;
+}
+
+
+function strtrim(str)
+{
+  if (!str || str == null)
+    return "";
+  return str.replace(/^\s+|\s+$/g,'')
+}
+
+
+function strencode(str)
+{
+  return encodeURIComponent(str);
+}
+
+
+function jsCreateXMLRequest()
+{
+  var req;
+  if (window.XMLHttpRequest)
+  {
+    // Modern browsers
+    req = new XMLHttpRequest();
+  }
+  else
+  {
+    // Old IE versions
+    req = new ActiveXObject("Microsoft.XMLHTTP");
+  }
+  return req;
+}
+
+
+//
+// Function for creating AJAX POST request arguments list based
+// on fields and giving them specified types. Also basic check
+// for validity can be performed (e.g. field empty or not)
+//
+var lastPostArgs = Object();
+function jsMakePostArgs(fields, fprefix, fsuffix, nofail)
+{
+  var res = [];
+  lastPostArgs = Object();
+
+  for (var id in fields)
+  {
+    var elname = fprefix + id + fsuffix;
+    switch (fields[id])
+    {
+      case 4:
+        elname += "Sel";
+        break;
+    }
+
+    var elem = document.getElementById(elname);
+    if (!elem && !nofail)
+    {
+      jsErrorMessageBox("No such DOM element '"+ elname +"'.");
+      return "";
+    }
+
+    if (elem)
+    {
+      switch (fields[id])
+      {
+        case 1:
+          var vstr = strtrim(elem.value);
+          res.push(id+"="+strencode(vstr));
+          lastPostArgs[id] = vstr;
+          break;
+
+        case 2:
+          var vint = parseInt(strtrim(elem.value));
+          res.push(id+"="+vint);
+          lastPostArgs[id] = vint;
+          break;
+
+        case 3:
+          res.push(id+"="+(elem.checked ? "1" : "0"));
+          lastPostArgs[id] = elem.checked;
+          break;
+
+        case 4:
+          var vval = (elem.selectedIndex != -1) ? elem.options[elem.selectedIndex].value : -1;
+          res.push(id+"="+vval);
+          lastPostArgs[id] = vval;
+          break;
+
+        default:
+          jsErrorMessageBox("Unsupported field type in "+ elname);
+          return "";
+      }
+    }
+  }
+  return res.join("&");
+}
+
+
+function jsGetValue(elname, eltype)
+{
+  var elem = document.getElementById(elname);
+  if (!elem)
+  {
+    jsErrorMessageBox("No such DOM element '"+ elname +"'.");
+    return "";
+  }
+
+  switch (eltype)
+  {
+    case 1:
+      var vstr = strtrim(elem.value);
+      return strencode(vstr);
+
+    case 2:
+      var vint = parseInt(strtrim(elem.value));
+      return vint;
+
+    case 3:
+      return elem.checked ? "1" : "0";
+
+    case 4:
+      if (elem.selectedIndex != -1)
+        return elem.options[elem.selectedIndex].value;
+      else
+        return null;
+
+    default:
+      jsErrorMessageBox("Unsupported field type in "+ elname);
+      return "";
+  }
+}
+
+
+function jsShowPreviewImage(file)
+{
+  var nitem = document.getElementById("messageBox");
+  if (nitem)
+  {
+    nitem.innerHTML = "<div class='imageBoxInner'>"+
+      "<img src='"+file+"' alt='"+file+"' />"+
+      "</div>";
+
+    var elem = document.getElementById("messageBox");
+    elem.onclick = function () { jsCloseMessageBox(0, 0); }
+
+    nitem.style.display = "block";
+
+    return false;
+  }
+
+  return true;
+}
+
+
+function jsFormatSize(bytes)
+{
+  var suffixes = ["Bytes", "KiB", "MiB"];
+  var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
+  return (bytes / Math.pow(1024, i)).toFixed(1) +' '+ suffixes[i];
+}
+
+
+function jsStartFileUpload(formID, formTarget, fileSelID, fileMaxSize, fileCallback)
+{
+  var formFile = document.getElementById(fileSelID).files[0];
+  if (!formFile || typeof(formFile) !== "object")
+  {
+    jsErrorMessageBox("No file selected to be uploaded.");
+    return;
+  }
+
+  if (formFile.size > fileMaxSize)
+  {
+    jsErrorMessageBox("File size exceeds "+ jsFormatSize(fileMaxSize) +".");
+    return;
+  }
+
+  var filename = formFile.name;
+  var formElem = document.getElementById(formID);
+  if (!formElem)
+  {
+    jsErrorMessageBox("File upload form '"+ formID +"' element not found!");
+    return;
+  }
+
+  var formData = new FormData(formElem);
+  var req = jsCreateXMLRequest();
+  req.upload.addEventListener("progress", function(e)
+  {
+    if (e.lengthComputable)
+    {
+      var complete = Math.round(e.loaded * 100 / e.total);
+      if (complete < 100)
+        jsStatusMsg("Uploaded ["+filename+"] "+ complete.toString() +'%, '+ jsFormatSize(e.loaded));
+      else
+        jsStatusMsg("Upload ["+filename+"] finished ...");
+    }
+  }, false);
+  req.addEventListener("error", function(e)
+  {
+    jsErrorMessageBox("Error occured while uploading "+filename);
+  }, false);
+  req.addEventListener("abort", function(e)
+  {
+    jsStatusMsg("Upload of '"+filename+"' aborted.");
+  }, false);
+
+  req.onreadystatechange = function()
+  {
+    if (req.readyState == 4)
+    {
+      switch (req.status)
+      {
+        case 902:
+          jsStatusMsg(req.statusText);
+          jsMessageBox(req.responseText);
+          break;
+
+        case 903:
+          {
+            var nitem = document.getElementById("messageBox");
+            if (nitem)
+            {
+              nitem.innerHTML = "<div class='messageBoxInner'>"+ req.responseText +
+                "<div class='messageBoxControls'>"+
+                "</div></div>";
+              nitem.style.display = "block";
+            }
+          }
+          break;
+        
+        case 200:
+          if (fileCallback)
+          {
+            var tid = setTimeout(function(qtid)
+            {
+              jsRemoveUploadCB(qtid);
+              setTimeout(fileCallback, 10);
+              //jsTitleMessageBox("File upload", req.responseText);
+            }, 10, qtid);
+            jsUploadCBS.push(tid);
+          }
+          break;
+        
+        default:
+          jsStatusMsg("["+req.status+" - "+req.statusText+"] "+ req.responseText);
+          break;
+      }
+    }
+  }
+
+  req.open("POST", formTarget);
+  req.send(formData);
+}
+
+
+function jsCancelUploadCBS()
+{
+  if (jsUploadCBS.length > 0)
+  {
+    for (var tid in jsUploadCBS)
+      clearTimeout(tid);
+  }
+}
+
+
+function jsRemoveUploadCB(tid)
+{
+  var index = jsUploadCBS.indexOf(tid);
+  if (index >= 0)
+    jsUploadCBS.splice(index, 1);
+}
--- a/majax.inc.php	Tue Jan 24 16:44:30 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-<?
-//
-// FAPWeb - Simple Web-based Demoparty Management System
-// Common AJAX Javascript code module
-// (C) Copyright 2012-2015 Tecnic Software productions (TNSP)
-//
-
-
-function stCommonAJAX($backend, $failover)
-{
-?>
-<script type="text/javascript" src="ajax.js"></script>
-<script type="text/javascript">
-
-
-function jsSendPOSTRequest(params, success, failure)
-{
-<?
-  if (($csrfID = stGetSessionItem("csrfID", FALSE)) !== FALSE)
-    echo "  params += \"&csrfID=".$csrfID."\";\n";
-?>
-  var req = jsCreateXMLRequest();
-  req.open("POST", "<? echo $backend ?>", true);
-  req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
-  req.setRequestHeader("Content-length", params.length);
-  req.setRequestHeader("Connection", "close");
-
-  req.onreadystatechange = function()
-  {
-    if (req.readyState == 4)
-    {
-      switch (req.status)
-      {
-        case 404:
-          window.location = "<? echo $failover ?>";
-          break;
-        
-        case 902:
-          jsStatusMsg(req.statusText);
-          jsMessageBox(req.responseText);
-          break;
-
-        case 903:
-          {
-            var nitem = document.getElementById("messageBox");
-            if (nitem)
-            {
-              nitem.innerHTML = "<div class='messageBoxInner'>"+ req.responseText +
-                "<div class='messageBoxControls'>"+
-                "</div></div>";
-              nitem.style.display = "block";
-            }
-          }
-          break;
-        
-        case 200:
-          if (success)
-            success(req.responseText);
-          jsStatusMsg(req.statusText);
-          break;
-        
-        default:
-          if (failure)
-            failure(req.status, req.statusText, req.responseText);
-          else
-            jsStatusMsg("["+req.status+" - "+req.statusText+"] "+ req.responseText);
-          break;
-      }
-    }
-  }
-  req.send(params);
-}
-
-</script>
-<?
-}
-?>
\ No newline at end of file
--- a/msitegen.inc.php	Tue Jan 24 16:44:30 2017 +0200
+++ b/msitegen.inc.php	Tue Jan 24 17:25:48 2017 +0200
@@ -959,6 +959,72 @@
 }
 
 
+function stCommonAJAX($backend, $failover)
+{
+?>
+function jsSendPOSTRequest(params, success, failure)
+{
+<?php
+  if (($csrfID = stGetSessionItem("csrfID", FALSE)) !== FALSE)
+    echo "  params += \"&csrfID=".$csrfID."\";\n";
+  else
+    echo "// No CSRF?\n";
+?>
+  var req = jsCreateXMLRequest();
+  req.open("POST", "<? echo $backend ?>", true);
+  req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+  req.setRequestHeader("Content-length", params.length);
+  req.setRequestHeader("Connection", "close");
+
+  req.onreadystatechange = function()
+  {
+    if (req.readyState == 4)
+    {
+      switch (req.status)
+      {
+        case 404:
+          window.location = "<? echo $failover ?>";
+          break;
+        
+        case 902:
+          jsStatusMsg(req.statusText);
+          jsMessageBox(req.responseText);
+          break;
+
+        case 903:
+          {
+            var nitem = document.getElementById("messageBox");
+            if (nitem)
+            {
+              nitem.innerHTML = "<div class='messageBoxInner'>"+ req.responseText +
+                "<div class='messageBoxControls'>"+
+                "</div></div>";
+              nitem.style.display = "block";
+            }
+          }
+          break;
+        
+        case 200:
+          if (success)
+            success(req.responseText);
+          jsStatusMsg(req.statusText);
+          break;
+        
+        default:
+          if (failure)
+            failure(req.status, req.statusText, req.responseText);
+          else
+            jsStatusMsg("["+req.status+" - "+req.statusText+"] "+ req.responseText);
+          break;
+      }
+    }
+  }
+  req.send(params);
+}
+
+<?php
+}
+
 //
 // CLI related helper functions
 //
--- a/pages/vote.inc.php	Tue Jan 24 16:44:30 2017 +0200
+++ b/pages/vote.inc.php	Tue Jan 24 17:25:48 2017 +0200
@@ -5,7 +5,6 @@
 // (C) Copyright 2012-2015 Tecnic Software productions (TNSP)
 //
 $sessionType = "user";
-require_once "majax.inc.php";
 
 
 function stGetVoteButton()
@@ -77,7 +76,6 @@
 else
 if (($mode = stGetSessionItem("mode")) == "vote")
 {
-  stCommonAJAX("usrajax.php", "usrlogout.php");
 ?>
 <noscript>
 <div class="notice">If your browser supports JavaScript, enable it for smoother voting experience.</div>
--- a/show.js	Tue Jan 24 16:44:30 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-//
-// FAPWeb - Simple Web-based Demoparty Management System
-// Party main screen viewer
-// (C) Copyright 2012-2017 Tecnic Software productions (TNSP)
-//
-
-var failCount = 0;
-var lastUpdate = 0;
-var errorView = false;
-
-
-function updateView(txt)
-{
-  var view = document.getElementById("mainView");
-  if (view && txt != false && txt != "")
-    view.innerHTML = txt;
-}
-
-
-function displayError()
-{
-  // Increase failure count
-  if (++failCount >= 3 && !errorView)
-  {
-    errorView = true;
-    updateView("<div class=\"slideHeader\"><div class=\"slideHeaderDiv\"></div></div><div class=\"slideText\"><div class=\"guru\">Software Failure.&nbsp;&nbsp;&nbsp;Press left mouse button to continue.<br />Guru Meditation #00000004.0000AAC0</div></div>");
-  }
-}
-
-
-//
-// Update view when triggered by main tick
-//
-function viewChanged()
-{
-  var msuccess2 = function(txt)
-  {
-    // Successfully fetched new data, initiate view update
-    updateView(txt);
-  }
-
-  var msuccess1 = function(txt)
-  {
-    lastUpdate = txt;
-    jsSendPOSTRequest("action=get&type=slide", msuccess2, displayError);
-  }
-
-  jsSendPOSTRequest("action=get&type=update", msuccess1, displayError);
-}
-
-
-function setTickUpdate(qtime)
-{
-  if (!timeOutSet)
-  {
-    timeOutSet = true;
-    setTimeout(function() { tickMain(); }, qtime);
-  }
-}
-
-
-//
-// Main tick function, check for updates from server
-//
-function tickMain()
-{
-  timeOutSet = false;
-
-  var msuccess = function(txt)
-  {
-    failCount = 0;
-    if (txt == "changed")
-    {
-      viewChanged();
-      setTickUpdate(250);
-    }
-    else
-    if (txt == "reload")
-    {
-      location.reload();
-    }
-    else
-    {
-      setTickUpdate(500);
-    }
-  }
-  
-  var mfail = function(txt)
-  {
-    displayError();
-    setTickUpdate(5000);
-  }
-
-  jsSendPOSTRequest("action=check&lastUpdate="+lastUpdate, msuccess, mfail);
-}
-
-var timeOutSet = false;
-setTickUpdate(100);
-viewChanged();
--- a/show.php	Tue Jan 24 16:44:30 2017 +0200
+++ b/show.php	Tue Jan 24 17:25:48 2017 +0200
@@ -7,14 +7,14 @@
 require_once "mconfig.inc.php";
 require_once "msite.inc.php";
 require_once "msession.inc.php";
-require_once "majax.inc.php";
 
 $pageCSS = "css/show.css";
 
 stSetupCacheControl();
 
-cmPrintPageHeader("PARTY INFORMATION DISPLAY SYSTEM", "", FALSE);
-stCommonAJAX("showajax.php", "show.php");
+cmPrintPageHeader("PARTY INFORMATION DISPLAY SYSTEM",
+  "  <script type=\"text/javascript\" src=\"showajax.js.php\"></script>\n".
+  "  <script type=\"text/javascript\" src=\"genajax.js\"></script>\n", FALSE);
 ?>
 
 <noscript>
@@ -30,7 +30,6 @@
 
 <div class="showView" id="mainView"></div>
 
-<script type="text/javascript" src="show.js"></script>
 <?
 cmPrintPageFooter(FALSE);
 ?>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/showajax.js.php	Tue Jan 24 17:25:48 2017 +0200
@@ -0,0 +1,114 @@
+<?php
+header("Content-Type: application/javascript");
+require_once "mconfig.inc.php";
+require_once "msite.inc.php";
+require_once "msession.inc.php";
+?>
+//
+// FAPWeb - Simple Web-based Demoparty Management System
+// Party main screen viewer
+// (C) Copyright 2012-2017 Tecnic Software productions (TNSP)
+//
+<?php
+stCommonAJAX("showajax.php", "show.php");
+?>
+
+var failCount = 0;
+var lastUpdate = 0;
+var errorView = false;
+
+
+function updateView(txt)
+{
+  var view = document.getElementById("mainView");
+  if (view && txt != false && txt != "")
+    view.innerHTML = txt;
+}
+
+
+function displayError()
+{
+  // Increase failure count
+  if (++failCount >= 3 && !errorView)
+  {
+    errorView = true;
+    updateView("<div class=\"slideHeader\"><div class=\"slideHeaderDiv\"></div></div><div class=\"slideText\"><div class=\"guru\">Software Failure.&nbsp;&nbsp;&nbsp;Press left mouse button to continue.<br />Guru Meditation #00000004.0000AAC0</div></div>");
+  }
+}
+
+
+//
+// Update view when triggered by main tick
+//
+function viewChanged()
+{
+  var msuccess2 = function(txt)
+  {
+    // Successfully fetched new data, initiate view update
+    updateView(txt);
+  }
+
+  var msuccess1 = function(txt)
+  {
+    lastUpdate = txt;
+    jsSendPOSTRequest("action=get&type=slide", msuccess2, displayError);
+  }
+
+  jsSendPOSTRequest("action=get&type=update", msuccess1, displayError);
+}
+
+
+function setTickUpdate(qtime)
+{
+  if (!timeOutSet)
+  {
+    timeOutSet = true;
+    setTimeout(function() { tickMain(); }, qtime);
+  }
+}
+
+
+//
+// Main tick function, check for updates from server
+//
+function tickMain()
+{
+  timeOutSet = false;
+
+  var msuccess = function(txt)
+  {
+    failCount = 0;
+    if (txt == "changed")
+    {
+      viewChanged();
+      setTickUpdate(250);
+    }
+    else
+    if (txt == "reload")
+    {
+      location.reload();
+    }
+    else
+    {
+      setTickUpdate(500);
+    }
+  }
+  
+  var mfail = function(txt)
+  {
+    displayError();
+    setTickUpdate(5000);
+  }
+
+  jsSendPOSTRequest("action=check&lastUpdate="+lastUpdate, msuccess, mfail);
+}
+
+var timeOutSet = false;
+
+document.addEventListener("DOMContentLoaded",
+function ()
+{
+  setTickUpdate(100);
+  viewChanged();
+});
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usrajax.js.php	Tue Jan 24 17:25:48 2017 +0200
@@ -0,0 +1,18 @@
+<?php
+//
+// FAPWeb - Simple Web-based Demoparty Management System
+// User actions page AJAX javascript module
+// (C) Copyright 2012-2015 Tecnic Software productions (TNSP)
+//
+$sessionType = "user";
+require_once "mconfig.inc.php";
+require_once "msite.inc.php";
+require_once "msession.inc.php";
+
+
+if (!stUserSessionAuth() || !stCSRFCheck())
+  exit;
+
+stCommonAJAX("usrajax.php", "usrlogout.php");
+
+?>
\ No newline at end of file
--- a/usrajax.php	Tue Jan 24 16:44:30 2017 +0200
+++ b/usrajax.php	Tue Jan 24 17:25:48 2017 +0200
@@ -1,4 +1,4 @@
-<?
+<?php
 //
 // FAPWeb - Simple Web-based Demoparty Management System
 // User actions page AJAX backend module