How does one initiate active select fields with pre selected values?

robertDouglass - March 30, 2006 - 16:53
Project:Active Select
Version:HEAD
Component:Code
Category:support request
Priority:normal
Assigned:Unassigned
Status:active
Description

Sorry if I missed this in the documentation or elsewhere. When I create an element that is the target of another element, how can I give the target element a default value? The behavior seems to be that upon loading the page, the elements initialize themselves down the chain, thus wiping out the preset values. In fact, I see the value that I set as #default_value for a split second before the parent clobbers it. Thanks!

#1

Jaza - March 30, 2006 - 21:58

You can give a target element default values by setting some of the options to 'selected' from the very start. Activeselect is currently designed such that the AJAX mechanism fires not only when you change the selected values (in the source element), but also when the page loads.

The way that I make pre-selected values in category, is by using the 'extra' activeselect callback parameter. How you use this parameter is up to you: you can see how it's done in the category_activeselect() implementation, but that's kind of a complex example.

If you could explain what you're trying to do (perhaps with a diagram and/or a demonstration), I might be able to help you more.

#2

robertDouglass - March 31, 2006 - 08:30

I've got a multi-page form and the active select elements appear on each page. Now, if you make selections on A with target B, make a selection in B, and then move to page 2, the page loads correclty (A and B have correct values) and then A nukes B as it fires its onload events. I'll look into your suggestions. Thanks.

#3

robertDouglass - April 4, 2006 - 13:42

So I've managed to answer the question, partially, which generates a refinement of the original question.

Given activeselect elements A and B, where B is the target of A, when you generate the AJAX result to populate B, you need code like this:

Code snippet 1: The AJAX function

<?php
   
foreach ($options as $key => $value) {
      if (
$key == $extra) {
       
$options[$key] .= '|selected';
      }
    }
?>

$extra, in this case, comes from something that you stick into the form element for A:

Code snippet 2: the form element

<?php
  $form
['mms']['manufacturer'] = array(
   
'#type' => 'select',
   
'#title' => t('Manufacturer'),
   
'#default_value' => $extract['manufacturer'],
   
'#options' => $manufacturers,
   
'#required' => TRUE,
  );
  if (
$activeselect) {
   
$form['mms']['manufacturer']['#type'] = 'activeselect';
   
$form['mms']['manufacturer']['#activeselect_path'] = 'mms/get_models';
   
$form['mms']['manufacturer']['#activeselect_targets'] = 'model';
   
$form['mms']['manufacturer']['#activeselect_extra'] = $extract['model']; // <--- HERE
 
}
?>

This makes it so that the A form element loads in the browser using Code snippet 2, including the 'activeselect_extra' property, and initiates an AJAX request to populate B. Code snippet 1 returns the information needed to generate B and it uses the $extra variable to set which one should be selected.

Now, this leaves the question, how does one deal with C which is the target of B? Here is my complete AJAX callback function to generate B. Where in it do I define 'activeselect_extra' so that C is initialized with the correct value?

Code snippet 3: The full AJAX callback function

<?php
function mms_ajax_get_models($source, $targets, $string, $extra = NULL) {
  if (empty(
$source) || empty($targets) || empty($string)) {
    exit();
  }

 
// There is only 1 target in this example
 
$targets = explode(',', $targets);

 
// Initialize
 
$output = array();
 
 
// This gets the selected manufacturer values
 
$array = explode('||', $string);
  foreach (
$array as $key => $value) {
   
$match = explode('|', $value);
   
$array[$match[0]] = html_entity_decode($match[1]);
    unset(
$array[$key]);
  }
 
  foreach (
$targets as $target) {
   
$options = array();
  
    foreach (
$array as $key => $value) {
     
$options = mms_get_models($key);
    }
   
    foreach (
$options as $key => $value) {
      if (
$key == $extra) {
       
$options[$key] .= '|selected';
      }
    }
   
$output[$target] = activeselect_implode_array($options);
  }
 
activeselect_set_header_nocache();
 
  print
activeselect_implode_activeselect($output);
  exit();
}
?>

#4

Jaza - April 4, 2006 - 23:32

My feeling is that you're taking too direct an approach, by putting the value that you want selected (i.e. $extract['model']) directly into the '#activeselect_extra' attribute. You might get better results from using a bit more indirection, as I do in the category module.

With category_activeselect(), I put the node ID of the current category into the 'extra' attribute. Then the callback function takes this nid, and uses it to find all the current parents of that category. The code for making elements selected then looks like this:

<?php
foreach ($options as $key => $value) {
 
$options[$key] = str_replace('|', '&#124;', $options[$key]);
  if (
in_array($key, $parents)) {
   
$options[$key] .= '|selected';
  }
}
?>

I don't know if this is relevant to your problem, but I hope it helps in some way. Really, if you can get the default select value to work for A populating B, then it shouldn't be that different getting it to work for B populating C.

#5

robertDouglass - April 5, 2006 - 05:02

I think there are two blocking issues that prevent me from taking your approach. Let me describe the use case that I'm working on just so you know my background. I'm writing a widget that lets one assign multiple categories from a taxonomy that has three levels (thus A, B and C), and has tens of thousands of terms. Furthermore, the process of assigning must be iterative; the user will assign perhaps a hundred terms to one node, spanning the far reaches of the taxonomy. So the workflow is to select A, B, C, press "Add category", and then select A, B and C again. This gets repeated until all 100 terms are selected, and then the node gets saved.

So the first block is that I can't just pass the node id along because there isn't one at this point (node creation), and even if there were, it wouldn't help me much when changing the categorization. I *must* populate C based on what gets selected in B, and this is irrelevant of any node information.

The second block is that I don't build a complete B and C widget each time. This would be far too expensive. Instead, I build B only with those terms that are possible with the selection made in A, and use the SKIP_DANGEROUS_CHECK flag to handle validation myself. LIkewise, I build C based on the selection made in B.

Somehow, it must be possible in the AJAX callback function to say what the activeselect_extra attribute is. I imagine it must be something along the lines of this: $options[$key] .= '|selected'; . Here you have built a mechanism for passing the selected information along in the AJAX response. This correlates to the #default_value attribute of the form. What I need to know is whether there is a similar mechanism for passing the #activeselect_extra attribute as well?

#6

Jaza - April 5, 2006 - 06:10

There is currently no way to update the value of '#activeselect_extra' through the AJAX callback mechanism. The API is designed to let you update the target elements, not the source element - and '#activeselect_extra' is part of the source element.

Just to clarify: is it the extra attribute in the source element, or the extra attribute in the target element(s) that you want to update?

Adding support for this to the API shouldn't be too hard - if you're interested in doing so, please let me know, and feel free to submit any patches that you develop.

Also, regarding your comment that generating the full widget is too expensive: I have tested the activesleect implementation in the category module with a container holding 5,000+ categories, and it was able to load the page in an acceptable time (less than 15 seconds). This was relying on the category_get_tree() function, which is closely modelled on taxonomy_get_tree().

#7

robertDouglass - April 5, 2006 - 07:38

I'm trying to set the #activeselect_extra element on a target.

When I generate A, it is done in the main form function in PHP, thus setting the #activeselect_extra element for B is trivial and B appears with the right element selected. However, C which is a target of B, does not. The key information that I need, when building C, is which option has been selected for B, because that's how I'm building the list of options that are to appear in C:

<?php
   
foreach ($array as $key => $value) {
     
$options = mms_get_models($key);
    }
?>

As for taxonomy_get_tree, this function is completely unacceptable for building a three level hierarchy with tens of thousands of terms. The mms_get_models function, on the other hand, consists of almost nothing but a simple query:

SELECT id, name FROM {model} WHERE man_id = %d ORDER BY name ASC

Anyway, how I'm building A and B is working fine. It would also be fine if I had the option of not having the elements fire their AJAX events onpageload... perhaps this is the solution. Maybe I should just stop them from calling the AJAX replacement function when the page loads. Comments on that approach?

Thanks for your help!

#8

DayShallCome - November 13, 2006 - 06:10

Is there anyway to stop the javascript from firing on page load IF I have preset values (which is only true in some cases)?

#9

Fool2 - July 18, 2007 - 05:24

I commented out line 41 in activeselect.js. It says it's to make IE work but I can't see how-- IE is registering errors with or without it commented out. My pre-populated forms load a lot faster and don't glitch out-- the script tends to hang and you don't want your page glitching out after load.

#10

allentseng - September 16, 2008 - 08:24

if you have follow http://www.keyreels.com/drupal/locations.html like me

add
foreach ($options as $key => $value) {
if ($key == $extra) {
$options[$key]['selected'] .= 'selected';
}
}

#11

inforeto - July 1, 2009 - 09:50

For #8, an alternative to skip the firing on page load is to print drupal_to_js with preselected values.
(see http://drupal.org/node/190949#comment-1763372)

 
 

Drupal is a registered trademark of Dries Buytaert.