Feeds

Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
Aug 14 2013
Aug 14

In an earlier version I showed you how to make CCK fields read-only using Drupal 6. CCK was merged into Drupal 7 as the fields API, so a new approach is required. This new post will show an easy and safe technique to make Drupal 7 fields read-only.

Generally, using form alter on any form field and setting #disabled => TRUE works just fine. However, form alter is too early of a hook to use on entity fields. Moreover, if you disable a field, the browser won't even post the value. This can result in annoying validation issues.

The correct and safe way to do lock changes to a widget is to mark its HTML elements as read only, not as disabled. To do this, we use the readonly attribute. This ensures that the browser posts the value, unlike disabled. But we can't just stop here. We need to make sure the value isn't changed by a crafty user modifying his or her post request. Just imagine if they user the Chrome Inspector and altered the HTML attributes directly: they'd be able to modify the field. We need to prevent this.

In case you don't already have a field you want to lock down, let's create one really quickly.

  1. Browse to admin/structure/types.
  2. Find an existing content type, and click on manage fields.
  3. Add a field and give it the label Locked. The machine name should appear automatically as field_locked. Be sure to choose a text field type, using a textfield widget.

Now, let's hook in to the node form so we can add our #after_build callback. Keep in mind that in this example, we're adding our callback to all node forms. You might want to further limit this to a specific content type. You might also want to check if this is an existing node, as locking the field on node creation might not be necessary.

php

/**                                                                           
 * Implements hook_form_alter().                                              
 */                                                                           
function example_form_alter(&$form, &$form_state, $form_id) {                 
  if (isset($form['#node']) && $form_id == $form['#node']->type .'_node_form') {
    $form['#after_build'][] = 'example_after_build';                          
  }                                                                           
}

Let's keep things simple and modify a text widget. For simple widgets, the fields API generally uses the key value.

php

function example_after_build($form, &$form_state) {                           
  $field = 'field_locked';
  $form[$field]['und'][0]['value']['#attributes']['readonly'] = 'readonly';
  $form_state['values'][$field]['und'][0]['value'] = $form[$field]['und'][0]['value']['#default_value'];
  return $form;
}

And that's it! You can now count on your field being read-only. It will still be posted by the browser. And even if a user tries to post a changed value - or uses the browser inspector to edit the contents of the fields - our build function restores the original value. Fast, safe, and secure.

Below is the complete code for reference.

php

/**                                                                           
 * Implements hook_form_alter().                                              
 */                                                                           
function example_form_alter(&$form, &$form_state, $form_id) {                 
  if (isset($form['#node']) && $form_id == $form['#node']->type .'_node_form') {
    $form['#after_build'][] = 'example_after_build';                          
  }                                                                           
}

function example_after_build($form, &$form_state) {                           
  $field = 'field_locked';
  $form[$field]['und'][0]['value']['#attributes']['readonly'] = 'readonly';
  $form_state['values'][$field]['und'][0]['value'] = $form[$field]['und'][0]['value']['#default_value'];
  return $form;
}
Jul 29 2013
Jul 29

The Drupal Form API is powerful, but very extensive and complicated. In another post we discussed how to add a custom element from scratch. Watch out for the errors and mistakes highlighted in this advanced-level post.

Element and Field Defaults

To fully understand the API, you need to understand how elements are built. When you define a form field, custom or not, you have to give it a #type.

When the form is being built, Drupal will look at the #type and decide what to do with it. If it's a custom element you've defined, Drupal will look at the hook_element_info() that defined that custom element, and grab all the information from that array. That is, keys and values in that array will be merged into your field. This allows you to set defaults for that element #type. Let's look at an example. For simplification, let's define a basic custom element type:

1
2
3
4
5
6
7
8


function example_element_info() {
  $elements['custom_element'] = array(
    // Element declaration here ...
  );
  return $elements;
}

Note that this element won't do much. But now, let's use it in a form:

1
2
3
4
5
6
7
8


function example_some_form() {
  $form['my_field'] = array(
    '#type' => 'custom_element',
  );
  return $form;
}

If we want each usage of the element to, by default, have certain properties, we can just add it to the hook_element_info() function. So doing this:

1
2
3
4
5
6
7
8
9


function example_element_info() {
  $elements['custom_element'] = array(
    // Rest of element declaration here ...
    '#some_property' => 'some_property',
  );
  return $elements;
}

Will mean that all uses of your element in forms would be as if you had declared the form like so:

1
2
3
4
5
6
7
8
9


function example_some_form() {
  $form['my_field'] = array(
    '#type' => 'custom_element',
    '#some_property' => 'some_property',
  );
  return $form;
}

In other words, #some_property will "bubble" up from the element declaration to the actual element useful.

This means you can do powerful things, like declaring default #theme functions, default #prefix and #suffix content, etc.

To summarize: the form declaration for that element will get merged to the properties defined in hook_element_info(). So keep this in mind when defining your custom elements.

Hook Theme and Render Element

So you've defined your custom element, but now want to control its appearance. Easy right? Just give it a #theme function in hook_element_info(). Well, it's not that simple.

You might notice it's not rendering now or ignoring your theme function. So you go and add that function to the theme registry, using hook_theme().

But now we have another problem: our theme function isn't being called with the right arguments. That's because when defining this function in hook_theme(), you need to use the render element property, rather than your typical variables property. As follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19


// DO THIS
function example_theme() {
  return array(
    'gitbucket_field' => array(
      'render element' => 'element',
    ),
  );
}

// NOT THIS
function example_theme() {
  return array(
    'gitbucket_field' => array(
      'variables' => array('element' => NULL),
    ),
  );
}

And there you go: two big secrets about custom Drupal elements. With this knowledge on hand, you should be able to create all the custom elements you'll need. Without surprises.

Jul 09 2013
Jul 09

One of the most cryptic tasks in Drupal is defining a custom form element. That is, defining your own element types to be used and resused in your forms. This post will walk you through the basics. And there's no better way than to learn by example.

For our example element, let's define one that links to a github or bitbucket repository. These two sites host a lot of open source projects, so it makes sense to have an element that handles both. Note: this guide applies to Drupal 7. You should be able to make it work on Drupal 6 with some minor modifications. Let's call our element gitbucket.

![3]

Part of what makes the Form API in Drupal so powerful is that we can define compound custom elements. In other words, elements that contain one or more existing elements. This is quite useful for our use case, where we want our gitbucket field to have both, a source field (github or bitbucket) and a username/repository field. So let's get started.

The key to creating a custom element is hook_element_info(), where we provide information about our element. So let's define it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14


function example_element_info() {
  $elements['gitbucket'] = array(
    '#default_value' => '',
    '#input' => TRUE,
    '#process' => array('example_gitbucket_element_process'),
    '#theme' => array('gitbucket_field'),
    '#theme_wrappers' => array('form_element'),
    '#tree' => TRUE,
    '#value_callback' => 'example_gitbucket_element_value_callback',
  );
  return $elements;
}

As you can see above, we're providing the Form API with quite a bit of info. Let's walk through this hook step by step.

Our #process callback is what gets called when we're going to use the element in a form. This is where we turn our element from a simple definition into the compound element that we want. So we provide a callback function for it. We'll get to that in a bit.

We also define a #theme callback. This is what renders the inside of our element, including the subelements that we'll define. We have a lot of flexibility here when rendering. Using this callback, we'll make sure our element looks nice and readable.

Next we provide #theme_wrappers. As you'd imagine, this wraps all the elements after they come back from the #theme callback. Generally, you can just use the built-in form_element and let it do the work for you. That's exactly what we're doing here.

Last but not least, we define #tree and #value_callback. The first makes sure our nested element structure is preserved for our values, rather than flattened. The second provides a way to process values before they are returned to the form submit function. You need to provide a #value_callback if you want to do fancy processing on the input.

With all of that declared, we can now implement our #process function to construct our very own custom element:

 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


function example_gitbucket_element_process($element, $form_state, $complete_form) {

  $element['gitbucket']['source'] = array(
    '#type' => 'select',
    '#empty_option' => '- ' . t('Source') . ' -',
    '#options' => array(
      'github' => 'github://',
      'bitbucket' => 'bitbucket://',
    ),
    '#required' => $element['#required'],
    '#title' => t('Repository Source'),
    '#title_display' => 'invisible',
    '#theme_wrappers' => array(),
  );

  if (isset($element['#default_value']['gitbucket']['source'])) {
    $element['gitbucket']['source']['#default_value'] = $element['#default_value']['gitbucket']['source'];
  }

  $element['gitbucket']['address'] = array(
    '#type' => 'textfield',
    '#size' => 50,
    '#required' => $element['#required'],
    '#title' => t('Repository Name'),
    '#title_display' => 'invisible',
    '#theme_wrappers' => array(),
    '#attributes' => array('placeholder' => 'username/repository'),
  );

  if (isset($element['#default_value']['gitbucket']['address'])) {
    $element['gitbucket']['address']['#default_value'] = $element['#default_value']['gitbucket']['address'];
  }

  return $element;
}

As you can see, we're nesting elements in the snippet above. Our custom element consists of a select field for the repository source, and a textfield for the actual repository. The actual declaration of these fields is straightforward, and you can use almost anything in the Form API.

When nesting elements, there are a couple of quirks to watch out for. First, you probably don't want a bunch of nested titles shown. That's why we set #title_display to invisible. But we still declare a title for error handling purposes. We want the Form API to be able to pinpoint fields required nested fields and the like.

You also don't want the wrapping HTML structure around each individual nested element. You want it outside the entire compound element. So for each nested element, we set #theme_wrappers to an empty array.

And that's it. We load up default values as necessary, and we're done processing our custom elemenet. Let's move on to theming.

We want a nice inline look for our field, so let's keep the theming simple. First we need to register our function, and then implement it. Like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18


function example_theme() {
  return array(
    'gitbucket_field' => array(
      'render element' => 'element',
    ),
  );
}

function theme_gitbucket_field($variables) {
  $element = $variables['element'];
  $output = '';
  $output .= drupal_render($element['gitbucket']['source']);
  $output .= " "; // This space forces our fields to have a little room in between.
  $output .= drupal_render($element['gitbucket']['address']);
  return $output;
}

Now we're ready for the last part: the value callback. As you can see, our custom element has two parts. You should select either or none. But what happens if they just select a source with no repository name? If we marked the field as required, Drupal handles that for us by requesting both fields be populated. The problem appears when our field is not marked as required. In this case, if we fill just one and not the other, we want to treat this as the user having left the field empty. We can use the value callback for this .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18


function example_gitbucket_element_value_callback($element, $input = FALSE, &$form_state) {
  if ($input !== FALSE) {
    if ($input['gitbucket']['source'] && !$input['gitbucket']['address']) {
      $input['gitbucket']['source'] = '';
    }
    if ($input['gitbucket']['address'] && !$input['gitbucket']['source']) {
      $input['gitbucket']['address'] = '';
    }
    return $input;
  }
  elseif (!empty($element['#default_value'])) {
    return $element['#default_value'];
  }

  return;
}

Value callbacks should be divided into three conditions:

  1. Input is being provided directly. This happens when a form is submitted.
  2. No input is provided, but the field definition has a default value.
  3. No input is provided and there is no default value.

In the first part, we handle the edge case of partial input. If only half the field is filled out, we simply treat it as empty. Then we return the modified input.

And that's it. You're done. You've now defined a custom compound field that can be used in any Drupal forms. For example, let's declare a simple system settings form that uses our newly minted element.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17


function example_form() {
  $form = array();
  $form['required_field'] = array(
    '#type' => 'gitbucket',
    '#title' => t('Your favorite repository'),
    '#required' => TRUE,
    '#default_value' => variable_get('required_field', array()),
  );
  $form['optional_field'] = array(
    '#type' => 'gitbucket',
    '#title' => t('Your least favorite repository'),
    '#default_value' => variable_get('optional_field', array()),
  );
  return system_settings_form($form);
}

Try it out. Fill in partial input and hit submit. You'll see how our fields behave properly even with missing input.

Feb 28 2013
Feb 28

Ever wanted to give form elements a custom look? Theming entire forms is straightforward, if laborious. But theming individual textboxes, checkboxes and radio buttons is slightly more obscure. Keep reading to find out how to fully customize your form elements, from the input itself to the label.

You might be wondering: can't I just implement a theme_checkbox() function? Well, that might meet your requirements, but this doesn't handle the label at all. Let's set up our theme to give us maximum flexibility.

Let's start with a use case: Bootstrap likes to have checkboxes inside the label element. I happen to like this markup too. But Drupal places the checkbox next to the label. How can we achieve this without hacking core?

Start by copying over theme_form_element() into the template.php file in your theme. Let's assume the theme is called example. You can find the body here. Be sure to replace the word theme with example.

Now find the switch statement and replace it to look like this:

 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

if ($element['#type'] == 'checkbox') {
  $variables['rendered_element'] = ' ' . $prefix . $element['#children'] . $suffix . "\n";
  $output .= theme('form_element_label', $variables);
}
else {
  switch ($element['#title_display']) {
    case 'before':
    case 'invisible':
      $output .= ' ' . theme('form_element_label', $variables);
      $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n";
      break;

    case 'after':
      $output .= ' ' . $prefix . $element['#children'] . $suffix;
      $output .= ' ' . theme('form_element_label', $variables) . "\n";
      break;

    case 'none':
    case 'attribute':
      // Output no label and no required marker, only the children.
      $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n";
      break;
  }
}

Notice we wrapped it with an outer if statement. Here, we a define custom behavior for our checkbox. This allows us to give checkboxes special handling for labels using a custom theme_form_label(). Let's implement that, by copying the existing theme_form_element_label() from here and changing theme to example. But let's make a change. Remove the return statement (and the comment above it) and replace it with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

// Bootstrap wants us to add a class to the label as well.
if ($element['#type'] == 'checkbox') {
  $attributes['class'] = 'checkbox';
}

// The leading whitespace helps visually separate fields from inline labels.
if (!empty($variables['rendered_element'])) {
  return '  . drupal_attributes($attributes) . '>' . $variables['rendered_element'] . $t('!title !required', array('!title' => $title, '!required' => $required)) . "</label>\n";
}
else {
  return '  . drupal_attributes($attributes) . '>' . $t('!title !required', array('!title' => $title, '!required' => $required)) . "</label>\n";
}
''

Last but not least, Bootstrap also likes us to add the class checkbox to the input element itself. Though this isn't necessary for any CSS rules, the guide shows the markup this way. So it's best to be safe. We do this by overriding the theme_checkbox() function. Change the _form_set_class() function call to:


_form_set_class($element, array('form-checkbox', 'checkbox'));

And that's it. You now have a fully themed Bootstrap checkbox. More importantly, adding future elements and theming is very quick. You only need to set up all these theme functions once.

Check out the full template.php file for reference.

Sep 03 2012
Sep 03

In a recent post, I explained how to dynamically alter the AJAX behavior of an autocomplete field in Drupal. But what if you have the similar node reference field, and want to modify the result set? This post will show you how.

Let's go over some assumptions:

  1. For simplicity, we will alter the field to show only nodes in English.
  2. This will only alter the autocomplete behavior, not the validation. If your field is set to accept a limited set of nodes, the suggestions have to be a subset. Altering the validation of the field is a post for another - longer - day.
  3. Our field's machine name is field_reference.
  4. Our field is set to accept only 1 value.

First, let's add the menu hook and the autocomplete behavior. Fairly standard stuff:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

/**                                                                           
 * Implements hook_form_alter().                                              
 */                                                                           
function example_module_menu() {                                              
  $items = array();                                                           
  $items['example_autocomplete'] = array(                                     
    'type' => MENU_CALLBACK,                                                  
    'page callback' => 'example_module_autocomplete',                         
    'access callback' => TRUE,                                                      );                                                                          
  return $items;                                                              
}

function example_module_autocomplete($input) {                                
  $results = array();                                                         
  $resource = db_query_range("SELECT nid, title FROM {node} WHERE language = '%s' AND title LIKE '%s%%'", 'en', $input, 0, 10);

  while ($node = db_fetch_object($resource)) {                                
    $key = sprintf('%s [nid: %d]', $node->title, $node->nid);                 
    $results[$key] = $node->title;                                            
  }                                                                           
  print json_encode($results);                                                
  exit();                                                                     
}

As you can see, all we're doing is providing a new AJAX callback. In that callback, we select nodes from the database and limit them by language. Now, let's define our form alter hook. But remember, like all CCK fields, hook_form_alter is too early in the process to modify a field. So instead, we define an #after_build function that will get called further down the road. Like so:

1
2
3
4
5
6
7
8
9

/**                                                                           
 * Implements hook_form_alter().                                                   
 */                                                                               
function example_module_form_alter(&$form, &$form_state, $form_id) {          
  if ($form_id == 'post_node_form') {                                         
    $form['#after_build'][] = 'example_module_after_build';                   
  }                                                                           
}

Finally, we'll actually alter the form field in our #after_build function. If you're alter a field set to multiple values, be sure to loop over the field and alter all rows.


function example_module_after_build($form, &$form_state) {                    
  $form['field_reference'][0]['nid']['nid']['#autocomplete_path'] = 'example_autocomplete';
  return $form;                                                               
}
Jul 23 2012
Jul 23

The Views module is one of the most impressive features Drupal offers. With some clicks here and there, you can build a rich display of data in minutes. But to offer this kind of flexibility, Views makes some assumptions that can hamper your site's performance. In this post, you'll find out how to improve performance dramatically.

First, a quick technical explanation of what happens. Drupal shares the main record for each content type in the node table. That means that regardless of whether an entry is a blog post or an event, it will get a record in this table. This has the advantage that building aggregated lists of all entries is quite easy.

On Joins

How does this tie into Views? When you build a view that uses a field specific to a content type - for example, event date - it doesn't know if every entry will have this field or not. So when querying the database, it tells it "give me this node. And if it has a corresponding field called event date, give me that as well." This is called a LEFT JOIN, and it's quite slow. If on the other hand, we changed the query ever so slightly to "give me this node and the corresponding field event date," then the whole thing will execute much quicker. By doing this, the query is executing an INNER JOIN which joins each node entry with each event entry one to one.

Keep in mind, this will only work if you want the result set to contain events only. If you need to mix events and blog posts, you're out of luck. Well, sort of.

On Pagers

Guess what the most expensive part of your view is? That's right, the pager at the bottom. Turns out, counting in databases is slow. Really slow. So get rid of as many COUNT queries as you can. What's this got to do with pagination? To accurately paginate, Views executes a COUNT query on the entire result set. That means that even if you're displaying 20 records out of a million, Views will need to count all 20 million rows. This takes a long time.

So what are your options? You've got two. One is to get rid of pagination entirely. The other is to use a less accurate pager. We won't go over the former, because clients/product managers can be notoriously inflexible - good luck trying to convince them to use no pagination. But a less accurate pager? They can probably live with that.

So how does a less accurate pager work? First, it eliminates the "last" option. That means you don't need to know how many total results there are. Skipping more than 2 pages ahead at a time is also nixed. With these compromises, you can get rid of the COUNT query and simply use the current offset to build the pager. You'll only be able to accurately jump to pages before the one you're on, but often this is good enough.

On Distinct

DISTINCT clauses have their time and place - never. No but really, try to avoid them. Most of the time, you can eliminate the need for them by crafting a better query. Duplicates rows are usually a symptom of a bigger problem. So you shouldn't mark your view queries as needing DISTINCT unless you're absolutely certain it's needed. Or if you're lazy. Regardless of the reason, it will slow down your query. Worse - when you use DISTINCT in a view, a GROUP BY clause will be inserted as well. This will also slow down your query significantly. Fortunately, disabling DISTINCT will rid you of both problems. Of course, do check to make sure you're not getting duplicate results. If you're using INNER JOIN as described above, you most likely won't.

The Changes

After all that technical talk, let's actually apply these changes. Three assumptions:

  1. Let's assume your view's machine name is my_view.
  2. Let's also assume it's joining to a table called content_type_event for the additional event information.
  3. Let's pretend you've got a module floating around called my_module.

Here's how to do this The Right Way™:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
                                                                          
/**                                                                             
 * Implements hook_views_query_alter().                                         
 *                                                                              
 * Disables distinct and turns LEFT joins into INNER joins. This drammatically  
 * speeds up performance.                                                       
 */                                                                             
function my_module_views_query_alter(&$view, &$query) {                         
  if ($view->name == "my_view") {                                               
    $query->table_queue['content_type_notice']['join']->type = 'INNER';         
    $query->distinct = 0;                                                       
  }                                                                             
}

What about the pager? That's even easier. Just download the lite pager module and select it as the paging option for your view.

Conclusion

Views can be really helpful, no doubt. And it can get a bad rap about performance. But that's because - without much user input - it has to make a lot of assumptions to construct the query. With just a little bit of code, we can guide the query builder down the right path.

Jul 18 2012
Jul 18

Ever had an interface that needed an autocomplete field? Happens all the time - and Drupal makes that task trivial. But what if you need to change the autocomplete URL without reloading the page? Use these steps, and you'll find out how to do so with JavaScript.

For the purposes of this tutorial, we'll assume you don't care too much about the initial URL. So let's make it obvious that it's a placeholder. Let's start by declaring our menu entries.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
      
function our_autocompleted_field_menu() {                                     
  $items = array();                                                           
  $items['our_autocompleted_field_form'] = array(                             
    'type' => MENU_CALLBACK,                                                  
    'access callback' => TRUE,                                                
    'page callback' => 'drupal_get_form',                                     
    'page arguments' => array('our_autocompleted_field_form'),                
  );                                                                          
  $items['our_autocompleted_field/autocomplete/%'] = array(                   
    'type' => MENU_CALLBACK,                                                  
    'access callback' => TRUE,                                                
    'page callback' => 'our_autocompleted_field_autocomplete',                
    'page arguments' => array(2),                                             
  );                                                                          
  return $items;                                                              
}

Now let's declare the form with our field. Like any other autocomplete field, we declare it by using the #autocomplete property on our form definition.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
                                                                               
function our_autocompleted_field_form() {                                     
  $form = array();                                                            
  $form['our_autocompleted_field_toggle'] = array(                                   
    // This is just a sample link to demonstrate the dynamic autocomplete URL.
    '#type' => 'markup',                                                      
    '#value' => 'Toggle', 
  );                                                                          
  $form['our_autocompleted_field'] = array(                                   
    '#type' => 'textfield',                                                   
    '#id' => 'our-autocompleted-field',                                       
    // Drupal needs a valid autocomplete path, otherwise it will not add the  
    // behavior. So we add the placeholder argument and replace that on the   
    // JS frontend.                                                           
    '#autocomplete_path' => 'our_autocompleted_field/autocomplete/PLACEHOLDER',
  );                                                                          
  return $form;                                                               
}

Now, let's define our autocomplete function. To demonstrate the dynamic capabilities, let's use the first argument - which comes along with the path - as a way to tell us whether to return results in lowercase or uppercase. Of course, you can use the dynamically-changed URL to vary your results however you want.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
      
function our_autocompleted_field_autocomplete($letter_case, $input) {         
  if ($letter_case == 'uppercase') {                                          
    $function = 'strtoupper';                                                 
  }                                                                           
  else {                                                                      
    $function = 'strtolower';                                                 
  }

  $matches = array(                                                           
    'Suggestion 1',                                                           
    'Suggestion 2',                                                           
  );                                                                          
  $matches = array_map($function, $matches);

  print json_encode($matches);                                                
  exit();                                                                     
}

Finally, we use a little bit of magic on the frontend to change the autocomplete path. The trick is to reload the autocomplete object that Drupal generates, otherwise, the change won't take effect. You also need to get rid of the old behavior - that way you avoid confusing duplicate calls. Find or create your module's JavaScript file. Then add the following code:

 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
$(document).ready(function() {                                                
  // ID format is {OUR-FIELD-ID}-autocomplete.                                  
  var autocompletePath = $('#our-autocompleted-field-autocomplete')           
                         .val().replace('/PLACEHOLDER', '');

  // Keep track of the case we're in.                                         
  var isUpperCase = false;

  $('#our-autocompleted-field-toggle').click(function() {                     
    if (isUpperCase) {                                                        
      var newURL = autocompletePath + '/lowercase';                           
      isUpperCase = false;                                                    
    }                                                                         
    else {                                                                    
      var newURL = autocompletePath + '/uppercase';                           
      isUpperCase = true;                                                     
    }                                                                         
    // Trick Drupal into rebinding the autocomplete behavior.                 
    $('#our-autocompleted-field-autocomplete').val(newURL)                    
    .removeClass('autocomplete-processed');

    $('#our-autocompleted-field').unbind().val('');                           
    Drupal.behaviors.autocomplete(document);

    return false;                                                             
  });

  // Simulate a click when initializing so that we have a valid autocomplete  
  // path.                                                                    
  $('#our-autocompleted-field-toggle').click();                               
});

And that's it. This code will toggle your autocomplete results between uppercase and lowercase. It's a contrived example, of course. But think of the possibilities!

Jun 03 2012
Jun 03

Setting up Drupal 6 to use memcached is anything but trivial. There are plenty of instructions out there, but many of these are for MAMP 1.x, or involve MacPorts. This post will show you how to quickly set up memcached locally. Best of all, this guide will leave your system ready to install other PECL extensions quickly.

You will need XCode and MAMP 2.0 installed for any of this to work. Also, it assumes you're using PHP 5.2 through MAMP's settings, but it probably works with 5.3 too. Onward.

First and foremost, you should set up Homebrew. It's dramatically better than MacPorts. To install it - if you haven't already done so - just run:

/usr/bin/ruby -e "$(/usr/bin/curl -fsSL \
https://raw.github.com/mxcl/homebrew/master/Library/Contributions/install_homebrew.rb)"

Once that's done, we're ready to install memcached and libevent. Do that with:

brew install libevent
brew install autoconf
brew install libmemcached
# Don't worry about adding memcached to your LaunchAgents folder.
# We're going to run it manually.

And that's it for the easy part. Kudos to homebrew for making this very straightforward. Now for the interesting part.

To install a PECL extension using MAMP, you need to grab the PHP source. Go ahead and do this now by going to http://us.php.net/distributions/php-5.2.17.tar.bz2, changing 5.2.17 to whatever version of PHP 5.2 your MAMP installation has. To find this out, run find /Applications/MAMP/bin/php -name 'php5.2*' and make note of the last directory.

Once you've got the source, you'll need to extract it in /Applications/MAMP/bin/php/php5.2.17 under a folder structure with the path include/php. Confusing? These lines will do it for you.

cd /Applications/MAMP/bin/php/php5.2.17
mkdir include
cd include
curl http://museum.php.net/php5/php-5.2.17.tar.bz2 | tar -xz
mv * php

Now we've need to run ./configure on the PHP source, and remove pear.conf from the MAMP distribution - it comes corrupted out of the box, for some reason.

 cd php
./configure
cd ../../
rm conf/pear.conf

And finally, we're ready to install the memcached PECL extension and add it to php.ini so it's enabled. Like so:

cd bin
./pecl install memcached
cd ../
echo -e "\n[memcached]\nextension=memcached.so" >> conf/php.ini
cd ../../

And that's it. This leaves you at the root of MAMP's installation. You should restart MAMP and enjoy fast caching. Don't forget to start memcached by running:

memcached -vv

This will start an instance that prints activity to the screen. Very useful when debugging locally, and very easy to restart. This completes the tutorial.

If you ever need to install another PECL extension, your MAMP installation now has all the necessary tools to do so. Just use the PECL binary in /Applications/MAMP/bin/php/php5.2.17/bin and you should be good to go.

Below is the entire set of commands, for posterity.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/usr/bin/ruby -e "$(/usr/bin/curl -fsSL \
https://raw.github.com/mxcl/homebrew/master/Library/Contributions/install_homebrew.rb)"

brew install libevent
brew install autoconf
brew install libmemcached
# Don't worry about adding memcached to your LaunchAgents folder.
# We're going to run it manually.

cd /Applications/MAMP/bin/php/php5.2.17
mkdir include
cd include
curl http://museum.php.net/php5/php-5.2.17.tar.bz2 | tar -xz
mv * php
cd php
./configure
cd ../../
rm conf/pear.conf
cd bin
./pecl install memcached
cd ../
echo -e "\n[memcached]\nextension=memcached.so" >> conf/php.ini
cd ../../
May 14 2012
May 14

Generally, using form alter on any form field and setting #disabled => TRUE works great. However, form alter is too early of a hook to use on CCK's fields. Moreover, if you disable a field, the browser won't even post the value. This can result in annoying validation issues. So why this post? I found that the most popular post on this topic did not work for me. This post applies to Drupal 6. But relax, there's a Drupal 7 technique.

The correct and safe way to do lock changes to a widget is to mark its HTML elements as read only, not as disabled. To do this, we use the readonly attribute. This ensures that the browser posts the value, unlike disabled. But we can't just stop here. We need to make sure the value isn't changed by a crafty user modifying his post request.

First, let's hook in to the node form so we can add our #after_build callback. Keep in mind that in this example, we're adding our callback to all node forms. You might want to further limit this to a specific content type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10


/**                                                                           
 * Implements hook_form_alter().                                              
 */                                                                           
function example_form_alter(&$form, &$form_state, $form_id) {                 
  if (isset($form['#node']) && $form_id == $form['#node']->type .'_node_form') {
    $form['#after_build'][] = 'example_after_build';                          
  }                                                                           
}

Let's keep things simple and modify a text widget. For simple widgets, CCK generally uses the key value.

1
2
3
4
5
6
7
8


function example_after_build($form, &$form_state) {                           
  $field = 'field_example_cck_field';                                         
  $form[$field][0]['value']['#attributes']['readonly'] = 'readonly';          
  $form_state['values'][$field][0]['value'] = $form[$field]['#default_value']['value'];
  return $form;                                                               
}

And that's it! You can now count on your field being readonly. Yet it will still be posted by the browser. And even if a user tries to post a changed value - or uses Firebug to edit the contents of the fields - our build function restores the original value.

Below is the complete code for reference.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17


/**                                                                           
 * Implements hook_form_alter().                                              
 */                                                                           
function example_form_alter(&$form, &$form_state, $form_id) {                 
  if (isset($form['#node']) && $form_id == $form['#node']->type .'_node_form') {
    $form['#after_build'][] = 'example_after_build';                          
  }                                                                           
}

function example_after_build($form, &$form_state) {                           
  $field = 'field_example_cck_field';                                         
  $form[$field][0]['value']['#attributes']['readonly'] = 'readonly';          
  $form_state['values'][$field][0]['value'] = $form[$field]['#default_value']['value'];
  return $form;                                                               
}

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web