// A map icon image is identified by a capital letter between the icon base
// name and the icon extension. Also identify the disallowed key/killword and
// password characters. (The Google base URL is defined in java custom.)
//
var icon_extension = ".png";
var icon_base = google_base_url + "/mapfiles/marker";
var disallowed_keyword_characters = /[^-\w%. "]/g;

// Identify the start and join components to assemble a Google Calendar
// subscription URL from calendar IDs of groups. Also, identify the beginning
// and end components for an iCalendar feed URL of a group's calendar ID. (The
// calendar base URL and the XML subscription are defined in java custom.)
//
var gcal_subscription_start = calendar_base_url + "/render?cid=";
var gcal_subscription_join = "&cid=";
var ical_subscription_pre = calendar_base_url + "/ical/";
var ical_subscription_post = xml_subscription_post + ".ics";

// Identify the default source for the calendar iframe node.
//
var calendar_source_default = calendar_base_url + "/embed?"
    + "showTitle=0&height=500&wkst=2&bgcolor=%23FFFFFF&"
    + "src=p5bf54btk9v2qvtqbsn37ljmqk%40group.calendar.google.com&"
    + "color=%2329527A&ctz=America%2FLos_Angeles";

// Create an identifier that breaks up a cookie for a group to extract out its
// hexademical calendar event color. Identify the start of the iframe src URL
// for the color specification, and the ID of the iframe node that we put the
// coalition calendar into.
//
var cal_color_start = "color=%23";
var group_event_color_cookie_cutter = "(?:c([0-9A-F]{6}))";
var calendar_node_id = "coalition_calendar";

// Identify the coalition cookie name, used to store the group ID numbers that
// are in the user's coalition, and the HTML node ID that contains the
// coalition group listing. Also, identify the HTML node ID that contains the
// group finder group listing.
//
var coalition_cookie_name = "cal_coalition_groups";
var coalition_listing_node_id = "coalition_listing";
var finder_listing_node_id = "finder_listing";

// Make an object to store data for the groups in our finder and coalition
// group listings, as well as for the groups displayed in the user's coaliton
// calendar.
//
var finder_group_data = {};
var coalition_group_data = {};
var coalition_calendar_data = {};

// Make a global variable to record the group that the color picker was
// brought up for. Also, store the default coalition calendar's source URL, as
// well as the start and end parts that are independent of the group-specific
// specifications.
//
var color_picker_group_id;
var coalition_calendar_source_default;
var calendar_source_start;
var calendar_source_end;

// Pass in the infobox identifier
//
function coalition_subscribe(type, infobox_id, mouse_event)
    {

    // Make an array to hold the calendar IDs and a variable to prepend on any
    // code error messages that we have. Also, make variables for any message
    // that we need to give to the user and for the calendar time zone.
    //
    var calendar_ids = new Array();
    var code_error = "Code error: Coalition subscribe: ";
    var message, time_zone;

    // If we are just to report the coalition groups on the calendar, do that
    // and eject.
    //
    if (report_calendar_groups(coalition_group_data, mouse_event))
        {return;}

    // Add the calendar ID for each group in the user's coalition to our list
    // of calendar IDs.
    //
    for (var group_id in coalition_group_data)
        {

        // Get the calendar source from the group ID, and then get the matches
        // for the calendar ID from the source.
        //
        var matches = calendar_source_matches(group_id);
        var source = matches[2];
        matches = source.match(calendar_id_from_source_matcher);

        // If we have matches, pull the calendar ID from it and add it to our
        // list of calendar IDs and also get the first time zone specification
        // we find. If there are no matches, let the user know we are skipping
        // the group--this is a code error, but not a critical one...
        //
        if (matches && matches.length > 1)
            {
            calendar_ids.push(matches[1]);
            if (matches[2] && ! time_zone)
                {time_zone = matches[2];}
            }
        else
            {
            alert(code_error + "Bad calendar source for group: " + group_id
                + " (skipped)");
            }
        }

    // If we did not find any calendar IDs, the user has nothing to subscribe
    // to.
    //
    if ( ! calendar_ids.length)
        {
        alert("No groups in coalition to subscribe to");
        return;
        }

    // Handle the type of subcription the user wants to make for the user's
    // coalition groups, which might require setting a message to give the the
    // user. If the type is unknown, eject with a code error.
    //
    if (type == "gcal")
        {

        // Assemble the URL to request the Google calendar to add the groups
        // in the user's coalition, and open it in a new window.
        //
        var url = gcal_subscription_start
            + calendar_ids.join(gcal_subscription_join);
        window.open(url);
        }
    else if (type == "ical")
        {

        // Define the pre and post strings that surround each calendar ID.
        //
        var pre = "<br>" + ical_subscription_pre;
        var post = ical_subscription_post + "\n";

        // Create a message and iCal feed listings for the user to subscribe
        // to the user's coalition groups.
        //
        message = "Here are the iCalendar feeds for your coalition groups:\n"
            + "<br>" + pre + calendar_ids.join(post + pre) + post
            + "<br>&nbsp;";
        }
    else if (type == "xml")
        {

        // Define the pre and post strings that surround each calendar ID.
        //
        var pre = "<br>" + xml_subscription_pre;
        var post = xml_subscription_post + "\n";

        // Create a message and XML feed listings for the user to subscribe
        // to the user's coalition groups.
        //
        message = "Here are the XML feeds for your coalition groups:\n"
            + "<br>" + pre + calendar_ids.join(post + pre) + post
            + "<br>&nbsp;";
        }
    else if (type == "other")
        {

        // Get the coalition calendar node, or eject with an error.
        //
        var calendar_node = document.getElementById(calendar_node_id);
        if ( ! calendar_node)
            {die(code_error + "Cannot find coalition calendar");}

        // Use the coalition calendar source URL, except change the embed
        // tag with the embed helper.
        //
        var embed_url = calendar_node.src;
        embed_url = embed_url.replace("embed", "embedhelper");

        // Assemble the HTML message for information about subscribing in
        // other ways to the events of the groups in the coalition.
        //
        message = "Click on one of the following links for information on how"
            + " to:\n"
            + "<ul>\n"
            + "<li><a target=\"_blank\"\n"
            + "href=\"http://www.google.com/support/calendar/bin/answer.py"
            + "?answer=37245&topic=15301\">Subscribe\n"
            + "to your coalition&rsquo;s events via email.</a></li>\n"
            + "<li><a target=\"_blank\"\n"
            + "href=\"http://www.google.com/ig/directory?hl=en&type=gadgets"
            + "&url=www.google.com/ig/modules/calendar3.xml\">Add\n"
            + "a Google Calendar &ldquo;gadget&rdquo; to your iGoogle home\n"
            + "page.</a></li>\n"
            + "<li><a target=\"_blank\"\n"
            + "href=\"" + embed_url + "\">Embed\n"
            + "your subscribed Google Calendar coalition on your\n"
            + "webpage.</a></li>\n"
            + "</ul>\n";
        }
    else
        {die(code_error + "Unhandled coalition subscription type: " + type);}

    // If we have a message to give, deliver it to the user via the passed in
    // infobox identifier.
    //
    if (message)
        {

        // Define the name of the infobox's font node, using the identifier
        // that was passed in, and get that node. Make sure we have the
        // infobox ID, or eject with an error.
        //
        var infobox_font_id = infobox_id + "_font_ib";
        var infobox_font_node = document.getElementById(infobox_font_id);
        if ( ! infobox_id)
            {die(code_error + "No infobox ID passed in for message");}

        // If we can find the infobox font node, replace it's text with the
        // message that we have and stick the infobox, which will also bring
        // any existing such infobox down as well.
        //
        if (infobox_font_node)
            {
            infobox_font_node.innerHTML = message;
            stick_ib(infobox_id, mouse_event);
            }
        }
    }

// Pass in the form object that includes all the input fields that identify
// what conditions to find groups under, and this function assembles the SQL
// WHERE condition from these fields, and passes it to an AJAX script to
// find the groups and assemble the HTML code to make the group listing, via
// the groups found callback function.
//
function find_groups(node, mouse_event)
    {

    // Give the user an indication that they are waiting for us to finish
    // execution. Try to obtain the information about the range area, and make
    // a variable for our code errors (optimistic eh?).
    //
    document.body.style.cursor = "wait";
    var parameters = get_range_area_info();
    var code_error = "Code error: Find groups: ";

    // Eject with an error if no node was passed in.
    //
    if ( ! node)
        {die(code_error + "No node passed in");}

    // If we are just to report the finder groups on the calendar, do that,
    // return the cursor back to normal, and eject.
    //
    if (report_calendar_groups(finder_group_data, mouse_event))
        {
        document.body.style.cursor = "default";
        return;
        }

    // If we failed to obtain the information about the range area, return the
    // cursor back to normal and let the user know that we need to know the
    // location (or we have a code error!).
    //
    if ( ! parameters)
        {
        document.body.style.cursor = "default";
        alert("Unknown location: Try again or use the marker on the map");
        return;
        }

    // Create OR and AND lists for those types of SQL WHERE components that we
    // will soon assemble.
    //
    var where_or = [];
    var where_and = [];
    var index, latitude_id, longitude_id;

    // Although the syntax is strange, go through all the form elements that
    // have both an id, a name, and a value, because all the elements that we
    // are interested have those things in common.
    //
    for (index = 0; index < node.form.length; index++)
        {
        var element = node.form.elements[index];
        if (element.id && element.name && element.value)
            {

            // If the element is checked and is something related to finding
            // groups, add them to our parameter specification. Interest
            // checkboxes are OR'ed, so that, if the group has at least one
            // interest in common with the search, it will pop up. Record the
            // latitude and longitude form name, so we can search taking it
            // into account later. Keywords and killwords will just be passed
            // through the parameters list.
            //
            if (element.id.match(/^check_int_\w/) && element.checked)
                {where_or.push(element.name + " = \"y\"");}
            else if (element.id == "check_verified" && element.checked)
                {where_and.push(element.name + " = \"y\"");}
            else if (element.name.match(/^group_(?:key|kill)words$/))
                {
                parameters[element.name] =
                    element.value.replace(disallowed_keyword_characters, "");
                }
            else if (element.id.match(/^hidden_latitude$/))
                {latitude_id = element.name;}
            else if (element.id.match(/^hidden_longitude$/))
                {longitude_id = element.name;}
            }
        }

    // We should have gotten the latitude and longitude IDs, because we
    // already have gotten location-related elements from above.
    //
    if (latitude_id == undefined || longitude_id == undefined)
        {die(code_error + "No latitude/longitude names found");}

    // Specify that the latitude and longitude of the groups that we search
    // for are within the (square) range specified by the user.
    //
    where_and.push(latitude_id + " BETWEEN " + parameters.lat_min + " AND "
        + parameters.lat_max);
    where_and.push(longitude_id + " BETWEEN " + parameters.lng_min + " AND "
        + parameters.lng_max);

    // Assemble any OR where components in parentheses as an AND component.
    //
    if (where_or.length)
        {where_and.push("(" + where_or.join(" OR ") + ")");}

    // Assemble all the AND components in the SQL WHERE specification, and
    // indicate that the groups should be listed alphabetically.
    //
    parameters.sql_where = where_and.join(" AND ");
    parameters.alphabetical = true;

    // Pass in the parameters any coalition cookie value that we have, so the
    // find groups script can know what groups are in the user's coalition and
    // which of those has their group calendar displayed on the user's
    // coalition calendar.
    //
    var cookie_value = get_cookie(coalition_cookie_name);
    if (cookie_value)
        {parameters.coalition_cookie = cookie_value;}

    // Make the AJAX script call to find the groups.
    //
    ajax_request("http://thepeoplescoalition.org/GVP/find_groups/", find_groups_callback, parameters);
    }

// This function takes the AJAX find groups request results. The groups in the
// request text (HTML) gets put into the group listing, the HTML's java code
// gets executed (populating the data of the groups found), and markers for
// each group are placed on the map. The mouse cursor it finally updated back
// to normal.
//
function find_groups_callback(request)
    {

    // Define an error message. If the request did not go through for some
    // reason, complain about it and eject.
    //
    var code_error = "Code error: Find groups callback: ";
    if ( ! (request && request.statusText == "OK" && request.responseText))
        {die("Finding groups failed: Try again: " + request.statusText);}

    // If there is no map, how did we get here? Some of the remaining code
    // depends on the map, so we should just eject here if we have none.
    //
    if ( ! map)
        {die(code_error + "No map");}

    // Find the node that contains the finder listing, or eject with an error.
    //
    var finder_listing_node = document.getElementById(finder_listing_node_id);
    if ( ! finder_listing_node)
        {die(code_error + "Failed to find the finder listing node");}

    // If there were any previous group listing, remove their markers from the
    // map. Then, delete the data from this previous group listing.
    //
    for (var group_id in finder_group_data)
        {map.removeOverlay(finder_group_data[group_id].map_marker);}
    finder_group_data = {};

    // Put the AJAX-found HTML code inside of the group finder listing area,
    // with any associated JAVA code extracted and executed. Also, create
    // an icon to represent the groups on the map.
    //
    var html_listing = extract_and_eval_java(request.responseText);
    finder_listing_node.innerHTML = html_listing;
    var index = 0;

    // Go though each group's data and add a labeled marker on the map for
    // each group, unless there are just too many of them.
    //
    for (var group_id in finder_group_data)
        {

        // Make a capital letter label for the group, from "A" to "Z".
        // However, if we are going past "Z", we are done adding labels to the
        // map.
        //
        index++;
        var label = String.fromCharCode("A".charCodeAt(0) + index - 1);
        if (label.charCodeAt(0) > "Z".charCodeAt(0))
            {break;}

        // Define the foreground group icon image, using the letter label, and
        // the default Google icon specifications.
        //
        var image = icon_base + label + icon_extension;
        var group_icon = new GIcon(G_DEFAULT_ICON, image);

        // Make a location object and a map marker object for the group.
        //
        var location_obj = new GLatLng(finder_group_data[group_id].latitude,
            finder_group_data[group_id].longitude);
        finder_group_data[group_id].map_marker = new GMarker(location_obj,
            group_icon);

        // Put the marker on the map for the group. If the marker is clicked,
        // the name of the group should appear.
        //
        var info = "Group: <b>" + finder_group_data[group_id].name + "</b>";
        GEvent.addListener(finder_group_data[group_id].map_marker, "click",
            function()
                {
                finder_group_data[group_id].map_marker.openInfoWindowHtml(
                    info);
                });
        map.addOverlay(finder_group_data[group_id].map_marker);
        }

    // Because we just got a new listing of groups, it means that the old
    // listing was wiped out. Therefore, any groups not currently in the
    // user's coalition should be removed from the coalition calendar.
    //
    var calendar_change;
    for (var group_id in coalition_calendar_data)
        {

        // If the group is in not in the user's coalition, remove it from the
        // coalition calendar data (but not the cookie, because only the
        // groups in the user's coalition have a cookie). Otherwise, if there
        // is a color preference for the group's events, set the group event
        // color legend for this group to that color.
        //
        if ( ! coalition_group_data[group_id])
            {
            delete coalition_calendar_data[group_id];
            calendar_change = true;
            }
        else
            {
            var color = get_calendar_group_event_color(group_id);
            set_group_event_color_legend(group_id, color);
            }
        }

    // If the calendar changed, rerender it. Return the cursor back to the
    // default.
    //
    if (calendar_change)
        {render_coalition_calendar();}
    document.body.style.cursor = "default";
    }

// Pass in the group data object, with group ID attributes, and the mouse
// event associated with the calling function. If the mouse event was
// triggered with the shift key pressed, then this function reports any group
// ID numbers in the group data that are also on the calendar (selected) and
// returns true. If shift was not pressed, false is returned.
//
function report_calendar_groups(group_data, mouse_event)
    {

    // Create a variable to record any group ID numbers that we should report,
    // and we know whether to report or not by the shift key being held down
    // for the passed-in mouse event.
    //
    var group_id_numbers = new Array();
    var report = mouse_event && mouse_event.shiftKey
        || window.event && window.event.shiftKey;

    // Report the group ID numbers that are in the data and on the calendar if
    // we are to do so.
    //
    if (report)
        {

        // Go through all of the group data groups, and add only the group ID
        // numbers of the groups that are on the calendar, as these are the
        // only groups selected in the group data.
        //
        for (var group_id in group_data)
            {

            // If the group is on the calendar, add the group's ID number to
            // our list.
            //
            if (coalition_calendar_data[group_id])
                {group_id_numbers.push(group_id.match("[0-9]+"));}}

        // If we have groups to report, report their IDs to the user, each ID
        // separated by a space in a selectable field.
        //
        if (group_id_numbers.length)
            {
            prompt("Groups in this section displayed on the calendar:",
                group_id_numbers.join(" "));
            }
        else
            {
            alert("No groups in this section are displayed on the calendar");
            }
        }

    // Return whether we reported on the selected groups.
    //
    return report;
    }

// This function takes a sting that contains code lines and removes and
// executes a block of Java in that code. The markers of the Java script are
// "<JAVA_EVAL_FOR_RETURN_START>" and "<JAVA_EVAL_FOR_RETURN_END>", alone on
// separate lines. The rest of the code is returned to the user as a string.
//
function extract_and_eval_java(code)
    {

    // Make variables to store the Java code lines and the other lines, from
    // the string of code, separated into lines. (If the multiline worked for
    // expressions, this would no-doubt be a lot easier, or at least shorter.)
    //
    var index;
    var java_lines = new Array();
    var other_lines = new Array();
    var lines = code.split("\n");

    // Segreate our Java lines from the other lines, based on our custom Java
    // code identifiers.
    //
    for (index in lines)
        {

        // Take out, but identify, the Java script markers in the code. If we
        // have a Java block, keep grabbing lines as Java until the end of the
        // block.
        //
        if (lines[index].match(/^\s*<JAVA_EVAL_FOR_RETURN_START>\s*$/))
            {java_lines.push("");}
        else if (lines[index].match(/^\s*<JAVA_EVAL_FOR_RETURN_END>\s*$/))
            {

            // Evaluate our Java code (except the first line which is a
            // dummy), and add the return of the execution to the lines we
            // will return.
            //
            var eval_output = eval(java_lines.slice(1).join("\n"));
            other_lines.push(eval_output.replace(/\s+$/, ""));
            java_lines = new Array();
            }
        else if (java_lines.length)
            {java_lines.push(lines[index]);}
        else
            {other_lines.push(lines[index]);}}

    // Return a string of the remaining code that we have.
    //
    return other_lines.join("\n");
    }

// Pass in the element just previous to the password section and the group ID
// number that the password section is for, and this function populates this
// section with a small form to submit the password to edit the group. If the
// function is called again, the section gets depopulated and made invisible.
//
function toggle_group_password_section(element, group_id_number)
    {

    // Make sure the user passed in the appropriate arguments.
    //
    var code_error = "Code error: Toggle group password section: ";
    if ( ! element)
        {die(code_error + "No element passed in");}
    else if (isNaN(group_id_number))
        {die(code_error + "No group ID passed in");}

    // Get the next element, which should be the password section element. If
    // we fail, eject with an error.
    //
    var password_section_node = element_relative(element, "nextSibling");
    if ( ! (password_section_node
        && password_section_node.nodeName.match(/^(?:div|span)$/i)))
        {die(code_error + "No section found");}

    // If the password section is already displayed, just wipe it out.
    // Otherwise, if the user is logged in, create the contents of the
    // password section and display it.
    //
    if (password_section_node.style.display == "block")
        {
        password_section_node.style.display = "none";
        password_section_node.innerHTML = "";
        }
    else if (logged_in())
        {

        // Create the contents of the section box. Make an input field for the
        // group's password and a button to enter the group editor.
        //
        password_section_node.innerHTML = "<font color=\"red\">*</font>\n"
            + "Group Password: <input type=\"password\">\n"
            + "<input type=\"button\" onclick=\"enter_group_editor(this, "
            + group_id_number + ")\" value=\"Edit Group\">\n";

        // Display the password section.
        //
        password_section_node.style.display = "block";
        }
    }

// Pass in the element just after the password input elemement and the group
// ID number that this password is for, and this function grabs the password
// from that input field, transfers it to the official form to enter the group
// editor, and submits that form, also with a field for identifying the group.
//
function enter_group_editor(element, group_id_number)
    {

    // Make sure the user passed in the appropriate arguments.
    //
    var code_error = "Code error: Enter group editor: ";
    if ( ! element)
        {die(code_error + "No element passed in");}
    else if (isNaN(group_id_number))
        {die(code_error + "No group ID passed in");}

    // Get the previous element, which should be the password input node. If
    // we fail, eject with an error.
    //
    var password_input_node = element_relative(element, "previousSibling");
    if ( ! password_input_node || password_input_node.type != "password")
        {die(code_error + "No password input node found");}

    // Get the password and the group password form node, along with its group
    // ID and password nodes.
    //
    var form_node = document.getElementById("group_edit_form");
    var id_node = document.getElementById("gvp_gid");
    var password_node = document.getElementById("gvp_pid");

    // If we failed to get those nodes, we have a serious problem, so just
    // eject.
    //
    if ( ! (form_node && id_node && password_node))
        {die(code_error + "Can't find form nodes");}

    // Put the password into an array of Unicode characters, in preparation
    // for transmission to the group registry.
    //
    var password_code = new Array();
    for (var index = 0; index < password_input_node.value.length; index++)
        {password_code.push(password_input_node.value.charCodeAt(index));}
    
    // Put the password code array into a string, update the ID for the group,
    // and submit and reset the form.
    //
    password_node.value = password_code.join("_");
    id_node.value = group_id_number;
    form_node.submit();
    form_node.reset();
    }

// Pass in a group identifier, and, if the group is not already in the user's
// coalition, the group listing information will be moved from the finder
// listing to the coalition listing; any map marker for the group will also be
// removed. All group listing information will be adjusted to be appropriate
// for the coalition listing. Finally, if the loading coalition argument is
// false, the cookie for the user's coalition group listing is updated to
// include the group.
//
function move_to_coalition(group_id, loading)
    {

    // If the group already appears to be in the coalition, just give a
    // message about it and eject.
    //
    if (coalition_group_data[group_id])
        {
        alert("Group already in coalition");
        return;
        }

    // Get the group row node from the group ID, and set a variable to report
    // our code errors.
    //
    var code_error = "Code error: Move to coalition: ";
    var group_row = document.getElementById(group_id);
    var group_cell;

    // If we did not get the group row node, die.
    //
    if ( ! group_row)
        {die(code_error + "Failed to find group row node: " + group_id);}

    // The data of the row should just be contained in a single cell. If we
    // can't find it, eject with an error.
    //
    if (group_row.cells.length)
        {group_cell = group_row.cells[0];}
    else
        {die(code_error + "Failed to find group cell node");}

    // Get the coalition table, or eject with an error.
    //
    var coalition_table = document.getElementById(coalition_listing_node_id);
    if ( ! coalition_table)
        {die(code_error + "Failed to find coalition table");}

    // If the group is in the user's coalition group listing, we should have
    // data on the group. So if we don't, eject with an error--something
    // strange is afoot at the Circle-K.
    //
    if ( ! finder_group_data[group_id])
        {die(code_error + "No data found for group ID: " + group_id);}

    // Find the finder table node from the finder group row, no matter how
    // high we need to go in the node hierarchy.
    //
    var finder_table = group_row.parentNode;
    while (finder_table && finder_table.nodeName.toLowerCase() != "table")
        {finder_table = finder_table.parentNode;}

    // Make sure we found the finder table, or eject with an error.
    //
    if ( ! finder_table)
        {die(code_error + "No finder table found");}

    // Clone the old group data cell, before we delete the entire row that
    // contains it from the finder table. IE seems to have problems without
    // this early cloning.
    //
    var old_cell = group_cell.cloneNode(true);
    finder_table.deleteRow(group_row.rowIndex);

    // Add a new row to the coalition table for the group data. Temporarily
    // turn off the row display, so we can modify the UI contents without the
    // user watching the shenanigans.
    //
    var new_row = coalition_table.insertRow(coalition_table.rows.length);
    var original_row_display = new_row.style.display;
    new_row.style.display = "none";

    // Add a new cell to the new row, and copy over the row ID, but append it
    // to indicate that this group row is now in the coalition. (We don't want
    // any duplicate IDs in case the user finds the same group again.)
    //
    var new_cell = new_row.insertCell(0);
    new_row.id = group_row.id + "-c";

    // If there is a marker for the group on the map, remove it.
    //
    if (map && finder_group_data[group_id].map_marker)
        {map.removeOverlay(finder_group_data[group_id].map_marker);}

    // Move the group data from the finder listing data to the coaliton
    // listing data.
    //
    coalition_group_data[group_id] = finder_group_data[group_id];
    delete finder_group_data[group_id];

    // If there is nothing left in the table listing groups, go ahead and
    // remove the table. This action is probably unneeded though.
    //
    if ( ! finder_table.rows.length)
        {finder_table.parentNode.removeChild(finder_table);}

    // Copy all the children nodes of the old group cell over to the new group
    // cell, which is in the coalition table. For some unknown reason, the
    // cloning part seems to be critical.
    //
    for (var index = 0; index < old_cell.childNodes.length; index++)
        {
        var child_node = old_cell.childNodes[index];
        new_cell.appendChild(child_node.cloneNode(true));
        }

    // Find the informational elements of the new group cell that we just
    // created. If we can't find these elements, something went terribly
    // wrong.
    //
    var new_elements = children_elements(new_cell);
    if ( ! new_elements.length)
        {die(code_error + "Failed to find new group elements");}

    // Go through the new group's elements, and change any elements to be
    // appropriate for the user's coalition listing.
    //
    for (var index in new_elements)
        {

        // Hide the current element if the class indicates that the new
        // element is only for the finder listing. Change the button (should
        // be only one) from being for adding the group to the user's
        // coalition to removing the group from the user's coalition.
        //
        if (new_elements[index].className == "finder_listing_only")
            {new_elements[index].style.display = "none";}
        else if (new_elements[index].type == "button")
            {

            // Change the button in title and functionality to be for removing
            // the group from the user's coalition.
            //
            new_elements[index].value = "Remove";
            new_elements[index].title = "Remove this group from My Coalition";
            new_elements[index].onclick =
                function() {remove_from_coalition(new_row, group_id);};
            }
        }

    // Now that we have completed the changed to the row, reset the display of
    // the row.
    //
    new_row.style.display = original_row_display;

    // If we are not loading all the coalition groups, update the user's
    // preferences via the cookie and update the numbering of the groups in
    // the coalition.
    //
    if ( ! loading)
        {
        renumber_coalition_groups();
        update_group_coalition_cookie(group_id);
        }
    }

// Pass in a group identifier, and the group listing information will be
// removed from the user's coalition listing and cookie.
//
function remove_from_coalition(group_row, group_id)
    {

    // Define a code error message for this function, and try to obtain the
    // coalition table elements.
    //
    var code_error = "Code error: Remove from coalition: ";
    var coalition_table = document.getElementById(coalition_listing_node_id);

    // If we failed to get one of the elements we should have, eject with an
    // error.
    //
    if ( ! group_row)
        {die(code_error + "No group row node");}
    if ( ! coalition_table)
        {die(code_error + "No coalition table");}

    // Remove the group row from the coalition table, undefine the group data
    // from the coalition listing data, renumber the coalition groups, and
    // remove the group ID from the group coalition cookie. 
    //
    coalition_table.deleteRow(group_row.rowIndex);
    delete coalition_group_data[group_id];
    renumber_coalition_groups();
    update_group_coalition_cookie(group_id, undefined, undefined, true);

    // If the group we just removed from the user's coalition was in the
    // coalition calendar but not also in the finder group listing, remove the
    // group's calendar from the user's coalition calendar, in both data and
    // form.
    //
    if (coalition_calendar_data[group_id] && ! finder_group_data[group_id])
        {
        delete coalition_calendar_data[group_id];
        render_coalition_calendar();
        }
    }

// This function renumbers the groups listed in the user's coalition section.
//
function renumber_coalition_groups()
    {

    // Set a variable to report our code errors.
    //
    var code_error = "Code error: Renumber coalition groups: ";
    var coalition_table = document.getElementById(coalition_listing_node_id);

    // Get the group row node from the group ID, or die.
    //
    if ( ! coalition_table)
        {die(code_error + "Failed to find coalition table");}

    // Make a variable to hold the current group cell node, and get an array
    // of all the coalition table rows.
    //
    var group_cell;
    var rows = coalition_table.rows;

    // Iterate through each row, which represents a group. Update the label
    // for each group to be a renumbered index.
    //
    for (var row_index = 0; row_index < rows.length; row_index++)
        {

        // Get the data cell of the row, the first child node. If we can't
        // find the group data cell, just eject with an error.
        //
        if (rows[row_index].cells.length)
            {group_cell = rows[row_index].cells[0];}
        else
            {die(code_error + "Group cell not in coalition table row");}

        // Find the informational elements of the group cell, or eject with an
        // error.
        //
        var group_elements = children_elements(group_cell);
        if ( ! group_elements.length)
            {die(code_error + "Failed to find group elements");}

        // Go through the group's elements looking for the group's label. Once
        // it is found, update it to the group index plus one.
        //
        for (var element_index in group_elements)
            {

            // If we find the element that contains the group label, replace
            // the contents of it to the new group index, and eject from the
            // element searching.
            //
            if (group_elements[element_index].className == "group_label")
                {
                group_elements[element_index].innerHTML
                    = (Number(row_index) + 1) + ".";
                break;
                }
            }
        }
    }

// Pass in the group identifier (like "group_523"), and this function will
// make sure that the user's cookie used to identify the groups in the
// coalition is updated. A "c" character will prepending the group identifier
// number in the cookie if the group's calendar is displayed on the user's
// coalition calendar, which is checked via the UI unless an override boolean
// value is passed in (last argument). If the remove argument is true, then
// the group's identifier number will be removed from the cookie.
//
function update_group_coalition_cookie(group_id, display, color, remove)
    {

    // Create, not to be too optimistic, a string to start any code error
    // messages. Then, extract the group ID number from the group ID.
    //
    var code_error = "Code error: Update group coalition cookie: ";
    var group_id_number = Number(group_id.match(/\d+$/));
    var identifier = group_id_number;

    // If we failed to get the group identifier number, something is seriously
    // wrong, so eject with an error.
    //
    if ( ! identifier)
        {die(code_error + "Failed to extract the group ID number");}

    // Only prepend the identifier with a "c" character if we are not removing
    // the identifier and if the group calendar is displayed on the user's
    // coalition calendar, which may be identified through the passed-in
    // variable or through the corresponding UI.
    //
    if (remove || display === false)
        {;}
    else if (display === true)
        {identifier = "c" + identifier;}
    else
        {

        // As there they are synchronized, use the first display checkbox that
        // we find, or die with an error because this could be a problem.
        // Prepend the group ID number with a "c" character if the group's
        // calendar is checked, to indicate that the calendar is displayed.
        //
        var checkboxes = document.getElementsByName(group_id + "-display");
        if ( ! checkboxes.length)
            {
            die(code_error + "Cannot find a display checkbox: " + group_id
                + "-display");
            }
        else if (checkboxes[0].checked)
            {identifier = "c" + identifier;}
        }

    // If we have a color specification for the group's calendar events, add
    // that to our identifier.
    //
    if (color)
        {identifier += "c" + color;}

    // Create variables for the old and new cookie components of group
    // identifiers. Then, get the cookie value that specifies the groups in
    // the user's coalition.
    //
    var old_crumbs = new Array();
    var new_crumbs = new Array();
    var cookie_value = get_cookie(coalition_cookie_name);
 
    // If we got cookie value, split it up into the cookie crumbs, one for
    // each group identifier.
    //
    if (cookie_value)
        {old_crumbs = cookie_value.split("_");}

    // Append or set the cookie value with the number of the beast--uhh, I
    // mean, group--that is currently being added to the user's coalition.
    //
    if (cookie_value != null && cookie_value != "")
        {
        
        // Make a matcher for the group identifier with a possible "c"
        // character prepending it (if it was previously set as displayed on
        // the user's coalition calendar) and a possible "c" and hexadecimal
        // color event color appending it (if the user specified a event color
        // preference for the group).
        //
        var index;
        var identifier_matcher = new RegExp("^c?" + group_id_number
            + group_event_color_cookie_cutter + "?$");

        // Assemble our list of new crumbs based on the old crumbs, but with
        // any blank crumbs removed and any crumbs matching the identifier
        // added or removed, as appropriate.
        //
        for (index in old_crumbs)
            {

            // If an old cookie crumb matches the current identifier, then add
            // it to our new cookie crumbs only if we are not to remove it and
            // we have not already added it before. Otherwise, just transfer
            // the old crumb to the new crumb array, if there is a real crumb.
            //
            var matches = old_crumbs[index].match(identifier_matcher);
            if (matches && matches.length)
                {

                // If we have not already added the identifier and we are not
                // to remove it, add it to the new cookie crumbs array, and
                // undefine the identifier so we won't add it any more times.
                //
                if (identifier != undefined && ! remove)
                    {

                    // If there was no color setting for the group cookie,
                    // preserve any color setting that already exists in the
                    // cookie.
                    //
                    if (color == undefined && matches[1])
                        {identifier += "c" + matches[1];}

                    // Add the group cookie identifier to our list of other
                    // cookies. Mark the cookie crumb identifier as consumed.
                    //
                    new_crumbs.push(identifier);
                    identifier = undefined;
                    }
                }
            else if (old_crumbs[index] != "")
                {new_crumbs.push(old_crumbs[index]);}}

        // If we never added the group identifier and we are not to remove it,
        // add it now.
        //
        if (identifier != undefined && ! remove)
            {new_crumbs.push(identifier);}}
    else if ( ! remove)
        {new_crumbs.push(identifier);}

    // If we have new crumbs, form a new cookie out of it, or eat the last one
    // if we don't have any new crumbs.
    //
    if (new_crumbs.length)
        {
        cookie_value = new_crumbs.join("_");
        set_cookie(coalition_cookie_name, cookie_value, cookie_max_days);
        }
    else if (cookie_value != null)
        {eat_cookie(coalition_cookie_name);}}

// Call this function after the map is loaded, but on page load, to load the
// user's coalition group listing. The groups are found via a user's cookie,
// and the groups are found through the find groups script via an AJAX request
// that follows up to our callback function. If no groups are found, then our
// default calendar is rendered.
//
function load_coalition()
    {

    // Create some variables to hold the group ID numbers that are to be put
    // into the user's coalition section, as well as what of those groups are
    // to be displayed on the user's coalition calendar. Create a string to
    // start any code error messages.
    //
    var calendar_groups = new Array();
    var coalition_groups = new Array();
    var code_error = "Code error: Load coalition: ";

    // Get the value of the cookie identifying these groups, and split it out
    // into an array.
    //
    var crumbs = new Array();
    var cookie_value = get_cookie(coalition_cookie_name);
    if (cookie_value)
        {crumbs = cookie_value.split("_");}

    // Go through each group specified in the coalition cookie, and add them
    // to our list of groups to be added to the user's coalition, and also
    // to the list, if appropriate, of group calendars that should be
    // displayed on the user's coalition calendar.
    //
    for (var index in crumbs)
        {

        // Get just the pure group ID number and add it to our list of groups
        // in the user's coalition. If the group ID crumb starts with a "c"
        // character, that means that the group's is to be displayed on the
        // user's coalition calendar.
        //
        var group_id_number = Number(crumbs[index].match(/\d+/));
        coalition_groups.push(group_id_number);
        if (crumbs[index].charAt(0) == "c")
            {calendar_groups.push(group_id_number);}}

    // If we have groups to be put in the user's coalition, kick off an AJAX
    // request to find the groups and use our callback to handle the results.
    // Otherwise, just default the user's coalition calendar.
    //
    if (coalition_groups.length)
        {

        // Try to obtain information about the range area, or eject with an
        // error, as we need this information to use our find groups script.
        // (However, for just populating the coalition listing, we don't
        // technically need this--I think.)
        //
        var parameters = get_range_area_info();
        if ( ! parameters)
            {die(code_error + "Failed to get range area info");}

        // Create the where condition to be match one of the identifiers for
        // the groups in the coalition. Also pass in the parameters any
        // coalition cookie value we have, so that the find groups script can
        // know what groups are in the user's coalition and which of those has
        // their group calendar displayed on the user's coalition calendar.
        //
        parameters.sql_where = "entry_id = "
            + coalition_groups.join(" OR entry_id = ");
        if (cookie_value)
            {parameters.coalition_cookie = cookie_value;}

        // Just to make things a little more independent, let's kick off a
        // (asynchonous) AJAX request. This will try to find the groups in the
        // user's coalition that we have assembled, and display them on the
        // calendar as appropriate--in theory.
        //
        ajax_request("http://thepeoplescoalition.org/GVP/find_groups/", function(request)
            {load_coalition_callback(request, calendar_groups,
                coalition_groups);},
            parameters);
        }
    else
        {default_calendar();}}

// Pass in the request object from the AJAX call to our find groups script to
// get the groups of the user's coalition as well as the array of group
// identifier numbers that should be displayed on the user's coalition
// calendar. Also pass in an array of group ID numbers that the cookie says
// are in the coalition. This function populates the group finder listing with
// those groups, if found, and moves them to the coalition group listing. The
// UI to display each group's calendar on the user's coalition calendar is
// checked, if appropriate, and the update coalition calendar function kicked
// off too.
//
function load_coalition_callback(request, calendar_groups, coalition_groups)
    {

    // Create, not to be too optimistic, a string to start any code error
    // messages. If the request did not go through for some reason, complain
    // about it and eject.
    //
    var code_error = "Code error: Load coalition callback: ";
    if ( ! (request && request.statusText == "OK" && request.responseText))
        {
        die("Coalition failed to load: Try Reloading: " + request.statusText);
        }

    // Find the node that contains the finder listing, and put the AJAX-found
    // HTML code inside of it, with any associated JAVA code extracted and
    // executed. Finally, create an icon to represent the groups on the map.
    //
    var finder_listing_node = document.getElementById(finder_listing_node_id);
    if ( ! finder_listing_node)
        {die(code_error + "Failed to find the finder listing node");}

    // Put the AJAX-found HTML code in the finder listing, with any associated
    // JAVA code extracted and executed.
    //
    var html_listing = extract_and_eval_java(request.responseText);
    finder_listing_node.innerHTML = html_listing;

    // Move all the groups we just put in the finder listing into the user's
    // coalition listing.
    //
    var group_id;
    for (group_id in finder_group_data)
        {move_to_coalition(group_id, true);}

    // If at least one group was found, since the groups were searched for
    // based on their exact group ID numbers, delete the cookie for any groups
    // that the cookie says should have been found but were not.
    //
    if (group_id)
        {

        // Search through all the coalition groups specified by the cookie,
        // deleting the cookie for any group that was not found.
        //
        for (var group_index in coalition_groups)
            {
            
            // Assemble the group ID from the group ID number. If the current
            // group was not found, delete its cookie.
            //
            var group_id = "group_" + coalition_groups[group_index];
            if ( ! coalition_group_data[group_id])
                {
                update_group_coalition_cookie(group_id, undefined, undefined,
                    true);
                }
            }
        }

    // For all the groups that are to be displayed in the user's coalition
    // calendar, check to see if those groups were actually found and loaded
    // into the user's coalition. For each group that was successfully loaded,
    // update the user's coalition calendar to add the group's calendar.
    //
    var coalition_calendar_loaded;
    for (var group_index in calendar_groups)
        {

        // Assemble the group ID from the cookie value group ID number, and
        // load the group's calendar if it is in the coalition.
        //
        group_id = "group_" + calendar_groups[group_index];
        if (coalition_group_data[group_id])
            {

            // Load the group's calendar onto the user's coalition calendar,
            // and mark that we have loaded the user's coalition calendar with
            // something.
            //
            update_group_display_on_calendar(group_id, undefined, undefined,
                true);
            coalition_calendar_loaded = true;
            }
        }

    // If groups were loaded to the user's coalition calendar, render the
    // calendar. Otherwise, just default the user's coalition calendar and
    // clear the goose egg from the finder area.
    //
    if (coalition_calendar_loaded)
        {render_coalition_calendar();}
    else
        {
        finder_listing_node.innerHTML = "";
        default_calendar();
        }
    }

// This function gets the coalition calendar node and sets the source to
// render our default calendar.
//
function default_calendar()
    {
    var calendar_node = document.getElementById(calendar_node_id);
    if (calendar_node)
        {calendar_node.src = calendar_source_default;}
    }

// Pass in the node of the checkbox, and the corresponding group's calendar
// will be displayed on the user's coalition calendar depending on whether the
// checkbox is checked or not.
//
function group_calendar_display_change(checkbox_node)
    {

    // Define a string to start any code error messages, and eject with an
    // error if we were not passed in a checkbox node.
    //
    var code_error = "Code error: Group calendar display change: ";
    if ( ! checkbox_node)
        {die(code_error + "No checkbox node passed in");}

    // Obtain the group ID from the name of the checkbox node, which should be
    // something like <group ID>-display. If we can't, eject with an error.
    //
    var group_id = String(checkbox_node.name.match(/^group_[0-9]+/));
    if ( ! group_id)
        {die(code_error + "Group ID not determined from checkbox name");}

    // Update the user's coalition calendar to include or not the group's
    // calendar, depending on whether the dispay checkbox is checked or not.
    //
    var display = checkbox_node.checked;
    update_group_display_on_calendar(group_id, display);
    }

// This function updates the coalition calendar regarding the display of the
// group with the passed-in group ID. The group's checkboxes are synchronized,
// the coalition calendar data is updated. Unless this function is called in
// the loading mode, the user's coalition cookie, the user's coalition
// calendar, and group calendar event color is also updated as appropriate.
// Only the first argument needs to be defined, neglecting the other arguments
// will make no change regarding them.
//
function update_group_display_on_calendar(group_id, display, color, loading)
    {

    // Define a string to start any code error messages, and eject with an
    // error if we have no group ID at this point.
    //
    var code_error = "Code error: Update coalition calendar: ";
    if ( ! group_id)
        {die(code_error + "No group ID passed in");}

    // If we could not find at least one checkbox, we have a problem, but not
    // a critical one.
    //
    var checkboxes = document.getElementsByName(group_id + "-display");
    if ( ! checkboxes.length)
        {
        alert(code_error + "No display checkboxes found for group: "
            + group_id);
        }

    // When loading the coalition, the only time this function is called is
    // when the group's calendar is on the user's coalition calendar, so make
    // sure the display is turned on. Also, create a variable to indicate
    // whether or not the calendar should be updated via this function.
    //
    if (loading)
        {display = true;}
    var update_calendar;

    // If the display is undefined, make no change and just get the display
    // value of the group's calendar from the first display checkbox.
    // Otherwise, the display is being updated, so set the checkboxes to
    // reflect this.
    //
    if (display == undefined)
        {

        // Find the display state from the first display checkbox. Indicate to
        // update the calendar only if the display of the group's calendar was
        // already on the user's coaltion calendar and we are setting the
        // color.
        //
        display = checkboxes[0].checked;
        if (display && color != undefined)
            {update_calendar = true;}
        }
    else
        {

        // Since the display is being set, we should update the calendar,
        // unless we are just loading the coalition calendar.
        //
        if ( ! loading)
            {update_calendar = true;}

        // As there may be two of them, make sure display checkboxes for the
        // group are synchronized.
        //
        for (var index = 0; index < checkboxes.length; index++)
            {checkboxes[index].checked = display;}
        }

    // If the group's calendar is to be displayed in the user's coalition
    // calendar, add the group's calendar source and time zone information to
    // the coalition calendar data. Otherwise, delete any coalition calendar
    // data for the group.
    //
    if (display)
        {
        var matches = calendar_source_matches(group_id);
        coalition_calendar_data[group_id]
            = {source: matches[2], zone: matches[3]};
        }
    else
        {delete coalition_calendar_data[group_id];}

    // If we are not just loading in the coalition, we need to handle updating
    // the coalition cookie, rendering the coalition calendar, and setting the
    // color legend--as appropriate.
    //
    if ( ! loading)
        {

        // If the group is in the user's coalition, update the cookie for it.
        //
        if (coalition_group_data[group_id])
            {update_group_coalition_cookie(group_id, display, color);}

        // If we need to update the calendar, render it. Also, turn off the
        // color legend for the group if it's calendar is not displayed.
        //
        if (update_calendar)
            {
            render_coalition_calendar();
            if ( ! display)
                {set_group_event_color_legend(group_id, "");}
            }
        }
    }

// This function updates the user's coaltion calendar based on the groups
// listed in the coalition calendar data. This update uses the first group's
// time zone as well as each group's calendar event color.
//
function render_coalition_calendar()
    {

    // Make an message for our code errors, and get the coalition calendar
    // node, or eject with an error.
    //
    var code_error = "Code error: Render coalition calendar: ";
    var calendar_node = document.getElementById(calendar_node_id);
    if ( ! calendar_node)
        {die(code_error + "Cannot find coalition calendar");}

    // If we have not yet initialized the user's coalition calendar variables
    // we need to use, do so now.
    //
    if ( ! coalition_calendar_source_default)
        {

        // Get the source URL from the default. Then, break the source URL
        // into pieces: the start part, the middle part that contains the
        // group calendar specifications that we ignore here, and the end
        // part. If we can't, eject with an error.
        //
        var source_url = calendar_source_default;
        var components = source_url.match(calendar_source_matcher);
        if ( ! components || components.length < 4)
            {die(code_error + "Default coalition calendar URL is ill");}

        // Save the default source, and the source start and end in our global
        // variables.
        //
        coalition_calendar_source_default = source_url;
        calendar_source_start = components[1];
        calendar_source_end = components[3];
        }

    // Initialize some variables for the new source specification of the
    // iframe for the user's coalition calendar, and any time zone information
    // that we get.
    //
    var calendar_time_zone = "";
    var new_source = calendar_source_start;

    // Include in the source specification all the group calendars that are
    // supposed to be in the coalition, along with their event color. Identify
    // the calendar time zone to be the first group's time zone, because that
    // is closest to the origin in the group finder, which was used to obtain
    // the coalition groups.
    //
    for (var group_id in coalition_calendar_data)
        {

        // If we have not specified the time zone yet, use the specification
        // from the first group that we find.
        //
        if ( ! calendar_time_zone)
            {calendar_time_zone = coalition_calendar_data[group_id]["zone"];}

        // Get the group calendar source specification, and add it to our new
        // source URL string along with the group's calendar event color.
        // Update the calendar event color legend for the group as well.
        //
        new_source += coalition_calendar_data[group_id]["source"];
        var color = get_calendar_group_event_color(group_id);
        new_source += cal_color_start + color + "&";
        set_group_event_color_legend(group_id, color);
        }

    // Finally, to actually render the calendar, just update the calendar node
    // source to our new value, and include the time zone specification, which
    // we may have to default to the general value if we don't have one.
    //
    if ( ! calendar_time_zone)
        {calendar_time_zone = calendar_source_end;}
    calendar_node.src = new_source + calendar_time_zone;
    }

// This function just records the group ID of the group who's color picker was
// brought up for, and brings up (or down) the color picker itself at the
// mouse event position by using the infobox stick function.
//
function stick_coalition_color_picker(group_id, mouse_event)
    {
    color_picker_group_id = group_id;
    stick_ib('color_ib', mouse_event);
    }

// Pass in a group ID, and this function returns a hexadecimal color for it
// (eg: "4A79GB"). This color is the user-specified color preference for the
// group's events, the default event color for the group, or an allowable
// Google calendar event color chosen by random, in that order of precedence.
//
function get_calendar_group_event_color(group_id)
    {

    // If we have a color that was specified by the user, use that color,
    // whether it is in the coalition data or the finder data for the
    // passed-in group.
    //
    var color;
    if (coalition_group_data[group_id])
        {color = coalition_group_data[group_id]["event_color_preference"];}
    if ( ! color && finder_group_data[group_id])
        {color = finder_group_data[group_id]["event_color_preference"];}

    // If we did not have a preferred color, use the default color for the
    // group, which may be identified through the finder or the coalition
    // data.
    //
    if ( ! color && coalition_group_data[group_id])
        {color = coalition_group_data[group_id]["calendar_event_color"];}
    if ( ! color && finder_group_data[group_id])
        {color = finder_group_data[group_id]["calendar_event_color"];}

    // If we do not have a color for the group at this point, just get a
    // random one. However, this should no longer happen, because all groups
    // should be forced to pick a default color for their calendar event
    // color.
    //
    if ( ! color)
        {color = taste_the_rainbow();}

    // Return the color that we found for the group.
    //
    return color;
    }

// Pass in a hexadecimal color, and this function updates the events color on
// the user's coalition calendar for the group that last brought up the color
// picker.
//
function set_calendar_group_event_color(color)
    {

    // Get the group that brought up the color picker, and make a variable for
    // our code errors.
    //
    var group_id = color_picker_group_id;
    var code_error = "Code error: Set calendar group event color: ";

    // Make sure that we have a group ID, or somehow the color picker is being
    // the color picker being brought (stuck) up.
    //
    if ( ! group_id)
        {die(code_error + "No color-pickin' group ID!");}

    // If there is no coalition or finder data for the group, we have nothing
    // left to do: the group is not displayed on the page at all.
    //
    if ( ! (coalition_group_data[group_id] || finder_group_data[group_id]))
        {return;}

    // Set the user's color preference to be the passed in color for the group
    // calendar events. If the color is "", it indicates to reset the color
    // back to the group default, if there is one. The group's information may
    // be from the coalition, the finder, or both.
    //
    if (coalition_group_data[group_id])
        {coalition_group_data[group_id]["event_color_preference"] = color;}
    if (finder_group_data[group_id])
        {finder_group_data[group_id]["event_color_preference"] = color;}

    // Update the group display on the coalition calendar to include, or
    // uninclude, the color specification.
    //
    update_group_display_on_calendar(group_id, undefined, color);
    }

// Pass in a hexadecimal color, and this function sets the background color of
// the display checkbox's label to that color: as a legend. If the checkbox
// indicates the display of the group's calendar is not on the user's
// coalition calendar, however, the legend is removed.
//
function set_group_event_color_legend(group_id, color)
    {

    // Define a message for our code errors, and make sure that we have a
    // group ID, or eject with an error.
    //
    var code_error = "Code error: Set group event color legend: ";
    if ( ! group_id)
        {die(code_error + "No group ID");}

    // If there are no display checkboxes for the group, it means that this
    // group is probably not listed in either the coalition or the finder
    // listings anymore, so just eject.
    //
    var checkboxes = document.getElementsByName(group_id + "-display");
    if ( ! checkboxes.length)
        {return;}

    // If the group calendar is not displayed on the user's coalition
    // calendar, then don't show a legend for the group's calendar events.
    //
    if ( ! checkboxes[0].checked)
        {color = "";}

    // For all the checkboxes we found, change the background color of its
    // associated label to the passed in color. However, if a color was not
    // specified, set the label color back to the default color.
    //
    for (var index = 0; index < checkboxes.length; index++)
        {

        // Get the label node of the checkbox, which should be the ckeckbox's
        // parent, or eject with an error.
        //
        var label_node = element_relative(checkboxes[index], "parentNode");
        if ( ! label_node)
            {die(code_error + "No checkbox label found: " + group_id);}

        // If the color was specified, set the background of the label to it,
        // and the text color to white. Otherwise, set the background to white
        // and the text color to black (like normal).
        //
        if (color)
            {
            label_node.style.color = "#FFFFFF";
            label_node.style.backgroundColor = "#" + color;
            }
        else
            {
            label_node.style.color = "#000000";
            label_node.style.backgroundColor = "#FFFFFF";
            }
        }
    }

// This function takes a group ID, and returns the group's calendar URL
// component matches based on the calendar source matcher pattern.
//
function calendar_source_matches(group_id)
    {

    // Define a code error message for this function, and get the entry form
    // node.
    //
    var code_error = "Code error: Group calendar source: ";
    if ( ! group_id)
        {die(code_error + "No group ID passed in");}

    // Regardless of whether the group is in the user's coalition or just
    // listed in the finder, get the group's calendar URL.
    //
    var calendar_url;
    if (coalition_group_data[group_id])
        {calendar_url = coalition_group_data[group_id][cal_db_field_name];}
    else if (finder_group_data[group_id])
        {calendar_url = finder_group_data[group_id][cal_db_field_name];}

    // Make sure we have the calendar URL for the group, or eject.
    //
    if ( ! calendar_url)
        {die(code_error + "No calendar URL found for group: " + group_id);}

    // Make sure we can find the calendar URL source for the group, or eject.
    //
    var matches = calendar_url.match(calendar_source_matcher);
    if ( ! matches || matches.length < 3)
        {
        die(code_error + "No calendar URL source identified for group: " +
            group_id);
        }

    // Return the group's calendar pattern matches that we found.
    //
    return matches;
    }

// Pass in the group register form node, and this function gets around EE's
// security issues it has with GET form submission, via the URL. Some text
// fields are temporarily modified to get around these issues. The form is
// submitted, and all modified fields returned to their original condition.
//
function enter_registry(form_node)
    {

    // The user must be logged in to enter the registry.
    //
    if ( ! logged_in())
        {return;}

    // Make an array to store the nodes and their values for any values we
    // change in order to submit the form.
    //
    var index;
    var original_values = new Array();

    // Temporarily clear unneeded text input fields of the form or encode them
    // to get around EE security issues, for when we submit the form.
    //
    for (index = 0; index < form_node.length; index++)
        {
        if (form_node.elements[index].type
            && form_node.elements[index].type.match(/^(text|hidden)$/i)
            && form_node.elements[index].value != "")
            {

            // Record the node and its current value, so we can reset it to
            // this value. Then, if the node's value is unneded, clear it.
            // Otherwise, encode it to get around EE security.
            //
            var node = form_node.elements[index];
            original_values.push(new Array(node, node.value));
            if (node.id.match(/_(key|kill)words$/)) {node.value = "";}
            else {node.value = encodeURIComponent(escape(node.value));}
            }
        }

    // Submit the form and return any fields we changed back to their original
    // values.
    //
    form_node.submit();
    for (index in original_values)
        {original_values[index][0].value = original_values[index][1];}
    }

// Pass in the input node of the key/killword, and this function makes sure
// only valid key/killword characters are in the input and saves the
// preferences for the node.
//
function keyword_change(node)
    {

    // Get a version of the node value that includes only the allowed keyword
    // characters.
    //
    var cool_value = node.value.replace(disallowed_keyword_characters, "");

    // If the node value has disallowed characters, complain about it and
    // remove them.
    //
    if (cool_value != node.value)
        {
        alert("Removing disallowed key/killword characters: see (info)");
        node.value = cool_value;
        }

    // Save the preferences for the node.
    //
    save_pref(node.id);
    }