/* 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/. * * This Source Code Form is "Incompatible With Secondary Licenses", as * defined by the Mozilla Public License, v. 2.0. */ /* This library assumes that the needed YUI libraries have been loaded already. */ var bz_no_validate_enter_bug = false; function validateEnterBug(theform) { // 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; } 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; } // 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; } } 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'); } /* 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) { var cal = new YAHOO.widget.Calendar('calendar_' + name, 'con_calendar_' + name, { START_WEEKDAY: start_weekday, MONTHS_LONG: months_long, WEEKDAYS_SHORT: weekdays_short }); 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. var timeRe = /\b(\d{1,2}):(\d\d)(?::(\d\d))?/; 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); } dateStr = dateStr + ' ' + d.getHours() + ':' + minutes; 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(); } } function setupEditLink(id) { var link_container = 'container_showhide_' + id; var input_container = 'container_' + id; var link = 'showhide_' + id; hideEditableField(link_container, input_container, link); } /* Hide input/select fields and show the text with (edit) next to it */ function hideEditableField( container, input, action, field_id, original_value, new_value, hide_input ) { YAHOO.util.Dom.removeClass(container, 'bz_default_hidden'); YAHOO.util.Dom.addClass(input, 'bz_default_hidden'); YAHOO.util.Event.addListener(action, 'click', showEditableField, new Array(container, input, field_id, new_value)); if(field_id != ""){ YAHOO.util.Event.addListener(window, 'load', checkForChangedFieldValues, new Array(container, input, field_id, original_value, hide_input )); } } /* showEditableField (e, ContainerInputArray) * Function hides the (edit) link and the text and displays the input/select field * * var e: the event * var ContainerInputArray: An array containing the (edit) and text area and the input being displayed * var ContainerInputArray[0]: the container that will be hidden usually shows the (edit) or (take) text * var ContainerInputArray[1]: the input area and label that will be displayed * 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 */ function showEditableField (e, ContainerInputArray) { var inputs = new Array(); var inputArea = YAHOO.util.Dom.get(ContainerInputArray[1]); if ( ! inputArea ){ YAHOO.util.Event.preventDefault(e); return; } YAHOO.util.Dom.addClass(ContainerInputArray[0], 'bz_default_hidden'); YAHOO.util.Dom.removeClass(inputArea, 'bz_default_hidden'); if ( inputArea.tagName.toLowerCase() == "input" ) { inputs.push(inputArea); } else if (ContainerInputArray[2]) { inputs.push(document.getElementById(ContainerInputArray[2])); } else { inputs = inputArea.getElementsByTagName('input'); } if ( inputs.length > 0 ) { // Change the first field's value to ContainerInputArray[2] // if present before focusing. 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; } } } } // focus on the first field, this makes it easier to edit inputs[0].focus(); if ( type == "input" || type == "textarea" ) { inputs[0].select(); } } 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]); var unhide = false; if ( el ) { if ( !ContainerInputArray[4] && (el.value != ContainerInputArray[3] || (el.value == "" && el.id != "qa_contact")) ) { unhide = true; } else { var set_default = document.getElementById("set_default_" + ContainerInputArray[2]); if ( set_default ) { if(set_default.checked){ unhide = true; } } } } if(unhide){ YAHOO.util.Dom.addClass(ContainerInputArray[0], 'bz_default_hidden'); YAHOO.util.Dom.removeClass(ContainerInputArray[1], 'bz_default_hidden'); } } function showPeopleOnChange( field_id_list ) { for(var i = 0; i < field_id_list.length; i++) { YAHOO.util.Event.addListener( field_id_list[i],'change', showEditableField, new Array('bz_qa_contact_edit_container', 'bz_qa_contact_input')); YAHOO.util.Event.addListener( field_id_list[i],'change',showEditableField, new Array('bz_assignee_edit_container', 'bz_assignee_input')); } } 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]); } } function initDefaultCheckbox(field_id){ YAHOO.util.Event.addListener( 'set_default_' + field_id,'change', boldOnChange, 'set_default_' + field_id); YAHOO.util.Event.addListener( window,'load', checkForChangedFieldValues, new Array( 'bz_' + field_id + '_edit_container', 'bz_' + field_id + '_input', 'set_default_' + field_id ,'1')); YAHOO.util.Event.addListener( window, 'load', boldOnChange, 'set_default_' + field_id ); } function showHideStatusItems(e, dupArrayInfo) { var el = document.getElementById('bug_status'); // finish doing stuff based on the selection. if ( el ) { showDuplicateItem(el); // 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 != '') { resolution.bz_lastSelected = resolution.selectedIndex; var emptyOption = new Option('', ''); resolution.insertBefore(emptyOption, resolution.options[0]); emptyOption.selected = true; } YAHOO.util.Dom.addClass('resolution_settings', 'bz_default_hidden'); if (document.getElementById('resolution_settings_warning')) { YAHOO.util.Dom.addClass('resolution_settings_warning', 'bz_default_hidden'); } YAHOO.util.Dom.addClass('duplicate_display', 'bz_default_hidden'); if ( (el.value == dupArrayInfo[1] && dupArrayInfo[0] == "is_duplicate") || bz_isValueInArray(close_status_array, el.value) ) { YAHOO.util.Dom.removeClass('resolution_settings', 'bz_default_hidden'); YAHOO.util.Dom.removeClass('resolution_settings_warning', 'bz_default_hidden'); // Remove the blank option we inserted. if (resolution && resolution.options[0].value == '') { resolution.removeChild(resolution.options[0]); resolution.selectedIndex = resolution.bz_lastSelected; } } if (resolution) { bz_fireEvent(resolution, 'change'); } } } function showDuplicateItem(e) { var resolution = document.getElementById('resolution'); var bug_status = document.getElementById('bug_status'); var dup_id = document.getElementById('dup_id'); if (resolution) { if (resolution.value == 'DUPLICATE' && bz_isValueInArray( close_status_array, bug_status.value) ) { // hide resolution show duplicate YAHOO.util.Dom.removeClass('duplicate_settings', 'bz_default_hidden'); YAHOO.util.Dom.addClass('dup_id_discoverable', 'bz_default_hidden'); // 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(); } } else { YAHOO.util.Dom.addClass('duplicate_settings', 'bz_default_hidden'); YAHOO.util.Dom.removeClass('dup_id_discoverable', 'bz_default_hidden'); dup_id.blur(); } } 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'); YAHOO.util.Dom.addClass('dup_id_discoverable', 'bz_default_hidden'); status.value = duplicate_or_move_bug_status; bz_fireEvent(status, 'change'); resolution.value = "DUPLICATE"; bz_fireEvent(resolution, 'change'); YAHOO.util.Event.preventDefault(e); } function setDefaultCheckbox(e, field_id) { 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'); } } 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'); } } } function updateCommentTagControl(checkbox, field) { if (checkbox.checked) { YAHOO.util.Dom.addClass(field, 'bz_private'); } else { YAHOO.util.Dom.removeClass(field, 'bz_private'); } } /** * 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'); } /** * 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. */ function showFieldWhen(controlled_id, controller_id, values) { 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. YAHOO.util.Event.addListener(controller, 'change', handleVisControllerValueChange, [controlled_id, controller, values]); } /** * Called by showFieldWhen when a field's visibility controller * changes values. */ function handleVisControllerValueChange(e, args) { var controlled_id = args[0]; var controller = args[1]; var values = args[2]; var label_container = document.getElementById('field_label_' + controlled_id); var field_container = document.getElementById('field_container_' + controlled_id); var selected = false; for (var i = 0; i < values.length; i++) { if (bz_valueSelected(controller, values[i])) { selected = true; break; } } if (selected) { 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'); } } /** * 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) { 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; } } function handleValControllerChange(e, args) { 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); } } // 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; } 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; } YAHOO.util.Dom.addClass(option, 'bz_hidden_option'); option.selected = false; option.disabled = true; hideOptionInIE(option, field); } // A convenience function to generate the "id" tag of an