8

In Drupal 7, when you have node with field which has unlimited values (let's say, image field), "add another item" response time becomes very slow after you added 10 - 20 items. How do you fight this problem? Have you ever encountered this problem?

I created a project, where user can add up to 100 values of an image field which in theory has unlimited values setting. But, after you add a dozen of images, every new click on "Add another item" becomes slower than previous. I know this happens because of the fact, that Drupal rebuilds this field and all its values after each ajax request, so the more values you added the more work Drupal has to do on every "ajax" request, but, actually, this is not quite great thing.

Are there any approaches on how to change/override such a behaviour?

3

building on the answer by Charlie, I found that it takes about the same amount of time to reload the block if you are adding 1 or 100 items, so here is a trick to add a select list of numbers in the form next to 'add more' so you can choose how many you are adding. This saves a lot of time and is still flexible. Could be wrapped in to a little module

<?php
/**
* Implements hook_field_attach_form()
*/
function village_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode){
  $options = array('language' => field_valid_language($langcode));
  // Merge default options.
  $default_options = array(
    'default' => FALSE,
    'deleted' => FALSE,
    'language' => NULL,
  );
  $options += $default_options;
  list(, , $bundle) = entity_extract_ids($entity_type, $entity);
  $instances = _field_invoke_get_instances($entity_type, $bundle, $options);
  // Iterate through the instances.
  $return = array();
  foreach ($instances as $instance) {
    // field_info_field() is not available for deleted fields, so use
    // field_info_field_by_id().
    $field = field_info_field_by_id($instance['field_id']);
    $field_name = $field['field_name'];
    //If we are looking at our field type and specific widget type, and we are multiple entries
    if($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED){
      //Check just in case the button is here, and add another #submit function
      if(isset($form[$field['field_name']]['und']['add_more'])){
        // add a simple select list, this defaults to numb 3
        $form[$field['field_name']]['add_more_number'] = array(
          '#type' => 'select',
          '#title' => t('Add more no.'),
          '#options' => drupal_map_assoc(range(0, 50)),
          '#default_value' => 2,
        );
        $form[$field['field_name']]['und']['add_more']['#submit'][] = 'village_field_add_more_submit';
        $form[$field['field_name']]['und']['add_more']['#value'] = 'Add more rows';
      }
    }
  }
}
function village_field_add_more_submit($form, &$form_state){
  $button = $form_state['triggering_element'];
  // Go one level up in the form, to the widgets container.
  $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1));
  $field_name = $element['#field_name'];
  $langcode = $element['#language'];
  $parents = $element['#field_parents'];
  // Alter the number of widgets to show. items_count = 0 means 1.
  $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);
  //get the number from the select
  $numbtoadd = $form[$field_name]['add_more_number']['#value'];
  if($numbtoadd){
    $field_state['items_count'] += $numbtoadd;
    field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
    $form_state['rebuild'] = TRUE;
  }
}
?>

I also posted the suggestion on Drupal.org at https://drupal.org/node/1394184#comment-8252701 where the op had a similar issue.

  • I adapted the code above for a custom field with unlimited cardinality and it worked well for me. The only change I made to the core logic was to subtract 1 from $numbtoadd before using it. I think this is because items_count is underrepresented since it's zero-based? – Dave Bruns Jan 20 '14 at 19:06
2

It's blowback from the nature of the form API and how it makes the entire $form and $form_state available back on the server. This is a cool thing for a lot of reasons, although I agree that it can be quite annoying from a performance perspective. Some stats on an Ubuntu 12.04 server running Apache2 with PHP-FPM:

  • I added 30 items to a file field, adding and uploading 1 at a time, and the total time for the upload + server response + javascript insertion of the new element went from 414 milliseconds, increasing on each successive upload by anywhere from 0-20 milliseconds, ending up at 800 milliseconds for trip number 30.

  • I clicked "Add another item" for an unlimited text field 100 times, and the total time went from 337 milliseconds up to 1.3 seconds. If my form was more complex, these numbers would only increase.

In $form_state['fields']['your_field_name']['und'] exists a property called items_count. This is used to calculate the number of field widgets that should be displayed for a given field. I would recommend you use hook_field_attach_form() to alter the $form_state before the field's widget is constructed and set the field's items_count property to be a larger number, thus giving you the number of fields you need right away. The user will still be able to add more items. It would be up to you to find a better way to hide the extra items from making the form 10 pages long; perhaps a div with overflow: scroll; could work. Anyway, this may be a starting point for you to find something that allows your workflow to go faster:

function mymodule_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
  $form_state['field']['field_my_field'][$langcode]['items_count'] = 100;
}

Edit: The sample code is missing some logic to ensure it only runs for the appropriate form and won't allow you to 'add another item'. I will revise this when I have a better working example locally.

  • Hello Charlie, I thought about trick you described too, but things become worse, when you user wants to reorder fields (in my case, it is a vital requirement). When you try to reorder one of 100 fields via drag'n'drop, browser hangs forever... – Timur Kamanin Dec 4 '12 at 10:25
  • Does it hang only on re-ordering file fields or text fields as well? That seems odd, since draggable.js shouldn't be sending anything back to the server, but just listening for row changes and then updating hidden input fields. Also, what browser and version are you experiencing the hang in? I think anything we discover here could be useful to lots of other users. – Charlie Schliesser Dec 4 '12 at 14:18
  • Yes, if you have a reproducible use case of draggable.js hanging that calls for a core issue. – user49 Dec 4 '12 at 18:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.