Friday, 25 May 2012

Drupal 7 - Render a block in a template file

I can see how Drupal's region system works well for content that flows vertically e.g. sidebars, and maybe even the main content area, but I've always struggled with positioning blocks in the header and footer. This is because for me at least, these areas have lots of blocks of content, in a more complex arrangement (perhaps separated into multiple columns etc). I thought about adding custom regions, maybe one for each column of blocks i wanted to display, but this seemed overly rigid, and I don't see the advantage of doing this over just manually rendering the blocks in my page.tpl.php for example (I think it would be slower to login to the website and mess around with the block admin GUI than to just make a tweak to my page.tpl.php).

So I had to search around for a good way to manually render blocks in a template file, and there are lots of sub-optimal solutions floating around the internet, so when I found a good solution, I thought I would document it here.


Solution

Chuck the following function at the bottom of your theme's template.php file:

/**
 * Custom function to render a block so I can manually position it in the markup
 */
function block_render($module, $block_id) {
  $block = block_load($module, $block_id);
  $block_content = _block_render_blocks(array($block));
  $build = _block_get_renderable_array($block_content);
  $block_rendered = drupal_render($build);
  return $block_rendered;
}

Next, use a preprocess function to set some variables for your tpl file e.g. in your THEME_preprocess_page() function, add something like this for each block you want to render:

$variables['commerce_cart_block'] = block_render('commerce_cart', 'cart');

Where the first argument is the module that produced the block, and the second is the block name - you can get these from going to the block admin page, and hovering the "configure" link e.g. for the Commerce Cart example above, the URL was http://example.com/admin/structure/block/manage/commerce_cart/cart/configure.

Now you have a $commerce_cart_block variable in your page.tpl.php to print wherever you want! I should think this would work for node.tpl.php as well (you would have to set the variables in your THEME_preprocess_node() function instead).

print $commerce_cart_block;


References
http://api.drupal.org/api/drupal/includes%21module.inc/function/module_invoke/7#comment-15709

Thursday, 22 March 2012

Rsync only specific files and directories

We recently decided that when deploying projects, it was safer to define includes rather than excludes i.e. define only the files/dirs that we want to copy and exclude everything else. Probably more work, but much safer because it means any new files (inc hidden files) will NOT be included by default - you have to manually add an include statement for them.

This turned out to be harder than I expected, but the best solution I found was the following 3 step process:

Step 1

Include the entire directory structure (but no files yet):
--include=*/

Step 2

Include the specific files / contents of specific dirs you need:
--include=/index.php
--include=/css/build/*
etc.

Step 3

Exclude everything else
--exclude=*


Explanation

The reason we start by including the entire directory structure is to greatly simplify our include statements in part (2). Without it, we would need to have separate include statements for every parent directory of every file we wanted to include e.g.
--include=/css
--include=/css/build
--include=/css/build/*
Instead of just:
--include=/css/build/*
This is because the final exclude-all-else statement is very powerful, and if you just saying you want to include the files inside /css/build/ isn't enough - because the exclude-all will encounter /css, won't find a matching include statement and so will exclude it!

You may however, not want to include hidden directories e.g. .git/ or .svn/. To do this, we insert this statement right at the beginning:
--exclude=.*/

Final template

rsync -av
--exclude=.*/
--include=*/ 
<!-- START CUSTOM INCLUDES -->
--include=/index.php
--include=/.htaccess
--include=/favicon.ico
--include=/img/*
--include=/css/build/*
--include=/js/build/*
<!-- END CUSTOM INCLUDES -->
--exclude=*
src/
dest/


Addition

There may be situations where you need to just recirsively include all files and subdirs within a directory. To do that, you can use this:
--include=css/**

Tuesday, 13 March 2012

Ubuntu 11.10 - Cannot add new item when editing main menu

I still use the old school "Main Menu" editor AKA Alacarte to create new application launchers. Unfortunately, there is a bug that means that alacarte doesn't know about all of it's dependencies. This means that if you just install alacarte, and run "Main Menu", most of the buttons (including New Item) don't work. The solution is to manually install gnome-panel as well e.g.
sudo apt-get install alacarte gnome-panel

Friday, 24 February 2012

Installing ANT on AWS EC2: Unable to locate tools.jar

When I try and use ANT, I get this message:
Unable to locate tools.jar. Expected to find it in /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0/lib/tools.jar
Most forum posts say to just install Sun Java. Loads of other ones said to check your JAVA_HOME variable and to ensure you had installed the JDK, and not just the JRE. Ultimately, the solution to this problem for me was installing the devel version of the JDK:
sudo yum install java-1.6.0-openjdk-devel

Get Eclipse to automatically compile your Sass

Using the Aptana plugin for Eclipse, I was able to get syntax highlighting working in Sass files (*.sass and now *.scss). It also allows you to compile a Sass file at the click of a button, which is great, but what we all really want is automatic compilation every time we make save a change.

Automatic compilation can be done using the sass --watch command, but this requires you to set it up every time you start working on a project. In Eclipse, I couldn't find an existing way to automate the compile process, so I wrote an ant build script to do it, and then set that as a Project Builder and set it to run on "Auto Build", meaning every time a change is saved.

1) The ant build script. Put this in a file called build.xml in the root of your project. It just says to run sass (/var/lib/gems/1.9.1/bin/sass) on the directory "scss" and output the result to the directory "css". Don't forget to update the path to your sass executable, as well as your css and scss directories.


    
        
            <srcfile/>
            <targetfile/>
            <fileset dir="scss" includes="*.scss"/>
            <mapper from="*.scss" to="*.css" type="glob"/>
        
    

Update: bare in mind you can add an <arg> element inside the element in order to send any additional arguments to Sass e.g. <arg value="--style=compressed"/> will output compressed CSS (useful for your production server).

2) Add it as a Project Builder: in the project properties, click Builders, then New, and select Ant Builder. Give it a name, and select the Buildfile (click Browse Workspace and navigate to your build.xml). Under Targets, click the Set Targets button next to Auto Build, and select sass and click OK. Finally, under Build Options, check "Specify working set of relevant resources" and click the button to specify only your scss directory - this ensures the build script will only be run when you change your Sass files.

Monday, 13 February 2012

Z-index doesn't work on empty element in IE9

The z-index property will not work on empty elements in IE8 and IE9 unless they have a background. The solution is to give them one, and then hide it using the opacity property.

background: white;
filter: alpha(opacity=1);

REFERENCES
http://stackoverflow.com/questions/6480774/z-index-problem-in-ie-with-transparent-div

Thursday, 2 February 2012

Drupal: iterating array items that are not properties (hashes)

I recently needed to iterate over the elements of an array, ignoring any "properties" (signified in Drupal using hashes e.g. $form['items']['#value']). Originally I did this by iterating over all the elements, and then filtering out those that I didn't want. Turns out there is a neater way: element_children(). This does the filtering for you, and returns the keys of normal (non-property) elements. You can use it like this:

foreach (element_children($form["items"]) as $i) {
    // something with $form["items"][$i]
}

Friday, 13 January 2012

jQuery code assist in Eclipse 3.7


This is something I have wanted for ages. jQuery code assist AKA code completion AKA autocomplete AKA intellisense, in my favourite IDE Eclipse.
  1. Install Aptana 3 plugin: go Help > Install New Software, and use this URL: http://download.aptana.com/studio3/plugin/install
  2. Enable jQuery bundle: make sure you are using the Aptana ("Web") perspective and go Commands > Bundle Development > Install Bundle > jQuery
  3. Use the Aptana JavaScript editor, rather than the default Eclipse / WTP one: go Window > Preferences > General > Editors > File Associations, and click *.js and then make the "JavaScript Source Editor" the default.

And that should be it - open a JavaScript file, and type $("input").cli and then hit Space and it should automatically add the code for a jQuery click() event handler.


ALTERNATIVE
You could also try jQueryWTP, or JSDT jQuery which is available in the Eclipse Marketplace, but I have no experience with these. Please add comments if you these plugins are actually any good.

REFERENCES
http://stackoverflow.com/questions/4721124/how-to-enable-jquery-support-in-aptana-studio-3

Wednesday, 11 January 2012

Google Analytics: track clicks on #hash anchor links

If on your website you want to treat different page anchors as different pages (e.g. if you load your pages using Ajax), then you need a way to tell GA (Google Analytics) about this. Unfortunately there's no simple settings change you can make in the GA tracking code snippet - you actually have to run some GA code yourself when the event occurs. It turns out this is quite simple:

_gaq.push(['_trackPageview', location.pathname + location.search + location.hash]);

But where do you use this? You could add it to the click event (either in your JavaScript, or in the markup, using the onClick attribute). But if you want a global solution, you need to fire it for all hashchanges. This can be done using the jQuery hashchange plugin:

$(window).hashchange(function() {
    // put any other hashchange magic you might want to do here
    _gaq.push(['_trackPageview', location.pathname + location.search + location.hash]);
});

REFERENCES
http://www.searchenginepeople.com/blog/how-to-track-clicks-on-anchors-in-google-analytics.html