blob: 7cf07d732a38e20861f64e40028c97af73676212 [file] [log] [blame]
/* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is the Bugzilla Bug Tracking System.
*
* The Initial Developer of the Original Code is Netscape Communications
* Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s): Christian Reis <kiko@async.com.br>
*/
/* this file contains functions to update form controls based on a
* collection of javascript arrays containing strings */
/* selectClassification reads the selection from f.classification and updates
* f.product accordingly
* - f: a form containing classification, product, component, varsion and
* target_milestone select boxes.
* globals (3vil!):
* - prods, indexed by classification name
* - first_load: boolean, specifying if it is the first time we load
* the query page.
* - last_sel: saves our last selection list so we know what has
* changed, and optimize for additions.
*/
function selectClassification(classfield, product, component, version, milestone) {
/* this is to avoid handling events that occur before the form
* itself is ready, which could happen in buggy browsers.
*/
if (!classfield) {
return;
}
/* if this is the first load and nothing is selected, no need to
* merge and sort all components; perl gives it to us sorted.
*/
if ((first_load) && (classfield.selectedIndex == -1)) {
first_load = false;
return;
}
/* don't reset first_load as done in selectProduct. That's because we
want selectProduct to handle the first_load attribute
*/
/* - sel keeps the array of classifications we are selected.
* - merging says if it is a full list or just a list of classifications
* that were added to the current selection.
*/
var merging = false;
var sel = Array();
/* if nothing selected, pick all */
var findall = classfield.selectedIndex == -1;
sel = get_selection(classfield, findall, false);
if (!findall) {
/* save sel for the next invocation of selectClassification() */
var tmp = sel;
/* this is an optimization: if we have just added classifications to an
* existing selection, no need to clear the form controls and add
* everybody again; just merge the new ones with the existing
* options.
*/
if ((last_sel.length > 0) && (last_sel.length < sel.length)) {
sel = fake_diff_array(sel, last_sel);
merging = true;
}
last_sel = tmp;
}
/* save original options selected */
var saved_prods = get_selection(product, false, true);
/* do the actual fill/update, reselect originally selected options */
updateSelect(prods, sel, product, merging);
restoreSelection(product, saved_prods);
selectProduct(product, component, version, milestone);
}
/* selectProduct reads the selection from the product control and
* updates version, component and milestone controls accordingly.
*
* - product, component, version and milestone: form controls
*
* globals (3vil!):
* - cpts, vers, tms: array of arrays, indexed by product name. the
* subarrays contain a list of names to be fed to the respective
* selectboxes. For bugzilla, these are generated with perl code
* at page start.
* - first_load: boolean, specifying if it is the first time we load
* the query page.
* - last_sel: saves our last selection list so we know what has
* changed, and optimize for additions.
*/
function selectProduct(product, component, version, milestone) {
if (!product) {
/* this is to avoid handling events that occur before the form
* itself is ready, which could happen in buggy browsers. */
return;
}
/* if this is the first load and nothing is selected, no need to
* merge and sort all components; perl gives it to us sorted. */
if ((first_load) && (product.selectedIndex == -1)) {
first_load = false;
return;
}
/* turn first_load off. this is tricky, since it seems to be
* redundant with the above clause. It's not: if when we first load
* the page there is _one_ element selected, it won't fall into that
* clause, and first_load will remain 1. Then, if we unselect that
* item, selectProduct will be called but the clause will be valid
* (since selectedIndex == -1), and we will return - incorrectly -
* without merge/sorting. */
first_load = false;
/* - sel keeps the array of products we are selected.
* - merging says if it is a full list or just a list of products that
* were added to the current selection. */
var merging = false;
var sel = Array();
/* if nothing selected, pick all */
var findall = product.selectedIndex == -1;
if (useclassification) {
/* update index based on the complete product array */
sel = get_selection(product, findall, true);
for (var i=0; i<sel.length; i++) {
sel[i] = prods[sel[i]];
}
} else {
sel = get_selection(product, findall, false);
}
if (!findall) {
/* save sel for the next invocation of selectProduct() */
var tmp = sel;
/* this is an optimization: if we have just added products to an
* existing selection, no need to clear the form controls and add
* everybody again; just merge the new ones with the existing
* options. */
if ((last_sel.length > 0) && (last_sel.length < sel.length)) {
sel = fake_diff_array(sel, last_sel);
merging = true;
}
last_sel = tmp;
}
/* do the actual fill/update */
if (component) {
var saved_cpts = get_selection(component, false, true);
updateSelect(cpts, sel, component, merging);
restoreSelection(component, saved_cpts);
}
if (version) {
var saved_vers = get_selection(version, false, true);
updateSelect(vers, sel, version, merging);
restoreSelection(version, saved_vers);
}
if (milestone) {
var saved_tms = get_selection(milestone, false, true);
updateSelect(tms, sel, milestone, merging);
restoreSelection(milestone, saved_tms);
}
}
/* updateSelect(array, sel, target, merging)
*
* Adds to the target select object all elements in array that
* correspond to the elements selected in source.
* - array should be a array of arrays, indexed by number. the
* array should contain the elements that correspond to that
* product.
* - sel is a list of selected items, either whole or a diff
* depending on merging.
* - target should be the target select object.
* - merging (boolean) determines if we are mergine in a diff or
* substituting the whole selection. a diff is used to optimize adding
* selections.
*
* Example (compsel is a select form control)
*
* var components = Array();
* components[1] = [ 'ComponentA', 'ComponentB' ];
* components[2] = [ 'ComponentC', 'ComponentD' ];
* source = [ 2 ];
* updateSelect(components, source, compsel, 0, 0);
*
* would clear compsel and add 'ComponentC' and 'ComponentD' to it.
*
*/
function updateSelect(array, sel, target, merging) {
var i, item;
/* If we have no versions/components/milestones */
if (array.length < 1) {
target.options.length = 0;
return false;
}
if (merging) {
/* array merging/sorting in the case of multiple selections */
/* merge in the current options with the first selection */
item = merge_arrays(array[sel[0]], target.options, 1);
/* merge the rest of the selection with the results */
for (i = 1 ; i < sel.length ; i++) {
item = merge_arrays(array[sel[i]], item, 0);
}
} else if ( sel.length > 1 ) {
/* here we micro-optimize for two arrays to avoid merging with a
* null array */
item = merge_arrays(array[sel[0]],array[sel[1]], 0);
/* merge the arrays. not very good for multiple selections. */
for (i = 2; i < sel.length; i++) {
item = merge_arrays(item, array[sel[i]], 0);
}
} else { /* single item in selection, just get me the list */
item = array[sel[0]];
}
/* clear select */
target.options.length = 0;
/* load elements of list into select */
for (i = 0; i < item.length; i++) {
target.options[i] = new Option(item[i], item[i]);
}
return true;
}
/* Selects items in control that have index defined in sel
* - control: SELECT control to be restored
* - selnames: array of indexes in select form control */
function restoreSelection(control, selnames) {
/* right. this sucks. but I see no way to avoid going through the
* list and comparing to the contents of the control. */
for (var j=0; j < selnames.length; j++) {
for (var i=0; i < control.options.length; i++) {
if (control.options[i].value == selnames[j]) {
control.options[i].selected = true;
}
}
}
}
/* Returns elements in a that are not in b.
* NOT A REAL DIFF: does not check the reverse.
* - a,b: arrays of values to be compare. */
function fake_diff_array(a, b) {
var newsel = new Array();
var found = false;
/* do a boring array diff to see who's new */
for (var ia in a) {
for (var ib in b) {
if (a[ia] == b[ib]) {
found = true;
}
}
if (!found) {
newsel[newsel.length] = a[ia];
}
found = false;
}
return newsel;
}
/* takes two arrays and sorts them by string, returning a new, sorted
* array. the merge removes dupes, too.
* - a, b: arrays to be merge.
* - b_is_select: if true, then b is actually an optionitem and as
* such we need to use item.value on it. */
function merge_arrays(a, b, b_is_select) {
var pos_a = 0;
var pos_b = 0;
var ret = new Array();
var bitem, aitem;
/* iterate through both arrays and add the larger item to the return
* list. remove dupes, too. Use toLowerCase to provide
* case-insensitivity. */
while ((pos_a < a.length) && (pos_b < b.length)) {
if (b_is_select) {
bitem = b[pos_b].value;
} else {
bitem = b[pos_b];
}
aitem = a[pos_a];
/* smaller item in list a */
if (aitem.toLowerCase() < bitem.toLowerCase()) {
ret[ret.length] = aitem;
pos_a++;
} else {
/* smaller item in list b */
if (aitem.toLowerCase() > bitem.toLowerCase()) {
ret[ret.length] = bitem;
pos_b++;
} else {
/* list contents are equal, inc both counters. */
ret[ret.length] = aitem;
pos_a++;
pos_b++;
}
}
}
/* catch leftovers here. these sections are ugly code-copying. */
if (pos_a < a.length) {
for (; pos_a < a.length ; pos_a++) {
ret[ret.length] = a[pos_a];
}
}
if (pos_b < b.length) {
for (; pos_b < b.length; pos_b++) {
if (b_is_select) {
bitem = b[pos_b].value;
} else {
bitem = b[pos_b];
}
ret[ret.length] = bitem;
}
}
return ret;
}
/* Returns an array of indexes or values from a select form control.
* - control: select control from which to find selections
* - findall: boolean, store all options when true or just the selected
* indexes
* - want_values: boolean; we store values when true and indexes when
* false */
function get_selection(control, findall, want_values) {
var ret = new Array();
if ((!findall) && (control.selectedIndex == -1)) {
return ret;
}
for (var i=0; i<control.length; i++) {
if (findall || control.options[i].selected) {
ret[ret.length] = want_values ? control.options[i].value : i;
}
}
return ret;
}