Recentemente precisei fazer um select dinâmico em ajax usando a API de formulário do Drupal, inicialmente tinha feito um javascript que fazia a chamada para um path do tipo callback onde retornava em JSON os options do select e via javascript mesmo eu alterava o conteúdo dos selects. Porém quando eu submetia o formulário o Drupal dava erro dizendo que a opção escolhida éra inválida.
Isso porque o Drupal faz um cache do formulário assim que você o acessa, e quando submetido ele testa os valores informados com os possíveis valores existentes no cache e não permite que seja diferente, isso é uma proteção muito boa, pois evita que usuários altere uma opção do select utilizando firebug.
Para contornar isso tive que escrever uma função que no momento em que retorna as opções via JSON que serão adicionadas no select também altera formulário cacheado. Veja função artigo_getoptionsforselect.
Abaixo segue o código parcial do meu módulo para entendimento.
Inicialmente somente o primeiro select é populadol com as tags no nível 1.

Selects populados conforme o select pai.

O código um pouco mais detalhado pode ser acessado aqui.
Outro detalhe importante, os valores dos selects estão organizados de forma hierarquica em três níveis utilizando um vocabulário próprio ( id 1 ), também utilizamos algumas funções do módulo taxonomy, ex: taxonomy_get_tree.
<?php
// $Id$
# Aqui declaramos o path que será utilizado pelo jquery para pegar os valores para 2 selects dependentes.
function artigo_menu() {
$items = array();
$items['artigo/getoptions/%'] = array(
'page callback' => 'artigo_getoptionsforselect',
'page arguments' => array(2),
'access arguments' => array('view artigo'),
);
return $items;
}
# Aqui a função para que pega os valores para os sub-selects e atualiza o formulário cacheado e retorna o json.
function artigo_getoptionsforselect( $id ){
//get the key of cache
$cid = $_POST['form_build_id'];
//get the form cache
$cache = form_get_cache($cid, $form_state);
//rebuild the options available in selects, because theses values changed by javascript
$cache['artigo']['veiculo']['#options'] = artigo_options_for_select( 'veiculo' );
if( isset( $_POST['veiculo'] ) )
$cache['artigo']['caderno']['#options'] = artigo_options_for_select( 'caderno', $_POST['veiculo'] );
if( isset( $_POST['caderno'] ) )
$cache['artigo']['coluna']['#options'] = artigo_options_for_select( 'coluna', $_POST['caderno'] );
//set the rebuild fomr cache
$n = form_set_cache($cid, $cache, $form_state);
$childrens = taxonomy_get_children($id, 1, $key = 'tid');
$options[0] = array('tid' => 0, 'name' => t("Select a option"));
foreach( $childrens as $c){
$n += 1;
$options[$n]['tid'] = $c->tid;
$options[$n]['name'] = $c->name;
}
print drupal_json($options);
exit;
}
function artigo_options_for_select( $type, $parent = null ){
$options = array();
$options[0] = " ";
switch( $type ){
case 'veiculo':
$veiculos = taxonomy_get_tree(1, 0, -1, 1);
$options[0] = t("Selecione o veículo");
foreach( $veiculos as $v){
$options[$v->tid] = $v->name;
}
break;
case 'caderno':
$cadernos = taxonomy_get_tree(1, $parent, -1, 1);
$options[0] = t("Selecione o caderno");
foreach( $cadernos as $v){
$options[$v->tid] = $v->name;
}
break;
case 'coluna':
$colunas = taxonomy_get_tree(1, $parent, -1, 1);
$options[0] = t("Selecione a coluna");
foreach( $colunas as $v){
$options[$v->tid] = $v->name;
}
break;
}
return $options;
}
# Aqui implementei o hook_load para deixar os valores do select disponiveis sempre que o node é carregado..
function _artigo_load( &$node ){
//get info about article
$artigo = db_fetch_array(db_query('SELECT a.*, n.vid FROM {artigo} a
INNER JOIN {node} n ON ( n.nid = a.nid )
WHERE n.uid = %d AND n.nid = %d', $node->uid, $node->nid));
//get categories of taxonomia, vocabulary 2
$tags = taxonomy_node_get_terms_by_vocabulary($node, 1);
$tags_keys = array_keys( $tags);
if ($artigo) {
$info = array();
$info["artigo"]["veiculo"] = $tags[$tags_keys[0]];
$info["artigo"]["caderno"] = $tags[$tags_keys[1]];
$info["artigo"]["coluna"] = $tags[$tags_keys[2]];
return $info;
}
}
# Implementação do form API.
function artigo_form(&$node, $form_state) {
$type = node_get_types('type', $node);
drupal_add_js(drupal_get_path('module', 'artigo') . '/artigo.js');
# .... alguns outros fields aqui ......
$form['artigo']['veiculo'] = array(
'#type' => 'select',
'#title' => t('Veículo'),
'#options' => artigo_options_for_select( 'veiculo' ),
'#default_value' => (int) $node->artigo['veiculo']->tid,
'#description' => t('Veículo de publicação.'),
'#required' => true,
);
$form['artigo']['caderno'] = array(
'#type' => 'select',
'#title' => t('Caderno'),
'#options' => artigo_options_for_select( 'caderno', $node->artigo['veiculo']->tid ),
'#default_value' => (int) $node->artigo['caderno']->tid,
'#required' => false,
'#description' => t('Caderno de publicação.'),
);
$form['artigo']['coluna'] = array(
'#type' => 'select',
'#title' => t('Coluna'),
'#options' => artigo_options_for_select( 'coluna', $node->artigo['caderno']->tid ),
'#default_value' => (int) $node->artigo['coluna']->tid,
'#description' => t('Coluna de publicação.'),
'#required' => false,
);
return $form;
}
?>
Aqui você pode baixar o javascript que eu utilizei para fazer as requisições ajax e popular os seletcs.
relacionados: http://drupal-br.org/suporte/modulos/dinamic-ajax-select-form-api
Comentários
Boa dica. Essa é a API do D6
Boa dica.
Essa é a API do D6 ou 5?
drupal 6
Utilizei isso no drupal 6, mas acredito que funciona também para a versão 5.