230 lines
7.6 KiB
JavaScript
230 lines
7.6 KiB
JavaScript
/* License: Dual-license GPL 3.0+ or MPL 1.1+
|
|
* Contributor(s): Vitaliy Filippov <vitalif@mail.ru>
|
|
*/
|
|
|
|
/*
|
|
|
|
JavaScript code to hide and show values of select fields on the query form.
|
|
I.e. hide dependent values when their controlling value is hidden.
|
|
This code uses client-side cached qfVisibility hash data generated by
|
|
fieldvaluecontrol.cgi?type=search.
|
|
|
|
qfVisibility format: {
|
|
// for each field:
|
|
field_name : {
|
|
// all legal values for the field:
|
|
legal : [ { id : 0, name : "NAME" }, ... ],
|
|
// visibility data for field values:
|
|
values : { controlled_field_name : { controlled_value_id : { visibility_value_id : 1, ... }, ... }, ... },
|
|
// visibility data for shown/hidden fields:
|
|
fields : { controlled_field_name : { visibility_value_id : 1, ... }, ... },
|
|
},
|
|
...
|
|
};
|
|
|
|
Important nuances of implementation:
|
|
|
|
Non-custom product-dependent Bugzilla fields (component, version, target_milestone)
|
|
values are not uniquely identified by their name. There could be many values with
|
|
same name belonging to different products.
|
|
|
|
Logically, such behavior is more convenient than one implemented in Bugzilla
|
|
custom fields, where each value has a unique name and could be "visible"
|
|
inside some, for example, products (you cannot possibly add different
|
|
descriptions for the value inside different products). So we need to support it
|
|
and remove duplicate names inside selectboxes.
|
|
|
|
But simply using names instead of IDs for filtering is insufficient: if
|
|
someday we'll have a custom field (say cf_1) depending on one of such "non-unique-value-name"
|
|
fields (say milestone), then selecting a product AND a milestone will display
|
|
the list of cf_1 values which are visible for ANY of milestones with selected name
|
|
ignoring selected product.
|
|
|
|
So we need to remember "which IDs of each name" are active now and filter
|
|
dependent values accordingly. I.e. when ONE product is selected, only ONE ID
|
|
could be present for each milestone displayed in selectbox.
|
|
|
|
We also need to preserve the sort order of dependent "non-unique-value-name" values,
|
|
respecting this "list of active IDs".
|
|
|
|
*/
|
|
|
|
var qfHandling = {};
|
|
addListener(window, 'load', initQueryformFields);
|
|
|
|
function initQueryformFields()
|
|
{
|
|
for (var i in qfVisibility)
|
|
{
|
|
if (!qfHandling[i])
|
|
handleQueryformField(null, document.getElementById(i));
|
|
initQueryformField(i);
|
|
}
|
|
}
|
|
|
|
function initQueryformField(i)
|
|
{
|
|
var f = document.getElementById(i);
|
|
if (f)
|
|
addListener(f, 'change', handleQueryformField_this);
|
|
}
|
|
|
|
// Get selected IDs of names selected in selectbox sel
|
|
function getQueryformSelectedIds(sel)
|
|
{
|
|
var opt = {};
|
|
var a;
|
|
var has_selected;
|
|
var l2 = sel.id.length+4;
|
|
// No selection is equivalent to full selection
|
|
for (var i = 0; i < sel.options.length; i++)
|
|
{
|
|
if (sel.options[i].selected)
|
|
{
|
|
has_selected = true;
|
|
break;
|
|
}
|
|
}
|
|
// Iterate over all options
|
|
for (var i = 0; i < sel.options.length; i++)
|
|
{
|
|
if (sel.options[i].selected || !has_selected)
|
|
{
|
|
// IDs of options are qf_<SELECTBOX_ID>_1_2_3_...
|
|
// where 1_2_3 is the list of IDs mapped to this option name
|
|
// which are currently visible.
|
|
a = sel.options[i].id.substr(l2).split('_');
|
|
for (var j in a)
|
|
opt[a[j]] = true;
|
|
}
|
|
}
|
|
return opt;
|
|
}
|
|
|
|
// Get the array with values of options selected in sel selectbox
|
|
function getPlainSelectedIds(sel)
|
|
{
|
|
var o = {};
|
|
for (var i = 0; i < sel.options.length; i++)
|
|
if (sel.options[i].selected)
|
|
o[sel.options[i].value] = true;
|
|
return o;
|
|
}
|
|
|
|
// Check visibility of some field or value when it's visible only for ids
|
|
// which are keys of $visible_for_ids, and if $selected_ids are selected by now.
|
|
// Examples:
|
|
// f({ 1: 1, 3: 1 }, { 1: 1, 2: 1 }) = true
|
|
// f({}, { 1: 1, 2: 1 }) = true
|
|
// f({ 2: 1 }, { 1: 1 }) = false
|
|
function qfCheckVisibility(visible_for_ids, selected_ids)
|
|
{
|
|
vis = true;
|
|
// Visible also if visible_for_ids is an empty hash
|
|
if (visible_for_ids)
|
|
{
|
|
for (var cid in visible_for_ids)
|
|
{
|
|
vis = false;
|
|
if (selected_ids[cid])
|
|
{
|
|
vis = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return vis;
|
|
}
|
|
|
|
// handleQueryformField on 'this' change
|
|
function handleQueryformField_this(event)
|
|
{
|
|
return handleQueryformField(event, this);
|
|
}
|
|
|
|
// Handle change of selection inside a selectbox 'controller'
|
|
// Update all dependent fields
|
|
function handleQueryformField(event, controller)
|
|
{
|
|
var controlled, controlled_selected;
|
|
var vis, item, legal, name2id, name2id_order, valueVD;
|
|
qfHandling[controller.id] = true; // prevent double-action during init
|
|
var VD = qfVisibility[controller.id];
|
|
var visibility_selected = getQueryformSelectedIds(controller);
|
|
var ids = {};
|
|
for (var i in VD.fields)
|
|
ids[i] = true;
|
|
for (var i in VD.values)
|
|
ids[i] = true;
|
|
for (var controlled_id in ids)
|
|
{
|
|
controlled = document.getElementById(controlled_id);
|
|
if (!controlled)
|
|
continue;
|
|
if (controlled.nodeName != 'SELECT')
|
|
{
|
|
// Just show/hide non-select fields
|
|
item = document.getElementById(controlled_id+'_cont');
|
|
if (item)
|
|
{
|
|
item.style.display = qfCheckVisibility(
|
|
VD.fields[controlled_id], visibility_selected
|
|
) ? '' : 'none';
|
|
}
|
|
continue;
|
|
}
|
|
// Save and clear old selection for dependent field:
|
|
controlled_selected = getPlainSelectedIds(controlled);
|
|
bz_clearOptions(controlled);
|
|
// Loop over all legal values and remember currently
|
|
// visible IDs inside name2id preserving their original
|
|
// order using name2id_order
|
|
legal = qfVisibility[controlled_id]['legal'];
|
|
name2id = {};
|
|
name2id_order = [];
|
|
if (qfCheckVisibility(VD.fields[controlled_id], visibility_selected))
|
|
{
|
|
var vis_val = VD.values[controlled_id];
|
|
// Field is visible
|
|
for (var i in legal)
|
|
{
|
|
if (!vis_val || qfCheckVisibility(vis_val[legal[i][0]], visibility_selected))
|
|
{
|
|
// Value is visible
|
|
if (!name2id[legal[i][1]])
|
|
{
|
|
name2id[legal[i][1]] = [];
|
|
name2id_order.push(legal[i][1]);
|
|
}
|
|
name2id[legal[i][1]].push(legal[i][0]);
|
|
}
|
|
}
|
|
}
|
|
// Create options
|
|
for (var i in name2id_order)
|
|
{
|
|
i = name2id_order[i];
|
|
item = bz_createOptionInSelect(controlled, i, i);
|
|
/* Save particular selected IDs for the same name
|
|
for cascade selection of such fields.
|
|
At the moment, only component, version and target_milestone fields
|
|
can have many values with the same name, and they do not affect the
|
|
case of cascade selection when there are no custom fields depending
|
|
on them. */
|
|
item.id = 'qf_'+controlled_id+'_'+name2id[i].join('_');
|
|
if (controlled_selected[i])
|
|
{
|
|
// Restore selection
|
|
item.selected = true;
|
|
}
|
|
}
|
|
handleQueryformField(event, controlled);
|
|
item = document.getElementById(controlled_id+'_cont');
|
|
if (item)
|
|
{
|
|
// Hide fields with no options
|
|
item.style.display = controlled.options.length ? '' : 'none';
|
|
}
|
|
}
|
|
}
|