field.js 37.7 KB
Newer Older
1 2 3
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
 *
5 6
 * This Source Code Form is "Incompatible With Secondary Licenses", as
 * defined by the Mozilla Public License, v. 2.0.
7 8 9 10 11
 */

/* This library assumes that the needed YUI libraries have been loaded 
   already. */

12
var bz_no_validate_enter_bug = false;
13
function validateEnterBug(theform) {
14 15 16 17 18 19 20
    // This is for the "bookmarkable templates" button.
    if (bz_no_validate_enter_bug) {
        // Set it back to false for people who hit the "back" button
        bz_no_validate_enter_bug = false;
        return true;
    }

21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
    var component = theform.component;
    var short_desc = theform.short_desc;
    var version = theform.version;
    var bug_status = theform.bug_status;
    var description = theform.comment;
    var attach_data = theform.data;
    var attach_desc = theform.description;

    var current_errors = YAHOO.util.Dom.getElementsByClassName(
        'validation_error_text', null, theform);
    for (var i = 0; i < current_errors.length; i++) {
        current_errors[i].parentNode.removeChild(current_errors[i]);
    }
    var current_error_fields = YAHOO.util.Dom.getElementsByClassName(
        'validation_error_field', null, theform);
    for (var i = 0; i < current_error_fields.length; i++) {
        var field = current_error_fields[i];
        YAHOO.util.Dom.removeClass(field, 'validation_error_field');
    }

    var focus_me;

    // These are checked in the reverse order that they appear on the page,
    // so that the one closest to the top of the form will be focused.
    if (attach_data.value && YAHOO.lang.trim(attach_desc.value) == '') {
        _errorFor(attach_desc, 'attach_desc');
        focus_me = attach_desc;
    }
49 50 51 52 53 54 55 56
    // bug_status can be undefined if the bug_status field is not editable by
    // the currently logged in user.
    if (bug_status) {
        var check_description = status_comment_required[bug_status.value];
        if (check_description && YAHOO.lang.trim(description.value) == '') {
            _errorFor(description, 'description');
            focus_me = description;
        }
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
    }
    if (YAHOO.lang.trim(short_desc.value) == '') {
        _errorFor(short_desc);
        focus_me = short_desc;
    }
    if (version.selectedIndex < 0) {
        _errorFor(version);
        focus_me = version;
    }
    if (component.selectedIndex < 0) {
        _errorFor(component);
        focus_me = component;
    }

    if (focus_me) {
        focus_me.focus();
        return false;
    }

    return true;
}

function _errorFor(field, name) {
    if (!name) name = field.id;
    var string_name = name + '_required';
    var error_text = BUGZILLA.string[string_name];
    var new_node = document.createElement('div');
    YAHOO.util.Dom.addClass(new_node, 'validation_error_text');
    new_node.innerHTML = error_text;
    YAHOO.util.Dom.insertAfter(new_node, field);
    YAHOO.util.Dom.addClass(field, 'validation_error_field');
}

90 91 92 93 94
/* This function is never to be called directly, but only indirectly
 * using template/en/default/global/calendar.js.tmpl, so that localization
 * works. For the same reason, if you modify this function's parameter list,
 * you need to modify the documentation in said template as well. */
function createCalendar(name, start_weekday, months_long, weekdays_short) {
95
    var cal = new YAHOO.widget.Calendar('calendar_' + name, 
96 97 98 99 100
                                        'con_calendar_' + name,
                                        { START_WEEKDAY:  start_weekday,
                                          MONTHS_LONG:    months_long,
                                          WEEKDAYS_SHORT: weekdays_short
                                        });
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
    YAHOO.bugzilla['calendar_' + name] = cal;
    var field = document.getElementById(name);
    cal.selectEvent.subscribe(setFieldFromCalendar, field, false);
    updateCalendarFromField(field);
    cal.render();
}

/* The onclick handlers for the button that shows the calendar. */
function showCalendar(field_name) {
    var calendar  = YAHOO.bugzilla["calendar_" + field_name];
    var field     = document.getElementById(field_name);
    var button    = document.getElementById('button_calendar_' + field_name);

    bz_overlayBelow(calendar.oDomContainer, field);
    calendar.show();
    button.onclick = function() { hideCalendar(field_name); };

    // Because of the way removeListener works, this has to be a function
    // attached directly to this calendar.
    calendar.bz_myBodyCloser = function(event) {
        var container = this.oDomContainer;
        var target    = YAHOO.util.Event.getTarget(event);
        if (target != container && target != button
            && !YAHOO.util.Dom.isAncestor(container, target))
        {
            hideCalendar(field_name);
        }
    };

    // If somebody clicks outside the calendar, hide it.
    YAHOO.util.Event.addListener(document.body, 'click', 
                                 calendar.bz_myBodyCloser, calendar, true);

    // Make Esc close the calendar.
    calendar.bz_escCal = function (event) {
        var key = YAHOO.util.Event.getCharCode(event);
        if (key == 27) {
            hideCalendar(field_name);
        }
    };
    YAHOO.util.Event.addListener(document.body, 'keydown', calendar.bz_escCal);
}

function hideCalendar(field_name) {
    var cal = YAHOO.bugzilla["calendar_" + field_name];
    cal.hide();
    var button = document.getElementById('button_calendar_' + field_name);
    button.onclick = function() { showCalendar(field_name); };
    YAHOO.util.Event.removeListener(document.body, 'click',
                                    cal.bz_myBodyCloser);
    YAHOO.util.Event.removeListener(document.body, 'keydown', cal.bz_escCal);
}

/* This is the selectEvent for our Calendar objects on our custom 
 * DateTime fields.
 */
function setFieldFromCalendar(type, args, date_field) {
    var dates = args[0];
    var setDate = dates[0];

    // We can't just write the date straight into the field, because there 
    // might already be a time there.
163
    var timeRe = /\b(\d{1,2}):(\d\d)(?::(\d\d))?/;
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
    var currentTime = timeRe.exec(date_field.value);
    var d = new Date(setDate[0], setDate[1] - 1, setDate[2]);
    if (currentTime) {
        d.setHours(currentTime[1], currentTime[2]);
        if (currentTime[3]) {
            d.setSeconds(currentTime[3]);
        }
    }

    var year = d.getFullYear();
    // JavaScript's "Date" represents January as 0 and December as 11.
    var month = d.getMonth() + 1;
    if (month < 10) month = '0' + String(month);
    var day = d.getDate();
    if (day < 10) day = '0' + String(day);
    var dateStr = year + '-' + month  + '-' + day;

    if (currentTime) {
        var minutes = d.getMinutes();
        if (minutes < 10) minutes = '0' + String(minutes);
        var seconds = d.getSeconds();
        if (seconds > 0 && seconds < 10) {
            seconds = '0' + String(seconds);
        }

189
        dateStr = dateStr + ' ' + d.getHours() + ':' + minutes;
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
        if (seconds) dateStr = dateStr + ':' + seconds;
    }

    date_field.value = dateStr;
    hideCalendar(date_field.id);
}

/* Sets the calendar based on the current field value. 
 */ 
function updateCalendarFromField(date_field) {
    var dateRe = /(\d\d\d\d)-(\d\d?)-(\d\d?)/;
    var pieces = dateRe.exec(date_field.value);
    if (pieces) {
        var cal = YAHOO.bugzilla["calendar_" + date_field.id];
        cal.select(new Date(pieces[1], pieces[2] - 1, pieces[3]));
        var selectedArray = cal.getSelectedDates();
        var selected = selectedArray[0];
        cal.cfg.setProperty("pagedate", (selected.getMonth() + 1) + '/' 
                                        + selected.getFullYear());
        cal.render();
    }
}
212

213 214 215 216 217 218
function setupEditLink(id) {
    var link_container = 'container_showhide_' + id;
    var input_container = 'container_' + id;
    var link = 'showhide_' + id;
    hideEditableField(link_container, input_container, link);
}
219

220
/* Hide input/select fields and show the text with (edit) next to it */
221
function hideEditableField( container, input, action, field_id, original_value, new_value ) {
222 223
    YAHOO.util.Dom.removeClass(container, 'bz_default_hidden');
    YAHOO.util.Dom.addClass(input, 'bz_default_hidden');
224
    YAHOO.util.Event.addListener(action, 'click', showEditableField,
225
                                 new Array(container, input, field_id, new_value));
226
    if(field_id != ""){
227 228
        YAHOO.util.Event.addListener(window, 'load', checkForChangedFieldValues,
                        new Array(container, input, field_id, original_value));
229 230 231 232
    }
}

/* showEditableField (e, ContainerInputArray)
233
 * Function hides the (edit) link and the text and displays the input/select field
234 235 236
 *
 * var e: the event
 * var ContainerInputArray: An array containing the (edit) and text area and the input being displayed
237
 * var ContainerInputArray[0]: the container that will be hidden usually shows the (edit) or (take) text
238
 * var ContainerInputArray[1]: the input area and label that will be displayed
239 240
 * var ContainerInputArray[2]: the input/select field id for which the new value must be set
 * var ContainerInputArray[3]: the new value to set the input/select field to when (take) is clicked
241 242
 */
function showEditableField (e, ContainerInputArray) {
243 244 245 246 247 248
    var inputs = new Array();
    var inputArea = YAHOO.util.Dom.get(ContainerInputArray[1]);    
    if ( ! inputArea ){
        YAHOO.util.Event.preventDefault(e);
        return;
    }
249 250
    YAHOO.util.Dom.addClass(ContainerInputArray[0], 'bz_default_hidden');
    YAHOO.util.Dom.removeClass(inputArea, 'bz_default_hidden');
251 252
    if ( inputArea.tagName.toLowerCase() == "input" ) {
        inputs.push(inputArea);
253 254
    } else if (ContainerInputArray[2]) {
        inputs.push(document.getElementById(ContainerInputArray[2]));
255 256 257 258
    } else {
        inputs = inputArea.getElementsByTagName('input');
    }
    if ( inputs.length > 0 ) {
259 260
        // Change the first field's value to ContainerInputArray[2]
        // if present before focusing.
261 262 263 264 265 266 267 268 269 270 271 272
        var type = inputs[0].tagName.toLowerCase();
        if (ContainerInputArray[3]) {
            if ( type == "input" ) {
                inputs[0].value = ContainerInputArray[3];
            } else {
                for (var i = 0; inputs[0].length; i++) {
                    if ( inputs[0].options[i].value == ContainerInputArray[3] ) {
                        inputs[0].options[i].selected = true;
                        break;
                    }
                }
            }
273
        }
274 275
        // focus on the first field, this makes it easier to edit
        inputs[0].focus();
276 277 278
        if ( type == "input" ) {
            inputs[0].select();
        }
279
    }
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
    YAHOO.util.Event.preventDefault(e);
}


/* checkForChangedFieldValues(e, array )
 * Function checks if after the autocomplete by the browser if the values match the originals.
 *   If they don't match then hide the text and show the input so users don't get confused.
 *
 * var e: the event
 * var ContainerInputArray: An array containing the (edit) and text area and the input being displayed
 * var ContainerInputArray[0]: the conainer that will be hidden usually shows the (edit) text
 * var ContainerInputArray[1]: the input area and label that will be displayed
 * var ContainerInputArray[2]: the field that is on the page, might get changed by browser autocomplete 
 * var ContainerInputArray[3]: the original value from the page loading.
 *
 */  
function checkForChangedFieldValues(e, ContainerInputArray ) {
    var el = document.getElementById(ContainerInputArray[2]);
298
    var unhide = false;
299
    if ( el ) {
300
        if ( el.value != ContainerInputArray[3] ||
301
            ( el.value == "" && el.id != "alias" && el.id != 'qa_contact') ) {
302 303 304
            unhide = true;
        }
        else {
305 306
            var set_default = document.getElementById("set_default_" +
                                                      ContainerInputArray[2]);
307 308 309 310 311 312 313 314
            if ( set_default ) {
                if(set_default.checked){
                    unhide = true;
                }              
            }
        }
    }
    if(unhide){
315 316
        YAHOO.util.Dom.addClass(ContainerInputArray[0], 'bz_default_hidden');
        YAHOO.util.Dom.removeClass(ContainerInputArray[1], 'bz_default_hidden');
317 318 319 320 321 322
    }

}

function hideAliasAndSummary(short_desc_value, alias_value) {
    // check the short desc field
323 324
    hideEditableField( 'summary_alias_container','summary_alias_input',
                       'editme_action','short_desc', short_desc_value);  
325
    // check that the alias hasn't changed
326
    var bz_alias_check_array = new Array('summary_alias_container',
327 328 329
                                     'summary_alias_input', 'alias', alias_value);
    YAHOO.util.Event.addListener( window, 'load', checkForChangedFieldValues,
                                 bz_alias_check_array);
330 331 332 333
}

function showPeopleOnChange( field_id_list ) {
    for(var i = 0; i < field_id_list.length; i++) {
334
        YAHOO.util.Event.addListener( field_id_list[i],'change', showEditableField,
335
                                      new Array('bz_qa_contact_edit_container',
336 337
                                                'bz_qa_contact_input'));
        YAHOO.util.Event.addListener( field_id_list[i],'change',showEditableField,
338
                                      new Array('bz_assignee_edit_container',
339
                                                'bz_assignee_input'));
340 341 342
    }
}

343 344 345 346 347 348 349 350 351 352 353 354 355
function assignToDefaultOnChange(field_id_list, default_assignee, default_qa_contact) {
    showPeopleOnChange(field_id_list);
    for(var i = 0, l = field_id_list.length; i < l; i++) {
        YAHOO.util.Event.addListener(field_id_list[i], 'change', function(evt, defaults) {
            if (document.getElementById('assigned_to').value == defaults[0]) {
                setDefaultCheckbox(evt, 'set_default_assignee');
            }
            if (document.getElementById('qa_contact')
                && document.getElementById('qa_contact').value == defaults[1])
            {
                setDefaultCheckbox(evt, 'set_default_qa_contact');
            }
        }, [default_assignee, default_qa_contact]);
356 357 358
    }
}

359
function initDefaultCheckbox(field_id){
360 361 362
    YAHOO.util.Event.addListener( 'set_default_' + field_id,'change', boldOnChange,
                                  'set_default_' + field_id);
    YAHOO.util.Event.addListener( window,'load', checkForChangedFieldValues,
363 364
                                  new Array( 'bz_' + field_id + '_edit_container',
                                             'bz_' + field_id + '_input',
365
                                             'set_default_' + field_id ,'1'));
366
    
367 368
    YAHOO.util.Event.addListener( window, 'load', boldOnChange,
                                 'set_default_' + field_id ); 
369 370
}

371 372 373 374 375
function showHideStatusItems(e, dupArrayInfo) {
    var el = document.getElementById('bug_status');
    // finish doing stuff based on the selection.
    if ( el ) {
        showDuplicateItem(el);
376 377 378 379 380

        // Make sure that fields whose visibility or values are controlled
        // by "resolution" behave properly when resolution is hidden.
        var resolution = document.getElementById('resolution');
        if (resolution && resolution.options[0].value != '') {
381
            resolution.bz_lastSelected = resolution.selectedIndex;
382 383 384 385
            var emptyOption = new Option('', '');
            resolution.insertBefore(emptyOption, resolution.options[0]);
            emptyOption.selected = true;
        }
386
        YAHOO.util.Dom.addClass('resolution_settings', 'bz_default_hidden');
387
        if (document.getElementById('resolution_settings_warning')) {
388 389
            YAHOO.util.Dom.addClass('resolution_settings_warning',
                                    'bz_default_hidden');
390
        }
391
        YAHOO.util.Dom.addClass('duplicate_display', 'bz_default_hidden');
392

393 394 395 396

        if ( (el.value == dupArrayInfo[1] && dupArrayInfo[0] == "is_duplicate")
             || bz_isValueInArray(close_status_array, el.value) ) 
        {
397 398 399 400
            YAHOO.util.Dom.removeClass('resolution_settings', 
                                       'bz_default_hidden');
            YAHOO.util.Dom.removeClass('resolution_settings_warning', 
                                       'bz_default_hidden');
401 402 403 404

            // Remove the blank option we inserted.
            if (resolution && resolution.options[0].value == '') {
                resolution.removeChild(resolution.options[0]);
405
                resolution.selectedIndex = resolution.bz_lastSelected;
406 407 408 409 410
            }
        }

        if (resolution) {
            bz_fireEvent(resolution, 'change');
411 412 413 414 415 416 417
        }
    }
}

function showDuplicateItem(e) {
    var resolution = document.getElementById('resolution');
    var bug_status = document.getElementById('bug_status');
418
    var dup_id = document.getElementById('dup_id');
419
    if (resolution) {
420
        if (resolution.value == 'DUPLICATE' && bz_isValueInArray( close_status_array, bug_status.value) ) {
421
            // hide resolution show duplicate
422 423 424
            YAHOO.util.Dom.removeClass('duplicate_settings', 
                                       'bz_default_hidden');
            YAHOO.util.Dom.addClass('dup_id_discoverable', 'bz_default_hidden');
425 426 427 428 429
            // check to make sure the field is visible or IE throws errors
            if( ! YAHOO.util.Dom.hasClass( dup_id, 'bz_default_hidden' ) ){
                dup_id.focus();
                dup_id.select();
            }
430 431
        }
        else {
432 433 434
            YAHOO.util.Dom.addClass('duplicate_settings', 'bz_default_hidden');
            YAHOO.util.Dom.removeClass('dup_id_discoverable', 
                                       'bz_default_hidden');
435
            dup_id.blur();
436 437 438 439 440 441 442 443
        }
    }
    YAHOO.util.Event.preventDefault(e); //prevents the hyperlink from going to the url in the href.
}

function setResolutionToDuplicate(e, duplicate_or_move_bug_status) {
    var status = document.getElementById('bug_status');
    var resolution = document.getElementById('resolution');
444
    YAHOO.util.Dom.addClass('dup_id_discoverable', 'bz_default_hidden');
445
    status.value = duplicate_or_move_bug_status;
446
    bz_fireEvent(status, 'change');
447
    resolution.value = "DUPLICATE";
448
    bz_fireEvent(resolution, 'change');
449 450 451
    YAHOO.util.Event.preventDefault(e);
}

452
function setDefaultCheckbox(e, field_id) {
453 454 455 456 457
    var el = document.getElementById(field_id);
    var elLabel = document.getElementById(field_id + "_label");
    if( el && elLabel ) {
        el.checked = "true";
        YAHOO.util.Dom.setStyle(elLabel, 'font-weight', 'bold');
458 459 460
    }
}

461 462 463 464 465 466 467 468 469 470 471
function boldOnChange(e, field_id){
    var el = document.getElementById(field_id);
    var elLabel = document.getElementById(field_id + "_label");
    if( el && elLabel ) {
        if( el.checked ){
            YAHOO.util.Dom.setStyle(elLabel, 'font-weight', 'bold');
        }
        else{
            YAHOO.util.Dom.setStyle(elLabel, 'font-weight', 'normal');
        }
    }
472
}
473

474
function updateCommentTagControl(checkbox, field) {
475
    if (checkbox.checked) {
476
        YAHOO.util.Dom.addClass(field, 'bz_private');
477
    } else {
478
        YAHOO.util.Dom.removeClass(field, 'bz_private');
479 480 481
    }
}

482 483 484 485 486 487 488 489 490 491 492 493 494 495
/**
 * Reset the value of the classification field and fire an event change
 * on it.  Called when the product changes, in case the classification
 * field (which is hidden) controls the visibility of any other fields.
 */
function setClassification() {
    var classification = document.getElementById('classification');
    var product = document.getElementById('product');
    var selected_product = product.value; 
    var select_classification = all_classifications[selected_product];
    classification.value = select_classification;
    bz_fireEvent(classification, 'change');
}

496 497 498 499 500
/**
 * Says that a field should only be displayed when another field has
 * a certain value. May only be called after the controller has already
 * been added to the DOM.
 */
501
function showFieldWhen(controlled_id, controller_id, values) {
502 503 504
    var controller = document.getElementById(controller_id);
    // Note that we don't get an object for "controlled" here, because it
    // might not yet exist in the DOM. We just pass along its id.
505 506
    YAHOO.util.Event.addListener(controller, 'change',
        handleVisControllerValueChange, [controlled_id, controller, values]);
507 508 509 510 511 512 513 514 515
}

/**
 * Called by showFieldWhen when a field's visibility controller 
 * changes values. 
 */
function handleVisControllerValueChange(e, args) {
    var controlled_id = args[0];
    var controller = args[1];
516
    var values = args[2];
517 518 519 520 521

    var label_container = 
        document.getElementById('field_label_' + controlled_id);
    var field_container =
        document.getElementById('field_container_' + controlled_id);
522 523 524 525 526 527 528 529 530
    var selected = false;
    for (var i = 0; i < values.length; i++) {
        if (bz_valueSelected(controller, values[i])) {
            selected = true;
            break;
        }
    }

    if (selected) {
531 532 533 534 535 536 537 538
        YAHOO.util.Dom.removeClass(label_container, 'bz_hidden_field');
        YAHOO.util.Dom.removeClass(field_container, 'bz_hidden_field');
    }
    else {
        YAHOO.util.Dom.addClass(label_container, 'bz_hidden_field');
        YAHOO.util.Dom.addClass(field_container, 'bz_hidden_field');
    }
}
539

540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
/**
 * This is a data structure representing the tree of controlled values.
 * Let's call the "controller value" the "source" and the "controlled
 * value" the "target". A target can have only one source, but a source
 * can have an infinite number of targets.
 *
 * The data structure is a series of hash tables that go something
 * like this:
 *
 * source_field -> target_field -> source_value_id -> target_value_ids
 *
 * We always know source_field when our event handler is called, since
 * that's the field the event is being triggered on. We can then enumerate
 * through every target field, check the status of each source field value,
 * and act appropriately on each target value.
 */
var bz_value_controllers = {};
// This keeps track of whether or not we've added an onchange handler
// for the source field yet.
var bz_value_controller_has_handler = {};
function showValueWhen(target_field_id, target_value_ids,
                       source_field_id, source_value_id, empty_shows_all)
562
{
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
    if (!bz_value_controllers[source_field_id]) {
        bz_value_controllers[source_field_id] = {};
    }
    if (!bz_value_controllers[source_field_id][target_field_id]) {
        bz_value_controllers[source_field_id][target_field_id] = {};
    }
    var source_values = bz_value_controllers[source_field_id][target_field_id];
    source_values[source_value_id] = target_value_ids;

    if (!bz_value_controller_has_handler[source_field_id]) {
        var source_field = document.getElementById(source_field_id);
        YAHOO.util.Event.addListener(source_field, 'change',
            handleValControllerChange, [source_field, empty_shows_all]);
        bz_value_controller_has_handler[source_field_id] = true;
    }
578 579 580
}

function handleValControllerChange(e, args) {
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
    var source = args[0];
    var empty_shows_all = args[1];

    for (var target_field_id in bz_value_controllers[source.id]) {
        var target = document.getElementById(target_field_id);
        if (!target) continue;
        _update_displayed_values(source, target, empty_shows_all);
    }
}

/* See the docs for bz_option_duplicate count lower down for an explanation
 * of this data structure.
 */
var bz_option_hide_count = {};

function _update_displayed_values(source, target, empty_shows_all) {
    var show_all = (empty_shows_all && source.selectedIndex == -1);

    bz_option_hide_count[target.id] = {};

    var source_values = bz_value_controllers[source.id][target.id];
    for (source_value_id in source_values) {
        var source_option = getPossiblyHiddenOption(source, source_value_id);
        var target_values = source_values[source_value_id];
        for (var i = 0; i < target_values.length; i++) {
            var target_value_id = target_values[i];
            _handle_source_target(source_option, target, target_value_id,
                                  show_all);
609
        }
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
    }

    // We may have updated which elements are selected or not selected
    // in the target field, and it may have handlers associated with
    // that, so we need to fire the change event on the target.
    bz_fireEvent(target, 'change');
}

function _handle_source_target(source_option, target, target_value_id,
                               show_all)
{
    var target_option = getPossiblyHiddenOption(target, target_value_id);

    // We always call either _show_option or _hide_option on every single
    // target value. Although this is not theoretically the most efficient
    // thing we can do, it handles all possible edge cases, and there are
    // a lot of those, particularly when this code is being used on the
    // search form.
    if (source_option.selected || (show_all && !source_option.disabled)) {
        _show_option(target_option, target);
    }
    else {
        _hide_option(target_option, target);
    }
}

/* When an option has duplicates (see the docs for bz_option_duplicates
 * lower down in this file), we only want to hide it if *all* the duplicates
 * would be hidden. So we keep a counter of how many duplicates each option
 * has. Then, when we run through a "change" call for a source field,
 * we count how many times each value gets hidden, and only actually
 * hide it if the counter hits a number higher than the duplicate count.
 */
var bz_option_duplicate_count = {};

function _show_option(option, field) {
    if (!option.disabled) return;
    option = showOptionInIE(option, field);
    YAHOO.util.Dom.removeClass(option, 'bz_hidden_option');
    option.disabled = false;
}

function _hide_option(option, field) {
    if (option.disabled) return;

    var value_id = option.bz_value_id;

    if (field.id in bz_option_duplicate_count
        && value_id in bz_option_duplicate_count[field.id])
    {
        if (!bz_option_hide_count[field.id][value_id]) {
            bz_option_hide_count[field.id][value_id] = 0;
662
        }
663 664 665 666 667 668 669 670
        bz_option_hide_count[field.id][value_id]++;
        var current = bz_option_hide_count[field.id][value_id];
        var dups    = bz_option_duplicate_count[field.id][value_id];
        // We check <= because the value in bz_option_duplicate_count is
        // 1 less than the total number of duplicates (since the shown
        // option is also a "duplicate" but not counted in
        // bz_option_duplicate_count).
        if (current <= dups) return;
671
    }
672 673 674 675 676

    YAHOO.util.Dom.addClass(option, 'bz_hidden_option');
    option.selected = false;
    option.disabled = true;
    hideOptionInIE(option, field);
677 678
}

679 680 681 682 683 684
// A convenience function to generate the "id" tag of an <option>
// based on the numeric id that Bugzilla uses for that value.
function _value_id(field_name, id) {
    return 'v' + id + '_' + field_name;
}

685 686 687 688 689 690 691 692
/*********************************/
/* Code for Hiding Options in IE */
/*********************************/

/* IE 7 and below (and some other browsers) don't respond to "display: none"
 * on <option> tags. However, you *can* insert a Comment Node as a
 * child of a <select> tag. So we just insert a Comment where the <option>
 * used to be. */
693
var ie_hidden_options = {};
694 695 696 697
function hideOptionInIE(anOption, aSelect) {
    if (browserCanHideOptions(aSelect)) return;

    var commentNode = document.createComment(anOption.value);
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712
    commentNode.id = anOption.id;
    // This keeps the interface of Comments and Options the same for
    // our other functions.
    commentNode.disabled = true;
    // replaceChild is very slow on IE in a <select> that has a lot of
    // options, so we use replaceNode when we can.
    if (anOption.replaceNode) {
        anOption.replaceNode(commentNode);
    }
    else {
        aSelect.replaceChild(commentNode, anOption);
    }

    // Store the comment node for quick access for getPossiblyHiddenOption
    if (!ie_hidden_options[aSelect.id]) {
713
        ie_hidden_options[aSelect.id] = {};
714 715
    }
    ie_hidden_options[aSelect.id][anOption.id] = commentNode;
716 717 718
}

function showOptionInIE(aNode, aSelect) {
719
    if (browserCanHideOptions(aSelect)) return aNode;
720 721 722 723 724 725

    // We do this crazy thing with innerHTML and createElement because
    // this is the ONLY WAY that this works properly in IE.
    var optionNode = document.createElement('option');
    optionNode.innerHTML = aNode.data;
    optionNode.value = aNode.data;
726 727 728 729 730 731 732 733 734 735 736
    optionNode.id = aNode.id;
    // replaceChild is very slow on IE in a <select> that has a lot of
    // options, so we use replaceNode when we can.
    if (aNode.replaceNode) {
        aNode.replaceNode(optionNode);
    }
    else {
        aSelect.replaceChild(optionNode, aNode);
    }
    delete ie_hidden_options[aSelect.id][optionNode.id];
    return optionNode;
737 738 739 740 741
}

function initHidingOptionsForIE(select_name) {
    var aSelect = document.getElementById(select_name);
    if (browserCanHideOptions(aSelect)) return;
742
    if (!aSelect) return;
743 744 745 746 747 748 749 750 751 752 753

    for (var i = 0; ;i++) {
        var item = aSelect.options[i];
        if (!item) break;
        if (item.disabled) {
          hideOptionInIE(item, aSelect);
          i--; // Hiding an option means that the options array has changed.
        }
    }
}

754 755 756 757 758 759 760 761 762 763 764 765 766
/* Certain fields, like the Component field, have duplicate values in
 * them (the same name, but different ids). We don't display these
 * duplicate values in the UI, but the option hiding/showing code still
 * uses the ids of these unshown duplicates. So, whenever we get the
 * id of an unshown duplicate in getPossiblyHiddenOption, we have to
 * return the actually-used <option> instead.
 *
 * The structure of the data looks like:
 *
 *  field_name -> unshown_value_id -> shown_value_id_it_is_a_duplicate_of
 */
var bz_option_duplicates = {};

767
function getPossiblyHiddenOption(aSelect, optionId) {
768 769 770 771 772 773 774

    if (bz_option_duplicates[aSelect.id]
        && bz_option_duplicates[aSelect.id][optionId])
    {
        optionId = bz_option_duplicates[aSelect.id][optionId];
    }

775 776 777 778 779 780 781 782 783
    // Works always for <option> tags, and works for commentNodes
    // in IE (but not in Webkit).
    var id = _value_id(aSelect.id, optionId);
    var val = document.getElementById(id);

    // This is for WebKit and other browsers that can't "display: none"
    // an <option> and also can't getElementById for a commentNode.
    if (!val && ie_hidden_options[aSelect.id]) {
        val = ie_hidden_options[aSelect.id][id];
784 785
    }

786 787 788 789
    // We add this property for our own convenience, it's used in
    // other places.
    val.bz_value_id = optionId;

790
    return val;
791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
}

var browser_can_hide_options;
function browserCanHideOptions(aSelect) {
    /* As far as I can tell, browsers that don't hide <option> tags
     * also never have a X position for <option> tags, even if
     * they're visible. This is the only reliable way I found to
     * differentiate browsers. So we create a visible option, see
     * if it has a position, and then remove it. */
    if (typeof(browser_can_hide_options) == "undefined") {
        var new_opt = bz_createOptionInSelect(aSelect, '', '');
        var opt_pos = YAHOO.util.Dom.getX(new_opt);
        aSelect.removeChild(new_opt);
        if (opt_pos) {
            browser_can_hide_options = true;
        }
        else {
            browser_can_hide_options = false;
        }
    }
    return browser_can_hide_options;
}

/* (end) option hiding code */
815 816 817 818 819 820 821 822 823 824 825 826 827 828 829

/**
 * The Autoselect
 */
YAHOO.bugzilla.userAutocomplete = {
    counter : 0,
    dataSource : null,
    generateRequest : function ( enteredText ){ 
      YAHOO.bugzilla.userAutocomplete.counter = 
                                   YAHOO.bugzilla.userAutocomplete.counter + 1;
      YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
      var json_object = {
          method : "User.get",
          id : YAHOO.bugzilla.userAutocomplete.counter,
          params : [ { 
830
            match : [ decodeURIComponent(enteredText) ],
831
            include_fields : [ "name", "real_name" ]
832 833 834 835 836 837 838 839 840
          } ]
      };
      var stringified =  YAHOO.lang.JSON.stringify(json_object);
      var debug = { msg: "json-rpc obj debug info", "json obj": json_object, 
                    "param" : stringified}
      YAHOO.bugzilla.userAutocomplete.debug_helper( debug );
      return stringified;
    },
    resultListFormat : function(oResultData, enteredText, sResultMatch) {
841 842
        return ( YAHOO.lang.escapeHTML(oResultData.real_name) + " ("
                 + YAHOO.lang.escapeHTML(oResultData.name) + ")");
843 844 845 846 847 848 849 850 851 852
    },
    debug_helper : function ( ){
        /* used to help debug any errors that might happen */
        if( typeof(console) !== 'undefined' && console != null && arguments.length > 0 ){
            console.log("debug helper info:", arguments);
        }
        return true;
    },    
    init_ds : function(){
        this.dataSource = new YAHOO.util.XHRDataSource("jsonrpc.cgi");
853
        this.dataSource.connTimeout = 30000;
854
        this.dataSource.connMethodPost = true;
855 856
        this.dataSource.connXhrMode = "cancelStaleRequests";
        this.dataSource.maxCacheEntries = 5;
857 858
        this.dataSource.responseSchema = {
            resultsList : "result.users",
859 860
            metaFields : { error: "error", jsonRpcId: "id"},
            fields : [
861
                { key : "name" },
862 863 864
                { key : "real_name"}
            ]
        };
865 866 867 868 869 870 871 872
    },
    init : function( field, container, multiple ) {
        if( this.dataSource == null ){
            this.init_ds();  
        }            
        var userAutoComp = new YAHOO.widget.AutoComplete( field, container, 
                                this.dataSource );
        // other stuff we might want to do with the autocomplete goes here
873
        userAutoComp.maxResultsDisplayed = BUGZILLA.param.maxusermatches;
874 875 876 877 878 879 880
        userAutoComp.generateRequest = this.generateRequest;
        userAutoComp.formatResult = this.resultListFormat;
        userAutoComp.doBeforeLoadData = this.debug_helper;
        userAutoComp.minQueryLength = 3;
        userAutoComp.autoHighlight = false;
        // this is a throttle to determine the delay of the query from typing
        // set this higher to cause fewer calls to the server
881 882
        userAutoComp.queryDelay = 0.05;
        userAutoComp.useIFrame = true;
883 884
        userAutoComp.resultTypeList = false;
        if( multiple == true ){
885
            userAutoComp.delimChar = [","];
886 887 888 889 890
        }
        
    }
};

891 892 893 894 895
YAHOO.bugzilla.fieldAutocomplete = {
    dataSource : [],
    init_ds : function( field ) {
        this.dataSource[field] =
          new YAHOO.util.LocalDataSource( YAHOO.bugzilla.field_array[field] );
896 897
    },
    init : function( field, container ) {
898 899
        if( this.dataSource[field] == null ) {
            this.init_ds( field );
900
        }
901 902 903
        var fieldAutoComp =
          new YAHOO.widget.AutoComplete(field, container, this.dataSource[field]);
        fieldAutoComp.maxResultsDisplayed = YAHOO.bugzilla.field_array[field].length;
904
        fieldAutoComp.formatResult = fieldAutoComp.formatEscapedResult;
905 906 907 908 909 910
        fieldAutoComp.minQueryLength = 0;
        fieldAutoComp.useIFrame = true;
        fieldAutoComp.delimChar = [","," "];
        fieldAutoComp.resultTypeList = false;
        fieldAutoComp.queryDelay = 0;
        /*  Causes all the possibilities in the field to appear when a user
911 912
         *  focuses on the textbox 
         */
913 914
        fieldAutoComp.textboxFocusEvent.subscribe( function(){
            var sInputValue = YAHOO.util.Dom.get(field).value;
915 916
            if( sInputValue.length === 0
                && YAHOO.bugzilla.field_array[field].length > 0 ){
917 918 919 920 921
                this.sendQuery(sInputValue);
                this.collapseContainer();
                this.expandContainer();
            }
        });
922 923 924
        fieldAutoComp.dataRequestEvent.subscribe( function(type, args) {
            args[0].autoHighlight = args[1] != '';
        });
925 926
    }
};
927 928 929 930 931 932 933 934 935 936 937 938 939

/**
 * Set the disable email checkbox to true if the user has disabled text
 */
function userDisabledTextOnChange(disabledtext) {
    var disable_mail = document.getElementById('disable_mail');
    if (disabledtext.value === "" && !disable_mail_manually_set) {
        disable_mail.checked = false;
    }
    if (disabledtext.value !== "" && !disable_mail_manually_set) {
        disable_mail.checked = true;
    }
}
940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987

/**
 * Force the browser to honour the selected option when a page is refreshed,
 * but only if the user hasn't explicitly selected a different option.
 */
function initDirtyFieldTracking() {
    // old IE versions don't provide the information we need to make this fix work
    // however they aren't affected by this issue, so it's ok to ignore them
    if (YAHOO.env.ua.ie > 0 && YAHOO.env.ua.ie <= 8) return;
    var selects = document.getElementById('changeform').getElementsByTagName('select');
    for (var i = 0, l = selects.length; i < l; i++) {
        var el = selects[i];
        var el_dirty = document.getElementById(el.name + '_dirty');
        if (!el_dirty) continue;
        if (!el_dirty.value) {
            var preSelected = bz_preselectedOptions(el);
            if (!el.multiple) {
                preSelected.selected = true;
            } else {
                el.selectedIndex = -1;
                for (var j = 0, m = preSelected.length; j < m; j++) {
                    preSelected[j].selected = true;
                }
            }
        }
        YAHOO.util.Event.on(el, "change", function(e) {
            var el = e.target || e.srcElement;
            var preSelected = bz_preselectedOptions(el);
            var currentSelected = bz_selectedOptions(el);
            var isDirty = false;
            if (!el.multiple) {
                isDirty = preSelected.index != currentSelected.index;
            } else {
                if (preSelected.length != currentSelected.length) {
                    isDirty = true;
                } else {
                    for (var i = 0, l = preSelected.length; i < l; i++) {
                        if (currentSelected[i].index != preSelected[i].index) {
                            isDirty = true;
                            break;
                        }
                    }
                }
            }
            document.getElementById(el.name + '_dirty').value = isDirty ? '1' : '';
        });
    }
}