commit cac142cfa923b6829026cfb6b67e6f765699f94e
Author: Jun Omae <jun66j5@gmail.com>
Date:   Mon Sep 24 00:00:34 2018 +0900

    Optimize DOM operations (#9606)

diff --git a/dynamicfieldsplugin/trunk/dynfields/htdocs/dynfields.js b/dynamicfieldsplugin/trunk/dynfields/htdocs/dynfields.js
index 28f1df4d8..11e94ec2e 100755
--- a/dynamicfieldsplugin/trunk/dynfields/htdocs/dynfields.js
+++ b/dynamicfieldsplugin/trunk/dynfields/htdocs/dynfields.js
@@ -39,13 +39,14 @@ jQuery(function ($) {
   };
 
   window.setup_triggers = function () {
-    if (window.dynfields_rules == undefined)
-      window.dynfields_rules = {};
+    var rules = window.dynfields_rules;
+    if (rules === undefined)
+      window.dynfields_rules = rules = {};
 
     for (var prop in triggers) {
       triggers[prop].selector = get_selector(prop);
       triggers[prop].forEach(function (spec) {
-        spec.rule = window.dynfields_rules[spec.rule_name];
+        spec.rule = rules[spec.rule_name];
       });
     }
   };
diff --git a/dynamicfieldsplugin/trunk/dynfields/htdocs/layout.js b/dynamicfieldsplugin/trunk/dynfields/htdocs/layout.js
index 0d7c8269c..8dd729fb1 100755
--- a/dynamicfieldsplugin/trunk/dynfields/htdocs/layout.js
+++ b/dynamicfieldsplugin/trunk/dynfields/htdocs/layout.js
@@ -18,33 +18,34 @@ var Layout = function (name) {
 
   // Returns true of the field needs its own row
   this.needs_own_row = function (field) {
-    var $field = jQuery('#field-' + field);
-    if ($field.length)
-      return $field.is('TEXTAREA');
-    return false;
+    var element = document.getElementById('field-' + field);
+    return element !== null && element.tagName.toLowerCase() === 'textarea';
   };
 
   // Update the field layout given a spec
   this.update = function (spec) {
     var this_ = this;
+    var name = this.name;
 
     // save original field order
-    if (window.dynfields_orig_field_order == undefined)
-      window.dynfields_orig_field_order = Object();
+    var orig_field_order = window.dynfields_orig_field_order;
+    if (orig_field_order === undefined)
+      window.dynfields_orig_field_order = orig_field_order = {};
 
-    if (window.dynfields_orig_field_order[this.name] == undefined) {
-      window.dynfields_orig_field_order[this.name] = [];
+    var field_order = orig_field_order[name];
+    if (field_order === undefined) {
+      orig_field_order[name] = field_order = [];
       jQuery(this.selector).each(function (i, e) {
         var field = this_.get_field($(this));
         if (field)
-          window.dynfields_orig_field_order[this_.name].push(field);
+          field_order.push(field);
       });
     }
 
     // get visible and hidden fields
     var visible = [];
     var hidden = [];
-    jQuery.each(window.dynfields_orig_field_order[this.name], function (i, field) {
+    jQuery.each(field_order, function (i, field) {
       var tx = this_.get_tx(field);
       if (tx.hasClass('dynfields-hide')) {
         hidden.push(field);
@@ -68,12 +69,12 @@ var Layout = function (name) {
     jQuery(this.selector).each(function (i, e) {
       var old_field = this_.get_field($(this));
       var old_slot = -1;
-      if (old_field.length)
+      if (old_field.length !== 0)
         old_slot = jQuery.inArray(old_field, new_fields);
       var new_field = new_fields[i];
 
       // check to allow *this* field be in its own row
-      if (i % 2 == 1 && old_field.length && this_.needs_own_row(new_field))
+      if (i % 2 === 1 && old_field.length !== 0 && this_.needs_own_row(new_field))
         skip_slot += 1;
 
       var new_slot = i + skip_slot;
@@ -107,9 +108,13 @@ inputs_layout.get_tx = function (field) {
 
 // get_field
 inputs_layout.get_field = function (td) {
-  var input = td.find(':input:first');
-  if (!input.length) return '';
-  return input.attr('id').slice(6);
+  var name = td.attr('data-dynfields-name');
+  if (!name) {
+    var input = td.find(':input:first');
+    name = input.length !== 0 ? input[0].id.slice(6) : '<missing>';
+    td.attr('data-dynfields-name', name);
+  }
+  return name !== '<missing>' ? name : '';
 };
 
 // move_field
@@ -125,16 +130,16 @@ inputs_layout.move_field = function (field, i) {
 
   // find correct column (tx) to insert field
   var col = 'col' + ((i % 2) + 1);
-  if (tr.find('th').length) {
+  var old_th = tr.find('th:first');
+  if (old_th.length !== 0) {
     if (col == 'col1') {
-      var old_th = tr.find('th:first');
-      if (old_th.get(0) != th.get(0)) { // don't move self to self
+      if (old_th[0] != th[0]) { // don't move self to self
         old_th.before(th);
         old_th.before(td);
       }
     } else {
       var old_td = tr.find('td:has(:input):last');
-      if (old_td.get(0) != td.get(0)) { // don't move self to self
+      if (old_td[0] != td[0]) { // don't move self to self
         old_td.after(td);
         old_td.after(th);
       }
@@ -168,7 +173,12 @@ header_layout.get_tx = function (field) {
 
 // get_field
 header_layout.get_field = function (th) {
-  return (th.attr('id') ? th.attr('id').slice(2) : '');
+  var name = th.attr('data-dynfields-name');
+  if (!name) {
+    var name = th.length !== 0 ? th[0].id.slice(2) : '<missing>';
+    th.attr('data-dynfields-name', name);
+  }
+  return name !== '<missing>' ? name : '';
 };
 
 // move_field
@@ -178,19 +188,19 @@ header_layout.move_field = function (field, i) {
 
   // find correct row (tr) to insert field
   var row = Math.round(i / 2 - 0.5); // round down
-  var tr = jQuery('#ticket').find('.properties tr:eq(' + row + ')');
+  var tr = jQuery('#ticket .properties').find('tr:eq(' + row + ')');
 
   // find correct column (tx) to insert field
-  if (tr.find('th').length) {
+  var old_th = tr.find('th:first');
+  if (old_th.length !== 0) {
     if (i % 2 == 0) {
-      var old_th = tr.find('th:first');
-      if (old_th.get(0) != th.get(0)) { // don't move self to self
+      if (old_th[0] != th[0]) { // don't move self to self
         old_th.before(th);
         old_th.before(td);
       }
     } else {
       var old_td = tr.find('td:last');
-      if (old_td.get(0) != td.get(0)) { // don't move self to self
+      if (old_td[0] != td[0]) { // don't move self to self
         old_td.after(td);
         old_td.after(th);
       }
diff --git a/dynamicfieldsplugin/trunk/dynfields/htdocs/rules.js b/dynamicfieldsplugin/trunk/dynfields/htdocs/rules.js
index ad03dbe09..28bddf5ad 100755
--- a/dynamicfieldsplugin/trunk/dynfields/htdocs/rules.js
+++ b/dynamicfieldsplugin/trunk/dynfields/htdocs/rules.js
@@ -9,7 +9,7 @@ var Rule = function (name) {
   this.query = function (spec) {}; // for query page
 
   // register this rule by adding to global variable
-  if (window.dynfields_rules == undefined)
+  if (window.dynfields_rules === undefined)
     window.dynfields_rules = {};
   window.dynfields_rules[name] = this;
 };
@@ -27,7 +27,7 @@ clearrule.apply = function (input, spec) {
 
   var target = spec.target;
 
-  if (spec.clear_on_change == undefined)
+  if (spec.clear_on_change === undefined)
     return;
 
   var field = jQuery('#field-' + target);
@@ -50,7 +50,7 @@ var copyrule = new Rule('CopyRule'); // must match python class name exactly
 
 // apply
 copyrule.apply = function (input, spec) {
-  var id = input.attr('id');
+  var id = input[0].id;
   if (spec.value === undefined ||
       !((id.endsWith('_reassign_owner') && spec.trigger == 'owner') ||
         id.slice(6) === spec.trigger))
@@ -92,7 +92,7 @@ var defaultrule = new Rule('DefaultRule'); // must match python class name exact
 
 // apply
 defaultrule.apply = function (input, spec) {
-  if (input.attr('id').slice(6) !== spec.trigger)
+  if (input[0].id.slice(6) !== spec.trigger)
     return;
 
   var $field = jQuery(get_selector(spec.target));
@@ -150,11 +150,12 @@ var hiderule = new Rule('HideRule'); // must match python class name exactly
 
 // setup
 hiderule.setup = function (input, spec) {
-  var id = input.attr('id');
+  var id = input[0].id;
   if (id) { // no input fields when on /query page
     // show and reset elements controlled by this input field
     var trigger = id.substring(6); // ids start with 'field-'
-    jQuery('.dynfields-' + trigger)
+    jQuery('#properties, #ticket .properties')
+      .find('.dynfields-' + trigger)
       .removeClass('dynfields-hide dynfields-' + trigger)
       .show();
   }
@@ -169,13 +170,13 @@ hiderule.apply = function (input, spec) {
   var clear_on_hide = spec.clear_on_hide.toLowerCase() == 'true';
   var hide_always = spec.hide_always.toLowerCase() == 'true'
   if (clear_on_hide || hide_always) {
-    th = jQuery('#h_' + spec.target);
+    th = jQuery('#h_' + target);
     td = th.next('td');
     td.addClass('dynfields-hide dynfields-' + trigger);
     th.addClass('dynfields-hide dynfields-' + trigger);
   }
 
-  if (input.attr('id').slice(6) !== spec.trigger)
+  if (input.attr('id').slice(6) !== trigger)
     return;
 
   // process hide rule
@@ -185,11 +186,12 @@ hiderule.apply = function (input, spec) {
   else
     v = input.val();
   var l = spec.trigger_value.split('|'); // supports list of trigger values
-  if ((jQuery.inArray(v, l) != -1 && spec.op == 'hide') ||
-    (jQuery.inArray(v, l) == -1 && spec.op == 'show')) {
-
+  var idx = jQuery.inArray(v, l);
+  if (idx !== -1 && spec.op === 'hide' || idx === -1 && spec.op === 'show') {
     // we want to hide the input fields's td and related th
-    var field = jQuery('#field-' + target + ',input[name="field_' + target + '"]');
+    var field = jQuery(
+        '#field-<target>, #properties input[name="field_<target>"]'
+        .replace(/<target>/g, target));
     var td = field.closest('td');
     var th = td.prev('th');
     var cls = 'dynfields-hide dynfields-' + trigger;
@@ -199,18 +201,19 @@ hiderule.apply = function (input, spec) {
     th.addClass(cls);
 
     // let's also clear out the field's value to avoid confusion
-    if (spec.clear_on_hide.toLowerCase() == 'true' &&
-      field.val() && field.val().length) { // Chrome fix - see #8654
-      if (field.attr('type') == 'checkbox') {
-        if (field.is(':checked')) {
-          field.removeAttr('checked').change();
+    if (spec.clear_on_hide.toLowerCase() == 'true') {
+      var oldval = field.val();
+      if (oldval && oldval.length) { // Chrome fix - see #8654
+        if (field.attr('type') == 'checkbox') {
+          if (field.is(':checked')) {
+            field.removeAttr('checked').change();
+          }
+        } else {
+          // only cascade rules if value changes
+          var newval = field.val('').val();
+          if (oldval != newval)
+            field.change(); // cascade rules
         }
-      } else {
-        // only cascade rules if value changes
-        var oldval = field.val();
-        var newval = field.val('').val();
-        if (oldval != newval)
-          field.change(); // cascade rules
       }
     }
   }
@@ -218,7 +221,7 @@ hiderule.apply = function (input, spec) {
 
 // complete
 hiderule.complete = function (input, spec) {
-  jQuery('.dynfields-hide').hide();
+  jQuery('#properties .dynfields-hide, #ticket .properties .dynfields-hide').hide();
 
   // update layout (see layout.js)
   inputs_layout.update(spec);
@@ -232,7 +235,7 @@ hiderule.complete = function (input, spec) {
         '    <a href="#no3" onClick="jQuery(\'.dynfields-link\').show(); jQuery(\'#dynfields-show-link\').remove();">Show hidden fields</a>' +
         '  </td>' +
         '</tr>';
-      jQuery('.dynfields-link:last').parents('tbody:first').append(html);
+      jQuery('.dynfields-link:last').closest('tbody').append(html);
     }
   }
 };
@@ -242,23 +245,24 @@ hiderule.query = function (spec) {
   // hide hide_always fields on /query page
   if (spec.hide_always.toLowerCase() == 'true') {
     // hide from columns section
+    var target = spec.target;
     var query_form = jQuery('form#query')
     var filters_fieldset = query_form.find('fieldset#filters');
-    filters_fieldset.find('input[value="' + spec.target + '"]')
+    filters_fieldset.find('input[value="' + target + '"]')
       .prop('checked', false)
       .parent().hide();
     // hide from and/or column filters/dropdowns
-    filters_fieldset.find('#add_filter option[value="' + spec.target + '"]') // trac <= 0.12.1
+    filters_fieldset.find('#add_filter option[value="' + target + '"]') // trac <= 0.12.1
       .hide();
-    filters_fieldset.find('.actions option[value="' + spec.target + '"]') // trac 0.12.2
+    filters_fieldset.find('.actions option[value="' + target + '"]') // trac 0.12.2
       .hide();
     var columns_fieldset = query_form.find('fieldset#columns');
-    columns_fieldset.find('input[value="' + spec.target + '"]')
+    columns_fieldset.find('input[value="' + target + '"]')
       .prop('checked', false)
       .parent().hide();
     // Hide from "Group results by" dropdown.
     var group_select = query_form.find('#group');
-    group_select.find('option[value="' + spec.target + '"]')
+    group_select.find('option[value="' + target + '"]')
       .hide();
   }
 };
@@ -272,8 +276,8 @@ var validaterule = new Rule('ValidateRule'); // must match python class name exa
 // setup
 validaterule.setup = function (input, spec) {
   var field = jQuery('#field-' + spec.target);
-  var submit = jQuery('input[name=submit]');
-  var form = submit.parents('form');
+  var form = jQuery('#propertyform');
+  var submit = form.find('input[name=submit]');
 
   // reset "alert only once per form submission"
   submit.click(function () {
@@ -339,7 +343,7 @@ var setrule = new Rule('SetRule'); // must match python class name exactly
 
 // setup
 setrule.setup = function (input, spec) {
-  if (spec.value == undefined)
+  if (spec.value === undefined)
     return;
 
   // ensure trigger field's new value is in spec's list
