]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 463598: Improve the performance of the JavaScript that adjusts field values
authormkanat%bugzilla.org <>
Sun, 21 Jun 2009 19:34:33 +0000 (19:34 +0000)
committermkanat%bugzilla.org <>
Sun, 21 Jun 2009 19:34:33 +0000 (19:34 +0000)
based on the value of another field
Patch by Max Kanat-Alexander <mkanat@bugzilla.org> r=wicked, a=mkanat

Bugzilla/Field/Choice.pm
js/field.js
skins/standard/global.css
template/en/default/admin/fieldvalues/confirm-delete.html.tmpl
template/en/default/bug/field-events.js.tmpl
template/en/default/bug/field.html.tmpl
template/en/default/global/user-error.html.tmpl

index fe5c7bdcbb765b64531251d28c377a08facb1d20..f23b0c46d4de219674e5bcd53c931de21f5733fc 100644 (file)
@@ -199,7 +199,7 @@ sub _check_if_controller {
     my $self = shift;
     my $vis_fields = $self->controls_visibility_of_fields;
     my $values     = $self->controlled_values;
-    if (@$vis_fields || @$values) {
+    if (@$vis_fields || scalar(keys %$values)) {
         ThrowUserError('fieldvalue_is_controller',
             { value => $self, fields => [map($_->name, @$vis_fields)],
               vals => $values });
@@ -287,13 +287,13 @@ 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;
+    my %controlled_values;
     foreach my $field (@$fields) {
-        my $controlled = Bugzilla::Field::Choice->type($field)
-                         ->match({ visibility_value_id => $self->id });
-        push(@controlled_values, @$controlled);
+        $controlled_values{$field->name} = 
+            Bugzilla::Field::Choice->type($field)
+            ->match({ visibility_value_id => $self->id });
     }
-    $self->{controlled_values} = \@controlled_values;
+    $self->{controlled_values} = \%controlled_values;
     return $self->{controlled_values};
 }
 
@@ -431,4 +431,14 @@ The key that determines the sort order of this item.
 
 The L<Bugzilla::Field> object that this field value belongs to.
 
+=item C<controlled_values>
+
+Tells you which values in B<other> fields appear (become visible) when this
+value is set in its field.
+
+Returns a hashref of arrayrefs. The hash keys are the names of fields,
+and the values are arrays of C<Bugzilla::Field::Choice> objects,
+representing values that this value controls the visibility of, for
+that field.
+
 =back
index 629fb8a23b0bf8c357e2efb65e75b7bbf04c8d92..700c1de8dc81dbd7e8c32c84b41b90f1434389c6 100644 (file)
@@ -389,40 +389,52 @@ function handleVisControllerValueChange(e, args) {
     }
 }
 
-function showValueWhen(controlled_field_id, controlled_value, 
-                       controller_field_id, controller_value)
+function showValueWhen(controlled_field_id, controlled_value_ids
+                       controller_field_id, controller_value_id)
 {
     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]);
+        handleValControllerChange, [controlled_field_id, controlled_value_ids,
+                                    controller_field, controller_value_id]);
 }
 
 function handleValControllerChange(e, args) {
     var controlled_field = document.getElementById(args[0]);
-    var controlled_value = args[1];
+    var controlled_value_ids = 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');
+    var controller_value_id = args[3];
+
+    var controller_item = document.getElementById(
+        _value_id(controller_field.id, controller_value_id));
+
+    for (var i = 0; i < controlled_value_ids.length; i++) {
+        var item = getPossiblyHiddenOption(controlled_field,
+                                           controlled_value_ids[i]);
+        if (item.disabled && controller_item && controller_item.selected) {
+            item = 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);
         }
-        item.disabled = true;
-        hideOptionInIE(item, controlled_field);
     }
 }
 
+// A convenience function to generate the "id" tag of an <option>
+// based on the numeric id that Bugzilla uses for that value.
+function _value_id(field_name, id) {
+    return 'v' + id + '_' + field_name;
+}
+
 /*********************************/
 /* Code for Hiding Options in IE */
 /*********************************/
@@ -431,24 +443,50 @@ function handleValControllerChange(e, args) {
  * 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. */
+var ie_hidden_options = new Array();
 function hideOptionInIE(anOption, aSelect) {
     if (browserCanHideOptions(aSelect)) return;
 
     var commentNode = document.createComment(anOption.value);
-    aSelect.replaceChild(commentNode, anOption);
+    commentNode.id = anOption.id;
+    // This keeps the interface of Comments and Options the same for
+    // our other functions.
+    commentNode.disabled = true;
+    // replaceChild is very slow on IE in a <select> that has a lot of
+    // options, so we use replaceNode when we can.
+    if (anOption.replaceNode) {
+        anOption.replaceNode(commentNode);
+    }
+    else {
+        aSelect.replaceChild(commentNode, anOption);
+    }
+
+    // Store the comment node for quick access for getPossiblyHiddenOption
+    if (!ie_hidden_options[aSelect.id]) {
+        ie_hidden_options[aSelect.id] = new Array();
+    }
+    ie_hidden_options[aSelect.id][anOption.id] = commentNode;
 }
 
 function showOptionInIE(aNode, aSelect) {
-    if (browserCanHideOptions(aSelect)) return;
-    // If aNode is an Option
-    if (typeof(aNode.value) != 'undefined') return;
+    if (browserCanHideOptions(aSelect)) return aNode;
 
     // 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);
+    optionNode.id = aNode.id;
+    // replaceChild is very slow on IE in a <select> that has a lot of
+    // options, so we use replaceNode when we can.
+    if (aNode.replaceNode) {
+        aNode.replaceNode(optionNode);
+    }
+    else {
+        aSelect.replaceChild(optionNode, aNode);
+    }
+    delete ie_hidden_options[aSelect.id][optionNode.id];
+    return optionNode;
 }
 
 function initHidingOptionsForIE(select_name) {
@@ -465,26 +503,19 @@ function initHidingOptionsForIE(select_name) {
     }
 }
 
-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];
-            }
-        }
+function getPossiblyHiddenOption(aSelect, optionId) {
+    // Works always for <option> tags, and works for commentNodes
+    // in IE (but not in Webkit).
+    var id = _value_id(aSelect.id, optionId);
+    var val = document.getElementById(id);
+
+    // This is for WebKit and other browsers that can't "display: none"
+    // an <option> and also can't getElementById for a commentNode.
+    if (!val && ie_hidden_options[aSelect.id]) {
+        val = ie_hidden_options[aSelect.id][id];
     }
 
-    /* Otherwise we just return the Option we found. */
-    return aSelect.options[val_index];
+    return val;
 }
 
 var browser_can_hide_options;
index d36f88f4aa94cd694c072323208e338b41be789b..3e57654341644a565d9509ab488259aa3453da2b 100644 (file)
@@ -319,7 +319,7 @@ div#docslinks {
 
 /** End Comments **/
 
-.bz_default_hidden, .bz_tui_hidden {
+.bz_default_hidden, .bz_tui_hidden, .bz_hidden_field, .bz_hidden_option {
     /* We have !important because we want elements with these classes to always
      * be hidden, even if there is some CSS that overrides it (we use these
      * classes inside JavaScript to hide things). */
@@ -456,13 +456,6 @@ div.user_match {
     vertical-align: top;
 }
 
-.bz_hidden_field, .bz_hidden_option {
-    display: none;
-}
-.bz_hidden_option {
-    visibility: hidden;
-}
-
 .calendar_button {
     background: transparent url("global/calendar.png") no-repeat;
     width: 20px;
index b215edf04d1ac1ccac6aa053b9b7050d55b96461..64c24357d62cf9b56101ba754765eb1ce0cc2ed5 100644 (file)
     [% 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>
+       [% FOREACH field_name = value.controlled_values.keys %]
+         [% FOREACH controlled = value.controlled_values.${field_name} %]
+           <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 %]
        [% END %]
       </li>
     [% END %]
index 7cdf64687605b724f5fa14c32ba6c97a0bf61ead..06fba12450e5671407bd65e59705cabd0a794dbf 100644 (file)
                 '[% 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 %]',
+  [% FOREACH controlled_field = legal_value.controlled_values.keys %]
+    [% SET cont_ids = [] %]
+    [% FOREACH val = legal_value.controlled_values.$controlled_field %]
+      [% cont_ids.push(val.id) %]
+    [% END %]
+    showValueWhen('[% controlled_field FILTER js %]',
+                  [[% cont_ids.join(',') FILTER js %]],
                   '[% field.name FILTER js %]',
-                  '[% legal_value.name FILTER js %]');
+                  [% legal_value.id FILTER js %]);
   [% END %]
 [% END %]
index 68cc82a77d7ff65436667a3836d2454adf40073f..d02f9801bac2b9677a531d9552dc9817e1756723 100644 (file)
             [% SET control_value = legal_value.visibility_value %]
             [% SET control_field = field.value_field %]
             <option value="[% legal_value.name FILTER html %]"
+                    id="v[% legal_value.id FILTER html %]_
+                        [%- field.name FILTER html %]"
               [%# We always show selected values, even if they should be
                 # hidden %]
               [% IF value.contains(legal_value.name).size %]
               %]
                 class="bz_hidden_option" disabled="disabled"
               [% END %]>
-              [%- legal_value.name FILTER html -%]
-            </option>
+              [%- legal_value.name FILTER html %]</option>
           [% END %]
         </select>
         [%# When you pass an empty multi-select in the web interface,
index 58be9ea73f8bb7a7d853df842c1ac07fa44fa419..73b70106776f9c4204c6f50e3f0528569bf42ba6 100644 (file)
     [% 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>
+        [% FOREACH field_name = vals.keys %]
+          [% FOREACH val = vals.${field_name} %]
+            <li>[% val.field.name FILTER html %]:
+              '[% val.name FILTER html %]'</li>
+          [% END %]
         [% END %]
       </ul>
     [% END %]