3
\$\begingroup\$

TLDR: Rendering is too slow when having lots of custom fields and rules. So I need help to improve this javascript code.

#9606: Trac ticket about issue

I have a trac 1.0.8 installed on a ubuntu machine with DynamicFields 1.2.6 installed (compiled from latest revisions 0.11 folder). I have more than 100 custom fields with 30 ticket types. Each field has its own rule.

  • It takes too long to get response from js.
  • Disabling Rules doesn't seem to speed up the page loading.

I mainly think there are some inefficiencies in the js code.

/*
 * Layout 'class'
 */
var Layout = function (name) {
 this.name = name;
 // Selector for all field tds/ths
 this.selector = '';
 // Return the given field name's td/th element
 this.get_tx = function (field) {};
 // Return the given td/th element's field name
 this.get_field = function (tx) {};
 // Move a field's tds and ths to slot i
 this.move_field = function (field, i) {};
 // 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;
 };
 // Update the field layout given a spec
 this.update = function (spec) {
 var this_ = this;
 // save original field order
 if (window.dynfields_orig_field_order == undefined)
 window.dynfields_orig_field_order = Object();
 if (window.dynfields_orig_field_order[this.name] == undefined) {
 window.dynfields_orig_field_order[this.name] = [];
 jQuery(this.selector).each(function (i, e) {
 var field = this_.get_field($(this));
 if (field)
 window.dynfields_orig_field_order[this_.name].push(field);
 });
 }
 // get visible and hidden fields
 var visible = [];
 var hidden = [];
 jQuery.each(window.dynfields_orig_field_order[this.name], function (i, field) {
 var tx = this_.get_tx(field);
 if (tx.hasClass('dynfields-hide')) {
 hidden.push(field);
 } else {
 visible.push(field);
 }
 });
 // get new field order
 var new_fields = jQuery.merge(visible, hidden); // warning: side-effects!
 // order the fields
 this.order_fields(new_fields);
 };
 this.order_fields = function (new_fields) {
 var this_ = this;
 var skip_slot = 0;
 // determine which fields need to move and move 'em!
 jQuery(this.selector).each(function (i, e) {
 var old_field = this_.get_field($(this));
 var old_slot = -1;
 if (old_field.length)
 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))
 skip_slot += 1;
 var new_slot = i + skip_slot;
 // check if field is in the correct slot in the new order
 if (new_slot != old_slot && i < new_fields.length) {
 // wrong order!
 this_.move_field(new_field, new_slot);
 }
 // check to move *next* field to its own row
 if (old_field.length && this_.needs_own_row(new_field))
 skip_slot += 1;
 });
 }
};
/*
 * Inputs Layout implementation
 */
var inputs_layout = new Layout('inputs');
// selector
inputs_layout.selector = '#properties td[class!=fullrow]:parent';
// get_tx
inputs_layout.get_tx = function (field) {
 return jQuery('#field-' + field).closest('td');
};
// get_field
inputs_layout.get_field = function (td) {
 var input = td.find(':input:first');
 if (!input.length) return '';
 return input.attr('id').slice(6);
};
// move_field
inputs_layout.move_field = function (field, i) {
 var td = this.get_tx(field);
 var th = td.prev('th');
 // find correct row (tr) to insert field
 var row = Math.round(i / 2 - 0.5); // round down
 var $properties = jQuery('#properties');
 row += $properties.find('td.fullrow').length; // skip fullrows
 var tr = $properties.find('tr:eq(' + row + ')');
 // find correct column (tx) to insert field
 var col = 'col' + ((i % 2) + 1);
 if (tr.find('th').length) {
 if (col == 'col1') {
 var old_th = tr.find('th:first');
 if (old_th.get(0) != th.get(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
 old_td.after(td);
 old_td.after(th);
 }
 }
 } else {
 // no columns so just insert
 tr.append(th);
 tr.append(td);
 }
 // let's set col
 td.removeClass('col1 col2');
 th.removeClass('col1 col2');
 td.addClass(col);
 th.addClass(col);
};
/*
 * Header Layout implementation
 */
var header_layout = new Layout('header');
// selector
header_layout.selector = '#ticket .properties th:parent';
// get_tx
header_layout.get_tx = function (field) {
 return jQuery('#h_' + field);
};
// get_field
header_layout.get_field = function (th) {
 return (th.attr('id') ? th.attr('id').slice(2) : '');
};
// move_field
header_layout.move_field = function (field, i) {
 var th = this.get_tx(field);
 var td = th.next('td');
 // 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 + ')');
 // find correct column (tx) to insert field
 if (tr.find('th').length) {
 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
 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
 old_td.after(td);
 old_td.after(th);
 }
 }
 } else {
 // no columns so just insert
 tr.append(th);
 tr.append(td);
 }
};

Is there anyone who has knowledge about this subject?

Also here is my inspectation:

enter image description here

200_success
146k22 gold badges190 silver badges478 bronze badges
asked Aug 2, 2016 at 8:58
\$\endgroup\$
5
  • \$\begingroup\$ "I mainly think there are some inefficiencies in the js code." Are you sure, though? Have you profiled the code to see where the bottlenecks are or is it a guess? I'm not saying your wrong necessarily, but it'd be a huge fool's errand if it turns out the bottleneck is somewhere else entirely \$\endgroup\$ Commented Aug 2, 2016 at 12:37
  • \$\begingroup\$ You can look especially here: code \$\endgroup\$ Commented Aug 2, 2016 at 13:33
  • 1
    \$\begingroup\$ you could use .toArray() to convert a jQuery object to a plain javascript array, and then you can do a normal for loop or a forEach loop (just revert i and e in the callback). But I think you should take care of the functions that manipulate the DOM, instead. \$\endgroup\$ Commented Aug 2, 2016 at 14:38
  • \$\begingroup\$ Manipulating DOM is a great idea, but unfortunately my DOM is just short as it can be, I mean it is doing only what it has to do. So, I am stuck with this piece of code. Especially here: code \$\endgroup\$ Commented Aug 2, 2016 at 14:57
  • \$\begingroup\$ To avoid layout calculations try processing a detached deep-cloned container var work = src.cloneNode(true); ....... src.parentNode.replaceChild(work, src);. Although I'm not sure it makes any difference in case of jQuery and that layout library, but it might work in vanilla js. Also, building the entire html of the container as string and then using it to overwrite the source could work because there should be just one layout adjustment. \$\endgroup\$ Commented Aug 3, 2016 at 18:48

1 Answer 1

1
\$\begingroup\$

A few thoughts:

  • Define all your class methods on the object prototype. This will generally perform better than methods defined on the instance.
  • You currently store jQuery selector strings on your objects. Have you considered actually storing the jQUery collection that results from the selectors? If you are not dynamically adding/removing DOM elements from these collections, you can save yourself a lot of overhead by not having to re-query the DOM every time that you want to work with the collection again.
  • You spend a decent amount of time rendering and painting. There are ways to optimize CSS such that you reduce time spent in these areas. Take a look at this site - https://csstriggers.com/ - for a good reference on what CSS impacts are for different CSS properties across popular browsers in terms of when a layout, rendering, or painting change is triggered. You don't really show your CSS in this question, but there may be opportunity to improve overall speed by changing the way you specify things.
answered Aug 2, 2016 at 15:53
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.