Drupal Inline Popup Reference Field

By Jimmy Bonney | October 7, 2009

Inline

It seems that I am spending some time learning Drupal so the content of this post may defer from the editorial line a bit but as usual, I hope it may help someone.

The current drupal installation I am working with has CCK and Popups: Add and Reference (let’s call it PAR for future (shorter) references) modules. Unfortunately, by default the links added by PAR are in a new div tag below the input fields which increase the length of the form quite a bit as you can see below.

PAR - Links under input field

What I am proposing here is a way to put the PAR links inline with the input field. I am quite a beginner with Drupal so the method may not be really clean or fail proof and your comments about any easier way of doing it are welcomed. The result looks like the screenshot below.

PAR - Links under input field

In order to get to the result, I had to modify the following files:

  1. the module file: popups_reference.module
  2. the template file of my theme: template.php

In the popups_reference.module file (that is located under sites/all/modules/popups_reference), I have modified the function popups_reference_alter_item(&$form, $key, $item, $fields) in order to remove the link addition from the $form[$key]['#suffix']. Instead, I am putting the links in an array that is used later by the post_render function.

The post_render function is actually looking for some string that I have added in the template.php and is replacing it if needed, i.e. if there is a PAR link to output. Let’s take a look at what was done in the template.php file (located under sites/all/themes/MYTHEME (where MYTHEME is to be replaced by the appropriate name, i.e. your theme name)).

In the template.php, I override the form_element function that is used to display the field titles, description and input. You can see the complete function here under, but the only addition is the following line after the value is added to the $output variable: $output .= "<span class=\"popups-reference-link\"/>\n";

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
/**
 * Function used to overwrite the default display of form elements.
 * The description tag is placed before the input tag
 * A span tag for the popups_reference module has been added before the final div.
 * It allows to have the links created by the popups_reference module inline with the input field.
 */
function NewsFlash_form_element($element, $value) {
  $output  = '<div class="form-item"';
  if (!empty($element['#id'])) {
    $output .= ' ';
  }
  $output .= ">\n";
  $required = !empty($element['#required']) ? '<span class="form-required" title="'. t('This field is required.') .'">*</span>' : '';

  if (!empty($element['#title'])) {
    $title = $element['#title'];
    if (!empty($element['#id'])) {
      $output .= ' <label for="'. $element['#id'] .'">'. t('!title: !required', array('!title' => filter_xss_admin($title), '!required' => $required)) ."</label>\n";
    }
    else {
      $output .= ' <label>'. t('!title: !required', array('!title' => filter_xss_admin($title), '!required' => $required)) ."</label>\n";
    }
  }

  $output .= " $value\n";

  // Add a span tag that will be replaced if necessary when the popups_reference
  // module adds links to create new node.
  $output .= "<span class=\"popups-reference-link\"/>\n";

  if (!empty($element['#description'])) {
    $output .= ' <div class="description">'. $element['#description'] ."</div>\n";
  }

  $output .= "</div>\n";

  return $output;
}

And that’s it to display the Add New: Add XXX link inline with the input or select field.

I have attached the modified popups_reference.module and my template.php to this post. If you have any comment regarding a better way to do that, I’d be glad to know, so feel free to drop a comment.

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
<?php
// $Id: popups_reference.module,v 1.1.2.12 2009/03/07 06:54:25 starbow Exp $

/**
 * @file
 * Modify the Node Reference widget to use a popup to add a new node.
 */

$links_replacement;

/**
 * Implementation of hook_form_alter().
 *
 * Modifies the nodereference setting form and the basic node form.
 */
function popups_reference_form_alter(&$form, $form_state, $form_id) {
  if ($form_id == 'content_field_edit_form' && $form['#field']['type'] == 'nodereference') {
    // Add a checkbox to the nodereference settings page.
    $field_name = $form['#field']['field_name'];
    $form['field']['show_add_link'] = array(
    '#type' => 'checkbox',
      '#default_value' => variable_get('popups_reference_show_add_link_'. $field_name, TRUE),
      '#title' => t('Show the "Add New: Node Type" Popup links'),
      '#description' => t("Activate Popups:Add and Reference behavior for this reference.")
    );
    $form['#submit'][] = '_popups_reference_manage_fields_submit';
  }
  elseif (isset($form['type'])) {
    // Add the "Add New: Node Type" links.
    $node = $form['#node'];
    if ($form['type']['#value'] .'_node_form' == $form_id) {
      $fields = content_fields();
      foreach ($form as $key => $item) {
        if (is_array($item)) {
          $type = $item['#type'];
          if ($type == 'fieldset') { // Loop through all the subitems.
            foreach ($form[$key] as $subkey => $subitem) {
              popups_reference_alter_item($form[$key], $subkey, $subitem, $fields);
            }
          }
          else {
            popups_reference_alter_item($form, $key, $item, $fields);
          }
        }

      }
    }
  }
}

/**
 * Implementation of hook_nodeapi().
 * Add cookies with node info when a new node is created.
 * These cookies will be found by the popups_reference behavior and used
 *   to select the newly created node in the reference widget.
 */
function popups_reference_nodeapi($node, $op) {
  if ($op == 'insert') {
      $five = time()+300; // 5 minutes in the future.
      setcookie("PopupRefNid", $node->nid, $five, '/');
//      setcookie("PopupRefTitle", $node->title, $five, '/');
      setrawcookie("PopupRefTitle", rawurlencode($node->title), $five, '/');
  }
}

/**
 * Submit added to the the nodereference settings form.
 * Set a variable for each nodereference field.
 */
function _popups_reference_manage_fields_submit($form, &$form_state) {
  $field_name = $form['#field']['field_name'];
  variable_set('popups_reference_show_add_link_'. $field_name, $form_state['values']['show_add_link']);
}

/**
 * Run on every element in the basic node form.
 * Wrap the enabled nodereference fields, and add the popup links.
 *
 * @param $form - the form (or fieldgroup).
 * @param $key - form element name.
 * @param $item - the form element array.
 * @param $fields - all fields info.
 */
function popups_reference_alter_item(&$form, $key, $item, $fields) {
  $field_name = strstr($key, 'field_'); // Check if $key starts with 'field_';
  if (isset($fields[$field_name]) &&
      $fields[$field_name]['type'] == 'nodereference' &&
      variable_get('popups_reference_show_add_link_'. $field_name, TRUE)) {
    $type = $form['type']['#value'];
    $field = content_fields($field_name, $type);
    $wrapper_id = 'popups-reference-' . _popups_reference_counter();
    $links = _popups_reference_links($field, $type, $wrapper_id, $field['widget']['type']);
    if ($links) {
      // Put the nodereference widget and links in an wpopups link cssrapper.
      // Makes it easy to find for Ahah targeting, and popups_reference behavior selecting.
      global $links_replacement;
      // Register the links into the global array. Key is the field name so that we can identify the
      // correct element in the post_render method
      $links_replacement[$field_name] = implode(', ', $links);
      // Set prefix and suffix
      $form[$key]['#prefix'] = '<div >';
      $form[$key]['#suffix'] = '</div>';
      // Set the post render method that will be called when the variable are rendered
      $form[$key]['#post_render'] = array('cck_field_post_render_popups_reference_link');
    }
  }
}

// Post_render method
function cck_field_post_render_popups_reference_link($content, $element){
  global $links_replacement;
  //dsm($element);
  foreach(  $links_replacement as $field_name => $links) {
    // If the content contains the field_name, then we can display the links
    if(strstr($content, $field_name)){
      $tag_to_replace = "<span class=\"popups-reference-link\"/>";
      $replacing_tag_prefix = '<span class="popups-reference-link"> Créer: ';
      $replacing_tag_suffix = '</span>';
      // If the type of nodereference is an input or a select, then we can replace all (i.e. the only one) span tag existing
      if ($element['#type'] != 'nodereference_buttons'){
        $content = str_replace($tag_to_replace, $replacing_tag_prefix . $links . $replacing_tag_suffix, $content);
      }
      // If the type of nodereference is a list of checkboxes, then we replace only the last span tag
      // i.e the tag that appears after all the checkboxes
      else {
        $last_occurrence = strrpos($content, $tag_to_replace);
        $content = substr_replace($content, $replacing_tag_prefix . $links . $replacing_tag_suffix, $last_occurrence, $last_occurrence + strlen($tag_to_replace) - strlen($content));
      }
    }
  }
  return $content;
}

/**
 * Generates 'Add new...' link
 * for each allowed content type
 *
 * @param $field
 * @param $src_type - the type of base node.
 * @param $wrapper_id - id for the wrapper around the node reference.
 * @param $type - the type of widget.
 * @return Array of html links.
 */
function _popups_reference_links($field, $src_type, $wrapper_id, $widget_type) {
  if ($widget_type == 'nodereference_select' || $widget_type == 'nodereference_buttons') {
    // Target the wrapper for replacing.
    popups_add_popups(array('a.'.$wrapper_id=>array('targetSelectors'=>array('#'.$wrapper_id))));
  }
  else if ($widget_type == 'nodereference_autocomplete') {
    // Don't replace the autocomplete when done.
    popups_add_popups(array('a.'.$wrapper_id=>array('noUpdate'=>TRUE)));
  }
  else { // Unsupported type.
    return;
  }
  $options = array(
    'attributes' => array(
      'class' => $wrapper_id . ' popups-reference',
      'rel' => $wrapper_id,
  ),
    'query' => array('destination' => 'node/add/' . str_replace('_', '-', $src_type)),
  );
  $links = array();
  $all_types = node_get_types();
  foreach ($field['referenceable_types'] as $add_type => $value) {
    if (!empty($value) && (user_access("create $add_type content") || user_access('administer nodes'))) {
    //if (!empty($value) && user_access("create $add_type content")) {
      drupal_add_js(drupal_get_path('module', 'popups_reference') .'/popups_reference.js');
      $path = 'node/add/' . str_replace('_', '-', $add_type);
      $name = $all_types[$add_type]->name;
      $links[] = l(" $name", $path, $options);
    }
  }
  return $links;
}

/**
 * A counter for generating unique element id's.
 *
 * @return int: next integer.
 */
function _popups_reference_counter() {
  static $count = 0;
  return $count++;
}

For the time being, comments are managed by Disqus, a third-party library. I will eventually replace it with another solution, but the timeline is unclear. Considering the amount of data being loaded, if you would like to view comments or post a comment, click on the button below. For more information about why you see this button, take a look at the following article.