2

I maintain a contrib module, and I'm in the process of porting it from Drupal 6 to Drupal 7. The module (http://www.drupal.org/project/csm) adds fields to the edit form for each content type (i.e., the form at admin/structure/types/manage/[node-type], so part of the porting process involves displaying those fields as new vertical tabs (see "Submit message settings" and "Node creation page title" in the screenshot below).

The problem is, when the fields are changed to be part of the vertical tabs, I seem to lose control over how the data that is entered into those fields is saved to the variable table. I've stepped through the submission of the form with a breakpoint set in variable_set(), but only one variable seems to be set for each vertical tab that I've added: previously, each new field was added separately to the variable table, and I was able to control the name of the variable by setting the name of the field. This is what I'd like to do now.

Am I missing something? Is there some good documentation somewhere that covers what I'm trying to do? Here's the code that I'm using to add the new vertical tabs (and the fields in those tabs):

function csm_form_node_type_form_alter(&$form, $form_state) {

  // We need to know the current site language in order to present the form with
  // appropriate sections expanded.
  global $language;
  $current_lang = $language->language;

  // Build the form section that allows the user to change the submit messsages.
  $form['csm'] = array(
    '#type' => 'fieldset',
    '#title' => t('Submit message settings'),
    '#access' => user_access('change ' . $form['#node_type']->type . 'submit messages'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#group' => 'additional_settings',
    '#tree' => TRUE,
    '#weight' => 100,
    '#attributes' => array('class' => array('csm-form')),
  );

  // Add submit message subsections for each language
  foreach (language_list() as $lang => $details) {
    $form['csm'][$lang] = _csm_lang_fieldsets($lang, $form['#node_type']->type);
    $form['csm'][$lang]['#title'] = t($details->name);
    if ($lang == $current_lang) {
      $form['csm'][$lang]['#collapsed'] = FALSE;
  }

  // Add token help section
  $form['csm']['view']['token_help'] = array(
    '#title' => t('Replacement patterns'),
    '#type' => 'fieldset',
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    );

  $form['csm']['view']['token_help']['help'] = array(
    '#theme' => 'token_tree',
    '#token_types' => array('node'),
    '#global_types' => TRUE,
    '#click_insert' => TRUE,
  );

  // Build the form section that allows the user to change the node creation
  // page title.
  $form['cnpt'] = array(
    '#type' => 'fieldset',
    '#title' => t('Node creation page title'),
    '#access' => user_access('change ' . $form['#node_type']->type . ' creation page title'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#group' => 'additional_settings',
    '#tree' => TRUE,
    '#weight' => 101,
    '#attributes' => array('class' => array('csm-form')),
  );

  // Add create form title subsections for each language
  foreach (language_list() as $lang => $details) {
    $form['cnpt'][$lang] = _csm_lang_fieldsets($lang, $form['#node_type']->type, 'title');
    $form['cnpt'][$lang]['#title'] = t($details->name);
    if ($lang == $current_lang) {
      $form['cnpt'][$lang]['#collapsed'] = FALSE;
    }
  }

  // Add token help section
  $form['cnpt']['view']['token_help'] = array(
    '#title' => t('Replacement patterns'),
    '#type' => 'fieldset',
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    );

  $form['cnpt']['view']['token_help']['help'] = array(
    '#theme' => 'token_tree',
    '#token_types' => array('node'),
    '#global_types' => TRUE,
    '#click_insert' => TRUE,
    );
  }
}

_csm_lang_fieldsets() is a helper module that does some of the form building (a new form section has to be added for each language that is enabled on the site):

function _csm_lang_fieldsets($lang = NULL, $form_type, $type = 'message') {

  if ($lang == NULL) {
    global $language;
    $lang = $language->language;
  }

  $fields = array(
    '#type' => 'fieldset',
    '#title' => t($lang),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    );

  switch ($type) {
    case 'message':
      $fields['csm_insert_msg_' . $lang] = array(
        '#type' => 'textfield',
        '#title' => t('Create message'),
        '#default_value' => t(variable_get('csm_insert_msg_' . $lang . '_' . $form_type, '')),
        '#description' => t('Override the default message displayed when a user creates a node of this type. Use <em>&lt;none&gt;</em> to display no message, or leave blank to use the default message ("[node:content-type:name] [node:Title] has been created.").'),
        '#size' => 60,
        '#maxlength' => 4096,
        '#required' => FALSE,
      );

      $fields['csm_update_msg_' . $lang] = array(
        '#type' => 'textfield',
        '#title' => t('Update message'),
        '#default_value' => t(variable_get('csm_update_msg_' . $lang . '_' . $form_type, '')),
        '#description' => t('Override the default message displayed when a user updates a node of this type. Use <em>&lt;none&gt;</em> to display no message, or leave blank to use the default message ("[node:content-type:name] [node:Title] has been updated.").'),
        '#size' => 60,
        '#maxlength' => 4096,
        '#required' => FALSE,
      );

      $fields['csm_delete_msg_' . $lang] = array(
        '#type' => 'textfield',
        '#title' => t('Delete message'),
        '#default_value' => t(variable_get('csm_delete_msg_' . $lang . '_' . $form_type, '')),
        '#description' => t('Override the default message displayed when a user deletes a node of this type. Use <em>&lt;none&gt;</em> to display no message, or leave blank to use the default message ("[node:content-type:name] [node:Title] has been deleted.").'),
        '#size' => 60,
        '#maxlength' => 4096,
        '#required' => FALSE,
      );

      return $fields;
      break;
    case 'title':
      $fields['csm_form_title_' . $lang] = array(
        '#type' => 'textfield',
        '#title' => t('Node create form title'),
        '#default_value' => t(variable_get('csm_form_title_' . $lang . '_' . $form_type, '')),
        '#description' => t('Override the default title for the node creation page. Use <em>&lt;none&gt;</em> to display no title, or leave blank to use the default title ("Create [node:content-type:name]").'),
        '#size' => 60,
        '#maxlength' => 4096,
        '#required' => FALSE,
      );

      return $fields;
      break;
  }
}

For comparison, here's the code that generates the vertical tab that comment.module adds to the form. It adds variables called comment_anonymous_article, comment_article, comment_default_mode_article, comment_default_per_page_article, comment_form_location_article , comment_page, comment_preview_article, and comment_subject_field_article to the variable table. These names seem to be taken from form elements, and it is how I'd expect my own code to work.

/**
 * Implements hook_form_FORM_ID_alter().
 */
function comment_form_node_type_form_alter(&$form, $form_state) {
  if (isset($form['type'])) {
    $form['comment'] = array(
      '#type' => 'fieldset',
      '#title' => t('Comment settings'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#group' => 'additional_settings',
      '#attributes' => array(
        'class' => array('comment-node-type-settings-form'),
      ),
      '#attached' => array(
        'js' => array(drupal_get_path('module', 'comment') . '/comment-node-form.js'),
      ),
    );
    // Unlike coment_form_node_form_alter(), all of these settings are applied
    // as defaults to all new nodes. Therefore, it would be wrong to use #states
    // to hide the other settings based on the primary comment setting.
    $form['comment']['comment'] = array(
      '#type' => 'select',
      '#title' => t('Default comment setting for new content'),
      '#default_value' => variable_get('comment_' . $form['#node_type']->type, COMMENT_NODE_OPEN),
      '#options' => array(
        COMMENT_NODE_OPEN => t('Open'),
        COMMENT_NODE_CLOSED => t('Closed'),
        COMMENT_NODE_HIDDEN => t('Hidden'),
      ),
    );
    $form['comment']['comment_default_mode'] = array(
      '#type' => 'checkbox',
      '#title' => t('Threading'),
      '#default_value' => variable_get('comment_default_mode_' . $form['#node_type']->type, COMMENT_MODE_THREADED),
      '#description' => t('Show comment replies in a threaded list.'),
    );
    $form['comment']['comment_default_per_page'] = array(
      '#type' => 'select',
      '#title' => t('Comments per page'),
      '#default_value' => variable_get('comment_default_per_page_' . $form['#node_type']->type, 50),
      '#options' => _comment_per_page(),
    );
    $form['comment']['comment_anonymous'] = array(
      '#type' => 'select',
      '#title' => t('Anonymous commenting'),
      '#default_value' => variable_get('comment_anonymous_' . $form['#node_type']->type, COMMENT_ANONYMOUS_MAYNOT_CONTACT),
      '#options' => array(
        COMMENT_ANONYMOUS_MAYNOT_CONTACT => t('Anonymous posters may not enter their contact information'),
        COMMENT_ANONYMOUS_MAY_CONTACT => t('Anonymous posters may leave their contact information'),
        COMMENT_ANONYMOUS_MUST_CONTACT => t('Anonymous posters must leave their contact information'),
      ),
      '#access' => user_access('post comments', drupal_anonymous_user()),
    );
    $form['comment']['comment_subject_field'] = array(
      '#type' => 'checkbox',
      '#title' => t('Allow comment title'),
      '#default_value' => variable_get('comment_subject_field_' . $form['#node_type']->type, 1),
    );
    $form['comment']['comment_form_location'] = array(
      '#type' => 'checkbox',
      '#title' => t('Show reply form on the same page as comments'),
      '#default_value' => variable_get('comment_form_location_' . $form['#node_type']->type, COMMENT_FORM_BELOW),
    );
    $form['comment']['comment_preview'] = array(
      '#type' => 'radios',
      '#title' => t('Preview comment'),
      '#default_value' => variable_get('comment_preview_' . $form['#node_type']->type, DRUPAL_OPTIONAL),
      '#options' => array(
        DRUPAL_DISABLED => t('Disabled'),
        DRUPAL_OPTIONAL => t('Optional'),
        DRUPAL_REQUIRED => t('Required'),
      ),
    );
  }
}

Screenshot of the CSM module

  • 1
    Could you show actual code you use? – Mołot Jun 13 '13 at 11:02
  • Thanks, @ Mołot, I've edited my original post to include the code that generates the form elements :) – Jim Jun 13 '13 at 11:10
  • I don't quite get what happens. Is '#tree` set there? Just a question. Vertical tabs works OK for me, but I build my forms from scratch. – Mołot Jun 13 '13 at 11:15
  • I've reworked the code so that it's clearer, and included the code that does the same job in comment.module for comparison, as well as the variables this code adds to the variable table. Yes, '#tree' is set to true. – Jim Jun 13 '13 at 12:10
0

I remove the setting of "#tree" to FALSE, and everything works fine again.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.