I'm trying to use hook_menu to create a link to a view which takes an argument. However if I use the path (in $items[view-path/%dest]) that I've already set as the path in the view then the link doesn't appear. I'm guessing there's a path conflict somewhere. Is there a way round this? Or can I use another method to return the view?
I'm using the following code:
/**
* implementation of hook_menu().
*/
function sign_custom_menu() {
$items['view-path/%dest'] = array(
'title' => 'Link to view',
'page callback' => 'sign_custom_hello',
'page arguments' => array(1), //(corrected typo from 'page arguements')
'access callback' => TRUE,
'type' => MENU_NORMAL_ITEM,
'menu_name' => 'menu-student-links',
);
return $items;
}
function dest_to_arg() {
// would normally be dynamic to get view with correct argument
$arg = 73;
return $arg;
}
Thanks in advance.
Addition
function sign_custom_hello() {
//return t('Hello!');
}
I managed to answer my problem. Basically I used a different path to the one I had set in the view and then used views_page() as my "page callback". I passed it the arguments for the view, the page ID and it's own additional arguments to make the view work. I was able to use a wildcard in the menu item to pass to views_page() by using the to_arg() function that works with hook_menu() to pass in wildcards. The 'page arguments' pass in the three arguments. The last argument, "1" is a reference to which position in the path the argument appears (starting from 0).
The working code is:
<?php
/**
* implementation of hook_menu().
*/
function sign_custom_menu() {
$items['view-path/%dest'] = array(
'title' => 'link to view',
'page callback' => 'views_page',
'page arguments' => array('view_name', 'page_1', 1),
'access callback' => TRUE,
'type' => MENU_NORMAL_ITEM,
'menu_name' => 'menu-student-links',
);
return $items;
}
//this function is needed from the "%dest" argument in hook_menu above
function dest_to_arg() {
// would normally be dynamic to get view with correct argument
$arg = 73;
return $arg;
}
?>
I don't have much experience with wildcard URLs in my custom modules much, but I researched the issue in the Pro Drupal Development book. From what I read in the "Wildcards and Parameter Replacement" section on page 77, I think you may want to use $items['view-path/%'] instead. Using %dest apparently makes drupal look for a dest_load function.
Items that appear in menus can't be created by a wildcard router item: each menu item corresponds to exactly one path. That is, if you have a router item that is foo/%bar and %bar can have 10 different values, Drupal's menu system isn't going to create 10 new menu items off of one router item definition.
So what you're going to need to do is create a router item for each possible argument ahead of time. Otherwise, you're going to have to look outside Drupal's menu system and think about creating a separate Views block that looks like a menu but is really a Views unordered list of the available options.
To do the former, you need to implement hook_menu_alter() to add your custom router item after everything else, including the wildcard router item you're trying to override. Your custom router item will be more or less the same as the wildcard router item, but with some defaults set that would normally be derived from the wildcard.
For example, if I wanted to create a new router item for user/1/edit which overrides the built-in user/%user_category/edit, I'd implement hook_menu_alter() as so:
function mymodule_menu_alter(&$items) {
// user_edit and user_edit_access expect a user object
$account = user_load(array('uid' => 1));
$items['user/1/edit'] = array(
'type' => MENU_CALLBACK,
'page arguments' => array($account),
'access arguments' => array($account),
) + $items['user/%user_category/edit'];
}
In this example, user/%user_category/edit calls user_edit() and user_edit_access() for the page and access callbacks, respectively, and they both attempt to use the wildcard. Since there is no wildcard in your router item, you need override the arguments to say "check user 1".
You'll do this for each and every possible value of the wildcard.
But this isn't enough: notice I used MENU_CALLBACK instead of MENU_NORMAL_ITEM. If you use MENU_NORMAL_ITEM, your router item is going to show up in the Navigation menu, not in your custom menu, even if you set menu_name (I don't know why this is: it should work). But you can get around this by using menu_link_save().
Consider this implementation of hook_init():
function mymodule_init() {
$router_path = 'user/1/edit';
// Check to see if the custom router item has been added to menu_links.
// This is to ensure the menu has already been rebuilt.
$router_item = db_fetch_object(db_query("SELECT * FROM {menu_links} WHERE router_path = '%s'", $router_path));
// Only create a new menu item if the router item exists and it
// hasn't already been created (it's hidden until created).
if ($router_item && $router_item->hidden) {
$item = array(
'link_title' => 'Edit Administrator',
'link_path' => $router_path,
'menu_name' => 'primary-links',
'router_path' => $router_path,
'mlid' => $router_item->mlid,
);
// Save the menu item.
menu_link_save($item);
}
}
In this implementation, it checks to see if the custom router has already been created and hasn't been otherwise modified. If this is true, it creates a menu link in the primary links menu that references your custom router item.
Obviously, since this is in hook_init(), it'll perform the check on every page: this is to ensure it fires after the menu is rebuilt. It shouldn't be much of a performance hit, but it's something to keep in mind.
As you can see, it's a long and drawn out process to do this: if you're not going to go the Views fake-menu route, it might be better to just manually create the links yourself.
Related
OK, so I have a weird thing to do, I'd appreciate any help. When you go to the Drupal admin panel and click Structure, you get a menu which contains Blocks, Content types, Menus and so on.
Is there a way I can programatically build one of those menus based on the path? For example if I have my module named test and all sub-actions of my module are located at www.drupalsite.com/admin/test/action_name, can I build my menu with all the /test/action_name there exist in the current module?
I know there's the option of hard-coding the menu, but I want to avoid it if possible.
It's hard to be that descriptive without some more information but you'd just need to implement hook_menu() and loop through your list of actions, creating a menu item for each. Every time the menu is rebuilt your menu hook will be called and the current list of actions will be built as menu links. Something like this:
function mymodule_menu() {
$actions = mymodule_get_actions_list();
foreach ($actions as $action) {
$items['admin/test/' . $action->name] = array(
'title' => $action->name,
'access arguments' => array('some permission'),
'page callback' => 'mymodule_callback',
'page_arguments' => array($action->name)
);
}
return $items;
}
function mymodule_callback($action_name) {
// Load the action and display the page
}
After you call your custom code to create one of these actions, be sure to call menu_rebuild() so your hook runs and the new action is added to menu.
You can't use wildcards in menu paths? A quick summary of my problem (which I've made sure makes sense, so you're not wasting your time): I have a menu which i'm showing on node pages of a certain content-type. My path to a node page would be like...
events/instal2010
...where instal2010 would be the name of an event (event is the content-type).
I'm using the Context and Menu Block modules to place a menu in the sidebar on that page...
Event (the default active item)
Programme
Visitor info
Book tickets
... where the path for Programme would be
events/instal2010/programme
So for this to work for many different events, those menu items need a wildcard in their path, e.g.
events/*/programme
Perhaps it's time to ditch menus and just use a block with php to determine what page we're on from the URL.
Any advice from experienced hands would be awsome, thanks.
You cannot create menu items with wildcards from the administrative interface of Drupal, but you can create menu items with wildcards in a module. I would recommend creating a custom module that uses hook_menu() to create the menu items. An example implementation would look something like:
function YOURMODULE_menu() {
$items = array();
$items['events/%/programme'] = array(
'title' => 'Programme',
'description' => 'Loads a program page',
'page callback' => 'YOUR CUSTOM FUNCTION NAME', // Custom function used to perform any actions, display the page, etc
'page arguments' => array(1), // Passes wildcard (%) to your page callback function
'access callback' => TRUE, // Change if you want to control access
'type' => MENU_NORMAL_ITEM, // Creates a link in the menu
'menu_name' => 'primary-links' // Adds the link to your primary links menu, change if needed
);
return $items;
}
In $items['events/%/programme'] = array(, the % is the wildcard and it will be passed to your page callback function. It may be helpful to read more about hook_menu() and the Anatomy of hook_menu may also help as well.
Kind-of a crazy question here...
I have a view display that's set up as a page. It looks great in theme A (desktop), but terrible in theme B (mobile). So I made a different version of the view for theme B. Since the desktop/mobile 'sites' are the same just with different themes, the url for this page will be the same regardless of hte theme selected.
So I would like to be able to point the user to:
mysite/this_crazy_view
and have the returned page select the proper view depending on which theme it's in. I know that if I were using blocks I would just assign the appropriate blocks to the page in question on a theme-by-theme basis, but since the displays are using page display I don't know what the right approach would be.
I would rather not rebuild the views as blocks if I can help it (if it can't be helped, so be it...) so I was hoping there was some way to conditionally load the view via the tpl.php file or something like that...
The code I'm using in my module (per #Clive 's recommendation below) is:
<?php
function bandes_custom_hook_menu() {
$items['charley_test'] = array(
'title' => 'Title',
'access arguments' => array('access content'),
'page callback' => 'bandes_custom_set_page_view',
'type' => MENU_NORMAL_ITEM );
return $items;
}
function bandes_custom_set_page_view() {
global $theme_key;
$view_name = $theme_key == 'mobile_jquery' ? 'course_views_mobile' : 'course_views';
$display_id = 'page_5';
return views_embed_view($view_name, $display_id);
}
?>
I've cleared the cache a number of times and tried a variety of different paths in the $items array. The course_views and course_views_mobile both definitely work on their own.
I was also wondering if I could just create a views-view--course-views--page-5.tpl.php which contains almost nothing aside from the views_embed_view(course_views_mobile, page_5) part? (Only on one of the two themes...)
Actually I think the answer was simpler than all of the above. The redirect thing was giving me fits, so I removed the module, reset the paths to what I had been using, and tried the template/theme approach instead.
This is: views-view--course-views--page-5.tpl.php, only used on the mobile theme, but referring to the non-mobile view (kinda gives me a headache, but it works)
<?php
//get the view
print "IM IN YR VUE, MESSING THNGZ UP!"; //yeah, I'm going to remove this part...
$view_name="course_views_mobile";
$display_id="page_5";
print views_embed_view($view_name, $display_id);
?>
Any reason that shouldn't work? (Or why it is a really bad idea?)
Me again :)
I just thought of an easy-ish way around this actually; if you can change the URL of your views to something other than the path you want to access them at (any path would do) you could implement a hook_menu() function in a custom module for that path, to make the choice depending on your theme:
function MYMODULE_hook_menu() {
$items['this_crazy_view'] = array(
'title' => 'Title',
'access arguments' => array('access content'),
'page callback' => 'MYMODULE_crazy_view_page',
'type' => MENU_CALLBACK // or MENU_NORMAL_ITEM if you want it to appear in menus as usual
);
return $items; // Forgot to add this orginally
}
function MYMODULE_crazy_view_page() {
global $theme_key;
$view_name = $theme_key == 'foo' ? 'theView' : 'theOtherView';
$display_id = 'page_1'; // Or whatever the page display is called
return views_embed_view($view_name, $display_id);
}
That should do the trick
What is the difference between MENU_NORMAL_ITEM and MENU_CALLBACK?
The more precise answer is that hook_menu() creates router items, and also menu links are generated. MENU_NORMAL_ITEM generates a menu link which will appear in the navigation menu, while MENU_CALLBACK does not add a menu link, so it won't appear in the menu.
MENU_NORMAL_ITEM creates a menu item while MENU_CALLBACK doesn't. That is the only difference.
Addition to the above comment, MENU_CALLBACK can be used in some scenarios such as AJAX.
Example: example.com/ajax/country_list is a MENU_CALLBACK which returns a list of countries in HTML,JSON or XML format... This menu doesn't appear in the browser.
You can visit http://api.drupal.org/api/group/menu/6 for more information.
Drupal maps urls to functions.
Means you need a function for every URL.The function is mostly present in a module.
ex mysite/add will have a mapping to a function in a module.
Many cases we don't want the URL as as a menu item but intend to use it for other purposes. The best example being a Ajax callback.
Ex: you have an auto-suggest form which calls a function suggest in the server.The front end Ajax will need a url to fire the request.Let the url be www.mysite/suggest
This is the case when you need a MENU_CALLBACK
function example_menu() {
$items['suggest'] = array(
'page callback' => 'example_suggest',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
return $items;
}
function example_suggest() {
//you can return the autosuggested items to the page
}
I have a custom Drupal module displaying some data in a table. Each row has a link which if clicked will delete the relevant row. Specifically, when the link is clicked it will take the user to a confirmation page. This page is really just a drupal form which says 'are you sure' with two buttons: 'Yes', 'No'. I figure I will need to pass the rowID to the confirmation page.
My question: What is the typically way to pass data to a new page in Drupal 7? I guess I could just add the rowID to the URL and use the $_GET[] from the confirmation page... I don't think this is very safe and was wondering if there was a better 'Drupal' way.
Thanks!
You'd use something like the following
<?php
function yourmod_menu() {
// for examlple
$items['yourmod/foo/%/delete'] = array(
'title' => 'Delete a foo',
'page callback' => 'drupal_get_form',
'page arguments' => array('youmode_foo_delete_confirm', 2), // 2 is the position of foo_id
'access arguments' => array('delete foo rows'),
'type' => MENU_CALLBACK,
);
return $items;
}
function yourmod_foo_delete_confirm($form, &$form_state, $foo_id) {
// load the row
$foo = yourmod_get_foo($foo_id);
// build your form, if you need to add anything to the confirm form
// ....
// Then use drupal's confirm form
return confirm_form($form,
t('Are you sure you want to delete the foo %title?',
array('%title' => $foo->title)),
'path/to/redirect',
t('Some description.'),
t('Delete'),
t('Cancel'));
}
?>
You can look here for examples of how core modules do it (have look at node_delete_confirm)
The simplest solution would be to use an existing module created for this purpose:
http://drupal.org/project/entityreference_prepopulate (for entity references)
http://drupal.org/project/nodereference_url (for node references)
http://drupal.org/project/prepopulate (for other form values)
You can configure which form values can be set from the URL, then rewrite the fields displayed in your table to generate the necessary links.
If the data are nodes, you can make the link node/%/delete where % is the nid. Drupal knows how to handle the delete page, as its a core path. Then, the delete confirmation follows the rest of the system and is very 'Drupal'.
I am not sure if this changed at all in Drupal 7, but this is what I did for countless modules.