In questo post vediamo come creare dei form con campi multipli, aggiungibili dinamicamente. Per esempio, ho creato un semplice tipo di nodo, che definisce un contatto di rubrica telefonica, con nome, cognome e vari campi con occorrenze multiple.
Come prima cosa, contiamo il numero di campi da disegnare. (Il numero di campi definiti collegati al nodo, e non meno di due):
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * The hook_form() * @param $node * @param $form_state * @return unknown_type */ function sfnode_form(&$node, $form_state){ if (isset($form_state['contatti_count'])) { $contatti_count = $form_state['contatti_count']; } else { $contatti_count = max(2, empty($node->contatti) ? 2 : count($node->contatti)); } |
Poi aggiungiamo un wrapper per contenere tutti i campi aggiuntivi del form:
1 2 3 4 5 6 7 8 9 10 11 12 |
// Add a wrapper for the choices and more button. $form['contatti_wrapper'] = array( '#tree' => FALSE, '#weight' => 4, '#prefix' => '<div id="contatti-wrapper" class="clear-block">', '#suffix' => '</div>', ); $form['contatti_wrapper']['contatti'] = array( '#prefix' => '<div id="contatti">', '#suffix' => '</div>', '#theme' => 'sfnode_contatti', ); |
Quindi creiamo i vari campi "contatto" per tutti i record salvati nel nodo.
1 2 3 4 5 6 |
// Add the current choices to the form. for ($delta = 0; $delta < $contatti_count; $delta++) { $ctype = isset($node->contatti[$delta]['ctype']) ? $node->contatti[$delta]['ctype'] : 'none'; $cval = isset($node->contatti[$delta]['cval']) ? $node->contatti[$delta]['cval'] : ''; $form['contatti_wrapper']['contatti'][$delta] = _sfnode_contatto_form($delta, $ctype, $cval); } |
..e un pulsante per aggiungere nuovi campi.
L'array #ahah collegato al campo, indica vari parametri per definire come dev'essere ricreato il contenuto di un certo campo quando si verifica un dato evento sull'oggetto. In questo caso, al click, il contenuto di #contatti viene sostituito dall'html ritornato da una chiamata a sfnode/newfield. A questa chiamata, risponderà la funzione sfnode_newfield() che rigenera il form e restituisce via json la parte interessante. Nel caso non sia abilitato js, invece, verrà eseguito un submit normale del form, chiamando sfnode_more_contatti_submit() che si occuperà di creare un nuovo form con il numero adeguato di campi.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$form['contatti_wrapper']['c_more'] = array( '#type' => 'submit', '#value' => t('Add'), '#description' => t("If the amount of boxes above isn't enough, click here to add more choices."), '#weight' => 1, '#submit' => array('sfnode_more_contatti_submit'), // If no javascript action. '#ahah' => array( 'path' => 'sfnode/newfield', 'wrapper' => 'contatti', 'method' => 'replace', 'effect' => 'fade', ), ); return $form; } |
Per prima cosa, occorre una funzione per generare la parte di form "interessante":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/** * Contact info (sub)form * @param $delta The field #Id * @param $type value of subfield 'contact type' * @param $value value of subfield 'contact value' * @return a part of form */ function _sfnode_contatto_form($delta, $type, $value){ $form = array('#tree' => TRUE); $form['ctype'] = array( '#type' => 'select', '#title' => t('Contatto @n', array('@n' => ($delta + 1))), '#default_value' => $type, '#options' => _sfnode_get_tipocontatto(NULL), '#parents' => array('contatti', $delta, 'ctype'), ); $form['cval'] = array( '#type' => 'textfield', '#default_value' => $value, '#parents' => array('contatti', $delta, 'cval'), ); return $form; } |
Quindi, una funzione di callback che risponda al menu sfnode/newfield, in modo da ritornare l'html dei nuovi campi.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/** * Callback for new field generation * @return nothing. just send the new form HTML to the browser via-json */ function sfnode_newfield(){ module_load_include('inc', 'node', 'node.pages'); $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'] = 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. $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id); // Render the new output. $choice_form = $form['contatti_wrapper']['contatti']; unset($choice_form['#prefix'], $choice_form['#suffix']); // Prevent duplicate wrappers. $output = theme('status_messages') . drupal_render($choice_form); drupal_json(array('status' => TRUE, 'data' => $output)); } |
..e infine, una funzione di callback che risponda al submit del form causato dal click sul pulsante "Aggiungi campo", quando javascript è disabilitato:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** * Submit callback for new field addition * @param $form * @param $form_state * @return unknown_type */ function sfnode_more_contatti_submit($form, &$form_state) { // Set the form to rebuild and run submit handlers. node_form_submit_build_node($form, $form_state); // Make the changes we want to the form state. if ($form_state['values']['c_more']) { $n = $_GET['q'] == 'sfnode/newfield' ? 1 : 5; $form_state['contatti_count'] = count($form_state['values']['contatti']) + $n; } } |
Infine, per avere un output grafico più piacevole, useremo una funzione di theming per ordinare i campi multipli in una tabella:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
/** * The hook_theme() * @param $existing * @param $type * @param $theme * @param $path * @return unknown_type */ function sfnode_theme($existing, $type, $theme, $path){ return array( 'sfnode_contatti' => array( 'arguments' => array('form' => NULL), ), ); } /** * Theming function for contact (sub)form * @param $form * @return unknown_type */ function theme_sfnode_contatti($form){ $header = array(t("Tipo"), t("Valore")); $rows = array(); foreach (element_children($form) as $key) { unset($form[$key]['ctype']['#title'], $form[$key]['cval']['#title']); $row = array( 'data' => array( array('data' => drupal_render($form[$key]['ctype']), 'class' => 'contatto-type'), array('data' => drupal_render($form[$key]['cval']), 'class' => 'contatto-val'), ), ); if (isset($form[$key]['#attributes'])) { $row = array_merge($row, $form[$key]['#attributes']); } $rows[] = $row; } $output = theme('table', $header, $rows); $output .= drupal_render($form); return $output; } |
I sorgenti completi del modulo sono disponibili su subversion:
1 |
svn co http://svn.hackzine.org/drupalmodules/modules/sfnode/ |
Oppure, in allegato trovate gli archivi tar.gz e tar.bz2 contenente uno snapshot da svn.