User:DavidHOzAu/votescript
- 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.
Closed bugs |
---|
Bug 0: Wikipedia integration doesn't seem to work
|
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
This work is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version. This work is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. See version 2 and version 3 of the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
*/ //-------------------------------------------------------------- // 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>"; } } /*
*/