I just created a project for a module I am already using locally.
http://drupal.org/project/pageapi
The module hijacks template_preprocess_page, and calls its own hook, hook_pageapi(). This hook runs after hook_init(), and after the page callback, but before any other preprocess page stuff. This is the ideal place for things like drupal_add_js(), especially in cases where we want to keep this out of hook_init().
In the future, I would like this to become the starting place for alternative js and css aggregation, at least on D6.
See also
CSS and JS alteration hooks module
I could just upload my local code, but I would prefer some community feedback before.
EDIT: Changing the syntax a bit :)
Iron out the API details
This is how a typical hook_pageapi() implementation might look like:
<?php
function mymodule_pageapi($api) {
// Add a stylesheet that is sitting in the module dir.
$api->css('css/mymodule.css');
// Add an external stylesheet, with absolute url, no aggregation please.
$api->cssExternal('http://example.com/external.css');
// Add a Drupal.settings.mymodule.colors js setting.
$api->jsSetting('mymodule.colors', array('board' => 'blue', 'stone' => 'white'));
// Add js file that is sitting in the module dir.
$api->js('js/mymodule.js');
// Script that is sitting in another module.
$api->js('js/anothermodule.js', 'module:anothermodule');
// Add a script sitting in libraries folder
$api->jsLibrary('jquery.cycle', 'jquery.cycle.min.js');
// or using the generic js() method with location specifier
$api->js('jquery.cycle.min.js', 'library:jquery.cycle');
// Add inline script
$api->jsInline("alert('foo');");
// Add a script that is dynamically generated with a php request, registered in hook_menu().
// Url would be http://mysite.com/mymodule/dynamic.js?abc=def
$api->jsDynamic('mymodule/dynamic.js', array('abc' => 'def'));
}
?>There are some things about the API syntax and naming schemes, which can be discussed.
Should we remove the "add" at the beginning of each method?
Should it be "script" or rather "js" ?
What about additional option parameters?
How to add scripts and css of other modules?
This is all bikeshedding, but now is the last chance :)
Alter or suppress js and css aggregation
How would we do that?
<?php
function mymodule_pageapi($api) {
$api->suppressJsAggregation();
$api->suppressPageVariable('breadcrumb');
$api->setJsAggregationHandler($handler);
}
?>Something like this..
Comments
Interesting. What's the
Interesting. What's the rationale, though, for using this instead of using drupal_add_(css|js) in hook_init() or elsewhere?
Also, why the OOP approach?
The Boise Drupal Guy!
hook_init() is a crowded
hook_init() is a crowded place, and so is hook_preprocess_page().
Modules fight ("module weight") to place their implementations in the desired position. If they don't, we see side effects with premature theme initialization, premature menu_get_item(), etc.
This alone would be a motivation for an additional hook, to release some of the pressure.
Nor, for drupal_add_(css|js) in particular:
Other things would be stuff with drupal_set_breadcrumb and menus, which again likely depend on menu_get_item().
Why the OOP approach?
- for convenience, shorter and more expressive/semantic syntax.
- it feels awkward having to drupal_get_path('module', $module) every time.
- i just think this new hook anatomy is the future :) (*)
(*) Why is this the future?
With hooks written in this way, it is much easier for the "hook provider" to change what actually happens behind the scenes, and to extend the possibilities of the hook.
For instance, in my local implementation, a $api->addModuleScript($file) will simply run a drupal_add_js() immediately. However, a future implementation might collect the stuff in an array first, so other modules can sort it, before it is added with drupal_add_js().
Also, the same hook could be invoked with different implementations for $api.
I don't know about "the
I don't know about "the future…" It looks like you are using objects merely as a wrapper around calling functions. I'm not sure the overhead adding by using objects like this would be worth it.
The Boise Drupal Guy!
It looks like you are using
The general point is, this "cheap" implementation can at one point be replaced with something else.
For instance, instead of a direct call to drupal_add_js(), one could collect the js files in the $api object, and then add it with drupal_add_js() all at once.
Now, to be honest, in this situation it would probably make little sense to do anything other than direct drupal_add_js() or drupal_add_css(). A lot of css and js is added outside of hook_pageapi, using core's drupal_add_js() and drupal_add_css(), so there would be little benefit in doing it differently for a few js and css added in hook_pageapi().
So, in case of pageapi js and css, it is really just syntax candy.
There are other examples imaginable, where this pattern really shines.
Think of the typical info hook - hook_imagecache_presets(), hook_menu(), etc.
When I produce a site, the imagecache presets are typically defined in code, like this:
<?php$api->addPreset('forum_userpic')->scale(100, 100, FALSE);
$api->addPreset('frontpage_section_icon')->scale(80, 80, FALSE);
$api->addPreset('frontpage_slider')->scaleAndCrop(183, 121);
?>
This is far less verbose, and easier to read (imo) than a huge nested array of definitions.
(and easier to manage and deploy than presets configured via the web interface)
- that much about syntax.
Now imagine a future version of imagecache, where this is the only way to define imagecache presets.
Internally this might still be managed as a nested configuration array, but:
- no other module author needs to learn this nested array structure
- the author of imagecache can change the structure of the nested array used internally, or replace this with something completely different, without breaking the api.
I am using this pattern more and more in my own modules, and I think it has potential.
Okay, well, you're kind of
Okay, well, you're kind of saying "I think other people should change their modules to work this completely different way that I just came up with." I don't think they're going to be very receptive to that…
The Boise Drupal Guy!
I'd rather say, if you write
I'd rather say, if you write a new module, this is something you might consider.
I will do it anyway, for my own modules.
The "old" way does not become any less valid or justified.
I think I am now in favor of
I think I am now in favor of a shorter syntax for the most frequently used things.
<?phpfunction mymodule_pageapi($api) {
$api->js('js/mymodule.js');
$api->js('js/anothermodule.something.js', 'module:anothermodule');
$api->css('css/mymodule.css')->aggregation(FALSE);
$api->inlineJs($script);
// or rather 'jsInline' ?
$api->jsInline($script);
// or some __set() magic, for easy heredoc?
$api->addedInlineJs = <<<EOT
alert('foo');
EOT;
}
?>
jsSetting signature
I wonder what would be a good signature and behavior for jsSetting.
<?php
function mymodule_pageapi($api) {
// we want to set Drupal.settings.mymodule.abc = 'def'
$api->jsSetting('mymodule', array('abc' => 'def'));
$api->jsSettings(array('mymodule' => array('abc' => 'def')));
// what about method chaining?
$api->jsSetting('mymodule')->set('abc', 'def');
$api->jsSetting('mymodule')->set(array('abc' => 'def'));
// what about deeper hierarchies?
$api->jsSetting('mymodule.abc', 'def');
// and some stuff I am not going to do:
// Should the module namespace be implicit?
// this is still about Drupal.settings.mymodule.abc.
$api->jsSetting('abc', 'def');
$api->jsSettings(array('abc' => 'def'));
// with an option to change the namespace?
// (don't really like that)
$api->jsSettings(array('abc' => 'def'), 'anothermodule');
}
?>
Changing the favicon
This is not online yet, but I have it on my local version.
You can now use pageapi to change the favicon, like this:
<?php
function mymodule_pageapi($api) {
switch ($_SERVER['HTTP_HOST']) {
case 'test.example.com':
// File is sitting in sites/all/modules/custom/mymodule/favicon/favicon-test.ico
// pageapi assumes the filepath is relative to the module path of mymodule.
$api->favicon('favicon/favicon-test.ico');
break;
case 'staging.example.com':
// File is sitting in sites/staging.example.com/favicon.ico
// The second parameter 'site' tells pageapi to understand the path as relative to conf_path().
$api->favicon('favicon.ico', 'site');
// alternatively, specify the filepath relative to Drupal root. (and use FALSE in the location argument)
$api->favicon(conf_path() . '/favicon.ico', FALSE);
break;
default:
// Use the regular favicon. Nothing to do.
}
?>