User:DavidHOzAu/votescript

From wikishia
Votescript v1.0

Script for tabulating results of the approval voting process.

  • This script has a user interface where the title of the survey page can be entered.
  • After the survey page has been parsed, you will have to edit the headings scraped off the page to match the heading of the option people voted under.
  • The preview and wikitable code update automatically whenever the heading text area loses focus.

TODO

Any contributions to this script would be appreciated. Leave a note on my talk page if you have any ideas.

Usage

Add the following to your user javascript:

//--------------------------------------------------------------
document.write('<script type="text/javascript" src="' 
   + 'http://en.wikipedia.org/w/index.php?title='
   + 'User:DavidHOzAu/votescript/code.js' 
   + '&action=raw&ctype=text/javascript&dontcountme=s"></script>');
//--------------------------------------------------------------

/*

Code

 */
//--------------------------------------------------------------
// GLOBAL VARIABLES
//--------------------------------------------------------------
var vs_data = new Array(3);	// persistent data : { users, votes, headings }

//--------------------------------------------------------------
// voteProcessor - process an entire dom, extract users & votes
//--------------------------------------------------------------
function voteProcessor(dom) {
    var lists = dom.getElementsByTagName("ol");
    var votes = new Array();
    var users = new Array();
    for (i = 0; list = lists[i]; i++) {
        var ret = processList(list, users, votes, (lists.length - i) - 1);
        if (ret) { // checksafe
            users = ret[0];
            votes = ret[1];
        }
    }
    vs_data[0] = users;
    vs_data[1] = votes;
}

//--------------------------------------------------------------
// getAllHeadings - Detect all headings in the DOM
//--------------------------------------------------------------
function getAllHeadings(dom, headings) {
    for (var i = 0; n = dom.childNodes[i]; i++) {
         if (n.nodeName.length == 2
             && !isNaN(n.nodeName.substr(1,1))
             && n.nodeName.toLowerCase().substr(0,1) == "h") {
             headings[headings.length] = getHeading(n);
         }
         if (n.childNodes.length > 0) {
             headings = getAllHeadings(n, headings); // recurse
         }
    }
    return headings;
}

//--------------------------------------------------------------
// getHeading - Detect the heading text in a node
//--------------------------------------------------------------
function getHeading(n) {
    for (var i = 0; el = n.getElementsByTagName("span")[i]; i++) {
        if (el.className == "mw-headline") {
            return el.innerHTML;
        }
    }
    return "unknown";
}

//--------------------------------------------------------------
// processList - extract users and votes from an option's list
//--------------------------------------------------------------
function processList(list, users, votes, choice) {
    if (!list) { return; } // failsafe
    for (var i = 0; el = list.getElementsByTagName("li")[i]; i++) {
        var uname = getUserFromLiElement(el);
        if (uname == "") {
            uname = getUnknownUser(el);
            if (uname == "") {
                return;
            }
        }
        var uid = wasUserRead(uname, users);
        if (uid < 0) {
           uid=users.length;
           votes[uid] = 0;
        }
        users[uid] = uname;
        votes[uid] += Math.pow(2, choice); // binary store
    }
    ret = new Array(); // pass back by array
    ret[0] = users;
    ret[1] = votes;
    return ret;
}

//--------------------------------------------------------------
// getUserFromLiElement - auto-magically detect user name
//--------------------------------------------------------------
function getUserFromLiElement(lielem) {
    for (var i = 0; link = lielem.getElementsByTagName("a")[i]; i++) {
        if (link.title.substr(0, 4) == "User") {
            return link.title.substr(5, link.title.length);
        }
    }
    return "";
}

//--------------------------------------------------------------
// getUnknownUser - user intervention for weird entries
//--------------------------------------------------------------
function getUnknownUser(el) {
    var text = getInnerText(el);
    var msg = "Couldn't identify user.  Please extract the user's name from the following text.";
    var reply = prompt(msg, text);
    if (reply == null) { return ""; }
    return reply;
}

//--------------------------------------------------------------
// getInnerText - get innerHTML without the HTML tags
//--------------------------------------------------------------
function getInnerText(el) {
    var re, text = el.innerHTML;
    // remove XML tags
    do {
        a = text;
        text = text.replace(/<[^>\/]*\/>/gi, "");
    } while (a != text);
    // strip HTML tags
    do {
        a = text;
        text = text.replace(/<[^>]*>([^<]*)<\/[^>]*>/gi, "$1");
    } while (a != text);
    // remove leading and trailing spaces
    text = text.replace(/^\s/gi, "");
    text = text.replace(/\s$/gi, "");
    return text;
}

//--------------------------------------------------------------
// wasUserRead - O(n) search for user name presence
//--------------------------------------------------------------
function wasUserRead(username, users) {
    for (var i = 0; i < users.length; i++) {
       if (users[i] == username) { return i; }
    }
    return -1;
}

//--------------------------------------------------------------
// gen_table - make HTML table preview of results
//--------------------------------------------------------------
function gen_table() {
    var users = vs_data[0];
    var votes = vs_data[1];
    var headings = vs_data[2];

    var totals = new Array();
    var str = '<table class="wikitable"><tr><th>User</th>';
    var i, j, k;
    for (i = 0; i < headings.length; i++) {
        str += "<th>" + headings[i] + "</th>";
        totals[i] = 0;
    }
    str += "</tr>";
    for (i = 0; i < users.length; i++) {
        str += "<tr><td>" + users[i] + "</td>";
        k = votes[i];
        for (j = headings.length-1; j >= 0; j--) { // MSB to LSB
            if (k - Math.pow(2, j) >= 0) { // is bit set?
                k -= Math.pow(2, j);       // turn it off
                str += "<td>X</td>";
                totals[j]++;
            } else {
                str += "<td>-</td>";
            }
        }
        str += "</tr>";
    }
    str += "<tr><th>Total</th>";
        for (i = headings.length-1; i >= 0; i--) { // Reverse order
        str += "<th>" + totals[i] + "</th>";
    }

    str += "</table>";
    str += "<p><i>" + users.length + " editors voted in this survey.</i></p>"
    return str;
}

//--------------------------------------------------------------
// gen_wiki - generate results as wikicode
//--------------------------------------------------------------
function gen_wiki() {
    var users = vs_data[0];
    var votes = vs_data[1];
    var headings = vs_data[2];

    var totals = new Array();
    var str = '<' + 'pre style="border-style: none; padding: 0; margin: 0"' + '>{|class="wikitable"\n!Users';
    var i, j, k;
    for (i = 0; i < headings.length; i++) {
        str += "!!" + headings[i];
        totals[i] = 0;
    }
    for (i = 0; i < users.length; i++) {
        str += "\n|-\n|" + users[i];
        k = votes[i];
        for (j = headings.length-1; j >= 0; j--) { // MSB to LSB
            if (k - Math.pow(2, j) >= 0) { // is bit set?
                k -= Math.pow(2, j);       // turn it off
                str += "|| X ";
                totals[j]++;
            } else {
                 str += "|| - ";
            }
        }
    }
    str += "\n|-\n!Total";
        for (i = headings.length-1; i >= 0; i--) { // Reverse order
        str += "!!" + totals[i];
    }

    str += "\n|}";
    str += "\n\n''" + users.length + " editors voted in this survey.''<" + "/pre" + ">";
    return str;
}

//--------------------------------------------------------------
// Adapt code for MediaWiki use
//--------------------------------------------------------------
var votescript_url = "User:DavidHOzAu/votescript/ui";
var vs_path_len = document.location.pathname.length;
if (document.location.pathname.substring(vs_path_len - votescript_url.length, vs_path_len) == votescript_url) {
    hookDOMEvent(window, "load", uiSetup);
}

//--------------------------------------------------------------
// hookDOMEvent - useful for hooking events in any element
//--------------------------------------------------------------
function hookDOMEvent(dom, hookName, hookFunct) {
    if (dom.addEventListener)
	dom.addEventListener(hookName, hookFunct, false);
    else if (dom.attachEvent)
	dom.attachEvent("on" + hookName, hookFunct);
}

//--------------------------------------------------------------
// uiHandler - UI setup code
//--------------------------------------------------------------
function uiSetup() {
    // blank the inner contents of the page
    var bodyContent = document.getElementById("bodyContent");
    while (bodyContent.childNodes.length > 0) bodyContent.removeChild(bodyContent.lastChild);

    bodyContent.innerHTML = '<div id="vs-dialog" style="margin: 1em auto 0; padding: 1em; width: 40em; background-color: #eef; border: 1px solid #cbf; position: relative; font-size: medium; -moz-border-radius: 1em; ">'
    + uiLabel("Survey page") + uiWrap("2em", uiButton("fetch", "Fetch", "button") + uiInput("page", "Wikipedia:Village_pump_(proposals)/Sidebar_redesign/Final_draft_vote"))
    + uiLabel("Headers") + uiTextArea("headers", "")
    + uiLabel("Preview") + uiOutput("preview", "")
    + uiLabel("Wikitable") + uiOutput("wiki", "")
    + '</div>' + uiHiddenText("hidden");

    document.getElementById("vs-page").style.position = "absolute";
    document.getElementById("vs-page").style.left = "0em";
    document.getElementById("vs-page").style.width = "32em";
    document.getElementById("vs-fetch").style.position = "absolute";
    document.getElementById("vs-fetch").style.right = "0em";
    document.getElementById("vs-fetch").style.width = "5.5em";

    hookDOMEvent(document.getElementById("vs-fetch"), "click", fetchVotes);
    hookDOMEvent(document.getElementById("vs-headers"), "change", uiUpdater);
}

//--------------------------------------------------------------
// UI Helper functions
//--------------------------------------------------------------
function uiLabel(label) {
    return('<div id="vs-label" style="height: 2em; background-color: #eef; cursor: default; position: relative">'
    + '<div id="vs-label-text" style="position: absolute; bottom: 0; left: 0; background-color: #eef; z-index: 3">' + label
    + ' </div><div id="vs-label-rule" style="position: absolute; bottom: 0.7em; right: 0; width: 100%; z-index: 2; height: 2px; border-bottom: 2px groove white"></div></div>');
}

function uiWrap(h, text) {
    return('<div id="vs-wrap" style="margin: 0.5em 1em; height: ' + h + '; position: relative">' + text + '</div>');
}

function uiInput(id, text) {
    return('<input name="' + id + '" value="' + text + '" id="vs-' + id + '" title="' + id + '" style="font-size: 100%"/>');
}

function uiButton(id, text, type) {
    return('<input type="' + type + '" value="' + text + '" id="vs-' + id + '" title="' + id + '" style="font-size: 100%"/>');
}

function uiTextArea(id, text) {
    return uiWrap("auto", '<textarea name="' + id + '" id="vs-' + id + '" style="cursor: text; width: 100%; height: 10em">' + text + '</textarea>');
}

function uiOutput(id, text) {
    return uiWrap("auto", '<div id="vs-' + id + '" style="overflow: auto; cursor: text; background-color: #f8f8ff; border: 1px solid #b8b8b8; height: 10em">' + text + '</div>');
}

function uiHiddenText(id) {
    return('<div id="vs-' + id + '" style="display: none; speak: none"></div>');
}

//--------------------------------------------------------------
// uiUpdater - update contents when heading text changes
//--------------------------------------------------------------
function uiUpdater() {
    vs_data[2] = document.getElementById("vs-headers").value.split(/[\n\r]+/);
    document.getElementById("vs-preview").innerHTML = gen_table();
    document.getElementById("vs-wiki").innerHTML = gen_wiki();
}

//--------------------------------------------------------------
// fetchVotes - fetch DOM of requested page
//--------------------------------------------------------------
function fetchVotes(event) {
    document.getElementById("vs-preview").innerHTML = "<i>Loading...</i>";
    x = sajax_init_object();
    if (!x) {
	alert("AJAX not supported");
	return false;
    }
    var url = document.getElementById("vs-page").value;
    x.onreadystatechange = function () { voteFetcher(x); };
    url = wgScriptPath + "/index.php?title=" + escape(url) + "&action=render";
    x.open("GET", url, true); 
    x.send(null);
}

//--------------------------------------------------------------
// voteFetcher - handle XMLHttpRequest messages
//--------------------------------------------------------------
function voteFetcher(x) {
    if (x.readyState == 3) { /* document.write(x.getAllResponseHeaders()); */ }
    if (x.readyState != 4) { return; }
    if (x.status != 200) {
        document.getElementById("vs-preview").innerHTML = "<i>Page not found or network error</i>";
        return;
    }
    if (x.responseText) {
        var dom = document.createElement("div"); // placeholder
        dom.innerHTML = x.responseText; // hack
        vs_data[2] = getAllHeadings(dom, new Array());	// get _ALL_ headings
        voteProcessor(dom);		// process dom for votes
        for (head in vs_data[2]) {
           document.getElementById("vs-headers").value += vs_data[2][head] + '\r\n';
        }
        uiUpdater();
    } else {
        document.getElementById("vs-preview").innerHTML = "<i>Response empty</i>";
    }
}
/* 

*/