If my intent is to write a generic theme which can be (ab)used by many users, I might want to make it as configurable as possible. Some of my audience may only have blocks on the left side, some may only have them on the right and some may have both.
If I'm also trying to maintain consistency in the layout, I may wish to be rigid about the width of the block columns (see Administration -> blocks -> preview) so that the block columns don't vary in width depending on what page I'm viewing. I'm left with little option but to make three separate themes to support the three layouts as there is no look-ahead to know if I'm going to have any blocks in a certain region.
In other words, I want to do something like this:
<?php
class Theme_foo
{
function header()
{
// Start the HTML document here
// ...
// Start the main table
echo '<table><tr>';
// Check to see if we're going to have any blocks on the left
if (block_hasAny('left'))
{
// Add a fixed-width column for the left blocks
echo '<td width="150">';
theme_blocks('left', $this);
echo '</td>';
}
// Start the main column
echo '<td>';
}
function footer()
{
// Finish the main column
echo '</td>';
// Check to see if we're going to have any blocks on the left
if (block_hasAny('right'))
{
// Add a fixed-width column for the right blocks
echo '<td width="150">';
theme_blocks('right', $this);
echo '</td>';
}
// Finish the HTML document here
// ...
}
}
?>Unless I'm really dumb and have missed another way to do this (which is certainly a possibility), I don't know how not waste space with the two fixed-width block columns if one of them is empty (again, since there's no look-ahead).
This may be as easy as adding a function to includes/theme.inc like the following:
function theme_hasBlocks($region)
{
$result = db_query("SELECT COUNT(*) FROM blocks WHERE (status = '1' OR custom = '1') ".
($region != "all" ? "AND region = '%s' " : "") .
"ORDER BY weight, module", $region == "left" ? 0 : 1);
// Forgive my ignorance here, as I don't know the format of $result:
return ($result[0] > 0);
}Either that or one could have theme_blocks() return a string (but not output anything) so that the caller can figure out what to do with them (just some thoughts)....
Comments
Comment #1
ax commented+1 from me. i needed exactly the same feature for a theme i did (and i guess others will need it, too). the way i did it was to similar to your option 2 ("have theme_blocks() return a string [...]"). more detailed:
- i copied
theme_blocks($region, &$theme)from theme.inc as a class method into my theme (MyTheme::theme_blocks($region)) (the&$themeisn't necessary anymore because it's$this) and used$this->blocks()instead oftheme_blocks(). so i don't have to mess with the core theme.inc, only with my theme.- i modified
MyTheme::theme_blocks()to accept an optional second parameter$op = "print". without second parameter, it does just the the same astheme_blocks(). with any other second parameter, it implements the "has_block_at_region" functionality and returns TRUE if there are any blocks at the specified region.- i also modified
theme_blocks()so that it doesn't return blocks as boxes ($theme->box()), but as blocks ($this->block()) so that i can layout blocks differently than boxes (of course then my theme needs to implement$this->block())was that clear?
guess this would be a good solution for drupal core, too.
Comment #2
matt-1 commentedWell, I did find a way around this. Or rather I used PHP's output buffering to implement the second method in the original report (i.e., outputing all the blocks of a given region to a string and then checking the string to see if one should output it).
However, this does seem like a hack unless this method is officially supported in
theme.inc. Here's why: iftheme_blocks()ever changes the way it handles output (e.g., adds a ``<br />'' before/after the$theme->box(...)loop), then the conditional may break (and so will the theme). We try to be smart and usetrim()to clean it up a little before we pass judgement, but this is not foolproof.If this happens however (and we are duped into thinking we have blocks when we don't), at least the theme will err on the side of inclusion...it will just look weird to have a big blank column.
Now one can use this function as follows:
Comment #3
ax commentedmine is better!
because yours computes and renders all blocks at
$regionto find out if there are any (not speaking of the uncertainities abouttheme_blocks()output you mention), mine just checks the result of the sql query intheme_blocks()(SELECT * FROM blocks WHERE region = '$region') to do the same.even if that wasn't clear from what i wrote before
Comment #4
matt-1 commentedActually I did see what you were doing before. I was trying to optimize for fewest number of database accesses to minimize load on a common resource (with Drupal, one can have a bazillion content servers running Apache, but I've yet to figure out a way to use more than one database server...I think this would involve rewriting a fair amount of Drupal's data access code).
I also figured that it would be unlikely for a theme to check for blocks in a given region without the intent to render them (so the rendering/computing isn't done more than once, it's just cached before being sent to the client). The rendering is only done once in both solutions.
If one is running a low-traffic site with a lot of blocks spread among different regions, your solution may be the winner. However, if one is running a very high-traffic site with relatively few blocks, the overhead associated with storing the rendered blocks is most likely less expensive than the database trip (probably around 1k per block). Both will work, it's really a matter of preference:
Perhaps an optimization is to cache the results of your query and only update them once every several minutes.
Keep in mind that the above is all hypothesis. I have no empirical evidence to support any of my claims.
Comment #5
matt-1 commentedI may have been confused to your original response. Yes, I did reread it and am probably an idiot (in which case, we've both implemented option 2 without extra database calls, but that leaves me confused about your added parameter to
theme_blocks()-- do you call it twice?).If you don't call
theme_blocks()more than once per region, then they're equally efficient in the sense that no rendering is done (i.e., the while loop never executes) if there are no blocks returned in the database query. But yours avoids the added overhead from the output buffer.One question: why do you make
theme_blocks()call$theme->block()? Why not just rewrite$theme->box()to do what you want? Is there an object you're returning to stack up and use later when rendering? Just curious....Comment #6
dries commentedWhy don't we change the
theme_block()function then? We can make it load the blocks in an array with (title, content)-tuples instead of emitting the blocks directly to screen? The themes would have to do something like:Feel free to explore this possibility and to submit some patches to frod with.
Comment #7
al commentedIf you're willing, you can also fix this issue with CSS.
Something like (note - I haven't tested this, but it should work):
With CSS:
Comment #8
ax commentedblock_list() as suggested by dries is available in drupal cvs since 19 Nov 2003 [1]. block_list($region) [2] returns an array of blocks, so to find out if there are blocks on either side, just check if this array is empty.
[1] http://lists.drupal.org/archives/drupal-cvs/2003-11/msg00414.html
[2] http://drupal.kollm.org/tmp/drupal-phpdoc/block_8module.html#a15