// script.js -- HotCRP JavaScript library
// HotCRP is Copyright (c) 2006-2013 Eddie Kohler and Regents of the UC
// Distributed under an MIT-like license; see LICENSE
function $$(id) {
return document.getElementById(id);
}
isArray = (function (toString) {
return function (x) {
return toString.call(x) === "[object Array]";
};
})(Object.prototype.toString);
function e_value(id, value) {
var elt = $$(id);
if (value == null)
return elt ? elt.value : undefined;
else if (elt)
elt.value = value;
}
function eltPos(e) {
if (typeof e == "string")
e = $$(e);
var pos = {
top: 0, left: 0, width: e.offsetWidth, height: e.offsetHeight,
right: e.offsetWidth, bottom: e.offsetHeight
};
while (e) {
pos.left += e.offsetLeft;
pos.top += e.offsetTop;
pos.right += e.offsetLeft;
pos.bottom += e.offsetTop;
e = e.offsetParent;
}
return pos;
}
function make_e_class(tag, className) {
var x = document.createElement(tag);
x.className = className;
return x;
}
function get_body() {
return document.body || document.getElementsByTagName("body")[0];
}
function event_stop(evt) {
if (evt.stopPropagation)
evt.stopPropagation();
else
evt.cancelBubble = true;
}
function event_prevent(evt) {
if (evt.preventDefault)
evt.preventDefault();
else
evt.returnValue = false;
}
setLocalTime = (function () {
var servhr24, showdifference = false;
function setLocalTime(elt, servtime) {
var d, s, hr, min, sec;
if (elt && typeof elt == "string")
elt = $$(elt);
if (elt && showdifference) {
d = new Date(servtime * 1000);
s = ["Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur"][d.getDay()];
s += "day " + d.getDate() + " ";
s += ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][d.getMonth()];
s += " " + d.getFullYear();
hr = d.getHours();
s += " " + (servhr24 ? hr : ((hr + 11) % 12) + 1);
if (servhr24 || d.getMinutes() || d.getSeconds())
s += ":" + (d.getMinutes() < 10 ? "0" : "") + d.getMinutes();
if (d.getSeconds())
s += ":" + (d.getSeconds() < 10 ? "0" : "") + d.getSeconds();
if (!servhr24)
s += (hr < 12 ? "am" : "pm");
s += " your time";
if (elt.tagName.toUpperCase() == "SPAN") {
elt.innerHTML = " (" + s + ")";
elt.style.display = "inline";
} else {
elt.innerHTML = s;
elt.style.display = "block";
}
}
}
setLocalTime.initialize = function (servtime, servzone, hr24) {
var now = new Date(), x;
if (Math.abs(now.getTime() - servtime * 1000) >= 300000
&& (x = $$("clock_drift_container")))
x.innerHTML = "
The HotCRP server’s clock is more than 5 minutes off from your computer’s clock. If your computer’s clock is correct, you should update the server’s clock.
";
servhr24 = hr24;
// print local time if server time is in a different time zone
showdifference = Math.abs(now.getTimezoneOffset() - servzone) >= 60;
};
return setLocalTime;
})();
loadDeadlines = (function () {
var dl, dlname, dltime, dlurl, redisplay_timeout, reload_timeout;
function redisplayDeadlines() {
redisplay_timeout = null;
displayDeadlines();
}
// this logic is repeated in the back end
function displayDeadlines() {
var s = "", amt, what = null, x, subtype,
time_since_load = new Date().getTime() / 1000 - +dl.load,
now = +dl.now + time_since_load,
elt = $$("maindeadline");
if (!elt)
return;
dlname = "";
dltime = 0;
if (dl.sub_open) {
x = {"sub_reg": "registration", "sub_update": "update",
"sub_sub": "submission"};
for (subtype in x)
if (+dl.now <= +dl[subtype] ? now - 120 <= +dl[subtype]
: dl[subtype + "_ingrace"]) {
dlname = "Paper " + x[subtype] + " deadline";
dltime = +dl[subtype];
break;
}
}
if (dlname) {
if (dlurl)
s = "" + dlname + " ";
else
s = dlname + " ";
amt = dltime - now;
if (!dltime || amt <= 0)
s += "is NOW";
else {
s += "in ";
if (amt > 259200 /* 3 days */) {
amt = Math.ceil(amt / 86400);
what = "day";
} else if (amt > 28800 /* 8 hours */) {
amt = Math.ceil(amt / 3600);
what = "hour";
} else if (amt > 3600 /* 1 hour */) {
amt = Math.ceil(amt / 1800) / 2;
what = "hour";
} else if (amt > 180) {
amt = Math.ceil(amt / 60);
what = "minute";
} else {
amt = Math.ceil(amt);
what = "second";
}
s += amt + " " + what + (amt == 1 ? "" : "s");
}
if (!dltime || dltime - now <= 180)
s = "" + s + "";
}
elt.innerHTML = s;
elt.style.display = s ? (elt.tagName.toUpperCase() == "SPAN" ? "inline" : "block") : "none";
if (!redisplay_timeout) {
if (what == "second")
redisplay_timeout = setTimeout(displayDeadlines, 250);
else if (what == "minute")
redisplay_timeout = setTimeout(displayDeadlines, 15000);
}
}
function reloadDeadlines() {
reload_timeout = null;
Miniajax.get(dlurl + "?ajax=1", loadDeadlines, 10000);
}
function loadDeadlines(dlx) {
var t;
if (dlx) {
dl = dlx;
dl.load = new Date().getTime() / 1000;
}
displayDeadlines();
if (dlurl && !reload_timeout) {
t = (dlname && (!dltime || dltime - dl.load <= 120) ? 45000 : 300000);
reload_timeout = setTimeout(reloadDeadlines, t);
}
}
loadDeadlines.init = function (dlx, dlurlx) {
dlurl = dlurlx;
loadDeadlines(dlx);
};
return loadDeadlines;
})();
var hotcrp_onload = [];
function hotcrp_load(arg) {
if (!arg)
for (x = 0; x < hotcrp_onload.length; ++x)
hotcrp_onload[x]();
else if (typeof arg === "string")
hotcrp_onload.push(hotcrp_load[arg]);
else
hotcrp_onload.push(arg);
}
hotcrp_load.time = function (servtime, servzone, hr24) {
setLocalTime.initialize(servtime, servzone, hr24);
};
hotcrp_load.opencomment = function () {
if (location.hash.match(/^\#?commentnew$/))
open_new_comment();
};
function highlightUpdate(which, off) {
var ins, i, result;
if (typeof which == "string") {
result = $$(which + "result");
if (result && !off)
result.innerHTML = "";
which = $$(which);
}
if (!which)
which = document;
i = which.tagName ? which.tagName.toUpperCase() : "";
if (i != "INPUT" && i != "BUTTON") {
ins = which.getElementsByTagName("input");
for (i = 0; i < ins.length; i++)
if (ins[i].className.substr(0, 2) == "hb")
highlightUpdate(ins[i], off);
}
if (which.className) {
result = which.className.replace(" alert", "");
which.className = (off ? result : result + " alert");
}
}
function hiliter(which, off) {
var elt = which;
while (elt && elt.tagName && (elt.tagName.toUpperCase() != "DIV"
|| elt.className.substr(0, 4) != "aahc"))
elt = elt.parentNode;
if (!elt || !elt.tagName)
highlightUpdate(null, off);
else if (off && elt.className)
elt.className = elt.className.replace(" alert", "");
else if (elt.className)
elt.className = elt.className + " alert";
}
var foldmap = {}, foldsession_unique = 1;
function fold(which, dofold, foldtype) {
var i, elt, selt, opentxt, closetxt, foldnum, foldnumid;
if (which instanceof Array) {
for (i = 0; i < which.length; i++)
fold(which[i], dofold, foldtype);
} else if (typeof which == "string") {
foldnum = foldtype;
if (foldmap[which] != null && foldmap[which][foldtype] != null)
foldnum = foldmap[which][foldtype];
foldnumid = foldnum ? foldnum : "";
elt = $$("fold" + which) || $$(which);
fold(elt, dofold, foldnum);
// check for session
if ((selt = $$('foldsession.' + which + foldnumid)))
selt.src = selt.src.replace(/val=.*/, 'val=' + (dofold ? 1 : 0) + '&u=' + foldsession_unique++);
else if ((selt = $$('foldsession.' + which)))
selt.src = selt.src.replace(/val=.*/, 'val=' + (dofold ? 1 : 0) + '&sub=' + (foldtype || foldnumid) + '&u=' + foldsession_unique++);
// check for focus
if (!dofold && (selt = $$("fold" + which + foldnumid + "_d"))) {
if (selt.setSelectionRange && selt.hotcrp_ever_focused == null) {
selt.setSelectionRange(selt.value.length, selt.value.length);
selt.hotcrp_ever_focused = true;
}
selt.focus();
}
} else if (which) {
foldnumid = foldtype ? foldtype : "";
opentxt = "fold" + foldnumid + "o";
closetxt = "fold" + foldnumid + "c";
if (dofold == null && which.className.indexOf(opentxt) >= 0)
dofold = true;
if (dofold)
which.className = which.className.replace(opentxt, closetxt);
else
which.className = which.className.replace(closetxt, opentxt);
// IE won't actually do the fold unless we yell at it
if (document.recalc)
try {
which.innerHTML = which.innerHTML + "";
} catch (err) {
}
}
return false;
}
function foldup(e, event, foldnum) {
var dofold = false, attr;
while (e && e.id.substr(0, 4) != "fold")
e = e.parentNode;
if (!e)
return true;
foldnum = foldnum || 0;
if (!foldnum && (m = e.className.match(/\bfold(\d*)[oc]\b/)))
foldnum = m[1];
dofold = !(new RegExp("\\bfold" + (foldnum ? foldnum : "") + "c\\b")).test(e.className);
if ((attr = e.getAttribute(dofold ? "onfold" : "onunfold"))) {
attr = new Function(attr);
attr.call(e);
}
if (event)
event_stop(event);
return fold(e, dofold, foldnum);
}
function crpfocus(id, subfocus, seltype) {
var selt = $$(id);
if (selt && subfocus)
selt.className = selt.className.replace(/links[0-9]*/, 'links' + subfocus);
var felt = $$(id + (subfocus ? subfocus : "") + "_d");
if (felt && !(felt.type == "text" && felt.value && seltype == 1))
felt.focus();
if (felt && felt.type == "text" && seltype == 3 && felt.select)
felt.select();
if ((selt || felt) && window.event)
window.event.returnValue = false;
if (seltype && seltype >= 1)
window.scrollTo(0, 0);
return !(selt || felt);
}
function crpSubmitKeyFilter(elt, event) {
var e = event || window.event;
var code = e.charCode || e.keyCode;
var form;
if (e.ctrlKey || e.altKey || e.shiftKey || code != 13)
return true;
form = elt;
while (form && form.tagName && form.tagName.toUpperCase() != "FORM")
form = form.parentNode;
if (form && form.tagName) {
elt.blur();
form.submit();
return false;
} else
return true;
}
function make_link_callback(elt) {
return function () {
window.location = elt.href;
};
}
// accounts
function contactPulldown(which) {
var pulldown = $$(which + "_pulldown");
if (pulldown.value != "") {
var name = $$(which + "_name");
var email = $$(which + "_email");
var parse = pulldown.value.split("`````");
email.value = parse[0];
name.value = (parse.length > 1 ? parse[1] : "");
}
var folder = $$('fold' + which);
folder.className = folder.className.replace("foldo", "foldc");
}
function shiftPassword(direction) {
var form = $$("accountform");
fold("account", direction);
if (form && form.whichpassword)
form.whichpassword.value = direction ? "" : "t";
}
// paper selection
function papersel(value, name) {
var ins = document.getElementsByTagName("input"),
xvalue = value, value_hash = true, i;
name = name || "pap[]";
if (isArray(value)) {
xvalue = {};
for (i = value.length; i >= 0; --i)
xvalue[value[i]] = 1;
} else if (value === null || typeof value !== "object")
value_hash = false;
for (var i = 0; i < ins.length; i++)
if (ins[i].name == name)
ins[i].checked = !!(value_hash ? xvalue[ins[i].value] : xvalue);
return false;
}
var papersel_check_safe = false;
function paperselCheck() {
var ins, i, e, values, check_safe = papersel_check_safe;
papersel_check_safe = false;
if ((e = $$("sel_papstandin")))
e.parentNode.removeChild(e);
ins = document.getElementsByTagName("input");
for (i = 0, values = []; i < ins.length; i++)
if ((e = ins[i]).name == "pap[]") {
if (e.checked)
return true;
else
values.push(e.value);
}
if (check_safe) {
e = document.createElement("div");
e.id = "sel_papstandin";
e.innerHTML = "";
$$("sel").appendChild(e);
return true;
}
alert("Select one or more papers first.");
return false;
}
var pselclick_last = {};
function pselClick(evt, elt) {
var i, j, sel, name, thisnum;
if (!(i = elt.id.match(/^(.*?)(\d+)$/)))
return;
name = i[1];
thisnum = +i[2];
if (evt.shiftKey && pselclick_last[name]) {
if (pselclick_last[name] <= thisnum) {
i = pselclick_last[name];
j = thisnum - 1;
} else {
i = thisnum + 1;
j = pselclick_last[name];
}
for (; i <= j; i++) {
if ((sel = $$(name + i)))
sel.checked = elt.checked;
}
}
pselclick_last[name] = thisnum;
return true;
}
function pc_tags_members(tag) {
var pc_tags = pc_tags_json, answer = [], pc, tags;
tag = " " + tag + " ";
for (pc in pc_tags)
if (pc_tags[pc].indexOf(tag) >= 0)
answer.push(pc);
return answer;
}
autosub = (function () {
var current;
function autosub_kp(event) {
var code, form, inputs, i;
event = event || window.event;
code = event.charCode || event.keyCode;
if (code != 13 || event.ctrlKey || event.altKey || event.shiftKey)
return true;
else if (current === false)
return false;
form = this;
while (form && form.tagName && form.tagName.toUpperCase() != "FORM")
form = form.parentNode;
if (form && form.tagName) {
inputs = form.getElementsByTagName("input");
for (i = 0; i < inputs.length; ++i)
if (inputs[i].name == "default") {
this.blur();
inputs[i].click();
return false;
}
}
return true;
}
return function (name, elt) {
var da = $$("defaultact");
if (da && typeof name === "string")
da.value = name;
current = name;
if (elt && !elt.onkeypress && elt.tagName.toUpperCase() == "INPUT")
elt.onkeypress = autosub_kp;
};
})();
function plactions_dofold() {
var elt = $$("placttagtype"), folded, x, i;
if (elt) {
folded = elt.selectedIndex < 0 || elt.options[elt.selectedIndex].value != "cr";
fold("placttags", folded, 99);
if (folded)
fold("placttags", true);
else if ((elt = $$("sel"))) {
if ((elt.tagcr_source && elt.tagcr_source.value != "")
|| (elt.tagcr_method && elt.tagcr_method.selectedIndex >= 0
&& elt.tagcr_method.options[elt.tagcr_method.selectedIndex].value != "schulze")
|| (elt.tagcr_gapless && elt.tagcr_gapless.checked))
fold("placttags", false);
}
}
if ((elt = $$("foldass"))) {
x = elt.getElementsByTagName("select");
for (i = 0; i < x.length; ++i)
if (x[i].name == "marktype") {
folded = x[i].selectedIndex < 0 || x[i].options[x[i].selectedIndex].value.charAt(0) == "x";
fold("ass", folded);
}
}
}
// assignment selection
var selassign_blur = 0;
function foldassign(which) {
var folder = $$("foldass" + which);
if (folder.className.indexOf("foldo") < 0 && selassign_blur != which) {
fold("ass" + which, false);
$$("pcs" + which).focus();
}
selassign_blur = 0;
return false;
}
function selassign(elt, which) {
if (elt) {
$$("ass" + which).className = "pctbname" + elt.value + " pctbl";
var i = $$("assimg" + which);
i.className = "ass" + elt.value;
hiliter(elt);
}
var folder = $$("folderass" + which);
if (folder && elt !== 0)
folder.focus();
setTimeout("fold(\"ass" + which + "\", true)", 50);
if (elt === 0) {
selassign_blur = which;
setTimeout("selassign_blur = 0;", 300);
}
}
// author entry
var numauthorfold = [];
function authorfold(prefix, relative, n) {
var elt;
if (relative > 0)
n += numauthorfold[prefix];
if (n <= 1)
n = 1;
for (var i = 1; i <= n; i++)
if ((elt = $$(prefix + i)) && elt.className == "aueditc")
elt.className = "auedito";
else if (!elt)
n = i - 1;
for (var i = n + 1; i <= 50; i++)
if ((elt = $$(prefix + i)) && elt.className == "auedito")
elt.className = "aueditc";
else if (!elt)
break;
// set number displayed
if (relative >= 0) {
e_value(prefix + "count", n);
numauthorfold[prefix] = n;
}
// IE won't actually do the fold unless we yell at it
elt = $$(prefix + "table");
if (document.recalc && elt)
try {
elt.innerHTML = elt.innerHTML + "";
} catch (err) {
}
return false;
}
function staged_foreach(a, f, backwards) {
var i = (backwards ? a.length - 1 : 0);
var step = (backwards ? -1 : 1);
var stagef = function () {
var x;
for (x = 0; i >= 0 && i < a.length && x < 100; i += step, ++x)
f(a[i]);
if (i < a.length)
setTimeout(stagef, 0);
};
stagef();
}
// temporary text
mktemptext = (function () {
function setclass(e, on) {
e.className = e.className.replace(on ? /\btemptextoff\b/ : /\btemptext\b/,
on ? "temptext" : "temptextoff");
}
function blank() {
}
return function (e, text) {
if (typeof e === "string")
e = $$(e);
var onfocus = e.onfocus || blank, onblur = e.onblur || blank;
e.onfocus = function (evt) {
if (this.value == text) {
this.value = "";
setclass(this, false);
}
onfocus.call(this, evt);
};
e.onblur = function (evt) {
if (this.value == "" || this.value == text) {
this.value = text;
setclass(this, true);
}
onblur.call(this, evt);
};
setclass(e, e.value == text);
};
})();
// check marks for ajax saves
function make_ajaxcheck_swinger(elt) {
return function () {
var h = elt.hotcrp_ajaxcheck;
var now = (new Date).getTime(), delta = now - h.start, opacity = 0;
if (delta < 2000)
opacity = 0.5;
else if (delta <= 7000)
opacity = 0.5 * Math.cos((delta - 2000) / 5000 * Math.PI);
if (opacity <= 0.03) {
elt.style.outline = h.old_outline;
clearInterval(h.interval);
h.interval = null;
} else
elt.style.outline = "4px solid rgba(0, 200, 0, " + opacity + ")";
};
}
function setajaxcheck(elt, rv) {
if (typeof elt == "string")
elt = $$(elt);
if (elt) {
var h = elt.hotcrp_ajaxcheck;
if (!h)
h = elt.hotcrp_ajaxcheck = {old_outline: elt.style.outline};
if (h.interval) {
clearInterval(h.interval);
h.interval = null;
}
var s;
if (rv.ok)
s = "Saved";
else if (rv.error)
s = rv.error.replace(/<\/?.*?>/g, "").replace(/\(Override conflict\)\s*/g, "").replace(/\s+$/, "");
else
s = "Error";
elt.setAttribute("title", s);
if (rv.ok) {
h.start = (new Date).getTime();
h.interval = setInterval(make_ajaxcheck_swinger(elt), 13);
} else
elt.style.outline = "5px solid red";
}
}
// open new comment
function open_new_comment(sethash) {
var x;
fold("addcomment", 0);
x = $$("foldaddcomment");
x = x ? x.getElementsByTagName("textarea") : null;
if (x && x.length)
setTimeout(function () { x[0].focus(); }, 0);
if (sethash)
location.hash = "#commentnew";
return false;
}
function cancel_comment() {
var x = $$("foldaddcomment");
x = x ? x.getElementsByTagName("textarea") : null;
if (x && x.length)
x[0].blur();
fold("addcomment", 1);
}
// quicklink shortcuts
function quicklink_shortcut(evt, code) {
// find the quicklink, reject if not found
var a = $$("quicklink_" + (code == 106 ? "prev" : "next")), f;
if (a && a.focus) {
// focus (for visual feedback), call callback
a.focus();
f = make_link_callback(a);
if (!Miniajax.isoutstanding("revprefform", f))
f();
return true;
} else
return false;
}
function comment_shortcut() {
if ($$("foldaddcomment")) {
open_new_comment();
return true;
} else
return false;
}
function shortcut(top_elt) {
var self, keys = {};
function keypress(evt) {
var code, a, f, target, x, i, j;
// IE compatibility
evt = evt || window.event;
code = evt.charCode || evt.keyCode;
target = evt.target || evt.srcElement;
// reject modified keys, interesting targets
if (code == 0 || evt.altKey || evt.ctrlKey || evt.metaKey
|| (target && target.tagName && target != top_elt
&& (x = target.tagName.toUpperCase())
&& (x == "TEXTAREA"
|| x == "SELECT"
|| (x == "INPUT"
&& (target.type == "file" || target.type == "password"
|| target.type == "text")))))
return true;
// reject if any forms have outstanding data
x = document.getElementsByTagName("form");
for (i = 0; i < x.length; ++i)
for (j = 0; j < x[i].childNodes.length; ++j) {
a = x[i].childNodes[j];
if (a.nodeType == 1 && a.tagName.toUpperCase() == "DIV"
&& a.className.match(/\baahc\b.*\balert\b/))
return true;
}
// call function
if (!keys[code] || !keys[code](evt, code))
return true;
// done
if (evt.preventDefault)
evt.preventDefault();
else
evt.returnValue = false;
return false;
}
function add(code, f) {
if (code != null)
keys[code] = f;
else {
add(106, quicklink_shortcut);
add(107, quicklink_shortcut);
if (top_elt == document)
add(99, comment_shortcut);
}
return self;
}
self = {add: add};
if (!top_elt)
top_elt = document;
else if (typeof top_elt === "string")
top_elt = $$(top_elt);
if (top_elt && !top_elt.hotcrp_shortcut) {
if (top_elt.addEventListener)
top_elt.addEventListener("keypress", keypress, false);
else
top_elt.onkeypress = keypress;
top_elt.hotcrp_shortcut = self;
}
return self;
}
// callback combination
function add_callback(cb1, cb2) {
if (cb1 && cb2)
return function () {
cb1.apply(this, arguments);
cb2.apply(this, arguments);
};
else
return cb1 || cb2;
}
// tags
var alltags = (function () {
var a = [], status = 0, cb = null;
function tagsorter(a, b) {
var al = a.toLowerCase(), bl = b.toLowerCase();
if (al < bl)
return -1;
else if (bl < al)
return 1;
else if (a == b)
return 0;
else if (a < b)
return -1;
else
return 1;
}
function getcb(v) {
if (v && v.tags) {
a = v.tags;
a.sort(tagsorter);
}
status = 2;
cb && cb(a);
}
return function (callback) {
if (!status && alltags.url) {
status = 1;
Miniajax.get(alltags.url, getcb);
}
if (status == 1)
cb = add_callback(cb, callback);
return a;
};
})();
function taghelp_tset(elt) {
var m = elt.value.substring(0, elt.selectionStart).match(/.*?([^#\s]*)(?:#\d*)?$/),
n = elt.value.substring(elt.selectionStart).match(/^([^#\s]*)/);
return (m && m[1] + n[1]) || "";
}
function taghelp_q(elt) {
var m = elt.value.substring(0, elt.selectionStart).match(/.*?(tag:\s*|r?order:\s*|#)([^#\s]*)$/),
n = elt.value.substring(elt.selectionStart).match(/^([^#\s]*)/);
return m ? [m[2] + n[1], m[1]] : null;
}
function taghelp(elt, report_elt, cleanf) {
var hiding = false;
function display() {
var tags, s, ls, a, i, t, cols, colheight, n, pfx = "";
elt.hotcrp_tagpress = true;
tags = alltags(display);
if (!tags.length || (elt.selectionEnd != elt.selectionStart))
return;
if ((s = cleanf(elt)) === null) {
report_elt.style.display = "none";
return;
}
if (typeof s !== "string") {
pfx = s[1];
s = s[0];
}
ls = s.toLowerCase();
for (i = 0, a = []; i < tags.length; ++i)
if (s.length == 0)
a.push(pfx + tags[i]);
else if (tags[i].substring(0, s.length).toLowerCase() == ls)
a.push(pfx + "" + tags[i].substring(0, s.length) + "" + tags[i].substring(s.length));
if (a.length == 0) {
report_elt.style.display = "none";
return;
}
t = "
";
cols = (a.length < 6 ? 1 : 2);
colheight = Math.floor((a.length + cols - 1) / cols);
for (i = n = 0; i < cols; ++i, n += colheight)
t += "