Custom Tags and Code reuse

In the standard tags module there is alot of useful code however it’s
not encapsulated in classes so I can’t really build my custom tags
upon the existing tags.

I would really like access to “children:each” and just pass it an
options hash or something. Am I missing some code somewhere? Is there
some magic way to reference these tags via your own custom tags or
classes?

Also is there some good info on the way Tags work, after spending my
life in methods this syntax seems so odd and I haven’t had much luck
trying to hunt down particular methods in the api.

Thanks-
Michael

BTW:
http://radiantcms.org/blog/2006/11/24/how-to-write-time-sensitive-or-expiring-content/
was very helpful for building custom tags via the new extensions
method.

Michael J. wrote:

In the standard tags module there is alot of useful code however it’s
not encapsulated in classes so I can’t really build my custom tags
upon the existing tags.

I would really like access to “children:each” and just pass it an
options hash or something. Am I missing some code somewhere? Is there
some magic way to reference these tags via your own custom tags or
classes?

To call out to another tag you can call render on a tag binding:

tag ‘child_urls’ do |tag|
tag.render(‘children:each’, ‘limit’ => ‘5’) do
tag.render(‘url’)
end
end

Also is there some good info on the way Tags work, after spending my
life in methods this syntax seems so odd and I haven’t had much luck
trying to hunt down particular methods in the api.

The basic principles of tag construction are covered in the Radius
quickstart guide:

http://radius.rubyforge.org/files/QUICKSTART.html


John L.
http://wiseheartdesign.com

To call out to another tag you can call render on a tag binding:

tag ‘child_urls’ do |tag|
tag.render(‘children:each’, ‘limit’ => ‘5’) do
tag.render(‘url’)
end
end

Thanks that really helped, one other question:

How do I pass locals into a tag I’m rendering from another tag?

For example I want to create my own custom tag that dynamically
generates the values for the <r:navigation> tag.

It would go something like:

tag ‘navigation_dynamic’ do |tag|
#some code to generate string of urls
tag.render(‘navigation’, ‘urls’ => ‘url string here’)
end

The ‘navigation_dynamic’ would have the nested values that the
‘navigation’ tag expects, but how do I pass those in?

Thanks-
Michael

I believe you can just set tag.locals.whatever inside the do…end block.

I tried to set my locals to be the locals of the navigation tag like
this:

tag ‘navigation_dynamic’ do |tag|
tag.render(‘navigation’, {‘urls’ => ‘Resources:
/resources/;Search: /resources/resources-search/;’}) do
tag.locals.navigation = tag.locals.navigation_dynamic
end
end

I got the error about including a ‘normal tag’, I tried a few
different approaches such as:
tag.locals.navigation = {:normal=>‘yo’}

The navigation tag in the standard_tags.rb is kinda trick, could you
explain the first line some?

hash = tag.locals.navigation = {}
tag.expand
raise TagError.new(“navigation' tag must include anormal’ tag”)
unless hash.has_key? :normal

Also, is there a good way to log debugging from the tags?

Thanks-
Michael

The first line does two things: Assigns a blank Hash to
tag.locals.navigation and also assigns ‘tag.locals.navigation’ to the
local
variable ‘hash’.

Since the next line is ‘tag.expand’, the block you pass would be
executed at
this point. The “trick” is that if you re-assign
tag.locals.navigationinside that block, whatever you assigned it would
fall out of scope after
the block ends.

So, to get your values to pass through, you should either merge the
contents
of your navigation_dynamic local in to tag.locals.navigation like so:

tag.locals.navigation.merge! tag.locals.navigation_dynamic

or set each individual key:

tag.locals.navigation[:here] # = something
tag.locals.navigation[:normal] #= something

The ‘r:navigation’ tag also expects that the elements in that hash will
be
tag-binding Procs (for lack of a better word).

I have a plan for the <r:navigation> tag that I haven’t completed yet,
but
it will allow you to have both external links and dynamically generated
menus, so you could have something like:

<r:navigation>
<r:children:each>
<r:item>
<r:label><r:title /></r:label>
<r:href><r:url /></r:href>
</r:item>
</r:children:each>
<r:normal>…</r:normal>

</r:navigation>

I also plan to have it support the old style for backwards
compatibility’s
sake, or even a combination.

Cheers,

Sean C.

On 1/16/07, Michael J. [email protected] wrote:

I believe you can just set tag.locals.whatever inside the do…end
block.

Thanks for you help on this, i’m still struggling…

The first line does two things: Assigns a blank Hash to
tag.locals.navigation and also assigns ‘tag.locals.navigation’ to the local
variable ‘hash’.

If we assign a blank hash to navigation and assign that to a variable
called ‘hash’ how can it contain any values if we assigned a blank
one?

To establish a baseline I tried copying the existing ‘navigation’ code
into my custom tag (with name edits) and I’m still getting the ‘normal
tag not found error’.

snippet:
<r:navigation_dynamic urls="[Resources: /resources/;Search:
/resources/resources-search/;Health: /resources/food-and-health/;]">
<r:normal><r:title /></r:normal>
<r:here><r:title /></r:here>
<r:selected><r:title
/>
</r:selected>
<r:between> | </r:between>
</r:navigation_dynamic>

head of custom tag;
tag ‘navigation_dynamic’ do |tag|
hash = tag.locals.navigation_dynamic = {}
tag.expand
raise TagError.new(“navigation_d' tag must include anormal’
tag”) unless hash.has_key? :normal

error:
navigation_d' tag must include anormal’ tag

I’ve dumped my cache and restarted the server a bunch. How can I get
logging to see what is contained in ‘tag.locals’? Can you see what
I’m doing wrong here/

Thanks-
Michael

If we assign a blank hash to navigation and assign that to a variable
called ‘hash’ how can it contain any values if we assigned a blank
one?

The tags that are rendered in the ‘tag.expand’ call set values in the
hash.
Look at their definitions and you’ll see it.

To establish a baseline I tried copying the existing ‘navigation’ code
into my custom tag (with name edits) and I’m still getting the ‘normal
tag not found error’.

Make sure that you redefine each of the nested tags in under
‘navigation’…
i.e. ‘navigation:normal’, ‘navigation:here’, ‘navigation:selected’,
‘navigation:between’; change the prefix (part before the colon) on each
one
to ‘navigation_dynamic’.

Sean

The tags that are rendered in the ‘tag.expand’ call set values in the hash.
Look at their definitions and you’ll see it.

I must be missing it, I don’t see an expand method anywhere other than
here:
http://radius.rubyforge.org/classes/Radius/TagBinding.html#M000011

And with that I don’t see how the ‘hash’ is passed to that method
inorder to set values in it?

Michael,

I believe you can just set tag.locals.whatever inside the do…end
block.

Sean

I must be missing it, I don’t see an expand method anywhere other than here:
http://radius.rubyforge.org/classes/Radius/TagBinding.html#M000011

Ok, looks like I’ll have to explain in more detail. For reference, I’m
looking at
http://dev.radiantcms.org/radiant/browser/branches/mental/radiant/app/models/standard_tags.rb#L461
.

The second line inside the tag definition, line 463, calls
‘tag.expand’. This causes any tags nested inside (in the page, mind
you) to be rendered/evaluated. The tags usually nested inside
<r:navigation> are listed starting at line 489. Here’s their
definitions:

[:normal, :here, :selected, :between].each do |symbol|
tag “navigation:#{symbol}” do |tag|
hash = tag.locals.navigation
hash[symbol] = tag.block
end
end

This generates tag definitions for everything in that array in the first
line of the snippet. So, for each one, we grab the
‘tag.locals.navigation’ that was assigned by the parent tag
(originally blank), and assign the hash key associated with this tag to
the tag block. So essentially, you’re passing a block/closure/proc back
to the parent tag that can be evaluated contextually (multiple times).

So here’s an overview, again:

  1. <r:navigation> somewhere in your page invokes the tag definition.
  2. We assign a blank Hash to tag.locals.navigation (and also,
    incidentally, to a local var called ‘hash’ – all variables are just
    references/by-reference in Ruby, so they’re really the same object)
  3. We evaluate the contained block inside <r:navigation> by calling
    tag.expand
    3.5) Each contained tag of ‘normal’, ‘here’, ‘selected’, ‘between’
    assigns its contained block to a key in tag.locals.navigation for future
    evaluation.
  4. <r:navigation> evaluation continues – parsing the urls/titles,
    evaluating various of the passed blocks on each one, depending on URL,
    then joining them together for output.

Let me know if I can clear anything else up.

Cheers,

Sean C.

Hmmm this smells like and looks like a great candidate for the
Wiki…or is it just me?

Yeah I agree, Sean has written up some pretty good stuff. I’ll be
happy to wikify some notes on this once I figure it out.

Are wiki accounts open to anyone or do I need to get some account info
in order to post? Any special area this should be placed?

Hmmm this smells like and looks like a great candidate for the
Wiki…or is it just me?

Michael,

Anyone can open a Wiki account. If you don’t find a good home for the
text, then you can create a page (it will be at the discretion of John
wether to leave it or remove it).

Michael J. wrote:

Are wiki accounts open to anyone or do I need to get some account info
in order to post? Any special area this should be placed?

You presently have to register and login in order to use the wiki.


John L.
http://wiseheartdesign.com

I added a wiki HowTo that contains some of the great information I got
on this thread about the ‘Navigation Tag’.

Thanks again for you great indepth explanations Sean. And for a great
CMS John.

http://dev.radiantcms.org/radiant/wiki/HowToUnderstandNavigationTagCode

Cheers-
Michael

Nice Wiki entry, Michael. I’m sure it’ll come handy soon to many of
us. If not for the navigation tag itself, but for the tag
customization.

Let me know if I can clear anything else up.

Whew! I’m finally rolling along on Radiant. Got the dynamic generation
of secondary nav menus worked out and now I’m easily adding my own
custom tags.

Thank you very much Sean for such detailed information. I’ll get
some wiki info up on this.

The thing that I missed was:

[:normal, :here, :selected, :between].each do |symbol|
tag “navigation:#{symbol}” do |tag|
hash = tag.locals.navigation
hash[symbol] = tag.block
end
end

I didn’t even recognize that this was multiple tag definitions, I
needed to make changes in that code.

Thanks-
Michael