Node Form Dominos via Node Reference and Prepopulate
I like Prepopulate. I like to have that pseudo-RESTful way of preloading a form to minimize the amount of work a user has to do to get to the point of submitting a form. But I also like clean URLs. This post reviews in detail a technique to use a single prepopulated nodereference field to prepopulate a bunch of other fields based on that reference. Since Prepopulate’s recent 2.0 release, it because a whole lot more difficult to use the Form API to work magic on what it provides.
I use this in conjunction with nodereference to tailor node forms for their relationship with the referenced node.
Step 1: Setting up your links
In order for anything to happen, first you need to shape your links properly. The way to use Prepopulate with Node Reference fields (any many others) has subtly shifted. In the case of nodereference, you now what your querystring to look like this:
edit[field_my_nodereference_field][∆][nid][nid]=[nid: ###]
Where ∆ is the delta of your field (probably 0), and ### is the Node NID.
Step 2: Set up a module
Create a new module, or use any old custom module you have laying around. Be sure it’s weight is higher than Prepopulate’s lofty “10”, because your module needs to react to what Prepopulate does. Read up on setting your module’s weight if need be.
You could avoid worrying about module weight if you wanted to wrangle the URL on your own, but at that point why are you using Prepopulate?
Step 3: hook_form_alter() and #after_build
This post won’t review how to use hook_form_alter(), but if you do not know about this amazing function, I am shocked. Go read up on it. Master it. And return here.
hook_form_alter() is great to change a form before it’s built. But Prepopulate now applies it’s changes after the form is built. That means you need to use #after_build to specify another function to come along after Prepopulate, grab the values it has applied, and make your changes.
function custom_form_alter(&$form, &$form_state, $form_id) { // You might want to put a check somewhere that this should only apply to // new users or new nodes. For example, if ($form['#node']->nid == NULL) $form['#after_build'] = 'custom_prepopulate_after_build'; }
Step 4: Grab the referenced node.
In order to inherit some values from the referenced node, you need to load it.
function custom_prepopulate_after_build($form, &$form_state) { $node = node_load(custom_prepopulate_nodereference_nid($form['field_my_nodereference_field'])); if (empty($node)) { // Bad reference from prepopulate. Nothing more to be done. return; } // Do stuff to your form here. // DO NOT FORGET THIS! return $form; } /** * Get the nid from the string [nid: #] * * @param $value * String containing the nid. * * @return * Integer value of a prospective nid. * @see nodereference_autocomplete_validate */ function custom_prepopulate_nodereference_nid($form_field) { preg_match('/^(?:\s\*|(.\*) )?\[\s\*nid\s\*:\s\*(\d+)\s\*\]$/', $value, $matches); if (!empty($matches)) { // Explicit [nid:n]. list(, $title, $nid) = $matches; return $nid; } return NULL; }
Step 5: Time for dominos
Now that we have a code skeleton with all the data we need, we can start in with some magic.
Put things like this in the “do stuff” section commented in the code block above.
Title Inheritance
if (empty($form['title']['#default_value'])) { $form['title']['#value'] = 'Discussion of "' . $node->title . '"'; }
Taxonomy Inheritance
foreach ($form['taxonomy']['tags'] as $vid => $taxonomy) { if (!is_numeric($vid)) { continue; } if (empty($form['taxonomy']['tags'][$vid]['#default_value'])) { $form['taxonomy']['tags'][$vid]['#value'] = taxonomy_implode_tags($node->taxonomy, $vid); } }
Was that complex?
Seems a bit involved, so I’m working out the best way to produce a helper module.
If what you want is to inherit values into your output, check out the Field Inherit project.
5 comments
I actually did not know that
I actually did not know that the Prepopulate module existed. This is a brilliant way of harnessing its power for parent-child node relationships and dependencies.
I have a question, though, in your
custom_prepopulate_nodereference_nid(), I think it could make sense to grab your nid from $_GET, instead of the regular expression (which may not match if the person has template overrides for the form elements) Or am I mistaken in assuming that the form elements are rendered?In any case, great work! It’s really useful stuff!
The Prepopulate module is a
The Prepopulate module is a really elegant solution for setting the defaults of any or all Form API elements on the page. While I could streamline this code by replicating something specialized for nodereference, I like Prepopulate. I like having the universal functionality to set whatever values I like, wherever I like.
By sticking with it, I keep other people thinking about it in the same namespace. That said, if I do pursue creating a magic module to facilitate what I describe in the post, I might just do something simplify the logic (and the URL).
I do wish the recent security update hadn’t made the URL so messy. It used to be simpler. I am still working out how to pull off some of my [previous tricks](http://stackoverflow.com/questions/3611381/how-do-i-hide-a-cck-nodereference-input-widget-in-after-build “How do I hide a CCK Nodereference input widget in #after_build?”).
This is awesome
Thanks for taking the time to make such a kick-ass tutorial. Can’t wait to try this. Perhaps more importantly, I can’t wait to try out your Field Inherit module (downloading now).
Hey Zach, glad you like it. I
Hey Zach, glad you like it. I wouldn’t want to steal any of joelstein ’s thunder. I mention the Field Inherit module because it has a similar concept of node inheritance. This tutorial focuses on inheritance into form defaults.
With latest prepopulate
With latest prepopulate (2.1), my prepopulation seems to get negated (and none of the ‘dominoes’ fire) with a module as simple as this:
function rs_event_toggles_form_alter(&$form, &$form_state, $form_id) { $form['#after_build'] = 'rs_event_toggles_prepopulate_after_build'; } function rs_event_toggles_prepopulate_after_build($form, &$form_state) { $node = node_load(rs_event_toggles_prepopulate_nodereference_nid($form['field_event'])); if (empty($node)) { // Bad reference from prepopulate. Nothing more to be done. return; } // Do stuff to your form here. $form['title']['#value'] = 'test'; // DO NOT FORGET THIS! return $form; } function rs_event_toggles_prepopulate_nodereference_nid($form_field) { preg_match('/^(?:\s</em>|(.<em>) )?[\s</em>nid\s<em>:\s</em>(\d+)\s*]$/', $value, $matches); if (!empty($matches)) { // Explicit [nid:n]. list(, $title, $nid) = $matches; return $nid; } return NULL; }