Index: trunk/grails-app/taglib/CustomTagLib.groovy
===================================================================
--- trunk/grails-app/taglib/CustomTagLib.groovy	(revision 827)
+++ trunk/grails-app/taglib/CustomTagLib.groovy	(revision 829)
@@ -9,4 +9,7 @@
 class CustomTagLib {
     static namespace = 'custom'
+
+    private static final Object helpBalloonLockable = new Object();
+    private static long helpBalloonCount = 0L;
 
     def resources = { attrs ->
@@ -295,4 +298,80 @@
 
     /**
+    * Customised version of helpBalloon as found in help-balloon plugin.
+    * This version can be used in ajax rendered templates where the original fails.
+    *
+    * Fields added:
+    * iconId - Optional to specify the anchor elements id, by default an id is generated if iconSrc is supplied.
+    * iconSrc - Optional to specify anchor image src, if not supplied no anchor image is output by the taglib.
+    *
+    * Example:
+    * <!--
+    * <custom:helpBalloon code="entry.date.done" iconSrc="${resource(plugin:'help-balloons', dir:'images', file:'balloon-icon.gif')}"/>
+    * or
+    * <a href="#" id="mynewanchor" onclick="return false;">this</a>
+    * <custom:helpBalloon code="entry.date.done" iconId="mynewanchor" />
+    * -->
+    */
+    def helpBalloon = {attrs, body ->
+        def mkp = new groovy.xml.MarkupBuilder(out) //this line will be unnecessary in versions of Grails after version 1.2
+
+        def title = attrs["title"]
+        def content = attrs["content"]
+        def code = attrs["code"]
+        def suffix = attrs["suffix"] ?: ".help"
+        def encodeAs = attrs["encodeAs"]
+        def iconId = attrs["iconId"]
+        def iconSrc =  attrs["iconSrc"]
+
+        if (!title && code) title = g.message(code: code)
+        if (!content && code) content = g.message(code: code + suffix)
+
+        title = title ?: ""
+        content = content ?: ""
+
+        if (encodeAs) {
+            switch (encodeAs.toUpperCase()) {
+
+                case "HTML":
+                    title = title.encodeAsHTML()
+                    content = content.encodeAsHTML()
+                    break
+
+                case "XML":
+                    title = title.encodeAsXML()
+                    content = content.encodeAsXML()
+                    break
+            }
+        }
+
+        def num
+        synchronized (helpBalloonLockable) {
+            num = helpBalloonCount++;
+        }
+
+        if(iconSrc) {
+            iconId = iconId ?: "customHb$num"
+            mkp.img( id: iconId, src: iconSrc)
+        }
+
+        def javascript = """var customHb$num = new HelpBalloon({
+    title: '${title.encodeAsJavaScript()}',
+    content: '${content.encodeAsJavaScript()}'"""
+
+        if(iconId) {
+        javascript +=""",
+    icon: \$('$iconId')"""
+        }
+
+        javascript += """
+});"""
+
+        mkp.script(type: "text/javascript") {
+            yieldUnescaped(javascript)
+        }
+
+    }
+
+    /**
     * Determine if a supplied string is considered a url or not.
     * The scheme/protocol can be adjusted, file:// has been excluded here.
