/****************************************************************

  Traction Software, Inc. Confidential and Proprietary Information

  Copyright (c) 1996-2010 Traction Software, Inc.
  All rights reserved.

****************************************************************/

// PLEASE DO NOT DELETE THIS LINE -- make copyright depends on it.

/*
 * This source file depends upon the presence of the following additional
 * JavaScript source files:
 *   - /images/modern/js/shared.js
 *   - /html/js/prototype.js
 *   - /html/js/traction.js
 *   - some i18n Core[_locale].js
 *   - some user-agent [ua].js
 *
 * This file is a library that collects the JavaScript functionality
 * for various types of setting editing interfaces.
 */

/**
 * This is the namespace object for various types of Class objects
 * created to manage individual settings.
 */
var Settings = {


  gotoView: function(viewType, checkDirty, extras) {
    if (!checkDirty ||
	(typeof(leavePage) != "function") ||
	leavePage()) {
      window.location.href = FORM_ACTION_READ_ONLY + "?type=" + viewType + (extras ? extras : "");
    }
  }


};

// PLEASE DO NOT DELETE THIS LINE -- make copyright depends on it.
function dialog_reset() {
  document.fm.fb_submit.value="Reset";
  submitForm();
}
function dialog_apply() {
  triggerCustomEvent("apply");
  document.fm.fb_submit.value="Apply";
  submitForm();
}

function dialog_custom(action) {
  triggerCustomEvent(action);
  document.fm.fb_submit.value = action;
  submitForm();
}

function setupAdminView() {
  onSetupAdminView();
  isDirty = (document.fm.isDirty) ? (document.fm.isDirty.value == "true") : false;
  syncButtons();

  // we don't do the adjustFooter() stuff here

  //admin_footer = document.getElementById("footer");
  //setInterval("adjustFooter()", 500); // 2 times per second, seems to be enough
}

function closeInsertLabelDialog(labelArray, dialogname) {
  var set = "";

  if (labelArray != null) {
    for (var i=0; i<labelArray.length; i++) {
      set += labelArray[i] + " ";
    }
  }

  eval("document.fm."+dialogname+".value = set;");
}


function mp_details(namespace, dir) {
  var extras = "";
  extras += "&principal="+eval("document.fm."+namespace+".value");
  extras += "&dir="+dir;
  openDialog("principaldetails", "principaldetails_window", "", extras, DIALOG_FLAG_MULTIPLE_WINDOWS);
}
function mp_details2(principal, dir) {
  var extras = "";
  extras += "&principal="+principal;
  extras += "&dir="+dir;
  openDialog("principaldetails", "principaldetails_window", "", extras, DIALOG_FLAG_MULTIPLE_WINDOWS);
}
function mp_keydown(event, namespace, dir, extras) {
  if (keyCode2(event) == 13) {
    mp_lookup(namespace, dir, extras);
    return false;
  } else {
    return true;
  }
}
function mp_lookup(namespace, dir, extras) {
  grabCenterWindow();
  
  if (!extras) extras = "";

  mp_lookup_(eval("document.fm."+namespace+"_text.value"), namespace, dir, extras);
}
function mp_lookup_(search, namespace, dir, extras) {

  extras += "&name="+search;
  extras += "&ptype=all";
  extras += "&class=p";
  if (dir) {
    extras += "&dir="+dir;
  }
  
  openDialog("adchecknames", "adchecknames_window", namespace, extras);

}

function closeDialog(viewtype, dialogname, params) {
  // we separate these into different functions so that they can be
  // defined elsewhere
  eval("if (closeDialog_"+viewtype+") { closeDialog_"+viewtype+"(viewtype, dialogname, params); }");
}

function getName(formElement) {
  return formElement.getAttribute("name", 0);
}

//
// Misc setting functions
//
function updateColor(formElement) {
  var name = getName(formElement);
  var colorbox = document.getElementById(name+"_colorbox");
  colorbox.style.backgroundColor = formElement.value;
}

//
// Lookup... functions
//
 
function closeDialog_adchecknames(viewtype, dialogname, params) { 
  if (params.length > 0) {
    eval("document.fm."+dialogname+".value = params[0][0];"+
	 "document.fm."+dialogname+"_text.value = params[0][1];");

    makeDirty();
  }
}


//
// enable/disable onchange handlers
//
function setup_disable_select(source, showArray, hideArray, targets) {

  // setup handler
  var select = eval("document.fm."+source);
  if (select) {

    var old_onchange = select.onchange;
    select.onchange = function() {
      old_onchange();
      do_disable_select(source, showArray, hideArray, targets);
    }

    // for firefox reload behavior that dave and i think is lame [ajm 01.Mar.2005]
    do_disable_select(source, showArray, hideArray, targets);
  }

}

function do_disable_select(source, enableArray, disableArray, targets) {

  var select = eval("document.fm."+source);
  var value = currentSelValue(select);

  var enable = matchesShowHideArray(value, enableArray, disableArray);
  
  for (var i=0; i<targets.length; i++) {
    var cur = targets[i];
    var control = eval("document.fm."+cur[0]);

    // if it's a select, select the default value
    if (!enable && cur[1]) {
      selectOptionByValue(control, cur[1]);
    }
    // disable as appropriate
    control.disabled = !enable;
  }
}


//
// hide/show onchange handlers
//
function setup_hideshow_select(source, showArray, hideArray, targets, skipStyles) {

  // make the targets subsettings
  for (var i=0; i<targets.length; i++) {
    var elm = document.getElementById(targets[i]);
    if (elm) {
      if (!skipStyles) {
	replaceClassName(elm, "", "subsetting-hideshow");
      }
    }
  }

  // setup handler
  var input = eval("document.fm."+source);
  if (input) {

    var onChangeFn = null;
    if (input.nodeName.toLowerCase() == "select") {
      onChangeFn = do_hideshow_select;
    }
    else if (input.nodeName.toLowerCase() == "input") {
      // If this was rendered by settings#checkbox_with_hidden, the
      // checkbox input will be called by this name.
      var chk = eval("document.fm."+source+"_checkbox");
      if (chk != null &&
	  chk.nodeName.toLowerCase() == "input" &&
	  chk.type.toLowerCase() == "checkbox") {
	input = chk;
	onChangeFn = do_hideshow_checkbox;
      }
    }

    if (onChangeFn != null) {
      var old_onchange = input.onchange;
      input.onchange = function() {
	old_onchange.bind(input)();
	onChangeFn(source, showArray, hideArray, targets);
      }
      // for firefox reload behavior that dave and i think is lame [ajm 01.Mar.2005]
      onChangeFn(source, showArray, hideArray, targets);
    }

  }

}

function do_hideshow_select(source, showArray, hideArray, targets) {

  var select = eval("document.fm."+source);
  var value = currentSelValue(select);

  var show = matchesShowHideArray(value, showArray, hideArray);
  
  for (var i=0; i<targets.length; i++) {
    var div = document.getElementById(targets[i]);
    if (div) {
      div.style.display = show ? "block" : "none";
    }
  }

}

function do_hideshow_checkbox(source, showArray, hideArray, targets) {

  // Assuming this was rendered with the
  // settings#checkbox_check_with_hidden function.
  var chk = eval("document.fm."+source+"_checkbox");
  // By definition, treat the value as "true" if the checkbox is
  // checked, and "false" otherwise.  The checkbox input in this case
  // doesn't have a meaningful value (the real value is stored in the
  // hidden input).
  var value = chk.checked ? "true" : "false";

  var show = matchesShowHideArray(value, showArray, hideArray);
  
  for (var i=0; i<targets.length; i++) {
    var div = document.getElementById(targets[i]);
    if (div) {
      div.style.display = show ? "block" : "none";
    }
  }

}

//
// used by hide/show and enable/disable mechanisms
//
function matchesShowHideArray(value, showArray, hideArray) {

  var show = false;
  
  if (showArray && showArray.length > 0) {
    // based on show
    show = false;
    for (var i=0; i<showArray.length; i++) {
      if (value == showArray[i] || value == "def-val-"+showArray[i]) {
	show = true;
	break;
      }
    }
  } else {
    // based on hide
    show = true;
    for (var i=0; i<hideArray.length; i++) {
      if (value == hideArray[i] || value == "def-val-"+hideArray[i]) {
	show = false;
	break;
      }
    }
  }

  return show;
}

var TEMPLATE_DATA = {};

function onTemplateChange(select, option) {}

// 
// FileTemplateSelect functions
//
function template_change(select) {
  var option = select.options[select.selectedIndex];
  var editable = option.className == "editable";

  var name = getName(select);
  var form = getParentByName(select, "FORM");
  var editButton = form[name+"_edit"];
  if (editButton) {
    editButton.disabled = !editable;
  }

  onTemplateChange(select, option);
}

function isDefaultValue(str) {
  return (str && str.length >= 8 && str.substring(0,8) == "def-val-");
}

function isTemplateValue(str) {
  return str.match(new RegExp("\\.template$"));
}

function isEditableValue(str) {
  return TEMPLATE_DATA[str] && TEMPLATE_DATA[str].editable;
}

function template_edit(formElement, editortype, directory) {
  var select = eval("document.fm."+formElement);
  var value = currentSelValue(select);
  if (isDefaultValue(value)) {
    value = select.getAttribute("defval", 0);
  }
  template_dialog(formElement, editortype, directory, value);
}
function template_new(formElement, editortype, directory) {
  template_dialog(formElement, editortype, directory, "");
}
function template_dialog(formElement, editortype, directory, name) {

  var extras = "";
  extras += "&dir="+directory;
  if (name) {
    extras += "&name="+name;
  }

  openDialogEx(editortype, 
	       SKINEDIT_FEATURES,
	       720,
	       725,
	       "",
	       formElement,
	       extras,
	       DIALOG_FLAG_MULTIPLE_WINDOWS);  
}
function template_choose(name) {
  if (currentSelValue(document.fm.name) == "-") {
    document.fm.name.selectedIndex++;
  }
  if (warnIgnoreChanges()) {
    document.fm.fb_submit.value = "Reset";
    document.fm.isDirty.value   = "false";
    submitForm();
  } else {
    selectOptionByValue(document.fm.name, name);
  }
}

/**
 * override this function to validate the template before submitting.
 * if there is a problem, use a javascript alert or confirm and return
 * false to stop the submit.
 * This version validates the saved file name only.
 */
function template_validate() {
  if (!validateSavedFileName()) {
    return false;
  }
  return true;
}

function template_save(disablebutton) {

  if (disablebutton != null) {
    disablebutton.disabled = true;
  }

  if (template_validate()) {
    document.fm.fb_submit.value = "Apply";
    document.fm.operation.value = "save";
    submitForm();
  } else {
    if (disablebutton != null) {
      disablebutton.disabled = false;
    }
  }

}
    
function template_delete_allowed(name, value) {
  return true;
}
function template_delete() {
  var name = currentSel(document.fm.name);
  var value = currentSelValue(document.fm.name);
  if (!isTemplateValue(value) &&
      isEditableValue(value) &&
      template_delete_allowed(name, value) && 
      confirm((new MessageFormat(i18n("template_file_delete_confirmation_message",
				      "Are you sure you want to permanently delete \"{0}\"?\n\n" +
				      "Click OK to delete.\nClick Cancel to return to this page."))).format(name)))
    {
      document.fm.fb_submit.value="Apply";
      document.fm.operation.value="delete";
      submitForm();
    }
}
function template_test(tester) {
  // open the tester window, setting dir= and name=
  var extras = "";
  if (document.fm.dir) {
    extras += "&dir="+document.fm.dir.value;
  }
  extras += "&name="+currentSelValue(document.fm.name);

  var width  = parseInt(ua("userdirtest_window_width",  "340"), 10);
  var height = parseInt(ua("userdirtest_window_height", "600"), 10);
  openDialogEx(tester,
	       ua("userdirtest_window_features") + getSizeAndPosition(width, height),
	       width,
	       height,
	       "",
	       "",
	       extras,
	       DIALOG_FLAG_MULTIPLE_WINDOWS);  
}

// declare this to avoid js errors
var ud_showAccounts_update;

function template_update_opener(selectedname, data) {
  if (window.opener != null && !window.opener.closed && window.opener.template_update && document.fm.dialogname) {
    window.opener.template_update(document.fm.type.value, document.fm.dialogname.value, selectedname, data);
  }  

  // this seems like a random location, but it's called in the footer
  // of the page and we need to update the Delete button.
  if (document.fm.Delete) {
    document.fm.Delete.disabled = (isTemplateValue(currentSelValue(document.fm.name)) ||
				   !isEditableValue(currentSelValue(document.fm.name)));
  }
}

function template_finished(name, data) {
  if (warnIgnoreChanges()) {
    template_update_opener(name, data);
    window.close();
  }
}

function template_update(viewtype, dialogname, selectedname, data) {

  Debug.println("settings.js:template_update(", viewtype, ", ", dialogname, ", ", selectedname, ", and some data");

  var select = eval("document.fm." + dialogname);
  if (select != null) {

    var selectedValue = (select.options.length > 0) ? select.value : null;

    // remove everything but the default
    if (select.options.length > 0 && isDefaultValue(select.options[0].value)) {
      select.options.length = 1;
    }
    else {
      select.options.length = 0;
    }

    var editButton = document.fm[dialogname + "_edit"];

    for (var cname in data) {
      if (cname == "extend") {
	continue;
      }
      var cur = data[cname];
      var index = select.options.length;
      select.options[index] = new Option(cur["displayname"],cur["name"]);
      if (selectedValue == cur["name"]) {
	select.options[index].selected = true;
	if (editButton != null) {
	  editButton.disabled = !cur["editable"];
	}
      }
      if (cur["editable"]) {
	select.options[index].className = "editable";
      }
    }

    var idx = selectIndexOfValue(select, selectedname);
    if (idx != -1 && idx <= select.options.length) {
      select.options[idx].selected = true;
      if (editButton != null) {
	editButton.disabled = !data[selectedname]["editable"];
      }
    }

    onTemplateSelectUpdate(select, selectedname, data);

  }

  onTemplateUpdate(selectedname, data);

}

function onTemplateSelectUpdate(select, selectedname, data) {
  makeDirty();
}

// override this as necessary
function onTemplateUpdate(selectedname, data) { }


function template_chooseproject() {
  if (warnIgnoreChanges()) {
    document.fm.proj.value = currentSelValue(document.fm.template_project).substring(2); // get past the ::
    go('Reset');
  } else {
    selectOption(document.fm.template_project, document.fm.proj.value, -1);
  }
}

function collapse_toggle(id) {
  var elm = document.getElementById(id);
  if (elm) {
    var wasVisible = (elm.style.display != 'none');
    elm.style.display = wasVisible ? 'none' : '';
    var img = document.getElementById(id+".img");
    if (img) {
      img.src = (wasVisible) ? "/images/modern/icons/show.gif" : "/images/modern/icons/hide.gif";
    }
  }
}


function onImageChange(selector, image) {
  if (selector == null) {
    return;
  }

  var val = selector.options[selector.selectedIndex].value;
  if (val.indexOf("def-val-") == 0) {
    val = val.substring(8);
  }
  if (image != null) {
    image.src = val;
  }
}

function uncheckUseDefault(settingName) {
  var useDefaultChk = document.getElementById("_x" + settingName);
  if (useDefaultChk && useDefaultChk.type == "checkbox") {
    useDefaultChk.checked = false;
  }
}


/**
 * An object representing a group select setting, in which the user
 * chooses multiple possible values, with one multiple select
 * representing the chosen values and the other representing the
 * available values.
 *
 * In these functions, <name>_available refers to the available SELECT
 * element ("inactive" list), <name>_available refers to the selected
 * SELECT element ("active" list, and <name> refers to the hidden
 * field that mirrors the selected element list.  [shep 10.Oct.2005]
 *
 * See also com.traction.sdl.admin.settings#groupselect
 */
Settings.GroupSelect = Class.create();
Settings.GroupSelect.prototype = {


  fieldNamespace: null,


  exclusive: false,


  val2description: new Array(),


  errorMessageContainer: null,


  isReadOnly: false,


  initialize: function(fieldNamespace, exclusive, val2description) {
    this.fieldNamespace = fieldNamespace;
    this.exclusive = exclusive;
    this.val2description = val2description;
  },


  init: function(isReadOnly) {
    this.isReadOnly = isReadOnly;
    var newValueInput = document.fm[this.fieldNamespace + "_new"];
    var newValueAddButton = document.fm[this.fieldNamespace + "_addbutton"];
    var removeValueButton = document.fm[this.fieldNamespace + "_removebutton"];
    if (isReadOnly) {
      if (newValueInput) {
	newValueInput.disabled = true;
      }
      if (newValueAddButton) {
	newValueAddButton.disabled = true;
      }
      if (removeValueButton) {
	removeValueButton.disabled = true;
      }
    } else {
      if (newValueInput && newValueAddButton) {
	Events.attach(newValueAddButton, Events.Click, this, this.addNew);
	Events.attach(newValueInput, Events.KeyUp, this, this.onNewFieldKeyUp);
	Events.attach(removeValueButton, Events.Click, this, this.removeSelectedOptions);
	this.errorMessageContainer = document.getElementById(this.fieldNamespace + "_new_value_error");
	this.validateAddNewValueButtonEnabled();
      }
    }
    this.consolidateSelection();
  },


  /**
   * An event handler that invokes the createNewProject function (below)
   * if the Enter key is pressed in a text field that is a member of
   * the Add New Project form.
   */
  onNewFieldKeyUp: function(event) {
    switch(keyCode2(event)) {
      case 13: // enter
      if (!document.fm[this.fieldNamespace + "_addbutton"].disabled) {
	this.addNew();
      }
      return;
      default:
      this.validateAddNewValueButtonEnabled();
    }
  },

  validateAddNewValueButtonEnabled: function() {
    document.fm[this.fieldNamespace + "_addbutton"].disabled = (document.fm[this.fieldNamespace + "_new"].value.trim() == "");
  },

  addNew: function() {

    var newLabelNameField = document.fm[this.fieldNamespace + "_new"];
    var newLabelName = newLabelNameField.value;
    var selectedValuesSel = document.fm[this.fieldNamespace + "_selected"];
    var availableValuesSel = document.fm[this.fieldNamespace + "_available"];

    if (selectIndexOfValue(selectedValuesSel, newLabelName) ||
	selectIndexOfValue(availableValuesSel, newLabelName)) {
      this.addNewError("That value already exists.");
    }
    if (selectIndexOfValue(sel, newLabelName)) {
      this.addNewError("That value already exists.");
    }

    addOption(selectedValuesSel, newLabelName, newLabelName);
    this.clearError();
    this.consolidateSelection();
    this.makeDirty();

  },


  removeSelectedOptions: function() {
    removeSelected(document.fm[this.fieldNamespace + "_selected"]);
    removeSelected(document.fm[this.fieldNamespace + "_available"]);
    this.consolidateSelection();
    this.makeDirty();
  },


  addNewError: function(msg) {
    if (this.errorMessageContainer) {
      show_default(true, this.errorMessageContainer);
      this.errorMessageContainer.innerHTML = msg;
    }
  },


  clearError: function() {
    if (this.errorMessageContainer) {
      show_default(false, this.errorMessageContainer);
      this.errorMessageContainer.innerHTML = "";
    }
  },


  activate: function() {
    if (this.exclusive) {
      moveSelected(document.fm[this.fieldNamespace + "_available"], document.fm[this.fieldNamespace + "_selected"]);
      this.changeDescription(document.fm[this.fieldNamespace + "_available"]);
    } else {
      copySelected(document.fm[this.fieldNamespace + "_available"], document.fm[this.fieldNamespace + "_selected"], true);
    }
    this.changeDescription(document.fm[this.fieldNamespace + "_selected"]);
    this.consolidateSelection();
    this.makeDirty();
  },


  deactivate: function() {
    if (this.exclusive) {
      moveSelected(document.fm[this.fieldNamespace + "_selected"], document.fm[this.fieldNamespace + "_available"]);
      this.changeDescription(document.fm[this.fieldNamespace + "_available"]);
    } else {
      removeSelected(document.fm[this.fieldNamespace + "_selected"]);
    }
    this.changeDescription(document.fm[this.fieldNamespace + "_selected"]);
    this.consolidateSelection();
    this.makeDirty();
  },


  activeup: function() {
    if (selShiftUp(document.fm[this.fieldNamespace + "_selected"])) {
      this.consolidateSelection();
      this.makeDirty();
    }
  },


  activedown: function() {
    if (selShiftDown(document.fm[this.fieldNamespace+"_selected"])) {
      this.consolidateSelection();
      this.makeDirty();
    }
  },


  consolidateSelection: function() {
    var src  = document.fm[this.fieldNamespace+"_selected"];
    var dest = document.fm[this.fieldNamespace];
    dest.value = selectToText(src);
    var unusedDest = document.fm[this.fieldNamespace+"_unused"];
    var unusedSrc = document.fm[this.fieldNamespace+"_available"];
    if (unusedSrc != null && unusedDest != null) {
      unusedDest.value = selectToText(unusedSrc);
    }
  },


  changeDescription: function(sel) {
    var descarea = document.getElementById(sel.name + "_desc");
    if (descarea == null) {
      return;
    }
    var desc = "";
    for (var i = 0; i < sel.options.length; i ++) {
      if (sel.options[i].selected) {
	if (desc != "") {
	  desc += "\n\n";
	}
	var curdesc = this.val2description[sel.options[i].value];
	if (curdesc == null || curdesc == "") {
	  curdesc = "(" + i18n("no_description_available", "no description available") + ")";
	}
	desc += "- " + curdesc;
      }
    }
    descarea.value = desc;
  },


  makeDirty: function() {
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  }


};

function checkAndEnableIfNotEmpty(value, checkbox) {
  if (value != null && trim(value) != "") {
    checkbox.checked = true;
    checkbox.disabled = false;
  }
}

var MARKER = "marker";

function onFontChange(ctrl) {
  var val = ctrl.value;
  if (val.length >= MARKER.length &&
      val.substring(val.length - MARKER.length) == MARKER) {
    ctrl.options[ctrl.selectedIndex + 1].selected = true;
  }
}

function onSkinChange(ctrl) {
}


function validateSavedFileName() {

  var displayName = document.fm.dispname.value;

  if (trim(displayName) == "") {
    alert(i18n("template_no_save_name_alert_message",
	       "Please specify a Name before clicking Save."));
    document.fm.dispname.focus();
    return false;
  }

  // Check for duplicate file names using the template data, not the
  // information stored in the file SELECT element.  And check the
  // shared and non-shared templates separately, when applicable.
  // [shep 20.Jun.2006]

  var ownershipSupported = (document.fm.shared != null);

  var matchShared = ownershipSupported ? document.fm.shared.checked : false;

  // if the display name hasn't changed and the shared status hasn't
  // changed, don't bother to validate the display name
  var displayNameChanged  = (document.fm.old_dispname == null ||
			     displayName != document.fm.old_dispname.value);
  var sharedStatusChanged = (ownershipSupported && ((document.fm.old_shared.value == "true") != matchShared));

  if (displayNameChanged || sharedStatusChanged) {

    for (var tName in TEMPLATE_DATA) {

      var t = TEMPLATE_DATA[tName];
      if ((!ownershipSupported || t.shared == matchShared) &&
	  t.displayname == displayName) {

	if (t.editable) {
	  var confirmationMessage = i18n("template_file_overwrite_confirmation_message",
					 "A file named '{0}' already exists.  Do you wish to overwrite that file?\n\n" +
					 "Click OK to overwrite.\nClick Cancel to return to this page.");
	  confirmationMessage = (new MessageFormat(confirmationMessage)).format(displayName);
	  var ret = confirm(confirmationMessage);
	  if (!ret) {
	    document.fm.dispname.focus();
	    return false;
	  }
	} else {
	  alert((new MessageFormat(i18n("template_file_name_uneditable_alert_message",
					"The name \"{0}\" is already in use by a read-only file.  " +
					"Please choose another name.")).format(displayName)));
	  document.fm.dispname.focus();
	  return false;
	}

	break;

      }

    }

    for (var tName in TEMPLATE_DATA) {

      var t = TEMPLATE_DATA[tName];
      if (t.shared != matchShared &&
	  t.displayname == displayName) {
	var confirmationMessage;
	if (!matchShared) {
	  confirmationMessage = i18n("template_file_same_name_as_shared_file_confirmation_message",
				     "The name \"{0}\" is already in use by a shared file.  " +
				     "You may save a private file with the same name, but it is not recommended.  " +
				     "Do you wish to save your private file with this name?\n\n" +
				     "Click OK to save anyway.\nClick Cancel to return to this page.");
	} else {
	  confirmationMessage = i18n("template_file_same_name_as_private_file_confirmation_message",
				     "The name \"{0}\" is already in use by a private file.  " +
				     "You may save a shared file with the same name, but it is not recommended.  " +
				     "Do you wish to save your shared file with this name?\n\n" +
				     "Click OK to save anyway.\nClick Cancel to return to this page.");
	}
	confirmationMessage = (new MessageFormat(confirmationMessage)).format(displayName);
	var ret = confirm(confirmationMessage);
	if (!ret) {
	  document.fm.dispname.focus();
	  return false;
	}

      }

    }

  } else if (document.fm.editable.value == "false") {
    alert((new MessageFormat(i18n("template_file_name_uneditable_alert_message",
				  "The name \"{0}\" is already in use by a read-only file.  " +
				  "Please choose another name.")).format(displayName)));
    document.fm.dispname.focus();
    return false;
  }

  return true;

}


/**
 * An object representing a list of user digest configurations (time
 * to send, email address to send to, etc.).  See also
 * com.traction.sdl.admin.settings#digestlist.user
 */
Settings.UserDigestList = Class.create();
Settings.UserDigestList.prototype = {


  digestStates: new Array(),


  digestRowContainer: null,


  fieldNamespace: "",


  emailAddresses: null,


  preferredEmailAddress: null,


  field: null,


  isReadOnly: false,


  initialize: function(fieldNamespace, emailAddresses, preferredEmailAddress, serverDigestStyle) {
    this.fieldNamespace = fieldNamespace;
    this.emailAddresses = emailAddresses;
    this.preferredEmailAddress = preferredEmailAddress;
    this.serverDigestStyle = serverDigestStyle;
  },


  init: function() {
    this.initDigestRows();
  },


  setField: function(field, isReadOnly) {
    this.field = field;
    this.isReadOnly = isReadOnly;
    if (this.preferredEmailAddress != null && !isReadOnly) {
      this.field.form[this.fieldNamespace + "_add"].disabled = false;
    }
  },


  initDigestRows: function() {
    this.digestRowContainer = document.getElementById(this.fieldNamespace + "_digestrows");
    this.addEmptyRow(this.digestStates.length == 0);
    for (var i = 0; i < this.digestStates.length; i ++) {
      this.addDigestRow(this.digestStates[i], i);
    }
  },


  addDigestRow: function(digestState, digestIndex) {

    var digestRow = document.createElement("TR");
    digestRow.id = this.fieldNamespace + "_" + digestIndex;

    var removeCell = document.createElement("TD");
    removeCell.className = "remove";
    var removeButton = document.createElement("INPUT")
    removeButton.type = "button";
    removeButton.name = this.fieldNamespace + "_" + digestIndex + "_remove";
    removeButton.value = i18n("Remove", "Remove");
    if (this.isReadOnly) {
      removeButton.disabled = true;
    }
    removeCell.appendChild(removeButton);

    var freqCell = document.createElement("TD");
    var freqSelect = document.createElement("SELECT");
    freqSelect.name = this.fieldNamespace + "_" + digestIndex + "_frequency";
    Settings.UserDigestList.addDigestFrequencyOptions(freqSelect, digestState.frequencyCode);
    freqCell.appendChild(freqSelect);

    var timeCell = document.createElement("TD");
    var hourSelect = document.createElement("SELECT");
    hourSelect.name = this.fieldNamespace + "_" + digestIndex + "_hours";
    Settings.UserDigestList.addHourOptions(hourSelect, digestState.getHour());
    var colonSpan = document.createElement("SPAN");
    colonSpan.innerHTML = " : ";
    var minuteSelect = document.createElement("SELECT");
    minuteSelect.name = this.fieldNamespace + "_" + digestIndex + "_minutes";
    Settings.UserDigestList.addMinuteOptions(minuteSelect, digestState.minute);
    var ampmSelect = document.createElement("SELECT");
    ampmSelect.name = this.fieldNamespace + "_" + digestIndex + "_ampm";
    Settings.UserDigestList.addAMPMOptions(ampmSelect, digestState.getAMPM());
    timeCell.appendChild(hourSelect);
    timeCell.appendChild(colonSpan);
    timeCell.appendChild(minuteSelect);
    timeCell.appendChild(ampmSelect);

    var emailCell = document.createElement("TD");
    var emailSelect = document.createElement("SELECT");
    emailSelect.name = this.fieldNamespace + "_" + digestIndex + "_email";
    Settings.UserDigestList.addEmailOptions(emailSelect, this.emailAddresses, digestState.email);
    emailCell.appendChild(emailSelect);

    var styleCell = document.createElement("TD");
    var styleSelect = document.createElement("SELECT");
    Settings.UserDigestList.addStyleOptions(styleSelect, digestState.styleCode);
    styleSelect.name = this.fieldNamespace + "_" + digestIndex + "_style";
    styleCell.appendChild(styleSelect);

    digestRow.appendChild(removeCell);
    digestRow.appendChild(freqCell);
    digestRow.appendChild(timeCell);
    digestRow.appendChild(emailCell);
    digestRow.appendChild(styleCell);

    this.digestRowContainer.appendChild(digestRow);

    var digestList = this;
    var propChangeListener = function(e) {
      digestList.onPropertyChange(digestIndex,
				  { frequency: freqSelect,
				    hour: hourSelect,
				    minute: minuteSelect,
				    ampm: ampmSelect,
				    email: emailSelect,
				    style: styleSelect });
    };
    var digestRemoveListener = function(e) {
      digestList.onRemoveClick(digestIndex, digestRow);
    };

    // Add these after the DOM has been updated; Safari doesn't do it
    // right otherwise.  [shep 15.Sep.2007]
    setTimeout(function() {
		 Debug.println("Adding handlers for digest row ", digestList.fieldNamespace, " : ", digestIndex);
		 Events.attach(freqSelect,   Events.Change, digestList, propChangeListener);
		 Events.attach(hourSelect,   Events.Change, digestList, propChangeListener);
		 Events.attach(minuteSelect, Events.Change, digestList, propChangeListener);
		 Events.attach(ampmSelect,   Events.Change, digestList, propChangeListener);
		 Events.attach(emailSelect,  Events.Change, digestList, propChangeListener);
		 Events.attach(styleSelect,  Events.Change, digestList, propChangeListener);
		 Events.attach(removeButton, Events.Click,  digestList, digestRemoveListener);
	       }, 100);

  },


  addEmptyRow: function(show) {

    var emptyRow = document.createElement("TR");
    emptyRow.id = this.fieldNamespace + "_empty";
    emptyRow.className = "none";
    emptyRow.style.display = show ? "" : "none";

    var emptyCell = document.createElement("TD");
    emptyCell.className = "remove";
    emptyCell.appendChild(document.createTextNode(" "));
    emptyRow.appendChild(emptyCell);

    emptyCell = document.createElement("TD");
    emptyCell.setAttribute("colspan", "4");
    emptyCell.colSpan = 4;
    emptyCell.appendChild(document.createTextNode("(" + i18n("None", "None") + ")"));
    emptyRow.appendChild(emptyCell);

    this.digestRowContainer.appendChild(emptyRow);

  },


  toggleEmptyRowDisplay: function(show) {
    document.getElementById(this.fieldNamespace + "_empty").style.display = show ? "" : "none";
  },


  getDigestSerialization: function() {
    var s = "";
    var next = 0;
    for (var num = 0; num < this.digestStates.length; num ++) {
      if (this.digestStates[num].enabled) {
	if (next > 0) {
	  s += ",";
	}
	s += this.digestStates[num].serialize(next);
	next ++;
      }
    }
    return s;
  },


  onPropertyChange: function(digestIndex, fields) {

    Debug.println("onPropertyChange for ", digestIndex, "...");

    var digestState = this.digestStates[digestIndex];

    var hour = parseInt(fields.hour.options[fields.hour.selectedIndex].value, 10);
    // 12 -> 0
    if (hour == 12) {
      hour = 0;
    }
    var am = (fields.ampm.options[fields.ampm.selectedIndex].value == "AM");
    // if pm, 0-11 -> 12-23
    if (!am) {
      hour += 12;
    }
    digestState.hour = hour;

    digestState.minute = parseInt(fields.minute.options[fields.minute.selectedIndex].value, 10);

    digestState.frequencyCode = parseInt(fields.frequency.options[fields.frequency.selectedIndex].value, 10);

    digestState.email = fields.email.options[fields.email.selectedIndex].value;

    digestState.styleCode = parseInt(fields.style.options[fields.style.selectedIndex].value, 10);

    Debug.println("Finished updating properties for ", digestIndex, ": ", digestState.serialize(""));
    this.makeDirty();

  },


  onRemoveClick: function(digestIndex, digestRow) {
    // mark it disabled
    this.digestStates[digestIndex].enabled = false;
    // hide the row
    show_TR(false, digestRow);
    this.makeDirty();
  },


  onAddClick: function() {
    var newDigestState = new Traction.UserDigestState(12, 0, 7, true, this.preferredEmailAddress, this.serverDigestStyle);
    var newDigestIndex = this.digestStates.length;
    this.digestStates[newDigestIndex] = newDigestState;
    this.addDigestRow(newDigestState, newDigestIndex);
    this.makeDirty();
  },


  makeDirty: function() {
    this.field.value = this.getDigestSerialization();
    Debug.println("New field value: ", this.field.name, " = ", this.field.value);
    var nonRemoved = 0;
    for (var i = 0; i < this.digestStates.length; i ++) {
      if (this.digestStates[i].enabled) {
	nonRemoved ++;
      }
    }
    this.toggleEmptyRowDisplay(nonRemoved == 0);
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  }


};


/**
 * i18n("digest_frequency_0");
 * i18n("digest_frequency_1");
 * i18n("digest_frequency_2");
 * i18n("digest_frequency_3");
 * i18n("digest_frequency_4");
 * i18n("digest_frequency_5");
 * i18n("digest_frequency_6");
 * i18n("digest_frequency_7");
 * i18n("digest_frequency_8");
 * i18n("digest_frequency_9");
 */
Settings.UserDigestList.FREQUENCY_CODES = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ];

Settings.UserDigestList.addDigestFrequencyOptions = function(freqSelector, selectedCode) {
  for (var i = 0; i < Settings.UserDigestList.FREQUENCY_CODES.length; i ++) {
    var opt = new Option(i18n("digest_frequency_" + Settings.UserDigestList.FREQUENCY_CODES[i], ""),
			      new String(Settings.UserDigestList.FREQUENCY_CODES[i]));
    freqSelector.options[freqSelector.options.length] = opt;
    if (selectedCode == i) {
      opt.selected = true;
    }
  }
};


Settings.UserDigestList.HOURS = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ];

Settings.UserDigestList.addHourOptions = function(hourSelector, selectedHour) {
  for (var i = 0; i < Settings.UserDigestList.HOURS.length; i ++) {
    var opt = new Option(new String(Settings.UserDigestList.HOURS[i]),
			 new String(Settings.UserDigestList.HOURS[i]));
    hourSelector.options[hourSelector.options.length] = opt;
    if (selectedHour == Settings.UserDigestList.HOURS[i]) {
      opt.selected = true;
    }
  }
};

Settings.UserDigestList.MINUTES = [ 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 ];

Settings.UserDigestList.addMinuteOptions = function(minuteSelector, selectedMinute) {
  for (var i = 0; i < Settings.UserDigestList.MINUTES.length; i ++) {
    var opt = new Option(((Settings.UserDigestList.MINUTES[i] < 10) ? "0" : "") + new String(Settings.UserDigestList.MINUTES[i]),
			 new String(Settings.UserDigestList.MINUTES[i]));
    minuteSelector.options[minuteSelector.options.length] = opt;
    if (selectedMinute == Settings.UserDigestList.MINUTES[i]) {
      opt.selected = true;
    }
  }
};

Settings.UserDigestList.AMPM = [ "AM", "PM" ];

Settings.UserDigestList.addAMPMOptions = function(ampmSelector, selectedAMPM) {
  for (var i = 0; i < Settings.UserDigestList.AMPM.length; i ++) {
    var opt = new Option(Settings.UserDigestList.AMPM[i],
			 Settings.UserDigestList.AMPM[i]);
    ampmSelector.options[ampmSelector.options.length] = opt;
    if (selectedAMPM == Settings.UserDigestList.AMPM[i]) {
      opt.selected = true;
    }
  }
};

Settings.UserDigestList.addEmailOptions = function(emailSelector, emailAddresses, selectedEmailAddress) {
  for (var i = 0; i < emailAddresses.length; i ++) {
    var opt = new Option(emailAddresses[i],
			 emailAddresses[i]);
    emailSelector.options[emailSelector.options.length] = opt;
    if (emailAddresses[i] == selectedEmailAddress) {
      opt.selected = true;
    }
  }
};

Settings.UserDigestList.STYLE_CODES = [ 0, 1, 2 ];
/**
 * i18n("digest_rendering_style_0");
 * i18n("digest_rendering_style_1");
 * i18n("digest_rendering_style_2");
 */

Settings.UserDigestList.addStyleOptions = function(styleSelector, selectedStyleCode) {
  for (var i = 0; i < Settings.UserDigestList.STYLE_CODES.length; i ++) {
    var opt = new Option(i18n("digest_rendering_style_" + Settings.UserDigestList.STYLE_CODES[i], ""),
			 Settings.UserDigestList.STYLE_CODES[i]);
    styleSelector.options[styleSelector.options.length] = opt;
    if (selectedStyleCode == Settings.UserDigestList.STYLE_CODES[i]) {
      opt.selected = true;
    }
  }
};


/**
 * An object representing a list of server digest configurations (time
 * to send, email address to send to, etc.).  See also
 * com.traction.sdl.admin.settings#digestlist.server
 */
Settings.ServerDigestList = Class.create();
Settings.ServerDigestList.prototype = {


  digestStates: new Array(),


  digestRowContainer: null,


  fieldNamespace: "",


  field: null,


  initialize: function(fieldNamespace) {
    this.fieldNamespace = fieldNamespace;
  },


  init: function() {
    this.initDigestRows();
  },


  setField: function(field, isReadOnly) {
    this.field = field;
    this.isReadOnly = isReadOnly;
    if (!isReadOnly) {
      this.field.form[this.fieldNamespace + "_add"].disabled = false;
    }
  },


  initDigestRows: function() {
    this.digestRowContainer = document.getElementById(this.fieldNamespace + "_digestrows");
    this.addEmptyRow(this.digestStates.length == 0);
    for (var i = 0; i < this.digestStates.length; i ++) {
      this.addDigestRow(this.digestStates[i], i);
    }
  },


  addDigestRow: function(digestState, digestIndex) {

    var digestRow = document.createElement("TR");
    digestRow.id = this.fieldNamespace + "_" + digestIndex;

    var removeCell = document.createElement("TD");
    removeCell.className = "remove";
    var removeButton = document.createElement("INPUT")
    removeButton.type = "button";
    removeButton.name = this.fieldNamespace + "_" + digestIndex + "_remove";
    removeButton.value = i18n("Remove", "Remove");
    if (this.isReadOnly) {
      removeButton.disabled = true;
    }
    removeCell.appendChild(removeButton);

    var freqCell = document.createElement("TD");
    var freqSelect = document.createElement("SELECT");
    freqSelect.name = this.fieldNamespace + "_" + digestIndex + "_frequency";
    Settings.UserDigestList.addDigestFrequencyOptions(freqSelect, digestState.frequencyCode);
    freqCell.appendChild(freqSelect);

    var timeCell = document.createElement("TD");
    var hourSelect = document.createElement("SELECT");
    hourSelect.name = this.fieldNamespace + "_" + digestIndex + "_hours";
    Settings.UserDigestList.addHourOptions(hourSelect, digestState.getHour());
    var colonSpan = document.createElement("SPAN");
    colonSpan.innerHTML = " : ";
    var minuteSelect = document.createElement("SELECT");
    minuteSelect.name = this.fieldNamespace + "_" + digestIndex + "_minutes";
    Settings.UserDigestList.addMinuteOptions(minuteSelect, digestState.minute);
    var ampmSelect = document.createElement("SELECT");
    ampmSelect.name = this.fieldNamespace + "_" + digestIndex + "_ampm";
    Settings.UserDigestList.addAMPMOptions(ampmSelect, digestState.getAMPM());
    timeCell.appendChild(hourSelect);
    timeCell.appendChild(colonSpan);
    timeCell.appendChild(minuteSelect);
    timeCell.appendChild(ampmSelect);

    digestRow.appendChild(removeCell);
    digestRow.appendChild(freqCell);
    digestRow.appendChild(timeCell);

    this.digestRowContainer.appendChild(digestRow);

    var digestList = this;
    var propChangeListener = function(e) {
      digestList.onPropertyChange(digestIndex,
				  { frequency: freqSelect,
				    hour: hourSelect,
				    minute: minuteSelect,
				    ampm: ampmSelect });
    };
    var digestRemoveListener = function(e) {
      digestList.onRemoveClick(digestIndex, digestRow);
    };

    // Add these after the DOM has been updated; Safari doesn't do it
    // right otherwise.  [shep 15.Sep.2007]
    setTimeout(function() {
		 Debug.println("Adding handlers for digest row ", digestList.fieldNamespace, " : ", digestIndex);
		 Events.attach(freqSelect,   Events.Change, digestList, propChangeListener);
		 Events.attach(hourSelect,   Events.Change, digestList, propChangeListener);
		 Events.attach(minuteSelect, Events.Change, digestList, propChangeListener);
		 Events.attach(ampmSelect,   Events.Change, digestList, propChangeListener);
		 Events.attach(removeButton, Events.Click,  digestList, digestRemoveListener);
	       }, 100);

  },


  addEmptyRow: function(show) {

    var emptyRow = document.createElement("TR");
    emptyRow.id = this.fieldNamespace + "_empty";
    emptyRow.className = "none";
    emptyRow.style.display = show ? "" : "none";

    var emptyCell = document.createElement("TD");
    emptyCell.className = "remove";
    emptyCell.appendChild(document.createTextNode(" "));
    emptyRow.appendChild(emptyCell);

    emptyCell = document.createElement("TD");
    emptyCell.setAttribute("colspan", "2");
    emptyCell.colSpan = 2;
    emptyCell.appendChild(document.createTextNode("(" + i18n("None", "None") + ")"));
    emptyRow.appendChild(emptyCell);

    this.digestRowContainer.appendChild(emptyRow);

  },


  toggleEmptyRowDisplay: function(show) {
    document.getElementById(this.fieldNamespace + "_empty").style.display = show ? "" : "none";
  },


  getDigestSerialization: function() {
    var s = "";
    var next = 0;
    for (var num = 0; num < this.digestStates.length; num ++) {
      if (this.digestStates[num].enabled) {
	if (next > 0) {
	  s += ",";
	}
	s += this.digestStates[num].serialize(next);
	next ++;
      }
    }
    return s;
  },


  onPropertyChange: function(digestIndex, fields) {

    Debug.println("onPropertyChange for ", digestIndex, "...");

    var digestState = this.digestStates[digestIndex];

    var hour = parseInt(fields.hour.options[fields.hour.selectedIndex].value, 10);
    // 12 -> 0
    if (hour == 12) {
      hour = 0;
    }
    var am = (fields.ampm.options[fields.ampm.selectedIndex].value == "AM");
    // if pm, 0-11 -> 12-23
    if (!am) {
      hour += 12;
    }
    digestState.hour = hour;

    digestState.minute = parseInt(fields.minute.options[fields.minute.selectedIndex].value, 10);

    digestState.frequencyCode = parseInt(fields.frequency.options[fields.frequency.selectedIndex].value, 10);

    Debug.println("Finished updating properties for ", digestIndex, ": ", digestState.serialize(""));
    this.makeDirty();

  },


  onRemoveClick: function(digestIndex, digestRow) {
    // mark it disabled
    this.digestStates[digestIndex].enabled = false;
    // hide the row
    show_TR(false, digestRow);
    this.makeDirty();
  },


  onAddClick: function() {
    var newDigestState = new Traction.DigestState(12, 0, 7, true);
    var newDigestIndex = this.digestStates.length;
    this.digestStates[newDigestIndex] = newDigestState;
    this.addDigestRow(newDigestState, newDigestIndex);
    this.makeDirty();
  },


  makeDirty: function() {
    this.field.value = this.getDigestSerialization();
    Debug.println("New field value: ", this.field.name, " = ", this.field.value);
    var nonRemoved = 0;
    for (var i = 0; i < this.digestStates.length; i ++) {
      if (this.digestStates[i].enabled) {
	nonRemoved ++;
      }
    }
    this.toggleEmptyRowDisplay(nonRemoved == 0);
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  }


};


Settings.Subscriptions = {


  NAMESPACES: [ "user", "user_defaults_u" ]


};


Settings.Subscriptions.EmailNotifier2 = {


  warningMessageContainer: null,


  filterActivationFields: new Array(),


  setup: function() {

    this.warningMessageContainer = document.getElementById("subscription_conflict_warning");

    for (var i = 0; i < Settings.Subscriptions.NAMESPACES.length; i ++) {
      var ns = Settings.Subscriptions.NAMESPACES[i];
      if (document.fm[ns + "_emailnotifier2_use_sections"]) {
	var useSectionsField = document.fm[ns + "_emailnotifier2_use_sections"];
	var useEventsField = document.fm[ns + "_emailnotifier2_use_events"];
	Events.attach(useSectionsField, Events.Change, this, this.toggleWarningDisplay);
	Events.attach(useEventsField, Events.Change, this, this.toggleWarningDisplay);
	this.filterActivationFields.push(useSectionsField);
	this.filterActivationFields.push(useEventsField);
	setup_hideshow_select(ns + "_emailnotifier2_use_sections",
			      ['true'],
			      [],
			      [ns + "_emailnotifier2_sections"],
			      false);
	setup_hideshow_select(ns + "_emailnotifier2_use_events",
			      ['true'],
			      [],
			      [ns + "_emailnotifier2_events"],
			      false);
      }
    }

    this.toggleWarningDisplay();

  },


  toggleWarningDisplay: function() {

    if (!this.warningMessageContainer) {
      return; // nothing to show
    }

    var yes = false;
    var duplicateYes = false;

    for (var i = 0; i < this.filterActivationFields.length; i ++) {
      var val = this.filterActivationFields[i].options[this.filterActivationFields[i].selectedIndex].value;
      if (val == "true" || val == "def-val-true") {
	if (yes) {
	  duplicateYes = true;
	  break;
	} else {
	  yes = true;
	}
      }
    }

    show_default(duplicateYes, this.warningMessageContainer);

  }


};


/**
 * An object representing a list of section configurations.
 * See also com.traction.sdl.admin.settings#sectionlist
 */
Settings.SectionList = Class.create();
Settings.SectionList.prototype = {


  fieldNamespace: "",


  readOnly: false,


  rows: new Array(),


  /**
   * A YAHOO.traction.SectionsLabelList
   */
  labelList: null,


  /**
   * The index into the sectionStates list of the malformed section
   * (if one is present).
   */
  badSection: -1,


  /**
   * "frontpage", "newspage", "digest", etc.
   */
  listType: null,


  /**
   * The project in which the section is scoped, if any (e.g., if the
   * section list type is "newspage", the name of the project whose
   * newspage is being configured).
   */
  projectName: null,


  /**
   * An indication of whether this SectionList setting represents a
   * defaults setting (the server default for a user's digest section
   * list, for example), versus a final setting (a user's digest
   * section list, for example).
   */
  defaults: false,


  /**
   * Indicates the display mode which is used for filtering out what
   * section editing controls are available (because in certain
   * contexts, some controls are not applicable).
   */
  displayMode: 1, // 1 == normal view


  /**
   * The FORM element that is used to launch the sections view to
   * preview the selected sections in this section list.
   */
  previewForm: null,


  /**
   * The names of the properties in the serialization.
   */
  keys: new Array(),


  /**
   * A list of lists, each of which represents the state one of the
   * sections in this section list.
   */
  sectionStates: new Array(),


  /**
   * A reference to the DOM element representing the INPUT whose value
   * stores the serialized encoding of the SectionList.
   */
  dataField: null,


  /**
   * A reference to the DOM element representing the INPUT whose value
   * stores the action to be performed on the SectionList data, if any
   * (e.g., copy sections from template).
   */
  actionField: null,


  /**
   * A temporary reference to the existing closeInsertLabelDialog
   * function, so that function can be re-bound to that name after the
   * custom version, the SectionList object instance function
   * onChooseLabels defined below (which is dynamically bound to that
   * name when the label dialog is opened), is called.
   */
  __closeInsertLabelDialog: null,


  /**
   * A temporary reference to the existing
   * closeModifyCollectionPortletDialog function, so that function can
   * be re-bound to that name after the custom version, the
   * SectionList object instance function onChooseCollection defined
   * below (which is dynamically bound to that name when the label
   * dialog is opened), is called.
   */
  __closeModifyCollectionPortletDialog: null,


  /**
   * A temporary reference to the existing closeSubsectionsDialog
   * function, so that function can be re-bound to that name after the
   * custom version, the SectionList object instance function
   * onEditSubsections defined below (which is dynamically bound to
   * that name when the label dialog is opened), is called.
   */
  __closeSubsectionsDialog: null,


  /**
   * The index of the section in the section list (as represented by
   * this object) of the section currently displayed for editing, if
   * any.
   */
  currentIndex: -1,


  /**
   * Map from template name to template description.
   */
  templatename2templatedesc: new Array(),


  /**
   * SectionList constructor.
   */
  initialize: function(fieldNamespace, readOnly) {
    this.fieldNamespace = fieldNamespace;
    this.readOnly = readOnly;
  },


  /**
   * Function called after the page is finished loading.
   */
  init: function() {

    this.rows = new Array();
    var idPrefix = this.fieldNamespace + "_" + Settings.SectionList.ROW_ID_PREFIX + "_";
    for (var i = 0; i < Settings.SectionList.ROW_IDS.length; i ++) {
      this.rows[i] = document.getElementById(idPrefix + Settings.SectionList.ROW_IDS[i]);
    }

    this.editTable = document.getElementById(this.fieldNamespace + "_sections_edittable");
    this.editRow = document.getElementById(this.fieldNamespace + "_sections_editrow");
    this.chooseRow = document.getElementById(this.fieldNamespace + "_sections_chooserow");

    this.updateSerialization();
    show_default(false, document.getElementById(this.fieldNamespace + "_placeholder"));
    show_TABLE(true, document.getElementById(this.fieldNamespace + "_editor"));

  },


  /**
   * Updates the SectionList's serialization in the corresponding
   * hidden INPUT form field and invokes the makeDirty function
   * defined in this page, if that function is defined.
   */
  makeDirty: function(name) {
    this.synchronize(name);
    Debug.println("New section data field value: ", this.dataField.name, " = ", this.dataField.value);
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  },


  /**
   * Changes the displayed name of the collection that has been set
   * for a collection driven section.
   */
  setCollection: function(val) {
    try {
      var elm = document.getElementById(this.fieldNamespace + "_cur_collection");
      elm.innerHTML = val;
    } catch (xcp) {}
  },


  /**
   * Changes the displayed number of subsections reported for the
   * current section.
   */
  setSubsectionsCount: function(val) {
    try {
      var elm = document.getElementById(this.fieldNamespace + "_cur_subsections_count");
      elm.innerHTML = val;
    } catch (xcp) {}  
  },

  /**
   * Returns the number of sections. editsections needs this to return
   * to the main edit form. [ajm 21.Jan.2008]
   */
  getCount: function() {
    return this.sectionStates.length;
  },

  /**
   * Event handler called when the state of the "Use default value"
   * checkbox is changed.  The new behavior for section list settings
   * is not to hide the section editing interface; and, if the
   * checkbox is changed to the checked state, to show a warning
   * message next to the checkbox label explaining that the displayed
   * section data will not be stored.
   */
  onUseDefaultToggle: function(on) {
    document.fm[this.fieldNamespace + "_usedefault"].value = (on ? "true" : "false");
    this.toggleDisplayUseDefaultSectionsWarning(on);
    this.makeDirty("usedefault", false);
  },


  toggleDisplayUseDefaultSectionsWarning: function(on) {
    show_default(on, document.getElementById(this.fieldNamespace + "_sectionedit_usedefault_warning"));
  },


  /**
   * Returns the appropriate mask for which rows to show for the given
   * section type.
   */
  getShowRowsForType: function(type) {
    var ret = Settings.SectionList.SECTIONTYPE2SHOWROWS[type];
    if (!ret) {
      ret = Settings.SectionList.SECTIONTYPE2SHOWROWS["defaultType"];
    }
    return ret;
  },


  /**
   * Event handler called when the state of the section type selector
   * is changed by the user (either by selecting a new option in the
   * type list or by creating a section from a URL).
   */
  onChangeType: function() {
    var selectedOpt = this.fixTypeSelected();
    this.fixDisplayedPropertiesForType(selectedOpt);
    this.setDefaultStatusFilters(selectedOpt.value);
    // sorting
    this.setSortOptions(true);
  },


  fixTypeSelected: function() {

    var typeSelector = document.fm[this.fieldNamespace + "_cur_type"];
    var opt = typeSelector[typeSelector.selectedIndex];
    // If separator is selected, select the next non-separator
    // option. [shep 13.Feb.2008]
    if (opt.value == "") {
      typeSelector.options[typeSelector.selectedIndex + 1].selected = true;
      opt = typeSelector[typeSelector.selectedIndex];
    }

    return opt;

  },


  setDefaultStatusFilters: function(typeVal) {

    var statusProps = Settings.SectionList.SECTIONTYPE2STATUSPROPS[typeVal];
    if (statusProps) {
      for (var i = 0; i < 5; i ++) {
	var iStr = new String(i);
	var enabled = (statusProps.disabled.indexOf(iStr) == -1);
	var checked = (statusProps.checked.indexOf(iStr) != -1);
	this.setStatusEnabledAndChecked(i, enabled, checked);
      }
    } else {
      for (var i = 0; i < 5; i ++) {
	this.setStatusEnabledAndChecked(i, true, (i != 4));
      }
    }

  },


  fixStatusRequirements: function(typeVal) {

    var statusProps = Settings.SectionList.SECTIONTYPE2STATUSPROPS[typeVal];
    if (statusProps) {
      for (var i = 0; i < 5; i ++) {
	var iStr = new String(i);
	var enabled = (statusProps.disabled.indexOf(iStr) == -1);
	if (!enabled) {
	  var checked = (statusProps.checked.indexOf(iStr) != -1);
	  this.setStatusEnabledAndChecked(i, enabled, checked);
	} else {
	  this.setStatusEnabled(i, true);
	}
      }
    } else {
      for (var i = 0; i < 5; i ++) {
	this.setStatusEnabled(i, true);
      }
    }

  },


  showRow: function(rowID) {
    return !Settings.SectionList.DISPLAYMODE2HIDEROWS[this.displayMode][rowID];
  },


  fixDisplayedPropertiesForType: function(selectedTypeOpt) {

    try {

      var val = selectedTypeOpt.value;
      var sectionClass = selectedTypeOpt.className;

      var mask = this.getShowRowsForType(val);

      for (var i = 0; i < this.rows.length; i ++) {
	if (this.rows[i]) {
	  var rowID = Settings.SectionList.ROW_IDS[i];
	  // show the row if the section type wants to show it (mask[i])
	  // and the current display mode wants it.
	  show_TR(mask[rowID] && this.showRow(rowID),
		  this.rows[i]);
	}
      }

      var isPageNameSection = ((sectionClass == "nameoperation") || (sectionClass == "name"));
      for (var i = 0; i < Settings.SectionList.PAGENAME_SECTIONS_HIDE_SHOW_CHECKBOXES.length; i ++) {
	show_default(!isPageNameSection, document.getElementById(this.fieldNamespace + "_sectionshow" + Settings.SectionList.PAGENAME_SECTIONS_HIDE_SHOW_CHECKBOXES[i]));
	show_default(!isPageNameSection, document.getElementById(this.fieldNamespace + "_show_label"  + Settings.SectionList.PAGENAME_SECTIONS_HIDE_SHOW_CHECKBOXES[i]));
      }

      var statusProps = Settings.SectionList.SECTIONTYPE2STATUSPROPS[val];
      if (statusProps) {
 	for (var i = 0; i < 5; i ++) {
 	  var iStr = new String(i);
	  var enabled = (statusProps.disabled.indexOf(iStr) == -1);
	  this.setStatusEnabled(i, enabled);
 	}
      } else {
 	for (var i = 0; i < 5; i ++) {
	  this.setStatusEnabled(i, true);
 	}
      }

      var vol = document.fm[this.fieldNamespace + "_cur_size"].value;
      if (isPageNameSection && vol != "1" && vol != "2") {
	selectOptionByValue(document.fm[this.fieldNamespace + "_cur_size"], "2");
      }

    } catch (xcp) {
      // hmmm, it would be nice if we had some way to handle this kind
      // of thing and let the user report the bug.  [shep 03.Feb.2009]
      if (debugging) {
	alert(xcp);
      }
    }

  },


  getStatusCheckbox: function(i) {
    return document.getElementById(this.fieldNamespace + "_sectionstatus" + i);
  },


  setStatusEnabledAndChecked: function(i, enabled, checked) {
    var checkBox = this.getStatusCheckbox(i);
    checkBox.checked = checked;
    checkBox.disabled = !enabled || this.readOnly;    
    var label = document.getElementById(this.fieldNamespace + "_sectionstatuslabel" + i);
    label.style.color = (enabled) ? "" : "#999";
  },


  setStatusEnabled: function(i, enabled) {
    var checkBox = this.getStatusCheckbox(i);
    checkBox.disabled = !enabled || this.readOnly;    
    var label = document.getElementById(this.fieldNamespace + "_sectionstatuslabel" + i);
    label.style.color = (enabled) ? "" : "#999";
  },


  toggleStatusCheckbox: function(i, checked) {
    var checkBox = this.getStatusCheckbox(i);
    checkBox.checked = checked;
  },


  /**
   * Called by onChangeType to update the available sort options,
   * given the type of section being edited (different types of
   * sections may support different sets of sorting options).
   */
  setSortOptions: function(setDefaultSort) {

    // The type of section, and its properties as they relate to what
    // sorting options are supported.
    var type         = currentSelValue(document.fm[this.fieldNamespace + "_cur_type"]);
    var supportsNone = (type == "collection" || type == "multi");
    var isThreadType = (type == "activethreads");

    var isAuthoredBy = (type == "recent" && document.fm[this.fieldNamespace + "_cur_user"].checked);
    var sortField = document.fm[this.fieldNamespace + "_cur_sort"];
    var sortOptions = sortField.options;
    var currentSelection = currentSelValue(sortField);

    // Remove all options.  Fixes Server29867.
    sortOptions.length = 0;

    var currentSelectionAvailable = false;

    var nextIdx = 0;
    for (var i = 0; i < Settings.SectionList.SECTION_SORT_OPTIONS.length; i ++) {
      var sortOpt = Settings.SectionList.SECTION_SORT_OPTIONS[i];
      var show;
      if (isAuthoredBy) {
	show = (i == 0 || i == 1);
      } else if (sortOpt.value == "0") {
	show = supportsNone;
      } else if (sortOpt.threadsOnly) {
	show = isThreadType;
      } else {
	show = true;
      }
      if (currentSelection == sortOpt.value) {
	currentSelectionAvailable = show;
      }
      if (show) {
	sortOptions[nextIdx] = new Option(sortOpt.text, sortOpt.value);
	nextIdx ++;
      }
    }

    // Only select the default sort option if the caller requested, or
    // if the previously selected value is not available.
    // Server43046. [shep 03.Feb.2009]
    if (setDefaultSort || !currentSelectionAvailable) {
      // Always select the default thread type section sorting for thread
      // type sections.
      if (isThreadType) {
	selectOptionByValue(sortField, "6");
      }
      // Always select the default existing order option ("none") for
      // sections that have a manual ordering.
      else if (supportsNone) {
	selectOptionByValue(sortField, "0");
      }
      // Otherwise, always select the default sorting: lifo (last in,
      // first out; newest first).
      else {
	selectOptionByValue(sortField, "2");
      }
    } else {
      selectOptionByValue(sortField, currentSelection);
    }

  },


  /**
   * Event handler called when the "Add" button is clicked to allow
   * the user to change the labels to be used for a label driven
   * section.
   */
  onEditLabelsClick: function(project) {
    var extras = "";
    switch (this.listType) {
    case "newspage":
    case "entry":
      extras += "&allprojs=1";
      if (project == '*') {
	extras += "&curproj=2";
      }
      if (project != "" && project != '*') {
	extras += "&fqlabels=false";
      }
      break;    
    case "digest":
      extras += "&allprojs=1";
      extras += "&curprojs=2";
      extras += "&curproj=1";
      extras += "&fqlabels=true";
      extras += "&perm=r";
      break;
    case "frontpage":
      extras += "&allprojs=2";
      extras += "&fqlabels=true";
      break;
    default:
      extras += "&allprojs=1";
      extras += "&fqlabels=true";
      extras += "&perm=r";
      break;
    }
    this.__closeInsertLabelDialog = closeInsertLabelDialog;
    closeInsertLabelDialog = this.onChooseLabels.bind(this);
    openInsertLabelDialogSimple(project, document.fm[this.fieldNamespace + "_cur_labels"].value, "", extras);
  },


  /**
   * Event handler called when the label chooser dialog is closed, in
   * order to update the labels to be used for a label driven section.
   */
  onChooseLabels: function(labelArray) {

    closeInsertLabelDialog = this.__closeInsertLabelDialog;

    var set = "";
    if (labelArray != null) {
      for (var i=0; i<labelArray.length; i++) {
	set += labelArray[i] + " ";
      }
    }
    document.fm[this.fieldNamespace + "_cur_labels"].value = set;

    this.makeDirty("labels", false);

  },


  /**
   * Validates the data for the section currently being edited (with
   * respect to the name of the form field that was changed, if any),
   * and updates the SectionList's serialization in hidden INPUT field
   * corresponding to this section list setting, synchronizing it with
   * the current state of the SectionList.
   * Formerly: sections_sync == ssync
   */
  synchronize: function(val) {

    if (val == "type") {
      // they explicitly selected this new type, so try to select any
      // default options associated with it, including sort
      // option....? [shep 03.Feb.2009]
      this.onChangeType();
    }
    else if (val == "min") {
      // verify that
      var min = document.fm[this.fieldNamespace + "_cur_min"].value;
      var max = document.fm[this.fieldNamespace + "_cur_max"].value;

      // 1) it's a number
      var num = new RegExp( "\\D+" );
      if (min.match(num)) {
	min = "";
      } else {
	// 2) it's > 0 (else replace with "")
	if ((min*1) <= 0) {
	  min = "";
	} else {
	  // 3) min <= max (adjust max if necessary)
	  if (max != "" && (min*1) > (max*1)) {
	    max = min;
	  }
	}
      }

      document.fm[this.fieldNamespace + "_cur_min"].value = min;
      document.fm[this.fieldNamespace + "_cur_max"].value = max;
    }
    else if (val == "max") {
      // verify that
      var min = document.fm[this.fieldNamespace + "_cur_min"].value;
      var max = document.fm[this.fieldNamespace + "_cur_max"].value;

      // 1) it's a number
      var num = new RegExp("\\D+");
      if (max.match(num)) {
	max = "";
      } else {
	// 2) it's > 0 (else replace with "")
	if ((max*1) <= 0) {
	  max = "";
	} else {
	  // 3) min <= max (adjust min if necessary)    
	  if (min != "" && (min*1) > (max*1)) {
	    min = max;
	  }
	}
      }

      document.fm[this.fieldNamespace + "_cur_min"].value = min;
      document.fm[this.fieldNamespace + "_cur_max"].value = max;
    }
    else if (val == "title") {
      // uncheck "use title resource"
      document.fm[this.fieldNamespace + "_cur_titleres"].checked = false;
    }
    else if (val == "titleres") {
      this.updateTitle();
    }
    else if (val == "id") {
      this.updateTitle();
    }
    else if (val == "user") {
      this.setSortOptions(false);
    }
    else if (val == "size") {
      // enable / disable show boxes as appropriate and set defaults
      this.onUpdateSize(currentSelValue(document.fm[this.fieldNamespace + "_cur_size"]), true);
    }
    else if (val == "active") {
      var opt = document.fm[this.fieldNamespace + "_sectionselect"].options[document.fm[this.fieldNamespace + "_sectionselect"].selectedIndex];
      opt.style.color = (document.fm[this.fieldNamespace + "_cur_active"].checked ? "#000" : "#999");
    }
    else if (val == "status") {

      var hasPublishedStatus = false;
      var hasLockedStatus = false;

      // check to see if we need to show warnings
      for (var i=0; i<5; i++) {
	if (document.fm[this.fieldNamespace + "_cur_status" + i].checked) {
	  switch (i) {
	  case 0:
	  case 1:
	  case 4:
	    hasPublishedStatus = true;
	    break;
	  case 2: 
	  case 3: 
	    hasLockedStatus = true;
	    break;
	  }
	}
      }

      var noPublishedStatusWarning = document.getElementById(this.fieldNamespace + "_status_pdr_none");
      noPublishedStatusWarning.style.display = (hasPublishedStatus) ? "none" : "";

      var noLockedStatusWarning = document.getElementById(this.fieldNamespace + "_status_lu_none");
      noLockedStatusWarning.style.display = (hasLockedStatus) ? "none" : "";

    } else if (val == "showtitle") {

      if (currentSelValue(document.fm[this.fieldNamespace + "_cur_showtitle"]) == "-1") {
	document.fm[this.fieldNamespace + "_cur_showadd"].checked = false;
	document.fm[this.fieldNamespace + "_cur_showadd"].disabled = true;
      } else {
	document.fm[this.fieldNamespace + "_cur_showadd"].disabled = this.readOnly;
      }

    }

    if (this.currentIndex >= 0) {
      this.editor2array(this.currentIndex);
    }
    this.updateSerialization();

    if (val != "usedefault") {
      var defaultToggle = document.fm[this.fieldNamespace + "_usedefaultsections"];
      var defaultField  = document.fm[this.fieldNamespace + "_usedefault"];
      if (defaultToggle && defaultToggle.checked) {
	defaultToggle.checked = false;
	this.toggleDisplayUseDefaultSectionsWarning(false);
	defaultField.value = "false";
      }
    }

  },


  /**
   * Event handler called when the state of the seciton size selector
   * is changed (either by the user or by script, as when a different
   * section is selected).
   */
  onUpdateSize: function(size, setdefault) {
    switch (size) {
    case "1": // titles
      this.setShowEnabled( [0,0,0,0,0], setdefault );
      document.fm[this.fieldNamespace + "_cur_show0"].checked = true;
      break;
    default:
    case "2": // details
      this.setShowEnabled( [1,1,0,0,0], setdefault );
      break;
    case "0": // snippets
      this.setShowEnabled( [1,1,0,0,0], setdefault );
      break;
    case "3": // brief
      this.setShowEnabled( [1,1,1,0,0], setdefault );
      break;
    case "4": // full
      this.setShowEnabled( [1,1,1,1,1], setdefault );
      break;
    }
  },


  /**
   * Called by onUpdateSize to indicate a mask for which "Show"
   * (content elements) checkboxes are enabled (i.e., whether they
   * apply to a section of a certain size), and a flag indicating
   * whether or not to reset the checkboxes to their default states.
   */
  setShowEnabled: function(arr, setdefault) {
    for (var i=0; i<arr.length; i++) {
      // disable the checkbox
      document.fm[this.fieldNamespace + "_cur_show"+i].disabled = this.readOnly || (arr[i] == 0);
      // grey the label if disabled
      var label = document.getElementById(this.fieldNamespace + "_show_label"+i);
      label.style.color = (arr[i] == 0) ? "#999" : "";
      // set the default when required
      if (setdefault) {
	document.fm[this.fieldNamespace + "_cur_show"+i].checked  = (arr[i] == 1);
      }
    }
  },


  /**
   * Called by synchronize to update the displayed title of the
   * section (which happens only if the "Use the id to get the Title
   * from a localized resource" checkbox has just been checked).
   */
  updateTitle: function() {
    if (document.fm[this.fieldNamespace + "_cur_titleres"].checked) {
      var prefix = this.listType + "_section_";
      var id = document.fm[this.fieldNamespace + "_cur_id"].value;
      var proj = this.projectName;
      if (proj == "" && (this.listType == "newspage" || this.listType == "digest")) {
	proj = "{0}";
      }
      var res = i18n_sections(prefix + id + "_title", i18n_sections(prefix + "title", ""));
      if (res != "") {
	// generate the title
	var zero = new RegExp( "\\{0\\}", "g" );
	res = res.replace(zero, proj);

	var one = new RegExp( "\\{1\\}", "g" );
	res = res.replace(one, id);

	document.fm[this.fieldNamespace + "_cur_title"].value = res;
      }
    }
  },


  shouldStoreValue: function(key, value) {
    return (value != "") || (key == "proj" && (this.listType == "newspage" || this.listType == "entry"));
  },


  /**
   * Performs the actual mechanics of updating the serialization
   * stored in the hidden INPUT field corresponding to this
   * SectionList setting.
   * formerly hsync
   */
  updateSerialization: function() {
    // Pass all section states for this object.
    this.dataField.value = this.getSerialization(this.sectionStates);
  },


  /**
   * Computes the serialization for the given list of section states.
   */
  getSerialization: function(forStates, forceActive) {

    // we serialize to a string that looks like x_key=value,... 
    // where value has , characters escaped as \, and \ as \\
    var str = "";
    var l = forStates.length;
    for (var i=0; i<l; i++) {
      var cur = forStates[i];
      for (var j=0; j<this.keys.length; j++) {
	// don't add empty strings
	//
	// unless we're supposed to! [shep 03.Feb.2009]
	if (this.shouldStoreValue(this.keys[j], forStates[i][j])) {
	  if (forceActive && this.keys[j] == "active") {
	    str += i+"_active=t"+",";
	  } else {
	    str += i+"_"+this.keys[j]+"="+escape_commas(forStates[i][j])+",";
	  }
	}
      }
    }

    return str;

  },


  /**
   * Called by onChooseSection to populate the section editing
   * interface with the data for the section at the given index i in
   * this SectionList's list of sections.
   */
  array2editor: function(i) {

    var cur = this.sectionStates[i];

    document.fm[this.fieldNamespace + "_cur_id"].value         = cur[ this.i_id ];
    document.fm[this.fieldNamespace + "_cur_active"].checked   = (cur[ this.i_active ] == 't');
    document.fm[this.fieldNamespace + "_cur_title"].value      = cur[ this.i_title ];
    document.fm[this.fieldNamespace + "_cur_titleres"].checked = (cur[ this.i_titleres ] == "t");
  
    selectOptionByValue(document.fm[this.fieldNamespace + "_cur_showtitle"], cur[ this.i_showtitle ]);

    selectOptionByValue(document.fm[this.fieldNamespace + "_cur_type"], cur[ this.i_type ]);

    selectOptionByValue(document.fm[this.fieldNamespace + "_cur_proj"], cur[ this.i_proj ]);

    this.setCollection( cur[ this.i_collection ] );

    document.fm[this.fieldNamespace + "_cur_fqids"].value        = cur[ this.i_fqids ];
    document.fm[this.fieldNamespace + "_cur_labels"].value       = cur[ this.i_labels ];
    document.fm[this.fieldNamespace + "_cur_search"].value       = cur[ this.i_search ];
    document.fm[this.fieldNamespace + "_cur_min"].value          = cur[ this.i_min ];
    document.fm[this.fieldNamespace + "_cur_max"].value          = cur[ this.i_max ];
    document.fm[this.fieldNamespace + "_cur_alltime"].checked    = (cur[ this.i_alltime ] == "t");
    document.fm[this.fieldNamespace + "_cur_duplicates"].checked = (cur[ this.i_duplicates ] != "t");

    // Disable and uncheck the showadd control if show title is
    // "Never"; enable it otherwise.
    if (cur[ this.i_showtitle ] == "-1") {
      document.fm[this.fieldNamespace + "_cur_showadd"].disabled = true;
      document.fm[this.fieldNamespace + "_cur_showadd"].checked = false;
    } else {
      document.fm[this.fieldNamespace + "_cur_showadd"].disabled = this.readOnly;
      document.fm[this.fieldNamespace + "_cur_showadd"].checked = (cur[ this.i_showadd ] == "t");
    }

    selectOptionByValue(document.fm[this.fieldNamespace + "_cur_sort"], cur[ this.i_sort ]);
    selectOptionByValue(document.fm[this.fieldNamespace + "_cur_fastsort"], cur[ this.i_fastsort ]);
    selectOptionByValue(document.fm[this.fieldNamespace + "_cur_size"], cur[ this.i_size ]);
    selectOptionByValue(document.fm[this.fieldNamespace + "_cur_width"], cur[ this.i_width ]);

    // it's ok to set both, we'll only keep one
    selectOptionByValue(document.fm[this.fieldNamespace + "_cur_fastsearchtype"], cur[ this.i_fastsearchtype ]);
    selectOptionByValue(document.fm[this.fieldNamespace + "_cur_attiviosearchtype"], cur[ this.i_fastsearchtype ]);

    // it's ok to set both, we'll only keep one
    document.fm[this.fieldNamespace + "_cur_fastfilter"].value = cur[ this.i_fastfilter ];
    document.fm[this.fieldNamespace + "_cur_attiviofilter"].value = cur[ this.i_fastfilter ];

    for (var i = 0; i <= 4; i ++) {
      document.fm[this.fieldNamespace + "_cur_show" + i].checked = (cur[ this.i_show ].charAt(i) == '1');
    }

    this.onUpdateSize( cur[ this.i_size ], false );

    for (var i = 0; i <= 3; i ++) {
      document.fm[this.fieldNamespace + "_cur_include" + i].checked = (cur[ this.i_include ].charAt(i) == '1');
    }

    for (var i = 0; i <= 5; i ++) {
      document.fm[this.fieldNamespace + "_cur_includedoc" + i].checked = (cur[ this.i_includedoc ].charAt(i) == '1');
    }

    for (var i = 0; i <= 4; i ++) {
      document.fm[this.fieldNamespace + "_cur_status" + i].checked = (cur[ this.i_status ].charAt(i) == '1');
    }

    document.fm[this.fieldNamespace + "_cur_templateid"].value = cur[ this.i_template ];
    selectOptionByValue(document.fm[this.fieldNamespace + "_cur_entryclass"], cur[ this.i_entryclass ]);


    if (this.listType == "digest") {
      document.fm[this.fieldNamespace + "_cur_repeat"].checked  = (cur[ this.i_repeat ] == "t");    
    }
    if (this.listType == "user") {
      document.fm[this.fieldNamespace + "_cur_user"].checked  = (cur[ this.i_user ] == "-11");
    } else {
      document.fm[this.fieldNamespace + "_cur_user"].checked  = (cur[ this.i_user ] == "-10");    
    }

    // This has to be down here now, because the cur_user field is
    // kind of part of the type, especially in terms of which sort
    // options are available for which types (which is set during
    // onChangeType). [shep 13.Jan.2009]
    // Also, pass forNewSection. [shep 03.Feb.2009]
    // In this 
    var selectedTypeOpt = this.fixTypeSelected();
    this.fixDisplayedPropertiesForType(selectedTypeOpt);
    this.fixStatusRequirements(selectedTypeOpt.value);

    this.setSubsectionsCount( this.countSubsections( cur[ this.i_sub ] ) );

    if (this.labelList && this.labelList != null) {
      this.labelList.sync();
    }

    document.fm[this.fieldNamespace + "_cur_sourceurl"].value = cur[ this.i_sourceurl ];

  },


  /**
   * Called by array2editor to count the number of subsections
   * represented in the section data for the section whose data is
   * being loaded into the section editing interface.
   */
  countSubsections: function(encoding) {

    if (!encoding) {
      return 0;
    }

    var ret = 0;
    var i = -1;
    while ((i = encoding.indexOf("_t=", i + 1)) != -1) {
      ret ++;
    }

    return ret;

  },


  /**
   * Called by synchronize to store the data represented in the
   * section editing interface in the corresponding data structure so
   * that it can be serialized in an up-to-date form.
   */
  editor2array: function(i) {

    // update the array
    var cur = this.sectionStates[i];

    cur[ this.i_id         ] = document.fm[this.fieldNamespace + "_cur_id"].value;
    cur[ this.i_active     ] = document.fm[this.fieldNamespace + "_cur_active"].checked ? "t" : "f";
    cur[ this.i_title      ] = document.fm[this.fieldNamespace + "_cur_title"].value;
    cur[ this.i_titleres   ] = document.fm[this.fieldNamespace + "_cur_titleres"].checked ? "t" : "f";
    cur[ this.i_showtitle  ] = currentSelValue(document.fm[this.fieldNamespace + "_cur_showtitle"]);
    cur[ this.i_type       ] = currentSelValue(document.fm[this.fieldNamespace + "_cur_type"]);
    cur[ this.i_proj       ] = currentSelValue(document.fm[this.fieldNamespace + "_cur_proj"]);
    cur[ this.i_fqids      ] = document.fm[this.fieldNamespace + "_cur_fqids"].value;
    cur[ this.i_labels     ] = document.fm[this.fieldNamespace + "_cur_labels"].value;
    cur[ this.i_search     ] = document.fm[this.fieldNamespace + "_cur_search"].value;
    cur[ this.i_min        ] = document.fm[this.fieldNamespace + "_cur_min"].value;
    cur[ this.i_max        ] = document.fm[this.fieldNamespace + "_cur_max"].value;
    cur[ this.i_alltime    ] = document.fm[this.fieldNamespace + "_cur_alltime"].checked ? "t" : "f";
    cur[ this.i_duplicates ] = document.fm[this.fieldNamespace + "_cur_duplicates"].checked ? "f" : "t";
    cur[ this.i_sort       ] = currentSelValue(document.fm[this.fieldNamespace + "_cur_sort"]);
    cur[ this.i_fastsort   ] = currentSelValue(document.fm[this.fieldNamespace + "_cur_fastsort"]);
    cur[ this.i_size       ] = currentSelValue(document.fm[this.fieldNamespace + "_cur_size"]);
    cur[ this.i_width      ] = currentSelValue(document.fm[this.fieldNamespace + "_cur_width"]);

    // only keep one
    if (cur[ this.i_type ] == "fastsearch") {
      cur[ this.i_fastfilter ] = document.fm[this.fieldNamespace + "_cur_fastfilter"].value;
      cur[ this.i_fastsearchtype ] = currentSelValue(document.fm[this.fieldNamespace + "_cur_fastsearchtype"]);
    }
    else {
      cur[ this.i_fastfilter ] = document.fm[this.fieldNamespace + "_cur_attiviofilter"].value;
      cur[ this.i_fastsearchtype ] = currentSelValue(document.fm[this.fieldNamespace + "_cur_attiviosearchtype"]);
    }
  
    cur[ this.i_showadd    ] = document.fm[this.fieldNamespace + "_cur_showadd"].checked ? "t" : "f";

    // update the Sections title
    document.fm[this.fieldNamespace + "_sectionselect"].options[i].text = document.fm[this.fieldNamespace + "_cur_title"].value;

    cur[ this.i_show       ] = ( (document.fm[this.fieldNamespace + "_cur_show0"].checked ? "1" : "0") +
			    (document.fm[this.fieldNamespace + "_cur_show1"].checked ? "1" : "0") +
			    (document.fm[this.fieldNamespace + "_cur_show2"].checked ? "1" : "0") +
			    (document.fm[this.fieldNamespace + "_cur_show3"].checked ? "1" : "0") +
			    (document.fm[this.fieldNamespace + "_cur_show4"].checked ? "1" : "0") );

    if (cur[this.i_type] == "updated") {
      cur[ this.i_include ] = "1111";
    } else {
      cur[ this.i_include ] = ( (document.fm[this.fieldNamespace + "_cur_include0"].checked ? "1" : "0") +
			   (document.fm[this.fieldNamespace + "_cur_include1"].checked ? "1" : "0") +
			   (document.fm[this.fieldNamespace + "_cur_include2"].checked ? "1" : "0") +
			   (document.fm[this.fieldNamespace + "_cur_include3"].checked ? "1" : "0") );
    }

    if (this.listType == "digest") {
      cur[ this.i_repeat ] = document.fm[this.fieldNamespace + "_cur_repeat"].checked ? "t" : "f";
    }
    if (this.listType == "user") {
      cur[ this.i_user ] = document.fm[this.fieldNamespace + "_cur_user"].checked ? "-11" : "-101";
    } else {
      cur[ this.i_user ] = document.fm[this.fieldNamespace + "_cur_user"].checked ? "-10" : "-101";
    }

    cur[ this.i_includedoc ] = ( (document.fm[this.fieldNamespace + "_cur_includedoc0"].checked ? "1" : "0") +
			    (document.fm[this.fieldNamespace + "_cur_includedoc1"].checked ? "1" : "0") +
			    (document.fm[this.fieldNamespace + "_cur_includedoc2"].checked ? "1" : "0") +
			    (document.fm[this.fieldNamespace + "_cur_includedoc3"].checked ? "1" : "0") +
			    (document.fm[this.fieldNamespace + "_cur_includedoc4"].checked ? "1" : "0") +
			    (document.fm[this.fieldNamespace + "_cur_includedoc5"].checked ? "1" : "0") );

    cur[ this.i_status ] = ( (document.fm[this.fieldNamespace + "_cur_status0"].checked ? "1" : "0") +
			     (document.fm[this.fieldNamespace + "_cur_status1"].checked ? "1" : "0") +
			     (document.fm[this.fieldNamespace + "_cur_status2"].checked ? "1" : "0") +
			     (document.fm[this.fieldNamespace + "_cur_status3"].checked ? "1" : "0") +
			     (document.fm[this.fieldNamespace + "_cur_status4"].checked ? "1" : "0") );

    cur[ this.i_sourceurl ] = document.fm[this.fieldNamespace + "_cur_sourceurl"].value;

    cur[ this.i_template ] = document.fm[this.fieldNamespace + "_cur_templateid"].value;
    cur[ this.i_entryclass ] = currentSelValue(document.fm[this.fieldNamespace + "_cur_entryclass"]);

  },


  /**
   * @return the index in this SectionList's list of sections of the
   * currently selected section.
   */
  getCurrentIndex: function() {
    return document.fm[this.fieldNamespace + "_sectionselect"].selectedIndex;  
  },


  /**
   * Event handler called when a different section is selected in the
   * displayed selector listing the sections (either by the user or by
   * script, as when a section is created, copied or deleted).
   * @param forNewSection indicates whether the section that has been
   * "chosen" is a newly created section or just an existing section
   * that has been selected.  This parameter is no longer used.
   */
  onChooseSection: function(forNewSection) {
  
    var index = this.getCurrentIndex();
  
    // hide the editor
    show_TABLE(0, this.editTable);

    if (index >= 0) {
      // fill the editor
      this.array2editor(index);

      show_TR(0, this.chooseRow);
      show_TR(1, this.editRow);
    } else {
      show_TR(1, this.chooseRow);
      show_TR(0, this.editRow);    
    }

    // show the editor
    show_TABLE(1, this.editTable);

    // keep track of the current
    this.currentIndex = index;

  },


  /**
   * Event handler called when the user clicks the up arrow to move
   * the selected sections up in the list of sections.  To reflect the
   * new ordering, this function both changes the underlying data
   * structures as necessary, and updates the displayed OPTIONs in the
   * SELECT element.
   */
  onMoveUpClick: function() {

    var opts = document.fm[this.fieldNamespace + "_sectionselect"].options;

    // shift the data (similar to selShiftUp)
    var tmp;

    // don't swap the top one because there's nothing before it
    var swap = false;
    for (var i=0; i<opts.length; i++) {
      if (opts[i].selected) {
	// swap them
	if (swap) {
	  if (this.currentIndex == i) {
	    this.currentIndex = i-1;
	  }

	  tmp = this.sectionStates[i];
	  this.sectionStates[i] = this.sectionStates[i-1];
	  this.sectionStates[i-1] = tmp;
	}
      } else {
	swap = true; // start swapping after we've seen one unselected
      }
    }
  
    selShiftUp(opts);

    this.makeDirty(null, false);

  },


  /**
   * Event handler called when the user clicks the up arrow to move
   * the selected sections down in the list of sections.  To reflect
   * the new ordering, this function both changes the underlying data
   * structures as necessary, and updates the displayed OPTIONs in the
   * SELECT element.
   */
  onMoveDownClick: function() {

    var opts = document.fm[this.fieldNamespace + "_sectionselect"].options;

    // shift the data (similar to selShiftDown)
    var tmp;

    // don't swap the bottom one because there's nothing after it
    var swap = false;
    for(var i=(opts.length-1); i >= 0; i--) {
      if (opts[i].selected) {      
	if (swap) {
	  if (this.currentIndex == i) {
	    this.currentIndex = i+1;
	  }
	  // swap them
	  tmp = this.sectionStates[i];
	  this.sectionStates[i] = this.sectionStates[i+1];
	  this.sectionStates[i+1] = tmp;
	}
      } else {
	swap = true; // start swapping after we've seen one unselected
      }
    }
  
    selShiftDown(opts);

    this.makeDirty(null, false);  

  },


  /**
   * Event handler called when the user clicks the "New" button to
   * create a new section.  This function both adds a new element to
   * the underlying data structures, and updates the displayed OPTIONs
   * in the SELECT elements.
   */
  onNewClick: function() {

    var n = document.fm[this.fieldNamespace + "_sectionselect"].options.length;

    this.sectionStates[n] = new Array();

    // blank out the settings
    for (var i=0; i<this.keys.length; i++) {
      this.sectionStates[n][i] = "";
    }

    var defaultProject;
    if (this.listType == "digest") {
      defaultProject = "";
    }
    else if (this.listType == "newspage" || this.listType == "entry") {
      if (this.defaults) {
	defaultProject = "";
      } else {
	defaultProject = "::" + this.projectName;
      }
    }
    else {
      defaultProject = "*";
    }

    // provide a few defaults
    this.sectionStates[n][ this.i_active     ] = "t";
    this.sectionStates[n][ this.i_title      ] = i18n("sectionlist_new_section_default_title", "Untitled");
    this.sectionStates[n][ this.i_titleres   ] = "f";
    this.sectionStates[n][ this.i_showtitle  ] = (this.listType == "digest") ? "0" : "1"; // default to always [ajm 10.May.2006]
    this.sectionStates[n][ this.i_type       ] = document.fm[this.fieldNamespace + "_cur_type"].options[0].value;
    this.sectionStates[n][ this.i_proj       ] = defaultProject;
    this.sectionStates[n][ this.i_alltime    ] = "f";
    this.sectionStates[n][ this.i_duplicates ] = "t";
    this.sectionStates[n][ this.i_size       ] = "3"; // this works because we choose cur_type.options[0], which defaults to 2
    this.sectionStates[n][ this.i_width      ] = "2"; // this works because we choose cur_type.options[0], which defaults to 2
    this.sectionStates[n][ this.i_show       ] = "11000";
    this.sectionStates[n][ this.i_include    ] = "1110";
    this.sectionStates[n][ this.i_includedoc ] = "111111";
    this.sectionStates[n][ this.i_status     ] = "11110";
    this.sectionStates[n][ this.i_repeat     ] = "f";
    this.sectionStates[n][ this.i_user       ] = (this.listType == "user") ? "-11" : "-101";
    this.sectionStates[n][ this.i_sub        ] = "";
    this.sectionStates[n][ this.i_showadd    ] = "t";
    this.sectionStates[n][ this.i_template   ] = "";
    this.sectionStates[n][ this.i_entryclass ] = "";
    this.sectionStates[n][ this.i_max        ] = "10"; // set max of 10 [shep 31.Mar.2008]

    // insert and select
    insertOptionAt(document.fm[this.fieldNamespace + "_sectionselect"], i18n("sectionlist_new_section_default_title", "Untitled"), n);
    document.fm[this.fieldNamespace + "_sectionselect"].selectedIndex = n;

    // update the editor
    this.onChooseSection(true);

    // focus and select the title
    this.selectAndFocusSectionTitle();

    this.makeDirty(null, true);

  },


  onPreviewClick: function() {

    if (this.previewForm == null) {
//       alert("no preview form?");
      return;
    }

    var opts = document.fm[this.fieldNamespace + "_sectionselect"].options;

    if (opts.length == 0) {
      // nothing to preview...
      return;
    }

    var states = new Array();

    for (var i=0; i<opts.length; i++) {
      if (opts[i].selected) {
	states[states.length] = this.sectionStates[i];
      }
    }

    if (states.length == 0) {
      states = this.sectionStates;
    }

    this.previewForm.serialization.value = this.getSerialization(states, true);

    this.previewForm.submit();

  },


  selectAndFocusSectionTitle: function() {

    var title = document.fm[this.fieldNamespace + "_cur_title"];    
    
    // first we need to get focus
    title.focus();

    if (title.setSelectionRange) { // For Mozilla
        title.setSelectionRange(0,title.value.length);
    }
    else if (title.createTextRange) { // For IE
        var range = title.createTextRange();
        range.moveStart("character", 0);
        range.moveEnd("character", title.value.length);
        range.select();
    }
    else {
        title.select();
    }

  },

  /**
   * Event handler called when the user clicks the "Copy" button to
   * create a copy of the currently selected section.  This function
   * both adds a new element to the underlying data structures, and
   * updates the displayed OPTIONs in the SELECT elements.
   */
  onCopyClick: function() {
  
    var src = document.fm[this.fieldNamespace + "_sectionselect"].selectedIndex;
    var n = document.fm[this.fieldNamespace + "_sectionselect"].options.length;

    this.sectionStates[n] = new Array();

    // copy the settings
    for (var i=0; i<this.keys.length; i++) {
      this.sectionStates[n][i] = this.sectionStates[src][i];
    }
  
    // insert and select
    insertOptionAt(document.fm[this.fieldNamespace + "_sectionselect"], this.sectionStates[src][ this.i_title ], n);
    document.fm[this.fieldNamespace + "_sectionselect"].selectedIndex = n;

    // update the editor
    this.onChooseSection(false);

    // focus and select the title
    this.selectAndFocusSectionTitle();

    this.makeDirty(null, false);

  },


  /**
   * Event handler called when the user clicks the "Delete" button to
   * delete the currently selected sections.  This function both
   * removes the appropriate elements from the underlying data
   * structures, and updates the displayed OPTIONs in the SELECT
   * elements.  It also requires the user to confirm the operation.
   */
  onDeleteClick: function() {

    if (confirm(i18n("sectionlist_selected_sections_delete_confirmation_message",
		     "Are you sure you want to delete the selected sections?"))) {

      var opts = document.fm[this.fieldNamespace + "_sectionselect"].options;
      var last = 0;
  
      for (var i=opts.length-1; i >= 0; i--) {
	if (opts[i].selected) {
	  // delete it
	  this.sectionStates[i] = null;
	  opts[i] = null;
	  last = i;
	}
      }

      // need to explicitly delete nulls for Arrays
      deleteNulls(opts);
      deleteNulls(this.sectionStates);
  
      if (last >= opts.length) {
	last = opts.length - 1;
      }

      // select the one before it and update the editor
      document.fm[this.fieldNamespace + "_sectionselect"].selectedIndex = last;
      this.onChooseSection(false);

      this.makeDirty(null, false);
    }

  },


  /**
   * Event handler called when the user clicks the "Choose" button to
   * choose a collection for a collection driven section.
   */
  onEditCollectionClick: function() {

    var user = null;
    var collection = null;

    var val = this.sectionStates[this.getCurrentIndex()][ this.i_collection ];
    var pipe = val.indexOf('|');
    if (pipe != -1) {
      user = trim(val.substring(0,pipe));
      collection = trim(val.substring(pipe+1));
    }

    this.__closeModifyCollectionPortletDialog = closeModifyCollectionPortletDialog;
    closeModifyCollectionPortletDialog = this.onChooseCollection.bind(this);
    openModifyCollectionPortletDialog(this.getCurrentIndex(), 0, user, collection, "ignore",
				      i18n("sectionlist_choose_collection_window_title", "Choose a Collection"));

  },


  /**
   * Event handler called when the collection chooser dialog is
   * closed, in order to update the collection to be used for a
   * collection driven section.
   */
  onChooseCollection: function(index, column, username, collection, portletsize) {
    closeModifyCollectionPortletDialog = this.__closeModifyCollectionPortletDialog;
    var val = username + " | " + collection;
    this.sectionStates[this.getCurrentIndex()][ this.i_collection ] = val;
    this.setCollection(val);
    this.makeDirty(null, false);
  },


  /**
   * This function doesn't seem to be used anymore.
   */
  showBadSection: function() {
    if (this.badSection >= 0) {
      usedefaultchk.checked = false;
      sections_mod_usedefault();
      document.fm[this.fieldNamespace + "_sectionselect"].selectedIndex = this.badSection;
      this.onChooseSection(false);
    }  
  },


  /**
   * Event handler called when the user invokes an operation that will
   * not result in the setting data in this view being saved, but
   * rather in some server side operation to edit this section list
   * data (e.g., copy sections from a template).  This function is
   * capable of performing this operation for a classic handler view
   * or for a newer settings view.
   */
  applyChanges: function() {
    // handlers
    if (typeof(applyChanges) == "function") {
      applyChanges();
    }
    // settings views
    else {
      dialog_apply();
    }
  },


  customOperation: function(operation) {
    this.actionField.value = operation; // what does this do? quite possibly nothing.
//     go(operation); we don't use waitforload2 here...
    document.fm.fb_submit.value = operation;
    document.fm.submit();
  },


  /**
   * Event handler called when the user clicks the "Upgrade" button to
   * upgrade their section definitions to the newer format.
   */
  onUpgradeAcceptClick: function() {
    this.makeDirty(null, false);
    this.customOperation("upgrade");
  },


  /**
   * Event handler called when the user clicks the "Add > > >" button
   * to copy sections from a project template into the list of
   * sections represented by this SectionList.
   */
  onApplyTemplateClick: function() {
    this.makeDirty(null, false);
    this.customOperation("applytemplate");
  },


  /**
   * Event handler called when the user clicks the "Modify" button to
   * edit the subsections associated with the section currently being
   * edited.
   */
  onEditSubsectionsClick: function() {
    if (typeof(closeSubsectionsDialog) == "function") {
      this.__closeSubsectionsDialog = closeSubsectionsDialog;
    }
    closeSubsectionsDialog = this.onEditSubsections.bind(this);
    openDialog("subsectionseditor", "modern_relationships_window", "", "&data=" + encode_url_parameter(this.sectionStates[this.getCurrentIndex()][ this.i_sub ]), DIALOG_FLAG_REUSE_WINDOW);
  },


  /**
   * Event handler called when the subsections editor dialog is
   * closed, in order to update the subsections associated with the
   * section currently being edited.
   */
  onEditSubsections: function(count, data) {
    if (this.__closeSubsectionsDialog) {
      closeSubsectionsDialog = this.__closeSubsectionsDialog;
    }
    this.sectionStates[this.getCurrentIndex()][ this.i_sub ] = data;
    this.setSubsectionsCount(count);
    this.makeDirty("sub", false);
  },


  onChangeTemplate: function() {
    var desc =
    this.templatename2templatedesc[document.fm[this.fieldNamespace +
					       "_template"].options[document.fm[this.fieldNamespace +
										"_template"].selectedIndex].value];
    if (desc == null) {
      desc = "";
    }
    document.getElementById(this.fieldNamespace + "_template_description").innerHTML = desc;
  },


  /**
   * Prompts for a URL using a normal javascript prompt and then fills
   * in the section with the corresponding settings
   */
  promptSectionFromURL: function() {
    var url = prompt(i18n("sectionlist_fromurl_prompt_message",
			  "Type or copy and paste a URL from any Traction view to have the section information filled in automatically") + ":",
		     document.fm[this.fieldNamespace + "_cur_sourceurl"].value);
    if (url) {
      this.syncSectionToURL(url);
    }
  },

  /**
   * Event handler called when the user edits the "Sections from URL"
   * field, to update the definition of the section currently being
   * edited so that it is as close to equivalent as possible to the
   * specification for the view represented by the URL.
   */
  syncSectionToURL: function(url) {

    var u;
    if (url) {
      u = new Util.URL(url);
      // store this
      document.fm[this.fieldNamespace + "_cur_sourceurl"].value = url;
    } else {
      u = new Util.URL(document.fm[this.fieldNamespace + "_cur_sourceurl"].value);
    }

    if (u.path == FORM_ACTION_READ_ONLY || u.path == FORM_ACTION_READ_WRITE) { // valid path

      var type = Settings.SectionList.VIEWTYPE2SECTIONTYPE[u.query.params["type"]];

      var proj = u.query.params["proj"];
      if (proj != null && (proj.indexOf(",") != -1 != proj.trim() == "")) {
	proj = null;
      } else if (proj.indexOf("::") == 0) {
	proj = proj.substring(2);
      } else if (proj.indexOf(":") == 0) {
	proj = proj.substring(1);
      }

      var defaultVol = null;

      if (type != null) {

	switch(type) {

	case "cat":
	case "addcatpresent":
	case "remcatgone":
	  var cats = u.query.params["cat"];
	  if (cats == null) {
	    // XXX No labels to go with this label-driven section.
	  }
	  var realLabels = Traction.Label.parselist(cats, proj);
	  var labelText = new Array(realLabels.length);
	  for (var i = 0; i < realLabels.length; i ++) {
	    labelText[i] = realLabels[i].getRapidSelectorFormat();
	  }
	  this.onChooseLabels(labelText);
	  break;

	case "recent":
	case "updated":
	case "activethreads":
	case "published":
	case "unpublished":
	case "locked":
	case "unlocked":
	  break;

	case "allpagenameoperations":
	case "addpagenameoperations":
	case "changepagenameoperations":
	case "removepagenameoperations":
	case "allpagenames":
	case "unassignedpagenames":
	case "orphanedpagenames":
	  if (document.fm[this.fieldNamespace + "_cur_max"].value == "") {
	    document.fm[this.fieldNamespace + "_cur_max"].value = "10";
	  }
	  break;

	case "multi":
	  var ids = u.query.params["rec"];
	  if (ids == null || ids == "") {
	    // XXX No record IDs to use with 'Specific Articles' section.
	  }
	  document.fm[this.fieldNamespace + "_cur_fqids"].value = ids.replace(/,/gi, " ");
	  break;

	case "attiviosearch":
	case "fastsearch":
	  if (!selectOptionByValue(document.fm[this.fieldNamespace + "_cur_type"], type)) {
	    // XXX FAST search is not licensed on this server.
	  }

	  defaultVol = "0";

	  var query = u.query.params["query"];
	  if (query != null) {
	    document.fm[this.fieldNamespace + "_cur_search"].value = query.replace(/\+/gi, " ");
	  } else {
	    // No query to go with this FAST search-driven section.
	  }

	  var sortby = u.query.params["sortby"]; // FAST search version of sort parameter
	  if (sortby != null) {
	    selectOptionByValue(document.fm[this.fieldNamespace + "_cur_fastsort"], sortby);
	  }

	  var fastSearchType = u.query.params["searchtype"];
	  if (fastSearchType != null) {
	    // it's ok to set both, we'll only keep one
	    selectOptionByValue(document.fm[this.fieldNamespace + "_cur_fastsearchtype"], fastSearchType);
	    selectOptionByValue(document.fm[this.fieldNamespace + "_cur_attiviosearchtype"], fastSearchType);
	  }

	  var types = u.query.params["types"];
	  if (types != null) {
	    var ALL_TYPES = "ecamtp";
	    for (var i = 0; i < ALL_TYPES.length; i ++) {
	      document.fm[this.fieldNamespace + "_cur_includedoc" + i].checked = (types.indexOf(ALL_TYPES.substring(i, i + 1)) != -1);
	    }
	  }

	  var filter = u.query.params["filter"];
	  if (filter != null && typeof(filter) != "function") {
	    // it's ok to set both, we'll only keep one
	    document.fm[this.fieldNamespace + "_cur_fastfilter"].value = filter.replace(/\+\+/gi, " +").replace(/([^\s])\+/gi, "$1 ");
	    document.fm[this.fieldNamespace + "_cur_attiviofilter"].value = filter.replace(/\+\+/gi, " +").replace(/([^\s])\+/gi, "$1 ");
	  }

	  if (document.fm[this.fieldNamespace + "_cur_max"].value == "") {
	    document.fm[this.fieldNamespace + "_cur_max"].value = "10";
	  }

	  break;

	default:
	  // XXX There does not seem to be a section type corresponding to the view type.
	  alert("Could not find a section type that matches the view type '" + u.query.params["type"] + "'.");

	}

	// (almost) universal settings: type, project, search
	// expression, volume, title, sort.
	selectOptionByValue(document.fm[this.fieldNamespace + "_cur_type"], type);
	Debug.println("Identified section type '", type, "'");

	this.onChangeType();

	var edate = u.query.params["edate"];
	if (type == "fastsearch" || type == "attiviosearch") {
	  var daterange = u.query.params["daterange"];
	  if (daterange != null && (daterange == "t" || daterange == "true")) {
	    document.fm[this.fieldNamespace + "_cur_alltime"].checked = (edate == null || edate == "all");
	  } else {
	    var datetype = u.query.params["datetype"];
	    document.fm[this.fieldNamespace + "_cur_alltime"].checked = (datetype == null || datetype == "anytime");
	  }
	} else {
	  document.fm[this.fieldNamespace + "_cur_alltime"].checked = (edate == null || edate == "all");
	}

	if (type != "fastsearch" &&
            type != "attiviosearch" && 
	    type.indexOf("pagenames") == -1 &&
	    type.indexOf("pagenameoperations") == -1) {
	  try {
	    var sx = (new Util.SearchExpression(u)).getInfixExpression();
	    Debug.println("Using search expression \"", sx, "\"");
	    if (sx != null) {
	      document.fm[this.fieldNamespace + "_cur_search"].value = sx;
	    }
	  } catch (xcp) { }
	}

	if (proj != null) {
	  selectOptionByText(document.fm[this.fieldNamespace + "_cur_proj"], proj, false);
	}

// 	var min = u.query.params["min"];
// 	if (min != null) {
// 	  document.fm[this.fieldNamespace + "_cur_min"].value = min;
// 	}
// 	var max = u.query.params["max"];
// 	if (max != null) {
// 	  document.fm[this.fieldNamespace + "_cur_max"].value = max;
// 	}

	var vol = Settings.SectionList.VOLUME2SECTIONVOLUME[u.query.params["brief"]];
	if (vol == null) {
	  vol = defaultVol;
	}
	if (vol != null) {
	  selectOptionByValue(document.fm[this.fieldNamespace + "_cur_size"], vol);
	}

	var title = u.query.params["title"];
	if (title != null && title != "") {
	  document.fm[this.fieldNamespace + "_cur_title"].value = title;
	}

	var sort = u.query.params["sort"];
	if (sort != null) {
	  for (var i = 0; i < Settings.SectionList.SECTION_SORT_OPTIONS.length; i ++) {
	    if (Settings.SectionList.SECTION_SORT_OPTIONS[i].value == sort) {
	      selectOptionByValue(document.fm[this.fieldNamespace + "_cur_sort"], sort);
	      break;
	    }
	  }
	}

	this.synchronize(null, false);

      }

    }

  }


};


/**
 * The prefix (after the field namespace) for the id attributes of the
 * rows in the section editing interface.  The id attributes are of
 * the form "<field-namespace>_sectionedit_row_<row-name>".
 */
Settings.SectionList.ROW_ID_PREFIX = "sectionedit_row";


/**
 * The ids (not including the the field namespace or row id prefix
 * defined above) of the rows in the section editing interface, in the
 * order in which they occur in the underlying HTML.
 */
Settings.SectionList.ROW_IDS = [ "id", "titlefromid", "title", "showtitle", "addtosection", "templateid", "entryclass", "type", "project", "fqids", "labels", "collection", "search", "fastfilter", "fastsearchtype", "attiviofilter", "attiviosearchtype", "minmax", "alltime", "user", "duplicates", "sort", "fastsort", "volume", "width", "components", "includetypes", "status", "includedoctypes", "subsections", "group_display", "group_advanced" ];


/**
 * A mapping from the name of a section type (e.g., "recent") to
 * another map which indicates for each section editing row ID whether
 * that row should be displayed (i.e., whether that row contains an
 * interface for editing a property that applies to the given type of
 * section).
 */
Settings.SectionList.SECTIONTYPE2SHOWROWS = {

  cat: {
    id: true, titlefromid: true, title: true, showtitle: true, addtosection: true, templateid: true, entryclass: true, type: true, project: false, fqids: false, labels: true, collection: false, search: true, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: true, alltime: true, user: true, duplicates: true, sort: true, fastsort: false, volume: true, width: true, components: true, includetypes: true, status: true, includedoctypes: false, subsections: true, group_display: true, group_advanced: true
  },

  addcatpresent: {
    id: true, titlefromid: true, title: true, showtitle: true, addtosection: true, templateid: true, entryclass: true, type: true, project: false, fqids: false, labels: true, collection: false, search: true, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: true, alltime: true, user: true, duplicates: true, sort: true, fastsort: false, volume: true, width: true, components: true, includetypes: true, status: true, includedoctypes: false, subsections: true, group_display: true, group_advanced: true
  },

  // same as cat except for addtosection
  remcatgone: {
    id: true, titlefromid: true, title: true, showtitle: true, addtosection: false, templateid: false, entryclass: false, type: true, project: false, fqids: false, labels: true, collection: false, search: true, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: true, alltime: true, user: true, duplicates: true, sort: true, fastsort: false, volume: true, width: true, components: true, includetypes: true, status: true, includedoctypes: false, subsections: true, group_display: true, group_advanced: true
  },

  multi: {
    id: true, titlefromid: true, title: true, showtitle: true, addtosection: false, templateid: false, entryclass: false, type: true, project: false, fqids: true, labels: false, collection: false, search: true, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: true, alltime: true, user: true, duplicates: true, sort: true, fastsort: false, volume: true, width: true, components: true, includetypes: true, status: true, includedoctypes: false, subsections: true, group_display: true, group_advanced: true
  },

  collection: {
    id: true, titlefromid: true, title: true, showtitle: true, addtosection: false, templateid: false, entryclass: false, type: true, project: false, fqids: false, labels: false, collection: true, search: true, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: true, alltime: true, user: true, duplicates: true, sort: true, fastsort: false, volume: true, width: true, components: true, includetypes: true, status: true, includedoctypes: false, subsections: true, group_display: true, group_advanced: true
  },

  recent: {
    id: true, titlefromid: true, title: true, showtitle: true, addtosection: true, templateid: true, entryclass: true, type: true, project: true, fqids: false, labels: false, collection: false, search: true, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: true, alltime: true, user: true, duplicates: true, sort: true, fastsort: false, volume: true, width: true, components: true, includetypes: true, status: true, includedoctypes: false, subsections: true, group_display: true, group_advanced: true
  },

  updated: {
    id: true, titlefromid: true, title: true, showtitle: true, addtosection: false, templateid: false, entryclass: false, type: true, project: true, fqids: false, labels: false, collection: false, search: true, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: true, alltime: true, user: true, duplicates: true, sort: true, fastsort: false, volume: true, width: true, components: true, includetypes: false, status: true, includedoctypes: false, subsections: true, group_display: true, group_advanced: true
  },

  fastsearch: {
    id: true, titlefromid: true, title: true, showtitle: true, addtosection: false, templateid: false, entryclass: false, type: true, project: true, fqids: false, labels: false, collection: false, search: true, fastfilter: true, fastsearchtype: true, attiviofilter: false, attiviosearchtype: false, minmax: true, alltime: true, user: true, duplicates: true, sort: false, fastsort: true, volume: true, width: true, components: true, includetypes: false, status: true, includedoctypes: true, subsections: false, group_display: true, group_advanced: true
  },

  attiviosearch: {
    id: true, titlefromid: true, title: true, showtitle: true, addtosection: false, templateid: false, entryclass: false, type: true, project: true, fqids: false, labels: false, collection: false, search: true, fastfilter: false, fastsearchtype: false, attiviofilter: true, attiviosearchtype: true, minmax: true, alltime: true, user: true, duplicates: true, sort: false, fastsort: true, volume: true, width: true, components: true, includetypes: false, status: true, includedoctypes: true, subsections: false, group_display: true, group_advanced: true
  },

  allpagenameoperations: {
    id: true, titlefromid: true, title: true, showtitle: true, addtosection: false, templateid: false, entryclass: false, type: true, project: true, fqids: false, labels: false, collection: false, search: false, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: true, alltime: true, user: true, duplicates: false, sort: true, fastsort: false, volume: false, width: true, components: true, includetypes: false, status: false, includedoctypes: false, subsections: false, group_display: true, group_advanced: true
  },

  // same as allpagenameoperations
  allpagenames: {
    id: true, titlefromid: true, title: true, showtitle: true, addtosection: false, templateid: false, entryclass: false, type: true, project: true, fqids: false, labels: false, collection: false, search: false, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: true, alltime: true, user: true, duplicates: false, sort: true, fastsort: false, volume: false, width: true, components: true, includetypes: false, status: false, includedoctypes: false, subsections: false, group_display: true, group_advanced: true
  },

  // same as recent
  published: {
    id: true, titlefromid: true, title: true, showtitle: true, addtosection: false, templateid: false, entryclass: false, type: true, project: true, fqids: false, labels: false, collection: false, search: true, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: true, alltime: true, user: true, duplicates: true, sort: true, fastsort: false, volume: true, width: true, components: true, includetypes: true, status: true, includedoctypes: false, subsections: true, group_display: true, group_advanced: true
  },

  defaultType: {
    id: true, titlefromid: true, title: true, showtitle: true, addtosection: false, templateid: false, entryclass: false, type: true, project: false, fqids: false, labels: false, collection: false, search: true, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: true, alltime: true, user: true, duplicates: true, sort: true, fastsort: false, volume: true, width: true, components: true, includetypes: true, status: true, includedoctypes: false, subsections: true, group_display: true, group_advanced: true
  }

};

// addcatpresent same as cat
Settings.SectionList.SECTIONTYPE2SHOWROWS.addcatpresent = Settings.SectionList.SECTIONTYPE2SHOWROWS.cat;

// activethreads same as updated
Settings.SectionList.SECTIONTYPE2SHOWROWS.activethreads = Settings.SectionList.SECTIONTYPE2SHOWROWS.updated;

// all page name operation iterators same
Settings.SectionList.SECTIONTYPE2SHOWROWS.addpagenameoperations = Settings.SectionList.SECTIONTYPE2SHOWROWS.allpagenameoperations;
Settings.SectionList.SECTIONTYPE2SHOWROWS.changepagenameoperations = Settings.SectionList.SECTIONTYPE2SHOWROWS.allpagenameoperations;
Settings.SectionList.SECTIONTYPE2SHOWROWS.removepagenameoperations = Settings.SectionList.SECTIONTYPE2SHOWROWS.allpagenameoperations;

// all page name iterators the same
Settings.SectionList.SECTIONTYPE2SHOWROWS.unassignedpagenames = Settings.SectionList.SECTIONTYPE2SHOWROWS.allpagenames;
Settings.SectionList.SECTIONTYPE2SHOWROWS.orphanedpagenames = Settings.SectionList.SECTIONTYPE2SHOWROWS.allpagenames;

// all status-based iterators the same (and same as recent)
Settings.SectionList.SECTIONTYPE2SHOWROWS.unpublished = Settings.SectionList.SECTIONTYPE2SHOWROWS.published;
Settings.SectionList.SECTIONTYPE2SHOWROWS.locked = Settings.SectionList.SECTIONTYPE2SHOWROWS.published;
Settings.SectionList.SECTIONTYPE2SHOWROWS.unlocked = Settings.SectionList.SECTIONTYPE2SHOWROWS.published;
Settings.SectionList.SECTIONTYPE2SHOWROWS.rejected = Settings.SectionList.SECTIONTYPE2SHOWROWS.published;
Settings.SectionList.SECTIONTYPE2SHOWROWS.unrejected = Settings.SectionList.SECTIONTYPE2SHOWROWS.published;


// the display mode of a section constrains which controls are
// applicable.
Settings.SectionList.DISPLAY_MODE_LOOKUP = 0;
Settings.SectionList.DISPLAY_MODE_VIEW = 1;
Settings.SectionList.DISPLAY_MODE_MAIL = 2;
Settings.SectionList.DISPLAYMODE2HIDEROWS = new Array();

/**
 * For "lookup" display type, the sections aren't being viewed at all,
 * so there is no point in ever showing these: Section ID; Get Section
 * Title from i18n Resource via ID; Show Section Title; Show Add
 * Article Link; Template Article ID; Article Class; Min; Max; Ignore
 * date range and include all articles; Skip articles appearing in
 * other sections; Sorting; FAST Sorting; Volume; Width; Show
 * (components); and Subsections.
 */
Settings.SectionList.DISPLAYMODE2HIDEROWS[Settings.SectionList.DISPLAY_MODE_LOOKUP] = { id: true, titlefromid: true, title: false, showtitle: true, addtosection: true, templateid: true, entryclass: true, type: false, project: false, fqids: false, labels: false, collection: false, search: false, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: true, alltime: true, user: true, duplicates: true, sort: true, fastsort: true, volume: true, width: true, components: true, includetypes: false, status: false, includedoctypes: false, subsections: true, group_display: true, group_advanced: true };

/**
 * Normal views may need all the controls.
 */
Settings.SectionList.DISPLAYMODE2HIDEROWS[Settings.SectionList.DISPLAY_MODE_VIEW] = { id: false, titlefromid: false, title: false, showtitle: false, addtosection: false, templateid: false, entryclass: false, type: false, project: false, fqids: false, labels: false, collection: false, search: false, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: false, alltime: false, user: false, duplicates: false, sort: false, fastsort: false, volume: false, width: false, components: false, includetypes: false, status: false, includedoctypes: false, subsections: false, group_display: false, group_advanced: false };

/**
 * Email messages, such as digests, will be viewed, but certain
 * controls are not applicable to control the display of sections to
 * be displayed in an email message: Show Add Article Link; Template
 * Article ID; Article Class; Ignore date range and include all
 * articles; and Subsections.
 */
Settings.SectionList.DISPLAYMODE2HIDEROWS[Settings.SectionList.DISPLAY_MODE_MAIL] = { id: false, titlefromid: false, title: false, showtitle: false, addtosection: true, templateid: true, entryclass: true, type: false, project: false, fqids: false, labels: false, collection: false, search: false, fastfilter: false, fastsearchtype: false, attiviofilter: false, attiviosearchtype: false, minmax: false, alltime: true, user: false, duplicates: false, sort: false, fastsort: false, volume: false, width: false, components: false, includetypes: false, status: false, includedoctypes: false, subsections: false, group_display: false, group_advanced: false };
  

/**
 * The list of indices in the list of checkboxes for the "Show"
 * (components) that should be hidden for pagename or
 * pagenameoperation driven sections.
 */
Settings.SectionList.PAGENAME_SECTIONS_HIDE_SHOW_CHECKBOXES = [ 2, 3, 4 ];


/**
 * A mapping from the name of a section type (e.g., "published"; for
 * status change driven sections only) to another map which indicates
 * the lists of indices of the checkboxes in the "Filter by Status"
 * checkboxes that should be hidden and which should be checked, in
 * order to be consistent with the type of section currently being
 * edited (some of which do not support custom checking or unchecking
 * of those checkboxes).
 */
Settings.SectionList.SECTIONTYPE2STATUSPROPS = {

  //
  // 0: draft
  // 1: published
  // 4: rejected
  // 2: locked
  // 3: unlocked
  //
  // default published status: 01
  // default locked status: 23
  //

  published: {
    disabled: "014",
    checked: "123"
  },
  unpublished: {
    disabled: "014",
    checked: "023"
  },
  rejected: {
    disabled: "014",
    checked: "423"
  },
  unrejected: {
    disabled: "014",
    checked: "023"
  },
  locked: {
    disabled: "23", 
    checked: "012"  
  },
  unlocked: {
    disabled: "23", 
    checked: "013"  
  }
};


/**
 * A list of section sort options, each with a value and text field to
 * be used for the OPTION elements' corresponding value attribute and
 * text property, and a flag indicating whether the option applies to
 * threaded section types only (sorting options involving thread
 * activity apply to activethread type sections only).
 */
Settings.SectionList.SECTION_SORT_OPTIONS = [
  { value: "2",  text: i18n("sectionlist_sort_option_newest_first", "Newest First"), threadsOnly: false },
  { value: "1",  text: i18n("sectionlist_sort_option_oldest_first", "Oldest First"), threadsOnly: false },
  { value: "3",  text: i18n("sectionlist_sort_option_alpha", "Alphabetical by Title"), threadsOnly: false },
  { value: "5",  text: i18n("sectionlist_sort_option_alpha_backwards", "Reverse Alphabetical by Title"), threadsOnly: false },
  { value: "4",  text: i18n("sectionlist_sort_option_random", "Random"), threadsOnly: false },
  { value: "0",  text: i18n("sectionlist_sort_option_none", "None"), threadsOnly: false },
  { value: "6",  text: i18n("sectionlist_sort_option_last_active_thread", "Last Active Thread First"), threadsOnly: true },
  { value: "7",  text: i18n("sectionlist_sort_option_earliest_active_thread", "Earliest Active Thread First"), threadsOnly: true },
  { value: "8",  text: i18n("sectionlist_sort_option_largest_thread_first", "Most Commented-On First"), threadsOnly: true },
  { value: "9",  text: i18n("sectionlist_sort_option_smallest_thread_first", "Least Commented-On First"), threadsOnly: true }
];


/**
 * A mapping from standard Traction view type names to the equivalent
 * standard Traction section type name.  In most cases, these values
 * will be the same.
 */
Settings.SectionList.VIEWTYPE2SECTIONTYPE = {
  cat: "cat",
  addcatpresent: "addcatpresent",
  remcatgone: "remcatgone",
  rchron: "recent",
  updated: "updated",
  activethreads: "activethreads",
  multi: "multi",
  single: "multi",
  fastsearch: "fastsearch",
  attiviosearch: "attiviosearch",
  allpagenameoperations: "allpagenameoperations",
  addpagenameoperations: "addpagenameoperations",
  changepagenameoperations: "changepagenameoperations",
  removepagenameoperations: "removepagenameoperations",
  allpagenames: "allpagenames",
  unassignedpagenames: "unassignedpagenames",
  orphanedpagenames: "orphanedpagenames",
  published: "published",
  unpublished: "unpublished",
  locked: "locked",
  unlocked: "unlocked"
};


/**
 * A mapping from various possible known values of the standard
 * "brief" Traction URL parameter to the equivalent value of the
 * section editing interface's Volume selector.
 */
Settings.SectionList.VOLUME2SECTIONVOLUME = {
  titles: "1",
  t: "1",
  details: "2",
  d: "2",
  snippets: "0",
  s: "0",
  brief: "3",
  b: "3",
  y: "3",
  full: "4",
  n: "4",
  comments: "4",
  c: "4",
  feed: "5",
  f: "5"
};


/**
 * An object representing a color picker setting, including the
 * functions for opening the color picker dialog/overlay, and the
 * functions for translating the choice to the appropriate format for
 * the current setting.
 * See also com.traction.sdl.admin.settings#colorinput
 */
Settings.ColorPicker = Class.create();
Settings.ColorPicker.prototype = {


  options: new Array(),


  formField: null,


  colorBox: null,


  picking: false,


  initialize: function(options) {

    this.options = options;

  },


  init: function(options) {
    this.formField = options.formField;
    this.colorBox = document.getElementById(this.options.fieldNamespace + "_colorbox");;
  },


  refreshColor: function() {
    this.colorBox.style.backgroundColor = this.formField.value;
    this.makeDirty();
  },


  onColorClick: function() {
    this.openColorPicker();
  },


  openColorPicker: function() {
    Settings.COLOR_PICKER.colorPickerSetting = this;
    document.getElementById("yui-picker-panel-heading").innerHTML = this.options.settingLabel;
    Settings.COLOR_PICKER.dialog.show();
    var setVal = Util.getRGBFromString(this.formField.value);
    if (setVal == null) {
      setVal = Util.getRGBFromString(this.colorBox.style.backgroundColor);
    }
    Settings.COLOR_PICKER.picker.setValue(setVal, true);
  },


  onPickColor: function(colorHex) {

    this.formField.value = "#" + colorHex;
    this.colorBox.style.backgroundColor = this.formField.value;
    this.makeDirty();

    // Okay, I can't seem to get this to work yet.  The ColorAnim
    // object keeps saying "TypeError: this.init is not a function"
    // and I can't figure out how to fix this.  [shep 26.Nov.2007]
//     var cbox = this.colorBox;
//     cbox.style.border = "2px solid red;"
//     var cboxID = this.options.fieldNamespace + "_colorbox";
//     setTimeout(function() {
// 		 try {
// 		   var anim = YAHOO.util.ColorAnim(cboxID, { borderColor: { to: "#fff" } });
// 		   anim.animate();
// 		   cbox.style.border = "none";
// 		 } catch (xyz) {
// 		   alert(xyz);
// 		 }
// 	       }, 1);

  },

  makeDirty: function() {
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  }


};



//create a new object for this module:
Settings.COLOR_PICKER = {


  colorPickerSetting: null,


  picker: null,


  dialog: null,


  name: "Settings.COLOR_PICKER",


  onRGBChange: function(o) {
    Debug.println(YAHOO.lang.dump(o));
  },


  onRenderPickerDialog: function() {
    if (!this.picker) { //make sure that we haven't already created our Color Picker
      var pickerOptions = {
	container: this.dialog,
	images: {
	  PICKER_THUMB: "/html/js/yui/colorpicker/assets/picker_thumb.png",
	  HUE_THUMB: "/html/js/yui/colorpicker/assets/hue_thumb.png"
	},
	showhexcontrols: true,
	showhsvcontrols: true,
	showhexsummary: false
      };
      this.picker = new YAHOO.widget.ColorPicker("yui-picker", pickerOptions);
      this.picker.on("rgbChange", this.onRGBChange.bind(this));
      Debug.println("Initialized YAHOO ColorPicker for Setting editors.");
    }
  },


  init: function() {

    var dialogOptions = {
      width : "500px",
      close: true,
      fixedcenter : true,
      visible : false, 
      constraintoviewport : true,
      buttons : [
      { text: i18n("OK", "Submit"), handler: this.handleSubmit.bind(this), isDefault: true },
      { text: i18n("Cancel", "Cancel"), handler: this.handleCancel.bind(this) }
      ]
    };

    try {
      // Instantiate the Dialog
      this.dialog = new YAHOO.widget.Dialog("yui-picker-panel", dialogOptions);
      Debug.println("Initialized YAHOO Dialog for color picker.");
    } catch (x) {
      Debug.println("Error attempting YAHOO Dialog for color picker: ", x);
    }

    // Once the Dialog renders, we want to create our Color Picker
    // instance.
    this.dialog.renderEvent.subscribe(this.onRenderPickerDialog.bind(this));
			
    // If we wanted to do form validation on our Dialog, this
    // is where we'd do it.  Remember to return true if validation
    // passes; otherwise, your Dialog's submit method won't submit.
    this.dialog.validate = function() {
      return true;
    };

    // This dialog doesn't talk to a server, so it is not necessary to
    // do response handling.
    this.dialog.callback = {
      success: function() { },
      thisfailure: function() { }
    };

    // We're all set up with our Dialog's configurations;
    // now, render the Dialog
    this.dialog.render();

    //initialization complete:
    Debug.println("Rendered dialog for YAHOO color picker.");

  },

		
  handleSubmit: function() {
    this.colorPickerSetting.onPickColor.bind(this.colorPickerSetting)(document.getElementById(this.picker.ID.HEX).value);
    this.dialog.hide();
  },
 

  handleCancel: function() {
    //the cancel method automatically hides the Dialog
    this.dialog.cancel();
  }

   
};


Settings.ProjectLogoImageSelect = Class.create();
Settings.ProjectLogoImageSelect.prototype = {


  options: new Array(),


  initialize: function(options) {
    this.options = options;
    Settings.PROJECT_LOGO_IMAGE_SELECTORS.push(this);
  },


  init: function() {
    this.updateImagePreview();
  },


  onImageChange: function() {
    this.updateImagePreview();
    this.makeDirty();
  },


  updateImagePreview: function() {
    var newSrc = document.fm[this.options.fieldNamespace].options[document.fm[this.options.fieldNamespace].selectedIndex].value;
    if (newSrc.indexOf("def-val-") == 0) {
      newSrc = newSrc.substring(8);
    }
    newSrc = this.options.logoImageDirectory + newSrc;
    document.getElementById(this.options.fieldNamespace + "_preview").src = newSrc;
  },


  makeDirty: function() {
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  },


  updateLogoListing: function(newLogoImageDirectory, newDefaultLogo, newLogos) {

    // this can change if one of the logos was updated (in which case
    // the mini-version is automatically bumped).
    this.options.logoImageDirectory = newLogoImageDirectory;

    var selector = document.fm[this.options.fieldNamespace];

    var lastSelection = (selector.options.length > 0) ? selector.options[selector.selectedIndex].value : "";
    var lastSelectionDef = (lastSelection.indexOf("def-val-") != -1);
    var restoredSelection = false;

    selector.options.length = 0;

    if (newLogos.length > 0) {

      selector.options[0] = new Option(i18n("default_selection_indicator", "Default") + " (" + newDefaultLogo + ")", "def-val-" + newDefaultLogo);
      if (lastSelectionDef) {
	selector.options[0].selected = true;
	restoredSelection = true;
      }

      for (var i = 0; i < newLogos.length; i ++) {
	selector.options[i + 1] = new Option(newLogos[i], newLogos[i]);
	if (newLogos[i] == lastSelection) {
	  selector.options[i + 1].selected = true;
	  restoredSelection = true;
	}
      }

    }

    if (!restoredSelection) {
      this.updateImagePreview();
      this.makeDirty();
    }

  }


};


/**
 * Registered project logo image select settings (which want to listen
 * for logo image list updates using the logomanager view launched
 * from this view).
 */
Settings.PROJECT_LOGO_IMAGE_SELECTORS = new Array();


/**
 * Registered profile picture IMG elements (which want to listen for
 * changes in the selected profile picture from the
 * profilepictures/defaultprofilepictures view launched from this
 * view).
 */
Settings.PROFILE_PICTURE_IMAGES = new Array();

function updateFromProfilePictureManager(updateWithData) {
  var updateThese = Settings.PROFILE_PICTURE_IMAGES;
  for (var i = 0; i < updateThese.length; i ++) {
    updateThese[i].src    = updateWithData["url"];
    updateThese[i].width  = new String(updateWithData["width"]);
    updateThese[i].height = new String(updateWithData["height"]);
  }
}

/**
 * Namespace container for functions and data relating to the Project
 * Setup interface, particularly the Project Setup | Settings page.
 */
Settings.Project = new Array();


Settings.Project.openProjectRenameView = function(projectId, projectName) {
  Settings.gotoView("renameproject", true, "&projid=" + projectId + "&proj=" + encode_url_parameter(projectName));
};


Settings.Project.openProjectDeleteView = function(projectId, projectName) {
  Settings.gotoView("deleteproject", true, "&projid=" + projectId + "&proj=" + encode_url_parameter(projectName));
};



/**
 * Handles the synthetic setting that a given project to be activated
 * or deactivated from the Project Setup | Settings page.
 */
Settings.Project.Status = {


  /**
   * Handles a click on the Activate/Deactivate toggle button
   * representing a user request to activate or deactivate the project
   * being administered.  Dispatches a toggleProjectActive AJAX RPC
   * request.
   */
  toggle: function() {

    document.fm.toggleprojectstatus.disabled = true;

    var poststring = "";
    poststring = fm_append(poststring, "type=ajaxrpc");
    poststring = fm_append(poststring, "method=toggleProjectActive");
    poststring = fm_append(poststring, "p0="+document.fm.proj.value);
    poststring = fm_append(poststring, "p1="+(document.fm.projectstatus.value == "true" ? "false" : "true"));
    poststring = fm_append(poststring, "browserid="+document.fm.browserid.value);

    xmlpost_async(FORM_ACTION_READ_WRITE, poststring, document.fm.toggleprojectstatus.nextSibling, true, Settings.Project.Status.toggle_wakeup);

  },


  /**
   * Handles the response to a toggleProjectActive AJAX RPC request.
   * If the request completed successfully, the message explaining the
   * status of the project's current status and the label for the
   * toggle button are updated accordingly.
   */
  toggle_wakeup: function(responseText) {

    document.fm.toggleprojectstatus.disabled = false;

    if (wasUnauthorized(responseText)) {
      failureUnauthorized();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(responseText)) {
      failureServerUnavailable();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var error = findErrorAndFeedback(responseText)[0];
    if (error != null && error != "" && error != "undefined") {
      alert(error);
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var results;
    eval("results = " + responseText);

    if (results["success"]) {
      document.fm.projectstatus.value = results["active"] ? "true" : "false";
      document.getElementById("projectstatusmessage").innerHTML =
	(results["active"]) ?
	i18n("setting_project_status_active_message", "This project is <b>active</b>.") :
	i18n("setting_project_status_inactive_message", "This project is <b>inactive</b>.");
      var fmt = new MessageFormat(results["active"] ?
				  i18n("setting_project_status_deactivate_project_button_title",
				       "Deactivate {0} Project") :
				  i18n("setting_project_status_activate_project_button_title",
				       "Activate {0} Project"));
      buttonValue = fmt.format(document.fm.projectdisplayname.value);
      document.fm.toggleprojectstatus.value = buttonValue;
    }
    else {
      alert(i18n("setting_project_status_unexpected_error_message",
		 "The project status could not be changed."));
    }

  }


};


/**
 * Handles the synthetic setting that allows support for the
 * Published/Draft feature to be activated for a given project from
 * the Project Setup | Settings page.
 */
Settings.Project.DraftSupport = {


  /**
   * Handles a click on the Activate button representing a user
   * request to activate support for the Published/Draft feature for
   * the project being administered.  A confirmation dialog is raised
   * before the activateDraftModeSupport AJAX RPC call is dispatched.
   */
  activate: function() {

    var fmt = new MessageFormat(i18n("setting_project_draftsupport_activate_confirmation_message",
				     "You are about to activate the Publish/Draft feature for the '{0}' project.  This operation cannot be reversed.\\nAre you sure you want to continue?\\n\\nClick OK to continue.\\nClick Cancel to Abort."));
    if (!confirm(fmt.format(document.fm.projectdisplayname.value))) {
      return;
    }
    document.fm.activatedraftsupport.disabled = true;

    var poststring = "";
    poststring = fm_append(poststring, "type=ajaxrpc");
    poststring = fm_append(poststring, "method=activateDraftModeSupport");
    poststring = fm_append(poststring, "p0="+document.fm.proj.value);
    poststring = fm_append(poststring, "browserid="+document.fm.browserid.value);

    xmlpost_async(FORM_ACTION_READ_WRITE, poststring, document.fm.activatedraftsupport.nextSibling, true, Settings.Project.DraftSupport.activate_wakeup);

  },


  /**
   * Handles the response to a activateDraftModeSupport AJAX RPC
   * request.  If the request completed successfully, the message
   * explaining the status of the project's support for the
   * Published/Draft feature is updated accordingly.
   */
  activate_wakeup: function(responseText) {

    if (wasUnauthorized(responseText)) {
      failureUnauthorized();
      document.fm.toggleprojectstatus.disabled = false;
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(responseText)) {
      failureServerUnavailable();
      document.fm.toggleprojectstatus.disabled = false;
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var error = findErrorAndFeedback(responseText)[0];
    if (error != null && error != "" && error != "undefined") {
      document.fm.toggleprojectstatus.disabled = false;
      alert(error);
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var results;
    eval("results = " + responseText);

    if (results["success"]) {
      document.fm.projectstatus.value = results["active"] ? "true" : "false";
      document.getElementById("draftsupportmessage").innerHTML =
	i18n("setting_project_draftsupport_active_message",
	     "This project supports the Publish/Draft feature.");
      document.fm.activatedraftsupport.value = i18n("setting_project_draftsupport_activate_disabled_button_title",
						    "Draft Support Activated");
    }
    else {
      alert(i18n("setting_project_draftsupport_unexpected_error_message",
		 "The Published/Draft Mode feature could not be activated for this project."));
    }

  }


};


Settings.RenameProject = {


  init: function(id, name, disp) {
  },


  onNewNameChange: function() {
  },


  onDisplayNameChange: function() {
  }


};


Settings.DeleteProject = {


  id: null,


  name: null,


  disp: null,


  init: function(id, name, disp) {
    Settings.DeleteProject.id = id;
    Settings.DeleteProject.name = name;
    Settings.DeleteProject.disp = disp;
    if (document.fm.confirmchk) {
      document.fm.confirmchk.checked = false;
      Settings.DeleteProject.onChangeConfirmCheckboxState();
    }
  },


  confirmDeleteRequest: function() {

    if (document.fm.go.disabled) {
      return false;
    }

    return confirm("You are about to permanently delete the project " + Settings.DeleteProject.disp + " / " +
		   Settings.DeleteProject.name + " (id " + Settings.DeleteProject.id + ").\n" +
		   "There is no way to undo this operation.  All the entries and properties of this project will be deleted.\n\n" +
		   "Click OK to continue.\n" + "Click Cancel to cancel this operation.");
    
  },


  onChangeConfirmCheckboxState: function() {
    document.fm.go.disabled = !document.fm.confirmchk.checked;
  }


};


/**
 * Namespace container for functions and data relating to the Server
 * Setup | People page.
 */
Settings.People = {


  /**
   * Handles a click on the checkbox representing the active/inactive
   * state of a user account for a given user data table row,
   * dispatching a toggleUserActive AJAX RPC request.
   */
  onToggleUserStatus: function(oArgs) {
    var elCheckbox = oArgs.target;
    elCheckbox.disabled = true;
    var elRecord = this.getRecord(elCheckbox);
    var id = elRecord.getData("id");
    var username = elRecord.getData("username");
    var poststring = "type=ajaxrpc&method=toggleUserActive" +
    "&p0=" + id +
    "&p2=" + (elCheckbox.checked ? "true" : "false") +
    "&browserid=" + document.fm.browserid.value;
    xmlpost_async(FORM_ACTION_READ_WRITE, poststring, null, true, Settings.People.onToggleUserStatus_wakeup, elCheckbox);
  },


  /**
   * Handles the response to a toggleUserActive AJAX RPC request.  If
   * the request completed successfully, there is currently no
   * feedback.  If there was an error that prevented the
   * active/inactive toggle operation from succeeding, a dialog is
   * raised with the error message, and the checkbox state is reverted
   * to reflect that is has not changed on the server side.
   */
  onToggleUserStatus_wakeup: function(responseText, elCheckbox) {

    elCheckbox.disabled = false;

    if (wasUnauthorized(responseText)) {
      failureUnauthorized();
      elCheckbox.checked = !elCheckbox.checked;
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(responseText)) {
      failureServerUnavailable();
      elCheckbox.checked = !elCheckbox.checked;
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var error = findErrorAndFeedback(responseText)[0];
    if (error != null && error != "" && error != "undefined") {
      alert(error);
      elCheckbox.checked = !elCheckbox.checked;
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var results;
    eval("results = " + responseText);

    if (results["success"]) {
    }
    else {
      elCheckbox.checked = !elCheckbox.checked;
      alert(results["error"]);
    }

  },


  onUserDataTablePageRequest: function() {
    document.fm.search.disabled = true;
    document.fm.reset.disabled = true;
    setWaitCursor(true);
    var throbber = document.getElementById("nav_throbber");
    xmlprogress(1, throbber);
  },


  onUserDataTablePageResponse: function() {
    document.fm.search.disabled = false;
    document.fm.reset.disabled = false;
    var throbber = document.getElementById("nav_throbber");
    xmlprogress(0, throbber);
    setWaitCursor(false);
  },


  /**
   * Formats the user name for a row in a user data table to include a
   * link to the Personal Setup interface for the current user.
   */
  userNameFormatter: function(elCell, oRecord, oColumn, oData) {
    YAHOO.util.Dom.addClass(elCell, "username");
    elCell.innerHTML = '<a title="' + i18n('ssetup_people_username_title_tip', 'Click to Edit') + '"href="/traction/post?handler=personalsetup&userid=' +
     oRecord.getData("id") + '">' + 
     oRecord.getData("username") + '</a>';
  },


  /**
   * Formats the server-admin flag for a row in a user data table to
   * include an image depicting a green checkbox if the flag indicates
   * that the user has Server Setup permission.
   */
  serverAdminFormatter: function(elCell, oRecord, oColumn, oData) {
    YAHOO.util.Dom.addClass(elCell, "serveradmin");
    elCell.innerHTML = (oRecord.getData("serveradmin") == "true") ?
      '<img src="/images/modern/check_on_green.gif" border="0" width="11" height="11">' :
      "";
  }


};

//i18n("column_sort_tip_ascending")
//i18n("column_sort_tip_descending")

/**
 * Handles browsing of existing users on the Browse tab of the Server
 * Setup | People page.
 */
Settings.People.Browser = {


  existingUsers: null,


  /**
   * Performs initialization associated with the Browse tab of the
   * Server Setup | People page.  Invoked on window load for the
   * Server Setup | People page.
   */
  init: function() {

    var options = {

      // The view type for retrieving data
      dataSourceViewType: "usertable",

      // DOM & Form information
      historyFieldName: "yui-history-field",
      historyFrameName: "yui-history-frame",
      containerID: "userlist",
      filterField: document.fm.filter,
      statusFilterCheckbox: document.fm.showinactive,
      searchButton: document.fm.search,
      resetButton: document.fm.reset,
      form: document.fm,

      emptyMsg: i18n("ssetup_people_usertable_no_records_message", "No user accounts found."),

      // Listeners
      toggleStatusListener: Settings.People.onToggleUserStatus,
      requestListener: Settings.People.onUserDataTablePageRequest,
      responseListener: Settings.People.onUserDataTablePageResponse,

      // Request defaults / initial values
      defaultSortKey: "username",
      defaultSortOrder: "asc",
      defaultRowsPerPage: 15,

      // DataSource fields / DataTable columns
      columnDefs: [
        {
	  key: "username",
	  label: i18n("ssetup_people_usertable_column_heading_username", "User Name"),
	  sortable: true,
	  formatter: Settings.People.userNameFormatter
	},
        {
	  key: "fullname",
	  label: i18n("ssetup_people_usertable_column_heading_fullname", "Full Name"),
	  sortable: true
	},
        {
	  key: "email",
	  label: i18n("ssetup_people_usertable_column_heading_email", "Email"),
	  sortable: true,
	  formatter: "email"
	},
        {
	  key: "serveradmin",
	  label: i18n("ssetup_people_usertable_column_heading_serveradmin", "Server Admin"),
	  sortable: true,
	  formatter: Settings.People.serverAdminFormatter
	},
        {
	  key: "active",
	  label: i18n("ssetup_people_usertable_column_heading_active", "Active"),
	  sortable: true,
	  formatter: YAHOO.widget.DataTable.formatCheckbox
	}
      ],
      fields: [ "username", "fullname", "email", "serveradmin", "id", "active" ]

    };
    

    Settings.People.Browser.existingUsers = new Traction.TabularDataBrowser(options);

    tabupdate(document.fm.people_tab.value);

  }


};


/**
 * Handles the new user creation operations on the Add Users tab of
 * the Server Setup | People page.
 */
var SPEC = {


  /**
   * A list of maps, each containing field names and field values for
   * the fields in the add new user form, populated properly by the
   * initFieldStates function below.
   */
  INITIAL_FIELD_STATES: [
    { name: "newuser", value: "" },
    { name: "newpassword", value: "" },
    { name: "newfullname", value: "" },
    { name: "newemail", value: "" },
    { name: "newaddtogroupid", value: "" },
    { name: "newactivechk", value: true },
    { name: "newpwchange", value: false },
    { name: "newwelcome", value: true },
    { name: "newserverdigests", value: true }
  ],


  /**
   * The user table for the Add Users tab, showing users added during
   * the current visit to the Server Setup | People page.
   */
  newUsers: null,


  /**
   * The form used by control (normally document.fm)
   */
  fm: null,

  /**
   * Indicates if there should be a new users table.
   */
  noNewUsersTable: false,

  /**
   *
   */
  hasMoreOptions: true,

  /**
   * { scope: scope, success: function, failure: function, argument: args }
   * 
   * The callback will be called with a single parameter
   * {responseText: responseText, argument: argument, userinfo: userinfo, error: errorMessage} matching the
   * style of YAHOO.util.Connect.asyncRequest
   *
   */
  callback: null,

  /**
   * Performs initialization associated with the Add Users tab of the
   * Server Setup | People page.  Invoked on window load for the
   * Server Setup | People page.
   */
  init: function(event, form, noNewUsersTable, hasMoreOptions, callback) {
    SPEC.fm = form || document.fm;
    SPEC.noNewUsersTable = noNewUsersTable;
    SPEC.hasMoreOptions = hasMoreOptions;
    SPEC.callback = callback;
    SPEC.initFieldStates();
    SPEC.initNewUsersTable();
  },

  reset: function() {
    SPEC.resetNewUserFields(true);
  },

  /**
   * Initializes the data that keeps track of the add new user form
   * initial field states, so that the state can be restored.  Invoked
   * on window load for the Server Setup | People page by init
   * (above).
   */
  initFieldStates: function() {
    for (var i = 0; i < SPEC.INITIAL_FIELD_STATES.length; i ++) {
      var fieldState = SPEC.INITIAL_FIELD_STATES[i];
      var field = SPEC.fm[fieldState.name];
      // If the field is disabled, as in the corresponding feature
      // isn't available, just go to the next one.
      if (field.disabled) {
	continue;
      }
      if (field.nodeName == "SELECT") {
	fieldState.value = currentSelValue(field);
      }
      else if (field.type == "checkbox") {
	fieldState.value = field.checked;
      }
      else {
	fieldState.value = field.value;
      }
//       if (field.type == "text") {
// 	Events.attach(field, Events.KeyUp, window, SPEC.onFieldKeyUp);
//       }
    }
  },


  /**
   * Initializes the new user data table object,
   * SPEC.newUsers, mainly by creating the
   * DataSource and DataTable.  Invoked on window load for the Server
   * Setup | People page by init (above).
   */
  initNewUsersTable: function() {

    if (SPEC.noNewUsersTable) return;

    var options = {
      containerID: "newuserlist",
      fields: [ { key: "id" }, { key: "username" }, { key: "fullname" }, { key: "email" }, { key: "group" }, { key: "serveradmin" }, { key: "active" } ],
      sortOptions: {
	key: "username",
	dir:"asc"
      },
      existingTableID: "newusertable",
      columnDefs: [
        {
	  key: "username",
	  label: i18n("ssetup_people_usertable_column_heading_username", "User Name"),
	  sortable: true,
	  formatter: Settings.People.userNameFormatter
	},
        {
	  key: "fullname",
	  label: i18n("ssetup_people_usertable_column_heading_fullname", "Full Name"),
	  sortable: true
	},
        {
	  key: "email",
	  label: i18n("ssetup_people_usertable_column_heading_email", "Email"),
	  sortable: true,
	  formatter: "email"
	},
        {
	  key: "group",
	  label: i18n("ssetup_people_usertable_column_heading_group", "Group"),
	  sortable: true
	},
        {
	  key: "serveradmin",
	  label: i18n("ssetup_people_usertable_column_heading_serveradmin", "Server Admin"),
	  sortable: true,
	  formatter: Settings.People.serverAdminFormatter
	},
        {
	  key: "active",
	  label: i18n("ssetup_people_usertable_column_heading_active", "Active"),
	  sortable: true,
	  formatter: YAHOO.widget.DataTable.formatCheckbox
	}
      ]
    };

    SPEC.newUsers = new Traction.TabularDataAccumulator(options);
    SPEC.newUsers.dataWidgets.dataTable.subscribe("checkboxClickEvent",
								     Settings.People.onToggleUserStatus,
								     SPEC.newUsers.dataWidgets.dataTable,
								     true);

  },


  /**
   * An event handler that invokes the createNewUser function (below)
   * if the Enter key is pressed in a text field that is a member of
   * the Add New User form.
   */
  onFieldKeyUp: function(event) {
    switch(keyCode2(event)) {
      case 13: // enter
      if (!SPEC.fm.createuser.disabled) {
	SPEC.createNewUser();
      }
    }
  },


  /**
   * Triggers a createNewUser AJAX RPC request to create a new
   * Traction user account using the user name and other properties
   * entered by the administrator.  Invoked by the "Add User" button's
   * onclick handler.
   */
  createNewUser: function() {

    if ( SPEC.fm.newwelcome.checked && trim(SPEC.fm.newemail.value) == "") {
      var msg = i18n("ssetup_people_welcome_email_requires_email_address_alert_message",
		     "Please specify an email address or un-check 'Send welcome email'.");
      if (this.callback) {
	SPRC.fireCallback(this.callback, null, null, msg, null);	
      } else {
	alert(msg);
      }
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    SPEC.fm.createuser.disabled = true;

    var poststring = "";
    poststring = fm_append(poststring, "type=ajaxrpc");
    poststring = fm_append(poststring, "method=createNewUser");
    // AjaxRPC::createNewUser -
    // String userName, String password, String fullName, String emailAddress, boolean active, boolean forcePasswordChange, boolean sendWelcomeMessage
    poststring = fm_append(poststring, "p0="+encode_url_parameter(SPEC.fm.newuser.value));
    poststring = fm_append(poststring, "p1="+encode_url_parameter(SPEC.fm.newpassword.value));
    poststring = fm_append(poststring, "p2="+encode_url_parameter(SPEC.fm.newfullname.value));
    poststring = fm_append(poststring, "p3="+encode_url_parameter(SPEC.fm.newemail.value));
    poststring = fm_append(poststring, "p4="+(SPEC.fm.newactivechk.checked ? "true" : "false"));
    poststring = fm_append(poststring, "p5="+(SPEC.fm.newpwchange.checked ? "true" : "false"));
    poststring = fm_append(poststring, "p6="+encode_url_parameter(currentSelValue(SPEC.fm.newaddtogroupid)));
    poststring = fm_append(poststring, "p7="+(SPEC.fm.newwelcome.checked ? "true" : "false"));
    poststring = fm_append(poststring, "p8="+(SPEC.fm.newserverdigests.checked ? "true" : "false"));
    poststring = fm_append(poststring, "browserid="+encode_url_parameter(SPEC.fm.browserid.value));

    xmlpost_async(FORM_ACTION_READ_WRITE,
		  poststring,
		  SPEC.fm.createuser.nextSibling,
		  true,
		  SPEC.createNewUser_wakeup,
		  { group: currentSel(SPEC.fm.newaddtogroupid), callback: this.callback });

  },


  /**
   * Handles the response to a createNewUser AJAX RPC request.  On
   * success, a new row is added to the new user data table;
   * otherwise, the error message is displayed appropriately.
   */
  createNewUser_wakeup: function(responseText, params) {

    var groupName = params.group;
    var callback = params.callback;
    
    SPEC.fm.createuser.disabled = false;

    if (wasUnauthorized(responseText)) {
      if (callback != null) SPRC.fireCallback(callback);	
      failureUnauthorized();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(responseText)) {
      if (callback != null) SPRC.fireCallback(callback);	
      failureServerUnavailable();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var message = null;
    // Indicates whether there was an error creating the account.
    var isError = false;
    // Indicates whether there was a non-fatal error creating the
    // account (such as not being able to send the welcome email) that
    // the administrator should be warned about.
    var isWarning = true;
    var results = null;

    var error = findErrorAndFeedback(responseText)[0];
    if (error != null && error != "" && error != "undefined") {
      message = error;
      isError = true;
    }
    else {

      eval("results = " + responseText);

      if (results["success"]) {
	// Fields in results["userinfo"]: id, username, fullname,
	// active, serveradmin, email
	// we don't print a message on success. [shep 11.Jan.2008]
	if (results["welcome"] && !results["welcome"]["success"]) {
	  message = results["welcome"]["error"];
	  if (!message) {
	    message = (new MessageFormat(i18n("ssetup_people_newuser_welcome_message_generic_error",
					      "The account was created successfully but the welcome message could not be sent to {0}. " +
					      "Please check the email address, your server's SMTP configuration, and the Outgoing Email \"From\" Address setting."))).format(results["userinfo"]["email"]);
	  }
	  isWarning = true;
	}
      }
      else {
	message = results["error"];
	isError = true;
      }

    }

    var msgContainer = document.getElementById("people-create-user-status");
    if (msgContainer) {
      var msgDIV = document.createElement("DIV");

      if (isError) {
	msgDIV.className = "error";
      }
      else {
	if (isWarning) {
	  msgDIV.className = "warning";
	}
	SPEC.resetNewUserFields();
	if ( msgContainer.hasChildNodes() ) {
	  msgContainer.removeChild( msgContainer.firstChild );
	}

      }

      if (msgContainer.hasChildNodes()) {
	msgContainer.removeChild( msgContainer.firstChild );
      }

      if (message != null) {
// 	msgDIV.appendChild(document.createTextNode(message));
	msgDIV.innerHTML = message;
	msgContainer.appendChild(msgDIV);
      }
    }

    // show the table (it's hidden initially)
    var newUsersContainer = document.getElementById("newuserlist");
    if (newUsersContainer) {
      newUsersContainer.style.display = "block";

      // update the DataTable listing the new users.
      if (!isError && typeof(results.userinfo) != "undefined") {
	results.userinfo["group"] = groupName;
	SPEC.newUsers.dataWidgets.dataTable.addRow( results.userinfo );
      }
    }

    SPEC.fireCallback(callback, responseText, results.userinfo, isError ? message : null, isWarning ? message : null);
  },

  fireCallback: function(callback, responseText, userinfo, errorMessage, warningMessage) {
    if (callback) {
      var obj = { responseText: responseText, argument: callback.argument, userinfo: userinfo, error: errorMessage, warning: warningMessage };
      var func = userinfo ? callback.success : callback.failure;
      if (callback.scope) {
	func.apply(callback.scope, [obj]);
      } else {
	func(obj);
      }
      return true;
    } else {
      return false;
    }
  },

  /**
   * Resets the text fields used for the new user creation request
   * (Username, Password, Full Name, Email Address), and other fields
   * (Add to Group, Send Welcome Email, Require Password Change,
   * Activate User) as well, if the argument for the allFields
   * parameter is true (as it might be if invoked from an explicit
   * reset request button handler).
   */
  resetNewUserFields: function(allFields) {
    for (var i = 0; i < SPEC.INITIAL_FIELD_STATES.length; i ++) {
      var fieldState = SPEC.INITIAL_FIELD_STATES[i];
      var field = SPEC.fm[fieldState.name];
      if (field.type == "text") {
	field.value = fieldState.value;
      }
      else if (allFields) {
	if (field.type == "checkbox") {
	  field.checked = fieldState.value;
	}
	else if (field.nodeName == "SELECT") {
	  selectIndexOfValue(field, fieldState.value);
	}
      }
    }
    setTimeout(function() {
		 SPEC.fm.newuser.focus();
	       }, 100);
  },


  /**
   * Requests a new random password from the server.
   */
  createRandomPassword: function() {
    SPEC.fm.createrandompassword.disabled = true;
    var poststring = "";
    poststring = fm_append(poststring, "type=ajaxrpc");
    poststring = fm_append(poststring, "method=createRandomPassword");
    poststring = fm_append(poststring, "browserid="+SPEC.fm.browserid.value);
//     poststring = fm_append(poststring, "p0=8"); // could set length of random password here; default is 8
    xmlpost_async(FORM_ACTION_READ_WRITE, poststring, null, true, SPEC.createRandomPassword_wakeup);
  },


  /**
   * Handles the response to a createRandomPassword AJAX RPC request.
   * If the request completed successfully, the new random password is
   * inserted into the newpassword field.
   */
  createRandomPassword_wakeup: function(responseText) {

    SPEC.fm.createrandompassword.disabled = false;

    if (wasUnauthorized(responseText)) {
      failureUnauthorized();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(responseText)) {
      failureServerUnavailable();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var error = findErrorAndFeedback(responseText)[0];
    if (error != null && error != "" && error != "undefined") {
      alert(error);
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    SPEC.fm.newpassword.value = responseText;

  }

};
Settings.People.Creator = SPEC;


Settings.Projects = {


  onLoad: function() {

    if (document.fm["server_enableincomingtrackback"]) {
      setup_hideshow_select("server_enableincomingtrackback",
			    ['true'],
			    [],
			    ["journal_trackbackprojid"],
			    false);
    }

  },


  /**
   * Handles a click on the checkbox representing the active/inactive
   * state of a project for a given project data table row,
   * dispatching a toggleProjectActive AJAX RPC request.
   */
  onToggleProjectStatus: function(oArgs) {
    var elCheckbox = oArgs.target;
    elCheckbox.disabled = true;
    var elRecord = this.getRecord(elCheckbox);
    var id = elRecord.getData("id");
    var projname = elRecord.getData("name");
    var poststring = "type=ajaxrpc&method=toggleProjectActive" +
    "&p0=" + projname +
    "&p1=" + (elCheckbox.checked ? "true" : "false") +
    "&browserid=" + document.fm.browserid.value;
    xmlpost_async(FORM_ACTION_READ_WRITE, poststring, null, true, Settings.Projects.onToggleProjectStatus_wakeup, elCheckbox);
  },


  /**
   * Handles the response to a toggleProjectActive AJAX RPC request.
   * If the request completed successfully, there is currently no
   * feedback.  If there was an error that prevented the
   * active/inactive toggle operation from succeeding, a dialog is
   * raised with the error message, and the checkbox state is reverted
   * to reflect that is has not changed on the server side.
   */
  onToggleProjectStatus_wakeup: function(responseText, elCheckbox) {

    elCheckbox.disabled = false;

    if (wasUnauthorized(responseText)) {
      failureUnauthorized();
      elCheckbox.checked = !elCheckbox.checked;
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(responseText)) {
      failureServerUnavailable();
      elCheckbox.checked = !elCheckbox.checked;
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var error = findErrorAndFeedback(responseText)[0];
    if (error != null && error != "" && error != "undefined") {
      alert(error);
      elCheckbox.checked = !elCheckbox.checked;
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var results;
    eval("results = " + responseText);

    if (results["success"]) {
    }
    else {
      elCheckbox.checked = !elCheckbox.checked;
      alert(results["error"]);
    }

  },


  onProjectDataTablePageRequest: function() {
    document.getElementById("prevLink").disabled = true;
    document.getElementById("nextLink").disabled = true;
    document.fm.search.disabled = true;
    document.fm.reset.disabled = true;
    setWaitCursor(true);
    var throbber = document.getElementById("nav_throbber");
    xmlprogress(1, throbber);
  },


  onProjectDataTablePageResponse: function() {
    document.fm.search.disabled = false;
    document.fm.reset.disabled = false;
    var throbber = document.getElementById("nav_throbber");
    xmlprogress(0, throbber);
    setWaitCursor(false);
  },


  /**
   * Formats the project name for a row in the project table to
   * include a link to the Project Setup interface for the current
   * user.
   */
  projectNameFormatter: function(elCell, oRecord, oColumn, oData) {
    YAHOO.util.Dom.addClass(elCell, "projname");
    elCell.innerHTML =  '<a title="Click to Edit" href="/traction/post?handler=admin&proj='+ 
      encode_url_parameter(oRecord.getData("name")) +'">' + 
      oRecord.getData("name") + '</a>';
  },


  onDeleteProjectChange: function() {
    document.fm.server_delete_project_go.disabled = (document.fm.server_delete_project_target.value == "-1");
  },


  openProjectDeleteView: function() {
    Settings.gotoView("deleteproject", true, "&projid=" + document.fm.server_delete_project_target.value);
  }


};


/**
 * Handles browsing of existing project on the Browse tab of the
 * Server Setup | Projects page.
 */
Settings.Projects.Browser = {


  existingProjects: null,


  /**
   * Performs initialization associated with the Browse tab of the
   * Server Setup | Projects page.  Invoked on window load for the
   * Server Setup | Projects page.
   */
  init: function() {

    var options = {

      // The view type for retrieving data
      dataSourceViewType: "projecttable",

      // DOM & Form information
      historyFieldName: "yui-history-field",
      historyFrameName: "yui-history-frame",
      containerID: "projectlist",
      filterField: document.fm.filter,
      statusFilterCheckbox: document.fm.showinactive,
      searchButton: document.fm.search,
      resetButton: document.fm.reset,
      form: document.fm,

      emptyMsg: i18n("ssetup_projects_projecttable_no_records_message", "No projects found."),

      // Listeners
      toggleStatusListener: Settings.Projects.onToggleProjectStatus,
      requestListener: Settings.Projects.onProjectDataTablePageRequest,
      responseListener: Settings.Projects.onProjectDataTablePageResponse,

      // Request defaults / initial values
      defaultSortKey: "name",
      defaultSortOrder: "asc",
      defaultRowsPerPage: 15,

      // DataSource fields / DataTable columns
      columnDefs: [
      {
	key: "name",
	label: i18n("ssetup_projects_name_col_heading", "Name"),
	sortable: true,
	formatter: Settings.Projects.projectNameFormatter
      },
      {
	key: "displayname",
	label: i18n("ssetup_projects_displayname_col_heading", "Display Name"),
	sortable: true
      },
      {
	key: "active",
	label: i18n("ssetup_projects_active_col_heading", "Active"),
	sortable: true,
	formatter: YAHOO.widget.DataTable.formatCheckbox
      }
      ],
      fields: [ "name", "displayname", "mailbox", "active", "id" ]

    };

    Settings.Projects.Browser.existingProjects = new Traction.TabularDataBrowser(options);
    tabupdate(document.fm.projects_tab.value);

  }


};


var SPRC = {

  /**
   * A list of maps, each containing field names and field values for
   * the fields in the add new project form, populated properly by the
   * initFieldStates function below.
   */
  INITIAL_FIELD_STATES: [
    { name: "newproj_name", value: "" },
    { name: "newproj_displayname", value: "" },
    { name: "newproj_active", value: true }
  ],


  /**
   * Keeps track of whether the user has entered anything specifically
   * in the display name field for the new project; if it's set to
   * true, we know the user has entered a custom display name, and
   * that therefore we should not be echoing the new project name
   * they're typing in the display name field.  This field is reset to
   * false in the resetNewProjectFields function.
   */
  nameDirty: false,


  /**
   * The project table for the Add Projects tab, showing projects
   * added during the current visit to the Server Setup | Projects
   * page.
   */
  newProjects: null,

  /**
   * The form used by control (normally document.fm)
   */
  fm: null,

  /**
   * Indicates if there should be a new projects table.
   */
  noNewProjectsTable: false,

  /**
   *
   */
  hasMoreOptions: true,

  /**
   * { scope: scope, success: function, failure: function, moreOptions: function, argument: args }
   * 
   * The callback will be called with a single parameter
   * {responseText: responseText, argument: argument, error: errorMessage, projectinfo: projectinfo} matching the
   * style of YAHOO.util.Connect.asyncRequest
   *
   */
  callback: null,

  /**
   * Performs initialization associated with the Add Projects tab of
   * the Server Setup | Projects page.  Invoked on window load for the
   * Server Setup | Projects page.
   */
  init: function(event, form, noNewProjectsTable, hasMoreOptions, callback) {
    SPRC.fm = form || document.fm;
    SPRC.noNewProjectsTable = noNewProjectsTable;
    SPRC.hasMoreOptions = hasMoreOptions;
    SPRC.callback = callback;
    SPRC.initFieldStates();
    SPRC.initNewProjectsTable();
  },

  reset: function() {
    SPRC.resetNewProjectFields(true);
  },

  /**
   * Initializes the data that keeps track of the add new project form
   * initial field states, so that the state can be restored.  Invoked
   * on window load for the Server Setup | Projects page by init
   * (above).
   */
  initFieldStates: function() {
    SPRC.nameDirty = false;
    for (var i = 0; i < SPRC.INITIAL_FIELD_STATES.length; i ++) {
      var fieldState = SPRC.INITIAL_FIELD_STATES[i];
      var field = SPRC.fm[fieldState.name];

      if (field.nodeName == "SELECT") {
	fieldState.value = currentSelValue(field);
      }
      else if (field.type == "checkbox") {
	fieldState.value = field.checked;
      }
      else {
	fieldState.value = field.value;
      }
    }
  },


  /**
   * Initializes the new project data table object,
   * SPRC.newProjects, mainly by creating the
   * DataSource and DataTable.  Invoked on window load for the Server
   * Setup | Projects page by init (above).
   */
  initNewProjectsTable: function() {

    if (SPRC.noNewProjectsTable) return;

    var options = {
      containerID: "newprojectlist",
      fields: [ { key: "id" }, { key: "name" }, { key: "displayname" }, { key: "active" }, { key: "template" } ],
      sortOptions: {
	key: "name",
	dir:"asc"
      },
      existingTableID: "newprojecttable",
// 	    result.put("projectinfo", projInfo);

// 	    projInfo.put("id", newProject.getId());
// 	    projInfo.put("name", newProject.getName());
// 	    projInfo.put("displayname", newProject.getDisplayName());
// 	    projInfo.put("active", newProject.isActive() ? "checked" : "" );
// 	    projInfo.put("template", ((projectTemplate == null) ? "" : projectTemplate.getName()));
      columnDefs: [
        {
	  key: "name",
	  label: i18n("ssetup_projects_name_col_heading", "Name"),
	  sortable: true,
	  formatter: Settings.Projects.projectNameFormatter
	},
        {
	  key: "displayname",
	  label: i18n("ssetup_projects_displayname_col_heading", "Display Name"),
	  sortable: true
	},
        {
	  key: "active",
	  label: i18n("ssetup_projects_active_col_heading", "Active"),
	  sortable: true,
	  formatter: YAHOO.widget.DataTable.formatCheckbox
	},
        {
	  key: "template",
	  label: i18n("ssetup_projects_settings_source_col_heading", "Template / Project"),
	  sortable: true
	}
      ]
    };

    SPRC.newProjects = new Traction.TabularDataAccumulator(options);
    SPRC.newProjects.dataWidgets.dataTable.subscribe("checkboxClickEvent",
									  Settings.Projects.onToggleProjectStatus,
									  SPRC.newProjects.dataWidgets.dataTable,
									  true);
//       this.myDataSource = new YAHOO.util.DataSource(YAHOO.util.Dom.get("newprojects")); 
//       this.myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE; 
//       this.myDataSource.responseSchema = { 
// 	fields: [ { key: "id" }, { key: "name" }, { key:"displayname" }, { key:"active" }, { key: "template" } ] 
//       }; 
	 
//       this.myDataTable = new YAHOO.widget.DataTable("newprojecttable", myColumnDefs, this.myDataSource, 
// 						    {
// 						    sortedBy:{key:"name",dir:"asc"}}); 

//       this.myDataTable.subscribe("checkboxClickEvent", Settings.Projects.onToggleProjectStatus, this.myDataTable, true);

//     };

  },


  /**
   * An event handler that invokes the createNewProject function (below)
   * if the Enter key is pressed in a text field that is a member of
   * the Add New Project form.
   */
  onFieldKeyUp: function(event) {
    switch(keyCode2(event)) {
      case 13: // enter
      if (!SPRC.fm.newproj_add.disabled) {
	SPRC.createNewProject();
      }
    }
  },


  /**
   * This function is used both in the Server Setup | Projects page's
   * Add Projects tab, and in the Advanced Project Creation view
   * (type=newproject).
   */
  onChangeNewProjectName: function() {
    SPRC.nameDirty = true;
  },


  /**
   * This function is used both in the Server Setup | Projects page's
   * Add Projects tab, and in the Advanced Project Creation view
   * (type=newproject).
   */
  onChangeNewProjectDisplayName: function() {
    if (!SPRC.nameDirty) {
      SPRC.fm.newproj_name.value = SPRC.sanitizeName(SPRC.fm.newproj_displayname.value);
    }
  },

  sanitizeName: function(str) {
    var illegal = /[ \"_?\,\.!#;:@$%^&*\=\~\/\\|\(\)\[\]\{\}\'\`\<\>\+\-]+/g;
    return str.replace(illegal, "");
  },

  /**
   * Triggers a createNewProject AJAX RPC request to create a new
   * Traction project using the project name and other properties
   * entered by the administrator.  Invoked by the "Add Project"
   * button's onclick handler.
   */
  createNewProject: function() {

    var editFirst = document.getElementById("project-settings-edit-true").checked;
    if (editFirst) {
      SPRC.createNewProjectEditSettings();
    } else {
      SPRC.createNewProjectNormal();
    }

  },


  createNewProjectNormal: function() {

    SPRC.fm.newproj_add.disabled = true;

    if (trim(SPRC.fm.newproj_name.value) == "") {
      var msg = i18n("ssetup_projects_new_project_requires_name_alert_message", "Please enter a name for the new project.");
      if (this.callback) {
	// added a callback that will be used by the dialog [ajm 19.Mar.2009]
	SPRC.fireCallback(this.callback, null, null, msg);	
      } else {
	alert(msg);
      }
      SPRC.fm.newproj_add.disabled = false;
      SPRC.fm.newproj_displayname.focus();
      return;
    }

    // As opposed to a project template.
    var useProject = document.getElementById("project-settings-source-project").checked;
    var projVal = SPRC.fm.newproj_project.value;
    if (useProject) {
      if (projVal.trim() == "") {
	var msg = i18n("ssetup_projects_new_project_copy_requires_project_alert_message", "Please enter the name of a project to copy.");
	if (this.callback) {
	  // added a callback that will be used by the dialog [ajm 19.Mar.2009]
	  SPRC.fireCallback(this.callback, null, null, msg);	
	} else {
	  alert(msg);
	}
	SPRC.fm.newproj_project.focus();
	SPRC.fm.newproj_add.disabled = false;
	return;
      }
      projVal = "::" + projVal;
    }

    var poststring = "";
    poststring = fm_append(poststring, "type=ajaxrpc");
    poststring = fm_append(poststring, "method=createNewProject");
    // AjaxRPC::createNewProject -
    // String projectName, String displayName, boolean active, String projectTemplateName, String templateLocaleName, boolean sectionsFromTemplate
    poststring = fm_append(poststring, "p0="+encode_url_parameter(SPRC.fm.newproj_name.value));
    poststring = fm_append(poststring, "p1="+encode_url_parameter(SPRC.fm.newproj_displayname.value));
    poststring = fm_append(poststring, "p2="+(SPRC.fm.newproj_active.checked ? "true" : "false"));
    poststring = fm_append(poststring, "p3="+(useProject ? projVal : currentSelValue(SPRC.fm.newproj_template)));
    poststring = fm_append(poststring, "p4="+currentSelValue(SPRC.fm.newproj_templatelocale));
    poststring = fm_append(poststring, "p5="+(SPRC.fm.newproj_templatesections.checked ? "true" : "false"));
    poststring = fm_append(poststring, "p6="+(SPRC.fm.newproj_templatepermissions.checked ? "true" : "false"));
    poststring = fm_append(poststring, "browserid="+encode_url_parameter(SPRC.fm.browserid.value));

    xmlpost_async(FORM_ACTION_READ_WRITE,
		  poststring,
		  SPRC.fm.newproj_add.nextSibling,
		  true,
		  SPRC.createNewProjectNormal_wakeup, this.callback);

  },


  /**
   * Handles the response to a createNewProject AJAX RPC request.  On
   * success, a new row is added to the new project data table;
   * otherwise, the error message is displayed appropriately.
   */
  createNewProjectNormal_wakeup: function(responseText, callback) {

    SPRC.fm.newproj_add.disabled = false;

    if (wasUnauthorized(responseText)) {
      if (callback != null) SPRC.fireCallback(callback);	
      failureUnauthorized();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(responseText)) {
      if (callback != null) SPRC.fireCallback(callback);	
      failureServerUnavailable();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var message = null;
    var isError = false;
    var results = null;

    var error = findErrorAndFeedback(responseText)[0];
    if (error != null && error != "" && error != "undefined") {
      message = error;
      isError = true;
    }
    else {

      eval("results = " + responseText);

      if (results["success"]) {
	// we don't print a message on success. [shep 11.Jan.2008]
      }
      else {
	message = results["error"];
	isError = true;
      }

    }

    var msgContainer = document.getElementById("projects-create-project-status");
    if (msgContainer) {
      var msgDIV = document.createElement("DIV");

      if (isError) {
	msgDIV.className = "error";
      }
      else {
	SPRC.resetNewProjectFields();
	if ( msgContainer.hasChildNodes() ) {
	  msgContainer.removeChild( msgContainer.firstChild );
	}

      }

      if (msgContainer.hasChildNodes()) {
	msgContainer.removeChild( msgContainer.firstChild );
      }

      if (message != null) {
	msgDIV.appendChild(document.createTextNode(message));
	msgContainer.appendChild(msgDIV);
      }
    }

    // show the table (it's hidden initially)
    var newProjectsContainer = document.getElementById("newprojectlist");
    if (newProjectsContainer) {
      newProjectsContainer.style.display = "block";

      if (!isError || typeof(results.projectinfo) != "undefined") {
	SPRC.newProjects.dataWidgets.dataTable.addRow( results.projectinfo );
      }
    }

    // added a callback that will be used by the dialog [ajm 19.Mar.2009]
    SPRC.fireCallback(callback, responseText, results.projectinfo, isError ? message : null);
  },

  fireCallback: function(callback, responseText, projectinfo, errorMessage) {
    if (callback) {
      var obj = { responseText: responseText, argument: callback.argument, error: errorMessage, projectinfo: projectinfo };
      var func = projectinfo ? callback.success : callback.failure;
      if (callback.scope) {
	func.apply(callback.scope, [obj]);
      } else {
	func(obj);
      }
      return true;
    } else {
      return false;
    }
  },

  createNewProjectEditSettings: function() {

    var useProject = document.getElementById("project-settings-source-project").checked;
    var projVal = SPRC.fm.newproj_project.value;
    if (useProject) {
      if (projVal.trim() == "") {
	alert("Please enter a project to copy.");
	SPRC.fm.newproj_project.focus();
	return;
      }
      projVal = "::" + projVal;
    }

    if (typeof(leavePage) == "function") {
      if (!leavePage()) {
	return;
      }
    }

    var referrer = (SPRC.fm.h_referer && SPRC.fm.h_referer.value) ? SPRC.fm.h_referer.value : "/traction";

    var url = FORM_ACTION_READ_ONLY + "?type=newproject";
    var extras = "&dir=config/project/templates&fb_submit=Apply&operation=copy&name=newproject.template" +
      "&stickyparameters=newproj_active,newproj_name,newproj_displayname,newproj_templatelocale,newproj_templatesections,newproj_templatepermissions" +
      "&source=" + (useProject ? projVal : currentSelValue(SPRC.fm.newproj_template)) +
      "&newproj_active=" + (SPRC.fm.newproj_active.checked ? "true" : "false") +
      "&newproj_name=" + encode_url_parameter(SPRC.fm.newproj_name.value) +
      "&newproj_displayname=" + encode_url_parameter(SPRC.fm.newproj_displayname.value) +
      ((ua("encode_html_entities_in_javascript", "false") == "true") ? "&amp;" : "&") + "copy_locale=" + currentSelValue(SPRC.fm.newproj_templatelocale) +
      "&newproj_templatesections=" + (SPRC.fm.newproj_templatesections.checked ? "true" : "false") +
      "&newproj_templatepermissions=" + (SPRC.fm.newproj_templatepermissions.checked ? "true" : "false") +
      "&h_referer=" + encode_url_parameter(referrer) +
      "&browserid=" + SPRC.fm.browserid.value;

    Settings.gotoView("newproject", false, extras);

  },


  /**
   * Resets the text fields used for the new project creation request
   * (Project Name, Display Name), and other fields (Active) as well,
   * if the argument for the allFields parameter is true (as it might
   * be if invoked from an explicit reset request button handler).
   */
  resetNewProjectFields: function(allFields) {
    SPRC.nameDirty = false;
    for (var i = 0; i < SPRC.INITIAL_FIELD_STATES.length; i ++) {
      var fieldState = SPRC.INITIAL_FIELD_STATES[i];
      var field = SPRC.fm[fieldState.name];
      if (field.type == "text") {
	field.value = fieldState.value;
      }
      else if (allFields) {
	if (field.type == "checkbox") {
	  field.checked = fieldState.value;
	}
	else if (field.nodeName == "SELECT") {
	  selectIndexOfValue(field, fieldState.value);
	}
      }
    }
    setTimeout(function() {
		 SPRC.fm.newproj_displayname.focus();
	       }, 100);
  },


  onChangeProjectSettingsSource: function(newVal) {

    // In theory, useProject === !useTemplate, but...
    var useTemplate = (newVal == "template");
    var useProject = (newVal == "project");
    SPRC.updateProjectSettingSourceFieldDisplay(useTemplate, useProject);

    if (useTemplate) {
      SPRC.fm.newproj_template.focus();
    }
    else if (useProject) {
      if (trim(SPRC.fm.newproj_project.value) == "") {
	YAHOO.traction.ProjectCompleter.setup(SPRC.fm.newproj_project);
	YAHOO.traction.ProjectCompleter.completersIntializedArray["newproj_project"].onInputClick();
      }
      setTimeout(function() {
		   SPRC.fm.newproj_project.focus();
		 }, 100);
    }

  },


  syncProjectSettingSourceFieldDisplay: function() {
    // In theory, useProject === !useTemplate, but...
    var useTemplate = document.getElementById("project-settings-source-template").checked;
    var useProject = document.getElementById("project-settings-source-project").checked;
    SPRC.updateProjectSettingSourceFieldDisplay(useTemplate, useProject);
  },


  updateProjectSettingSourceFieldDisplay: function(useTemplate, useProject) {
    if (this.hasMoreOptions) return;

    show_TR(useTemplate, document.getElementById("add-project-template"));

    // this isn't shown in the dialog version
    var elm = document.getElementById("add-project-template-managelink");
    if (elm) show_TR(useTemplate, elm);

    show_TR(useTemplate, document.getElementById("add-project-template-locale"));
    show_TR(useProject, document.getElementById("add-project-project"));
  },


  onChangeSettingsEdit: function(newVal) {
  },

  moreOptions: function(A) {
    var tr = getParentByName(A, "TR");
    show_TR(false, tr);
    while (tr = tr.nextSibling) {
      if (tr.tagName == "TR" && tr.id != "add-project-project") {
	show_TR(true, tr);      
      }
    }
    SPRC.hasMoreOptions = false;

    if (this.callback && this.callback.moreOptions) {
      var obj = {};
      if (this.callback.scope) {
	this.callback.moreOptions.apply(this.callback.scope, []);
      } else {
	this.callback.moreOptions();
      }
    }
  }

};
Settings.Projects.Creator = SPRC;

Settings.Projects.Templates = {


  /**
   * Open the template editor with this pre-loaded.
   */
  edit: function(ptInfo, ptIndex, ptRow) {
    Settings.Projects.Templates.openTemplateEditor(ptInfo.name, "");
  },


  view: function(ptInfo, ptIndex, ptRow) {
    Settings.Projects.Templates.openTemplateEditor(ptInfo.name, "");
  },


  del: function(ptInfo, ptIndex, ptRow) {

    if (!confirm((new MessageFormat(i18n("ssetup_projects_delete_template_confirmation_message",
					 "Are you sure you want to delete the '{0}' project template?"))).format(ptInfo.displayName))) {
      return;
    }

    var poststring = "";
    poststring = fm_append(poststring, "type=projecttemplateedit");
    poststring = fm_append(poststring, "dir=config/project/templates");
    poststring = fm_append(poststring, "name=" + ptInfo.name);
    poststring = fm_append(poststring, "operation=delete");
    poststring = fm_append(poststring, "fb_submit=Apply");
    poststring = fm_append(poststring, "browserid="+document.fm.browserid.value);

    xmlpost_async(FORM_ACTION_READ_WRITE,
		  poststring,
		  null,
		  true,
		  Settings.Projects.Templates.del_wakeup,
		  { ptInfo: ptInfo, ptIndex: ptIndex, ptRow: ptRow });

  },


  del_wakeup: function(responseText, pt) {

    if (wasUnauthorized(responseText)) {
      failureUnauthorized();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(responseText)) {
      failureServerUnavailable();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var error = findErrorAndFeedback(responseText)[0];
    if (error != null && error != "" && error != "undefined") {
      alert(error);
      return;
    }

    pt.ptRow.parentNode.removeChild(pt.ptRow);

    document.fm.newproj_template.options[selectIndexOfValue(document.fm.newproj_template, pt.ptInfo["name"])] = null;
    
  },


  copy: function(ptInfo, piIndex, ptRow) {
    var url = "&source=" + ptInfo.name +
      "&fb_submit=Apply&operation=copy" +
      ((ua("encode_html_entities_in_javascript", "false") == "true") ? "&amp;" : "&") + "copy_locale=" +
      currentSelValue(document.fm.copytemplate_locale);
    Settings.Projects.Templates.openTemplateEditor("projecttemplate.template", url);
  },


  add: function() {

    var useDefaults = document.getElementById("template-settings-source-defaults").checked;
    var useScratch = document.getElementById("template-settings-source-scratch").checked;
    var useProject = document.getElementById("template-settings-source-project").checked;

    if (useDefaults) {
      Settings.Projects.Templates.openTemplateEditor("projecttemplate.template", "&source=::&fb_submit=true&operation=copy");
    } else if (useScratch) {
      Settings.Projects.Templates.openTemplateEditor("projecttemplate.template", "");
    } else if (useProject) {
      if (document.fm.newtemplatesourceproj.value.trim() == "") {
	alert(i18n("ssetup_projects_copy_template_requires_project_alert_message", "Please enter a project name."));
	if (trim(document.fm.newtemplatesourceproj.value) == "") {
	  YAHOO.traction.ProjectCompleter.setup(document.fm.newtemplatesourceproj);
	  YAHOO.traction.ProjectCompleter.completersIntializedArray["newtemplatesourceproj"].onInputClick();
	}
	document.fm.newtemplatesourceproj.focus();
	return;
      }
      Settings.Projects.Templates.openTemplateEditor("projecttemplate.template",
						     "&source=::" + document.fm.newtemplatesourceproj.value + "&fb_submit=Apply&operation=copy");
    }

  },


  onChangeTemplateSettingsSource: function(newVal) {

    var useDefaults = (newVal == "defaults");
    var useScratch = (newVal == "scratch");
    var useProject = (newVal == "project");

    if (useProject) {
      document.fm.newtemplatesourceproj.focus();
      if (trim(document.fm.newtemplatesourceproj.value) == "") {
	YAHOO.traction.ProjectCompleter.setup(document.fm.newtemplatesourceproj);
	YAHOO.traction.ProjectCompleter.completersIntializedArray["newtemplatesourceproj"].onInputClick();
      }
    }

  },


  openTemplateEditor: function(templateName, extras) {

    extras += "&dir=config/project/templates&browserid=" + document.fm.browserid.value;
    if (templateName) {
      extras += "&name="+templateName;
    }

    openDialogEx("projecttemplateedit",
		 SKINEDIT_FEATURES,
		 720,
		 725,
		 "",
		 "newproj_template",
		 extras,
		 DIALOG_FLAG_MULTIPLE_WINDOWS);

  },


  ptInfos: new Array(),


  ptRowContainer: null,


  init: function() {
    this.initTemplateRows();
  },


  setField: function(field) {
    this.field = field;
  },


  refreshRows: function(templateData) {

    var cur = this.ptRowContainer.firstChild;
    while (cur != null) {
      var next = cur.nextSibling;
      cur.parentNode.removeChild(cur);
      cur = next;
    }

    this.ptInfos = new Array();

    for (var cname in templateData) {
      if (cname == "extend") {
	continue;
      }
      var cur = templateData[cname];
      this.ptInfos.push(new Traction.ProjectTemplateInfo(cur["name"], cur["displayname"], cur["description"], cur["template"] != null, cur["editable"]));
    }

    this.initTemplateRows();

  },


  initTemplateRows: function() {
    this.ptRowContainer = document.getElementById("managetemplates_ptrows");
    for (var i = 0; i < this.ptInfos.length; i ++) {
      this.addPTRow(this.ptInfos[i], i);
    }
  },


  addPTRow: function(ptInfo, ptIndex) {

    var ptRow = document.createElement("TR");

//     var spacerTD = document.createElement("TD");
//     spacerTD.appendChild(document.createTextNode(" "));

    var nameTD = document.createElement("TD");
    nameTD.style.textAlign = "left";
    nameTD.appendChild(document.createTextNode(ptInfo.name + ".properties"));

    var displayNameTD = document.createElement("TD");
    displayNameTD.style.textAlign = "left";
    displayNameTD.appendChild(document.createTextNode(ptInfo.displayName));

    var descTD = document.createElement("TD");
    descTD.style.textAlign = "left";
    descTD.appendChild(document.createTextNode(ptInfo.description));

    var opsTD = document.createElement("TD");
    opsTD.style.textAlign = "left";

    var editButton = null;
    var deleteButton = null;
    var viewButton = null;
    var copyButton = null;

    if (ptInfo.editable) {

      editButton = document.createElement("INPUT");
      editButton.type = "button";
      editButton.value = i18n("Edit_ellipses", "Edit...");
      opsTD.appendChild(editButton);
      opsTD.appendChild(document.createTextNode(" "));

      deleteButton = document.createElement("INPUT");
      deleteButton.type = "button";
      deleteButton.value = i18n("Delete", "Delete");
      opsTD.appendChild(deleteButton);

    }

    if (ptInfo.hasSettings) {
      if (!ptInfo.editable) {
	viewButton = document.createElement("INPUT");
	viewButton.type = "button";
	viewButton.value = i18n("View", "View");
	opsTD.appendChild(viewButton);
      }
      opsTD.appendChild(document.createTextNode(" "));
      copyButton = document.createElement("INPUT");
      copyButton.type = "button";
      copyButton.value = i18n("Copy", "Copy");
      opsTD.appendChild(copyButton);
    }

//     ptRow.appendChild(spacerTD);
    ptRow.appendChild(nameTD);
    ptRow.appendChild(displayNameTD);
    ptRow.appendChild(descTD);
    ptRow.appendChild(opsTD);

    this.ptRowContainer.appendChild(ptRow);

    var ptEditListener = editButton ? function(e) {
      Settings.Projects.Templates.edit(ptInfo, ptIndex, ptRow);
    } : null;
    var ptDeleteListener = deleteButton ? function(e) {
      Settings.Projects.Templates.del(ptInfo, ptIndex, ptRow);
    } : null;
    var ptViewListener = viewButton ? function(e) {
      Settings.Projects.Templates.view(ptInfo, ptIndex, ptRow);
    } : null;
    var ptCopyListener = copyButton ? function(e) {
      Settings.Projects.Templates.copy(ptInfo, ptIndex, ptRow);
    } : null;

    // Add these after the DOM has been updated; Safari doesn't do it
    // right otherwise.  [shep 15.Sep.2007]
    setTimeout(function() {
		 Debug.println("Adding handlers for project template row ", ptIndex);
		 if (editButton != null) {
		   Events.attach(editButton, Events.Click, Settings.Projects.Templates, ptEditListener);
		 }
		 if (deleteButton != null) {
		   Events.attach(deleteButton, Events.Click, Settings.Projects.Templates, ptDeleteListener);
		 }
		 if (viewButton != null) {
		   Events.attach(viewButton, Events.Click, Settings.Projects.Templates, ptViewListener);
		 }
		 if (copyButton != null) {
		   Events.attach(copyButton, Events.Click, Settings.Projects.Templates, ptCopyListener);
		 }
	       }, 100);

  }


};



Settings.ServerEmail = {


  onMakeDirty: function() {
    if (document.fm.testsmtp) {
      document.fm.testsmtp.disabled = true;
    }
    if (document.fm.testmailbox) {
      document.fm.testmailbox.disabled = true;
    }
  },


  testSMTP: function() {
    if (!isDirty) {
      openTestMail("_testmail", null, true);
    } else {
      alert(i18n("You_must_first_apply_your_changes",
		 "You must first apply your changes"));
    }
  },


  testServerMailbox: function() {
    if (!isDirty) {
      openTestMail("_testmail", "*", false, "&servermailbox=true");
    } else {
      alert(i18n("You_must_first_apply_your_changes",
		 "You must first apply your changes"));
    }
  },


  sendDigestNow: function() {
    var button = document.getElementById("server_send_digest_now_button");
    var poststring = "";
    poststring = fm_append(poststring, "type=ajaxrpc");
    poststring = fm_append(poststring, "method=sendDigest");
    poststring = fm_append(poststring, "browserid="+document.fm.browserid.value);
    xmlpost_async(FORM_ACTION_READ_ONLY, poststring, button.nextSibling, true, Settings.ServerEmail.sendDigestNow_wakeup, new Date());
  },


  sendDigestNow_wakeup: function(responseText, sendTime) {

    if (wasUnauthorized(responseText)) {
      failureUnauthorized();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(responseText)) {
      failureServerUnavailable();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var error = findErrorAndFeedback(responseText)[0];
    var statusMsg;

    if (error) {
      statusMsg = error;
    }
    else {
      var results;
      eval("results = " + responseText);
      if (results["success"]) {
	statusMsg = (new MessageFormat(i18n("ssetup_email_send_digest_now_success_message",
					    "Successfully sent digest at {0}."))).format(sendTime.formatDate("g:i:s a"));
      } else {
	statusMsg = (new MessageFormat(i18n("ssetup_email_send_digest_now_error_message",
					    "Attempt to send digest at {0} failed."))).format(sendTime.formatDate("g:i:s a"));
      }
    }
    document.getElementById("server_send_digest_now_status").innerHTML = statusMsg;

  },


  verifyCatchAllProject: function(dirty) {
    if (dirty) {
      makeDirty();
    }
    Settings.ServerEmail.toggleCatchAllProjectWarning(false);
    var curSelVal = currentSelValue(document.fm.journal_mail_catch_all_project);
    if (curSelVal.indexOf("def-val-") == 0) {
      return;
    }
    var poststring = "";
    poststring = fm_append(poststring, "type=ajaxrpc");
    poststring = fm_append(poststring, "method=checkCatchAllProject");
    poststring = fm_append(poststring, "p0=" + curSelVal);
    xmlpost_async(FORM_ACTION_READ_ONLY, poststring, document.fm.journal_mail_catch_all_project.nextSibling, true, Settings.ServerEmail.verifyCatchAllProject_wakeup);
  },


  verifyCatchAllProject_wakeup: function(responseText) {

    if (wasUnauthorized(responseText)) {
      failureUnauthorized();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(responseText)) {
      failureServerUnavailable();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var error = findErrorAndFeedback(responseText)[0];
    if (error != null && error != "" && error != "undefined") {
      alert(error);
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var results;
    eval("results = " + responseText);

    Settings.ServerEmail.toggleCatchAllProjectWarning(!results.isSuitable);

  },


  toggleCatchAllProjectWarning: function(show) {
    show_default(show, document.getElementById("journal_mail_catch_all_project_notsuitable"));
  }


};


Settings.ServerEmail.onLoad = function() {

  onMakeDirty = Settings.ServerEmail.onMakeDirty;

  // setup hide/show handlers
  setup_hideshow_select("server_pop_preauthenticate",

			// specify one of the next two properties
			['true'], // values of the above property under which we should SHOW the below
			[], // values of the above property under which we should HIDE the below

			// list of controls to hide/show
			[
			 "server_pop_preauthenticate_server", 
			 "server_pop_preauthenticate_username",
			 "server_pop_preauthenticate_password"
			 ]);  

  setup_hideshow_select("server_smtp_authenticate", 
			['true'],
			[],
			[
			 "server_smtp_authenticate_username",
			 "server_smtp_authenticate_password"
			 ]);

  setup_hideshow_select("journal_mailprotocol",	// when selecting from this setting
			['imap'], // if this value is selected SHOW the following controls...
			[],	
			[	
			 "journal_mail_imap_encryption",
			 "journal_mail_imap_port"
			 ]);

  setup_hideshow_select("journal_mailprotocol",	// when selecting from this setting
			['pop'], // if this value is selected SHOW the following controls...
			[],	
			[	
			 "journal_mail_pop_encryption",
			 "journal_mail_pop_port"
			 ]);

  Settings.ServerEmail.verifyCatchAllProject(false);

};


Settings.ServerEmail.DefaultEmailReplyFormat = Class.create();
Settings.ServerEmail.DefaultEmailReplyFormat.prototype = {


  fieldNamepace: null,


  initialize: function(fieldNamespace) {
    this.fieldNamespace = fieldNamespace;
  },


  onChange: function() {
    document.fm[this.fieldNamespace].value = new String(parseInt(document.fm[this.fieldNamespace + "_sel"].options[document.fm[this.fieldNamespace + "_sel"].selectedIndex].value, 10) +
							(document.fm[this.fieldNamespace + "_locked"].checked ? 1 : 0));
    makeDirty();
  }


};


Settings.ServerEmail.IncludeLinks = Class.create();
Settings.ServerEmail.IncludeLinks.prototype = {


  fieldNamepace: null,


  initialize: function(fieldNamespace) {
    this.fieldNamespace = fieldNamespace;
  },


  onChange: function() {
    document.fm[this.fieldNamespace].value = new String((document.fm[this.fieldNamespace + "_chk"].checked ? 2 : 0) + (document.fm[this.fieldNamespace + "_locked"].checked ? 1 : 0));
    makeDirty();
  }


};


Settings.ServerFiles = {
  onLoad: function() {
    // setup hide/show handlers
    setup_hideshow_select("server_enforcemaxattach",
			  // specify one of the next two properties
			  ['true'], // values of the above property under which we should SHOW the below
			  [], // values of the above property under which we should HIDE the below
			  // list of controls to hide/show
			  [ "server_maxattachsize" ]);  
  }
};


/**
 * An object representing a setting having two selectors, in which the
 * user chooses a user and one of the collections in that user's list
 * of collections.  The list of available ID collections is filled in
 * dynamically based upon the selected user.
 *
 * See also com.traction.sdl.admin.settings#singlecollectionselect
 */
Settings.CollectionSelect = Class.create();
Settings.CollectionSelect.prototype = {


  fieldNamespace: null,


  initialUserName: null,


  initialCollectionTitle: null,


  userSelect: null,


  collectorSelect: null,


  initialize: function(fieldNamespace, initialUserName, initialCollectionTitle) {
    this.fieldNamespace = fieldNamespace;
    this.initialUserName = initialUserName;
    this.initialCollectionTitle = initialCollectionTitle;
  },


  onLoad: function() {
    this.collectorSelect = new Traction.Select(this.fieldNamespace + "_title",
					       { submitMode: Traction.Select.MODE_NORMAL });
    this.userSelect = new Traction.Select(this.fieldNamespace + "_username",
					  { submitMode: Traction.Select.MODE_NORMAL });
    // select the initial user name option
    this.userSelect.selectOption(null, this.initialUserName, -1, true);
    this.retrieveCollections(this.initialCollectionTitle);
  },


  retrieveCollections: function(selectValue) {
    var poststring = "";
    poststring = fm_append(poststring, "type=ajaxrpc");
    poststring = fm_append(poststring, "method=getCollectionNames");
    poststring = fm_append(poststring, "p0=" + encode_url_parameter(this.userSelect.currentValue()));
    poststring = fm_append(poststring, "browserid="+document.fm.browserid.value);
    xmlpost_async(FORM_ACTION_READ_ONLY, poststring, this.userSelect.selector.nextSibling, true, this.retrieveCollections_wakeup.bind(this), selectValue);
  },


  retrieveCollections_wakeup: function(responseText, selectValue) {

    if (wasUnauthorized(responseText)) {
      failureUnauthorized();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    if (wasUnreachable(responseText)) {
      failureServerUnavailable();
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    var error = findErrorAndFeedback(responseText)[0];
    if (error != null && error != "" && error != "undefined") {
      alert(error);
      return; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }

    var results;
    eval("results = " + responseText);

    this.collectorSelect.removeAll();
    this.collectorSelect.add("", "", false, false);
    for (var i = 0; i < results.length; i ++) {
      this.collectorSelect.add(results[i], results[i], false, false);
    }

    if (selectValue) {
      this.collectorSelect.selectOption(null, selectValue, -1, true);
    }

  },


  onChangeUser: function() {
    this.retrieveCollections(null);
    this.makeDirty();
  },


  makeDirty: function() {
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  }


};


Settings.ServerDefaults = {


  onLoad: function() {

    setup_hideshow_select("project_defaults_p_mailprotocol",	// when selecting from this setting
			  ['imap'], // if this value is selected SHOW the following controls...
			  [],	
			  [	
			   "project_defaults_p_mail_imap_encryption",
			   "project_defaults_p_mail_imap_port"
			   ]);

    setup_hideshow_select("project_defaults_p_mailprotocol",	// when selecting from this setting
			  ['pop'], // if this value is selected SHOW the following controls...
			  [],	
			  [	
			   "project_defaults_p_mail_pop_encryption",
			   "project_defaults_p_mail_pop_port"
			   ]);

  }


};


Settings.ServerGeneral = {


  SERVER_NAME: null,


  SERVER_BASE_URL: null,


  init: function() {
    if (document.fm.server_ftuseold) {
      Events.attach(document.fm.server_ftuseold, Events.Change, window, Settings.ServerGeneral.onChangeFTUseOld);
      Settings.ServerGeneral.onChangeFTUseOld();
    }
  },


  onChangeFTUseOld: function() {
    var ftuoVal = document.fm.server_ftuseold.options[document.fm.server_ftuseold.selectedIndex].value;
    document.fm.ftuseold.value = (ftuoVal.indexOf("true") != -1) ? "true" : "false";
  },


  shutdownServer: function() {
    var shutdownConfirm = (new MessageFormat(i18n("ssetup_general_shutdown_confirmation_message",
						  "Are you sure you want to shut down {0} on {1}?"))).format(Settings.ServerGeneral.SERVER_NAME, Settings.ServerGeneral.SERVER_BASE_URL);
    if (confirm(shutdownConfirm)) {
      document.fm.fb_submit.value = "Shutdown Traction";
      document.fm.submit();
    }
  },


  restartServer: function() {
    var restartConfirm = (new MessageFormat(i18n("ssetup_general_restart_confirmation_message",
						 "Are you sure you want to restart {0} on {1}?"))).format(Settings.ServerGeneral.SERVER_NAME, Settings.ServerGeneral.SERVER_BASE_URL);
    if (confirm(restartConfirm)) {
      document.fm.fb_submit.value = "Restart Traction";
      document.fm.submit();
    }
  },


  setupRdbRebuild: function() {
    Settings.gotoView("rebuildsetup_db", true, "");
  },


  migrateToWebdav: function() {
    if (confirm(i18n("ssetup_general_migrating_to_webdav_warning_message",
		     "You are about to migrate your attachments to WebDAV.\\n" +
		     "Once started, this process cannot be reversed.\\n" +
		     "Are you sure you want to continue?\\n\\n" +
		     "Click OK to continue.\\nClick Cancel to Abort."))) {
      if (!isDirty) {
	document.fm.fb_submit.value = "Migrate Attachments to WebDAV";
	document.fm.submit();
      }
    }
  },


  modifyUserDirectory: function() {
    Settings.gotoView("userdirmodify", true, "");
  },


  launchFeedEdit: function() {
    window.open("/traction/read?type=feededit");
  },


  launchSearchEngineSetup: function(engineName) {
    openDialog("searchsetup", "searchsetup_window", "searchsetup-" + engineName, "&searchengine=" + engineName)
  },


  clearMyCache: function() {
    if (!isDirty) {
      document.fm.fb_submit.value = "Clear Cache";
      document.fm.submit();
    }
  },


  gotoJournalSetup: function() {
    Settings.gotoView("journalsetup", true, "");
  },


  gotoExportJ2X: function() {
    Settings.gotoView("j2x", true, "");
  },


  gotoImportX2J: function() {
    Settings.gotoView("x2j", true, "");
  },


  onRebuildChange: function(chkField, hiddenField) {
    hiddenField.value = chkField.checked ? "true" : "false";
    if (typeof(makeDirtyRebuildOption) == "function") {
      makeDirtyRebuildOption();
    }
  },


  onMetaPropertyChange: function() {
    document.fm.rebuild.checked = true;
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  },


  migrateToWebDAV: function() {
    if (confirm(i18n("ssetup_general_migrating_to_webdav_warning_message", "You are about to migrate your attachments to WebDAV.\nOnce started, this process cannot be reversed.\nAre you sure you want to continue?\n\nClick OK to continue.\nClick Cancel to Abort."))) {
      if (!isDirty) {
	document.fm.fb_submit.value = 'Migrate Attachments to WebDAV';
	document.fm.submit();
      }
    }
  },


  openSearchSections: function() {
    window.open(FORM_ACTION_READ_ONLY + "?type=searchsections");
  }


};


Settings.DisabledOptionList = Class.create();
Settings.DisabledOptionList.prototype = {


  fieldNamespace: null,


  initialize: function(fieldNamespace) {
    this.fieldNamespace = fieldNamespace;
  },


  onStatusChange: function(changedChk) {
    var finalVal = "";
    var i = 0;
    var chk = document.fm[this.fieldNamespace + "_chk_" + i];
    while (chk != null) {
      // Notice we check for not checked, which means DISabled, which
      // is what the setting is.
      if (!chk.checked) {
	if (finalVal != "") {
	  finalVal += ",";
	}
	finalVal += chk.value;
      }
      i ++;
      chk = document.fm[this.fieldNamespace + "_chk_" + i];
    }
    document.fm[this. fieldNamespace].value = finalVal;
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  }


};


Settings.Network = {


  onLoad: function() {
    // setup hide/show handlers

    // Features / Tuning Tab
    setup_hideshow_select("server_enable_gzip",
			  ['true'],
			  [],
			  [ "server_gzip_mimetypes" ]);

    // Access List Tab
    setup_hideshow_select("server_enableac",
			  ['true'],
			  [],
			  [ "server_acceptaddress" ]);

    // Outgoing HTTP Requests Tab
    setup_hideshow_select("server_enable_http_proxy",
			  // specify one of the next two properties
			  ['true'], // values of the above property under which we should SHOW the below
			  [], // values of the above property under which we should HIDE the below
			  // list of controls to hide/show
			  [ "server_http_proxy_address", "server_http_proxy_port", "server_http_proxy_login_settings" ]);
    setup_hideshow_select("server_http_proxy_use_login",
			  // specify one of the next two properties
			  ['true'], // values of the above property under which we should SHOW the below
			  [], // values of the above property under which we should HIDE the below
			  // list of controls to hide/show
			  [ "server_http_proxy_username", "server_http_proxy_password", "server_http_proxy_use_ntlm" ]);
    setup_hideshow_select("server_http_proxy_use_ntlm",
			  // specify one of the next two properties
			  ['true'], // values of the above property under which we should SHOW the below
			  [], // values of the above property under which we should HIDE the below
			  // list of controls to hide/show
			  [ "server_http_proxy_login_ntlm_settings" ]);

  }


};


Settings.Network.HTTPSecurityLevel = {


  init: function(inputElm) {
    Settings.Network.HTTPSecurityLevel.inputElm = document.fm.server_secure;
    Settings.Network.HTTPSecurityLevel.sync();
  },


  onChange: function() {
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
    Settings.Network.HTTPSecurityLevel.sync();
  },


  sync: function() {
    var warndiv;
    warndiv = document.getElementById("tlsWarning");
    if (warndiv) warndiv.style.display = (Settings.Network.HTTPSecurityLevel.inputElm.selectedIndex != 0) ? "" : "none";
    warndiv = document.getElementById("tlsCaWarning");
    if (warndiv) warndiv.style.display = (Settings.Network.HTTPSecurityLevel.inputElm.selectedIndex >= 2) ? "" : "none";
  }


};



Settings.InternetAddressList = Class.create();
Settings.InternetAddressList.prototype = {


  addresses: new Array(),


  addressRowContainer: null,


  fieldNamespace: "",


  field: null,


  initialize: function(fieldNamespace) {
    this.fieldNamespace = fieldNamespace;
  },


  init: function() {
    this.initAddressRows();
  },


  setField: function(field) {
    this.field = field;
    this.field.form[this.fieldNamespace + "_add"].disabled = false;
  },


  initAddressRows: function() {
    this.addressRowContainer = document.getElementById(this.fieldNamespace + "_addressrows");
    this.addEmptyRow(this.addresses.length == 0);
    for (var i = 0; i < this.addresses.length; i ++) {
      this.addAddressRow(this.addresses[i], i);
    }
  },


  addAddressRow: function(address, addressIndex) {

    var addressRow = document.createElement("TR");
    addressRow.id = this.fieldNamespace + "_" + addressIndex;
    if (!address.valid) {
      addressRow.className = "invalid";
    }

    var removeCell = document.createElement("TD");
    removeCell.className = "remove";
    var removeButton = document.createElement("INPUT")
    removeButton.type = "button";
    removeButton.name = this.fieldNamespace + "_" + addressIndex + "_remove";
    removeButton.value = i18n("Remove", "Remove");
    if (this.isReadOnly) {
      removeButton.disabled = true;
    }
    removeCell.appendChild(removeButton);

    var ipCell = document.createElement("TD");
    var ipInput = document.createElement("INPUT");
    ipInput.type = "text";
    ipInput.size = "15";
    ipInput.name = this.fieldNamespace + "_" + addressIndex + "_ip";
    ipInput.value = address.ip;
    ipCell.appendChild(ipInput);

    var notesCell = document.createElement("TD");
    var notesInput = document.createElement("INPUT");
    notesInput.type = "text";
    notesInput.size = "15";
    notesInput.name = this.fieldNamespace + "_" + addressIndex + "_notes";
    notesInput.value = address.notes;
    notesCell.appendChild(notesInput);

    addressRow.appendChild(removeCell);
    addressRow.appendChild(ipCell);
    addressRow.appendChild(notesCell);

    this.addressRowContainer.appendChild(addressRow);

    var addressList = this;
    var propChangeListener = function(e) {
      addressList.onPropertyChange(addressIndex,
				   { ip: ipInput, notes: notesInput });
    };
    var addressRemoveListener = function(e) {
      addressList.onRemoveClick(addressIndex, addressRow);
    };

    // Add these after the DOM has been updated; Safari doesn't do it
    // right otherwise.  [shep 15.Sep.2007]
    setTimeout(function() {
		 Debug.println("Adding handlers for address row ", addressList.fieldNamespace, " : ", addressIndex);
		 Events.attach(ipInput,      Events.Change, addressList, propChangeListener);
		 Events.attach(notesInput,   Events.Change, addressList, propChangeListener);
		 Events.attach(removeButton, Events.Click,  addressList, addressRemoveListener);
	       }, 100);

  },


  addEmptyRow: function(show) {

    var emptyRow = document.createElement("TR");
    emptyRow.id = this.fieldNamespace + "_empty";
    emptyRow.className = "none";
    emptyRow.style.display = show ? "" : "none";

    var emptyCell = document.createElement("TD");
    emptyCell.className = "remove";
    emptyCell.appendChild(document.createTextNode(" "));
    emptyRow.appendChild(emptyCell);

    emptyCell = document.createElement("TD");
    emptyCell.setAttribute("colspan", "2");
    emptyCell.colSpan = 2;
    emptyCell.appendChild(document.createTextNode("(" + i18n("None", "None") + ")"));
    emptyRow.appendChild(emptyCell);

    this.addressRowContainer.appendChild(emptyRow);

  },


  toggleEmptyRowDisplay: function(show) {
    document.getElementById(this.fieldNamespace + "_empty").style.display = show ? "" : "none";
  },


  getAddressListSerialization: function() {
    var s = "";
    var next = 0;
    for (var num = 0; num < this.addresses.length; num ++) {
      if (!this.addresses[num].removed) {
	if (next > 0) {
	  s += ",";
	}
	s += this.addresses[num].serialize(next);
	next ++;
      }
    }
    return s;
  },


  onPropertyChange: function(addressIndex, fields) {
    Debug.println("onPropertyChange for ", addressIndex, "...");
    var address = this.addresses[addressIndex];
    address.ip = fields.ip.value;
    address.notes = fields.notes.value;
    Debug.println("Finished updating properties for ", addressIndex, ": ", address.serialize(""));
    this.makeDirty();
  },


  onRemoveClick: function(addressIndex, addressRow) {
    // mark it disabled
    this.addresses[addressIndex].removed = true;
    // hide the row
    show_TR(false, addressRow);
    this.makeDirty();
  },


  onAddClick: function() {
    var newAddress = new Traction.InternetAddress("", "", true);
    var newAddressIndex = this.addresses.length;
    this.addresses[newAddressIndex] = newAddress;
    this.addAddressRow(newAddress, newAddressIndex);
    this.makeDirty();
  },


  makeDirty: function() {
    this.field.value = this.getAddressListSerialization();
    Debug.println("New field value: ", this.field.name, " = ", this.field.value);
    var nonRemoved = 0;
    for (var i = 0; i < this.addresses.length; i ++) {
      if (!this.addresses[i].removed) {
	nonRemoved ++;
      }
    }
    this.toggleEmptyRowDisplay(nonRemoved == 0);
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  }


};


Settings.ProjectMailbox = {


  NAMESPACES: [ "project", "project_defaults_p" ],


  onMakeDirty: function(){
    if (document.fm.testmailbox) {
      document.fm.testmailbox.disabled = true;
    }
  },


  testSetup: function(proj) {
    openTestMail("_testmail", proj, false);
  }


};


Settings.ProjectMailbox.onLoad = function() {
  onMakeDirty = Settings.ProjectMailbox.onMakeDirty;
  Settings.ProjectMailbox.setupDependencies();
};


Settings.ProjectMailbox.setupDependencies = function() {
  for (var i = 0; i < Settings.ProjectMailbox.NAMESPACES.length; i ++) {
    var ns = Settings.ProjectMailbox.NAMESPACES[i];
    if (document.fm[ns + "_mailprotocol"]) {
      setup_hideshow_select(ns + "_mailprotocol",	// when selecting from this setting
			    ['imap'], // if this value is selected SHOW the following controls...
			    [],	
			    [	
			     ns + "_mail_imap_encryption",
			     ns + "_mail_imap_port"
			     ]);
      setup_hideshow_select(ns + "_mailprotocol",	// when selecting from this setting
			    ['pop'], // if this value is selected SHOW the following controls...
			    [],	
			    [	
			     ns + "_mail_pop_encryption",
			     ns + "_mail_pop_port"
			     ]);
    }
  }
};


Settings.MailHeaderMatcherList = Class.create();
Settings.MailHeaderMatcherList.prototype = {


  matchers: new Array(),


  matcherRowContainer: null,


  fieldNamespace: "",


  field: null,


  projects: null,


  currentProjectName: -1,


  isReadOnly: false,


  initialize: function(fieldNamespace, currentProjectName, projects) {
    this.fieldNamespace = fieldNamespace;
    this.currentProjectName = currentProjectName;
    this.projects = projects;
  },


  init: function() {
    this.initMatcherRows();
  },


  setField: function(field, isReadOnly) {
    this.field = field;
    this.isReadOnly = isReadOnly;
    if (!isReadOnly) {
      this.field.form[this.fieldNamespace + "_add"].disabled = false;
    }
  },


  initMatcherRows: function() {
    this.matcherRowContainer = document.getElementById(this.fieldNamespace + "_matcherrows");
    this.addEmptyRow(this.matchers.length == 0);
    for (var i = 0; i < this.matchers.length; i ++) {
      this.addMatcherRow(this.matchers[i], i);
    }
  },


  addMatcherRow: function(matcher, matcherIndex) {

    var matcherRow = document.createElement("TR");
    matcherRow.id = this.fieldNamespace + "_" + matcherIndex;

    var removeCell = document.createElement("TD");
    removeCell.className = "remove";
    var removeButton = document.createElement("INPUT")
    removeButton.type = "button";
    removeButton.name = this.fieldNamespace + "_" + matcherIndex + "_remove";
    removeButton.value = i18n("Remove", "Remove");
    if (this.isReadOnly) {
      removeButton.disabled = true;
    }
    removeCell.appendChild(removeButton);

    var headerCell = document.createElement("TD");
    var headerInput = document.createElement("INPUT");
    headerInput.type = "text";
    headerInput.size = "30";
    headerInput.name = this.fieldNamespace + "_" + matcherIndex + "_header";
    headerInput.value = matcher.header;
    headerCell.appendChild(headerInput);

    var matchCell = document.createElement("TD");
    var matchInput = document.createElement("INPUT");
    matchInput.type = "text";
    matchInput.size = "15";
    matchInput.name = this.fieldNamespace + "_" + matcherIndex + "_match";
    matchInput.value = matcher.match;
    matchCell.appendChild(matchInput);

    var labelCell = document.createElement("TD");
    labelCell.style.whiteSpace = "nowrap";
    labelCell.style.textAlign = "left";
    var projectSelector = document.createElement("SELECT");
    for (var p = 0; p < this.projects.length; p ++) {
      projectSelector.options[p] = new Option(this.projects[p].displayName, this.projects[p].name);
      if (this.projects[p].name == matcher.labelProjectName) {
	projectSelector.options[p].selected = true;
      }
    }
    var labelSelector = document.createElement("SELECT");
    labelSelector.options[0] = new Option("", "");
    labelCell.appendChild(projectSelector);
    labelCell.appendChild(document.createTextNode(" "));
    labelCell.appendChild(labelSelector);

    matcherRow.appendChild(removeCell);
    matcherRow.appendChild(headerCell);
    matcherRow.appendChild(matchCell);
    matcherRow.appendChild(labelCell);

    this.matcherRowContainer.appendChild(matcherRow);

    // Fill in the labels.
    setTimeout(function() {
		 matcherList.fillInLabels(matcher.labelProjectName, labelSelector, matcher.labelName);
	       }, 1);

    var matcherList = this;
    var fields = { header: headerInput, match: matchInput, project: projectSelector, label: labelSelector };

    var projChangeFunction = this.onLabelProjectChange.bind(this);
    var projChangeListener = function() {
      projChangeFunction(projectSelector, labelSelector, matcherIndex, fields);
    };

    var propChangeListener = function(e) {
      matcherList.onPropertyChange(matcherIndex, fields, true);
    };
    var matcherRemoveListener = function(e) {
      matcherList.onRemoveClick(matcherIndex, matcherRow);
    };

    // Add these after the DOM has been updated; Safari doesn't do it
    // right otherwise.  [shep 15.Sep.2007]
    setTimeout(function() {
		 Debug.println("Adding handlers for matcher row ", matcherList.fieldNamespace, " : ", matcherIndex);
		 Events.attach(headerInput,     Events.Change, matcherList, propChangeListener);
		 Events.attach(matchInput,      Events.Change, matcherList, propChangeListener);
		 Events.attach(projectSelector, Events.Change, matcherList, projChangeListener);
		 Events.attach(labelSelector,   Events.Change, matcherList, propChangeListener);
		 Events.attach(removeButton,    Events.Click,  matcherList, matcherRemoveListener);
	       }, 100);

  },


  addEmptyRow: function(show) {

    var emptyRow = document.createElement("TR");
    emptyRow.id = this.fieldNamespace + "_empty";
    emptyRow.className = "none";
    emptyRow.style.display = show ? "" : "none";

    var emptyCell = document.createElement("TD");
    emptyCell.className = "remove";
    emptyCell.appendChild(document.createTextNode(" "));
    emptyRow.appendChild(emptyCell);

    emptyCell = document.createElement("TD");
    emptyCell.setAttribute("colspan", "3");
    emptyCell.colSpan = 3;
    emptyCell.appendChild(document.createTextNode("(" + i18n("None", "None") + ")"));
    emptyRow.appendChild(emptyCell);

    this.matcherRowContainer.appendChild(emptyRow);

  },


  toggleEmptyRowDisplay: function(show) {
    document.getElementById(this.fieldNamespace + "_empty").style.display = show ? "" : "none";
  },


  onLabelProjectChange: function(projectSelector, labelSelector, matcherIndex, fields) {
    var selectedOption = projectSelector.options[projectSelector.selectedIndex];
    this.makeDirty();
    this.fillInLabels(selectedOption.value, labelSelector, labelSelector.options[labelSelector.selectedIndex].value, matcherIndex, fields);
  },


  fillInLabels: function(labelProjectName, labelSelector, labelName, matcherIndex, fields) {
    Traction.LabelCache.getLabels(labelProjectName, this.fillInLabels_wakeup.bind(this),
				  { labelSelector: labelSelector, labelName: labelName, matcherIndex: matcherIndex, fields: fields });
  },


  fillInLabels_wakeup: function(labelArray, targetData) {

    targetData.labelSelector.options.length = 0;

    var selectedIndex = -1;
    if (targetData.labelName != "") {
      var matchRSLabel = targetData.labelName.toLowerCase().replace(/\s+/gi, "");
      for (var idx = 0; idx < labelArray.length; idx ++) {
	if (labelArray[idx].label.toLowerCase().replace(/\s+/gi, "") == matchRSLabel) {
	  selectedIndex = idx;
	  break;
	}
      }
    }

    var i = 0;
    if (selectedIndex == -1) {
      targetData.labelSelector.options[i] = new Option("", "");
      targetData.labelSelector.options[i].selected = true;
      i ++;
    }
    for (var l = 0; l < labelArray.length; l ++) {
      targetData.labelSelector.options[i] = new Option(labelArray[l].label, labelArray[l].label);
      if (targetData.labelName == labelArray[l].label) {
	targetData.labelSelector.options[i].selected = true;
      }
      i ++;
    }

    if (typeof(targetData.matcherIndex) != "undefined") {
      this.onPropertyChange(targetData.matcherIndex, targetData.fields, false);
    }

  },


  getMatcherListSerialization: function() {
    var s = "";
    var next = 0;
    for (var num = 0; num < this.matchers.length; num ++) {
      if (!this.matchers[num].removed) {
	if (next > 0) {
	  s += ",";
	}
	s += this.matchers[num].serialize(next);
	next ++;
      }
    }
    return s;
  },


  onPropertyChange: function(matcherIndex, fields, dirty) {
    Debug.println("onPropertyChange for ", matcherIndex, "...");
    var matcher = this.matchers[matcherIndex];
    matcher.header = fields.header.value;
    matcher.match = fields.match.value;
    matcher.labelProjectName = fields.project.options[fields.project.selectedIndex].value;
    matcher.labelName = fields.label.options[fields.label.selectedIndex].value;
    Debug.println("Finished updating properties for ", matcherIndex, ": ", matcher.serialize(""));
    if (dirty) {
      this.makeDirty();
    }
  },


  onRemoveClick: function(matcherIndex, matcherRow) {
    // mark it disabled
    this.matchers[matcherIndex].removed = true;
    // hide the row
    show_TR(false, matcherRow);
    this.makeDirty();
  },


  onAddClick: function() {
    var newMatcher = new Traction.MailHeaderMatcher("", "", this.currentProjectName, "");
    var newMatcherIndex = this.matchers.length;
    this.matchers[newMatcherIndex] = newMatcher;
    this.addMatcherRow(newMatcher, newMatcherIndex);
    this.makeDirty();
  },


  makeDirty: function() {
    this.field.value = this.getMatcherListSerialization();
    Debug.println("New field value: ", this.field.name, " = ", this.field.value);
    var nonRemoved = 0;
    for (var i = 0; i < this.matchers.length; i ++) {
      if (!this.matchers[i].removed) {
	nonRemoved ++;
      }
    }
    this.toggleEmptyRowDisplay(nonRemoved == 0);
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  }


};


Settings.StandardMailRoutingRuleList = Class.create();
Settings.StandardMailRoutingRuleList.prototype = {


  rules: new Array(),


  rowContainer: null,


  fieldNamespace: "",


  field: null,


  initialize: function(fieldNamespace) {
    this.fieldNamespace = fieldNamespace;
  },


  init: function() {
    this.initRuleRows();
  },


  setField: function(field, isReadOnly) {
    this.field = field;
    this.isReadOnly = isReadOnly;
    if (!isReadOnly) {
      this.field.form[this.fieldNamespace + "_add"].disabled = false;
    }
  },


  initRuleRows: function() {
    this.rowContainer = document.getElementById(this.fieldNamespace + "_rulerows");
    this.addEmptyRow(this.rules.length == 0);
    for (var i = 0; i < this.rules.length; i ++) {
      this.addRuleRow(this.rules[i], i);
    }
  },


  addRuleRow: function(rule, ruleIndex) {

    var ruleRow = document.createElement("TR");
    ruleRow.id = this.fieldNamespace + "_" + ruleIndex;

    var removeCell = document.createElement("TD");
    removeCell.className = "remove";
    var removeButton = document.createElement("INPUT")
    removeButton.type = "button";
    removeButton.name = this.fieldNamespace + "_" + ruleIndex + "_remove";
    removeButton.value = i18n("Remove", "Remove");
    if (this.isReadOnly) {
      removeButton.disabled = true;
    }
    removeCell.appendChild(removeButton);

    var headerCell = document.createElement("TD");
    var headerInput = document.createElement("INPUT");
    headerInput.type = "text";
    headerInput.name = this.fieldNamespace + "_" + ruleIndex + "_header";
    headerInput.value = rule.header;
    headerInput.style.width = "100%";
    headerCell.appendChild(headerInput);

    var regexCell = document.createElement("TD");
    var regexInput = document.createElement("INPUT");
    regexInput.type = "text";
    regexInput.size = "25";
    regexInput.name = this.fieldNamespace + "_" + ruleIndex + "_regex";
    regexInput.value = rule.regex;
    regexInput.style.width = "100%";
    regexCell.appendChild(regexInput);

    var projCell = document.createElement("TD");
    projCell.style.whiteSpace = "nowrap";
    projCell.style.textAlign = "left";
    var projInput = document.createElement("INPUT");
    projInput.type = "text";
    projInput.size = "15";
    projInput.name = this.fieldNamespace + "_" + ruleIndex + "_project";
    projInput.value = rule.project;
    projInput.style.width = "100%";
    projCell.appendChild(projInput);

    ruleRow.appendChild(removeCell);
    ruleRow.appendChild(headerCell);
    ruleRow.appendChild(regexCell);
    ruleRow.appendChild(projCell);

    this.rowContainer.appendChild(ruleRow);

    var ruleList = this;
    var fields = { header: headerInput, regex: regexInput, project: projInput };
    var propChangeListener = function(e) {
      ruleList.onPropertyChange(ruleIndex, fields, true);
    };
    var ruleRemoveListener = function(e) {
      ruleList.onRemoveClick(ruleIndex, ruleRow);
    };

    // Add these after the DOM has been updated; Safari doesn't do it
    // right otherwise.  [shep 15.Sep.2007]
    setTimeout(function() {
		 Debug.println("Adding handlers for rule row ", ruleList.fieldNamespace, " : ", ruleIndex);
		 Events.attach(headerInput,     Events.Change, ruleList, propChangeListener);
		 Events.attach(regexInput,      Events.Change, ruleList, propChangeListener);
		 Events.attach(projInput,       Events.Change, ruleList, propChangeListener);
		 Events.attach(removeButton,    Events.Click,  ruleList, ruleRemoveListener);
	       }, 100);

  },


  addEmptyRow: function(show) {

    var emptyRow = document.createElement("TR");
    emptyRow.id = this.fieldNamespace + "_empty";
    emptyRow.className = "none";
    emptyRow.style.display = show ? "" : "none";

    var emptyCell = document.createElement("TD");
    emptyCell.className = "remove";
    emptyCell.appendChild(document.createTextNode(" "));
    emptyRow.appendChild(emptyCell);

    emptyCell = document.createElement("TD");
    emptyCell.setAttribute("colspan", "3");
    emptyCell.colSpan = 3;
    emptyCell.appendChild(document.createTextNode("(" + i18n("None", "None") + ")"));
    emptyRow.appendChild(emptyCell);

    this.rowContainer.appendChild(emptyRow);

  },


  toggleEmptyRowDisplay: function(show) {
    document.getElementById(this.fieldNamespace + "_empty").style.display = show ? "" : "none";
  },


  getRuleListSerialization: function() {
    var s = "";
    var next = 0;
    for (var num = 0; num < this.rules.length; num ++) {
      if (!this.rules[num].removed) {
	if (next > 0) {
	  s += ",";
	}
	s += this.rules[num].serialize(next);
	next ++;
      }
    }
    return s;
  },


  onPropertyChange: function(ruleIndex, fields, dirty) {
    Debug.println("onPropertyChange for ", ruleIndex, "...");
    var rule = this.rules[ruleIndex];
    rule.header   = fields.header.value;
    rule.regex    = fields.regex.value;
    rule.project  = fields.project.value;
    Debug.println("Finished updating properties for ", ruleIndex, ": ", rule.serialize(""));
    if (dirty) {
      this.makeDirty();
    }
  },


  onRemoveClick: function(ruleIndex, ruleRow) {
    // mark it disabled
    this.rules[ruleIndex].removed = true;
    // hide the row
    show_TR(false, ruleRow);
    this.makeDirty();
  },


  onAddClick: function() {
    var newRule = new Traction.StandardMailEntryRoutingRule("", "", "");
    var newRuleIndex = this.rules.length;
    this.rules[newRuleIndex] = newRule;
    this.addRuleRow(newRule, newRuleIndex);
    this.makeDirty();
    this.field.form[this.fieldNamespace + "_" + newRuleIndex + "_header"].focus();
  },


  makeDirty: function() {
    this.field.value = this.getRuleListSerialization();
    Debug.println("New field value: ", this.field.name, " = ", this.field.value);
    var nonRemoved = 0;
    for (var i = 0; i < this.rules.length; i ++) {
      if (!this.rules[i].removed) {
	nonRemoved ++;
      }
    }
    this.toggleEmptyRowDisplay(nonRemoved == 0);
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  }


};

Settings.MailboxAliasManager = Class.create();
Settings.MailboxAliasManager.prototype = {


  aliases: new Array(),


  matcherRowContainer: null,


  fieldNamespace: "",


  mailToField: null,


  mailCatsField: null,


  projects: null,


  currentProjectName: "",


  isReadOnly: false,


  initialize: function(fieldNamespace, currentProjectName, projects) {
    this.fieldNamespace = fieldNamespace;
    this.currentProjectName = currentProjectName;
    this.projects = projects;
  },


  init: function() {
    this.initAliasRows();
  },


  setFields: function(mailToField, mailCatsField, isReadOnly) {
    this.mailToField = mailToField;
    this.mailCatsField = mailCatsField;
    this.isReadOnly = isReadOnly;
    if (!isReadOnly) {
      this.mailToField.form[this.fieldNamespace + "_add"].disabled = false;
    }
  },


  initAliasRows: function() {
    this.aliasRowContainer = document.getElementById(this.fieldNamespace + "_aliasrows");
    this.addEmptyRow(this.aliases.length == 0);
    for (var i = 0; i < this.aliases.length; i ++) {
      this.addAliasRow(this.aliases[i], i);
    }
  },


  addAliasRow: function(alias, aliasIndex) {

    var aliasRow = document.createElement("TR");
    aliasRow.id = this.fieldNamespace + "_" + aliasIndex;

    var removeCell = document.createElement("TD");
    removeCell.className = "remove";
    var removeButton = document.createElement("INPUT")
    removeButton.type = "button";
    removeButton.name = this.fieldNamespace + "_" + aliasIndex + "_remove";
    removeButton.value = i18n("Remove", "Remove");
    if (this.isReadOnly) {
      removeButton.disabled = true;
    }
    removeCell.appendChild(removeButton);

    var recipientCell = document.createElement("TD");
    var recipientInput = document.createElement("INPUT");
    recipientInput.type = "text";
    recipientInput.size = "15";
    recipientInput.name = this.fieldNamespace + "_" + aliasIndex + "_recipient";
    recipientInput.value = alias.recipient;
    recipientCell.appendChild(recipientInput);

    var labelCell = document.createElement("TD");
    labelCell.style.whiteSpace = "nowrap";
    labelCell.style.textAlign = "left";
    var projectSelector = document.createElement("SELECT");
    var selectCurrentProject = (alias.labelProjectName == "");
    for (var p = 0; p < this.projects.length; p ++) {
      projectSelector.options[p] = new Option(this.projects[p].displayName, this.projects[p].name);
      if (selectCurrentProject) {
	if (this.projects[p].name == this.currentProjectName) {
	  projectSelector.options[p].selected = true;
	}
      } else {
	if (this.projects[p].name == alias.labelProjectName) {
	  projectSelector.options[p].selected = true;
	}
      }
    }
    var labelSelector = document.createElement("SELECT");
    labelSelector.options[0] = new Option("", "");
    labelCell.appendChild(projectSelector);
    labelCell.appendChild(document.createTextNode(" "));
    labelCell.appendChild(labelSelector);

    aliasRow.appendChild(removeCell);
    aliasRow.appendChild(recipientCell);
    aliasRow.appendChild(labelCell);

    this.aliasRowContainer.appendChild(aliasRow);

    // Fill in the labels.
    var queryProj = alias.labelProjectName ? alias.labelProjectName : this.currentProjectName;
    var fields = { recipient: recipientInput, project: projectSelector, label: labelSelector };

    setTimeout(function() {
		 aliasList.fillInLabels(queryProj, labelSelector, alias.labelName, aliasIndex, fields);
	       }, 1);

    var aliasList = this;

    var projChangeFunction = this.onLabelProjectChange.bind(this);
    var projChangeListener = function() {
      projChangeFunction(projectSelector, labelSelector, aliasIndex, fields);
    };

    var propChangeListener = function(e) {
      aliasList.onPropertyChange(aliasIndex, fields, true);
    };
    var aliasRemoveListener = function(e) {
      aliasList.onRemoveClick(aliasIndex, aliasRow);
    };

    // Add these after the DOM has been updated; Safari doesn't do it
    // right otherwise.  [shep 15.Sep.2007]
    setTimeout(function() {
		 Debug.println("Adding handlers for alias row ", aliasList.fieldNamespace, " : ", aliasIndex);
		 Events.attach(recipientInput,  Events.Change, aliasList, propChangeListener);
		 Events.attach(projectSelector, Events.Change, aliasList, projChangeListener);
		 Events.attach(labelSelector,   Events.Change, aliasList, propChangeListener);
		 Events.attach(removeButton,    Events.Click,  aliasList, aliasRemoveListener);
	       }, 100);

  },


  addEmptyRow: function(show) {

    var emptyRow = document.createElement("TR");
    emptyRow.id = this.fieldNamespace + "_empty";
    emptyRow.className = "none";
    emptyRow.style.display = show ? "" : "none";

    var emptyCell = document.createElement("TD");
    emptyCell.className = "remove";
    emptyCell.appendChild(document.createTextNode(" "));
    emptyRow.appendChild(emptyCell);

    emptyCell = document.createElement("TD");
    emptyCell.setAttribute("colspan", "3");
    emptyCell.colSpan = 3;
    emptyCell.appendChild(document.createTextNode("(" + i18n("None", "None") + ")"));
    emptyRow.appendChild(emptyCell);

    this.aliasRowContainer.appendChild(emptyRow);

  },


  toggleEmptyRowDisplay: function(show) {
    document.getElementById(this.fieldNamespace + "_empty").style.display = show ? "" : "none";
  },


  onLabelProjectChange: function(projectSelector, labelSelector, aliasIndex, fields) {
    var selectedOption = projectSelector.options[projectSelector.selectedIndex];
    this.makeDirty();
    this.fillInLabels(selectedOption.value, labelSelector, labelSelector.options[labelSelector.selectedIndex].value, aliasIndex, fields);
  },


  fillInLabels: function(labelProjectName, labelSelector, labelName, aliasIndex, fields) {
    Debug.println("fillInLabels(", labelProjectName, ", ", labelSelector, ", ", labelName, ", ", aliasIndex, ", ", fields);
    Traction.LabelCache.getLabels(labelProjectName, this.fillInLabels_wakeup.bind(this),
				  { labelSelector: labelSelector, labelName: labelName, aliasIndex: aliasIndex, fields: fields });
  },


  fillInLabels_wakeup: function(labelArray, targetData) {

    Debug.println("fillInLabels_wakeup...");

    targetData.labelSelector.options.length = 0;

    var selectedIndex = -1;
    if (targetData.labelName != "") {
      var matchRSLabel = targetData.labelName.toLowerCase().replace(/\s+/gi, "");
      for (var idx = 0; idx < labelArray.length; idx ++) {
	if (labelArray[idx].label.toLowerCase().replace(/\s+/gi, "") == matchRSLabel) {
	  selectedIndex = idx;
	  break;
	}
      }
    }

    var i = 0;
    if (selectedIndex == -1) {
      targetData.labelSelector.options[i] = new Option("", "");
      targetData.labelSelector.options[i].selected = true;
      i ++;
    }
    for (var l = 0; l < labelArray.length; l ++) {
      targetData.labelSelector.options[i] = new Option(labelArray[l].label, labelArray[l].label);
      if (targetData.labelName == labelArray[l].label) {
	targetData.labelSelector.options[i].selected = true;
      }
      i ++;
    }

    if (typeof(targetData.aliasIndex) != "undefined") {
      this.onPropertyChange(targetData.aliasIndex, targetData.fields, false);
    }

  },


  getAliasLabelSerialization: function() {
    var s = "";
    var next = 0;
    for (var num = 0; num < this.aliases.length; num ++) {
      if (!this.aliases[num].removed) {
	if (next > 0) {
	  s += ",";
	}
	s += this.aliases[num].serialize(next);
	next ++;
      }
    }
    return s;
  },


  getAliasRecipientsSerialization: function() {
    var s = "";
    for (var num = 0; num < this.aliases.length; num ++) {
      if (!this.aliases[num].removed) {
	if (s != "") {
	  s += ",";
	}
	s += this.aliases[num].recipient;
      }
    }
    return s;
  },


  onPropertyChange: function(aliasIndex, fields, dirty) {
    Debug.println("onPropertyChange for ", aliasIndex, "...");
    var alias = this.aliases[aliasIndex];
    alias.recipient = fields.recipient.value;
    alias.labelProjectName = fields.project.options[fields.project.selectedIndex].value;
    alias.labelName = fields.label.options[fields.label.selectedIndex].value;
    Debug.println("Finished updating properties for ", aliasIndex, ": ", alias.serialize(""));
    if (dirty) {
      this.makeDirty();
    }
  },


  onRemoveClick: function(aliasIndex, aliasRow) {
    // mark it disabled
    this.aliases[aliasIndex].removed = true;
    // hide the row
    show_TR(false, aliasRow);
    this.makeDirty();
  },


  onAddClick: function() {
    var newAlias = new Traction.MailAliasRule("", this.currentProjectName, "");
    var newAliasIndex = this.aliases.length;
    this.aliases[newAliasIndex] = newAlias;
    this.addAliasRow(newAlias, newAliasIndex);
    this.makeDirty();
  },


  makeDirty: function() {
    this.mailToField.value = this.getAliasLabelSerialization();
    this.mailCatsField.value = this.getAliasRecipientsSerialization();
    Debug.println("New mailto: ", this.mailToField.name, " = ", this.mailToField.value, "; New mailcats: ", this.mailCatsField.name, " = ", this.mailCatsField.value);
    var nonRemoved = 0;
    for (var i = 0; i < this.aliases.length; i ++) {
      if (!this.aliases[i].removed) {
	nonRemoved ++;
      }
    }
    this.toggleEmptyRowDisplay(nonRemoved == 0);
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  }


};


Settings.LabelGroupSelect = Class.create();
Settings.LabelGroupSelect.prototype = Object.extend(new Settings.GroupSelect(),

/*LabelGroupSelect =*/ {

  addNew: function() {

    var newLabelNameField = document.fm[this.fieldNamespace + "_new"];
    var newLabelName = newLabelNameField.value.trim();
    if (newLabelName == "") {
      this.addNewError(i18n("setting_newproject_template_labels_topic_new_label_name_empty_alert_message", "Enter a label name to add."));
      return;
    }

    if (!checkCategory(newLabelName)) {
      this.addNewError((new MessageFormat(i18n("psetup_categories_invalid_label_name_error_message", "The label \"{0}\" contains invalid characters or an invalid sequence of colons.  Labels may contain colons, but may not begin or end with a colon, and may not have two or more consecutive colons.  Labels may not contain most other common punctuation.")).format(newLabelName)));
      return;
    }

    var selectedValuesSel = document.fm[this.fieldNamespace + "_selected"];
    var availableValuesSel = document.fm[this.fieldNamespace + "_available"];
    var flattenedLabelName = this.getFlattenedLabelName(newLabelName);

    if ((this.selectIndexOfLabelNameValue(selectedValuesSel, flattenedLabelName) != -1) ||
	(this.selectIndexOfLabelNameValue(availableValuesSel, flattenedLabelName) != -1)) {
      this.addNewError(i18n("setting_newproject_template_labels_topic_new_label_name_exists_alert_message", "That label has already been added."));
      return;
    }

    addOption(selectedValuesSel, newLabelName, newLabelName);
    this.clearError();
    newLabelNameField.value = "";
    this.validateAddNewValueButtonEnabled();
    this.consolidateSelection();
    this.makeDirty();
    newLabelNameField.focus();

  },

  getFlattenedLabelName: function(ln) {
    var discard = new RegExp("[\\s\"]+", "gi");
    return ln.replace(discard, "").toLowerCase();
  },

  selectIndexOfLabelNameValue: function(sel, flattenedLabelName) {
    var opts = sel.options;
    for (var i=0; i<opts.length; i++) {
      if (flattenedLabelName == this.getFlattenedLabelName(opts[i].value)) {
	return i;
      }
    }
    return -1;
  }


}

);


Settings.JournalSetup = {


  validatedFields: new Array(),


  onLoad: function() {
    Settings.JournalSetup.validateAll();
    Settings.JournalSetup.toggleThrobber(false);
  },


  onUnload: function() {
    Settings.JournalSetup.toggleThrobber(false);
  },


  previous: function() {
    history.go(-1);
  },


  next: function() {
    Settings.JournalSetup.apply();
  },


  toggleSectionCollapse: function(name) {
    collapse_toggle(name);
    document.fm.toggled_advanced.value = "true";
  },


  toggleThrobber: function(on) {
    var throbber = document.getElementById("throbber");
    if (throbber != null) {
      xmlprogress(on ? 1 : 0, throbber);
    }
  },


  validateAll: function() {
    if (document.fm.Previous) {
      document.fm.Previous.disabled = false;
    }
    for (var i = 0; i < Settings.JournalSetup.validatedFields.length; i ++) {
      if (!Settings.JournalSetup.validatedFields[i]) {
	document.fm.Next.disabled = true;
	return false;
      }
    }
    document.fm.Next.disabled = false;
    return true;
  },


  apply: function() {
//     document.fm.fb_submit.value = 'Apply';
    Settings.JournalSetup.toggleThrobber(true);
//     document.fm.Previous.disabled = true;
//     document.fm.Next.disabled = true;
//     document.fm.submit();
    go("Apply");
  }


};


Settings.JournalSetup.FirstJournal = {


  onLoad: function() {
    Settings.JournalSetup.toggleThrobber(false);
    Settings.JournalSetup.FirstJournal.setInitialFocus();
  },


  setInitialFocus: function() {
    document.fm.newjournal_first_user_name.focus();
  }


};


Settings.JdbcExt = {


  namespace: "",


  onLoad: function() {
    Settings.JournalSetup.toggleThrobber(false);
  },


  onUnload: function() {
    Settings.JournalSetup.toggleThrobber(false);
  },


  toggleSectionCollapse: function(name) {
    collapse_toggle(name);
    document.fm.toggled_advanced.value = "true";
  },


  toggleThrobber: function(on) {
    var throbber = document.getElementById("throbber");
    if (throbber != null) {
      xmlprogress(on ? 1 : 0, throbber);
    }
  },


  apply: function() {
    // address,port,sid,localpath,localpath_use_default,remotepath
    if (currentSelValue(document.fm[Settings.JdbcExt.namespace + "_type"]) == "derby_net") {
      document.fm[Settings.JdbcExt.namespace + "_address"].value = document.fm[Settings.JdbcExt.namespace + "_derby_address"].value;
      document.fm[Settings.JdbcExt.namespace + "_port"].value = document.fm[Settings.JdbcExt.namespace + "_derby_port"].value;
      document.fm[Settings.JdbcExt.namespace + "_remotepath"].value = document.fm[Settings.JdbcExt.namespace + "_derby_remotepath"].value;
    }
    else if (currentSelValue(document.fm[Settings.JdbcExt.namespace + "_type"]) == "derby_local") {
      document.fm[Settings.JdbcExt.namespace + "_localpath"].value = document.fm[Settings.JdbcExt.namespace + "_derby_localpath"].value;
      document.fm[Settings.JdbcExt.namespace + "_localpath_use_default"].value = (document.fm[Settings.JdbcExt.namespace + "_derby_localpath_use_default"].checked ? "true" : "false");
    }
    else if (currentSelValue(document.fm[Settings.JdbcExt.namespace + "_type"]) == "oracle") { // oracle
      document.fm[Settings.JdbcExt.namespace + "_address"].value = document.fm[Settings.JdbcExt.namespace + "_oracle_address"].value;
      document.fm[Settings.JdbcExt.namespace + "_port"].value = document.fm[Settings.JdbcExt.namespace + "_oracle_port"].value;
      document.fm[Settings.JdbcExt.namespace + "_sid"].value = document.fm[Settings.JdbcExt.namespace + "_oracle_sid"].value;
    }
    else { // custom
      document.fm[Settings.JdbcExt.namespace + "_url"].value = document.fm[Settings.JdbcExt.namespace + "_custom_url"].value;
      document.fm[Settings.JdbcExt.namespace + "_driver"].value = document.fm[Settings.JdbcExt.namespace + "_custom_driver"].value;
    }

    Settings.JdbcExt.toggleThrobber(true);
    dialog_apply("Apply");

  }


};


Settings.UserEmailAddressList = Class.create();
Settings.UserEmailAddressList.prototype = {


  externalAddresses: new Array(),


  localAddresses: new Array(),


  preferredAddress: null,


  rowContainer: null,


  fieldNamespace: "",


  isReadOnly: false,


  initialize: function(fieldNamespace, preferredAddress, isReadOnly) {
    this.fieldNamespace = fieldNamespace;
    this.preferredAddress = preferredAddress;
    this.isReadOnly = isReadOnly;
  },


  init: function() {
    this.initAddressRows();
    if (!this.isReadOnly) {
      document.fm[this.getName("add", false)].disabled = false;
    }
  },


  initAddressRows: function() {
    this.rowContainer = document.getElementById(this.getName("rows", false));
    this.addEmptyRow(this.externalAddresses.length == 0 && this.localAddresses.length == 0);
    for (var i = 0; i < this.externalAddresses.length; i ++) {
      this.addRow(this.externalAddresses[i], i, true);
    }
    for (var i = 0; i < this.localAddresses.length; i ++) {
      this.addRow(this.localAddresses[i], i, false);
    }
  },


  getName: function(name, isExternal, i) {
    var ret = this.fieldNamespace;
    if (typeof(i) != "undefined") {
      ret += "_" + i;
    }
    if (isExternal) {
      ret += "_external";
    }
    if (name) {
      ret += "_" + name;
    }
    return ret;
  },


  createRemoveCell: function(row, address, addressIndex, isExternal) {

    if (isExternal) {
      var emptyCell = document.createElement("TD");
      emptyCell.className = "remove";
      emptyCell.appendChild(document.createTextNode(" "));
      row.appendChild(emptyCell);
      return null;
    }

    var removeCell = document.createElement("TD");
    removeCell.className = "remove";
    var removeButton = document.createElement("INPUT")
    removeButton.type = "button";
    removeButton.name = this.getName("remove", false, addressIndex);
    removeButton.value = i18n("Remove", "Remove");
    if (this.isReadOnly) {
      removeButton.disabled = true;
    }
    removeCell.appendChild(removeButton);

    row.appendChild(removeCell);

    return removeButton;

  },


  createPreferredCell: function(row, address, addressIndex, isExternal) {
    var preferredCell = document.createElement("TD");
    var preferredInput = Form.newInput(this.getName("preferred", false));
    preferredInput.setAttribute("type", "radio");
    preferredInput.setAttribute("id", this.getName("preferred", isExternal, addressIndex));
    preferredInput.value = address;
    if (address == this.preferredAddress) {
      setTimeout(function() {
		   preferredInput.setAttribute("checked", "true");
		   preferredInput.checked = true;
		 }, 1);
    }
    preferredCell.appendChild(preferredInput);
    row.appendChild(preferredCell);
    return preferredInput;
  },


  createAddressCell: function(row, address, addressIndex, isExternal) {
    var addressCell = document.createElement("TD");
    var addressInput = Form.newInput(this.getName("address", isExternal, addressIndex));
    addressInput.type = "text";
    if (isExternal) {
      addressInput.setAttribute("readonly", "true");
      addressInput.readOnly = true;
      addressInput.style.backgroundColor = "#ebebe4";
      addressInput.style.color = "#aca899";
    }
    addressInput.value = address;
    addressInput.style.width = "100%";
    addressCell.appendChild(addressInput);
    row.appendChild(addressCell);
    return addressInput;
  },


  addRow: function(address, addressIndex, isExternal) {

    var row = document.createElement("TR");
    row.id = this.getName("", isExternal, addressIndex);

    var removeButton = this.createRemoveCell(row, address, addressIndex, isExternal);
    var preferredInput = this.createPreferredCell(row, address, addressIndex, isExternal);
    var addressInput = this.createAddressCell(row, address, addressIndex, isExternal);

    this.rowContainer.appendChild(row);

    var t = this;
    var propChangeListener = function(e) {
      t.onPropertyChange(addressIndex, isExternal, true);
    };

    var removeListener = function(e) {
      t.onRemoveClick(row, addressIndex);
    };

    // Add these after the DOM has been updated; Safari doesn't do it
    // right otherwise.  [shep 15.Sep.2007]
    setTimeout(function() {
		 Events.attach(addressInput, Events.Change, t, propChangeListener);
		 Events.attach(addressInput, Events.KeyUp, t, propChangeListener);
		 Events.attach(preferredInput, Events.Click, t, propChangeListener);
		 if (!isExternal) {
		   Events.attach(removeButton, Events.Click, t, removeListener);
		 }
	       }, 100);

  },


  addEmptyRow: function(show) {

    var emptyRow = document.createElement("TR");
    emptyRow.id = this.fieldNamespace + "_empty";
    emptyRow.className = "none";
    emptyRow.style.display = show ? "" : "none";

    var emptyCell = document.createElement("TD");
    emptyCell.className = "remove";
    emptyCell.appendChild(document.createTextNode(" "));
    emptyRow.appendChild(emptyCell);

    emptyCell = document.createElement("TD");
    emptyCell.setAttribute("colspan", "4");
    emptyCell.colSpan = 3;
    emptyCell.appendChild(document.createTextNode("(" + i18n("None", "None") + ")"));
    emptyRow.appendChild(emptyCell);

    this.rowContainer.appendChild(emptyRow);

  },


  toggleEmptyRowDisplay: function(show) {
    document.getElementById(this.fieldNamespace + "_empty").style.display = show ? "" : "none";
  },


  onPropertyChange: function(addressIndex, isExternal, dirty) {
    Debug.println("onPropertyChange for ", addressIndex, (isExternal ? " [external]": " [local]"), "...");
    if (!isExternal) {
      var address = document.fm[this.getName("address", isExternal, addressIndex)].value;
      this.localAddresses[addressIndex] = address;
      document.getElementById(this.getName("preferred", false, addressIndex)).value = address;
    }
    if (dirty) {
      this.makeDirty();
    }
  },


  onRemoveClick: function(row, index) {
    var changePreferred = document.getElementById(this.getName("preferred", false, index)).checked;
    // remove the row
    row.parentNode.removeChild(row);
    this.localAddresses[index] = ""; // just blow it away
    if (changePreferred) {
      this.selectDefaultPreferredEmail();
    }
    this.makeDirty();
  },


  selectDefaultPreferredEmail: function() {
    var elm = null;
    if (this.externalAddresses.length > 0) {
      elm = document.getElementById(this.getName("preferred", true, 0));
    }
    if (elm == null) {
      for (var i = 0; i < this.localAddresses.length; i ++) {
	if (trim(this.localAddresses[i]) != "") {
	  elm = document.getElementById(this.getName("preferred", false, i));
	  break;
	}
      }
    }
    if (elm) {
      elm.setAttribute("checked", "true");
      elm.checked = true;
    }
  },


  onAddClick: function() {
    var newAddressIndex = this.localAddresses.length;
    this.localAddresses[newAddressIndex] = "";
    this.addRow("", newAddressIndex, false);
    this.makeDirty();
  },


  makeDirty: function() {

    var nonRemoved = 0;
    var serialization = "";
    for (var i = 0; i < this.localAddresses.length; i ++) {
      if (trim(this.localAddresses[i]) != "") {
	if (nonRemoved > 0) {
	  serialization += ","
	}
	serialization += trim(this.localAddresses[i]);
	nonRemoved ++;
      }
    }
    document.fm[this.fieldNamespace].value = serialization;

    this.toggleEmptyRowDisplay(nonRemoved == 0 && this.externalAddresses.length == 0);
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }

  }


};


Settings.HiddenTextWithOptionsAndCustom = Class.create();
Settings.HiddenTextWithOptionsAndCustom.prototype = {


  fieldNamespace: null,


  initialize: function(fieldNamespace) {
    this.fieldNamespace = fieldNamespace;
  },


  getOptionValue: function() {
    var sel = document.fm[this.fieldNamespace + "_options"];
    return sel.options[sel.selectedIndex].value;
  },


  getCustomField: function() {
    return document.fm[this.fieldNamespace + "_custom"];
  },


  getCustomFieldContainer: function() {
    return document.getElementById(this.fieldNamespace + "_custom_container");
  },


  getCustomValue: function() {
    return this.getCustomField().value;
  },


  onChangeOption: function() {

    var oVal = this.getOptionValue();

    var useDef = (oVal.indexOf("def-val-") == 0);
    this.toggleUseDefault(useDef);


    var useCustom = (oVal == "custom");
    this.toggleShowCustomField(useCustom);

    if (useCustom) {
      this.onCustomValueChange();
      this.getCustomField().focus();
    }
    else if (useDef) {
      this.setHidden("");
    } else {
      this.setHidden(oVal);
    }

    if (typeof(makeDirty) == "function") {
      makeDirty();
    }

  },


  toggleUseDefault: function(useDef) {
    document.fm[this.fieldNamespace + "_usedefault"].value = (useDef ? "true" : "false");
  },


  toggleShowCustomField: function(useCustom) {
    show_default(useCustom, this.getCustomFieldContainer());
  },


  onCustomValueChange: function() {
    this.setHidden(this.getCustomValue());
  },


  setHidden: function(val) {
    document.fm[this.fieldNamespace].value = val;
  }


};


Settings.TextInputWithSuggestions = Class.create();
Settings.TextInputWithSuggestions.prototype = {


  fieldNamespace: null,


  defaultSuggestion: "",


  initialize: function(fieldNamespace, defaultSuggestion) {
    this.fieldNamespace = fieldNamespace;
    this.defaultSuggestion = defaultSuggestion;
  },


  getSuggestionSelector: function() {
    return document.fm[this.fieldNamespace + "_suggestions"];
  },


  getSelectedSuggestion: function() {
    var sel = this.getSuggestionSelector();
    return sel.options[sel.selectedIndex].value;
  },


  setValue: function(val) {
    document.fm[this.fieldNamespace].value = val;
  },


  toggleUseDefault: function(useDef) {
    document.fm[this.fieldNamespace + "_usedefault"].checked = useDef;
  },


  getUseDefault: function() {
    return document.fm[this.fieldNamespace + "_usedefault"].checked;
  },


  setTextValue: function(val) {
    document.fm[this.fieldNamespace].value = val;
  },


  customIsSelected: function() {
    var sel = this.getSuggestionSelector();
    return (selectIndexOfValue(sel, "") == sel.selectedIndex);
  },


  onChangeSuggestion: function() {
    if (!this.customIsSelected()) {
      this.setTextValue(this.getSelectedSuggestion());
    }
    this.toggleUseDefault(false);
    this.makeDirty();
  },


  selectCustomSuggestion: function() {
    var sel = this.getSuggestionSelector();
    sel.options[selectIndexOfValue(sel, "")].selected = true;
  },


  selectDefaultSuggestion: function() {
    var sel = this.getSuggestionSelector();
    var idx = selectIndexOfValue(sel, this.defaultSuggestion);
    if (idx == -1) {
      idx = selectIndexOfValue(sel, "");
    }
    sel.options[idx].selected = true;
  },


  onChangeText: function() {
    this.selectCustomSuggestion();
    this.toggleUseDefault(false);
    this.makeDirty();
  },


  onToggleUseDefaultCheckbox: function() {
    if (this.getUseDefault()) {
      this.selectDefaultSuggestion();
      this.setTextValue(this.getSelectedSuggestion());
    }
    this.makeDirty();
  },


  makeDirty: function() {
    if (typeof(makeDirty) == "function") {
      makeDirty();
    }
  }


};


Settings.JournalSetup.Confirmation = {

  pinger: null,

  restartEnabled: true,

  testing: false,

  progressCallbackHandler: null,

  onlineText: null,

  offlineText: null,

  init: function(restartEnabled, testing) {
    Settings.JournalSetup.Confirmation.restartEnabled = restartEnabled;
    Settings.JournalSetup.Confirmation.testing = testing;
  },

  initNormal: function(restartEnabled, testing, onlineText, offlineText) {
    Settings.JournalSetup.Confirmation.init(restartEnabled, testing);
    Settings.JournalSetup.Confirmation.onlineText = onlineText;
    Settings.JournalSetup.Confirmation.offlineText = offlineText;
    if (restartEnabled) {
      Settings.JournalSetup.Confirmation.startPingingNormal(false);
    }
  },

  initForRecordProcessProgress: function(progressCallbackHandler, restartEnabled, testing) {
    Settings.JournalSetup.Confirmation.init(restartEnabled, testing);
    Settings.JournalSetup.Confirmation.progressCallbackHandler = progressCallbackHandler;
    if (restartEnabled) {
      Settings.JournalSetup.Confirmation.startPingingForRecordProcess(false);
    }
  },

  pingSetup: function(callbackHandler, throbber, waitBeforeStarting) {
    Settings.JournalSetup.Confirmation.pinger =
      new Util.PingServer(throbber, null /* no status text area */,
			  5000 /* 5s between pings */, 45 /* max attempts */, true,
			  null /* no callback on each failure */,
			  callbackHandler);
    setTimeout(function() {
		 Settings.JournalSetup.Confirmation.pinger.start();
	       }, waitBeforeStarting);
  },

  startPingingNormal: function(now) {
    Settings.JournalSetup.Confirmation.pingSetup(Settings.JournalSetup.Confirmation.onServerStatusCallbackNormal,
						 document.getElementById("poll-spinner"),
						 now ? 1 : (Settings.JournalSetup.Confirmation.testing ? 5000 : 10000));
  },

  startPingingForRecordProcess: function(now) {
    Settings.JournalSetup.Confirmation.pingSetup(Settings.JournalSetup.Confirmation.onServerStatusCallbackForRecordProcess,
						 null /* manage our own throbber */,
						 now ? 1 : (Settings.JournalSetup.Confirmation.testing ? 5000 : 30000));
  },

  onServerStatusCallbackNormal: function(online) {
    Settings.JournalSetup.Confirmation.serveronline = online;
    document.getElementById("next").disabled = false;
    document.getElementById("next").style.display = '';
    xmlprogress(0, document.getElementById("poll-spinner"));
//     document.getElementById("throbber").style.display = 'none';
    var serverStateText = document.getElementById("serverstatetext");
    serverStateText.innerHTML = online ? Settings.JournalSetup.Confirmation.onlineText : Settings.JournalSetup.Confirmation.offlineText;
    if (Settings.JournalSetup.Confirmation.testing) {
      serverStateText.innerHTML = serverStateText.innerHTML + " (In test mode, you must click the button to continue.)";
    } else {
      Settings.JournalSetup.Confirmation.gotoFrontPage();
    }
  },

  onServerStatusCallbackForRecordProcess: function(online) {
    if (!online) {
      alert("The TeamPage server has not come back online yet after a long delay. Please check to make sure that the server started as expected.");
    }
    Settings.JournalSetup.Confirmation.enableRequestProgressButton();
    xmlprogress(0, document.getElementById("progress-request-spinner"));
    if (online) {
      Settings.JournalSetup.Confirmation.requestProgressReport();
    }
  },

  gotoFrontPage: function() {
    Settings.gotoView("home", false, "");
  },

  confirmOnlineStatus: function() {
    if (serveronline) {
      return true;
    }
    return confirm(i18n("jsetup_journal_creation_complete_server_offline_next_link_confirmation_message",
			"We have not yet established that the server is online.  Are you sure you would still like to try to navigate to the home page?"));
  },

  enableRequestProgressButton: function() {
    var b = document.getElementById("request-progress-button");
    b.disabled = false;
    b.value = Settings.JournalSetup.Confirmation.testing ? "Update (test)" : "Update"
  },

  requestProgressReport: function() {
    document.getElementById("request-progress-button").disabled = true;
    var url = FORM_ACTION_RECORD_PROCESSOR + "?type=recprocprogress_&ajax=true";
    if (Settings.JournalSetup.Confirmation.testing) {
      url += "&test=true";
    }
    xmlget_async(url, document.getElementById("progress-request-spinner"), true, Settings.JournalSetup.Confirmation.showProgress, null, 0);
  },

  setFinished: function() {
    show_default(false, document.getElementById("request-progress-button"));
    show_default(true, document.getElementById("go-home-button"));
  },

  showProgress: function(responseText) {

    document.getElementById("request-progress-button").disabled = false;

    if (wasUnreachable(responseText)) {
      document.getElementById("progress-message").innerHTML = "Server is offline.";
      return;
    }

    var unauthorized = wasUnauthorized(responseText);

    if (!unauthorized) {
      var error = findErrorAndFeedback(responseText)[0];
      if (error != null && error != "" && error != "undefined") {
	alert(error);
	return;
      }
    }

    if (unauthorized ||
	responseText == "nodata") {
      document.getElementById("progress-message").innerHTML = Settings.JournalSetup.Confirmation.progressCallbackHandler.getCompletionMessage();
      Settings.JournalSetup.Confirmation.setFinished();
      return;
    }

    Settings.JournalSetup.Confirmation.fillInProgressReport(responseText);

  },

  fillInProgressReport: function(responseText) {

    var data;
    eval("data = " + responseText);

    var progressTotal = 0;
    var total = 0;

    for (var i = 0; i < data["progress"].length; i ++) {
      var typeCell = document.getElementById("record-type-" + i);    
      show_TR(data["totals"][i] != 0, typeCell.parentNode);
      typeCell.innerHTML = data["progress"][i] + " / " + data["totals"][i];
      progressTotal += data["progress"][i];
      total += data["totals"][i];
    }

    var progressMsg;
    if (total == 0) {
      progressMsg = Settings.JournalSetup.Confirmation.progressCallbackHandler.getCompletionMessage();
      Settings.JournalSetup.Confirmation.setFinished();
    } else {
      var pctDone = Math.round((progressTotal / total) * 10000) / 100;
      progressMsg = pctDone + "% of Records Processed";
      document.getElementById("record-type-total").innerHTML = progressTotal + " / " + total;
    }

    document.getElementById("progress-message").innerHTML = progressMsg;

  }

};


Settings.JournalSetup.Confirmation.Import = {

  init: function(restartEnabled, testing) {
    Settings.JournalSetup.Confirmation.initForRecordProcessProgress(this, restartEnabled, testing);
  },

  getCompletionMessage: function() {
    return "Import Complete; Server Restarting";
  }

};


Settings.JournalSetup.Confirmation.Rebuild = {

  init: function(restartEnabled, testing) {
    Settings.JournalSetup.Confirmation.initForRecordProcessProgress(this, restartEnabled, testing);
  },

  getCompletionMessage: function() {
    return "Rebuild Complete; Server Restarting";
  }

};


// Borrowed from yui-traction-docmgt Util.js
if (!Form.newInput) {
Form.newInput = function(name) {
  if (isIE/* from shared.js*/ && name) {
    return document.createElement("<input name=\"" + name + "\" >");a
  } else {
    var ret = document.createElement("input");
    if (name) ret.name = name;
    return ret;
  }
};}
