diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/productform.js | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/js/productform.js b/js/productform.js new file mode 100644 index 000000000..0be0d4971 --- /dev/null +++ b/js/productform.js @@ -0,0 +1,295 @@ +/* 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 */ + +/* 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; + 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; +} + |