How To: Create Global Tags in a Plugin
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.