/* Depends on MochiKit */
/* Build the br namespaces */
if(typeof(br) == 'undefined')
  var br = {};
if(typeof(br.form) == 'undefined')
  br.form = {};
if(typeof(br.dom) == 'undefined')
  br.dom = {};
if(typeof(br.ajax) == 'undefined')
  br.ajax = {};

/* Register functions in a simple call registry */
br.dom.onLoadRegistry = {
  register: function(loadfunc) {
    MochiKit.Signal.connect(window, 'onload', loadfunc);
  }
};
br.dom.onLoad = function() { return null; };

Nullify = function(item) {
  return function() {item = null;};
};

/* Iterator Functions */
br.iflatzip = function(p, q/*, ...*/) {
  /***
      Unlike MochiKit.Iter.izip, returns a single iterable.
      
      iflatzip(p, q, ...) => [p0, q0, ..., p1, q1, ...]
  ***/
  var map = MochiKit.Base.map;
  var next = MochiKit.Iter.next;
  var iterables = map(MochiKit.Iter.iter, arguments);
  var container = MochiKit.Iter.cycle(iterables);
  return {
    "repr": function() { return "iflatzip(...)"; },
    "toString": MochiKit.Base.forward("repr"),
    "next": function() { return next(next(container)); }
  };
};

br.dom.highlight = function() {
  MochiKit.Iter.forEach(arguments, function(element) {
    new MochiKit.Visual.Highlight(element);
  });
};

/* Update Message */
var UpdateMessage = new rl.Prototype({
  initialize: function(element) {
    this.element = MochiKit.DOM.getElement(element);
  },
  
  show: function(message) {
    if(!MochiKit.Base.isUndefinedOrNull(message))
      MochiKit.DOM.replaceChildNodes(this.element, message);
    MochiKit.Style.setDisplayForElement('', this.element);
  },
  
  hide: function() {
    setTimeout(MochiKit.Base.bind(function() {
      MochiKit.Visual.fade(this.element, {duration: 0.8});
    }, this), 500);
  }
});

br.dom.updatemessage = null;
MochiKit.Signal.connect(window, 'onload', function() {
  br.dom.updatemessage = new UpdateMessage('updatemsg');
});
MochiKit.Signal.connect(window, 'onunload', function() { 
  br.dom.updatemessage = null;
});


/* Page message */
var PageMessages = new rl.Prototype({
  initialize: function(element) {
    this.element = MochiKit.DOM.getElement(element);
    this.msgCount = 0;
  },
  
  loadOptions: function(options) {
    var opts = { 'closeAfter': null };
    MochiKit.Base.update(opts, options || {});
    return opts;
  },
  
  buildCloseButton: function() {
    var md = MochiKit.DOM;
    var closer = md.A({href:'#'}, '[X]');
    MochiKit.Signal.connect(closer, 'onclick', function(e) {
      $(this.parentNode.parentNode).fadeOut(0.5*1000);
      e.stop();
      return false;
    });
    return closer;
  },

  buildMessage: function(inner, messageClass, options) {
    options = this.loadOptions(options);
    var md = MochiKit.DOM;
    var msgID = ('pagemsgd' + (this.msgCount++));
    var messageNode = md.DIV({id: msgID, 'class': 'message'},
      md.DIV({'class': messageClass},this.buildCloseButton(), ' ', inner)
    );
    if(options.closeAfter != null) {
      setTimeout(function() {
        $(messageNode).filter(':visible').fadeOut(0.5 * 1000);
      }, options.closeAfter*1000);
    }

    return messageNode;
  },
  
  append: function(node) {
    this.element.appendChild(node);
    return node;
  },
  
  info: function(message, options) {
    return this.append(this.buildMessage(message, 'info', options));
  },
  
  warn: function(message, options) {
    return this.append(this.buildMessage(message, 'warn', options));
  },
  
  error: function(message, options) {
    return this.append(this.buildMessage(message, 'error', options));
  }
});

var Flash = null;
MochiKit.Signal.connect(window, 'onload', function() { 
  Flash = new PageMessages('pagemessages');
});
MochiKit.Signal.connect(window, 'onunload', Nullify(Flash));

/* ViewletInfo events:
 *
 * beforeExpand (this)
 * expand (this)
 * beforeCollapse (this)
 * collapse (this)
 * unload (this)
 */
var ViewletInfo = new rl.Prototype({
  initialize: function(element, options) {
    var m = MochiKit.Base, md = MochiKit.DOM;
    
    this.setOptions(options); 

    this.element = md.getElement(element);
    this.id = this.element.id;
    this.cookieID = this.id + '-expansion';
    this.element.scviewlet = this;
    
    this.inner = md.getElement(this.element.id + '-inner');

    this.initialState = rl.Element.visible(this.inner) ? 'expanded' : 'collapsed';
    this.firstExpansion = true;

    this.head = rl.Element.down(this.element, 'div', 'head');
    this.controls = rl.Element.down(this.element, 'div', 'controls');
    this.toggler = md.getElement(this.element.id+'-toggle');

    this.connectEvents();
    this.handleInitialState();
  },
  
  setOptions: function(options) {
    var m = MochiKit.Base;
    this.options = m.update({
      onExpand: m.noop,
      onCollapse: m.noop,
      fadeDuration: 0.3
    }, options || {});
  },
  
  // Establishes the core event connections, including the ones to
  // `options.onExpand` and `options.onCollapse`
  connectEvents: function() {
    var connect = MochiKit.Signal.connect;
    connect(window, 'onunload', this, this.onWindowUnload);
    connect(this.toggler, 'onclick', this, this.onTogglerClick);
    connect(this, 'expand', this.options.onExpand);
    connect(this, 'collapse', this.options.onCollapse);
  },
  
  // Called after initialization is all complete so that expand / collapse
  // handlers may kick in.
  handleInitialState: function() {
    if(this.initialState == 'expanded')
      this.onExpand();
    else
      this.onCollapse();
  },
  
  onWindowUnload: function(evt) {
    MochiKit.Signal.signal(this, 'unload', this);
    // Remove circular reference between the element and this object.
    // This should prevent memory leaks (particularly in IE).
    this.element.scviewlet = null;
  },
  
  isExpanded: function() {
    return rl.Element.visible(this.inner);
  },
  
  state: function() {
    return this.isExpanded() ? 'expanded' : 'collapsed';
  },
  
  onExpand: function() {
    this.toggler.innerHTML = SCViewlet.arrow['expanded'];
    MochiKit.Signal.signal(this, 'expand', this, this.firstExpansion);
    this.firstExpansion = false;
  },
  
  onCollapse: function() {
    this.toggler.innerHTML = SCViewlet.arrow['collapsed'];
    MochiKit.Signal.signal(this, 'collapse', this);
  },

  onTogglerClick: function(evt) {
    evt.stop();

    var m = MochiKit.Base, md = MochiKit.DOM, Signal = MochiKit.Signal;
    var beforeEvent, afterEvent;
    var action = SCViewlet.invert[this.state()];
    switch(action) {
      case 'expanded':
        beforeEvent = 'beforeExpand';
        afterEvent = m.method(this, this.onExpand);
        break;
      case 'collapsed':
        beforeEvent = 'beforeCollapse';
        afterEvent = m.method(this, this.onCollapse);
        break;
    }
    var fx_options = {
      duration: this.options.fadeDuration * 1000, 
      after_finish: afterEvent
    };
    
    Signal.signal(this, beforeEvent, this);
    if(action=='expanded')
      $(this.inner).slideDown(fx_options.duration, fx_options.after_finish);
    else
      $(this.inner).slideUp(fx_options.duration, fx_options.after_finish);
    
    rl.Cookie.set(this.cookieID, action);
  }
});

var SCViewlet = {
  invert: {'expanded': 'collapsed', 'collapsed': 'expanded'},
  arrow: {'expanded': '&uarr;', 'collapsed': '&darr;'},
  
  expanded: function(element) {
    var innerID = MochiKit.DOM.getElement(element).id + '-inner';
    return rl.Element.visible(innerID);
  },
  state: function(element) {
    return SCViewlet.expanded(element) ? 'expanded' : 'collapsed';
  },
  
  loaded: false,
  registry: {},
  pending: [],
  
  register: function(element, options) {
    if(SCViewlet.loaded) {
      var vinfo = new ViewletInfo(element, options);
      SCViewlet.registry[vinfo.id] = vinfo;
    } else {
      if(SCViewlet.pending.length == 0) {
        MochiKit.Signal.connect(window, 'onload', SCViewlet.loadRegistry);
      }
      
      SCViewlet.pending.push([element, options]);
    }
  },
  
  loadRegistry: function(evt) {
    while(SCViewlet.pending.length > 0) {
      var reginfo = SCViewlet.pending.shift();
      var element = reginfo[0], options = reginfo[1];
      
      var vinfo = new ViewletInfo(element, options);
      SCViewlet.registry[vinfo.id] = vinfo;
    }
    
    SCViewlet.loaded = true;
  },
  
  // For old style viewlets
  toggle: function(element, link) {
    var md = MochiKit.DOM;
    element = md.getElement(element);
    link = md.getElement(link);
    var cookieID = element.id + '-expansion';
    var inner = md.getElement(element.id + '-inner');
    var curstate = SCViewlet.state(element);
    var inverse = SCViewlet.invert[curstate];
    
    var oncomplete = function(){link.innerHTML = SCViewlet.arrow[inverse];};
    
    $(inner).slideToggle(0.3*1000, oncomplete);
    
    rl.Cookie.set(cookieID, inverse);
    return false;
  }
};

var FavoritesManager = new rl.Prototype({
  initialize: function(element, baseURL) {
    this.element = MochiKit.DOM.getElement(element);
    this.baseURL = baseURL;
    
    this.redAlertColor = MochiKit.Color.Color.redColor(
      ).lighterColorWithLevel(0.35).toHexString();
  },
  
  redalert: function() {
    new MochiKit.Visual.Highlight(
      // Go to the inner part of the viewlet, which is the main area outside
      // of the favorites element.
      rl.Element.up(this.element, null, 'inner'),
      { startcolor: this.redAlertColor }
    );
    return this;
  },
  
  add: function(itemID) {
    var onFailure = MochiKit.Base.method(this, function(err) {
      this.redalert();
      Flash.error('Could not add to favorites: ' + err.req.responseText);
    });
    var onSuccess = MochiKit.Base.method(this, this.addFavoriteInfo);
    return new rl.JSONRequest(
      Path.join(this.baseURL, '@@favorites.add', itemID)
    ).post().addCallbacks(onSuccess, onFailure);
  },
  
  remove: function(itemID, element) {
    element = MochiKit.DOM.getElement(element);
    var onFailure = MochiKit.Base.method(this, function(err) {
      this.redalert();
      Flash.error('Could not remove from favorites: ' + err.req.responseText);
    });
    var onSuccess = MochiKit.Base.method(this, function(request) {
      this.removeFavoriteDOM(element);
    });
    return new rl.Request(
      Path.join(this.baseURL, '@@favorites.remove', itemID),
      {mimeType: 'text/plain'}
    ).post().addCallbacks(onSuccess, onFailure);
  },
  
  addFavoriteInfo: function(info) {
    var m = MochiKit.Base, md = MochiKit.DOM;
    var empty = rl.Element.down(this.element, 'div', 'empty');
    if(empty && rl.Element.visible(empty))
      rl.Element.hide(empty);
    
    /* This mirrors the code in `favorites.py`, but now allows favorites.py
     * to be used in non-CMS skins as the quick add viewlet returns JSON 
     * instead of HTML
     */
    var iconSPAN = md.DIV({'class': 'favorite-icon'});
    iconSPAN.innerHTML = '&nbsp;';
    if(info['icon'])
      MochiKit.Style.setStyle(iconSPAN, {'background-image': 'url('+info.icon+')'});
    
    var favoriteText = md.DIV({'class': 'favorite-text'},
      md.A({'href': info.manage_url, title: info.path}, info.title),
      ' ', md.TT(null, info.display_path)
    );
    
    var key = info.key, domID = info.dom_id;
    var removeLink = md.A({'href': '#'}, '[X]');
    MochiKit.Signal.connect(removeLink, 'onclick', this, 
      m.method(this, function(e) { this.remove(key, domID); e.preventDefault(); })
    );
    
    var favoriteDOM = md.DIV({'class': 'favorite', 'id': domID},
      iconSPAN, favoriteText, md.DIV({'class': 'removal-buttons'}, removeLink)
    );
    
    md.appendChildNodes(this.element, favoriteDOM);
    new MochiKit.Visual.Highlight(favoriteDOM);
  },
  
  removeFavoriteDOM: function(element) {
    element = MochiKit.DOM.getElement(element);
    var md = MochiKit.DOM;
    $(element).fadeOut(
      0.5*1000,
      MochiKit.Base.method(this, function() {
        $(element).remove();
        var otherFaves = $(this.element).find('.favorite');
        if(!otherFaves.size()) {
          $(this.element).find('div.empty:hidden').show();
        }
      }
    ));
  }
});
var SelectionHighlighter = new rl.Prototype({
  initialize: function(element, className) {
    var md = MochiKit.DOM, itertools = MochiKit.Iter;
    var class_expr = className ? '.'+className : '';
    this.element = md.getElement(element);
    this.selectbox_expr = "#"+this.element.id+" input"+class_expr;
    this.selectBoxes = $(this.element).find('input'+class_expr).get();
    
    itertools.forEach(this.selectBoxes, function(el) {
      MochiKit.Signal.connect(el, 'onclick', this, this.onClick);
    }, this);
    
    var toggler = $(this.element).find("input.selection-toggler").get(0);
    if(toggler)
      MochiKit.Signal.connect(toggler, 'onclick', this, this.toggle_all);
    this.highlightSelected();
  },
  
  toggle_all: function(e) {
    var cb = e.src();
    if(cb.checked)
      $(this.selectbox_expr).each(function(highlighter) {
        this.checked = true;
        highlighter.highlight($(this).parents('tr').get(0));
      }, [this]);
    else
      $(this.selectbox_expr).each(function(highlighter) {
        this.checked = false;
        highlighter.removeHighlight($(this).parents('tr').get(0));
      }, [this]);
  },
  
  highlight: function(row) {
    $(row).addClass('selected');
  },
  
  removeHighlight: function(row) {
    $(row).removeClass('selected');
  },
  
  highlightSelected: function() {
    MochiKit.Iter.forEach(this.selectBoxes, function(el) {
      var row = $(el).parents('tr').get(0);
      if (el.checked) this.highlight(row);
      else this.removeHighlight(row);
    }, this);
  },
  
  onClick: function(e) {
    var cb = e.src();
    var row = $(cb).parents('tr').get(0);
    if (cb.checked) this.highlight(row);
    else this.removeHighlight(row);
  }
});

br.ajax.LiveCheckbox = new rl.Prototype({
  initialize: function(element, url, options) {
    this.element = MochiKit.DOM.getElement(element);
    this.url = url;
    this.options = MochiKit.Base.update({
      ajaxOptions: {}
    }, options || {});
    
    MochiKit.Signal.connect(this.element, 'onclick', this, this.handleClick);
  },
  
  handleClick: function(e) {
    var m = MochiKit.Base;
    var fail = m.method(this, function(err) {
      Flash.error('Error: ' + err.req.responseText);
      this.element.checked = !(this.element.checked);
    });
    var success = m.method(this, function(checked) {
      this.element.checked = checked;
    });
    
    new rl.JSONRequest(this.url).post(
      m.queryString({'value': this.element.checked})
    ).addCallbacks(success, fail);
  }
});

/* Form Helpers, many borrowed from prototype.js */
// FIXME: As we wean back off of prototype, this should be moved - preferably to rocketlib
MochiKit.Base.update(br.form, {
  toggleSubmit: function(button, newtext) {
    button = MochiKit.DOM.getElement(button);
    button.value = newtext;
    button.disabled = true;
    return true;
  }
});
var toggleSubmit = br.form.toggleSubmit;

br.form.element = {
  disable: function(element) { 
    element = MochiKit.DOM.getElement(element);
    element.blur();
    element.disabled = 'true';
  },
  enable: function(element) { 
    element = MochiKit.DOM.getElement(element);
    element.disabled = '';
  }
};

MochiKit.Base.update(br.dom, {
  visible: function(element) {
    return rl.Element.visible(element);
  },

  toggle: function() {
    MochiKit.Base.map(rl.Element.toggle, arguments);
  },
    
  hide: function() {
    MochiKit.Base.map(rl.Element.hide, arguments);
  },
    
  show: function() {
    MochiKit.Base.map(rl.Element.show, arguments);
  },

  remove: function(element) {
    rl.getjq(element).remove();
  },

  update: function(element, content) {
    rl.getjq(element).html(content);
  },
    
  toDOM: function(html) {
    return html;
  },
  
  find: function(element, tagname, classname) {
    return MochiKit.DOM.getElementsByTagAndClassName(
        tagname, classname, element);
  },
    
  findFirst: function(element, tagname, classname) {
    return rl.Element.down(element, tagname, classname);
  }
});


/*****************************************************
 * Sortable Tables                                   *
 *****************************************************/
var SortableFuncs = {
  mouseOverFunc : function (e) {
    $(e.src()).addClass('over');
  },

  mouseOutFunc : function (e) {
    $(e.src()).removeClass('over');
  },

  ignoreEvent : function (e) {
    e.stop();
  }
};

var Arrows = {
  // \u2193 is down arrow, \u2191 is up arrow
  Up: "\u2191",
  Down: "\u2193"
};

var SortableManager = new rl.Prototype({
  initialize: function(table) {
    this.thead = null;
    this.tbody = null;
    this.columns = [];
    this.rows = [];
    this.sortState = {};
    this.sortkey = 0;
    this.tableClass = null;
    
    if(!rl.is_undefined(table)) this.initWithTable(table);
  },

  initWithTable: function(table) {
    /***

        Initialize the SortableManager with a table object
    
    ***/
    var m = MochiKit.Base, md = MochiKit.DOM, itertools = MochiKit.Iter;
    table = md.getElement(table);
    if(!table)
      return false;

    this.tableClass = md.getNodeAttribute(table, 'class');
    // Find the thead
    this.thead = table.getElementsByTagName('thead')[0];

    // get the mochi:format key and contents for each column header
    var cols = this.thead.getElementsByTagName('th');
    itertools.forEach(itertools.izip(cols, itertools.count()), function(nodeAndIndex){
      var node = nodeAndIndex[0], index=nodeAndIndex[1];
      var attr = md.getNodeAttribute(node, "mochi:format");
      this.columns.push({
        'sortkey': index,
        format: attr,
        element: node,
        sortable: (attr? true : false)
      });
    }, this);

    // scrape the tbody for data
    this.tbody = table.getElementsByTagName('tbody')[0];

    // every row
    var rows = this.tbody.getElementsByTagName('tr');
    itertools.forEach(rows, function(row) {
      var rowData = [];
      var cells = row.getElementsByTagName('td');
      itertools.forEach(itertools.izip(cells, itertools.count()), function(cellAndIndex) {
        var cell = cellAndIndex[0], j = cellAndIndex[1];
        var obj = md.scrapeText(cell);
        switch(this.columns[j].format) {
          case 'isodate':
            obj = MochiKit.DateTime.isoDate(obj);
            break;
          case 'str':
            break;
          case 'istr':
            obj = obj.toLowerCase();
            break;
          case 'int':
            obj = parseInt(obj, 10);
            break;
          case 'float':
            obj = parseFloat(obj);
            break;
        }
        rowData.push(obj);
      }, this);

      // Stow away a reference to the Table Row and save it.
      rowData.row = row.cloneNode(true);
      this.rows.push(rowData);
    }, this);

    // Get the first sortable column and set that as the sortkey before
    // drawing sorted rows.
    var firstSortable = this.firstSortableColumn();
    if(!rl.is_undefined(firstSortable))
      this.sortkey = firstSortable.sortkey;

    // do initial sort on first column
    this.drawSortedRows(this.sortkey, true, false);
  },
  
  firstSortableColumn: function() {
    for(var i=0; i<this.columns.length; i++) {
      var col = this.columns[i];
      if(col.sortable == true)
        return col;
    }
  },

  onSortClick: function(e, sortkey) {
    sortkey = sortkey.toString();
    MochiKit.Logging.logDebug('onSortClick', e.src(), sortkey);

    var order = this.sortState[sortkey];

    if(order == null) {
      order = true;
    } else if (sortkey == this.sortkey) {
      order = !order;
    }
    this.drawSortedRows(sortkey, order, false);
  },

  drawSortedRows: function(key, forward, clicked) {
    /***

        Draw the new sorted table body, and modify the column headers
        if appropriate

    ***/
    var m = MochiKit.Base, md = MochiKit.DOM, itertools = MochiKit.Iter;
    var signal = MochiKit.Signal;
    MochiKit.Logging.logDebug('drawSortedRows', key, forward);
    this.sortkey = key;

    // sort based on the state given (forward or reverse)
    var cmp = (forward ? m.keyComparator : m.reverseKeyComparator);
    this.rows.sort(cmp(key));

    // Re-apply 'zebra' striping;
    if(this.tableClass == 'listing') {
      var rd = itertools.izip(this.rows, itertools.cycle(['even','odd']));
      itertools.forEach(rd, function(rowAndParity){
        var rowdata = rowAndParity[0], parity=rowAndParity[1];
        itertools.forEach(['odd', 'even'], function(p) {
          md.removeElementClass(rowdata.row, p);
        });
        md.addElementClass(rowdata.row, parity);
      }, this);
    }

    // save it so we can flip next time
    this.sortState[key] = forward;

    // get every "row" element from this.rows and make a new tbody
    var newBody = md.TBODY(null, m.map(m.itemgetter("row"), this.rows));

    // swap in the new tbody
    this.tbody = MochiKit.DOM.swapDOM(this.tbody, newBody);

    itertools.forEach(itertools.izip(this.columns, itertools.count()), function(colAndIndex){
      var col = colAndIndex[0], i=colAndIndex[1];
      if(!col.sortable)
        return;
      
      var node = col.element.cloneNode(true);
      
      var lastChild = node.lastChild;
      if(!rl.is_null(lastChild) && (lastChild.innerHTML==Arrows.Up || lastChild.innerHTML==Arrows.Down))
        MochiKit.DOM.removeElement(lastChild);

      // Remove existing events to minimize leaks (esp. in IE)
      signal.disconnectAll(col.element);
      
      // Set new events for the new node
      signal.connect(node, 'onclick', this, 
        m.method(this, function(e) { this.onSortClick(e, i); })
      );
      signal.connect(node, 'onmousedown', SortableFuncs.ignoreEvent);
      signal.connect(node, 'onmouseover', SortableFuncs.mouseOverFunc);
      signal.connect(node, 'onmouseout', SortableFuncs.mouseOutFunc);
      
      // If this is the sorted column
      if(key == i) {
        // forward sorts mean the rows get bigger going down
        var arrow = (forward ? Arrows.Down : Arrows.Up);

        // add the character to the column header
        node.appendChild(md.SPAN(null, arrow));

        if(clicked)
          signal.signal(node, 'onclick');
      }
      // swap in the new header
      col.element = MochiKit.DOM.swapDOM(col.element, node);
    }, this);
  }
});


/* Editable Tables */
br.form.editableTableNameRegistry = [];
br.form.editableTables = {};
br.form.registerEditableTable = function(tablename) {
    MochiKit.Logging.log('Registering as editable: '+tablename);
    br.form.editableTableNameRegistry.push(tablename);
};

br.form.buildEditableTables = function() {
    MochiKit.Iter.forEach(br.form.editableTableNameRegistry, function(name) {
        br.form.editableTables[name] = new br.form.EditableTable(name);
    });
};
br.dom.onLoadRegistry.register(br.form.buildEditableTables);

br.form.EditableTable = new rl.Prototype({
  initialize: function(tablename) {
    var md = MochiKit.DOM;
    var table = md.getElement(tablename);
    this.tableName = table.id;
    this.table = table;

    var thead = table.getElementsByTagName('thead')[0];
    var columns = thead.getElementsByTagName('th');
    this.thead = thead;
    this.columnLength = columns.length;
    this.columnSizes = MochiKit.Base.map(
        function(node){ return node.getAttribute('br:size'); }, columns
    );

    this.tbody = table.getElementsByTagName('tbody')[0];
    var rows = this.tbody.getElementsByTagName('tr');
    this.rowCount = rows.length;

    var addRowID = tablename.replace('-table', '-addRowButton');
    var removeBottomID = tablename.replace('-table', '-removeRowButton');

    var tfoot = md.TFOOT(null,
      md.TR(null,
        md.TD({colspan:this.columnLength},
          md.INPUT({
            'type': 'button',
            'value': 'Add Row',
            'id': addRowID
          }),
          '  ',
          md.INPUT({
            'type': 'button',
            'value': 'Remove Final Row',
            'id': removeBottomID
          })
        )
      )
    );
    md.appendChildNodes(table, tfoot);
    MochiKit.Signal.connect(addRowID, 'onclick', this, this.addRow);
    MochiKit.Signal.connect(removeBottomID, 'onclick', this, this.removeBottomRow);
  },

  addRow: function(e) {
    MochiKit.Logging.log('Entering addRow');
    var md = MochiKit.DOM;
    var rowName = this.tableName.replace('-table', '-rows');
    rowName = rowName + '.' + this.rowCount;
    md.appendChildNodes(this.tbody, md.TR({'id': rowName}));

    for(var i=0; i<this.columnLength; i++) {
      var widgetName = rowName + ':list';
      md.appendChildNodes(rowName, md.TD(null, 
        md.TEXTAREA({
          'name': rowName + ':list',
          'cols': this.columnSizes[i],
          'rows': 3
        }, '')
      ));
    }

    var buttonId=this.tableName.replace('-table','-removeRowButton');
    br.form.element.enable(buttonId);

    this.rowCount += 1;
    br.dom.highlight(rowName);
  },

  removeBottomRow: function(e) {
    e.stop();
    var buttonId = this.tableName.replace('-table','-removeRowButton');
    var childRows = this.tbody.getElementsByTagName('tr');
    if(childRows.length == 1) {
      Flash.error('Cannot remove final row');
      br.form.element.disable(buttonId);
    } else {
      this.tbody.removeChild(childRows[childRows.length-1]);
      this.rowCount -= 1;
    }
  }
});


/* Textile Preview */
br.form.textilePreview = function(containername, elementname) {
  var container = MochiKit.DOM.getElement(containername);
  var element = MochiKit.DOM.getElement(elementname);
  var parameters = MochiKit.Base.queryString({'content': element.value});
  var hidefunc = function(e) { br.form.hideTextilePreview(containername); };
  var req = new rl.Request('@@textileWidgetPreview', {
    onLoading: function(transport) {
        br.dom.updatemessage.show('Getting Preview');
    },
    onSuccess: function(transport) {
      var md = MochiKit.DOM;
      br.dom.updatemessage.hide();
      var innerElement=md.getElement(containername.replace('-container', '-inner'));
      var newElementName=containername.replace('-container', '-preview');
      var newElementInnerName = newElementName + '-content';
      var hideButton = md.INPUT({'type': 'button', 'value': 'Hide Preview'});
      var newElement = md.DIV(
        {'id': newElementName, 'class': 'textPreview'},
        md.DIV({'class': 'buttons'}, hideButton),
        md.DIV({'id': newElementInnerName, 'class': 'previewInner'})
      );
      MochiKit.Signal.connect(hideButton, 'onclick', hidefunc);
      MochiKit.DOM.replaceChildNodes(container, [newElement, innerElement]);
      md.getElement(newElementInnerName).innerHTML = transport.responseText;
      br.dom.highlight(newElement);
    }
  }).post(parameters);
  return false;
};

br.form.hideTextilePreview = function(container) {
  var container = MochiKit.DOM.getElement(container);
  var previewID = container.id.replace('-container', '-preview');
  var preview = MochiKit.DOM.getElement(previewID);
  if(!preview) {
    MochiKit.Logging.log('Preview Not Found');
    return false;
  }
  container.removeChild(preview);
  return false;
};

