How to update block with step links based on AJAXified multipage entity form state? Brief

I'm using Entityform with Field group configured as Multipage with AJAX enabled for multipage groups provided by Field Group Ajaxified Multipage. So there are multiple steps and user can access (jump into) these steps through provided Multipage jumplist block.

See some sample images (to give you some visual idea):




Problem

The problem is that form is AJAX (dynamic) and jumplist block is static (which is unaware of form context and links doesn't work) with some basic jQuery code (so it's not reloaded when user goes to next pages).

As result this is what happens:


when field_group multipage is AJAX, the form validation works per step, but jumplist doesn't work (jQuery events are not attached to the links and other steps are not available as it needs to be loaded via AJAX),
when field_group multipage is not AJAX, the jQuery events works, so you can switch between pages, but the server-side validation doesn't work as nothing is submitted on Next button


but I need both to work (server-side validation and jumplist links).

Further info

See also the following related issue.

I'm thinking to do:


jQuery, but I'm not sure how to access the step the user is in,
rendering navigation block (or jumplist it-self) with the AJAX form (dirty and tricky), e.g.

// Implements hook_form_alter().
$block = module_invoke('multipage_jumplist', 'block_view', 'd07d74b8ad268f8c'); 
$rendered_block = theme('block', $block);
$theme_code = theme('multipage_jumplist', $foo);
$form['navigation'] = array('#markup' =&gt; $rendered_block);

not to use AJAX, but I need it for step-by-step server-side validation (FGAM+IFE),
create a session variable and pass to the JS (but the page won't be reloaded), or override jumplist theme hook, e.g.

// hook_form_alter - Get step we're in.
$step = empty($form_state['storage']['field_group_ajaxified_multipage_step']) ? 1 : $form_state['storage']['field_group_ajaxified_multipage_step'];
$curr_step = &amp;$_SESSION[end((arg())) . '-step'];
$curr_step = max($step, $curr_step);
// theme_multipage_jumplist_item - Parse the jumplist
$visited_steps = &amp;$_SESSION[end((arg())) . '-step'];
if ($variables['counter'] &lt;= $visited_steps) {
    // return either clickable item or not.
}

customize/patch the module (where to start),
find some alternative solution


Question

How do I alter/update my multipage jumplist block (unaware of form context) based on the current step user is in having AJAXified form?

By updating I mean either knowing which element is active, or have ability to extend the behaviour and disable further steps (from being clickable) which weren't visited yet.

What's your suggestion? What would be the Drupal-way?
This was implemented by patching/extending FGAM module and the following jQuery code:

/**
 * @file
 * Connect jumplist to their corresponding multipage panes.
 */
(function ($) {
  Drupal.behaviors.multipageJumplistFix = {
    step: 1,
    maxStep: 1,
    attach: function(context) {

      // Wrapper changes its id but it's id must be static!
      $('div[id^="foo-entityform-edit-form"][id$="form-ajax-wrapper"]').attr('id', 'foo-entityform-edit-form-form-ajax-wrapper');

      this.step = $('.multipage_step_change.active').attr('step');
      this.numSteps = $('.multipage-jumplist &gt; li').length;
      this.maxStep = Math.max (this.maxStep, this.step);

      this.updateActive ();

    },
    updateActive: function () {
      $('.multipage-jumplist a').removeClass('active');
      $('.multipage-jumplist a[step=' + this.step + ']').addClass ('active');

      for (var i = 1; i &lt;= this.numSteps; ++i) {
        if (i &gt; this.maxStep + 1)
          $('.multipage-jumplist a[step=' + i + ']').addClass ('disabled');
        else
          $('.multipage-jumplist a[step=' + i + ']').removeClass ('disabled');
      }
    }
  };

  $(function () {
    $('.multipage-jumplist').html($('.multipage-jumplist').html());

    $('.multipage-jumplist a').each(function (index, el, e) {
      el.step = index + 1;
      $(this).click (function (e) {
        console.log (this.step);
        if (!$(this).hasClass('disabled')) {
          $('.multipage_step_change[step=' + this.step + ']').trigger('mousedown');
          return false;
        }
      });
    });

    Drupal.behaviors.multipageJumplistFix.attach();
  });
})(jQuery);


which is loaded from the theme and then few enhancements in template.php:

/**
 * Implements hook_form_alter().
 */
function theme_form_alter(&amp;$form, &amp;$form_state, $form_id) {
  if (strpos($form_id, 'entityform_edit_form') !== FALSE) {
    drupal_add_js(drupal_get_path('theme', 'foo') .'/js/eforms.js');

    $step = empty($form_state['storage']['field_group_ajaxified_multipage_step']) ? 1 : $form_state['storage']['field_group_ajaxified_multipage_step'];
    $curr_step = &amp;$_SESSION[end((arg())) . '-step'];
    $curr_step = max($step, $curr_step);

    unset($form['actions']['prev']);
    unset($form['actions']['next']);

    if (!empty($form['#multipage_jumplist_instances'])) {
      $num_steps = count($form['#multipage_jumplist_instances'][0]['panes']);

      for ($i = 1; $i &lt;= $num_steps; ++$i) {
        $active = $step == $i;
        $is_prev = $i == ($step - 1);
        $is_next = $i == ($step + 1);
        $form['actions'][$is_prev ? 'prev' : ($is_next ? 'next' : ('step' . $i))] = array(
          '#type' =&gt; 'submit',
          '#value' =&gt; $is_next ? t('Continue') : ($is_prev ? t('Previous') : ('Step' . $i)),
          '#id' =&gt; 'step' . $i,
          '#attributes' =&gt; array('step' =&gt; $i, 'style' =&gt; (($is_prev || $is_next) ? '' : 'display: none'), 'class' =&gt; array('multipage_step_change', ($active ? 'active' : ''))),
          '#submit' =&gt; array('theme_multipage_step_change_submit'),
          '#ajax' =&gt; array(
            'wrapper' =&gt; 'foo-entityform-edit-form-form-ajax-wrapper',
            'callback' =&gt; '_field_group_ajaxified_multipage_form_callback',
          ),
        );
        if ($i &lt; $step) {
          // We don't want validation when going back.
          $form['actions']['step' . $i]['#limit_validation_errors'] = array();
        }
      }
    }

  }
}

function theme_multipage_step_change_submit(&amp;$form, &amp;$form_state) {
  $step = substr($form_state['triggering_element']['#id'], 4);

  $form_state['triggering_element']['#array_parents'] = array('actions', 'step' . $step);
  $form_state['triggering_element']['#parents']       = array('actions', 'step' . $step);

  _field_group_ajaxified_multipage_form_submit($form, $form_state);

  $form_state['rebuild'] = TRUE;
}


This can be also extended by theme overrides for: theme_multipage_jumplist() and theme_multipage_jumplist_item().

Hope that helps.