Hello.
Just starting out doing some actual drupal dev and I've got a real noob question. I'm trying to get a multi-select form field to redisplay it's values after it's been saved. This is for an admin settings page and it's nothing spectacular, just a learning exercise.

My Code:

function mymod_admin_settings()
{
     $admin_form=array();
     //Build an associated node type list, easy enough.
     $node_list = array();
     $result = db_query("SELECT type, name FROM {node_type} ");
     while($row = db_fetch_object($result))
     {
  	$node_list[$row->type]=$row->name;
     }
     //use node_list as options list
     $admin_form['mymod_nodetype'] = array(
         '#type' => 'select',
         '#title' => t('Select Node Types'),
         '#options' => $node_list,
         '#multiple' => TRUE,
         '#required' => TRUE,
         '#description' => t('Select the node types'),
   	  '#default' => variable_get('mymod_nodetype',NULL),
    );
    return system_settings_form($admin_form);
}

Up to this point, the menu hook and all that has worked perfectly. I get to this page and see the setting with no problems. I know that the settings are being saved because if I go: print_r(variable_get('mymod_nodetype',NULL)); I end up with the correctly valued variable, which is an array. But how do I get them to be highlighted since the '#default' doesn't seem to be picking it up?

Any help would be appreciated.
Many Thanks!

Comments

Tecktron’s picture

So I found out that when the array returns from it's saved state (the variable get array) it is saved in a way that it not the original array of [key]=>value, but it is remapped and saved as [key]=>key. So the key value is being used to store the key, where as I'm a more user friendly display as the value.

An example print_r() output of the aforementioned $form array:

Array
(
    [mymod_nodetype] => Array
        (
            [#type] => select
            [#title] => Select Node Types
            [#options] => Array
                (
                    [page] => Page
                    [news] => News
                    [blog] => Blog
                    [mycontenttype] => My Custom Content Type
                )

            [#multiple] => 1
            [#required] => 1
            [#description] => Select the node types
            [#default] => Array
                (
                    [news] => news
                    [mycontenttype] => mycontenttype
                )

        )
)

I assume that this is Drupal's default behavior since it is probably calling drupal_map_assoc on an already associated array. Why would it do this? I don't know. Annoying for trying to save a custom array of values. Is there a way around this to get this to work? I'm doing some more searching and if I find any answer I will post it. I personally think this is an overlooked bug, but I'm sure it's probably considered more of a 'feature'.

Your help is greatly appreciated.
Thanks.

Tecktron’s picture

So in further investigation, If I write a bit of code to fix the saved array to match the created options array, it still is not rendered correctly. The function I used for this was:

function fixsavedarray(&$item,$key,$list_array)
{
	if(array_key_exists($key,$list_array)){
		$item=$list_array[$key];
	}
}

//Then added this in to the aformentioned function
//right above the line: $admin_form['mymod_nodetype'] = array(

$selected_nodes= variable_get('mymod_nodetype',NULL);
array_walk($selected_nodes,'fixsavedarray',$node_list);

//Then replace '#default' in the $form array with:
'#default' => $selected_nodes

The arrays now match in [key]=>value, but it didn't solve anything.

Tecktron’s picture

So in a roundabout way of doing things I've made this work using the '#post_render' option in the form. I then use a search and replace on the $content element for where the value='[key]' and insert a selected='selected' where there is a match. Of course I've broken out the retrieval of the 2 arrays into a separate function and this is no longer a pretty as I'd hoped it would have been, but hopefully it helps someone out who's also having Drupal nightmares.

My final (working) code:

function mymod_fixsavedarray(&$item,$key,$list_array)
{
	if(array_key_exists($key,$list_array)){
		$item=$list_array[$key];
	}
}

function mymod_prender($content, $elements)
{
	$list=array();
	mymod_getlistarrays($list);
	foreach($list as $name => $arr){
		if(substr_count($name,'selected_')>0){
			foreach($arr as $key => $val){
				$find_value = '<option value="'.$key.'">'.$val.'</option>';
				$replace_value = '<option value="'.$key.'" selected="selected">'.$val.'</option>';
				$content = str_replace($find_value,$replace_value,$content);
			}
		}
	}
	return $content;
}

function mymod_getlistarrays(&$list)
{
        $node_list = array();
  	$result = db_query("SELECT type, name FROM {node_type} ");
        while($row = db_fetch_object($result))
  	{
  		$node_list[$row->type]=$row->name;
  	}

  	$selected_nodes = variable_get('mymod_nodetype',NULL);

  	array_walk($selected_nodes,'mymod_fixsavedarray',$node_list);

  	$list['nodes']=$node_list;
  	$list['selected_nodes']=$selected_nodes;
}


function mymod_admin_settings()
{
	$list=array();
	mymod_getlistarrays($list);

	$form['mymod_nodetype'] = array(
         '#type' => 'select',
         '#title' => t('Select Node Types'),
         '#options' => $list['nodes'],
         '#multiple' => TRUE,
 	 '#required' => TRUE,
         '#description' => t('Select the node types'),
   	 '#default_value' => $list['selected_nodes'],
   	 '#post_render' => array('mymod_prender'),
    );
  	return system_settings_form($form);
}

If anybody comes up with a better solution, please do tell.

I love answering my own questions, but hey at least I'm learning something and don't have to thank anybody for their help.

jaypan’s picture

#default should have been #default_value.

Contact me to contract me for D7 -> D10/11 migrations.

Tecktron’s picture

I did catch that, it was a typo from the copy/paste and change of mod name to protect the innocent. It made no difference, and I fixed it in last post, but the issue is(was) still the same.

prakashp’s picture

The form element definition should be

$admin_form['mymod_nodetype'] = array(
  '#type' => 'select',
  '#title' => t('Select Node Types'),
  '#default_value' => variable_get('mymod_nodetype',  array()),
  '#options' => $node_list,
  '#multiple' => TRUE,
  '#required' => TRUE,
  '#description' => t('Select the node types'),
);
jaypan’s picture

The order doesn't matter. That's the whole point of the keyed array.

Contact me to contract me for D7 -> D10/11 migrations.

prakashp’s picture

You're right, the order does not matter, though I tried the code I posted on my test site and it works.

Tecktron’s picture

I did try this, and you are correct, this does seem to work.

If you take the original code I posted, and place the default values first, it works.
I don't understand quite how (or why) Drupal is rendering this in such a fashion that it matters which order these are in, since the form itself is an associated array (the whole point of that type of array), but it does.
An explanation would be nice, but I've wasted enough of everybody's time (including my own) with this.

Here (again) is the full thing with the default values first. This is working.

function mymod_admin_settings()
{
     $admin_form=array();

     //Build an associated node type list, easy enough.
     $node_list = array();
     $result = db_query("SELECT type, name FROM {node_type} ");
     while($row = db_fetch_object($result))
     {
      $node_list[$row->type]=$row->name;
     }

     //use node_list as options list
     $admin_form['mymod_nodetype'] = array(
         '#type' => 'select',
         '#title' => t('Select Node Types'),
        //Note that here we add the default values from the stored variable FIRST
         '#default_values' => variable_get('mymod_nodetype',array()),
        //now we add our associated array of options
         '#options' => $node_list,
         '#multiple' => TRUE,
         '#required' => TRUE,
         '#description' => t('Select the node types'),
    );
    return system_settings_form($admin_form);
}

Wasn't that was a whole lot easier? I think it was. Thanks Prakash!

One thing to keep in mind is that the saved array, is still not an the original associated array, but an associated array of the array keys (see 1st comment).

Now to mention this in the form API for future users to learn.