New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

help: using AJAX in a field formatter? #607

Closed
bradfordcondon opened this Issue Aug 28, 2018 · 5 comments

Comments

Projects
None yet
2 participants
@bradfordcondon
Copy link
Member

bradfordcondon commented Aug 28, 2018

I'm trying to add a feature a simple feature to a field formatter: a select box which will render different stuff via AJAX.

Here's the relevant code:


      $element[0]['select'] = [
        '#type' => 'select',
        '#options' => $select,
        '#title' => 'Please select a map to view',
        '#default_value' => 0,
        '#ajax' => [
          'callback' => 'tripalmap_organism_featuremap_callback',
          'wrapper' => 'tripalmap-featuremap-organism-selector-wrapper',
          'effect' => 'fade',
        ],
      ];

      $selected = 0;

      if (isset($element['#values']['select_featuremap'])) {
        $selected = $element['#values']['select_featuremap'];
      }
      
      $element[0]['rendered_maps'] = [
        '#type' => 'fieldset',
        '#collapsible' => FALSE,
        '#prefix' => '<div id="tripalmap-featuremap-organism-selector-wrapper">',
        '#suffix' => '</div>',
      ];

      if ($selected != 0){
        $form = drupal_get_form('tripal_map_genetic_map_overview_form', $selected);
        $content = drupal_render($form);
        $element[0]['rendered_maps']['map'] = [
          '#type' => 'markup',
          '#markup' => $content,
        ];

      }
      return $element;
    }

here's the callback, which i placed in the main .module file.



function tripalmap_organism_featuremap_callback($form, &$form_state){
  ddl("it never fires");
  return $form[0];
}

As you can guess from the message, I never get the "it never fires" message logged.

Question 1, answered thanks to @laceysanderson :

One other thing to note, fields will only render the number of $elements as there are $items. So if you have only a single item, you can't have your select as 0 and your fieldset as 1. This has caught me many times! Instead you should have $element[0] = ['#type' => 'markup', '#tree' => TRUE]; then $element[0]['select'] and $element[0]['fieldset']

So next question, why wont the callback ever fire? I'm fairly certain this code would work if i were in a $form.

@bradfordcondon

This comment has been minimized.

Copy link
Member

bradfordcondon commented Aug 28, 2018

im writing this in an issue because i think AJAX enabled fields should go in a guide topic probably...

@spficklin

This comment has been minimized.

Copy link
Member

spficklin commented Aug 28, 2018

AJAX can be used in in field formatters. I use it in the tripal_network module to allow users to select a network module for viewing when they are viewing a Network page. It uses a select box to allow the end-user to select the module and then draw it.

To make AJAX work in a formatter you would have to

  1. Use a Drupal form which supports ajax. This makes sense because you have a select box which is a form element. In the code sample above you're adding a select box to a Drupal render array which probably (not 100% sure) does not ajaxify the element.
  2. Write your own JavaScript that calls ajax for you when the select box element changes.

I think the first route is easiest to go with and was the approach I used (https://github.com/tripal/tripal_network/blob/7.x-1.x/includes/TripalFields/sio__network_diagram/sio__network_diagram_formatter.inc)

@bradfordcondon

This comment has been minimized.

Copy link
Member

bradfordcondon commented Sep 6, 2018

i got this working by following stephen's suggestion number 1. Basically I need to know that elements are NOT forms.

I'm keeping this issue open as a reminder to add a super basic explanation to the dev guide's formatter/widget guide, and link to https://www.drupal.org/docs/7/api/render-arrays/render-arrays-overview

@bradfordcondon

This comment has been minimized.

Copy link
Member

bradfordcondon commented Sep 24, 2018

Hi all- i wrote a very simple guide but its probably not tripal core worthy. It can happily live on my blog, linked here. https://www.bradfordcondon.com/blog/

If anyone thinks it would be helpful let me know and ill reconsider...

@bradfordcondon

This comment has been minimized.

Copy link
Member

bradfordcondon commented Sep 24, 2018

full guide:

Introduction

Link: original GitHub issue

I found myself in a predicament: I wanted to include a dynamic element in my Tripal field's formatter.

However, I couldnt for the life of me get the AJAX callback to run in the formatter.

The problem: renderable arrays

Drupal has its special way of doing AJAX! You should read the documentation carefully!. To Drupal, AJAX only makes sense as on forms.

Here's the problem: formatters are not forms. Instead, they are renderable arrays! This is obvious in hindsight: rather than accepting $form and &$form_state, they accept &$element, $entity_type, $entity, $langcode, $items, $display, where $element is the renderable array.

This means if we want to add an AJAX callback, we actually need a seperate form file tahts get added in using drupal_get_form(). If we do this, we can build the AJAX as Drupal expects it.

the solution: drupal_get_form

Here's my form file: as you can see its a standard form following Drupal AJAX conventions. We provide a rendered_maps fieldset with the prefix defining the wrapper. The selector has specifies that wrapper, and the AJAX callback function tripalmap_organism_featuremap_callback. We then define that function to simply return the piece of the form that should be rebuilt: the rendered_maps fieldset!



function tripal_map_organism_featuremap_selector($form, &$form_state, $select) {

  $selected = 0;

  if (isset($form_state['values']['featuremap_select'])) {
    $selected = isset($form_state['values']['featuremap_select']);
  }


  $form['rendered_maps'] = [
    '#type' => 'fieldset',
    '#collapsible' => FALSE,
    '#prefix' => '<div id="tripalmap-featuremap-organism-selector-wrapper">',
    '#suffix' => '</div>',
  ];


  $form['rendered_maps']['featuremap_select'] = [
    '#type' => 'select',
    '#options' => $select,
    '#title' => 'Please select a map to view',
    '#default_value' => $selected,
    '#ajax' => [
      'callback' => 'tripalmap_organism_featuremap_callback',
      'wrapper' => 'tripalmap-featuremap-organism-selector-wrapper',
      'effect' => 'fade',
    ],
  ];


  $chosen = 0;

  if (isset($form_state['values']['featuremap_select'])) {
    $chosen = $form_state['input']['featuremap_select'];
  }

  if ($chosen != 0) {


    $mini_form = tripal_map_genetic_map_overview_form([], $form_state, $chosen);

    $form['rendered_maps']['map'] = $mini_form;

    return $form;
  }

  return $form;
}

/**
 * @param $form
 * @param $form_state
 *
 * @return mixed
 */
function tripalmap_organism_featuremap_callback($form, &$form_state) {

  return $form['rendered_maps'];
}

In the field formatter, we simply add this form and put the markup in the element:


      //multiple maps for this organism, let user select.  Create a special form for that so we can have an AJAX select box
      $select= $select + $select_add;

      $form = drupal_get_form('tripal_map_organism_featuremap_selector', $select);
      $content = drupal_render($form);
        $element[] = [
          '#type' => 'markup',
          '#markup' => $content,
        ];
        return $element;
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment