diff options
Diffstat (limited to 'productmenu.js')
-rw-r--r-- | productmenu.js | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/productmenu.js b/productmenu.js new file mode 100644 index 000000000..d917d325c --- /dev/null +++ b/productmenu.js @@ -0,0 +1,242 @@ +// 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 product name. the +// array should contain the elements that correspont to that +// product. Example: +// var array = Array(); +// array['ProductOne'] = [ 'ComponentA', 'ComponentB' ]; +// updateSelect(array, source, target); +// - sel is a list of selected items, either whole or a diff +// depending on sel_is_diff. +// - sel_is_diff determines if we are sending in just a diff or the +// whole selection. a diff is used to optimize adding selections. +// - target should be the target select object. +// - single specifies if we selected a single item. if we did, no +// need to merge. + +function updateSelect( array, sel, target, sel_is_diff, single, blank ) { + + var i, j, comp; + + // if single, even if it's a diff (happens when you have nothing + // selected and select one item alone), skip this. + if ( ! single ) { + + // array merging/sorting in the case of multiple selections + if ( sel_is_diff ) { + + // merge in the current options with the first selection + comp = merge_arrays( array[sel[0]], target.options, 1 ); + + // merge the rest of the selection with the results + for ( i = 1 ; i < sel.length ; i++ ) { + comp = merge_arrays( array[sel[i]], comp, 0 ); + } + } else { + // here we micro-optimize for two arrays to avoid merging with a + // null array + comp = 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++ ) { + comp = merge_arrays( comp, array[sel[i]], 0 ); + } + } + } else { + // single item in selection, just get me the list + comp = array[sel[0]]; + } + + // save the selection in the target select so we can restore it later + var selections = new Array(); + for ( i = 0; i < target.options.length; i++ ) + if (target.options[i].selected) selections.push(target.options[i].value); + + // clear select + target.options.length = 0; + + // add empty "Any" value back to the list + if (blank) target.options[0] = new Option( blank, "" ); + + // load elements of list into select + for ( i = 0; i < comp.length; i++ ) { + target.options[target.options.length] = new Option( comp[i], comp[i] ); + } + + // restore the selection + for ( i=0 ; i<selections.length ; i++ ) + for ( j=0 ; j<target.options.length ; j++ ) + if (target.options[j].value == selections[i]) target.options[j].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(); + + // do a boring array diff to see who's new + for ( var ia in a ) { + var found = 0; + for ( var ib in b ) { + if ( a[ia] == b[ib] ) { + found = 1; + } + } + if ( ! found ) { + newsel[newsel.length] = a[ia]; + } + found = 0; + } + 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; + } + +// selectProduct reads the selection from f[productfield] and updates +// f.version, component and target_milestone accordingly. +// - f: a form containing product, component, varsion and +// target_milestone select boxes. +// 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. +// - usetms: this is a global boolean that is defined if the +// bugzilla installation has it turned on. generated in perl too. +// - first_load: boolean, specifying if it's 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( f , productfield, componentfield, blank ) { + + // this is to avoid handling events that occur before the form + // itself is ready, which happens in buggy browsers. + + if ( ( !f ) || ( ! f[productfield] ) ) { + 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 ) && ( f[productfield].selectedIndex == -1 ) ) { + first_load = 0; + 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 = 0; + + // - sel keeps the array of products we are selected. + // - is_diff says if it's a full list or just a list of products that + // were added to the current selection. + // - single indicates if a single item was selected + var sel = Array(); + var is_diff = 0; + var single; + + // if nothing selected, pick all + if ( f[productfield].selectedIndex == -1 ) { + for ( var i = 0 ; i < f[productfield].length ; i++ ) { + sel[sel.length] = f[productfield].options[i].value; + } + single = 0; + } else { + + for ( i = 0 ; i < f[productfield].length ; i++ ) { + if ( f[productfield].options[i].selected ) { + sel[sel.length] = f[productfield].options[i].value; + } + } + + single = ( sel.length == 1 ); + + // save last_sel before we kill it + var tmp = last_sel; + last_sel = sel; + + // this is an optimization: if we've added components, no need + // to remerge them; just merge the new ones with the existing + // options. + + if ( ( tmp ) && ( tmp.length < sel.length ) ) { + sel = fake_diff_array(sel, tmp); + is_diff = 1; + } + } + + // do the actual fill/update + updateSelect( cpts, sel, f[componentfield], is_diff, single, blank ); +} |