How To: Create Global Tags in a Plugin

Posted by Sean Cribbs on Thursday, September 14, 2006 | |

Hello, I’m Sean Cribbs. You may have seen me helping people on the mailing list or seen my behaviors for creating blog-style comments.

John Long has invited me to author a “How To” column for the Radiant weblog. The purpose of this column is to collect and distill useful solutions to common tasks that one might want to accomplish with Radiant. These “How To” entries will supplement an already growing section of the wiki called HowTos. For our first installment, we’ll discuss how to create your own “global tags”, or Radius tags that are available to every page, and encapsulate those in a plugin.

Global Tags

In general, Radius tags are used to provide hooks into Radiant for doing cool stuff like picking a random picture out of a collection or displaying a list of recent articles. There are guides on how to use the standard collection of tags here and here.

However, there are times when you need to define a new Radius tag to do something that isn’t covered in the standard collection, and you need it available to every page. One good example is in the code formatting plugin by Tom de Grunt: putting your code snippet between r:code tags creates a code block in your page that is syntax-highlighted.

Here’s a step-by-step way to create a place for your own global tags.

Step 1: Generate a plugin

Open a shell and change directory into your Radiant install. Here we’ll generate a plugin just as we would for any Rails application.

  % ruby script/generate plugin 00_mytags

You should get some output that looks like this:

      create  vendor/plugins/00_mytags/lib
      create  vendor/plugins/00_mytags/tasks
      create  vendor/plugins/00_mytags/test
      create  vendor/plugins/00_mytags/README
      create  vendor/plugins/00_mytags/Rakefile
      create  vendor/plugins/00_mytags/init.rb
      create  vendor/plugins/00_mytags/install.rb
      create  vendor/plugins/00_mytags/lib/00_mytags.rb
      create  vendor/plugins/00_mytags/tasks/00_mytags_tasks.rake
      create  vendor/plugins/00_mytags/test/00_mytags_test.rb

Sidebar: It’s important to note that the plugin name starts with 00. This is so the plugin is loaded before any other plugins that may define other tags or behaviors. If it is loaded after any such plugins, the behaviors they define will not receive your global tags and you may get “undefined tag” errors. It doesn’t really matter what your plugin is called, just so long as it lexically comes first in the vendor/plugins directory.

Change directory to the plugin you just created and let’s get started.

  % cd vendor/plugins/00_mytags

Step 2: Hook It Together

For the sake of cleanliness and convention, we’re going to keep our code inside the lib/ directory. For that to work, we need to load the source file from that directory when the application starts. Open init.rb in your favorite text editor and add the following line:

  require '00_mytags'

If you add other files later with separate tag definitions, you need to add another require statement in init.rb. In most cases, you could just copy-and-paste the new code into 00_tags.rb.

Open lib/00_mytags.rb with your favorite text editor and we’ll take the next step.

Step 3: Define your tags with Behavior::Base

We’re going to take advantage of the fact that every page, regardless of whether it has an assigned behavior, delegates to the Behavior::Base class for rendering, and all behaviors descend from Behavior::Base. See also the addendum. Let’s define our “Hello, World!” tag.

  # Put me in lib/00_mytags.rb
  Behavior::Base.define_tags do 

    # <r:hello [name="Sean"] />
    tag "hello" do |tag|
      name = tag.attr['name'] || "World"
      "Hello, #{name}!"
    end
  
  end

Easy, right? Each new tag definition is started with a tag "name" block and returns the content of the tag.

Step 4: Start Radiant

Save your file(s) and close your editor. Change directory to the root of your Radiant install.

If you’re in a development environment, start up Radiant with ruby script/server. If you’re on a production box (and you’ve thoroughly tested your tags) restart your instance of Radiant, either with ruby script/process/reaper or whatever your host proscribes.

So there you have it! Enjoy your global tags and please share new and interesting ones with the community!

Addendum

I discussed with John another method for defining tags that involves redefining the initialize method of PageContext (app/models/page_context.rb), rather than adding tag blocks via Behavior::Base.define_tags. In the long run, this is better because it circumvents the loading-order issue. Here’s some code to put in 00_mytags.rb.

  require 'page_context' # make sure we have the previous definition first
  
  class PageContext < Radius::Context
    alias_method :old_init, :initialize
    
    def initialize(page)
      old_init page
      
      define_tag "hello" do |tag|
        name = tag.attr['name'] || "World"
        "Hello, #{name}!"
      end
    end
  
  end

This uses Module#alias_method to keep a handle on the old method, run it, then define new tag blocks. While not as clean as the other method, it’s clearly more flexible in totum.