In the first article of our series, we introduced Display Suite and showed how it allows you to easily visualize nodes and fields without writing any code. Just like Panels, Display Suite is one of those indispensable tools for Drupalistas who are fed up with hacking templates to do stuff which shouldn't be that complicated. If you haven't heard of Display Suite already, check out the project page!
We demonstrated how you could use Display Suite to show lists of referenced nodes within a build mode. However, you don't get a lot of control over how and what Display Suite shows. Display Suite should be able to do more, right? Let's find out.
We'll return to our cookbook example. Our Cook nodes show lists of referencing Recipe nodes. This results in a neat list of recipes for a particular chef. But what if our top chef stars in a regular TV show and we want to display in which show he'll be featured next?
First, create a new content type called 'Show' with two extra fields. First, a node reference field called 'Cook' which - obviously - references available cook nodes. Second, you'll need to install the Date module and add a Datetime field called 'Transmission'. This field indicates when a show will be or has been aired.

Now, if you'd try the solution outlined in our previous article, you'll notice it will show all transmissions while we are interested in showing only the next upcoming show in which a chef will star. Backstage, Display Suite runs a basic SELECT query which fetches all the shows with a reference to a particular cook. You don't get any control over the conditional behavior of the query. This makes perfect sense because DS is not intended to handle complicated queries. If you want to get anything done in Drupal, you'll let Views handle it. And of course, the authors of Display Suite have provided a way to integrate views.
The basic idea here is to pass the node id (nid) of the active cook node as an argument to a block View turned into a Display Suite field. Within Views, we are free to use filters, relationships, row style and list style plugins,... Display Suite will integrate the output of the view transparently into the active build mode.
Start by creating a new block View. Add a new argument (not a filter) to your View. This should be the node reference field you've added to your 'Show' content type. Configure the argument to provide a 'default argument'. Set the argument type to 'fixed entry'. Leave the 'default argument' textfield empty. Now add a few fields to your View. The node title and the transmission time will do the job.

Let's move over to the DS Fields manager at 'admin/ds/nd/fields'. This panel allows you to manage how Display Suite handles fields. Even better: it allows you to turn blocks into fields which can be used within any build mode. Sweet!
Click on the collapsed 'add new block field' fieldset. This form allows you to turn a block into a field. Just provide a name (i.e. Transmissions) and a machine field key (i.e. transmissions). Select the block you want to display in your field. You'll notice your Views block will be available too. Click 'save block field'. Make a note of the field key you've just entered. We'll need it later.

Switch to the DS Layout manager at 'admin/ds/layout' and select the 'Full node' build mode for your Cook type. You'll find your new 'Transmissions' field between the disabled fields. Just drag and drop it in a region.
Let's take a peek at how things look front end. Create a cook node, a few recipes and a few transmissions which reference your cook. You'll see how the referenced recipes will show up nicely. But the transmissions are missing in action. Remember how we provided a default argument to the view with a fixed, empty entry? Well, that's our culprit.
At this point, you're probably wondering why on earth I made you add an argument to your block view. If you want to filter out nodes based on a node reference dynamically, you'll need an argument. If we've been taught anything when it comes to Views, passing arguments to block Views is near impossible. Then again, we still need a block view to turn into a Display Suite field.
Actually, the question here is: how do we pass the node id of our active cook to a block view?
We'll need to dive into a bit of code to do that. We're going to bridge the gap between the node and the View with the magic of hook_ds_fields_alter(). In a nutshell: we'll override the way Display Suite handles the field with our own custom implementation.
Create a new custom module and add this implementation of hook_ds_fields_alter().
/** * Implementation of hook_ds_fields_alter(&$fields) */ function dsexample_ds_fields_alter(&$fields) { // 1. We change the type of the field to function instead of block $fields['transmissions']['type'] = DS_FIELD_TYPE_FUNCTION; // 2. Defines a nice formatter callback method: render_view_block_arguments $fields['transmissions']['properties']['formatters']['render_view_block_arguments'] = t('Default'); // 3. Add some extra properties which we can use in the function later on. 'key' => 'default_argument_fixed', 'field_object_value' => 'nid' ) ); } }
Remember the DS field key you had to keep in the back of your head? We'll need it here. $fields is an array of all the available fields for any given node and build mode. You'll need to single out the transmissions field since that's the one we are going to alter. So change the key of the $fields array to match with your own field key.
Now, let's skim through the code.
1. Every field has it's own type which is defined as a constant. The transmissions field is a DS_FIELD_TYPE_BLOCK constant. We'll change that to a DS_FIELD_TYPE_FUNCTION. That's how we let DS know that it has to run a custom formatter function instead of a block when it's parsing the field.
2. We'll need to let DS know which function to execute. You'll do that by setting a new key/value pair in $fields['transmissions]['properties]['formatters]. The key is the function name (i.e. render_view_block_arguments) and the value is the label which will be shown on the lay out manager when you select a formatter.
3. Finally, we'll need to define which field we are going to pass to the view as an argument. We'll do that in $fields['transmissions]['properties]['arguments]. This is actually a good place to add all sorts of extra data to our field to be used further down the road. Recall the empty fixed entry argument we've set in our block View? We're going to fill the 'arguments' property with the node id of the active cook node. Change 'field_cook_nid' with the machine name of your Views argument. Exporting your View in code allows you to retrieve the correct name.
The 'key' property is set to 'default_argument_fixed' which is just fine. The 'field_object_value' property should match with the name of the property of the active node you want to pass to the View as an argument. Obviously, we want to pass the node id of the active node.
We're not done yet. We still need to define our render_view_block_arguments() formatter function. Just copy and paste the code below.
/** * Render a ds field which was originally a block through this function. */ function render_view_block_arguments($field) { $data = ''; // Load the view. if ($view = views_get_view($name)) { if ($view->access($display_id)) { // let's pass all arguments we got from our parent and pass it to our embedded view. foreach ($field['properties']['arguments'] as $argument => $value) { $view->display[$display_id]->handler->options['arguments'][$argument][$value['key']] = $field['object']->{$value['field_object_value']}; } } $output = $view->execute_display($display_id); $data = theme('block', (object) $output); } } $view->destroy(); } return $data; }
Let's take a moment to review this function. It will be run when our transmissions DS field is parsed. First it will retrieve our View, then it will sink the properties we've set in our hook_ds_fields_alter() implementation and finally it will execute and display the View.
Check and double check your code for any mistakes. Let's take a peek at our cook node. Will it display the upcoming feature of our star chef? Well, not yet. Display Suite caches field settings to keep things running smoothly. hook_ds_fields_alter() is only run when a field hasn't been cached yet. Clearing your cache should do the trick. Remember, you have to do this every time you make a substantial change to hook_ds_fields_alter().
And... there you have it:

This is how things should be looking. A nice list of transmissions featuring your star chef
Mission accomplished!
Tips
Since we're using Views now to control our list, we can use it's entire toolset of filters, sorting options, relationships and what-not. We can push things a bit further. Display Suite also provides a row style plugin for Views. Which actually means you can define a build mode for your nodes in Views and control it through the DS lay out manager
Caveats
This technique has a few caveats though which can discourage the intrepid developer.
First and foremost, you'll need to clear your cache if you update hook_ds_fields_alter(). It's the first thing you should try in case of trouble.
The dreaded 'error on line 294 in ds.module' error. If you've made a typo in $fields[]['properties']['formatters'], DS will break because it tries to execute an unexisting function. Fix the error and try clearing your cache. If the problem persist, you should delete your field in 'admin/ds/nd/fields', clear your cache, then create it again and clear your cache once more.
Your View might not get executed without any apparent reason. Well, actually there is one. If your View name is too long (i.e. carousel_of_images_of_ponies_and_kittens), the Blocks module runs into trouble. Views will return an MD5 hash of your View name instead. Our render_view_block_arguments() can't find the View based on the hash name and break. For good measure, keep those View names short and sweet.
Even then, your View might stubbornly refuses to be executed. Check the block display in your View through 'admin/build/views'. The argument property in your block display is probably inherited from the defaults display. The above code expects the property to be explicitly set on the block display. So, click the 'override' button of the argument, update and save the View.
The perceptive reader will no doubt notice that you'll need to accommodate for every particular View field by hard coding it's name and the arguments. This can result in a lot of code repetition. It also means writing Yet Another Custom Module. Not to mention hard coding parameters against a specific implementation is bad practice.
So, the next step would involve devising a more generic, reusable approach.






As you mention in your next
As you mention in your next to last paragraph, I'm curious if these two functions in a custom module are really a better solution than "hacking templates to do stuff which shouldn't be that complicated."
over complicating?
Does something about display suite muck with views argument handling? If cook is just a content type, why not use the 'node id from url' option in the 'provide default argument' view config? Yet another simple option would be to use a code field and views_embed_view to place the block in the field and grab the nid from the url with arg().
Argument handling
Display Suite doesn't muck with views argument handling. DS manages - amongst other things - node displays as a whole. In this case, the code takes the nid of the node DS is building and passes it as an argument programmatically to our view.
Yes, you could use the 'node id from url' option, but that only works when you're actually viewing a single node (through the /node/%nid path)
Yes, you could also go for views_embed_view() using arg(), but then you could also use the execute PHP code option in the view itself and do without the extra code field. Of coruse, you don't want to do that. First, it only works when there's actually a nid in the path (and you have to single it out). Second, you're storing executable code in the DB which should be avoided at all times.
I agree, though, that this is an elaborate solution, but it does the job in a clean, safe and reusable way. If you don't want to go through the hassle of using Views, you could also write a custom query + theming function which you would use in the render_view_block_arguments() function. As long as it outputs rendered HTML, you're good to go.
There are some escape
There are some escape characters on line 6 of your come ("$amp;") that cause parse errors.
Fatal error: Call to undefined function
I got the problem: Fatal error: Call to undefined function () when setting the display field type to DS_FIELD_TYPE_FUNCTION
I solved this problem by changing the ds.module by setting
$function = (isset($field_settings[$key]['format'])) ? $field_settings[$key]['format'] : key($field['properties']['formatters']);
to
$function = (isset($field_settings[$key]['format']) && ($field_settings[$key]['format'])!="") ? $field_settings[$key]['format'] : key($field['properties']['formatters']);
on line 293 because the $field_settings[$key]['format'] was "" and isset() was throwing that fatal error because of the "".
the ideal should be a module update to this patch or something.
D7
Hi,
nice tutorial. It would be nice to be pointed in the right direction for a D7 version of this.
simply changing to the d7 hook creates undefined index: function error in ds.module, line 385.
The $field['function'] property is indeed empty for the field 'transmissions'
Do we need to hardcode the $field['function'] somewhere?
any help would be appreciated. I think this functionality is really awesome.
greets,
I added following line to the
I added following line to the hook_ds_fields_info_alter():
$fields['upcomming_show']['function'] = 'render_view_block_arguments';
when I hardcode the view arguments like:
$view->display[$display_id]->handler->options['arguments'][$argument][$value['key']] = 2;the resulting output still lists *all* the transmissions, instead of only the ones of the subject cook.
may it have something to do that arguments in views3 moved to contextual filters?
thanks
D7 issues
Hi Raf,
I'm noticing the same behavior. I'm trying to pass a few term id's to a view and I'm running into the same problems. There are a few differences between D6 and D7.
A few notes so far:
* You're right adding the function property in the info_alter hook. If you're defining your field as DS_TYPE_FUNCTION, you're going to need it. I'm not sure if the formatter definition is still required.
* The implementation of the function is also different. FIrst of all, the object isn't in $field['object'] but in $field['entity']. So there's a difference. Then you have to cope with the way the information is stored in the entity. I'm thinking about using the field API to fetch the required data transparently rather then directly editing the arrays.
* Finally, there's Views 3. As far as I can tell, there isn't a big difference between the way Views 2 and Views 3 objects are build. Functionally speaking, the same logic (passing a fixed argument) still applies.
I'm going to do an updated article as soon as I can get it working in D7.
D7 issues - hopefully solved someday
I tried to study this issue as well. Unluckily I'm such a novice in Drupal that I was not able to solve it completely. Anyway some aspects I found - With the changes discussed above I managed at least to get the render_view_block_arguments to be run, but I just don't get anything out of executing the display ($output stays empty, but display_id has some value (1 in my environment)). So probably somehow the arguments/variables provided for Views3 are not OK. But it was impossible for me to figure this out. Hopefully someone more skilled in the art can do the job.
I really hope to get this in D7 as it provides super cool way of making DS and Views to play together. Although already without this DS is such a nice thing that I would not know what I would do without it.
D7, DS & Views3
Excellent question - Unfortunatly at least to me Views3 documentation is still not in best condition and it seems very difficult to make this similar handling for D7 to work :-). So any help on identifying how to make this to work with D7 would be super!
solved drupal 7
@raf instead of using
$view->display[$display_id]->handler->options['arguments'][$argument][$value['key']] = 2;
try
$view->args[0] = $field['entity']->{$value['field_object_value']};
or as in your example
$view->args[0] = 2;
that should pass the argument to the view
EVA for D7
Great tutorial, nicely explained.
I might be wrong, but I think Entity Views Attachment module by Jeff Eaton might perform a similar function in D7.
If you create an 'entity content' views display using ev, use node as the entity type and your content type in the 'bundle' parameter then you can select to pass the entity 'id' through as an argument, which is then offered as a contextual filter (argument) in the view.
EVA then puts the view into the node content as a field, which you can move around etc. This method (since D7) has the advantages of maintaining no extra custom code.
http://drupal.org/project/eva
apologies if I have misread the use-case described here.
In D7, there isn't even a
In D7, there isn't even a hook_ds_fields_alter(). Will try EVA.
A working D7 example: /** *
A working D7 example:
/**
* Implementation of hook_ds_fields_alter(&$fields)
*/
function mt_custom_ds_fields_info_alter(&$fields, $entity_type) {
if (isset($fields['block_teilnahme'])) {
// 1. We change the type of the field to function instead of block
$fields['block_teilnahme']['field_type'] = DS_FIELD_TYPE_FUNCTION;
// 2. Defines a nice formatter callback method: render_view_block_arguments
$fields['block_teilnahme']['function'] = 'mt_custom_render_view_block_arguments';
// 3. Add some extra properties which we can use in the function later on.
$fields['block_teilnahme']['properties']['arguments'] = array(
'content_id' => array(
'key' => 'default_argument_fixed',
'field_entity_value' => 'nid'
)
);
}
}
/**
* Render a ds field which was originally a block through this function.
*/
function mt_custom_render_view_block_arguments($field) {
$data = '';
list($module, $delta) = explode('|', $field['properties']['block']);
list($name, $display_id) = explode('-', $delta);
// Load the view.
if ($view = views_get_view($name)) {
if ($view->access($display_id)) {
if (isset($field['properties']['arguments'])) {
// let's pass all arguments we got from our parent and pass it to our embedded view.
foreach ($field['properties']['arguments'] as $argument => $value) {
$view->args[0] = $field['entity']->nid;
}
}
$output = $view->execute_display($display_id);
if (!empty($output['content'])) {
$data = '' . $output['subject'] . '' . $output['content'];
}
}
$view->destroy();
}
return $data;
}
Post new comment