When you use AHAH buttons to update (pieces of) a form, and the replaced markup (the data returned by the callback function) contains new AHAH buttons, the AHAH behaviors are not attached to these new buttons. This is caused because the ahah behavior uses Drupal.settings to loop through all AHAH buttons. While each AHAH button has a bunch of settings attached, using Drupal.settings is preferred over using $('.classname:not(..)').each(...., which is a more common way to attach behaviors.

If the AHAH does it's request, and the callback function is built, AHAH creates a new Drupal.settings object in the background, containing all (new) AHAH buttons.

Currently you need to call drupal_add_js() and add $javascript['setting'] to the response function's output in order to make this work.

How about rewriting the Drupal.settings object by default after each AHAH request?

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

skilip’s picture

Title: Allow AHAH to load Drupal.settings with the response data » Allow AHAH to reset Drupal.settings after loading response data
quicksketch’s picture

Status: Active » Needs review
FileSize
755 bytes

This is immensely important. We made provisions for this exact functionality in #360081: Pass Settings Locally to JS Behaviors. Now we just need to actually use it. However rather than recreating the global Drupal.settings.* after each AHAH request, we're taking the safer route of leaving the original variable in-tact (in case items on the page continue to be dependent on them), and passing a new settings variable locally to all the behaviors.

This patch simply makes ahah.js use this feature that we put in place for this exact purpose.

RobLoach’s picture

Issue tags: +JavaScript, +ahah

Hitting up some tagination.

sun’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -JavaScript, -ahah

This patch makes absolutely sense.

I've tested this patch manually by uploading/re-ordering files and running batches. All works fine.

Well, all works fine regarding this patch... apparently, each file upload via AHAH throws a load of PHP notices (also without this patch), not affecting the functionality, i.e. just silly variable usage without testing whether array keys actually exist. To be fixed elsewhere.

Ready to go.

sun’s picture

Issue tags: +JavaScript, +ahah

Didn't mean to remove issue tags.

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Great. Committed to CVS HEAD.

Does that mean we can clean-up some code in core?

quicksketch’s picture

Yes, some followup issues will probably be necessary. This particular issue was going to be blocking a File Field in core, since I needed an AHAH "Remove" button to show up after clicking the AHAH "Upload" button (so AHAH behaviors inside of returned HTML). There aren't any places in particular that *need* this functionality yet, but book.module might be a good candidate, or the drill-down menu selectors.

We'll probably finish off #399982: Simplify and standardize returning an AHAH response before beginning cleanup, since it should reduce a lot of duplicated code. Thanks Dries, I'll be posting what I have so far on the File Field issue soon.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

Pedro Lozano’s picture

Version: 7.x-dev » 6.x-dev
Status: Closed (fixed) » Needs review
FileSize
577 bytes

Why isn't this in D6? I just used the code from quicktabs module.

enikola’s picture

I see the solution proposed by quicksketch (#2) is in D7. My question is: are there any plans for this to be backported to D6?

quicksketch’s picture

Version: 6.x-dev » 7.x-dev
Status: Needs review » Closed (fixed)

It's not (and won't be) in Drupal 6 because it's an API change. We can't have modules all of a sudden requiring "Drupal 6.17 or higher", because they are dependent on this functionality. Anything that is written for Drupal 6 should work with all versions of it.

enikola’s picture

Version: 7.x-dev » 6.x-dev
Status: Closed (fixed) » Needs review

Unfortunately the patch isn't complete, drupal.js must also be patched. I'll be able to upload a patch for testing tomorrow.

edit: Oh, sorry, I should refresh the page before posting again.

I see, it makes sense. I think the patch would be still interesting for many people though. Where should I post it, in this thread or should I open a new one?

sun’s picture

Version: 6.x-dev » 7.x-dev
Status: Needs review » Closed (fixed)

You surely can attach further patches here, but please keep the issue closed. However, one of the previous follow-ups mentioned that a solution for this exists in the quicktabs module.

enikola’s picture

Here is a working patch against 6.16 that takes quicksketch (#2) approach for reattaching behaviours by providing new settings object rather than modifying the global one, in case other JS rely on it.

Example:

$js_settings = drupal_add_js(NULL);
drupal_json(array('status' => TRUE, 'data' => $output, 'settings' => call_user_func_array('array_merge_recursive', $js_settings['setting'])));
etisbew’s picture

I have applied the patch that you provided in the context below. see the code. I have no luck. I appreciate your further help. thanks.

Below is my code please check and let me know your suggestions.

function add_loyalty(){

global $user;

$period = _get_loyalty_programs();

// This is the event to change the list box values

$form['loyalty']['program'] = array(
'#type' => 'select',
'#default' => 0,
'#options' => $period,
'#ahah' => array(
'event' => 'change',
'path' => 'program/status_js',
'wrapper' => 'target',

)

);

//$pstatus = array(0 => 'Select Your Status', '1' => 'No Status','2'=>'Silver','3'=>'Gold','4'=>'Platinum','5'=>'Elite','6'=>'Executive Platinum','7'=>'Premier','8'=>'Premier Executive');

// Here render the list box dynamically

$form['loyalty']['pstatus'] = array(
'#type' => 'select',
'#title' => 'Program Status',
'#prefix' => '',
'#options'=>array(0 => 'Select Your Status'),
'#suffix' => ''

);

$lstatus = array(0 => '0 year', '1' => '1 year','2'=>'2 year','3'=>'3 year','4'=>'4 year','5'=>'5 year','6'=>'6 year','7'=>'7 year','8'=>'8 year','9'=>'9 year','10'=>'>=10 Years');
$form['loyalty']['lstatus'] = array(
'#type' => 'select',
'#title' => 'Length of current Status',
'#options' => $lstatus,

);

$fredumption = array('1' => '1 per year','2'=>'2 per year','3'=>'3 per year','4'=>'4 per year','5'=>'5 per year','6'=>'6 per year','7'=>'7 per year','8'=>'8 per year','9'=>'9 per year','10'=>'10 per year');
$form['loyalty']['fredumption'] = array(
'#type' => 'select',
'#title' => 'Frequency of redemption',
'#options' => $fredumption ,

);

$elength = array('1' => '1 year','2'=>'2 years','3'=>'3 years','4'=>'4 years','5'=>'5 years','6'=>'6 years','7'=>'7 years','8'=>'8 years','9'=>'9 years','10'=>'>=10 years');
$form['loyalty']['elength'] = array(
'#type' => 'select',
'#title' => 'Enrollment Length',
'#options' => $elength,

);

$form[loyalty]['uname'] = array(
'#type' => 'textfield',
'#title' => t('User Name'),
'#size' => 30,
'#maxlength' => 64,

);

$form[loyalty]['pword'] = array(
'#type' => 'password',
'#title' => t('Password'),
'#size' => 30,
'#maxlength' => 64,

);

$form[loyalty]['account_number'] = array(
'#type' => 'textfield',
'#title' => t('Account Number'),
'#size' => 30,
'#maxlength' => 64,

);

$form['#redirect'] = 'loyalty/flash';

$form['submit']=array(
'#type'=>'submit',
'#value'=>t('Add It')

);

return $form;

}

// Change program status list biox values dynamically with ahah interface

function program_status_js() {

$form_state = array('storage' => NULL, 'submitted' => FALSE);
$form_build_id = $_POST['form_build_id'];

// Get the form from the cache.
$form = form_get_cache($form_build_id, $form_state);
$args = $form['#parameters'];
$form_id = array_shift($args);

// We will run some of the submit handlers so we need to disable redirecting.
//$form['#redirect'] = FALSE;

// We need to process the form, prepare for that by setting a few internals
// variables.
$form['#post'] = $_POST;
$form['#programmed'] = $form['#redirect'] = FALSE;
$form_state['post'] = $_POST;

// Build, validate and if possible, submit the form.
drupal_process_form($form_id, $form, $form_state);

// This call recreates the form relying solely on the form_state that the
// drupal_process_form set up.

// Render the new output.
$replacement['#type'] = 'select';
$replacement['#title'] = 'Program Status';
$replacement['#id'] = 'edit-pstatus';
$replacement['#name'] = 'pstatus1';

$psquery = "SELECT node.nid AS nid,
node.title AS title,
node_data_field_program_status_value.field_program_status_value_value AS ps_value,
node_node_data_field_loyalty_programs.nid AS ps_nid,
node.title AS node_title
FROM node node
LEFT JOIN content_field_loyalty_programs node_data_field_loyalty_programs ON node.vid = node_data_field_loyalty_programs.vid
LEFT JOIN node node_node_data_field_loyalty_programs ON node_data_field_loyalty_programs.field_loyalty_programs_nid = node_node_data_field_loyalty_programs.nid
LEFT JOIN content_type_program_status node_data_field_program_status_value ON node.vid = node_data_field_program_status_value.vid
WHERE (node.type in ('program_status')) AND (node_data_field_loyalty_programs.field_loyalty_programs_nid = {$form_state['values']['program']})";

$psres = db_query($psquery);

$replacement['#options'][0]='Select Your Status';

while($row = db_fetch_object($psres)){
$replacement['#options'][check_plain(trim($row->nid))]=check_plain(trim($row->title));
}

/*$replacement['#attributes'] = array(
'id' => 'edit-pstatus',
'class' => 'ahah-processed',
'name' => 'pstatus',

);*/

//$replacement['#options'][1]='sample2';

$form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
$output = drupal_render($replacement);

form_set_error(NULL, '', TRUE); // remove CSS error styles
drupal_get_messages(); // remove all messages

// unset($choice_form['#prefix'], $choice_form['#suffix']); // Prevent duplicate wrappers.
//$output = drupal_render($choice_form);

drupal_json(array('status' => TRUE, 'data' => $output));
}

Thanks

duckzland’s picture

I'm facing similar problem countless of time while building a module, and seems the problem is fixed by including / calling quicktabs_ahah.js.

siramsay’s picture

Hello, sorry if I've missed something here but duckzland, how do you include or call quicktabs_ahah.js ?

thanks

siramsay’s picture

duckzland’s picture

use drupal_add_js() function and point the path to quicktabs_ahah.js or simply copy the quicktabs_ahah.js to the custom module and point drupal_add_js() there

siramsay’s picture

thanks, still stuck or something else is wrong

this is the add js I used drupal_add_js(drupal_get_path('module','MYMODULE') . 'quicktabs_ahah.js' );

I placed it at the beginning of the module. is that the correct place?

duckzland’s picture

if you copy the quicktabs_ahah.js to your module then :


drupal_add_js(drupal_get_path('module','MYMODULE') . '/quicktabs_ahah.js' );

should load the js, you can check in firebug to see if the js is loaded or not though.

siramsay’s picture

thanks!! that is what I had including the / before the quick tabs, firebug actually shows that it is loaded

here is the script I have, it is from this tutorial on how to add fields http://jamestombs.co.uk/2010-07-08/adding-additional-form-elements-using...

maybe something else wrong in the code? remove button?


//----------------------------------------------------Ahah code--------------------------------------------------------//
 
 ahah_helper_register($form, $form_state); // Absolutely vital for ahah_helper to do it's job   


   
  // We check to see if quantity has been set, ie if the form has been submitted yet.  If not, we show 1 field,you can change this to however many fields you want to show up as default.

    if (!isset($form_state['storage']['quantity'])) {
       $quantity = 1;
     }
    else {
        // If the form has been submitted we get the quantity that was stored.
        $quantity = $form_state['storage']['quantity'];
        
        // We then do a check to see if the user has clicked the "Add another" button, if they have we increase the amount by 1.
        if (isset($form_state['values']['guest']['add_more']) && $form_state['values']['op'] == 'Add another guest') {
        $quantity++;
        }
   }
 
// We create a hidden form element to store the amount of fields that the user has added.
  $form['quantity'] = array(
    '#type' => 'value',
    '#value' => $quantity,
  );

  $form['guest'] = array(
    '#type' => 'fieldset',
    '#title' => t('Guest(s)'),
    '#prefix' => '<div id="guest-wrapper">',
    '#suffix' => '</div>',
    '#tree' => TRUE // This is important for ahah_helper.
  );
  
// We now do a simple loop, creating however many textfields are defined by $form_state['storage']['quantity']
  for ($i = 1; $i <= $quantity; $i++) {
    

// The element name needs to be different for each textfield, otherwise we will only get one value after the form is submitted.
    $form['guest']['name_'. $i] = array(
      '#type' => 'textfield',
      '#title' => t('Name'),
      '#size' => 30,
      '#default_value' => $form_state['values']['guest']['name_'. $i],
    );
  
    $form['guest']['passport_'. $i] = array(
      '#type' => 'textfield',
      '#title' => t('Passport'),
      '#size' => 30,
      '#default_value' => $form_state['values']['guest']['passport_'. $i],
    );
    $form['guest']['birthday_'. $i] = array(
      '#type' => 'textfield',
      '#title' => t('Birthday'),
      '#size' => 30,
      '#default_value' => $form_state['values']['guest']['birthday_'. $i],
    );
	


//Remove button.
   if ($quantity > 1){	   
     $form['guest']['remove_'. $i] = array(
      '#type' => 'button',
      '#value' => t('Remove Guest'),
      '#ahah' => array(
      'event' => 'click', // When the button is "clicked", AHAH will do it's job
      'path' => ahah_helper_path(array('guest')), 
// The array features the wrapper form field. So our form wrapper is $form['guest'], so we set this to array('guest'). If your form was   $form['guest']['another_wrapper'], the path would be array('guest', 'another_wrapper').
 		'wrapper' => 'guest-wrapper', // We then define the wrapper which will be changed.
         ),
 
      ); 
    
    }

}
 
  
// We add in a button with the #ahah element which will handle all our work for us.
  $form['guest']['add_more'] = array(  
	'#type' => 'button',
    '#value' => t('Add another guest'),
    '#ahah' => array(
      'event' => 'click', // When the button is "clicked", AHAH will do it's job
      'path' => ahah_helper_path(array('guest')), 
// The array features the wrapper form field. So our form wrapper is $form['guest'], so we set this to array('guest'). If your form was   $form['guest']['another_wrapper'], the path would be array('guest', 'another_wrapper').
     'wrapper' => 'guest-wrapper', // We then define the wrapper which will be changed.
    ),
  );







//------------------------------------------------------------------------------------------------------


// Adds a simple submit button that refreshes the form and clears its contents -- this is the default behavior for forms.
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
   //  '#submit' => array('sendmail'),
  );

return $form;
}

wasiabbas’s picture

No need of patch to extend the drupal.settings object. just simply output the ahah response like this.


       $js_settings = drupal_add_js(NULL, NULL);
	$output .= '<script type="text/javascript">jQuery.extend(Drupal.settings,' . drupal_to_js(call_user_func_array('array_merge_recursive', $js_settings['setting'] )) . " );</script>\n";
	

but all this solution is not useful because the JavaScript array that has been built so far for $scope is returned by drupal_add_js() function. so the existing buttons path are generates, doesnt rebuild the js.
let me know if anyone has a solution to generate the fresh javascript array.

Thanks.