aboutsummaryrefslogtreecommitdiffstats
path: root/productmenu.js
blob: e633ab3279d844c62fac8428a9f126f27500a840 (plain)
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// 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
    // - selectedIndex is the index of the first selected item
    // - selectedValue is the value of the first selected item
    var sel = Array();
    var is_diff = 0;
    var single;
    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++ ) {
            sel[sel.length] = f[productfield].options[i].value;
        }
        single = 0;
    } else {

        for ( i = blank ? 1 : 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 );
}