// -*- mode:js2; tab-width:2; indent-tabs-mode:nil;  -*-
// Javascript for MFPL Internet Rights Workshop (depends on jquery being already loaded)

// Author: Daniel Kahn Gillmor
// Date: 2008-10-07 14:40:20-0400


var ir = {
  /* functions for dealing with an update of the list of all rights: */
  dealwithlistupdate: function(data) {
    ir.alllangs = data['langs'];
    ir.you = data['you']; /* store info about the current user */
    ir.maxrights = Number(data['maxrights']);
    ir.rightcount = Number(data['rightcount']);
    ir.state.value = data['state'];

    if (ir.state.value == 'pre') {
      if (data.rooms)
        ir.state.update(data.rooms);
    } else if (ir.state.value == 'post') {
      if (!ir.isboard) {
        ir.isboard = true;
        ir.clearlist();
        ir.ridfocus = undefined;
        ir.allrights = {};
        ir.newright.reset();
      }
    } else {
      ir.state.hide();
    }

    if (data['rights']) {
      var maxedout = (ir.rightcount >= ir.maxrights);

      // if we're maxed out on rights, and the new right form is showing, discard it.
      if (maxedout &&
          (undefined === ir.ridfocus))
        ir.newright.reset();

      if (undefined != ir.you) {
        /* if ridfocus is set, we should display the right edit form too. */

        if (ir.isboard || ir.newright.rightform || maxedout) {
          if (ir.newright.button)
            ir.newright.button.style.display = 'none';
        } else {
          if (ir.newright.button === undefined) {

            // need to call ir.newright.createform in a wrapped function (instead of
            // leaving it as a raw target) to ensure that no errant
            // arguments get passed during click().

            ir.newright.button = ir.createbutton(ir.t('Add right'), function() { ir.newright.createform(); });
            if (ir.modebuttonlocation)
              ir.modebuttonlocation.appendChild(ir.newright.button);
          }
          ir.newright.button.style.display = 'inline';
        }
      }

      /* only bother updating the page if this data is aligned with what
       we currently think it should be: */

      if (ir.ridfocus === data.ridfocus) {
        var rights =  data['rights'];
        for (var rightname in rights) {
          /*	alert(rightname + ': '); */
          ir.dealwithright(rightname, rights[rightname]);
        }
      }
    }
    ir.pending.complete();
  },

  listupdate: function() {
    var data = {};
    if (ir.ridfocus)
      data['rid'] = ir.ridfocus;

    ir.pending.display();
    $.getJSON('/json', data, ir.dealwithlistupdate);
  },

  clearlist: function() {
    while (ir.rightlist.firstChild)
      ir.rightlist.removeChild(ir.rightlist.firstChild);
  },

  scrollboard: function() {
    /* don't bother scrolling if everything fits on the current screen. */
     if (ir.rightlist.clientHeight < window.innerHeight) {
       ir.boardscrolloffset = 0;
       ir.rightlist.style.top = '0px';
       return;
    }

    ir.boardscrolloffset--;

    ir.rightlist.style.top = ir.boardscrolloffset + 'px';

    var topkid = ir.rightlist.firstChild;
    if (topkid) {
      if (topkid.clientHeight < -1*ir.boardscrolloffset) {
        ir.boardscrolloffset += topkid.clientHeight;
        ir.rightlist.appendChild(topkid);
        ir.rightlist.style.top = ir.boardscrolloffset + 'px';
      }
    }
  },

  /* building an object to display a right on the board (and we might
   * be able to reuse this for the scribe view also): */
  createnewright: function() {
    var template = document.createElement('tbody');
    var tr = template.appendChild(document.createElement('tr'));
    var label = tr.appendChild(document.createElement('th'));

    label.rowSpan = Math.ceil(ir.countobj(ir.alllangs) / ir.columncount) + 1;

    template.label = label.appendChild(document.createElement('div'));
    template.label.appendChild(document.createTextNode(''));
    template.label.className = 'title';

    var cellsthisrow = 0;
    for (var langix in ir.alllangs) {
      var l = ir.alllangs[langix];
      template[l] = tr.appendChild(document.createElement('td')).appendChild(document.createElement('div'));
      template[l].lang = l;
      template[l].appendChild(document.createTextNode(''));
      cellsthisrow++;
      if (cellsthisrow >= ir.columncount) {
        /* the row is full; start a new one */
        tr = template.appendChild(document.createElement('tr'));
        cellsthisrow = 0;
      }
    }
    if (cellsthisrow > 0 ) { /* the last row has at least one item in
                              * it, so polish it off */
      tr = template.appendChild(document.createElement('tr'));
    }
    template.status = tr.appendChild(document.createElement('td'));
    template.status.colSpan = ir.columncount;
    template.status.className = 'end';
    template.posted = template.status.appendChild(document.createElement('div'));
    template.posted.appendChild(document.createTextNode(''));
    template.posted.className = 'posted';
    template.endorsers = template.status.appendChild(document.createElement('div'));
    template.endorsers.appendChild(document.createTextNode(''));
    template.endorsers.className = 'endorsers';

    return template;
  },

  creatependingimg: function() {
    var ret = document.createElement('img');
    ret.src = 'images/indicator.white.gif';
    return ret;
  },
  createbutton: function(text, click) {
    var ret = document.createElement('button');
    ret.readycontent = ret.appendChild(document.createTextNode(text));
    ret.pendingcontent = ir.creatependingimg();
    ret.onclick = click;
    return ret;
  },

  /* transform the given right into an edit box: */
  geteditfields: function(right) {
    for (var langix in ir.alllangs) {
      var l = ir.alllangs[langix];
      var data = right[l].firstChild.nodeValue;
      while (right[l].firstChild)
        right[l].removeChild(right[l].firstChild);
      right[l].editfield = document.createElement('textarea');
      right[l].editfield.className = 'righttext';
      right[l].editfield.name = 'right_title_' + l;
      right[l].editfield.id = 'right_title_' + l;
      right[l].editfield.value = data;

//       var wrapper = document.createElement('fieldset');
//       wrapper.appendChild(document.createElement('legend')).appendChild(document.createTextNode(langix));
//      wrapper.appendChild(right[l].editfield);
      right[l].appendChild(right[l].editfield);
      right[l].className = 'content';
      // and make sure we're showing the translations:
      right[l].translate.style.display = 'block';
    }
    right.posted.style.display = 'none';
    right.endorsers.style.display = 'none';
    return right;
  },

  newright: {
    createform: function(baseon) {
      if (undefined === ir.rightlist)
        return alert('could not find a place to put the new edit box.');

      var submitlabel = 'Add right';
      if (undefined === baseon)
        baseon = ir.newrighttbody();
      else
        submitlabel = 'Submit';
      /* hide endorsement button and modify/view history buttons */
      baseon.endorsebutton.style.display = 'none';
      baseon.editlink.style.display = 'none';

      var r = ir.geteditfields(baseon);
      if (ir.rightlist.rightform && ir.rightlist.rightform.parentNode)
        ir.newright.rightform.parentNode.replaceChild(r, ir.rightlist.rightform);
      else
        ir.rightlist.insertBefore(r, ir.rightlist.firstChild);
      if (ir.newright.button)
        ir.newright.button.style.display = 'none';

      r.status.appendChild(ir.createbutton(ir.t(submitlabel), ir.newright.click));
      r.status.appendChild(ir.createbutton(ir.t('Cancel'), ir.newright.reset));

      if (undefined === ir.newright.returnlink)
        ir.newright.returnlink = ir.createbutton(ir.t('Return to list of rights'), ir.newright.reset);

      if (ir.modebuttonlocation)
        ir.modebuttonlocation.appendChild(ir.newright.returnlink);

      ir.translationacceptsdisplay(baseon, 'inline');

      ir.newright.rightform = r;
      return r;
    },
    click: function() {
      this.setAttribute('disabled', 'disabled');
      this.appendChild(this.pendingcontent);
      var data = {};
      for (var langix in ir.alllangs) {
        var l = ir.alllangs[langix];
        var x = 'right_title_' + l;
        data[x] = document.getElementById(x).value;
      }
      if (ir.ridfocus) {
        data['rid'] = ir.ridfocus;
        data['latest_seen_rvid'] = ir.maxkey(ir.allrights);
      }

      $.ajax({
               type: 'POST',
               url: 'right',
               data: data,
               target: this,
               success: ir.newright.receive,
               error: ir.newright.error
             }
            );

    },
    receive: function(data, textStatus) {
      ir.newright.reset();
      ir.listupdate();
    },
    error: function(xhr, textStatus, errorThrown) {
      alert('Error submitting new right: (' + xhr.status +
            ' ' + xhr.statusText + ')\n' + xhr.responseText);
      ir.newright.reset();
    },
    reset: function() {
      if (ir.newright.rightform) {
        if (ir.newright.rightform.parentNode)
          ir.newright.rightform.parentNode.removeChild(ir.newright.rightform);
        ir.newright.rightform = undefined;
      }
      if (ir.newright.returnlink && ir.newright.returnlink.parentNode)
        ir.newright.returnlink.parentNode.removeChild(ir.newright.returnlink);

      if (ir.ridfocus) {
        /* revert to the overview */
        ir.clearlist();
        ir.ridfocus = undefined;
        ir.pending.location = ir.uidblock;
        ir.allrights = {};
        ir.listupdate();
      }
      if (ir.newright.button && ir.maxrights > ir.rightcount)
        ir.newright.button.style.display = 'inline';
    },
    rightform: undefined,
    button: undefined,
    returnlink: undefined
  },

  modifyright: function() {
    /* transition to modifying the right associated with this button */
    ir.ridfocus = this.right.rid;
    ir.clearlist();
    ir.newright.rightform = undefined;
    ir.newright.createform(this.right);

    /* add previous versions header */
    var divider = document.createElement('tbody');
    var cell = divider.appendChild(document.createElement('tr')).appendChild(document.createElement('th'));
    ir.pending.location = cell;
    cell.colSpan = ir.columncount + 1;
    cell.appendChild(document.createTextNode(ir.t('Previous versions')));
    ir.rightlist.appendChild(divider);

    ir.allrights = {}; // remove all lists of rights until the new data comes in.
    ir.listupdate();
  },

  /* take an existing right (a tbody section), and make it useful for scribes */
  scribifyright: function(right) {
    /* don't do any scribelike thing if there is no defined name */
    if (undefined === ir.you)
      return right;

    /* add link to edit right, if none exists */
    if (undefined === right.editlink) {
      right.editlink = ir.createbutton(ir.t('Modify/View History'), ir.modifyright);
      right.editlink.right = right; /* FIXME: can we avoid this cycle somehow? */
      right.label.parentNode.appendChild(right.editlink);
    }

    /* add endorse/rescind button, if none exists */
    if (undefined === right.endorsebutton ) {
      right.endorsebutton = ir.createbutton('', ir.endorse.click);
      right.endorsebutton.className = 'endorsement';
      right.endorsing = false;
      right.endorsebutton.right = right; /* FIXME: can we avoid this cycle somehow? */
      right.status.insertBefore(right.endorsebutton, right.status.firstChild);
    }
    if (ir.ridfocus) {
      right.endorsebutton.style.display = 'none';
      right.editlink.style.display = 'none';
    }

      /* this is a simple array of all the "accept" elements for translations. */
    right.accepts = [];

    /* add translation links and displays */
    for (var langix in ir.alllangs) {
      var l = ir.alllangs[langix];
      if (undefined === right[l].translate) {
        right[l].translate = document.createElement('div');
        var translator = document.createElement('div');
        translator.source = right[l];
        translator.className = 'translate';
        translator.appendChild(document.createTextNode('[' + langix + '] ' + ir.t('Translate!') + '→'));
        right[l].translate.appendChild(translator);
        var targlangcount = 0;
        for (var targlang in ir.alllangs) {
          if (targlang != langix) {
            /* newdisplay is where the new target language translation will be displayed */
            var newdisplay = document.createElement('div');
            newdisplay.className = 'transdisplay';
            var decoration = newdisplay.appendChild(document.createElement('div'));
            decoration.className = 'decoration';
            var accept = decoration.appendChild(ir.createimgtarget(function() {ir.translate.accept(this.parentNode.parentNode);}, 'images/accept.png', ir.t('accept')));
            if (ir.ridfocus)
              accept.style.display = 'inline';
            else
              accept.style.display = 'none';
            right.accepts.push(accept);
            decoration.appendChild(ir.createimgtarget(function() {ir.translate.dismiss(this.parentNode.parentNode);}, 'images/dismiss.png', ir.t('dismiss')));
            var trlabel = newdisplay.appendChild(document.createElement('span'));
            trlabel.appendChild(document.createTextNode(targlang + ': '));
            trlabel.className = 'label';
            newdisplay.body = newdisplay.appendChild(document.createElement('span'));
            newdisplay.body.setAttribute('lang', ir.alllangs[targlang]);
            newdisplay.style.display = 'none';
            right[l].translate.appendChild(newdisplay);

            var newtarg = ir.createbutton(ir.alllangs[targlang], ir.translate.click);

            /* bind the triggering target span to the display div: */
            newtarg.displaydiv = newdisplay;
            newdisplay.triggerbutton = newtarg;

            ir.translate.ready(newtarg);

            newtarg.setAttribute('title', targlang);
            newtarg.setAttribute('targlang', ir.alllangs[targlang]);
            translator.appendChild(newtarg);
            targlangcount++;
          }
        }
        /* FIXME: if more than one target language is present, do we want to create an "all" target? */
        right[l].parentNode.appendChild(right[l].translate);
      }
    }
    return right;
  },

  translationacceptsdisplay: function(right, displayvalue) {
    if (right.accepts) {
      for (var ix = 0; ix < right.accepts.length; ix++) {
        right.accepts[ix].style.display = displayvalue;
      }
    }
  },

  createimgtarget: function(onclick, imgsrc, title) {
    var ret = document.createElement('img');
    ret.className = 'target';
    ret.src = imgsrc;
    ret.onclick = onclick;
    return ret;
  },
  endorse: {
    click: function() {
      this.appendChild(this.pendingcontent);
      this.setAttribute('disabled', 'disabled');

      var action = 'endorse';
      if (this.right.endorsing)
        action = 'rescind';
      $.ajax({
               type: 'POST',
               url: action,
               data: {
                 rvid: this.right.rvid
               },
               target: this,
               rvid: this.right.rvid,
               goal: ! this.right.endorsing,
               success: ir.endorse.receive,
               error: ir.endorse.error
             }
            );

    },
    receive: function(data, textStatus) {
      if (this.target.right.rvid == this.rvid)
        this.target.right.endorsing = this.goal;
      ir.endorse.clearbutton(this.target);
      ir.endorse.updatebutton(this.target);
      var x = eval('('+data+')');
      if (x['rights']) {
        if (x['rights'][this.target.right.rid]) {
          ir.updateright(this.target.right, x['rights'][this.target.right.rid]);
        }
      }
    },
    error: function(xhr, textStatus, errorThrown) {
      alert('Translation error: (' + xhr.status +
            ' ' + xhr.statusText + ')\n' + xhr.responseText);
      ir.endorse.clearbutton(this.target);
      ir.endorse.updatebutton(this.target);
    },
    clearbutton: function(button) {
      for (var i = 0; i < button.childNodes.length; i++)
        if (button.childNodes[i] == button.pendingcontent)
          button.removeChild(button.pendingcontent);
      button.removeAttribute('disabled');
    },
    updatebutton: function(button) {
      if (button.right.endorsing) {
        button.firstChild.nodeValue = ir.t('Rescind endorsement');
      } else {
        button.firstChild.nodeValue = ir.t('Endorse Right');
      }
    }

  },

  getrighttext: function(loc) {
      /* in the usual case, the source is a div, and it contains a
       single text element. we should grab the value of the text from
       that element. */
    var ret = loc.firstChild.nodeValue;
      /* in the editing case, the source is a textarea within the div.
       * we should grab the value of the textarea.
       */
    if (ret === null) {
      ret = loc.firstChild.value;
    }
    return ret;
  },

  translate: {

    click: function() {

      var srctext = ir.getrighttext(this.parentNode.source);

      ir.translate.inprogress(this);

      $.ajax({
               type: 'GET',
               url: './',
               data: {
                 area: 'translate',
                 src_lang: this.parentNode.source.lang,
                 targ_lang: this.getAttribute('targlang'),
                 message: srctext
               },
               target: this,
               message: srctext,
               success: ir.translate.receive,
               error: ir.translate.error
             }
            );
    },

    /* what happens when the x gets clicked? */
    dismiss: function(targ) {
      targ.style.display = 'none';
      if (targ.triggerbutton) {
        targ.triggerbutton.style.display = 'inline';
        /* FIXME: if this happened, then we need to make sure that the "Translate!->" target div is available */
      }
    },
    accept: function(targ) {
      /* apply this translation to the associated target */

      var ta = document.getElementById('right_title_' + targ.body.lang);
      if (ta) {
        ta.value = targ.body.firstChild.nodeValue;
      } else {
        alert(ir.t('Nowhere to put this translation.'));
      }
      ir.translate.dismiss(targ);
    },

    receive: function(data, textStatus) {
      // this should be an ajax object formulated with the above invocation
      var srctext = this.target.parentNode.source.firstChild.nodeValue;
      ir.translate.ready(this.target);

      var loc = this.target.displaydiv.body;
      while (loc.firstChild)
        loc.removeChild(loc.firstChild);
      loc.appendChild(document.createTextNode(data));
      this.target.displaydiv.style.display = 'block';

      this.target.style.display = 'none';

      /* FIXME: if all the target languages are displayed, hide the whole "Translate!->" target div. */
//      alert('translate ' + this.message + ' from ' + this.target.parentNode.source.lang + ' into ' + this.target.getAttribute('targlang') + ":\n" + data);
    },
    error: function(xhr, textStatus, errorThrown) {

      alert('Translation error: (' + xhr.status +
            ' ' + xhr.statusText + ')\n' + xhr.responseText);

      ir.translate.ready(this.target);
//      alert('translate error ' + srctext + ' from ' + this.parentNode.source.lang + ' into: ' + this.getAttribute('targlang'));
    },
    inprogress: function(button) {
      while (button.firstChild)
        button.removeChild(button.firstChild);
      button.appendChild(button.pendingcontent);
      button.className = 'pending';
    },
    ready: function(button) {
      while (button.firstChild)
        button.removeChild(button.firstChild);
      button.appendChild(button.readycontent);
      button.className = 'ready';
      button.style.display = 'inline';
    }
  },

  updateright: function(orig, data) {
    orig.rvid = data.rvid;
    orig.rid = data.rid;

    for (var langix in ir.alllangs) {
	    var l = ir.alllangs[langix];
      if (orig[l].firstChild.nodeValue != data.localizations[l]) {
        if (orig[l].translate) {
        /* dismiss all translations */
          var n = orig[l].translate.firstChild;
          while (n) {
            if (n.className == 'transdisplay')
              ir.translate.dismiss(n);
            n = n.nextSibling;
          }
        }
        orig[l].firstChild.nodeValue = data.localizations[l];
      }
      if ('' == orig[l].firstChild.nodeValue) {
        orig[l].className = '';
        /* if there is no data, then hide all translations and the "Translate!->" section */
        if (orig[l].translate)
          orig[l].translate.style.display = 'none';
      } else {
        orig[l].className = 'content';
        /* if there is data, make sure the translations and the "Translate!->" section is visible */
        if (orig[l].translate)
          orig[l].translate.style.display = 'block';
      }
    }

    orig.label.firstChild.nodeValue = data['rightletter'];
    orig.endorsers.firstChild.nodeValue = data.endorsers.join(' ');
    orig.posted.firstChild.nodeValue = data.posted;

    /* change the endorsing button's text and behavior based on whether or not we are in the endorsers: */
    if (ir.you && orig.endorsebutton) {
      var found = false;
      for (var ex = 0; ex < data.endorsers.length; ex++)
        if (data.endorsers[ex] == ir.you)
          found = true;

      orig.endorsing = found;
      ir.endorse.updatebutton(orig.endorsebutton);
    }

    /* FIXME: adjust style to indicate number of endorsers */
  },

  dealwithright: function(rid, data) {
    var r = ir.allrights[rid];
    if ( r === undefined ) {
      /* three cases: */

      if (ir.isboard) {
      /* board: insert just after the end of the list, wherever that is. */
        r = ir.createnewright();

        var lastkey = ir.maxkey(ir.allrights);
        if (lastkey) {
          var lastright = ir.allrights[lastkey];
          ir.rightlist.insertBefore(r, lastright.nextSibling);
        } else
          ir.rightlist.appendChild(r);

      } else {
	      r = ir.newrighttbody();

        if (ir.ridfocus) {
      /* scribe edit view: go on top of list, after the first two kids (which are the edit box and the "previous versions" header) */
          // create a warning if this has come in after the time we transitioned to editing this right:
          if (ir.newright.rightform && ir.newright.rightform.rvid &&
              Number(ir.newright.rightform.rvid) < Number(data.rvid))
            r.className = 'overlapped';
          ir.rightlist.insertBefore(r, ir.rightlist.childNodes[2]);
        } else {
      /* must be the overview (scribe or otherwise): new rights go at end of list */
          ir.rightlist.appendChild(r);
        }
      }

	    ir.allrights[rid] = r;
    }
    ir.updateright(r, data);
  },


  t: function(string) {
    if (undefined === document.body.lang ||
        undefined === ir.localizations[document.body.lang] ||
        undefined === ir.localizations[document.body.lang][string])
      return string;

    return ir.localizations[document.body.lang][string];
  },

/* utility functions */
  /* returns the number of keys in the object */
  countobj: function(obj) {
    var ret = 0;
    for (var k in obj) ret++;
    return ret;
  },
/* return the largest key (forced to numeric comparisons) */
  maxkey: function(obj) {
    var ret = null;
    for (var k in obj)
      if (null === ret || Number(k) > ret)
        ret = Number(k);
    return ret;
  },
/* transforms an object into a string for alerting or printing */
  objtostring: function(obj) {
    var ret = '';
    for (var key in obj)
      ret += key + ': ' + obj[key] + "\n";
    return ret;
  },

  ajax: {

    /* thanks to PPK for this XMLHttpRequest wrapper!  See http://www.quirksmode.org/js/xmlhttp.html */
    sendRequest: function(url, callback, postData) {

	    var req = ir.ajax.createXMLHTTPObject();
	    if (!req) return;
	    var method = (postData) ? "POST" : "GET";
	    req.open(method,url,true);
	    req.setRequestHeader('User-Agent','XMLHTTP/1.0');
	    if (postData)
		    req.setRequestHeader('Content-type','application/x-www-form-urlencoded');
	    req.onreadystatechange = function () {
		    if (req.readyState != 4) return;
		    if (req.status != 200 && req.status != 304) {
          //			alert('HTTP error ' + req.status);
			    return;
		    }
		    callback(req);
	    };
	    if (req.readyState == 4) return;
	    req.send(postData);
    },

    XMLHttpFactories: [
	    function() {return new XMLHttpRequest();},
	    function() {return new ActiveXObject("Msxml2.XMLHTTP");},
	    function() {return new ActiveXObject("Msxml3.XMLHTTP");},
	    function() {return new ActiveXObject("Microsoft.XMLHTTP");}
    ],
    createXMLHTTPObject: function() {
	    var xmlhttp = false;
	    for (var i=0;i<ir.ajax.XMLHttpFactories.length;i++) {
		    try {
			    xmlhttp = ir.ajaxXMLHttpFactories[i]();
		    }
		    catch (e) {
			    continue;
		    }
		    break;
	    }
	    return xmlhttp;
    }

  },

  loader: function() {
    var jswarning = document.getElementById('javascriptwarning');
    if (jswarning)
      jswarning.parentNode.removeChild(jswarning);

    if (undefined === ir.rightlist)
      ir.rightlist = document.getElementById('rightlist');
    if (undefined === ir.uidblock)
      ir.uidblock = document.getElementById('user-id');

    if (undefined === ir.modebuttonlocation)
      ir.modebuttonlocation = document.getElementById('messages');

    ir.pending.location = ir.uidblock;

    ir.state.setup();

    /* the board view has a thisistheboard element. use this to determine whether to launch the scroll */
    if (document.getElementById('thisistheboard'))
      ir.isboard = true;

    if (ir.rightlist)
      ir.listupdate();

    if (ir.isboard) {
      if (undefined === ir.scrollint)
        ir.scrollint = setInterval(ir.scrollboard, 100);
      ir.rightlist.style.position = 'absolute';
    }

    if (ir.rightlist)
      if (undefined === ir.updateint)
        ir.updateint = setInterval(ir.listupdate, 10000);

  },
  newrighttbody: function() { return ir.scribifyright(ir.createnewright()); },

  state: {
    value: 'pre', // pre, active, or post
    block: undefined,
    rooms: {},
    display: undefined,

    setup: function() {
      if (undefined === ir.state.block) {
        ir.state.block = document.body.appendChild(document.createElement('div'));
        ir.state.block.id = 'workshopstate';
        ir.state.rooms = {};
        var x = ir.state.block.appendChild(document.createElement('div'));
        x.className = 'overview';
        ir.state.display = x.appendChild(document.createTextNode(''));
      }
    },
    update: function(rooms) {
      for (var room in rooms) {
        if (undefined === ir.state.rooms[room]) {
          var fs = ir.state.block.appendChild(document.createElement('fieldset'));
          ir.state.rooms[room] = {
            name:  fs.appendChild(document.createElement('legend')).appendChild(document.createTextNode('')),
            members: fs.appendChild(document.createElement('div')).appendChild(document.createTextNode(''))
          };
        }
        // FIXME: indicate state of each room as well!
        ir.state.rooms[room].name.nodeValue = rooms[room].name + ' [' + rooms[room].state + ']';
        ir.state.rooms[room].members.nodeValue = rooms[room].groups.length ? rooms[room].groups.join(' ') : ir.t('No one present');
        ir.state.display.nodeValue = ir.t(ir.state.descriptions[ir.state.value] ? ir.state.descriptions[ir.state.value] : 'I am seriously confused!');
      }
    },
    descriptions: {
      pre: 'The workshop has not yet begun.',
      post: 'The workshop has ended.',
      active: 'The workshop is underway.'
    },
    hide: function() {
      if (ir.state.block.parentNode)
        ir.state.block.parentNode.removeChild(ir.state.block);
    }

  },

  columncount: 2, /* 2 columns per right. */
  localizations: {
    /* this shoud get filled in by loading ir.XX.js (where XX is 2-letter language code) */
  },
  isboard: false,
  ridfocus: undefined, // this gets set to an rid when we're editing a right.  it should be undefined for board or overview
  rightlist: undefined,
  uidblock: undefined,
  modebuttonlocation: undefined,
  pending: {
    img: undefined, // the "spinner" image
    location: undefined, // if this is set, then while regular updates are going, it will get appended "spinner" image
    display: function() {
      if (undefined == ir.pending.img)
        ir.pending.img = ir.creatependingimg();
      if (ir.pending.location)
        ir.pending.location.appendChild(ir.pending.img);
    },
    complete: function() {
      if (ir.pending.img &&
          ir.pending.img.parentNode)
        ir.pending.img.parentNode.removeChild(ir.pending.img);
    }
  },
  boardscrolloffset: 0,
  scrollint: undefined,
  updateint: undefined,
  alllangs: {},
  allrights: {},
  maxrights: 10, /* this will get reset from ajax calls to the official amount */
  rightcount: 0, /* this will get reset from ajax calls to the official amount */
  you: undefined

};
