summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormkanat%bugzilla.org <>2008-11-07 18:34:39 +0100
committermkanat%bugzilla.org <>2008-11-07 18:34:39 +0100
commit63be194996849202878c4a87e4c68a25d1976d3e (patch)
tree657fbe1458ce256015a832251219669070886e1e
parentebd2e3a29a893e1ea26899bac53296fc6422f47c (diff)
downloadbugzilla-63be194996849202878c4a87e4c68a25d1976d3e.tar.gz
bugzilla-63be194996849202878c4a87e4c68a25d1976d3e.tar.xz
Bug 308253: Ability to add select (enum) fields to a bug whose list of values depends on the value of another field
Patch By Max Kanat-Alexander <mkanat@bugzilla.org> r=bbaetz, a=mkanat
-rw-r--r--Bugzilla/DB/Schema.pm100
-rw-r--r--Bugzilla/Field.pm82
-rw-r--r--Bugzilla/Field/Choice.pm48
-rw-r--r--Bugzilla/Install/DB.pm39
-rw-r--r--Bugzilla/Status.pm11
-rw-r--r--editfields.cgi2
-rwxr-xr-xeditvalues.cgi11
-rw-r--r--js/field.js121
-rw-r--r--js/util.js34
-rw-r--r--skins/standard/global.css5
-rw-r--r--template/en/default/admin/custom_fields/cf-js.js.tmpl12
-rw-r--r--template/en/default/admin/custom_fields/create.html.tmpl24
-rw-r--r--template/en/default/admin/custom_fields/edit.html.tmpl24
-rw-r--r--template/en/default/admin/fieldvalues/confirm-delete.html.tmpl17
-rw-r--r--template/en/default/admin/fieldvalues/create.html.tmpl21
-rw-r--r--template/en/default/admin/fieldvalues/edit.html.tmpl27
-rw-r--r--template/en/default/bug/create/create.html.tmpl11
-rw-r--r--template/en/default/bug/field-events.js.tmpl36
-rw-r--r--template/en/default/bug/field.html.tmpl32
-rw-r--r--template/en/default/bug/knob.html.tmpl12
-rw-r--r--template/en/default/global/messages.html.tmpl11
-rw-r--r--template/en/default/global/user-error.html.tmpl24
22 files changed, 589 insertions, 115 deletions
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm
index 778db72c5..ed1245d98 100644
--- a/Bugzilla/DB/Schema.pm
+++ b/Bugzilla/DB/Schema.pm
@@ -210,6 +210,26 @@ use constant SCHEMA_VERSION => '2.00';
use constant ADD_COLUMN => 'ADD COLUMN';
# This is a reasonable default that's true for both PostgreSQL and MySQL.
use constant MAX_IDENTIFIER_LEN => 63;
+
+use constant FIELD_TABLE_SCHEMA => {
+ FIELDS => [
+ id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
+ PRIMARYKEY => 1},
+ value => {TYPE => 'varchar(64)', NOTNULL => 1},
+ sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
+ isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'TRUE'},
+ visibility_value_id => {TYPE => 'INT2'},
+ ],
+ # Note that bz_add_field_table should prepend the table name
+ # to these index names.
+ INDEXES => [
+ value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
+ sortkey_idx => ['sortkey', 'value'],
+ visibility_value_id_idx => ['visibility_value_id'],
+ ],
+};
+
use constant ABSTRACT_SCHEMA => {
# BUG-RELATED TABLES
@@ -638,11 +658,15 @@ use constant ABSTRACT_SCHEMA => {
REFERENCES => {TABLE => 'fielddefs',
COLUMN => 'id'}},
visibility_value_id => {TYPE => 'INT2'},
+ value_field_id => {TYPE => 'INT3',
+ REFERENCES => {TABLE => 'fielddefs',
+ COLUMN => 'id'}},
],
INDEXES => [
fielddefs_name_idx => {FIELDS => ['name'],
TYPE => 'UNIQUE'},
fielddefs_sortkey_idx => ['sortkey'],
+ fielddefs_value_field_id_idx => ['value_field_id'],
],
},
@@ -688,98 +712,65 @@ use constant ABSTRACT_SCHEMA => {
bug_status => {
FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
+ @{ dclone(FIELD_TABLE_SCHEMA->{FIELDS}) },
is_open => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'},
+
],
INDEXES => [
bug_status_value_idx => {FIELDS => ['value'],
TYPE => 'UNIQUE'},
bug_status_sortkey_idx => ['sortkey', 'value'],
+ bug_status_visibility_value_id_idx => ['visibility_value_id'],
],
},
resolution => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
INDEXES => [
resolution_value_idx => {FIELDS => ['value'],
TYPE => 'UNIQUE'},
resolution_sortkey_idx => ['sortkey', 'value'],
+ resolution_visibility_value_id_idx => ['visibility_value_id'],
],
},
bug_severity => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
INDEXES => [
bug_severity_value_idx => {FIELDS => ['value'],
TYPE => 'UNIQUE'},
bug_severity_sortkey_idx => ['sortkey', 'value'],
+ bug_severity_visibility_value_id_idx => ['visibility_value_id'],
],
},
priority => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
INDEXES => [
priority_value_idx => {FIELDS => ['value'],
TYPE => 'UNIQUE'},
priority_sortkey_idx => ['sortkey', 'value'],
+ priority_visibility_value_id_idx => ['visibility_value_id'],
],
},
rep_platform => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
INDEXES => [
rep_platform_value_idx => {FIELDS => ['value'],
TYPE => 'UNIQUE'},
rep_platform_sortkey_idx => ['sortkey', 'value'],
+ rep_platform_visibility_value_id_idx => ['visibility_value_id'],
],
},
op_sys => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
+ FIELDS => dclone(FIELD_TABLE_SCHEMA->{FIELDS}),
INDEXES => [
op_sys_value_idx => {FIELDS => ['value'],
TYPE => 'UNIQUE'},
op_sys_sortkey_idx => ['sortkey', 'value'],
+ op_sys_visibility_value_id_idx => ['visibility_value_id'],
],
},
@@ -1409,23 +1400,6 @@ use constant ABSTRACT_SCHEMA => {
};
-use constant FIELD_TABLE_SCHEMA => {
- FIELDS => [
- id => {TYPE => 'SMALLSERIAL', NOTNULL => 1,
- PRIMARYKEY => 1},
- value => {TYPE => 'varchar(64)', NOTNULL => 1},
- sortkey => {TYPE => 'INT2', NOTNULL => 1, DEFAULT => 0},
- isactive => {TYPE => 'BOOLEAN', NOTNULL => 1,
- DEFAULT => 'TRUE'},
- ],
- # Note that bz_add_field_table should prepend the table name
- # to these index names.
- INDEXES => [
- value_idx => {FIELDS => ['value'], TYPE => 'UNIQUE'},
- sortkey_idx => ['sortkey', 'value'],
- ],
-};
-
# Foreign Keys are added in Bugzilla::DB::bz_add_field_tables
use constant MULTI_SELECT_VALUE_TABLE => {
FIELDS => [
diff --git a/Bugzilla/Field.pm b/Bugzilla/Field.pm
index a5e380a11..17459cb2c 100644
--- a/Bugzilla/Field.pm
+++ b/Bugzilla/Field.pm
@@ -98,6 +98,7 @@ use constant DB_COLUMNS => qw(
enter_bug
visibility_field_id
visibility_value_id
+ value_field_id
);
use constant REQUIRED_CREATE_FIELDS => qw(name description);
@@ -110,10 +111,11 @@ use constant VALIDATORS => {
obsolete => \&_check_obsolete,
sortkey => \&_check_sortkey,
type => \&_check_type,
- visibility_field_id => \&_check_control_field,
+ visibility_field_id => \&_check_visibility_field_id,
};
use constant UPDATE_VALIDATORS => {
+ value_field_id => \&_check_value_field_id,
visibility_value_id => \&_check_control_value,
};
@@ -125,6 +127,7 @@ use constant UPDATE_COLUMNS => qw(
enter_bug
visibility_field_id
visibility_value_id
+ value_field_id
type
);
@@ -292,7 +295,16 @@ sub _check_type {
return $type;
}
-sub _check_control_field {
+sub _check_value_field_id {
+ my ($invocant, $field_id, $is_select) = @_;
+ $is_select = $invocant->is_select if !defined $is_select;
+ if ($field_id && !$is_select) {
+ ThrowUserError('field_value_control_select_only');
+ }
+ return $invocant->_check_visibility_field_id($field_id);
+}
+
+sub _check_visibility_field_id {
my ($invocant, $field_id) = @_;
$field_id = trim($field_id);
return undef if !$field_id;
@@ -310,7 +322,7 @@ sub _check_control_field {
sub _check_control_value {
my ($invocant, $value_id, $field_id) = @_;
my $field;
- if (blessed($invocant)) {
+ if (blessed $invocant) {
$field = $invocant->visibility_field;
}
elsif ($field_id) {
@@ -528,6 +540,48 @@ sub controls_visibility_of {
=pod
+=over
+
+=item C<value_field>
+
+The Bugzilla::Field that controls the list of values for this field.
+
+Returns undef if there is no field that controls this field's visibility.
+
+=back
+
+=cut
+
+sub value_field {
+ my $self = shift;
+ if ($self->{value_field_id}) {
+ $self->{value_field} ||= $self->new($self->{value_field_id});
+ }
+ return $self->{value_field};
+}
+
+=pod
+
+=over
+
+=item C<controls_values_of>
+
+An arrayref of C<Bugzilla::Field> objects, representing fields that this
+field controls the values of.
+
+=back
+
+=cut
+
+sub controls_values_of {
+ my $self = shift;
+ $self->{controls_values_of} ||=
+ Bugzilla::Field->match({ value_field_id => $self->id });
+ return $self->{controls_values_of};
+}
+
+=pod
+
=head2 Instance Mutators
These set the particular field that they are named after.
@@ -552,6 +606,8 @@ They will throw an error if you try to set the values to something invalid.
=item C<set_visibility_value>
+=item C<set_value_field>
+
=back
=cut
@@ -572,6 +628,11 @@ sub set_visibility_value {
$self->set('visibility_value_id', $value);
delete $self->{visibility_value};
}
+sub set_value_field {
+ my ($self, $value) = @_;
+ $self->set('value_field_id', $value);
+ delete $self->{value_field};
+}
# This is only used internally by upgrade code in Bugzilla::Field.
sub _set_type { $_[0]->set('type', $_[1]); }
@@ -734,9 +795,24 @@ sub run_create_validators {
$class->_check_control_value($params->{visibility_value_id},
$params->{visibility_field_id});
+ my $type = $params->{type};
+ $params->{value_field_id} =
+ $class->_check_value_field_id($params->{value_field_id},
+ ($type == FIELD_TYPE_SINGLE_SELECT
+ || $type == FIELD_TYPE_MULTI_SELECT) ? 1 : 0);
return $params;
}
+sub update {
+ my $self = shift;
+ my $changes = $self->SUPER::update(@_);
+ my $dbh = Bugzilla->dbh;
+ if ($changes->{value_field_id} && $self->is_select) {
+ $dbh->do("UPDATE " . $self->name . " SET visibility_value_id = NULL");
+ }
+ return $changes;
+}
+
=pod
diff --git a/Bugzilla/Field/Choice.pm b/Bugzilla/Field/Choice.pm
index 4da644f1d..9e8fb1235 100644
--- a/Bugzilla/Field/Choice.pm
+++ b/Bugzilla/Field/Choice.pm
@@ -40,11 +40,13 @@ use constant DB_COLUMNS => qw(
id
value
sortkey
+ visibility_value_id
);
use constant UPDATE_COLUMNS => qw(
value
sortkey
+ visibility_value_id
);
use constant NAME_FIELD => 'value';
@@ -55,6 +57,7 @@ use constant REQUIRED_CREATE_FIELDS => qw(value);
use constant VALIDATORS => {
value => \&_check_value,
sortkey => \&_check_sortkey,
+ visibility_value_id => \&_check_visibility_value_id,
};
use constant CLASS_MAP => {
@@ -186,9 +189,12 @@ sub remove_from_db {
ThrowUserError("fieldvalue_still_has_bugs",
{ field => $self->field, value => $self });
}
- if (my @vis_fields = @{ $self->controls_visibility_of_fields }) {
+ my $vis_fields = $self->controls_visibility_of_fields;
+ my $values = $self->controlled_values;
+ if (@$vis_fields || @$values) {
ThrowUserError('fieldvalue_is_controller',
- { value => $self, fields => [map($_->name, @vis_fields)] });
+ { value => $self, fields => [map($_->name, @$vis_fields)],
+ vals => $values });
}
$self->SUPER::remove_from_db();
}
@@ -260,12 +266,41 @@ sub controls_visibility_of_fields {
return $self->{controls_visibility_of_fields};
}
+sub visibility_value {
+ my $self = shift;
+ if ($self->{visibility_value_id}) {
+ $self->{visibility_value} ||=
+ Bugzilla::Field::Choice->type($self->field->value_field)->new(
+ $self->{visibility_value_id});
+ }
+ return $self->{visibility_value};
+}
+
+sub controlled_values {
+ my $self = shift;
+ return $self->{controlled_values} if defined $self->{controlled_values};
+ my $fields = $self->field->controls_values_of;
+ my @controlled_values;
+ foreach my $field (@$fields) {
+ my $controlled = Bugzilla::Field::Choice->type($field)
+ ->match({ visibility_value_id => $self->id });
+ push(@controlled_values, @$controlled);
+ }
+ $self->{controlled_values} = \@controlled_values;
+ return $self->{controlled_values};
+}
+
############
# Mutators #
############
sub set_name { $_[0]->set('value', $_[1]); }
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
+sub set_visibility_value {
+ my ($self, $value) = @_;
+ $self->set('visibility_value_id', $value);
+ delete $self->{visibility_value};
+}
##############
# Validators #
@@ -312,6 +347,15 @@ sub _check_sortkey {
return $value;
}
+sub _check_visibility_value_id {
+ my ($invocant, $value_id) = @_;
+ $value_id = trim($value_id);
+ my $field = $invocant->field->value_field;
+ return undef if !$field || !$value_id;
+ my $value_obj = Bugzilla::Field::Choice->type($field)
+ ->check({ id => $value_id });
+ return $value_obj->id;
+}
1;
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm
index 7655b7619..b6bda167e 100644
--- a/Bugzilla/Install/DB.pm
+++ b/Bugzilla/Install/DB.pm
@@ -88,6 +88,9 @@ sub update_fielddefs_definition {
$dbh->bz_add_column('fielddefs', 'visibility_field_id', {TYPE => 'INT3'});
$dbh->bz_add_column('fielddefs', 'visibility_value_id', {TYPE => 'INT2'});
+ $dbh->bz_add_column('fielddefs', 'value_field_id', {TYPE => 'INT3'});
+ $dbh->bz_add_index('fielddefs', 'fielddefs_value_field_id_idx',
+ ['value_field_id']);
# Remember, this is not the function for adding general table changes.
# That is below. Add new changes to the fielddefs table above this
@@ -539,6 +542,8 @@ sub update_table_definitions {
# 2008-09-07 LpSolit@gmail.com - Bug 452893
_fix_illegal_flag_modification_dates();
+ _add_visiblity_value_to_value_tables();
+
################################################################
# New --TABLE-- changes should go *** A B O V E *** this point #
################################################################
@@ -2907,7 +2912,25 @@ sub _initialize_workflow {
# Make sure the bug status used by the 'duplicate_or_move_bug_status'
# parameter has all the required transitions set.
- Bugzilla::Status::add_missing_bug_status_transitions();
+ my $dup_status = Bugzilla->params->{'duplicate_or_move_bug_status'};
+ my $status_id = $dbh->selectrow_array(
+ 'SELECT id FROM bug_status WHERE value = ?', undef, $dup_status);
+ # There's a minor chance that this status isn't in the DB.
+ $status_id || return;
+
+ my $missing_statuses = $dbh->selectcol_arrayref(
+ 'SELECT id FROM bug_status
+ LEFT JOIN status_workflow ON old_status = id
+ AND new_status = ?
+ WHERE old_status IS NULL', undef, $status_id);
+
+ my $sth = $dbh->prepare('INSERT INTO status_workflow
+ (old_status, new_status) VALUES (?, ?)');
+
+ foreach my $old_status_id (@$missing_statuses) {
+ next if ($old_status_id == $status_id);
+ $sth->execute($old_status_id, $status_id);
+ }
}
sub _make_lang_setting_dynamic {
@@ -3098,6 +3121,20 @@ sub _fix_illegal_flag_modification_dates {
print "$rows flags had an illegal modification date. Fixed!\n" if ($rows =~ /^\d+$/);
}
+sub _add_visiblity_value_to_value_tables {
+ my $dbh = Bugzilla->dbh;
+ my @standard_fields =
+ qw(bug_status resolution priority bug_severity op_sys rep_platform);
+ my $custom_fields = $dbh->selectcol_arrayref(
+ 'SELECT name FROM fielddefs WHERE custom = 1 AND type IN(?,?)',
+ undef, FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT);
+ foreach my $field (@standard_fields, @$custom_fields) {
+ $dbh->bz_add_column($field, 'visibility_value_id', {TYPE => 'INT2'});
+ $dbh->bz_add_index($field, "${field}_visibility_value_id_idx",
+ ['visibility_value_id']);
+ }
+}
+
1;
__END__
diff --git a/Bugzilla/Status.pm b/Bugzilla/Status.pm
index d32b6c354..289e17260 100644
--- a/Bugzilla/Status.pm
+++ b/Bugzilla/Status.pm
@@ -46,13 +46,10 @@ use constant SPECIAL_STATUS_WORKFLOW_ACTIONS => qw(
use constant DB_TABLE => 'bug_status';
-use constant DB_COLUMNS => qw(
- id
- value
- sortkey
- isactive
- is_open
-);
+# This has all the standard Bugzilla::Field::Choice columns plus "is_open"
+sub DB_COLUMNS {
+ return ($_[0]->SUPER::DB_COLUMNS, 'is_open');
+}
sub VALIDATORS {
my $invocant = shift;
diff --git a/editfields.cgi b/editfields.cgi
index e1c7d12c2..446753044 100644
--- a/editfields.cgi
+++ b/editfields.cgi
@@ -66,6 +66,7 @@ elsif ($action eq 'new') {
custom => 1,
visibility_field_id => scalar $cgi->param('visibility_field_id'),
visibility_value_id => scalar $cgi->param('visibility_value_id'),
+ value_field_id => scalar $cgi->param('value_field_id'),
});
delete_token($token);
@@ -110,6 +111,7 @@ elsif ($action eq 'update') {
$field->set_obsolete($cgi->param('obsolete'));
$field->set_visibility_field($cgi->param('visibility_field_id'));
$field->set_visibility_value($cgi->param('visibility_value_id'));
+ $field->set_value_field($cgi->param('value_field_id'));
$field->update();
delete_token($token);
diff --git a/editvalues.cgi b/editvalues.cgi
index 338245284..5b82cee01 100755
--- a/editvalues.cgi
+++ b/editvalues.cgi
@@ -112,10 +112,12 @@ if ($action eq 'add') {
if ($action eq 'new') {
check_token_data($token, 'add_field_value');
- my $created_value = Bugzilla::Field::Choice->type($field)->create(
- { value => scalar $cgi->param('value'),
- sortkey => scalar $cgi->param('sortkey'),
- is_open => scalar $cgi->param('is_open') });
+ my $created_value = Bugzilla::Field::Choice->type($field)->create({
+ value => scalar $cgi->param('value'),
+ sortkey => scalar $cgi->param('sortkey'),
+ is_open => scalar $cgi->param('is_open'),
+ visibility_value_id => scalar $cgi->param('visibility_value_id'),
+ });
delete_token($token);
@@ -180,6 +182,7 @@ if ($action eq 'update') {
$vars->{'value_old'} = $value->name;
$value->set_name($cgi->param('value_new'));
$value->set_sortkey($cgi->param('sortkey'));
+ $value->set_visibility_value($cgi->param('visibility_value_id'));
$vars->{'changes'} = $value->update();
delete_token($token);
$vars->{'message'} = 'field_value_updated';
diff --git a/js/field.js b/js/field.js
index fb8716872..daa390482 100644
--- a/js/field.js
+++ b/js/field.js
@@ -359,3 +359,124 @@ function handleVisControllerValueChange(e, args) {
YAHOO.util.Dom.addClass(field_container, 'bz_hidden_field');
}
}
+
+function showValueWhen(controlled_field_id, controlled_value,
+ controller_field_id, controller_value)
+{
+ var controller_field = document.getElementById(controller_field_id);
+ // Note that we don't get an object for the controlled field here,
+ // because it might not yet exist in the DOM. We just pass along its id.
+ YAHOO.util.Event.addListener(controller_field, 'change',
+ handleValControllerChange, [controlled_field_id, controlled_value,
+ controller_field, controller_value]);
+}
+
+function handleValControllerChange(e, args) {
+ var controlled_field = document.getElementById(args[0]);
+ var controlled_value = args[1];
+ var controller_field = args[2];
+ var controller_value = args[3];
+
+ var item = getPossiblyHiddenOption(controlled_field, controlled_value);
+ if (bz_valueSelected(controller_field, controller_value)) {
+ showOptionInIE(item, controlled_field);
+ YAHOO.util.Dom.removeClass(item, 'bz_hidden_option');
+ item.disabled = false;
+ }
+ else if (!item.disabled) {
+ YAHOO.util.Dom.addClass(item, 'bz_hidden_option');
+ if (item.selected) {
+ item.selected = false;
+ bz_fireEvent(controlled_field, 'change');
+ }
+ item.disabled = true;
+ hideOptionInIE(item, controlled_field);
+ }
+}
+
+/*********************************/
+/* Code for Hiding Options in IE */
+/*********************************/
+
+/* IE 7 and below (and some other browsers) don't respond to "display: none"
+ * on <option> tags. However, you *can* insert a Comment Node as a
+ * child of a <select> tag. So we just insert a Comment where the <option>
+ * used to be. */
+function hideOptionInIE(anOption, aSelect) {
+ if (browserCanHideOptions(aSelect)) return;
+
+ var commentNode = document.createComment(anOption.value);
+ aSelect.replaceChild(commentNode, anOption);
+}
+
+function showOptionInIE(aNode, aSelect) {
+ if (browserCanHideOptions(aSelect)) return;
+ // If aNode is an Option
+ if (typeof(aNode.value) != 'undefined') return;
+
+ // We do this crazy thing with innerHTML and createElement because
+ // this is the ONLY WAY that this works properly in IE.
+ var optionNode = document.createElement('option');
+ optionNode.innerHTML = aNode.data;
+ optionNode.value = aNode.data;
+ var old_node = aSelect.replaceChild(optionNode, aNode);
+}
+
+function initHidingOptionsForIE(select_name) {
+ var aSelect = document.getElementById(select_name);
+ if (browserCanHideOptions(aSelect)) return;
+
+ for (var i = 0; ;i++) {
+ var item = aSelect.options[i];
+ if (!item) break;
+ if (item.disabled) {
+ hideOptionInIE(item, aSelect);
+ i--; // Hiding an option means that the options array has changed.
+ }
+ }
+}
+
+function getPossiblyHiddenOption(aSelect, aValue) {
+ var val_index = bz_optionIndex(aSelect, aValue);
+
+ /* We have to go fishing for one of our comment nodes if we
+ * don't find the <option>. */
+ if (val_index < 0 && !browserCanHideOptions(aSelect)) {
+ var children = aSelect.childNodes;
+ for (var i = 0; i < children.length; i++) {
+ var item = children[i];
+ if (item.data == aValue) {
+ // Set this for handleValControllerChange, so that both options
+ // and commentNodes have this.
+ children[i].disabled = true;
+ return children[i];
+ }
+ }
+ }
+
+ /* Otherwise we just return the Option we found. */
+ return aSelect.options[val_index];
+}
+
+var browser_can_hide_options;
+function browserCanHideOptions(aSelect) {
+ /* As far as I can tell, browsers that don't hide <option> tags
+ * also never have a X position for <option> tags, even if
+ * they're visible. This is the only reliable way I found to
+ * differentiate browsers. So we create a visible option, see
+ * if it has a position, and then remove it. */
+ if (typeof(browser_can_hide_options) == "undefined") {
+ var new_opt = bz_createOptionInSelect(aSelect, '', '');
+ var opt_pos = YAHOO.util.Dom.getX(new_opt);
+ aSelect.removeChild(new_opt);
+ if (opt_pos) {
+ browser_can_hide_options = true;
+ }
+ else {
+ browser_can_hide_options = false;
+ }
+ }
+ return browser_can_hide_options;
+}
+
+/* (end) option hiding code */
diff --git a/js/util.js b/js/util.js
index feef8fe41..86924210c 100644
--- a/js/util.js
+++ b/js/util.js
@@ -219,3 +219,37 @@ function bz_valueSelected(aSelect, aValue) {
return false;
}
+/**
+ * Tells you where (what index) in a <select> a particular option is.
+ * Returns -1 if the value is not in the <select>
+ *
+ * @param aSelect The select you're checking.
+ * @param aValue The value you want to know the index of.
+ */
+function bz_optionIndex(aSelect, aValue) {
+ for (var i = 0; i < aSelect.options.length; i++) {
+ if (aSelect.options[i].value == aValue) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/**
+ * Used to fire an event programmatically.
+ *
+ * @param anElement The element you want to fire the event of.
+ * @param anEvent The name of the event you want to fire,
+ * without the word "on" in front of it.
+ */
+function bz_fireEvent(anElement, anEvent) {
+ // IE
+ if (document.createEventObject) {
+ var evt = document.createEventObject();
+ return anElement.fireEvent('on' + anEvent, evt);
+ }
+ // Firefox, etc.
+ var evt = document.createEvent("HTMLEvents");
+ evt.initEvent(anEvent, true, true); // event type, bubbling, cancelable
+ return !anElement.dispatchEvent(evt);
+}
diff --git a/skins/standard/global.css b/skins/standard/global.css
index 0c2c0c434..16406afba 100644
--- a/skins/standard/global.css
+++ b/skins/standard/global.css
@@ -406,9 +406,12 @@ div.user_match {
vertical-align: top;
}
-.bz_hidden_field {
+.bz_hidden_field, .bz_hidden_option {
display: none;
}
+.bz_hidden_option {
+ visibility: hidden;
+}
.calendar_button {
background: transparent url("global/calendar.png") no-repeat;
diff --git a/template/en/default/admin/custom_fields/cf-js.js.tmpl b/template/en/default/admin/custom_fields/cf-js.js.tmpl
index bf72ff998..6c5bbf626 100644
--- a/template/en/default/admin/custom_fields/cf-js.js.tmpl
+++ b/template/en/default/admin/custom_fields/cf-js.js.tmpl
@@ -32,6 +32,18 @@ var select_values = new Array();
];
[% END %]
+function onChangeType(type_field) {
+ var value_field = document.getElementById('value_field_id');
+ if (type_field.value == [% constants.FIELD_TYPE_SINGLE_SELECT %]
+ || type_field.value == [% constants.FIELD_TYPE_MULTI_SELECT %])
+ {
+ value_field.disabled = false;
+ }
+ else {
+ value_field.disabled = true;
+ }
+}
+
function onChangeVisibilityField() {
var vis_field = document.getElementById('visibility_field_id');
var vis_value = document.getElementById('visibility_value_id');
diff --git a/template/en/default/admin/custom_fields/create.html.tmpl b/template/en/default/admin/custom_fields/create.html.tmpl
index 17bd5bfdc..a2db4708b 100644
--- a/template/en/default/admin/custom_fields/create.html.tmpl
+++ b/template/en/default/admin/custom_fields/create.html.tmpl
@@ -75,7 +75,7 @@
<tr>
<th align="right"><label for="type">Type:</label></th>
<td>
- <select id="type" name="type">
+ <select id="type" name="type" onchange="onChangeType(this)">
[% FOREACH type = field_types.keys %]
[% NEXT IF type == constants.FIELD_TYPE_UNKNOWN %]
<option value="[% type FILTER html %]">[% field_types.$type FILTER html %]</option>
@@ -112,6 +112,28 @@
</select>
</td>
</tr>
+
+ <tr>
+ <td colspan="2">&nbsp;</td>
+ <th>
+ <label for="value_field_id">
+ Field that controls the values<br>
+ that appear in this field:
+ </label>
+ </th>
+
+ <td>
+ <select disabled="disabled" name="value_field_id" id="value_field_id">
+ <option></option>
+ [% FOREACH sel_field = Bugzilla.get_fields({ is_select => 1 }) %]
+ <option value="[% sel_field.id FILTER html %]">
+ [% sel_field.description FILTER html %]
+ ([% sel_field.name FILTER html %])
+ </option>
+ [% END %]
+ </select>
+ </td>
+ </tr>
</table>
<p>
<input type="hidden" name="action" value="new">
diff --git a/template/en/default/admin/custom_fields/edit.html.tmpl b/template/en/default/admin/custom_fields/edit.html.tmpl
index 3d7355a77..b6a8ae9bd 100644
--- a/template/en/default/admin/custom_fields/edit.html.tmpl
+++ b/template/en/default/admin/custom_fields/edit.html.tmpl
@@ -109,10 +109,32 @@
[% IF field.is_select %]
<tr>
<th>&nbsp;</th>
- <td colspan="3">
+ <td>
<a href="editvalues.cgi?field=[% field.name FILTER url_quote %]">Edit
legal values for this field</a>.
</td>
+
+ <th>
+ <label for="value_field_id">
+ Field that controls the values<br>
+ that appear in this field:
+ </label>
+ </th>
+
+ <td>
+ <select name="value_field_id" id="value_field_id">
+ <option></option>
+ [% FOREACH sel_field = Bugzilla.get_fields({ is_select => 1 }) %]
+ [% NEXT IF sel_field.id == field.id %]
+ <option value="[% sel_field.id FILTER html %]"
+ [% ' selected="selected"'
+ IF sel_field.id == field.value_field.id %]>
+ [% sel_field.description FILTER html %]
+ ([% sel_field.name FILTER html %])
+ </option>
+ [% END %]
+ </select>
+ </td>
</tr>
[% END %]
</table>
diff --git a/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl b/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
index 2389fb6ae..b215edf04 100644
--- a/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
+++ b/template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
@@ -62,7 +62,8 @@
<h2>Confirmation</h2>
[% IF value.is_default || value.bug_count || (value_count == 1)
- || value.controls_visibility_of_fields.size
+ || value.controls_visibility_of_fields.size
+ || value.controlled_values.size
%]
<p>Sorry, but the '[% value.name FILTER html %]' value cannot be deleted
@@ -121,6 +122,20 @@
[% END %]
</li>
[% END %]
+
+ [% IF value.controlled_values.size %]
+ <li>This value controls the visibility of the following values in
+ other fields:<br>
+ [% FOREACH controlled = value.controlled_values %]
+ <a href="editvalues.cgi?action=edit&field=
+ [%- controlled.field.name FILTER url_quote %]&value=
+ [%- controlled.name FILTER url_quote %]">
+ [% controlled.field.description FILTER html %]
+ ([% controlled.field.name FILTER html %]):
+ [%+ controlled.name FILTER html %]</a><br>
+ [% END %]
+ </li>
+ [% END %]
</ul>
[% ELSE %]
diff --git a/template/en/default/admin/fieldvalues/create.html.tmpl b/template/en/default/admin/fieldvalues/create.html.tmpl
index bd6c427e6..f1eec1a5a 100644
--- a/template/en/default/admin/fieldvalues/create.html.tmpl
+++ b/template/en/default/admin/fieldvalues/create.html.tmpl
@@ -62,6 +62,27 @@
</td>
</tr>
[% END %]
+ [% IF field.value_field %]
+ <tr>
+ <th align="right">
+ <label for="visibility_value_id">Only appears when
+ [%+ field.value_field.description FILTER html %] is set to:
+ </label>
+ </th>
+ <td>
+ <select name="visibility_value_id" id="visibility_value_id">
+ <option></option>
+ [% FOREACH field_value = field.value_field.legal_values %]
+ [% NEXT IF field_value.name == '' %]
+ <option value="[% field_value.id FILTER none %]">
+ [%- field_value.name FILTER html -%]
+ </option>
+ [% END %]
+ </select>
+ <small>(Leave unset to have this value always appear.)</small>
+ </td>
+ </tr>
+ [% END %]
</table>
<input type="submit" id="create" value="Add">
<input type="hidden" name="action" value="new">
diff --git a/template/en/default/admin/fieldvalues/edit.html.tmpl b/template/en/default/admin/fieldvalues/edit.html.tmpl
index f12383866..b01415577 100644
--- a/template/en/default/admin/fieldvalues/edit.html.tmpl
+++ b/template/en/default/admin/fieldvalues/edit.html.tmpl
@@ -32,7 +32,9 @@
<table border="0" cellpadding="4" cellspacing="0">
<tr>
- <th valign="top"><label for="value_new">Field Value:</label></th>
+ <th valign="top" align="right">
+ <label for="value_new">Field Value:</label>
+ </th>
<td>
[% IF value.is_static %]
<input type="hidden" name="value_new" id="value_new"
@@ -56,6 +58,29 @@
<td>[% IF value.is_open %]Open[% ELSE %]Closed[% END %]</td>
</tr>
[% END %]
+ [% IF field.value_field %]
+ <tr>
+ <th align="right">
+ <label for="visibility_value_id">Only appears when
+ [%+ field.value_field.description FILTER html %] is set to:
+ </label>
+ </th>
+ <td>
+ <select name="visibility_value_id" id="visibility_value_id">
+ <option></option>
+ [% FOREACH field_value = field.value_field.legal_values %]
+ [% NEXT IF field_value.name == '' %]
+ <option value="[% field_value.id FILTER none %]"
+ [% ' selected="selected"'
+ IF field_value.id == value.visibility_value.id %]>
+ [%- field_value.name FILTER html -%]
+ </option>
+ [% END %]
+ </select>
+ <small>(Leave unset to have this value always appear.)</small>
+ </td>
+ </tr>
+ [% END %]
</table>
<input type="hidden" name="value" value="[% value.name FILTER html %]">
diff --git a/template/en/default/bug/create/create.html.tmpl b/template/en/default/bug/create/create.html.tmpl
index 0b941cc35..089292603 100644
--- a/template/en/default/bug/create/create.html.tmpl
+++ b/template/en/default/bug/create/create.html.tmpl
@@ -642,13 +642,12 @@ function handleWantsAttachment(wants_attachment) {
</select>
[% IF sel.name == "bug_status" %]
- [% FOREACH controlled = select_fields.bug_status.controls_visibility_of %]
- <script type="text/javascript">
- showFieldWhen('[% controlled.name FILTER js %]',
- 'bug_status',
- '[% controlled.visibility_value.name FILTER js %]');
+ <script type="text/javascript">
+ <!--
+ [%+ INCLUDE "bug/field-events.js.tmpl"
+ field = select_fields.bug_status %]
+ //-->
</script>
- [% END %]
[% END %]
</td>
[% END %]
diff --git a/template/en/default/bug/field-events.js.tmpl b/template/en/default/bug/field-events.js.tmpl
new file mode 100644
index 000000000..7cdf64687
--- /dev/null
+++ b/template/en/default/bug/field-events.js.tmpl
@@ -0,0 +1,36 @@
+[%# 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 the San Jose State
+ # University Foundation. Portions created by the Initial Developer
+ # are Copyright (C) 2008 the Initial Developer. All Rights Reserved.
+ #
+ # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
+ #%]
+
+[%# INTERFACE:
+ # field: a Bugzilla::Field object
+ #%]
+
+[% FOREACH controlled_field = field.controls_visibility_of %]
+ showFieldWhen('[% controlled_field.name FILTER js %]',
+ '[% field.name FILTER js %]',
+ '[% controlled_field.visibility_value.name FILTER js %]');
+[% END %]
+[% FOREACH legal_value = field.legal_values %]
+ [% FOREACH controlled_value = legal_value.controlled_values %]
+ showValueWhen('[% controlled_value.field.name FILTER js %]',
+ '[% controlled_value.name FILTER js %]',
+ '[% field.name FILTER js %]',
+ '[% legal_value.name FILTER js %]');
+ [% END %]
+[% END %]
diff --git a/template/en/default/bug/field.html.tmpl b/template/en/default/bug/field.html.tmpl
index 9b93a9ff2..d29aaa305 100644
--- a/template/en/default/bug/field.html.tmpl
+++ b/template/en/default/bug/field.html.tmpl
@@ -126,10 +126,20 @@
</option>
[% END %]
[% FOREACH legal_value = field.legal_values %]
+ [% SET control_value = legal_value.visibility_value %]
+ [% SET control_field = field.value_field %]
<option value="[% legal_value.name FILTER html %]"
- [%- " selected=\"selected\""
- IF value.contains(legal_value.name).size %]>
- [%- legal_value.name FILTER html %]</option>
+ [%# We always show selected values, even if they should be
+ # hidden %]
+ [% IF value.contains(legal_value.name).size %]
+ selected="selected"
+ [% ELSIF control_field && control_value
+ && !bug.${control_field.name}.contains(control_value.name)
+ %]
+ class="bz_hidden_option" disabled="disabled"
+ [% END %]>
+ [%- legal_value.name FILTER html -%]
+ </option>
[% END %]
</select>
[%# When you pass an empty multi-select in the web interface,
@@ -142,19 +152,19 @@
[% IF field.type == constants.FIELD_TYPE_MULTI_SELECT %]
<input type="hidden" name="defined_[% field.name FILTER html %]">
[% END %]
+
+ <script type="text/javascript">
+ <!--
+ initHidingOptionsForIE('[% field.name FILTER js %]');
+ [%+ INCLUDE "bug/field-events.js.tmpl" field = field %]
+ //-->
+ </script>
+
[% CASE constants.FIELD_TYPE_TEXTAREA %]
[% INCLUDE global/textarea.html.tmpl
id = field.name name = field.name minrows = 4 maxrows = 8
cols = 60 defaultcontent = value %]
[% END %]
-
- [% FOREACH controlled_field = field.controls_visibility_of %]
- <script type="text/javascript">
- showFieldWhen('[% controlled_field.name FILTER js %]',
- '[% field.name FILTER js %]',
- '[% controlled_field.visibility_value.name FILTER js %]');
- </script>
- [% END %]
[% ELSIF field.type == constants.FIELD_TYPE_TEXTAREA %]
<div class="uneditable_textarea">[% value FILTER wrap_comment(60)
FILTER html %]</div>
diff --git a/template/en/default/bug/knob.html.tmpl b/template/en/default/bug/knob.html.tmpl
index 038e6cbb3..be94559aa 100644
--- a/template/en/default/bug/knob.html.tmpl
+++ b/template/en/default/bug/knob.html.tmpl
@@ -122,16 +122,8 @@
['[% "is_duplicate" IF bug.dup_id %]',
'[% bug.bug_status FILTER js %]'] );
- [% FOREACH controlled = select_fields.bug_status.controls_visibility_of %]
- showFieldWhen('[% controlled.name FILTER js %]',
- 'bug_status',
- '[% controlled.visibility_value.name FILTER js %]');
- [% END %]
- [% FOREACH controlled = select_fields.resolution.controls_visibility_of %]
- showFieldWhen('[% controlled.name FILTER js %]',
- 'resolution',
- '[% controlled.visibility_value.name FILTER js %]');
- [% END %]
+ [% INCLUDE "bug/field-events.js.tmpl" field = select_fields.bug_status %]
+ [% INCLUDE "bug/field-events.js.tmpl" field = select_fields.resolution %]
</script>
[%# Common actions %]
diff --git a/template/en/default/global/messages.html.tmpl b/template/en/default/global/messages.html.tmpl
index d7de4fbc7..1a9c7220a 100644
--- a/template/en/default/global/messages.html.tmpl
+++ b/template/en/default/global/messages.html.tmpl
@@ -332,6 +332,17 @@
<li>Sortkey updated to
<em>[% changes.sortkey.1 FILTER html %]</em>.</li>
[% END %]
+ [% IF changes.visibility_value_id %]
+ [% IF value.visibility_value.defined %]
+ <li>It only appears when
+ [%+ value.field.value_field.description FILTER html %] is set to
+ '[%+ value.visibility_value.name FILTER html %]'.</li>
+ [% ELSE %]
+ <li>It now always appears, no matter what
+ [%+ value.field.value_field.description FILTER html %] is set to.
+ </li>
+ [% END %]
+ [% END %]
</ul>
[% ELSE %]
No changes made to the field value <em>[% value_old FILTER html %]</em>.
diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl
index 0936847f5..0b10b7396 100644
--- a/template/en/default/global/user-error.html.tmpl
+++ b/template/en/default/global/user-error.html.tmpl
@@ -412,7 +412,7 @@
[% ELSIF error == "field_control_must_be_select" %]
[% title = "Invalid Field Type Selected" %]
Only drop-down and multi-select fields can be used to control
- the visibility of other fields. [% field.description FILTER html %]
+ the visibility/values of other fields. [% field.description FILTER html %]
is not the right type of field.
[% ELSIF error == "field_invalid_name" %]
@@ -433,6 +433,11 @@
[% title = "Missing Name for Field" %]
You must enter a name for this field.
+ [% ELSIF error == "field_value_control_select_only" %]
+ [% title = "Invalid Value Control Field" %]
+ Only Drop-Down or Multi-Select fields can have a field that
+ controls their values.
+
[% ELSIF error == "fieldname_invalid" %]
[% title = "Specified Field Does Not Exist" %]
The field '[% field.name FILTER html %]' does not exist or
@@ -446,8 +451,21 @@
[% ELSIF error == "fieldvalue_is_controller" %]
[% title = "Value Controls Other Fields" %]
You cannot delete the '[% value.name FILTER html %]' value for this
- field because it controls the visibility of the following other fields:
- [%+ fields.join(', ') FILTER html %].
+ field because
+ [% IF fields.size %]
+ it controls the visibility of the following fields:
+ [%+ fields.join(', ') FILTER html %].
+ [% END %]
+ [% ' and ' IF fields.size AND vals.size %]
+ [% IF vals.size %]
+ it controls the visibility of the following field values:
+ <ul>
+ [% FOREACH val = vals %]
+ <li>[% val.field.name FILTER html %]:
+ '[% val.name FILTER html %]'</li>
+ [% END %]
+ </ul>
+ [% END %]
[% ELSIF error == "fieldvalue_is_default" %]
[% title = "Specified Field Value Is Default" %]