diff options
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"> </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> </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" %] |