Support

Account

Home Forums Backend Issues (wp-admin) JS performance issues Reply To: JS performance issues

  • Hi Elliot,
    This will be long one. I took on the performance issues related to page load once you manage to add several repeater field rows.
    In my test case (9 rows with nested repeater fields) it was taking over a minute for a page to unfreeze from JS routines.
    According to the JS profile I did in Chrome, majority of the JS time has been absorbed by acf.conditional_logic.refresh which is responsible for calling refresh_field for every conditional logic field. This is where the CPU time was eaten. In my case each refresh_field call took 0.2 to 0.5 seconds. Now multiply it by 255 items in CL array and you have your bottleneck. With every added repeater row this number was increasing.
    The culprit is here:
    var $targets = $('.field_key-' + item.field);
    This cycles through all fields of given key and does the rule checking for each of them. But you do it for every item in the CL array so almost all fields are processed several time redundantly.
    So the fix is to target only the particular field rather than all of the fields of given type. But it wasn’t as easy and straightforward as I initially expected. Due to the way you organised things in the first place I had to rewrite the logic in several places.

    Here is a walk-through of what I did (based on 4.3.4 code and including the fixes from my previous posts in this thread).

    First I changed the above mentioned line in refresh_field function to:
    var $targets = $('#'+item.field_id);
    but at this stage the item does not have the field_id element. So, I had find a way to include it.
    In acf-repeater/views/field.php after line 218 I’ve added the following lines:

    $sub_field['field_id'] = 'id_'.str_replace(array('[',']'), array('_', ''), $field['name'] . '_' . $i . '_' . $sub_field['key']);
    $attributes['id'] = $sub_field['field_id'];

    This resulted in id attribute being added to the TR element of the field. Also I have now field_id available in the $sub_field array which I can use later.
    I’ve commented out the check for for the index clone because with the new logic now I need the reference id being added to every field (otherwise new nested rows will not have the required info in them):

    //				if( $i !== 'acfcloneindex' )
    //				{
    //					$sub_field['conditional_logic']['status'] = 0;
    //					$sub_field['conditional_logic']['rules'] = array();
    //				}

    In acf/core/fields/_functions.php I’ve changed the last lines of create_field function to:

    // conditional logic
    if( $field['conditional_logic']['status'] )
    {
    	$field['conditional_logic']['field'] = $field['key'];
    	
    	?>
        <div class="condition" data-condition='<?php echo json_encode($field['conditional_logic']); ?>' data-field_id="<?=$field['field_id']?>"></div>
    	<?php
    }

    I couldn’t leave the inline JS there because jQuery append function will strip it out and nested row-clones will no longer have it. Now, field_id is saved in data attribute and I moved rules object to another data attribute.
    Because of the above changes I needed to create extra function which will fetch that data once the DOM is created. So, I’ve added acf.conditional_logic.get_items function in acf/js/input.min.js:

    get_items : function(context) {
        var _this = this;
        if (typeof(context) != "undefined") {
            conditions = $('.condition', context)
        } else {
            conditions = $('.condition')
        }
        conditions.each(function(){
            $condition = $(this).data('condition');
            $condition['field_id'] = $(this).data('field_id');
    
            if ($condition) {
                _this.items.push($condition);
            }
        })
    },

    This is rough and can be improved to not include data from row-clones. But performance impact is not that big anyway. This functions fetches all information from the data attributes which are added due to previously mentioned change.
    Now we need to call this function when needed. I do it
    once document is ready before the init function call:

    acf.conditional_logic.get_items();
    
    // conditional logic
    acf.conditional_logic.init();

    and when new repeater rows are being setup in ‘acf/setup_fields’ event handler:

    $element = $(el);
    $element.find('.repeater').each(function(){			
        acf.fields.repeater.set({ $el : $(this) }).init();			
    });
    acf.conditional_logic.get_items($element);

    In my case the edit page load time has been improved from 68 sec to 3.5 sec. The changes above also affected the response of the acf.conditional_login.change function. I hope you find this useful. Please review the suggested changes whether these break anything else and implement the fixes in next available release.
    If you have any questions as to why I did something one way and not the other then please let me know.
    Cheers.