productmenu.js 9.02 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
// 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;
    }

184 185 186 187 188 189
    // Do nothing if no products are defined (this avoids the
    // "a has no properties" error from merge_arrays function)
    if (f[productfield].length == blank ? 1 : 0) {
        return;
    }

190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
    // 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
212 213
    // - selectedIndex is the index of the first selected item
    // - selectedValue is the value of the first selected item
214 215 216
    var sel = Array();
    var is_diff = 0;
    var single;
217 218 219 220 221 222 223 224 225
    var selectedIndex = f[productfield].selectedIndex;
    var selectedValue = f[productfield].options[selectedIndex].value;

    // If nothing is selected, or the selected item is the "blank" value
    // at the top of the list which represents all products on drop-down menus,
    // then pick all products so we show all components.
    if ( selectedIndex == -1 || !cpts[selectedValue])
    {
        for ( var i = blank ? 1 : 0 ; i < f[productfield].length ; i++ ) {
226 227
            sel[sel.length] = f[productfield].options[i].value;
        }
228 229
        // If there is only one product, then only one product can be selected
        single = ( sel.length == 1 );
230 231
    } else {

232
        for ( i = blank ? 1 : 0 ; i < f[productfield].length ; i++ ) {
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
            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 );
}