| /* 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 && 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 != ""){ |
| // if WEBKIT_CHANGES |
| YAHOO.util.Event.addListener(field_id + '_nonedit_display', 'click', showEditableField, |
| new Array(container, input, undefined, undefined, field_id)); |
| // endif WEBKIT_CHANGES |
| 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 |
| * var ContainerInputArray[4]: [optional] the id of the input element to focus |
| */ |
| 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; |
| } |
| } |
| } |
| } |
| // if WEBKIT_CHANGES |
| var elementToFocus = YAHOO.util.Dom.get(ContainerInputArray[4]); |
| if (elementToFocus) { |
| // focus on the requested field |
| elementToFocus.focus(); |
| var elementToFocusType = elementToFocus.tagName.toLowerCase(); |
| if ( elementToFocusType == "input" || elementToFocusType == "textarea" ) { |
| elementToFocus.select(); |
| } |
| } else { |
| // focus on the first field, this makes it easier to edit |
| inputs[0].focus(); |
| if ( type == "input" || type == "textarea" ) { |
| inputs[0].select(); |
| } |
| } |
| // endif WEBKIT_CHANGES |
| } |
| 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 field = document.getElementById(controlled_id); |
| 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'); |
| /* If a custom field such as a textarea field contains some text, then |
| * its content is visible by default as a readonly field (assuming that |
| * the field is displayed). But if such a custom field contains no text, |
| * then it's not displayed at all and an (edit) link is displayed instead. |
| * This is problematic if the custom field is mandatory, because at least |
| * Firefox complains that you must enter a value, but is unable to point |
| * to the custom field because this one is hidden, and so the user has |
| * to guess what the web browser is talking about, which is confusing. |
| * So in that case, we display the custom field automatically instead of |
| * the (edit) link, so that the user can enter some text in it. |
| */ |
| var field_readonly = document.getElementById(controlled_id + '_readonly'); |
| |
| if (!field_readonly) { |
| var field_input = document.getElementById(controlled_id + '_input'); |
| var edit_container = |
| document.getElementById(controlled_id + '_edit_container'); |
| |
| if (field_input) { |
| YAHOO.util.Dom.removeClass(field_input, 'bz_default_hidden'); |
| } |
| if (edit_container) { |
| YAHOO.util.Dom.addClass(edit_container, 'bz_hidden_field'); |
| } |
| } |
| // Restore the 'required' attribute for mandatory fields. |
| if (field.getAttribute('data-required') == "true") { |
| field.setAttribute('required', 'true'); |
| field.setAttribute('aria-required', 'true'); |
| } |
| } |
| else { |
| YAHOO.util.Dom.addClass(label_container, 'bz_hidden_field'); |
| YAHOO.util.Dom.addClass(field_container, 'bz_hidden_field'); |
| // A hidden field must never be required, because the user cannot set it. |
| if (field.getAttribute('data-required') == "true") { |
| field.removeAttribute('required'); |
| field.removeAttribute('aria-required'); |
| } |
| } |
| } |
| |
| /** |
| * 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 <option> |
| // based on the numeric id that Bugzilla uses for that value. |
| function _value_id(field_name, id) { |
| return 'v' + id + '_' + field_name; |
| } |
| |
| /*********************************/ |
| /* 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. */ |
| var ie_hidden_options = {}; |
| function hideOptionInIE(anOption, aSelect) { |
| if (browserCanHideOptions(aSelect)) return; |
| |
| var commentNode = document.createComment(anOption.value); |
| 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]) { |
| ie_hidden_options[aSelect.id] = {}; |
| } |
| ie_hidden_options[aSelect.id][anOption.id] = commentNode; |
| } |
| |
| function showOptionInIE(aNode, aSelect) { |
| if (browserCanHideOptions(aSelect)) return aNode; |
| |
| // 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; |
| 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; |
| } |
| |
| function initHidingOptionsForIE(select_name) { |
| var aSelect = document.getElementById(select_name); |
| if (browserCanHideOptions(aSelect)) return; |
| if (!aSelect) return; |
| |
| 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. |
| } |
| } |
| } |
| |
| /* 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 = {}; |
| |
| function getPossiblyHiddenOption(aSelect, optionId) { |
| |
| if (bz_option_duplicates[aSelect.id] |
| && bz_option_duplicates[aSelect.id][optionId]) |
| { |
| optionId = bz_option_duplicates[aSelect.id][optionId]; |
| } |
| |
| // 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]; |
| } |
| |
| // We add this property for our own convenience, it's used in |
| // other places. |
| val.bz_value_id = optionId; |
| |
| return val; |
| } |
| |
| 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 */ |
| |
| /** |
| * 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 : [ { |
| Bugzilla_api_token: BUGZILLA.api_token, |
| match : [ decodeURIComponent(enteredText) ], |
| include_fields : [ "name", "real_name" ] |
| } ] |
| }; |
| 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) { |
| return ( YAHOO.lang.escapeHTML(oResultData.real_name) + " (" |
| + YAHOO.lang.escapeHTML(oResultData.name) + ")"); |
| }, |
| 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"); |
| this.dataSource.connTimeout = 30000; |
| this.dataSource.connMethodPost = true; |
| this.dataSource.connXhrMode = "cancelStaleRequests"; |
| this.dataSource.maxCacheEntries = 5; |
| this.dataSource.responseSchema = { |
| resultsList : "result.users", |
| metaFields : { error: "error", jsonRpcId: "id"}, |
| fields : [ |
| { key : "name" }, |
| { key : "real_name"} |
| ] |
| }; |
| }, |
| 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 |
| userAutoComp.maxResultsDisplayed = BUGZILLA.param.maxusermatches; |
| 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 |
| userAutoComp.queryDelay = 0.05; |
| userAutoComp.useIFrame = true; |
| userAutoComp.resultTypeList = false; |
| if( multiple == true ){ |
| userAutoComp.delimChar = [","]; |
| } |
| |
| } |
| }; |
| |
| YAHOO.bugzilla.fieldAutocomplete = { |
| dataSource : [], |
| init_ds : function( field ) { |
| this.dataSource[field] = |
| new YAHOO.util.LocalDataSource( YAHOO.bugzilla.field_array[field] ); |
| }, |
| init : function( field, container ) { |
| if( this.dataSource[field] == null ) { |
| this.init_ds( field ); |
| } |
| var fieldAutoComp = |
| new YAHOO.widget.AutoComplete(field, container, this.dataSource[field]); |
| fieldAutoComp.maxResultsDisplayed = YAHOO.bugzilla.field_array[field].length; |
| fieldAutoComp.formatResult = fieldAutoComp.formatEscapedResult; |
| 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 |
| * focuses on the textbox |
| */ |
| fieldAutoComp.textboxFocusEvent.subscribe( function(){ |
| var sInputValue = YAHOO.util.Dom.get(field).value; |
| if( sInputValue.length === 0 |
| && YAHOO.bugzilla.field_array[field].length > 0 ){ |
| this.sendQuery(sInputValue); |
| this.collapseContainer(); |
| this.expandContainer(); |
| } |
| }); |
| fieldAutoComp.dataRequestEvent.subscribe( function(type, args) { |
| args[0].autoHighlight = args[1] != ''; |
| }); |
| } |
| }; |
| |
| /** |
| * 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; |
| } |
| } |
| |
| /** |
| * 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' : ''; |
| }); |
| } |
| } |
| |
| /** |
| * Comment preview |
| */ |
| |
| var last_comment_text = ''; |
| |
| function show_comment_preview(bug_id) { |
| var Dom = YAHOO.util.Dom; |
| var comment = document.getElementById('comment'); |
| var preview = document.getElementById('comment_preview'); |
| |
| if (!comment || !preview) return; |
| if (Dom.hasClass('comment_preview_tab', 'active_comment_tab')) return; |
| |
| preview.style.width = (comment.clientWidth - 4) + 'px'; |
| preview.style.height = comment.offsetHeight + 'px'; |
| |
| var comment_tab = document.getElementById('comment_tab'); |
| Dom.addClass(comment, 'bz_default_hidden'); |
| Dom.removeClass(comment_tab, 'active_comment_tab'); |
| comment_tab.setAttribute('aria-selected', 'false'); |
| |
| var preview_tab = document.getElementById('comment_preview_tab'); |
| Dom.removeClass(preview, 'bz_default_hidden'); |
| Dom.addClass(preview_tab, 'active_comment_tab'); |
| preview_tab.setAttribute('aria-selected', 'true'); |
| |
| Dom.addClass('comment_preview_error', 'bz_default_hidden'); |
| |
| if (last_comment_text == comment.value) |
| return; |
| |
| Dom.addClass('comment_preview_text', 'bz_default_hidden'); |
| Dom.removeClass('comment_preview_loading', 'bz_default_hidden'); |
| |
| YAHOO.util.Connect.setDefaultPostHeader('application/json', true); |
| YAHOO.util.Connect.asyncRequest('POST', 'jsonrpc.cgi', |
| { |
| success: function(res) { |
| data = YAHOO.lang.JSON.parse(res.responseText); |
| if (data.error) { |
| Dom.addClass('comment_preview_loading', 'bz_default_hidden'); |
| Dom.removeClass('comment_preview_error', 'bz_default_hidden'); |
| Dom.get('comment_preview_error').innerHTML = |
| YAHOO.lang.escapeHTML(data.error.message); |
| } else { |
| document.getElementById('comment_preview_text').innerHTML = data.result.html; |
| Dom.addClass('comment_preview_loading', 'bz_default_hidden'); |
| Dom.removeClass('comment_preview_text', 'bz_default_hidden'); |
| last_comment_text = comment.value; |
| } |
| }, |
| failure: function(res) { |
| Dom.addClass('comment_preview_loading', 'bz_default_hidden'); |
| Dom.removeClass('comment_preview_error', 'bz_default_hidden'); |
| Dom.get('comment_preview_error').innerHTML = |
| YAHOO.lang.escapeHTML(res.responseText); |
| } |
| }, |
| YAHOO.lang.JSON.stringify({ |
| version: "1.1", |
| method: 'Bug.render_comment', |
| params: { |
| Bugzilla_api_token: BUGZILLA.api_token, |
| id: bug_id, |
| text: comment.value |
| } |
| }) |
| ); |
| } |
| |
| function show_comment_edit() { |
| var comment = document.getElementById('comment'); |
| var preview = document.getElementById('comment_preview'); |
| if (!comment || !preview) return; |
| if (YAHOO.util.Dom.hasClass(comment, 'active_comment_tab')) return; |
| |
| var preview_tab = document.getElementById('comment_preview_tab'); |
| YAHOO.util.Dom.addClass(preview, 'bz_default_hidden'); |
| YAHOO.util.Dom.removeClass(preview_tab, 'active_comment_tab'); |
| preview_tab.setAttribute('aria-selected', 'false'); |
| |
| var comment_tab = document.getElementById('comment_tab'); |
| YAHOO.util.Dom.removeClass(comment, 'bz_default_hidden'); |
| YAHOO.util.Dom.addClass(comment_tab, 'active_comment_tab'); |
| comment_tab.setAttribute('aria-selected', 'true'); |
| } |
| |
| function adjustRemainingTime() { |
| // subtracts time spent from remaining time |
| // prevent negative values if work_time > bz_remaining_time |
| var new_time = Math.max(bz_remaining_time - document.changeform.work_time.value, 0.0); |
| // get upto 2 decimal places |
| document.changeform.remaining_time.value = Math.round(new_time * 100)/100; |
| } |
| |
| function updateRemainingTime() { |
| // if the remaining time is changed manually, update bz_remaining_time |
| bz_remaining_time = document.changeform.remaining_time.value; |
| } |