Community

Render a full page template from custom menu callback

Hi, I'm developing a fairly complex site where I would like to take a url with arguments and render a page. For example I might have a page with the url /mypage, and extend it so that /mypage/media shows a gallery view of the media items associated with that page and its children; /mypage/summary shows a single-page summary of that page and it's children etc.

At first I thought that Views would be a natural fit for this type of task, but this is a complex bilingual site where content can consist of a mixture of languages and I have a custom mapping structure to determine which content to show to which users in which context. So this is beyond the capabilities of Views, at least as administered from the front end. I spent some time trying to figure out how to construct and render my own Views, but couldn't find any information on how to do this or even if it was possible.

So I rolled my own solution. At first I used template.php to check the value of url(n) and compare it to the contexts I had defined. Then I would load or create the content needed by manipulating the $node and $vars objects and telling it which page template I wanted it to use. This worked great while I was developing with node urls such as /node/n/media, but after I turned on pathauto the system broke, since /mypage/media did not exist as a separate node reference, and no longer falls back to /mypage as it did when /mypage was /node/n.

I got around that by changing the /media arguments to GET queries: /mypage&view=media, /mypage&view=summary etc.

This works well, but I really didn't want to give up the clean urls. So I tried another idea: use hook_menu in a custom module to catch the urls and deliver the rendered page as the result of a custom menu callback. The code looks like this:

function custom_resources_view_menu( $may_cache ) {
   // make a view-like custom path
   $items = array();
   if ( !$may_cache ) {
      // see if we are viewing a media item
      if ( arg(2) == "media" ) {
         $items[] = array(
            'path' => arg(0).'/'.arg(1).'/'.arg(2),
            'callback' => 'view_media_gallery',
            'access' => user_access('access content'),
            'type' => MENU_CALLBACK
         );
      }
   }
   return $items;
}

function view_media_gallery() {
   $path = url( arg(0).'/'.arg(1) );
   $path = i18n_get_normal_path( drupal_get_path_alias( $path ));
   $temp = explode("/", $path);
   $nid  = $temp[1];
   $node = node_load( $nid );
   return theme( 'gallery_page', $node );
}
function teara_gallery_page( &$node ) {
   $node -> vars['node'] = $node;
   $node -> vars['context'] = 'media';
   return _phptemplate_callback( 'page', $node -> vars );
}

Now this code does work, but there are some problems and confusing things:

  • This only works if the template I use is 'page'. The actual page template which I am using to render the page into is being fed into it by overriding $vars['template_file'] in template.php. This is what I was doing originally when /node/n/gallery would fall back to /node/n and I would get the 'media' context out of the url. I expected that I would be able to call the template I wanted to use here instead of 'page'. However if I use another template here, I get the template contents, but none of the actual rendered contents.
  • This only works if page.tpl.php has only one line: "print $content;" - if page.tpl.php contains other markup, then that markup will get used, but the entire rendered page using the correct template will be output where "print $content;" appears in page.tpl.php - effectively this wraps a full HTML page in another HTML page. I made a new template called raw.tpl.php which only included the line "print $content;" but this renders a blank page.
  • Using "return node_view( $node, FALSE, TRUE, FALSE );" instead of _phptemplate_callback results in the base node being rendered without using any template.

So what I have works, but it is a bit confusing and far from ideal as it requires us to warp page.tpl.php for our special purpose here and makes it fairly useless as a general page fallback. It seems odd that we have to use 'page' as the template and that there doesn't seem to be another method of rendering a page given a loaded node and some defined variables to feed it.

Can anyone offer a better solution? Perhaps there is another way to render a complete page given that I know the page template and can load the base node? I do need to load that node by the way - even though I am replacing the node $body with custom rendered content, the rest of the template requires information from the base node.

Or did I miss something with views? Can I create a View where I can perform my own queries and render the view contents with the same information I have here: a template, a node, and a context taken from the url?

Thanks!

Scott

Comments

Found partial solution

I found an easy partial solution which was to use mod-rewrite to convert my clean paths to query strings, but I'd still love to know how to generate an entire page programmatically if it is at all possible. Anyone?

Have a look at Wildcard Loader Arguments

I think the first thing to do is get your routing settled. I was thinking about something similar the other night and stumbled upon this - http://drupal.org/node/224170. There's a lot of info in that post. As far as module templating, I haven't really got my head around that yet but I do know it can be done - I think with a combination of menu callbacks/routing and hook_theme. More info on that here - http://drupal.org/node/165706.

This is Drupal 5, I'm pretty

This is Drupal 5, I'm pretty certain.

IT Sherpa, Drupal Mason

nobody click here