In my self made modal/wizard form I have a problem/bug with the back button. If I press on the continue button at the start of the wizard and then the back button, then I am at the start of the wizard form. At this I should not see the back button any more. I am at the start of my wizard form.
If do not use the popup ajax modal form all is as it should be. Only in the popup ajax wizard form the back button is still there.
I have made a small patch which fixes my problem.
I also removed a line of code. There was a variable set that is not used any where else.

Comments

daffie’s picture

StatusFileSize
new880 bytes
daffie’s picture

Title: Back button appearing when it is not suppost to. » Back button appearing when it is not supposed to.
merlinofchaos’s picture

Status: Needs review » Needs work

Can you please post a wizard config that duplicates this? I have a LOT of wizard forms and none of them exhibit the behavior you speak of; it is the kind of thing I keep an eye out for.

The way the code is written, if $form_state['previous'] is set, then there absolutely should be a back button. The code that loops through $form_state['order'] does not have an obvious way to even get $form_state['previous'] to == $form_state['step']. So there's something else wrong here, I think.

daffie’s picture

What I what to do is that the first step in my wizard is selecting an action and depending on the selected action there are one or more steps.
The combination of the wizard, (popup)modal and a changeing number of steps is not working. If you have ideas how I can fix this. Please let me know.

merlinofchaos’s picture

I believe the way to fix that in your could would be that if you are changing the steps, you should actually change $form_info['order'] so that the wizard knows what things will actually be based upon current data.

merlinofchaos’s picture

Note that I'd need to see what you're actually doing to provide better advice. But for now I have a feeling the problem you're seeing has more to do with your code manipulating incorrectly than wizard itself having a bug. I'll revise this opinion once I get more data.

daffie’s picture

After selecting the action the $form_info['order'] is changed. With the regular wizard page all is good. With the wizard in the (ajax)modal the new $form_info['order'] is lost or is using a cached old version.
I can make a simple example that has the problem in it.

merlinofchaos’s picture

Status: Needs work » Active

Aha! Now that's getting us to the meat of the problem!

Let's see your example, please!

daffie’s picture

I have made a small module for my example. In my example I do two attempts to change the order.
To produce the bug: Open the wizard in the modal. Then in the wizard select action2.
What happens: The trail changes the calling form does not.

My code for modalwizard.module:

<?php

/**
 * Implements hook_menu().
 */
function modalwizard_menu() {
  $items['modalwizard'] = array(
    'title' => 'Operation',
    'page callback' => 'modalwizard_page',
    'access callback' => TRUE,
    'type' => MENU_NORMAL_ITEM,
  );
  $items['modalwizard/%ctools_js/action'] = array(
    'title' => 'Operation',
    'page callback' => 'modalwizard_wizard',
    'page arguments' => array(1),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );

  return $items;
}

/**
 * Page menu callback.
 */
function modalwizard_page() {
  ctools_include('modal');
  ctools_include('ajax');
  ctools_modal_add_js();

  $output = 'start: '.ctools_modal_text_button('wizard in modal', 'modalwizard/nojs/action', '');
  $output .= '<br><br>';
  $output .= 'start: '.l('wizard in page', 'modalwizard/nojs/action');
  return $output;
}

/**
 * Page menu callback.
 */
function modalwizard_wizard($js = NULL, $step = NULL) {
//dsm('modalwizard_wizard');
  if ($js) {
    ctools_include('modal');
    ctools_include('ajax');
  }

  $form_info = array(
    'id' => 'action',
    'path' => 'modalwizard/' . ($js ? 'ajax' : 'nojs') . "/action/%step",
    'show trail' => TRUE,
    'show back' => TRUE,
    'show cancel' => TRUE,
    'show return' => FALSE,
    'next callback' =>  'modalwizard_wizard_next',
    'finish callback' => 'modalwizard_wizard_finish',
    'cancel callback' => 'modalwizard_wizard_cancel',
    'forms' => array(
      'start' => array(
        'form id' => 'modalwizard_start'
      ),
      'action1_configure1' => array(
        'form id' => 'modalwizard_action1_configure1'
      ),
      'action2_configure1' => array(
        'form id' => 'modalwizard_action2_configure1'
      ),
      'action2_configure2' => array(
        'form id' => 'modalwizard_action2_configure2'
      ),
    ),
  );

  $object_id = 1;

  if (empty($step)) {
    // We reset the form when $step is NULL because that means they have
    // for whatever reason started over.
//dsm('modalwizard_cache_clear');
    modalwizard_cache_clear($object_id);
    $step = 'start';
  }

  // This automatically gets defaults if there wasn't anything saved.
  $object = modalwizard_cache_get($object_id);

  // Add the order
  $form_info['order'] = modalwizard_order($object->action);

  $form_state = array(
    'ajax' => $js,
    // Put our object and ID into the form state cache so we can easily find
    // it.
    'object_id' => $object_id,
    'object' => &$object,
  );

//dsm($form_info);

  // Send this all off to our form. This is like drupal_get_form only wizardy.
  ctools_include('wizard');
  $form = ctools_wizard_multistep_form($form_info, $step, $form_state);
  $output = drupal_render($form);

  // If $output is FALSE, there was no actual form.
  if ($js) {
    // If javascript is active, we have to use a render array.
    $commands = array();
    if ($output === FALSE || !empty($form_state['complete']) || !empty($form_state['cancel'])) {
      $commands[] = ctools_modal_command_dismiss();
    }
    else {
      $commands = ctools_modal_form_render($form_state, $output);
    }
    print ajax_render($commands);
    exit;
  }
  else {
    if ($output === FALSE || !empty($form_state['complete']) || !empty($form_state['cancel'])) {
      drupal_goto('modalwizard');
    }
    else {
      return $output;
    }
  }
}

/**
 * Returns the order for the selected action.
 */
function modalwizard_order($action = 'action1') {
  if ($action == 'action2') {
    return array(
      'start' => t('Choose action'),
      'action2_configure1' => t('Action2 Configure1'),
      'action2_configure2' => t('Action2 Configure2'),
    );
  }
  else {
    return array(
      'start' => t('Choose action'),
      'action1_configure1' => t('Action1 Configure1'),
    );
  }
}

// ---------------------------------------------------------------------------
// Wizard caching helpers.

/**
 * Store our little cache so that we can retain data from form to form.
 */
function modalwizard_cache_set($id, $object) {
  ctools_include('object-cache');
  ctools_object_cache_set('modalwizard', $id, $object);
}

/**
 * Get the current object from the cache, or default.
 */
function modalwizard_cache_get($id) {
  ctools_include('object-cache');
  $object = ctools_object_cache_get('modalwizard', $id);
  if (!$object) {
    // Create a default object.
    $object = new stdClass;
    $object->action = 'action1';
  }

  return $object;
}

/**
 * Clear the wizard cache.
 */
function modalwizard_cache_clear($id) {
  ctools_include('object-cache');
  ctools_object_cache_clear('modalwizard', $id);
}

/**
 * Handle the 'next' click on the add/edit pane form wizard.
 *
 * All we need to do is store the updated pane in the cache.
 */
function modalwizard_wizard_next(&$form_state) {
  modalwizard_cache_set($form_state['object_id'], $form_state['object']);
}

/**
 * Handle the 'finish' click on teh add/edit pane form wizard.
 *
 * All we need to do is set a flag so the return can handle adding
 * the pane.
 */
function modalwizard_wizard_finish(&$form_state) {
  $form_state['complete'] = TRUE;
}

/**
 * Handle the 'cancel' click on the add/edit pane form wizard.
 */
function modalwizard_wizard_cancel(&$form_state) {
  $form_state['cancel'] = TRUE;
}


/**
 * Wizard start form. Choose an action.
 */
function modalwizard_start($form, &$form_state) {
  $form_state['title'] = t('Choose action');

  $form['action'] = array(
    '#title' => t('Choose action'),
    '#type' => 'radios',
    '#options' => array(
      'action1' => 'action1',
      'action2' => 'action2',
    ),
    '#default_value' => $form_state['object']->action,
    '#required' => TRUE,
  );

  return $form;
}

/**
 * Submit wizard start form.
 */
function modalwizard_start_submit(&$form, &$form_state) {
  $form_state['object']->action = $form_state['values']['action'];

  // Change the order
  $form_state['form_info']['order'] = modalwizard_order($form_state['values']['action']);

  // Change the next form
  $form_state['clicked_button']['#next'] = $form_state['values']['action'].'_configure1';

//dsm('modalwizard_start_submit');
//dsm($form_state);
}

/**
 * Wizard form action1 configure1.
 */
function modalwizard_action1_configure1($form, &$form_state) {
//dsm('modalwizard_action1_configure1');
  $form_state['title'] = t('Action 1 Configure 1');

  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Name your action1'),
    '#default_value' => isset($form_state['object']->name) ? $form_state['object']->name : '',
    '#required' => TRUE,
  );

  return $form;
}

/**
 * Submit wizard form action1 configure1.
 */
function modalwizard_action1_configure1_submit(&$form, &$form_state) {
  $form_state['object']->name = $form_state['values']['name'];
}

/**
 * Wizard form to action2 configure1.
 */
function modalwizard_action2_configure1($form, &$form_state) {
//dsm('modalwizard_action2_configure1');
  $form_state['title'] = t('Action 2 Configure 1');

  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Name your action2'),
    '#default_value' => isset($form_state['object']->name) ? $form_state['object']->name : '',
    '#required' => TRUE,
  );

  return $form;
}

/**
 * Submit wizard form action2 configure1.
 */
function modalwizard_action2_configure1_submit(&$form, &$form_state) {
  $form_state['object']->name = $form_state['values']['name'];
}
/**
 * Wizard form to action2 configure2.
 */
function modalwizard_action2_configure2($form, &$form_state) {
//dsm('modalwizard_action2_configure2');
  $form_state['title'] = t('Action 2 Configure 2');

  $form['amount'] = array(
    '#type' => 'textfield',
    '#title' => t('Amount for action2'),
    '#default_value' => isset($form_state['object']->amount) ? $form_state['object']->amount : '',
    '#required' => TRUE,
  );

  return $form;
}

/**
 * Submit wizard form action2 configure2.
 */
function modalwizard_action2_configure2_submit(&$form, &$form_state) {
  $form_state['object']->amount = $form_state['values']['amount'];
}

My code for modalwizard.info:

name = Modal Wizard Test
description = Modal wizard changing the order test.
core = 7.x
version = 1.x-dev
dependencies[] = ctools
MhueD’s picture

Version: 7.x-1.x-dev » 7.x-1.0
Priority: Normal » Major

This is still a problem: -please see http://drupal.org/node/1344708 ... which totally replicates on my core 7.14, CTools 7.x 1.0, build.

leolandotan’s picture

Issue summary: View changes

The patch in #1 fixed my issue on the Back button being displayed after coming from the next steps. Btw, Wont they apply this patch?

Thanks daffie!

daffie’s picture

@leolando.tan: I am happy that my patch solves your problem. I would love to get this patch committed. The problem is that somebody else needs to review this patch. If that somebody has reviewed this patch and comes to the conclusion that this patch is worth to be upgraded to status of RTBC (Reviewed and Tested By the Community), then the module maintainer can commit this patch. And I am hoping that you will be the one who will do the review. You will be helping the drupal community. The drupal community has lots of people who can help you.

leolandotan’s picture

Version: 7.x-1.0 » 7.x-1.5
Status: Active » Reviewed & tested by the community

Reproduced this issue on Drupal 7.34, CTools 7.x-1.5.
1. Enabled Chaos Tools (CTools) AJAX Example Example
2. Opened /ctools_ajax_sample
3. Clicked Wizard (default modal), Wizard (custom modal) and Wizard (button modal) and had no Back button on the first step
4. In these modal multi-step forms, I entered a value then clicked Continue
5. On the second and final step, I clicked Back and then on the first step which supposedly shouldn't have the Back button has it.
6. Clicked Back again and it loops through the steps.

Fix:
1. This patch fixed the issue by adding an additional condition if it's the right step to display the Back button.
2. This also took out a variable that has not been used anywhere in the code.

This also doesn't affect any other modules and Drupal core.

Note: This issue is only present for the modal version. The nojs version doesn't have this issue.

leolandotan’s picture

Oh my! I'm very sorry for that. I thought the re-test was just right away. -__-"

japerry’s picture

Assigned: Unassigned » merlinofchaos
Status: Reviewed & tested by the community » Needs review

If possible, I'd like Merlinofchaos to respond to the example.

damienmckenna’s picture

Version: 7.x-1.5 » 7.x-1.x-dev

sumthief’s picture

Looks like there is exists same problem.
I have other vision how to solve this problem (https://www.drupal.org/node/1344708#comment-11523645).

Attached patch here and close that problem as duplicate.

chris matthews’s picture

Assigned: merlinofchaos » Unassigned

The 2 year old patch in #19 to wizard.inc applied cleanly to the latest ctools 7.x-1.x-dev, but still needs to be reviewed and tested.

japerry’s picture

Status: Needs review » Closed (outdated)

Drupal 7 is no longer supported, closing.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.