Index: /trunk/src/application.properties
===================================================================
--- /trunk/src/application.properties	(revision 97)
+++ /trunk/src/application.properties	(revision 98)
@@ -1,4 +1,5 @@
 #utf-8
-#Sun Mar 15 21:57:51 EST 2009
+#Fri Apr 03 23:02:58 EST 2009
+plugins.help-balloons=1.2
 app.version=0.1
 plugins.acegi=0.5.1
Index: /trunk/src/grails-app/conf/SecurityConfig.groovy
===================================================================
--- /trunk/src/grails-app/conf/SecurityConfig.groovy	(revision 97)
+++ /trunk/src/grails-app/conf/SecurityConfig.groovy	(revision 98)
@@ -36,4 +36,6 @@
     '/css/*': ['IS_AUTHENTICATED_ANONYMOUSLY'],
     '/images/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
+    '/js/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
+    '/plugins/help-balloons-1.2/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
     '/login*': ['IS_AUTHENTICATED_ANONYMOUSLY'],
     '/login/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
Index: /trunk/src/grails-app/controllers/AppCoreController.groovy
===================================================================
--- /trunk/src/grails-app/controllers/AppCoreController.groovy	(revision 97)
+++ /trunk/src/grails-app/controllers/AppCoreController.groovy	(revision 98)
@@ -27,16 +27,24 @@
         if (request.method == 'POST') {
             def personInstance = Person.get(authenticateService.userDomain().id)
-    
-            personInstance.pass = params.pass
-            personInstance.password = authenticateService.encodePassword(personInstance.pass)
 
-            if (!personInstance.hasErrors() && personInstance.save()) {
-                //userCache.removeUserFromCache(personInstance.loginName)
-                flash.message = "Password changed successfully."
-                redirect(action:options)
+            if(params.repeatPass == params.pass) {
+                personInstance.pass = params.pass
+                personInstance.password = authenticateService.encodePassword(personInstance.pass)
+
+                if (!personInstance.hasErrors() && personInstance.save()) {
+                    //userCache.removeUserFromCache(personInstance.loginName)
+                    flash.message = "Password changed successfully."
+                    redirect(action:options)
+                }
+                else {
+                    render(view:'changePassword',model:[personInstance:personInstance])
+                }
             }
             else {
+                flash.message = "Passwords must match."
+//                 personInstance.addToErrors("Passwords must match.")
                 render(view:'changePassword',model:[personInstance:personInstance])
-            }                          
+            }
+                
         }  
     }
Index: /trunk/src/grails-app/controllers/EntryDetailedController.groovy
===================================================================
--- /trunk/src/grails-app/controllers/EntryDetailedController.groovy	(revision 97)
+++ /trunk/src/grails-app/controllers/EntryDetailedController.groovy	(revision 98)
@@ -28,7 +28,14 @@
         def entryInstance = Entry.get( params.id )
         if(entryInstance) {
-            entryInstance.delete()
-            flash.message = "Entry ${params.id} deleted"
-            redirect(action:list)
+            if(entryInstance.enteredBy.loginName == authenticateService.userDomain().loginName) {
+                entryInstance.delete()
+                flash.message = "Entry ${params.id} deleted"
+                redirect(action:list)
+            }
+            else {
+                flash.message = "You may only delete your own entries."
+                redirect(action:show,id:entryInstance.id)
+            }
+
         }
         else {
@@ -40,11 +47,18 @@
     def edit = {
         def entryInstance = Entry.get( params.id )
-
         if(!entryInstance) {
-            flash.message = "Entry not found with id ${params.id}"
-            redirect(action:list)
+                flash.message = "Entry not found with id ${params.id}"
+                redirect(action:list)
         }
         else {
-            return [ entryInstance : entryInstance ]
+
+            if(entryInstance.enteredBy.loginName == authenticateService.userDomain().loginName) {
+                return [ entryInstance : entryInstance ]
+            }
+            else {
+                flash.message = "You may only edit your own entries."
+                redirect(action:show,id:entryInstance.id)
+            }
+
         }
     }
@@ -69,7 +83,14 @@
 
     def create = {
-        def entryInstance = new Entry()
-        entryInstance.properties = params
-        return ['entryInstance':entryInstance]
+        try {
+            def taskInstance = Task.get(params.taskInstance.id)
+            def entryInstance = new Entry()
+            entryInstance.task = taskInstance
+            return ['entryInstance':entryInstance]
+        }
+        catch(Exception e) {
+            flash.message = "Please select a task, then 'Add Entry'"
+            redirect(controller:"taskDetailed", action:"list")
+        }
     }
 
Index: /trunk/src/grails-app/domain/Entry.groovy
===================================================================
--- /trunk/src/grails-app/domain/Entry.groovy	(revision 97)
+++ /trunk/src/grails-app/domain/Entry.groovy	(revision 98)
@@ -15,5 +15,5 @@
         comment(blank:false,maxSize:500)
         dateDone()
-        durationHour(min:0)
+        durationHour(min:0,max:16)
         durationMinute(min:0,max:59)
         
Index: /trunk/src/grails-app/i18n/messages.properties
===================================================================
--- /trunk/src/grails-app/i18n/messages.properties	(revision 97)
+++ /trunk/src/grails-app/i18n/messages.properties	(revision 98)
@@ -1,4 +1,7 @@
 person.pass.minSize.notmet=Password is less than the minimum size of [{3}]
 person.pass.blank=Password cannot be blank
+
+entry.duration=Duration
+entry.duration.help=The time (hh:mm) booked against this entry for date done.
 
 default.doesnt.match.message=Property [{0}] of class [{1}] with value [{2}] does not match the required pattern [{3}]
Index: /trunk/src/grails-app/views/appCore/changePassword.gsp
===================================================================
--- /trunk/src/grails-app/views/appCore/changePassword.gsp	(revision 97)
+++ /trunk/src/grails-app/views/appCore/changePassword.gsp	(revision 98)
@@ -37,4 +37,11 @@
 					</tr>
 
+                    <tr class="prop">
+                        <td valign="top" class="name"><label for="repeatPass">Repeat password:</label></td>
+                        <td valign="top" class="value">
+                            <input type="password" id="repeatPass" name="repeatPass" />
+                        </td>
+                    </tr>
+
 				</tbody>
 				</table>
Index: /trunk/src/grails-app/views/entryDetailed/create.gsp
===================================================================
--- /trunk/src/grails-app/views/entryDetailed/create.gsp	(revision 97)
+++ /trunk/src/grails-app/views/entryDetailed/create.gsp	(revision 98)
@@ -42,5 +42,5 @@
                                 </td>
                                 <td valign="top" class="value ${hasErrors(bean:entryInstance,field:'comment','errors')}">
-                                    <textarea style="width:450px" rows="5" cols="40" name="comment">${fieldValue(bean:entryInstance, field:'comment')}</textarea>
+                                    <textarea rows="5" cols="40" name="comment">${fieldValue(bean:entryInstance, field:'comment')}</textarea>
                                 </td>
                             </tr> 
@@ -57,21 +57,19 @@
                             <tr class="prop">
                                 <td valign="top" class="name">
-                                    <label for="durationHour">Duration Hour:</label>
+                                    <label for="durationHour">Duration:</label>
                                 </td>
-                                <td valign="top" class="value ${hasErrors(bean:entryInstance,field:'durationHour','errors')}">
-                                    <input type="text" id="durationHour" name="durationHour" value="${fieldValue(bean:entryInstance,field:'durationHour')}" />
-                                </td>
-                            </tr> 
-                        
-                            <tr class="prop">
-                                <td valign="top" class="name">
-                                    <label for="durationMinute">Duration Minute:</label>
-                                </td>
-                                <td valign="top" class="value ${hasErrors(bean:entryInstance,field:'durationMinute','errors')}">
-                                    <input type="text" id="durationMinute" name="durationMinute" value="${fieldValue(bean:entryInstance,field:'durationMinute')}" />
-                                </td>
-                            </tr> 
 
-                        
+                                <td valign="top" class="value">
+                                    <input class="duration ${hasErrors(bean:entryInstance,field:'durationHour','errors')}" 
+                                        type="text" id="durationHour" name="durationHour" 
+                                        value="${fieldValue(bean:entryInstance,field:'durationHour')}" />
+                                    :
+                                    <input class="duration ${hasErrors(bean:entryInstance,field:'durationMinute','errors')}" 
+                                        type="text" id="durationMinute" name="durationMinute" 
+                                        value="${fieldValue(bean:entryInstance,field:'durationMinute')}" />
+                                    <g:helpBalloon code="entry.duration" />
+                                </td> 
+                            </tr>
+                     
                             <tr class="prop">
                                 <td valign="top" class="name">
Index: /trunk/src/grails-app/views/entryDetailed/edit.gsp
===================================================================
--- /trunk/src/grails-app/views/entryDetailed/edit.gsp	(revision 97)
+++ /trunk/src/grails-app/views/entryDetailed/edit.gsp	(revision 98)
@@ -52,5 +52,5 @@
                                 </td>
                                 <td valign="top" class="value ${hasErrors(bean:entryInstance,field:'dateDone','errors')}">
-                                    <g:datePicker name="dateDone" value="${entryInstance?.dateDone}" ></g:datePicker>
+                                    <g:datePicker name="dateDone" value="${entryInstance?.dateDone}" precision="day"></g:datePicker>
                                 </td>
                             </tr> 
@@ -58,19 +58,18 @@
                             <tr class="prop">
                                 <td valign="top" class="name">
-                                    <label for="durationHour">Duration Hour:</label>
+                                    <label for="durationHour">Duration:</label>
                                 </td>
-                                <td valign="top" class="value ${hasErrors(bean:entryInstance,field:'durationHour','errors')}">
-                                    <input type="text" id="durationHour" name="durationHour" value="${fieldValue(bean:entryInstance,field:'durationHour')}" />
-                                </td>
-                            </tr> 
-                        
-                            <tr class="prop">
-                                <td valign="top" class="name">
-                                    <label for="durationMinute">Duration Minute:</label>
-                                </td>
-                                <td valign="top" class="value ${hasErrors(bean:entryInstance,field:'durationMinute','errors')}">
-                                    <input type="text" id="durationMinute" name="durationMinute" value="${fieldValue(bean:entryInstance,field:'durationMinute')}" />
-                                </td>
-                            </tr> 
+
+                                <td valign="top" class="value">
+                                    <input class="duration ${hasErrors(bean:entryInstance,field:'durationHour','errors')}" 
+                                        type="text" id="durationHour" name="durationHour" 
+                                        value="${fieldValue(bean:entryInstance,field:'durationHour')}" />
+                                    :
+                                    <input class="duration ${hasErrors(bean:entryInstance,field:'durationMinute','errors')}" 
+                                        type="text" id="durationMinute" name="durationMinute" 
+                                        value="${fieldValue(bean:entryInstance,field:'durationMinute')}" />
+                                    <g:helpBalloon code="entry.duration" />
+                                </td> 
+                            </tr>
                         
                             <tr class="prop">
@@ -78,6 +77,6 @@
                                     <label for="dateEntered">Date Entered:</label>
                                 </td>
-                                <td valign="top" class="value ${hasErrors(bean:entryInstance,field:'dateEntered','errors')}">
-                                    <g:datePicker name="dateEntered" value="${entryInstance?.dateEntered}" ></g:datePicker>
+                                <td valign="top" class="value">
+                                    <g:formatDate date="${entryInstance?.dateEntered}" format="EEE, dd MMM yyyy @ HH:mm"/>
                                 </td>
                             </tr> 
@@ -87,6 +86,6 @@
                                     <label for="enteredBy">Entered By:</label>
                                 </td>
-                                <td valign="top" class="value ${hasErrors(bean:entryInstance,field:'enteredBy','errors')}">
-                                    <g:select optionKey="id" from="${Person.list()}" name="enteredBy.id" value="${entryInstance?.enteredBy?.id}" ></g:select>
+                                <td valign="top" class="value">
+                                    ${entryInstance?.enteredBy?.toString()}
                                 </td>
                             </tr> 
Index: /trunk/src/grails-app/views/entryDetailed/list.gsp
===================================================================
--- /trunk/src/grails-app/views/entryDetailed/list.gsp	(revision 97)
+++ /trunk/src/grails-app/views/entryDetailed/list.gsp	(revision 98)
@@ -30,8 +30,10 @@
                    	        <g:sortableColumn property="dateDone" title="Date Done" />
                         
-                   	        <g:sortableColumn property="durationHour" title="Duration Hour" />
-                        
-                   	        <g:sortableColumn property="durationMinute" title="Duration Minute" />
-                        
+                   	        <g:sortableColumn property="enteredBy" title="Entered By" />
+
+                            <th>Edit</th>
+
+                            <th>Show</th>
+
                         </tr>
                     </thead>
@@ -40,5 +42,5 @@
                         <tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
                         
-                            <td><g:link action="show" id="${entryInstance.id}">${fieldValue(bean:entryInstance, field:'id')}</g:link></td>
+                            <td>${fieldValue(bean:entryInstance, field:'id')}</td>
                         
                             <td>${fieldValue(bean:entryInstance, field:'task')}</td>
@@ -48,7 +50,9 @@
                             <td>${fieldValue(bean:entryInstance, field:'dateDone')}</td>
                         
-                            <td>${fieldValue(bean:entryInstance, field:'durationHour')}</td>
-                        
-                            <td>${fieldValue(bean:entryInstance, field:'durationMinute')}</td>
+                            <td>${fieldValue(bean:entryInstance, field:'enteredBy')}</td>
+
+                            <td><g:link action="edit" id="${entryInstance.id}">Edit</g:link></td>
+
+                            <td><g:link action="show" id="${entryInstance.id}">Show</g:link></td>
                         
                         </tr>
Index: /trunk/src/grails-app/views/entryDetailed/show.gsp
===================================================================
--- /trunk/src/grails-app/views/entryDetailed/show.gsp	(revision 97)
+++ /trunk/src/grails-app/views/entryDetailed/show.gsp	(revision 98)
@@ -33,5 +33,5 @@
                             <td valign="top" class="name">Task:</td>
                             
-                            <td valign="top" class="value"><g:link controller="task" action="show" id="${entryInstance?.task?.id}">${entryInstance?.task?.encodeAsHTML()}</g:link></td>
+                            <td valign="top" class="value"><g:link controller="taskDetailed" action="show" id="${entryInstance?.task?.id}">${entryInstance?.task?.encodeAsHTML()}</g:link></td>
                             
                         </tr>
@@ -75,5 +75,5 @@
                             <td valign="top" class="name">Entered By:</td>
                             
-                            <td valign="top" class="value"><g:link controller="person" action="show" id="${entryInstance?.enteredBy?.id}">${entryInstance?.enteredBy?.encodeAsHTML()}</g:link></td>
+                            <td valign="top" class="value">${entryInstance?.enteredBy?.encodeAsHTML()}</td>
                             
                         </tr>
@@ -82,5 +82,5 @@
                             <td valign="top" class="name">Entry Type:</td>
                             
-                            <td valign="top" class="value"><g:link controller="entryType" action="show" id="${entryInstance?.entryType?.id}">${entryInstance?.entryType?.encodeAsHTML()}</g:link></td>
+                            <td valign="top" class="value">${entryInstance?.entryType?.encodeAsHTML()}</td>
                             
                         </tr>
Index: /trunk/src/grails-app/views/layouts/main.gsp
===================================================================
--- /trunk/src/grails-app/views/layouts/main.gsp	(revision 97)
+++ /trunk/src/grails-app/views/layouts/main.gsp	(revision 98)
@@ -5,4 +5,5 @@
         <link rel="shortcut icon" href="${createLinkTo(dir:'images',file:'gnuMimsIcon.ico')}" type="image/x-icon" />
         <g:layoutHead />
+        <g:helpBalloons/>
         <g:javascript library="application" />
     </head>
@@ -10,31 +11,31 @@
     <!-- Added g:pageProperty so that onload in each page works -->
     <body onload="<g:pageProperty name='body.onload'/>">
-    <div id="wrapper" style="height: 100%;">
-    <div id="top">
-    </div>
-    <div id="content" align="center">
-        <div id="spinner" class="spinner" style="display:none;">
-            <img src="${createLinkTo(dir:'images',file:'spinner.gif')}" alt="Spinner" />
-        </div>	
-        <!-- <div class="logo" style="text-align: center; width: 980px; height: 220px">
-          <img src="${createLinkTo(dir:'images',file:'logo.png')}"
-        alt="gnuMims" />
-        <g:render template="/adminmenubar" />
-
-        </div> -->
-        <div id="Header">
-            <a href="http://www.gnumims.org" id=HeaderLink></a>
+        <div id="wrapper" style="height: 100%;">
+            <div id="top">
+            </div>
+            <div id="content" align="center">
+                <div id="spinner" class="spinner" style="display:none;">
+                    <img src="${createLinkTo(dir:'images',file:'spinner.gif')}" alt="Spinner" />
+                </div>	
+                <!-- <div class="logo" style="text-align: center; width: 980px; height: 220px">
+                <img src="${createLinkTo(dir:'images',file:'logo.png')}"
+                alt="gnuMims" />
+                <g:render template="/adminmenubar" />
+        
+                </div> -->
+                <div id="Header">
+                    <a href="http://www.gnumims.org" id=HeaderLink></a>
+                </div>
+                <div class="appControl">
+                    <g:render template="/adminmenubar" />
+                </div>
+                <!-- Body wrapper div for IE -->
+                <div style="text-align: center; width: 980px">
+                    <g:layoutBody />
+                </div><!--IE wrapper-->
+            </div><!--content-->
+            <div id="bottom">
+            </div>
         </div>
-        <div class="appControl">
-             <g:render template="/adminmenubar" />
-        </div>
-        <!-- Body wrapper div for IE -->
-        <div style="text-align: center; width: 980px">
-            <g:layoutBody />
-        </div>
-    </div>
-    <div id="bottom">
-    </div>
-    </div>
     </body>	
 </html>
Index: /trunk/src/grails-app/views/taskDetailed/show.gsp
===================================================================
--- /trunk/src/grails-app/views/taskDetailed/show.gsp	(revision 97)
+++ /trunk/src/grails-app/views/taskDetailed/show.gsp	(revision 98)
@@ -163,5 +163,8 @@
                             <th style="color:Black">Comment</th>
                             <th style="color:Black">Date Done</th>
+                            <th style="color:Black">Duration</th>
                             <th style="color:Black">Entered By</th>
+                            <th style="color:Black">Edit</th>
+
 <!--                            <g:sortableColumn property="comment" title="Comment" />
                         
@@ -177,8 +180,9 @@
                             
                                 <td width="65%">${entry.comment}</td>
-                            
                                 <td><g:formatDate date="${entry.dateDone}" format="EEE, dd MMM yyyy"/></td>
-                        
+                                <td>${entry.durationHour}:${entry.durationMinute}</td>
                                 <td>${entry.enteredBy}</td>
+                                <td><g:link controller="entryDetailed" action="edit" id="${entry.id}">Edit</g:link></td>
+
                         </g:if>
                         
@@ -196,5 +200,7 @@
                             <th style="color:Black">Comment</th>
                             <th style="color:Black">Date Done</th>
+                            <th style="color:Black">Duration</th>
                             <th style="color:Black">Entered By</th>
+                            <th style="color:Black">Edit</th>
 <!--                            <g:sortableColumn property="commentW" title="Comment" />
                         
@@ -210,8 +216,8 @@
                             
                                 <td width="65%">${entry.comment}</td>
-                            
                                 <td><g:formatDate date="${entry.dateDone}" format="EEE, dd MMM yyyy"/></td>
-                        
+                                <td>${entry.durationHour}:${entry.durationMinute}</td>
                                 <td>${entry.enteredBy}</td>
+                                <td><g:link controller="entryDetailed" action="edit" id="${entry.id}">Edit</g:link></td>
                         </g:if>
                         
@@ -225,7 +231,10 @@
 
             <div class="buttons">
-                <span class="menuButton" style="height:50px">
-                    <g:link controller="entryDetailed" params="['task.id':taskInstance.id]" action="create">Add Entry</g:link>
-                </span>
+                <g:form controller="entryDetailed">
+                    <input type="hidden" name="taskInstance.id" value="${taskInstance?.id}" />
+                    <span class="button">
+                        <g:actionSubmit value="Add Entry" action="create"  class="edit"/>
+                    </span>
+                </g:form>
             </div>
 
Index: /trunk/src/web-app/css/main.css
===================================================================
--- /trunk/src/web-app/css/main.css	(revision 97)
+++ /trunk/src/web-app/css/main.css	(revision 98)
@@ -18,5 +18,5 @@
     background: #fff;
     color: #333;
-    font: 11px verdana, arial, helvetica, sans-serif;
+    font: 14px verdana, arial, helvetica, sans-serif;
     background: transparent url("../images/brushed_metal.png") repeat fixed center;
 }
@@ -60,5 +60,5 @@
     color: #006dba;
     font-weight: normal;
-    font-size: 16px;
+    font-size: 17px;
     margin: 0 0 .3em 0;
 }
@@ -71,5 +71,5 @@
     background-color: #fcfcfc;
     border: 1px solid #ccc;
-    font: 11px verdana, arial, helvetica, sans-serif;
+    font: 14px verdana, arial, helvetica, sans-serif;
     margin: 2px 0;
     padding: 2px 4px;
@@ -79,5 +79,5 @@
 }
 textarea {
-	width: 250px;
+	width: 450px;
 	height: 150px;
 	vertical-align: top;
@@ -102,5 +102,5 @@
 
 .appcontrolButton {
-    font-size: 10px;
+    font-size: 14px;
     padding: 5px 5px;
 }
@@ -127,5 +127,5 @@
 
 .menuButton {
-    font-size: 10px;
+    font-size: 14px;
     padding: 0 5px;
 }
@@ -183,4 +183,14 @@
     border: 1px solid red;
 }
+td.errors textarea {
+    border: 1px solid red;
+}
+
+input.duration {
+    width:40px;
+}
+input.duration.errors {
+    border: 1px solid red;
+}
 
 /* TABLES */
@@ -194,6 +204,6 @@
 }
 td, th {
-    font: 11px verdana, arial, helvetica, sans-serif;
-    line-height: 12px;
+    font: 14px verdana, arial, helvetica, sans-serif;
+    line-height: 17px;
     padding: 5px 6px;
     text-align: left;
@@ -203,5 +213,5 @@
     background: #fff url(../images/skin/shadow.jpg);
     color: #666;
-    font-size: 11px;
+    font-size: 14px;
     font-weight: bold;
     line-height: 17px;
@@ -211,5 +221,5 @@
     color: #333;
     display: block;
-    font-size: 10px;
+    font-size: 14px;
     text-decoration: none;
     width: 100%;
@@ -252,5 +262,5 @@
     border-top: 0;
     color: #666;
-    font-size: 10px;
+    font-size: 14px;
     overflow: hidden;
     padding: 10px 3px;
@@ -293,5 +303,5 @@
     border: 1px solid #ccc;
     color: #666;
-    font-size: 10px;
+    font-size: 14px;
     margin-top: -1px;
     margin-bottom: 5px;
@@ -305,5 +315,5 @@
     color: #333;
     cursor: pointer;
-    font-size: 10px;
+    font-size: 14px;
     font-weight: bold;
     margin-left: 3px;
Index: /trunk/src/web-app/plugins/help-balloons-1.2/js/HelpBalloon.js
===================================================================
--- /trunk/src/web-app/plugins/help-balloons-1.2/js/HelpBalloon.js	(revision 98)
+++ /trunk/src/web-app/plugins/help-balloons-1.2/js/HelpBalloon.js	(revision 98)
@@ -0,0 +1,1144 @@
+// 
+// Copyright (c) 2008 Beau D. Scott | http://www.beauscott.com
+// 
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+// 
+
+/**
+ * HelpBalloon.js
+ * Prototype/Scriptaculous based help balloons / dialog balloons
+ * @version 2.0.1
+ * @requires prototype.js <http://www.prototypejs.org/>
+ * @author Beau D. Scott <beau_scott@hotmail.com>
+ * 4/10/2008
+ */
+var HelpBalloon = Object.extend(Class.create(), {
+	/**
+	 * Enumerated value for dynamic rendering position.
+	 * @static
+	 */
+	POS_DYNAMIC: -1,
+	/**
+	 * Enumerated value for the top-left rendering position.
+	 * @static
+	 */
+	POS_TOP_LEFT: 0,
+	/**
+	 * Enumerated value for the top-right rendering position.
+	 * @static
+	 */
+	POS_TOP_RIGHT: 1,
+	/**
+	 * Enumerated value for the bottom-left rendering position.
+	 * @static
+	 */
+	POS_BOTTOM_LEFT: 2,
+	/**
+	 * Enumerated value for the bottom-right rendering position.
+	 * @static
+	 */
+	POS_BOTTOM_RIGHT: 3,
+	/**
+	 * CSS Classname to look for when doing auto link associations.
+	 * @static
+	 */
+	ELEMENT_CLASS_NAME: 'HelpBalloon',
+	/**
+	 * Global array of all HelpBalloon instances.
+	 * (Cheaper than document.getElementByClassName with a property check)
+	 * @static
+	 * @private
+	 */
+	_balloons: [],
+	/**
+	 * Event listener that auto-associates anchors with a dynamic HelpBalloon.
+	 * Also begins mouse movement registration
+	 * @static
+	 */
+	registerClassLinks: function(e) {
+		$A(document.getElementsByClassName(HelpBalloon.ELEMENT_CLASS_NAME))
+			.each(function(obj){
+			// Only apply any element with an href tag
+			if(obj && obj.tagName && obj.href && obj.href != '')
+			{
+				new HelpBalloon({
+					icon:obj,
+					method: 'get'
+				});
+			}
+		});
+		
+		Event.observe(document, 'mousemove', HelpBalloon._trackMousePosition);
+		
+	},
+	
+	/**
+	 * Private cache of the client's mouseX position
+	 */
+	_mouseX: 0,
+	
+	/**
+	 * Private cache of the client's mouseY position
+	 */
+	_mouseY: 0,
+	
+	/**
+	 * @param {Event} e
+	 */
+	_trackMousePosition: function(e) {
+		if(!e) e = window.event;
+		HelpBalloon._mouseX = e.clientX;
+		HelpBalloon._mouseY = e.clientY;
+	}
+});
+
+//
+// Event for activating HelpBalloon classed links
+//
+Event.observe(window, 'load', HelpBalloon.registerClassLinks);
+
+HelpBalloon.prototype = {
+	
+//
+// Properties
+// 	
+	/**
+	 * Configuration options
+	 * @var {HelpBalloon.Options}
+	 */
+	options: null,
+
+	/**
+	 * Containing element of the balloon
+	 * @var {Element}
+	 */
+	container: null,
+	/**
+	 * Inner content container
+	 * @var {Element}
+	 */
+	inner: null,
+	/**
+	 * A reference to the anchoring element/icon
+	 * @var {Element}
+	 */
+	icon: null,
+	/**
+	 * Content container
+	 * @var {Element}
+	 */
+	content: null,
+	/**
+	 * Closing button element
+	 * @var {Element}
+	 */
+	button: null,
+	/**
+	 * The closer object. This can be the same as button, but could 
+	 * also be a div with a png loaded as the back ground, browser dependent.
+	 * @var {Element}
+	 */
+	closer: null,
+	/**
+	 * Title container
+	 * @var {Element}
+	 */
+	titleContainer: null,
+	/**
+	 * Background container (houses the balloon images
+	 * @var {Element}
+	 */
+	bgContainer: null,
+	/**
+	 * Array of balloon image references
+	 * @var {Array}
+	 */
+	balloons: null,
+	
+	/**
+	 * The local store of 'title'. Will change if the balloon is making a remote call
+	 * unless options.title is specified
+	 * @var {String}
+	 */
+	_titleString: null,
+	
+	/**
+	 * The balloons visibility state.
+	 * @var {Boolean}
+	 */
+	visible: false,
+	
+	/**
+	 * Rendering status
+	 * @var {Boolean}
+	 */
+	drawn: false,
+
+	/**
+	 * Stores the balloon coordinates
+	 * @var {Object}
+	 */
+	balloonCoords: null,
+		
+	/**
+	 * Width,height of the balloons
+	 * @var {Array}
+	 */
+	balloonDimensions: null,
+	
+	/**
+	 * ID for HelpBalloon
+	 * @var {String}
+	 */
+	id: null,
+	
+	/**
+	 * Used at render time to measure the dimensions of the loaded balloon
+	 * @private
+	 */
+	_lastBalloon: null,
+
+//
+// Methods
+//
+
+	/**
+	 * @param {Object} options
+	 * @see HelpBalloon.Options
+	 * @constructor
+	 */
+	initialize: function(options)
+	{
+		
+		this.options = new HelpBalloon.Options();
+		Object.extend(this.options, options || {});
+
+		this._titleString = this.options.title;
+		this.balloonDimensions = [0,0];
+		
+		//
+		// Preload the balloon and button images so they're ready
+		// at render time
+		//
+		// 0 1
+		//  X
+		// 2 3
+		//
+		this.balloons = [];
+		for(var i = 0; i < 4; i++)
+		{
+			var balloon = new Element('img', {
+				src: this.options.balloonPrefix + i + this.options.balloonSuffix
+			});
+			this.balloons.push(balloon.src);
+		}
+		
+		this._lastBalloon = balloon;
+		
+		this.button = new Element('img', {
+			src: this.options.button
+		});
+		
+		//
+		// Create the anchoring icon, or attach the balloon to the given icon element
+		// If a string is passed in, assume it's a URL, if it's an object, assume it's
+		// a DOM member.
+		//
+		if(typeof this.options.icon == 'string')
+		{
+			this.icon = new Element('img', {
+				src: this.options.icon,
+				id: this.id + "_icon"
+			});
+			Element.setStyle(this.icon, this.options.iconStyle);
+		}
+		else
+		{
+			//
+			// Not a string given (most likely an object. Do not append the element
+			// Kind of a hack for now, but I'll fix it in the next version.
+			//
+			this.icon = this.options.icon;
+			this.options.returnElement = true;
+		}
+		
+		this.icon._HelpBalloon = this;
+			
+		//
+		// Attach rendering events
+		//
+
+		for(i = 0; i < this.options.useEvent.length; i++)
+			Event.observe(this.icon, this.options.useEvent[i], this.toggle.bindAsEventListener(this));
+		
+		this.container = new Element('div');
+		this.container._HelpBalloon = this;
+		
+		this.id = 'HelpBalloon_' + Element.identify(this.container);
+		
+		HelpBalloon._balloons.push(this);
+
+		//
+		// If we are not relying on other javascript to attach the anchoring icon
+		// to the DOM, we'll just do where the script is called from. Default behavior.
+		//
+		// If you want to use external JavaScript to attach it to the DOM, attach this.icon
+		//
+		if(!this.options.returnElement)
+		{
+			document.write('<span id="' + this.id + '"></span>');
+			var te = $(this.id);
+			var p = te.parentNode;
+			p.insertBefore(this.icon, te);
+			p.removeChild(te);
+		}
+	},
+	
+	/**
+	 * Toggles the help balloon
+	 * @param {Object} e Event
+	 */
+	toggle: function(event)
+	{
+		if(!event) event = window.event || {type: this.options.useEvent, target: this.icon};
+		var icon = Event.element(event);
+		Event.stop(event);
+		if(event.type == this.options.useEvent && !this.visible && icon == this.icon)
+		{
+			this.show(event);
+		}
+		else
+			this.hide();
+	},
+
+	/**
+	 * Triggers the balloon to appear
+	 */
+	show: function(event)
+	{
+		if(!this.visible){
+			if(!event) event = window.event;
+			if(!this.drawn || !this.options.cacheRemoteContent) this._draw();
+			this._reposition(event);
+			this._hideOtherHelps();
+			if(this.options.showEffect)
+			{
+				this.options.showEffect(this.container, Object.extend(this.options.showEffectOptions, {
+					afterFinish: this._afterShow.bindAsEventListener(this)
+				}));
+			}
+			else
+			{
+				this._afterShow();
+			}
+			Event.observe(window, 'resize', this._reposition.bindAsEventListener(this));
+		}
+	},
+	
+	/**
+	 * Sets the container to block styling and hides the elements below the
+	 * container (if in IE)
+	 * @private
+	 */
+	_afterShow: function()
+	{
+		Element.setStyle(this.container, {
+			'display': 'block'
+		});
+		this._hideLowerElements();
+		this.visible = true;
+		if(this.options.autoHideTimeout)
+		{
+			setTimeout(this._hideQueue.bind(this), this.options.autoHideTimeout);
+		}
+	},
+	
+	/**
+	 * Checks the mouse position and triggers a hide after the time specified in autoHideTimeout
+	 * if the mouse is not currently over the balloon, otherwise it requeue's a hide for later.
+	 */
+	_hideQueue: function()
+	{
+		if(Position.within(this.container, HelpBalloon._mouseX, HelpBalloon._mouseY))
+			setTimeout(this._hideQueue.bind(this), this.options.autoHideTimeout);
+		else
+			this.hide();
+	},
+	
+	/**
+	 * Hides the balloon
+	 */
+	hide: function()
+	{
+		if(this.visible)
+		{
+			this._showLowerElements();
+			if(this.options.hideEffect)
+			{
+				this.options.hideEffect(this.container, Object.extend(this.options.hideEffectOptions, {
+					afterFinish: this._afterHide.bindAsEventListener(this)
+				}));
+			}
+			else
+			{
+				this._afterHide();
+			}
+			Event.stopObserving(window, 'resize', this._reposition.bindAsEventListener(this));
+		}
+	},
+	
+	/**
+	 * Sets the container's display to block
+	 * @private
+	 */
+	_afterHide: function()
+	{
+		Element.setStyle(this.container, {
+			'display': 'none'
+		});
+		this.visible = false;
+	},
+	
+	/**
+	 * Redraws the balloon based on the current coordinates of the icon.
+	 * @private
+	 */
+	_reposition: function(event)
+	{
+		if(this.icon.tagName.toLowerCase() == 'area' || !!this.icon.isMap)
+		{
+			this.balloonCoords = Event.pointer(event);
+		}
+		else
+		{
+			this.balloonCoords = this._getXY(this.icon);
+			//Horizontal and vertical offsets in relation to the icon's 0,0 position.
+			// Default is the middle of the object
+			var ho = this.icon.offsetWidth / 2;
+			var vo = this.icon.offsetHeight / 2;
+			
+			var offsets = this.options.anchorPosition.split(/\s+/gi);
+			// Only use the first two specified values
+			if(offsets.length > 2)
+				offsets.length = 2;
+			
+			for(var i = 0; i < offsets.length; i++)
+			{
+				switch(offsets[i].toLowerCase())
+				{
+					case 'left':
+							ho = 0;
+						break;
+					case 'right':
+							ho = this.icon.offsetWidth;
+						break;
+					case 'center':
+							ho = this.icon.offsetWidth / 2;
+						break;
+					case 'top':
+							vo = 0;
+						break;
+					case 'middle':
+							vo = this.icon.offsetHeight / 2;
+						break;
+					case 'bottom':
+							vo = this.icon.offsetHeight;
+						break;
+					default:
+						var numVal = parseInt(offsets[i]); 
+						if(!isNaN(numVal))
+						{
+							// 0 = width, 1 = height (WxH)
+							if(i == 0)
+							{
+								if(numVal < 0)
+								{
+									ho = 0;
+								}
+								else
+								{
+									if(numVal > this.icon.offsetWidth)
+										ho = this.icon.offsetWidth;
+									else
+										ho = numVal
+								}
+							}
+							else
+							{
+								if(numVal < 0)
+								{
+									vo = 0;
+								}
+								else
+								{
+									if(numVal > this.icon.offsetHeight)
+										vo = this.icon.offsetHeight;
+									else
+										vo = numVal
+								}
+							}
+						}
+						break;	
+				}
+			}
+			this.balloonCoords.x += ho;
+			this.balloonCoords.y += vo;
+		}
+		
+		//
+		// Figure out what position to show based on available realestate
+		// unless 
+		// 0 1
+		//  X
+		// 2 3
+		// Number indicates position of corner opposite anchor
+		//
+		var pos = 1;
+		if(this.options.fixedPosition == HelpBalloon.POS_DYNAMIC)
+		{
+			var offsetHeight = this.balloonCoords.y - this.balloonDimensions[1];
+			if(offsetHeight < 0)
+				pos += 2;
+	
+			var offsetWidth = this.balloonCoords.x + this.balloonDimensions[0];
+			var ww = Prototype.Browser.IE ? document.body.clientWidth : window.outerWidth;
+			if(offsetWidth > ww)
+				pos -- ;
+		}
+		else
+			pos = this.options.fixedPosition;
+
+		var zx = 0;
+		var zy = 0;
+		
+		//
+		// 0 1
+		//  X
+		// 2 3
+		//
+		switch(pos)
+		{
+			case 0:
+				zx = this.balloonCoords.x - this.balloonDimensions[0];
+				zy = this.balloonCoords.y - this.balloonDimensions[1];
+				break;
+			
+			case 1:
+				zx = this.balloonCoords.x;
+				zy = this.balloonCoords.y - this.balloonDimensions[1];
+				break;
+			
+			case 2:
+				zx = this.balloonCoords.x - this.balloonDimensions[0];
+				zy = this.balloonCoords.y;
+				break;
+			
+			case 3:
+				zx = this.balloonCoords.x;
+				zy = this.balloonCoords.y;
+				break;
+		}
+		var containerStyle = {
+			/*'backgroundRepeat': 'no-repeat',
+			'backgroundColor': 'transparent',
+			'backgroundPosition': 'top left',*/
+			'left' 	: zx + "px",
+			'top'	: zy + "px",
+			'width' : this.balloonDimensions[0] + 'px',
+			'height' : this.balloonDimensions[1] + 'px'
+		}
+		if(Prototype.Browser.IE)
+		{
+			//
+			// Fix for IE alpha transparencies
+			//
+			if(this.balloons[pos].toLowerCase().indexOf('.png') > -1)
+			{
+				Element.setStyle(this.bgContainer, {
+					'left' 		: '0px',
+					'top'		: '0px',	
+					'filter'	: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.balloons[pos] + "', sizingMethod='scale')",
+					'width' 	: this.balloonDimensions[0] + 'px',
+					'height' 	: this.balloonDimensions[1] + 'px',
+					'position'	: 'absolute'
+				});
+			}
+			else
+				containerStyle['background'] = 'transparent url(' + this.balloons[pos] + ') top left no-repeat';
+		}
+		else
+		{
+				containerStyle['background'] = 'transparent url(' + this.balloons[pos] + ') top left no-repeat';
+		}
+		Element.setStyle(this.container, containerStyle);
+	},
+
+	/**
+	 * Renders the Balloon
+	 * @private
+	 */
+	_draw: function()
+	{
+		Element.setStyle(
+			this.container, 
+			Object.extend(this.options.balloonStyle, {
+				'position': 	'absolute',
+				'display': 		'none'
+			})
+		);
+		
+		var url = this.options.dataURL;
+		
+		//
+		// Play nicely with anchor tags being used as the icon. Use it's specified href as our
+		// data URL unless one has already been used specified in this.options.dataURL. 
+		// We'll also force a new request with this as it may be an image map.
+		//
+		if(this.icon.className == 'a')
+		{
+			if(!this.options.dataURL && this.icon.href != ''){
+				url = this.icon.href;
+				this.options.cacheRemoteContent = false;
+			}
+		}
+		
+		if(url && (!this.drawn || !this.options.cacheRemoteContent))
+		{
+			var cont = new Ajax.Request(this.options.dataURL, {asynchronous: false, method: this.options.method});
+			//
+			// Expects the following XML format:
+			// <HelpBalloon>
+			// 		<title>My Title</title>
+			// 		<content>My content</content>
+			// </HelpBaloon>
+			//
+			var doHTML = false;
+			if(cont.transport.responseXML)
+			{
+				var xml = cont.transport.responseXML.getElementsByTagName('HelpBalloon')[0];
+
+				if(xml)
+				{
+					if(!this.options.title)
+					{
+						xmlTitle = xml.getElementsByTagName('title')[0];
+						if(xmlTitle) this._titleString = xmlTitle.firstChild.nodeValue;
+					}
+
+					xmlContent = xml.getElementsByTagName('content')[0];
+					if(xmlContent) this.options.content = xmlContent.firstChild.nodeValue;
+				}
+				else
+					doHTML = true;
+			}
+			else
+				doHTML = true;
+
+			if(doHTML)
+			{
+				// Attempt to get the title from a <title/> HTML tag, unless the title option has been set. If so, use that.
+				if(!this.options.title)
+				{
+					var htmlTitle = cont.transport.responseText.match(/\<title\>([^\<]+)\<\/title\>/gi);
+					if(htmlTitle)
+					{
+						htmlTitle = htmlTitle.toString().replace(/\<title\>|\<\/title\>/gi, '');
+						this._titleString = htmlTitle;
+					}
+				}
+				this.options.content = cont.transport.responseText;
+			}
+		}
+		
+		this.balloonDimensions[0] = this._lastBalloon.width;
+		this.balloonDimensions[1] = this._lastBalloon.height;
+		
+		var contentDimensions = [
+			this.balloonDimensions[0] - (2 * this.options.contentMargin),
+			this.balloonDimensions[1] - (2 * this.options.contentMargin)
+		];
+		
+		var buttonDimensions = [
+			this.button.width,
+			this.button.height
+		];
+		
+		//
+		// Create all the elements on demand if they haven't been created yet
+		//
+		if(!this.drawn)
+		{
+			this.inner = new Element('div');
+		
+			this.titleContainer = new Element('div');
+			this.inner.appendChild(this.titleContainer);
+			
+			// PNG fix for IE
+			if(Prototype.Browser.IE && this.options.button.toLowerCase().indexOf('.png') > -1)
+			{
+				this.bgContainer = new Element('div');
+				
+				// Have to create yet-another-child of container to house the background for IE... when it was set in
+				// the main container, it for some odd reason prevents child components from being clickable.
+				this.container.appendChild(this.bgContainer);
+				
+				this.closer =  new Element('div');
+				Element.setStyle(this.closer, {
+					'filter':
+						"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.options.button + "', sizingMethod='scale')"
+				});
+			}
+			else
+			{
+				this.closer = this.button;
+			}
+			
+			Event.observe(this.closer, 'click', this.toggle.bindAsEventListener(this));
+			this.inner.appendChild(this.closer);
+			
+			this.content =  new Element('div');
+			this.inner.appendChild(this.content);
+			
+			this.container.appendChild(this.inner);
+			
+			document.getElementsByTagName('body')[0].appendChild(this.container);
+			
+			this.drawn = true;
+		}
+
+		// Reset the title element and reappend the title value (could have changed with a new URL)
+		this.titleContainer.innerHTML = '';
+		this.titleContainer.appendChild(document.createTextNode(this._titleString));
+		
+		// Reset content value:
+		this.content.innerHTML = this.options.content;
+
+		//
+		// Reapply styling to components as values might have changed
+		//
+		
+		Element.setStyle(this.inner, {
+			'position': 	'absolute',
+			'top':			this.options.contentMargin + 'px',
+			'left':			this.options.contentMargin + 'px',
+			'width': 		contentDimensions[0] + 'px',
+			'height': 		contentDimensions[1] + 'px'
+		});
+
+		Element.setStyle(this.titleContainer, {
+			'width':		(contentDimensions[0] - buttonDimensions[0]) + 'px',
+			'height':		buttonDimensions[1] + 'px',
+			'position':		'absolute',
+			'overflow':		'hidden',
+			'top': 			'0px',
+			'left': 		'0px'
+		});
+		
+		Element.setStyle(this.titleContainer, this.options.titleStyle);
+		
+		Element.setStyle(this.closer, {
+			'width': buttonDimensions[0] + 'px',
+			'height': buttonDimensions[1] + 'px',
+			'cursor': 	'pointer',
+			'position':	'absolute',
+			'top': 		'0px',
+			'right': 	'0px'
+		});
+		
+		Element.setStyle(this.content, {
+			'width':		contentDimensions[0] + 'px',
+			'height': 		(contentDimensions[1] - this.button.height) + 'px',
+			'overflow': 	'auto',
+			'position': 	'absolute',
+			'top': 			buttonDimensions[1] + 'px',
+			'left': 		'0px',
+			'fontFamily': 	'verdana',
+			'fontSize': 	'11px',
+			'fontWeight': 	'normal',
+			'color': 		'black'
+		});
+		
+	},
+
+	/**
+	 * Gets the current position of the obj
+	 * @param {Element} element to get position of
+	 * @return Object of (x, y, x2, y2)
+	 */
+	_getXY: function(obj)
+	{
+		var pos = Position.cumulativeOffset(obj)
+		var y = pos[1];
+		var x = pos[0];
+		var x2 = x + parseInt(obj.offsetWidth);
+		var y2 = y + parseInt(obj.offsetHeight);
+		return {'x':x, 'y':y, 'x2':x2, 'y2':y2};
+
+	},
+
+	/**
+	 * Determins if the object is a child of the balloon element
+	 * @param {Element} Element to check parentage
+	 * @return {Boolean}
+	 * @private
+	 */
+	_isChild: function(obj)
+	{
+		var i = 15;
+		do{
+			if(obj == this.container)
+				return true;
+			obj = obj.parentNode;
+		}while(obj && i--);
+		return false
+	},
+
+	/**
+	 * Determines if the balloon is over this_obj object
+	 * @param {Element} Object to look under
+	 * @return {Boolean}
+	 * @private
+	 */
+	_isOver: function(this_obj)
+	{
+		if(!this.visible) return false;
+		if(this_obj == this.container || this._isChild(this_obj)) return false;
+		var this_coords = this._getXY(this_obj);
+		var that_coords = this._getXY(this.container);
+		if(
+			(
+			 (
+			  (this_coords.x >= that_coords.x && this_coords.x <= that_coords.x2)
+			   ||
+			  (this_coords.x2 >= that_coords.x &&  this_coords.x2 <= that_coords.x2)
+			 )
+			 &&
+			 (
+			  (this_coords.y >= that_coords.y && this_coords.y <= that_coords.y2)
+			   ||
+			  (this_coords.y2 >= that_coords.y && this_coords.y2 <= that_coords.y2)
+			 )
+			)
+
+		  ){
+			return true;
+		}
+		else
+			return false;
+	},
+
+	/**
+	 * Restores visibility of elements under the balloon
+	 * (For IE)
+	 * TODO: suck yourself
+	 * @private
+	 */
+	_showLowerElements: function()
+	{
+		if(this.options.hideUnderElementsInIE)
+		{
+			var elements = this._getWeirdAPIElements();
+			for(var i = 0; i < elements.length; i++)
+			{
+				if(this._isOver(elements[i]))
+				{
+					if(elements[i].style.visibility != 'visible' && elements[i].hiddenBy == this)
+					{
+						elements[i].style.visibility = 'visible';
+						elements[i].hiddenBy = null;
+					}
+				}
+			}
+		}
+	},
+
+	/**
+	 * Hides elements below the balloon
+	 * (For IE)
+	 * @private
+	 */
+	_hideLowerElements: function()
+	{
+		if(this.options.hideUnderElementsInIE)
+		{
+			var elements = this._getWeirdAPIElements();
+			for(var i = 0; i < elements.length; i++)
+			{
+				if(this._isOver(elements[i]))
+				{
+					if(elements[i].style.visibility != 'hidden')
+					{
+						elements[i].style.visibility = 'hidden';
+						elements[i].hiddenBy = this;
+					}
+				}
+			}
+		}
+	},
+
+	/**
+	 * Determines which elements need to be hidden
+	 * (For IE)
+	 * @return {Array} array of elements
+	 */
+	_getWeirdAPIElements: function()
+	{
+		if(!Prototype.Browser.IE) return [];
+		var objs = ['select', 'input', 'object'];
+		var elements = [];
+		for(var i = 0; i < objs.length; i++)
+		{
+			var e = document.getElementsByTagName(objs[i]);
+			for(var j = 0; j < e.length; j++)
+			{
+				elements.push(e[j]);
+			}
+		}
+		return elements;
+	},
+
+	/**
+	 * Hides the other visible help balloons
+	 * @param {Event} e
+	 */
+	_hideOtherHelps: function(e)
+	{
+		if(this.options.hideOtherBalloonsOnDisplay)
+		{
+			$A(HelpBalloon._balloons).each(function(obj){
+				if(obj != this)
+				{
+					obj.hide();
+				}
+			}.bind(this));
+		}
+	}
+};
+
+/**
+ * HelpBalloon.Options
+ * Helper class for defining options for the HelpBalloon object
+ * @author Beau D. Scott <beau_scott@hotmail.com>
+ */
+HelpBalloon.Options = Class.create();
+HelpBalloon.Options.prototype = {
+	
+	/**
+	 * @constructor
+	 * @param {Object} overriding options
+	 */
+	initialize: function(values){
+		// Apply the overriding values to this
+		Object.extend(this, values || {});
+	},
+	
+	/**
+	 * Show Effect
+	 * The Scriptaculous (or compatible) showing effect function
+	 * @var Function
+	 */
+	showEffect: window.Scriptaculous ? Effect.Appear : null,
+	
+	/**
+	 * Show Effect options
+	 */
+	showEffectOptions: {duration: 0.2},
+	
+	/**
+	 * Hide Effect
+	 * The Scriptaculous (or compatible) hiding effect function
+	 * @var Function
+	 */
+	hideEffect: window.Scriptaculous ? Effect.Fade : null,
+	
+	/**
+	 * Show Effect options
+	 */
+	hideEffectOptions: {duration: 0.2},
+	
+	/**
+	 * For use with embedding this object into another. If true, the icon is not created
+	 * and not appeneded to the DOM at construction.
+	 * Default is false
+	 * @var {Boolean}
+	 */
+	returnElement: false,
+	
+	/**
+	 * URL to the anchoring icon image file to use. This can also be a direct reference 
+	 * to an existing element if you're using that as your anchoring icon.
+	 * @var {Object}
+	 */
+	icon: 'images/icon.gif',
+	
+	/**
+	 * Alt text of the help icon
+	 * @var {String}
+	 */
+	altText: 'Click here for help with this topic.',
+	
+	/**
+	 * URL to pull the title/content XML
+	 * @var {String}
+	 */
+	dataURL: null,
+	
+	/**
+	 * Static title of the balloon
+	 * @var {String}
+	 */
+	title: null,
+	
+	/**
+	 * Static content of the balloon
+	 * @var {String}
+	 */
+	content: null,
+	
+	/**
+	 * The event type to listen for on the icon to show the balloon.
+	 * Default 'click'
+	 * @var {String}
+	 */
+	useEvent: ['click'],
+	
+	/**
+	 * Request method for dynamic content. (get, post)
+	 * Default 'get'
+	 * @var {String}
+	 */
+	method:	'get',
+	
+	/**
+	 * Flag indicating cache the request result. If this is false, every
+	 * time the balloon is shown, it will retrieve the remote url and parse it
+	 * before the balloon appears, updating the content. Otherwise, it will make
+	 * the call once and use the same content with each subsequent showing.
+	 * Default true
+	 * @var {Boolean}
+	 */
+	cacheRemoteContent: true,
+	
+	/**
+	 * Vertical and horizontal margin of the content pane
+	 * @var {Number}
+	 */
+	contentMargin: 35,
+	
+	/**
+	 * X coordinate of the closing button
+	 * @var {Number}
+	 */
+	buttonX: 246,
+	
+	/**
+	 * Y coordinate of the closing button
+	 * @var {Number}
+	 */
+	buttonY: 35,
+	
+	/**
+	 * Closing button image path
+	 * @var {String}
+	 */
+	button: 'images/button.png',
+	
+	/**
+	 * Balloon image path prefix. There are 4 button images, numerically named, starting with 0.
+	 * 0 1
+	 *  X
+	 * 2 3
+	 * X indicates the anchor corner
+	 * @var {String}
+	 */
+	balloonPrefix: 'images/balloon-',
+	
+	/**
+	 * The image filename suffix, including the file extension
+	 * @var {String}
+	 */
+	balloonSuffix: '.png',
+	
+	/**
+	 * Position of the balloon's anchor relative to the icon element.
+	 * Combine one horizontal indicator (left, center, right) and one vertical indicator (top, middle, bottom).
+	 * Numeric values can also be used in an X Y order. So a value of 9 13 would place the anchor 9 pixels from
+	 * the left and 13 pixels below the top. (0,0 is top left). If values are greater than the width or height
+	 * the width or height of the anchor are used instead. If less than 0, 0 is used.
+	 * Default is 'center middle'
+	 * @var {String}
+	 */
+	anchorPosition: 'center middle',
+	
+	/**
+	 * Flag indicating whether to hide the elements under the balloon in IE.
+	 * Setting this to false can cause rendering issues in Internet Explorer
+	 * as some elements appear on top of the balloon if they're not hidden.
+	 * Default is true.
+	 * @var {Boolean}
+	 */
+	hideUnderElementsInIE: true,
+	
+	/**
+	 * Default Balloon styling
+	 * @var {Object}
+	 */	
+	balloonStyle: {},
+	
+	/**
+	 * Default Title Bar style
+	 * @var {Object}
+	 */
+	titleStyle: {
+		'color': 'black',
+		'fontSize': '16px',
+		'fontWeight': 'bold',
+		'fontFamily': 'Verdana'
+	},
+	
+	/**
+	 * Icon custom styling
+	 * @var {Object}
+	 */
+	iconStyle: {
+		'cursor': 'pointer'
+	},
+	
+	/**
+	 * Flag indication whether to automatically hide any other visible HelpBalloon on the page before showing the current one.
+	 * @var {Boolean}
+	 */
+	hideOtherBalloonsOnDisplay: true,
+	
+	/**
+	 * If you want the balloon to always display in a particular location, set this 
+	 */
+	fixedPosition: HelpBalloon.POS_DYNAMIC,
+	
+	/**
+	 * Number of milliseconds to hide the balloon after showing and after the mouse is not over the balloon.
+	 * A value of 0 means it will not auto-hide
+	 * @var {Number}
+	 */
+	autoHideTimeout: 0
+	
+};
