]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 308253: Ability to add select (enum) fields to a bug whose list of values depends...
authormkanat%bugzilla.org <>
Fri, 7 Nov 2008 17:34:39 +0000 (17:34 +0000)
committermkanat%bugzilla.org <>
Fri, 7 Nov 2008 17:34:39 +0000 (17:34 +0000)
Patch By Max Kanat-Alexander <mkanat@bugzilla.org> r=bbaetz, a=mkanat

22 files changed:
Bugzilla/DB/Schema.pm
Bugzilla/Field.pm
Bugzilla/Field/Choice.pm
Bugzilla/Install/DB.pm
Bugzilla/Status.pm
editfields.cgi
editvalues.cgi
js/field.js
js/util.js
skins/standard/global.css
template/en/default/admin/custom_fields/cf-js.js.tmpl
template/en/default/admin/custom_fields/create.html.tmpl
template/en/default/admin/custom_fields/edit.html.tmpl
template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
template/en/default/admin/fieldvalues/create.html.tmpl
template/en/default/admin/fieldvalues/edit.html.tmpl
template/en/default/bug/create/create.html.tmpl
template/en/default/bug/field-events.js.tmpl [new file with mode: 0644]
template/en/default/bug/field.html.tmpl
template/en/default/bug/knob.html.tmpl
template/en/default/global/messages.html.tmpl
template/en/default/global/user-error.html.tmpl

index 778db72c5c7d82832d86b5607a2f4c2be1d35f6f..ed1245d98b2a137923b03cd27e94fa9d732c81f4 100644 (file)
@@ -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 => [
index a5e380a11665a051061518ac9a6dc7aad3b2f9e3..17459cb2c8f4f44219e8395602ce89b0c9b6f261 100644 (file)
@@ -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
 
index 4da644f1dc3ce87cdb398f5c4e224997dce7d3ad..9e8fb123581e3032f74814dd12077c1b23a51dee 100644 (file)
@@ -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;
 
index 7655b7619101e024be637a6b9f6fa8f8a1226f98..b6bda167e7e1c80d552b527eae55b5e6562d6484 100644 (file)
@@ -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__
index d32b6c35454ae2080bca9c703678e9f721b70616..289e1726056add8f4533b98e1ab516dd795b9e31 100644 (file)
@@ -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;
index e1c7d12c2f7c36b012cf427657c0c61a387916e9..4467530441f9153bd2126f90deb5d0d24a4a81b8 100644 (file)
@@ -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);
index 33824528461f71ae27a3772b88e03acebfa60f7f..5b82cee01f11f0ec0fe5976dfad3bf2ae4bfd4af 100755 (executable)
@@ -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';
index fb8716872ec28d4158ced26187df0e2e12083b37..daa3904825b8115386bc56c131015a8a4164c620 100644 (file)
@@ -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 */
index feef8fe41110795ab4574caa4b5d441f31beddf4..86924210ca889c189576750160092c5fb1d1c78a 100644 (file)
@@ -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);
+}
index 0c2c0c434447f2e278a1ac1e6b6da12b0031cbd7..16406afba5d600ab505ae4ee4d50eaf63c66fbe6 100644 (file)
@@ -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;
index bf72ff99828f3d8de1867cecbf9ea73b7247f5de..6c5bbf626240c130eeeda23788c6ddd36fa2f707 100644 (file)
@@ -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');
index 17bd5bfdc86a3d32d0a66d4d05ffe25c032bd00c..a2db4708be807e6c1fab2776a6aa56515f5b8988 100644 (file)
@@ -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>
         </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">
index 3d7355a775261361311340a5038eccf4fb8cdfdd..b6a8ae9bd789babb5d9641854791b71be01044a5 100644 (file)
     [% 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>
index 2389fb6ae27973c960523ac890042c0d0c95c9ba..b215edf04d1ac1ccac6aa053b9b7050d55b96461 100644 (file)
@@ -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
         [% 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 %]
index bd6c427e6de28e18ce93cac2015559a98c5e9e1f..f1eec1a5a4a7c24d307680b7618e0beb406e7608 100644 (file)
         </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">
index f123838665725e8e59c2882cb37dbb177b6cb953..b014155774c0163e86c5d715da3a02a955647636 100644 (file)
@@ -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"
         <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 %]">
index 0b941cc3575b6b5deb36374ab47cb7d4aaac9772..0892926037715f50ccb320922ce5fb284b5eb094 100644 (file)
@@ -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 (file)
index 0000000..7cdf646
--- /dev/null
@@ -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 %]
index 9b93a9ff251207fbe20cf44d39d12e1311897dbd..d29aaa305c8491934ddd8ad1c9d0c62de9184106 100644 (file)
             </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,
         [% 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>
index 038e6cbb35cd2bc3092fb4658868ab17a402497f..be94559aa2da9d3cbd0ba4f3805f6f8d608a62e4 100644 (file)
                               ['[% "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 %]
index d7de4fbc72376fbbfe0e1da702a9909fc8ebd1b0..1a9c7220a58d2105d4fe23f6f74d33f226bbca67 100644 (file)
           <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>.
index 0936847f58eb553a93caf7984c1d6b2759f55e45..0b10b73965f30d8c75252f4765fd83b08b80967d 100644 (file)
   [% 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" %]
     [% 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 
   [% 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" %]