Index: /branches/util-apps/DomainUtil/.classpath
===================================================================
--- /branches/util-apps/DomainUtil/.classpath	(revision 766)
+++ /branches/util-apps/DomainUtil/.classpath	(revision 766)
@@ -0,0 +1,14 @@
+<classpath>
+    <classpathentry kind="src" path="src/java"/>
+    <classpathentry kind="src" path="src/groovy"/>
+    <classpathentry kind="src" path="grails-app/conf"/>
+    <classpathentry kind="src" path="grails-app/controllers"/>
+    <classpathentry kind="src" path="grails-app/domain"/>
+    <classpathentry kind="src" path="grails-app/services"/>
+    <classpathentry kind="src" path="grails-app/taglib"/>
+    <classpathentry kind="src" path="test/integration"/>
+    <classpathentry kind="src" path="test/unit"/>
+    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+    <classpathentry kind="con" path="com.springsource.sts.grails.core.CLASSPATH_CONTAINER"/>
+    <classpathentry kind="output" path="web-app/WEB-INF/classes"/>
+</classpath>
Index: /branches/util-apps/DomainUtil/.project
===================================================================
--- /branches/util-apps/DomainUtil/.project	(revision 766)
+++ /branches/util-apps/DomainUtil/.project	(revision 766)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>DomainUtil</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+	    <nature>com.springsource.sts.grails.core.nature</nature>
+		<nature>org.eclipse.jdt.groovy.core.groovyNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
Index: /branches/util-apps/DomainUtil/application.properties
===================================================================
--- /branches/util-apps/DomainUtil/application.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/application.properties	(revision 766)
@@ -0,0 +1,8 @@
+#Grails Metadata file
+#Fri Jan 28 09:40:40 EST 2011
+app.grails.version=1.2.2
+app.name=DomainUtil
+app.servlet.version=2.4
+app.version=0.1
+plugins.hibernate=1.2.2
+plugins.tomcat=1.2.2
Index: /branches/util-apps/DomainUtil/grails-app/conf/BootStrap.groovy
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/conf/BootStrap.groovy	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/conf/BootStrap.groovy	(revision 766)
@@ -0,0 +1,10 @@
+class BootStrap
+{
+
+    def init = { servletContext ->
+    } // init
+
+    def destroy = {
+    }
+
+} // end class
Index: /branches/util-apps/DomainUtil/grails-app/conf/BuildConfig.groovy
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/conf/BuildConfig.groovy	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/conf/BuildConfig.groovy	(revision 766)
@@ -0,0 +1,31 @@
+grails.project.class.dir = "target/classes"
+grails.project.test.class.dir = "target/test-classes"
+grails.project.test.reports.dir	= "target/test-reports"
+//grails.project.war.file = "target/${appName}-${appVersion}.war"
+grails.project.dependency.resolution = {
+    // inherit Grails' default dependencies
+    inherits( "global" ) {
+        // uncomment to disable ehcache
+        // excludes 'ehcache'
+    }
+    log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
+    repositories {        
+        grailsPlugins()
+        grailsHome()
+
+        // uncomment the below to enable remote dependency resolution
+        // from public Maven repositories
+        //mavenLocal()
+        //mavenCentral()
+        //mavenRepo "http://snapshots.repository.codehaus.org"
+        //mavenRepo "http://repository.codehaus.org"
+        //mavenRepo "http://download.java.net/maven/2/"
+        //mavenRepo "http://repository.jboss.com/maven2/"
+    }
+    dependencies {
+        // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
+
+        // runtime 'mysql:mysql-connector-java:5.1.5'
+    }
+
+}
Index: /branches/util-apps/DomainUtil/grails-app/conf/Config.groovy
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/conf/Config.groovy	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/conf/Config.groovy	(revision 766)
@@ -0,0 +1,87 @@
+// locations to search for config files that get merged into the main config
+// config files can either be Java properties files or ConfigSlurper scripts
+
+// grails.config.locations = [ "classpath:${appName}-config.properties",
+//                             "classpath:${appName}-config.groovy",
+//                             "file:${userHome}/.grails/${appName}-config.properties",
+//                             "file:${userHome}/.grails/${appName}-config.groovy"]
+
+// if(System.properties["${appName}.config.location"]) {
+//    grails.config.locations << "file:" + System.properties["${appName}.config.location"]
+// }
+
+grails.project.groupId = appName // change this to alter the default package name and Maven publishing destination
+grails.mime.file.extensions = true // enables the parsing of file extensions from URLs into the request format
+grails.mime.use.accept.header = false
+grails.mime.types = [ html: ['text/html','application/xhtml+xml'],
+                      xml: ['text/xml', 'application/xml'],
+                      text: 'text/plain',
+                      js: 'text/javascript',
+                      rss: 'application/rss+xml',
+                      atom: 'application/atom+xml',
+                      css: 'text/css',
+                      csv: 'text/csv',
+                      all: '*/*',
+                      json: ['application/json','text/json'],
+                      form: 'application/x-www-form-urlencoded',
+                      multipartForm: 'multipart/form-data'
+                    ]
+// The default codec used to encode data with ${}
+grails.views.default.codec="none" // none, html, base64
+grails.views.gsp.encoding="UTF-8"
+grails.converters.encoding="UTF-8"
+// enable Sitemesh preprocessing of GSP pages
+grails.views.gsp.sitemesh.preprocess = true
+// scaffolding templates configuration
+grails.scaffolding.templates.domainSuffix = 'Instance'
+
+// Set to false to use the new Grails 1.2 JSONBuilder in the render method
+grails.json.legacy.builder=false
+// enabled native2ascii conversion of i18n properties files
+grails.enable.native2ascii = true
+// whether to install the java.util.logging bridge for sl4j. Disable fo AppEngine!
+grails.logging.jul.usebridge = true
+// packages to include in Spring bean scanning
+grails.spring.bean.packages = []
+
+// set per-environment serverURL stem for creating absolute links
+environments {
+    production {
+        grails.serverURL = "http://www.changeme.com"
+    }
+    development {
+        grails.serverURL = "http://localhost:8080/${appName}"
+    }
+    test {
+        grails.serverURL = "http://localhost:8080/${appName}"
+    }
+
+}
+
+// log4j configuration
+log4j = {
+    // Example of changing the log pattern for the default console
+    // appender:
+    //
+    //appenders {
+    //    console name:'stdout', layout:pattern(conversionPattern: '%c{2} %m%n')
+    //}
+
+
+    error  'org.codehaus.groovy.grails.web.servlet',  //  controllers
+	       'org.codehaus.groovy.grails.web.pages', //  GSP
+	       'org.codehaus.groovy.grails.web.sitemesh', //  layouts
+	       'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping
+	       'org.codehaus.groovy.grails.web.mapping', // URL mapping
+	       'org.codehaus.groovy.grails.commons', // core / classloading
+	       'org.codehaus.groovy.grails.plugins', // plugins
+	       'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration
+	       'org.springframework',
+	       'org.hibernate',
+           'net.sf.ehcache.hibernate'
+
+    warn   'org.mortbay.log'
+}
+
+
+     
Index: /branches/util-apps/DomainUtil/grails-app/conf/DataSource.groovy
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/conf/DataSource.groovy	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/conf/DataSource.groovy	(revision 766)
@@ -0,0 +1,32 @@
+dataSource {
+	pooled = true
+	driverClassName = "org.hsqldb.jdbcDriver"
+	username = "sa"
+	password = ""
+}
+hibernate {
+    cache.use_second_level_cache=true
+    cache.use_query_cache=true
+    cache.provider_class='net.sf.ehcache.hibernate.EhCacheProvider'
+}
+// environment specific settings
+environments {
+	development {
+		dataSource {
+			dbCreate = "create-drop" // one of 'create', 'create-drop','update'
+			url = "jdbc:hsqldb:mem:devDB"
+		}
+	}
+	test {
+		dataSource {
+			dbCreate = "update"
+			url = "jdbc:hsqldb:mem:testDb"
+		}
+	}
+	production {
+		dataSource {
+			dbCreate = "update"
+			url = "jdbc:hsqldb:file:prodDb;shutdown=true"
+		}
+	}
+}
Index: /branches/util-apps/DomainUtil/grails-app/conf/UrlMappings.groovy
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/conf/UrlMappings.groovy	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/conf/UrlMappings.groovy	(revision 766)
@@ -0,0 +1,11 @@
+class UrlMappings {
+    static mappings = {
+      "/$controller/$action?/$id?"{
+	      constraints {
+			 // apply constraints here
+		  }
+	  }
+      "/"(view:"/index")
+	  "500"(view:'/error')
+	}
+}
Index: /branches/util-apps/DomainUtil/grails-app/conf/spring/resources.groovy
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/conf/spring/resources.groovy	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/conf/spring/resources.groovy	(revision 766)
@@ -0,0 +1,4 @@
+// Place your Spring DSL code here
+beans = {
+    
+}
Index: /branches/util-apps/DomainUtil/grails-app/controllers/UtilController.groovy
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/controllers/UtilController.groovy	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/controllers/UtilController.groovy	(revision 766)
@@ -0,0 +1,98 @@
+class UtilController {
+
+    def index = { redirect(action:createData,params:params) }
+
+    def run = {
+
+        def tp
+        def m
+
+        if(!TaskProcedure.count()) {
+            println "Creating."
+            tp = new TaskProcedure(linkedTask: Task.get(1))
+        }
+        else {
+            tp = TaskProcedure.list()[-1]
+            println "Use existing: ${tp}"
+        }
+
+        m = new MaintenanceAction(taskProcedure:tp, description: "action")
+        tp.addToMaintenanceActions(m)
+        tp.save()
+
+        def s = "TaskProcedure: ${tp.id} - ${tp.description}, ${tp.maintenanceActions} <br />"
+        s += "MaintenanceAction: ${MaintenanceAction.list()[-1].taskProcedure.description} <br />"
+        render  s
+
+    }
+
+    def createData = {
+
+        def p = [:]
+
+        //Base.
+        createBasePersons()
+        createBaseTasks()
+        //Demo.
+        createDemoTasks()
+
+        render Task.list()
+    }
+
+    def private createBasePersons() {
+        def p = [:]
+
+        if(!Authority.count())
+            def authority = new Authority(description:"Admin", authority: "ROLE_Admin") .save(failOnError:true)
+
+        if(!Person.count()) {
+            p = [loginName: 'admin',
+                    firstName: 'admin',
+                    lastName: 'Powers',
+                    pass: 'pass',
+                    password: 'pass']
+            def person = new Person(p) .save(failOnError:true)
+        }
+
+    }
+
+    def private createBaseTasks() {
+        def p = [:]
+
+        if(!TaskGroup.count())
+            def taskGroup = new TaskGroup(name:"Engineering Activites").save(failOnError:true)
+
+        if(!TaskStatus.count())
+            def taskStatus = new TaskStatus(name:'Not Started').save(failOnError:true)
+
+        if(!TaskPriority.count())
+            def taskPriority = new TaskPriority(name:'High').save(failOnError:true)
+
+        if(!TaskBudgetStatus.count())
+            def taskBudgetStatus = new TaskBudgetStatus(name:"Planned").save(failOnError:true)
+
+        if(!TaskType.count())
+            def taskType = new TaskType(name:"Immediate Callout") .save(failOnError:true)
+
+    }
+
+    def private createDemoTasks() {
+        def p = [:]
+
+        if(!Task.list()) {
+
+            //Task #1
+            p = [taskGroup:TaskGroup.get(1),
+                    taskStatus:TaskStatus.get(1),
+                    taskPriority:TaskPriority.get(1),
+                    taskBudgetStatus:TaskBudgetStatus.get(1),
+                    taskType:TaskType.get(1),
+                    leadPerson: Person.get(1),
+                    description:"100hr Service"]
+
+            def task = new Task(p).save(failOnError:true)
+        }
+
+    }
+
+}
Index: /branches/util-apps/DomainUtil/grails-app/domain
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/domain	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/domain	(revision 766)
@@ -0,0 +1,1 @@
+link ../../../features/taskProcedureRework/grails-app/domain
Index: /branches/util-apps/DomainUtil/grails-app/i18n/messages.properties
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/i18n/messages.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/i18n/messages.properties	(revision 766)
@@ -0,0 +1,55 @@
+default.doesnt.match.message=Property [{0}] of class [{1}] with value [{2}] does not match the required pattern [{3}]
+default.invalid.url.message=Property [{0}] of class [{1}] with value [{2}] is not a valid URL
+default.invalid.creditCard.message=Property [{0}] of class [{1}] with value [{2}] is not a valid credit card number
+default.invalid.email.message=Property [{0}] of class [{1}] with value [{2}] is not a valid e-mail address
+default.invalid.range.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid range from [{3}] to [{4}]
+default.invalid.size.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid size range from [{3}] to [{4}]
+default.invalid.max.message=Property [{0}] of class [{1}] with value [{2}] exceeds maximum value [{3}]
+default.invalid.min.message=Property [{0}] of class [{1}] with value [{2}] is less than minimum value [{3}]
+default.invalid.max.size.message=Property [{0}] of class [{1}] with value [{2}] exceeds the maximum size of [{3}]
+default.invalid.min.size.message=Property [{0}] of class [{1}] with value [{2}] is less than the minimum size of [{3}]
+default.invalid.validator.message=Property [{0}] of class [{1}] with value [{2}] does not pass custom validation
+default.not.inlist.message=Property [{0}] of class [{1}] with value [{2}] is not contained within the list [{3}]
+default.blank.message=Property [{0}] of class [{1}] cannot be blank
+default.not.equal.message=Property [{0}] of class [{1}] with value [{2}] cannot equal [{3}]
+default.null.message=Property [{0}] of class [{1}] cannot be null
+default.not.unique.message=Property [{0}] of class [{1}] with value [{2}] must be unique
+
+default.paginate.prev=Previous
+default.paginate.next=Next
+default.boolean.true=True
+default.boolean.false=False
+default.date.format=yyyy-MM-dd HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} created
+default.updated.message={0} {1} updated
+default.deleted.message={0} {1} deleted
+default.not.deleted.message={0} {1} could not be deleted
+default.not.found.message={0} not found with id {1}
+default.optimistic.locking.failure=Another user has updated this {0} while you were editing
+
+default.home.label=Home
+default.list.label={0} List
+default.add.label=Add {0}
+default.new.label=New {0}
+default.create.label=Create {0}
+default.show.label=Show {0}
+default.edit.label=Edit {0}
+
+default.button.create.label=Create
+default.button.edit.label=Edit
+default.button.update.label=Update
+default.button.delete.label=Delete
+default.button.delete.confirm.message=Are you sure?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Property {0} must be a valid URL
+typeMismatch.java.net.URI=Property {0} must be a valid URI
+typeMismatch.java.util.Date=Property {0} must be a valid Date
+typeMismatch.java.lang.Double=Property {0} must be a valid number
+typeMismatch.java.lang.Integer=Property {0} must be a valid number
+typeMismatch.java.lang.Long=Property {0} must be a valid number
+typeMismatch.java.lang.Short=Property {0} must be a valid number
+typeMismatch.java.math.BigDecimal=Property {0} must be a valid number
+typeMismatch.java.math.BigInteger=Property {0} must be a valid number
Index: /branches/util-apps/DomainUtil/grails-app/i18n/messages_da.properties
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/i18n/messages_da.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/i18n/messages_da.properties	(revision 766)
@@ -0,0 +1,56 @@
+default.doesnt.match.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overholder ikke mønsteret [{3}]
+default.invalid.url.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke en gyldig URL
+default.invalid.creditCard.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke et gyldigt kreditkortnummer
+default.invalid.email.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke en gyldig e-mail adresse
+default.invalid.range.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] ligger ikke inden for intervallet fra  [{3}] til [{4}]
+default.invalid.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] ligger ikke inden for størrelsen fra [{3}] til [{4}]
+default.invalid.max.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overstiger den maksimale værdi [{3}]
+default.invalid.min.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er under den minimale værdi [{3}]
+default.invalid.max.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overstiger den maksimale størrelse på [{3}]
+default.invalid.min.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er under den minimale størrelse på [{3}]
+default.invalid.validator.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overholder ikke den brugerdefinerede validering
+default.not.inlist.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] findes ikke i listen [{3}]
+default.blank.message=Feltet [{0}] i klassen [{1}] kan ikke være tom
+default.not.equal.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] må ikke være [{3}]
+default.null.message=Feltet [{0}] i klassen [{1}] kan ikke være null
+default.not.unique.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] skal være unik
+
+default.paginate.prev=Forrige
+default.paginate.next=Næste
+default.boolean.true=Sand
+default.boolean.false=Falsk
+default.date.format=yyyy-MM-dd HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} oprettet
+default.updated.message={0} {1} opdateret
+default.deleted.message={0} {1} slettet
+default.not.deleted.message={0} {1} kunne ikke slettes
+default.not.found.message={0} med id {1} er ikke fundet 
+default.optimistic.locking.failure=En anden bruger har opdateret denne {0} imens du har lavet rettelser
+
+default.home.label=Hjem
+default.list.label={0} Liste
+default.add.label=Tilføj {0}
+default.new.label=Ny {0}
+default.create.label=Opret {0}
+default.show.label=Vis {0}
+default.edit.label=Ret {0}
+
+default.button.create.label=Opret
+default.button.edit.label=Ret
+default.button.update.label=Opdater
+default.button.delete.label=Slet
+default.button.delete.confirm.message=Er du sikker?
+
+# Databindingsfejl. Brug "typeMismatch.$className.$propertyName for at passe til en given klasse (f.eks typeMismatch.Book.author)
+typeMismatch.java.net.URL=Feltet {0} skal være en valid URL
+typeMismatch.java.net.URI=Feltet {0} skal være en valid URI
+typeMismatch.java.util.Date=Feltet {0} skal være en valid Dato
+typeMismatch.java.lang.Double=Feltet {0} skal være et valid tal
+typeMismatch.java.lang.Integer=Feltet {0} skal være et valid tal 
+typeMismatch.java.lang.Long=Feltet {0} skal være et valid tal
+typeMismatch.java.lang.Short=Feltet {0} skal være et valid tal
+typeMismatch.java.math.BigDecimal=Feltet {0} skal være et valid tal
+typeMismatch.java.math.BigInteger=Feltet {0} skal være et valid tal
+
Index: /branches/util-apps/DomainUtil/grails-app/i18n/messages_de.properties
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/i18n/messages_de.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/i18n/messages_de.properties	(revision 766)
@@ -0,0 +1,55 @@
+default.doesnt.match.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] entspricht nicht dem vorgegebenen Muster [{3}]
+default.invalid.url.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gÃŒltige URL
+default.invalid.creditCard.message=Das Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gÃŒltige Kreditkartennummer
+default.invalid.email.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gÃŒltige E-Mail Adresse
+default.invalid.range.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht im Wertebereich von [{3}] bis [{4}]
+default.invalid.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht im Wertebereich von [{3}] bis [{4}]
+default.invalid.max.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist grÃ¶Ãer als der HÃ¶chstwert von [{3}]
+default.invalid.min.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist kleiner als der Mindestwert von [{3}]
+default.invalid.max.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ÃŒbersteigt den HÃ¶chstwert von [{3}]
+default.invalid.min.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] unterschreitet den Mindestwert von [{3}]
+default.invalid.validator.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist ungÃŒltig
+default.not.inlist.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht in der Liste [{3}] enthalten.
+default.blank.message=Die Eigenschaft [{0}] des Typs [{1}] darf nicht leer sein
+default.not.equal.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] darf nicht gleich [{3}] sein
+default.null.message=Die Eigenschaft [{0}] des Typs [{1}] darf nicht null sein
+default.not.unique.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] darf nur einmal vorkommen
+
+default.paginate.prev=Vorherige
+default.paginate.next=NÃ€chste
+default.boolean.true=Wahr
+default.boolean.false=Falsch
+default.date.format=dd.MM.yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} wurde angelegt
+default.updated.message={0} {1} wurde geÃ€ndert
+default.deleted.message={0} {1} wurde gelÃ¶scht
+default.not.deleted.message={0} {1} konnte nicht gelÃ¶scht werden
+default.not.found.message={0} mit der id {1} wurde nicht gefunden
+default.optimistic.locking.failure=Ein anderer Benutzer hat das {0} Object geÃ€ndert wÃ€hrend Sie es bearbeitet haben
+
+default.home.label=Home
+default.list.label={0} Liste
+default.add.label={0} hinzufÃŒgen
+default.new.label={0} anlegen
+default.create.label={0} anlegen
+default.show.label={0} anzeigen
+default.edit.label={0} bearbeiten
+
+default.button.create.label=Anlegen
+default.button.edit.label=Bearbeiten
+default.button.update.label=Aktualisieren
+default.button.delete.label=LÃ¶schen
+default.button.delete.confirm.message=Sind Sie sicher?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Die Eigenschaft {0} muss eine gÃŒltige URL sein
+typeMismatch.java.net.URI=Die Eigenschaft {0} muss eine gÃŒltige URI sein
+typeMismatch.java.util.Date=Die Eigenschaft {0} muss ein gÃŒltiges Datum sein
+typeMismatch.java.lang.Double=Die Eigenschaft {0} muss eine gÃŒltige Zahl sein
+typeMismatch.java.lang.Integer=Die Eigenschaft {0} muss eine gÃŒltige Zahl sein
+typeMismatch.java.lang.Long=Die Eigenschaft {0} muss eine gÃŒltige Zahl sein
+typeMismatch.java.lang.Short=Die Eigenschaft {0} muss eine gÃŒltige Zahl sein
+typeMismatch.java.math.BigDecimal=Die Eigenschaft {0} muss eine gÃŒltige Zahl sein
+typeMismatch.java.math.BigInteger=Die Eigenschaft {0} muss eine gÃŒltige Zahl sein
Index: /branches/util-apps/DomainUtil/grails-app/i18n/messages_es.properties
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/i18n/messages_es.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/i18n/messages_es.properties	(revision 766)
@@ -0,0 +1,30 @@
+default.doesnt.match.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no corresponde al patrÃ³n [{3}]
+default.invalid.url.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es una URL vÃ¡lida
+default.invalid.creditCard.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es un nÃºmero de tarjeta de crÃ©dito vÃ¡lida
+default.invalid.email.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es una direcciÃ³n de correo electrÃ³nico vÃ¡lida
+default.invalid.range.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no entra en el rango vÃ¡lido de [{3}] a [{4}]
+default.invalid.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no entra en el tamaÃ±o vÃ¡lido de [{3}] a [{4}]
+default.invalid.max.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] excede el valor mÃ¡ximo [{3}]
+default.invalid.min.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] es menos que el valor mÃ­nimo [{3}]
+default.invalid.max.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] excede el tamaÃ±o mÃ¡ximo de [{3}]
+default.invalid.min.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] es menor que el tamaÃ±o mÃ­nimo de [{3}]
+default.invalid.validator.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es vÃ¡lido
+default.not.inlist.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no esta contenido dentro de la lista [{3}]
+default.blank.message=La propiedad [{0}] de la clase [{1}] no puede ser vacÃ­a
+default.not.equal.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no puede igualar a [{3}]
+default.null.message=La propiedad [{0}] de la clase [{1}] no puede ser nulo
+default.not.unique.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] debe ser Ãºnica
+
+default.paginate.prev=Anterior
+default.paginate.next=Siguiente
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=La propiedad {0} debe ser una URL vÃ¡lida
+typeMismatch.java.net.URI=La propiedad {0} debe ser una URI vÃ¡lida
+typeMismatch.java.util.Date=La propiedad {0} debe ser una fecha vÃ¡lida
+typeMismatch.java.lang.Double=La propiedad {0} debe ser un nÃºmero vÃ¡lido
+typeMismatch.java.lang.Integer=La propiedad {0} debe ser un nÃºmero vÃ¡lido
+typeMismatch.java.lang.Long=La propiedad {0} debe ser un nÃºmero vÃ¡lido
+typeMismatch.java.lang.Short=La propiedad {0} debe ser un nÃºmero vÃ¡lido
+typeMismatch.java.math.BigDecimal=La propiedad {0} debe ser un nÃºmero vÃ¡lido
+typeMismatch.java.math.BigInteger=La propiedad {0} debe ser un nÃºmero vÃ¡lido
Index: /branches/util-apps/DomainUtil/grails-app/i18n/messages_fr.properties
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/i18n/messages_fr.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/i18n/messages_fr.properties	(revision 766)
@@ -0,0 +1,19 @@
+default.doesnt.match.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] ne correspond pas au pattern [{3}]
+default.invalid.url.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas une URL valide
+default.invalid.creditCard.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas un numÃ©ro de carte de crÃ©dit valide
+default.invalid.email.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas une adresse e-mail valide
+default.invalid.range.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas contenue dans l'intervalle [{3}] Ã  [{4}]
+default.invalid.size.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas contenue dans l'intervalle [{3}] Ã  [{4}]
+default.invalid.max.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] est supÃ©rieure Ã  la valeur maximum [{3}]
+default.invalid.min.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] est infÃ©rieure Ã  la valeur minimum [{3}]
+default.invalid.max.size.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] est supÃ©rieure Ã  la valeur maximum [{3}]
+default.invalid.min.size.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] est infÃ©rieure Ã  la valeur minimum [{3}]
+default.invalid.validator.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas valide
+default.not.inlist.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] ne fait pas partie de la liste [{3}]
+default.blank.message=La propriÃ©tÃ© [{0}] de la classe [{1}] ne peut pas Ãªtre vide
+default.not.equal.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] ne peut pas Ãªtre Ã©gale Ã  [{3}]
+default.null.message=La propriÃ©tÃ© [{0}] de la classe [{1}] ne peut pas Ãªtre nulle
+default.not.unique.message=La propriÃ©tÃ© [{0}] de la classe [{1}] avec la valeur [{2}] doit Ãªtre unique
+
+default.paginate.prev=PrÃ©cÃ©dent
+default.paginate.next=Suivant
Index: /branches/util-apps/DomainUtil/grails-app/i18n/messages_it.properties
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/i18n/messages_it.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/i18n/messages_it.properties	(revision 766)
@@ -0,0 +1,19 @@
+default.doesnt.match.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] non corrisponde al pattern [{3}]
+default.invalid.url.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] non Ãš un URL valido
+default.invalid.creditCard.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] non Ãš un numero di carta di credito valido
+default.invalid.email.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] non Ãš un indirizzo email valido
+default.invalid.range.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] non rientra nell'intervallo valido da [{3}] a [{4}]
+default.invalid.size.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] non rientra nell'intervallo di dimensioni valide da [{3}] a [{4}]
+default.invalid.max.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] Ãš maggiore di [{3}]
+default.invalid.min.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] Ãš minore di [{3}]
+default.invalid.max.size.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] Ãš maggiore di [{3}]
+default.invalid.min.size.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] Ãš minore di [{3}]
+default.invalid.validator.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] non Ãš valida
+default.not.inlist.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] non Ãš contenuta nella lista [{3}]
+default.blank.message=La proprietÃ  [{0}] della classe [{1}] non puÃ² essere vuota
+default.not.equal.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] non puÃ² essere uguale a [{3}]
+default.null.message=La proprietÃ  [{0}] della classe [{1}] non puÃ² essere null
+default.not.unique.message=La proprietÃ  [{0}] della classe [{1}] con valore [{2}] deve essere unica
+
+default.paginate.prev=Precedente
+default.paginate.next=Successivo
Index: /branches/util-apps/DomainUtil/grails-app/i18n/messages_ja.properties
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/i18n/messages_ja.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/i18n/messages_ja.properties	(revision 766)
@@ -0,0 +1,22 @@
+default.doesnt.match.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯ã[{3}]ãã¿ãŒã³ãšäžèŽããŠããŸããã
+default.invalid.url.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯ãURLã§ã¯ãããŸããã
+default.invalid.creditCard.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯ãæ­£åœãªã¯ã¬ãžããã«ãŒãçªå·ã§ã¯ãããŸããã
+default.invalid.email.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯ãã¡ãŒã«ã¢ãã¬ã¹ã§ã¯ãããŸããã
+default.invalid.range.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯ã[{3}]ãã[{4}]ç¯å²å
+ãæå®ããŠãã ããã
+default.invalid.size.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯ã[{3}]ãã[{4}]ä»¥å
+ãæå®ããŠãã ããã
+default.invalid.max.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯ãæå€§å€[{3}]ããå€§ããã§ãã
+default.invalid.min.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯ãæå°å€[{3}]ããå°ããã§ãã
+default.invalid.max.size.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯ãæå€§å€[{3}]ããå€§ããã§ãã
+default.invalid.min.size.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯ãæå°å€[{3}]ããå°ããã§ãã
+default.invalid.validator.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯ãã«ã¹ã¿ã ããªããŒã·ã§ã³ãééã§ããŸããã
+default.not.inlist.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯ã[{3}]ãªã¹ãå
+ã«å­åšããŸããã
+default.blank.message=[{1}]ã¯ã©ã¹ã®ãã­ããã£[{0}]ã®ç©ºçœã¯èš±å¯ãããŸããã
+default.not.equal.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯ã[{3}]ãšåç­ã§ã¯ãããŸããã
+default.null.message=[{1}]ã¯ã©ã¹ã®ãã­ããã£[{0}]ã«nullã¯èš±å¯ãããŸããã
+default.not.unique.message=ã¯ã©ã¹[{1}]ãã­ããã£[{0}]ã®å€[{2}]ã¯æ¢ã«äœ¿çšãããŠããŸãã
+
+default.paginate.prev=æ»ã
+default.paginate.next=æ¬¡ãž
Index: /branches/util-apps/DomainUtil/grails-app/i18n/messages_nl.properties
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/i18n/messages_nl.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/i18n/messages_nl.properties	(revision 766)
@@ -0,0 +1,55 @@
+default.doesnt.match.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] komt niet overeen met het vereiste patroon [{3}]
+default.invalid.url.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldige URL
+default.invalid.creditCard.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldig credit card nummer
+default.invalid.email.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is geen geldig e-mailadres
+default.invalid.range.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] valt niet in de geldige waardenreeks van [{3}] tot [{4}]
+default.invalid.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] valt niet in de geldige grootte van [{3}] tot [{4}]
+default.invalid.max.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] overschrijdt de maximumwaarde [{3}]
+default.invalid.min.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is minder dan de minimumwaarde [{3}]
+default.invalid.max.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] overschrijdt de maximumgrootte van [{3}]
+default.invalid.min.size.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is minder dan minimumgrootte van [{3}]
+default.invalid.validator.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] is niet geldig
+default.not.inlist.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] komt niet voor in de lijst [{3}]
+default.blank.message=Attribuut [{0}] van entiteit [{1}] mag niet leeg zijn
+default.not.equal.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] mag niet gelijk zijn aan [{3}]
+default.null.message=Attribuut [{0}] van entiteit [{1}] mag niet leeg zijn
+default.not.unique.message=Attribuut [{0}] van entiteit [{1}] met waarde [{2}] moet uniek zijn
+
+default.paginate.prev=Vorige
+default.paginate.next=Volgende
+default.boolean.true=Ja
+default.boolean.false=Nee
+default.date.format=dd-MM-yyyy HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} ingevoerd
+default.updated.message={0} {1} gewijzigd
+default.deleted.message={0} {1} verwijderd
+default.not.deleted.message={0} {1} kon niet worden verwijderd
+default.not.found.message={0} met id {1} kon niet worden gevonden
+default.optimistic.locking.failure=Een andere gebruiker heeft deze {0} al gewijzigd
+
+default.home.label=Home
+default.list.label={0} Overzicht
+default.add.label=Toevoegen {0}
+default.new.label=Invoeren {0}
+default.create.label=Invoeren {0}
+default.show.label=Details {0}
+default.edit.label=Wijzigen {0}
+
+default.button.create.label=Invoeren
+default.button.edit.label=Wijzigen
+default.button.update.label=Opslaan
+default.button.delete.label=Verwijderen
+default.button.delete.confirm.message=Weet je het zeker?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Attribuut {0} is geen geldige URL
+typeMismatch.java.net.URI=Attribuut {0} is geen geldige URI
+typeMismatch.java.util.Date=Attribuut {0} is geen geldige datum
+typeMismatch.java.lang.Double=Attribuut {0} is geen geldig nummer
+typeMismatch.java.lang.Integer=Attribuut {0} is geen geldig nummer
+typeMismatch.java.lang.Long=Attribuut {0} is geen geldig nummer
+typeMismatch.java.lang.Short=Attribuut {0} is geen geldig nummer
+typeMismatch.java.math.BigDecimal=Attribuut {0} is geen geldig nummer
+typeMismatch.java.math.BigInteger=Attribuut {0} is geen geldig nummer
Index: /branches/util-apps/DomainUtil/grails-app/i18n/messages_pt_BR.properties
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/i18n/messages_pt_BR.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/i18n/messages_pt_BR.properties	(revision 766)
@@ -0,0 +1,34 @@
+#
+# Translated by Lucas Teixeira - lucastex@gmail.com
+#
+
+default.doesnt.match.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o atende ao padrÃ£o definido [{3}]
+default.invalid.url.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o Ã© uma URL vÃ¡lida
+default.invalid.creditCard.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o Ã© um nÃºmero vÃ¡lido de cartÃ£o de crÃ©dito
+default.invalid.email.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o Ã© um endereÃ§o de email vÃ¡lido.
+default.invalid.range.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o estÃ¡ entre a faixa de valores vÃ¡lida de [{3}] atÃ© [{4}]
+default.invalid.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o estÃ¡ na faixa de tamanho vÃ¡lida de [{3}] atÃ© [{4}]
+default.invalid.max.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapass o valor mÃ¡ximo [{3}]
+default.invalid.min.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o atinge o valor mÃ­nimo [{3}]
+default.invalid.max.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o tamanho mÃ¡ximo de [{3}]
+default.invalid.min.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o atinge o tamanho mÃ­nimo de [{3}]
+default.invalid.validator.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o passou na validaÃ§Ã£o
+default.not.inlist.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o Ã© um valor dentre os permitidos na lista [{3}]
+default.blank.message=O campo [{0}] da classe [{1}] nÃ£o pode ficar em branco
+default.not.equal.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o pode ser igual a [{3}]
+default.null.message=O campo [{0}] da classe [{1}] nÃ£o pode ser vazia
+default.not.unique.message=O campo [{0}] da classe [{1}] com o valor [{2}] deve ser Ãºnico
+
+default.paginate.prev=Anterior
+default.paginate.next=PrÃ³ximo
+
+# Mensagens de erro em atribuiÃ§Ã£o de valores. Use "typeMismatch.$className.$propertyName" para customizar (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=O campo {0} deve ser uma URL vÃ¡lida.
+typeMismatch.java.net.URI=O campo {0} deve ser uma URI vÃ¡lida.
+typeMismatch.java.util.Date=O campo {0} deve ser uma data vÃ¡lida
+typeMismatch.java.lang.Double=O campo {0} deve ser um nÃºmero vÃ¡lido.
+typeMismatch.java.lang.Integer=O campo {0} deve ser um nÃºmero vÃ¡lido.
+typeMismatch.java.lang.Long=O campo {0} deve ser um nÃºmero vÃ¡lido.
+typeMismatch.java.lang.Short=O campo {0} deve ser um nÃºmero vÃ¡lido.
+typeMismatch.java.math.BigDecimal=O campo {0} deve ser um nÃºmero vÃ¡lido.
+typeMismatch.java.math.BigInteger=O campo {0} deve ser um nÃºmero vÃ¡lido.
Index: /branches/util-apps/DomainUtil/grails-app/i18n/messages_pt_PT.properties
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/i18n/messages_pt_PT.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/i18n/messages_pt_PT.properties	(revision 766)
@@ -0,0 +1,34 @@
+#
+# translation by miguel.ping@gmail.com, based on pt_BR translation by Lucas Teixeira - lucastex@gmail.com
+#
+
+default.doesnt.match.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o corresponde ao padrÃ£o definido [{3}]
+default.invalid.url.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o Ã© um URL vÃ¡lido
+default.invalid.creditCard.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o Ã© um nÃºmero vÃ¡lido de cartÃ£o de crÃ©dito
+default.invalid.email.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o Ã© um endereÂÃ§o de email vÃ¡lido.
+default.invalid.range.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o estÃ¡ dentro dos limites de valores vÃ¡lidos de [{3}] a [{4}]
+default.invalid.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] estÃ¡ fora dos limites de tamanho vÃ¡lido de [{3}] a [{4}]
+default.invalid.max.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o valor mÃ¡ximo [{3}]
+default.invalid.min.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o atinge o valor mÃ­nimo [{3}]
+default.invalid.max.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] ultrapassa o tamanho mÃ¡ximo de [{3}]
+default.invalid.min.size.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o atinge o tamanho mÃ­nimo de [{3}]
+default.invalid.validator.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o passou na validaÂÃ§Ã£o
+default.not.inlist.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o se encontra nos valores permitidos da lista [{3}]
+default.blank.message=O campo [{0}] da classe [{1}] nÃ£o pode ser vazio
+default.not.equal.message=O campo [{0}] da classe [{1}] com o valor [{2}] nÃ£o pode ser igual a [{3}]
+default.null.message=O campo [{0}] da classe [{1}] nÃ£o pode ser vazio
+default.not.unique.message=O campo [{0}] da classe [{1}] com o valor [{2}] deve ser Ãºnico
+
+default.paginate.prev=Anterior
+default.paginate.next=PrÃ³ximo
+
+# Mensagens de erro em atribuiÂÃ§Ã£o de valores. Use "typeMismatch.$className.$propertyName" para personalizar(eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=O campo {0} deve ser um URL vÃ¡lido.
+typeMismatch.java.net.URI=O campo {0} deve ser um URI vÃ¡lido.
+typeMismatch.java.util.Date=O campo {0} deve ser uma data vÃ¡lida
+typeMismatch.java.lang.Double=O campo {0} deve ser um nÃºmero vÃ¡lido.
+typeMismatch.java.lang.Integer=O campo {0} deve ser um nÃºmero vÃ¡lido.
+typeMismatch.java.lang.Long=O campo {0} deve ser um nÃºmero valido.
+typeMismatch.java.lang.Short=O campo {0} deve ser um nÃºmero vÃ¡lido.
+typeMismatch.java.math.BigDecimal=O campo {0} deve ser um nÃºmero vÃ¡lido.
+typeMismatch.java.math.BigInteger=O campo {0} deve ser um nÃºmero vÃ¡lido.
Index: /branches/util-apps/DomainUtil/grails-app/i18n/messages_ru.properties
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/i18n/messages_ru.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/i18n/messages_ru.properties	(revision 766)
@@ -0,0 +1,33 @@
+default.doesnt.match.message=ÐÐœÐ°ÑÐµÐœÐžÐµ [{2}] Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] ÐœÐµ ÑÐŸÐŸÑÐ²ÐµÑÑÑÐ²ÑÐµÑ ÐŸÐ±ÑÐ°Ð·ÑÑ [{3}]
+default.invalid.url.message=ÐÐœÐ°ÑÐµÐœÐžÐµ [{2}] Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] ÐœÐµ ÑÐ²Ð»ÑÐµÑÑÑ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐŒ URL-Ð°ÐŽÑÐµÑÐŸÐŒ
+default.invalid.creditCard.message=ÐÐœÐ°ÑÐµÐœÐžÐµ [{2}] Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] ÐœÐµ ÑÐ²Ð»ÑÐµÑÑÑ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐŒ ÐœÐŸÐŒÐµÑÐŸÐŒ ÐºÑÐµÐŽÐžÑÐœÐŸÐ¹ ÐºÐ°ÑÑÑ
+default.invalid.email.message=ÐÐœÐ°ÑÐµÐœÐžÐµ [{2}] Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] ÐœÐµ ÑÐ²Ð»ÑÐµÑÑÑ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐŒ e-mail Ð°ÐŽÑÐµÑÐŸÐŒ
+default.invalid.range.message=ÐÐœÐ°ÑÐµÐœÐžÐµ [{2}] Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] ÐœÐµ Ð¿ÐŸÐ¿Ð°ÐŽÐ°ÐµÑ Ð² ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐ¹ ÐžÐœÑÐµÑÐ²Ð°Ð» ÐŸÑ [{3}] ÐŽÐŸ [{4}]
+default.invalid.size.message=Ð Ð°Ð·ÐŒÐµÑ Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] (Ð·ÐœÐ°ÑÐµÐœÐžÐµ: [{2}]) ÐœÐµ Ð¿ÐŸÐ¿Ð°ÐŽÐ°ÐµÑ Ð² ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐ¹ ÐžÐœÑÐµÑÐ²Ð°Ð» ÐŸÑ [{3}] ÐŽÐŸ [{4}]
+default.invalid.max.message=ÐÐœÐ°ÑÐµÐœÐžÐµ [{2}] Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] Ð±ÐŸÐ»ÑÑÐµ ÑÐµÐŒ ÐŒÐ°ÐºÑÐžÐŒÐ°Ð»ÑÐœÐŸ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÐŸÐµ Ð·ÐœÐ°ÑÐµÐœÐžÐµ [{3}]
+default.invalid.min.message=ÐÐœÐ°ÑÐµÐœÐžÐµ [{2}] Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] ÐŒÐµÐœÑÑÐµ ÑÐµÐŒ ÐŒÐžÐœÐžÐŒÐ°Ð»ÑÐœÐŸ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÐŸÐµ Ð·ÐœÐ°ÑÐµÐœÐžÐµ [{3}]
+default.invalid.max.size.message=Ð Ð°Ð·ÐŒÐµÑ Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] (Ð·ÐœÐ°ÑÐµÐœÐžÐµ: [{2}]) Ð±ÐŸÐ»ÑÑÐµ ÑÐµÐŒ ÐŒÐ°ÐºÑÐžÐŒÐ°Ð»ÑÐœÐŸ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐ¹ ÑÐ°Ð·ÐŒÐµÑ [{3}]
+default.invalid.min.size.message=Ð Ð°Ð·ÐŒÐµÑ Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] (Ð·ÐœÐ°ÑÐµÐœÐžÐµ: [{2}]) ÐŒÐµÐœÑÑÐµ ÑÐµÐŒ ÐŒÐžÐœÐžÐŒÐ°Ð»ÑÐœÐŸ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐ¹ ÑÐ°Ð·ÐŒÐµÑ [{3}]
+default.invalid.validator.message=ÐÐœÐ°ÑÐµÐœÐžÐµ [{2}] Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] ÐœÐµ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÐŸ
+default.not.inlist.message=ÐÐœÐ°ÑÐµÐœÐžÐµ [{2}] Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] ÐœÐµ Ð¿ÐŸÐ¿Ð°ÐŽÐ°ÐµÑ Ð² ÑÐ¿ÐžÑÐŸÐº ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÑ
+ Ð·ÐœÐ°ÑÐµÐœÐžÐ¹ [{3}]
+default.blank.message=ÐÐŸÐ»Ðµ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] ÐœÐµ ÐŒÐŸÐ¶ÐµÑ Ð±ÑÑÑ Ð¿ÑÑÑÑÐŒ
+default.not.equal.message=ÐÐœÐ°ÑÐµÐœÐžÐµ [{2}] Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] ÐœÐµ ÐŒÐŸÐ¶ÐµÑ Ð±ÑÑÑ ÑÐ°Ð²ÐœÐŸ [{3}]
+default.null.message=ÐÐŸÐ»Ðµ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] ÐœÐµ ÐŒÐŸÐ¶ÐµÑ ÐžÐŒÐµÑÑ Ð·ÐœÐ°ÑÐµÐœÐžÐµ null
+default.not.unique.message=ÐÐœÐ°ÑÐµÐœÐžÐµ [{2}] Ð¿ÐŸÐ»Ñ [{0}] ÐºÐ»Ð°ÑÑÐ° [{1}] ÐŽÐŸÐ»Ð¶ÐœÐŸ Ð±ÑÑÑ ÑÐœÐžÐºÐ°Ð»ÑÐœÑÐŒ
+
+default.paginate.prev=ÐÑÐµÐŽÑÐŽÑÑÐ°Ñ ÑÑÑÐ°ÐœÐžÑÐ°
+default.paginate.next=Ð¡Ð»ÐµÐŽÑÑÑÐ°Ñ ÑÑÑÐ°ÐœÐžÑÐ°
+
+# ÐÑÐžÐ±ÐºÐž Ð¿ÑÐž Ð¿ÑÐžÑÐ²ÐŸÐµÐœÐžÐž ÐŽÐ°ÐœÐœÑÑ
+. ÐÐ»Ñ ÑÐŸÑÐœÐŸÐ¹ ÐœÐ°ÑÑÑÐŸÐ¹ÐºÐž ÐŽÐ»Ñ Ð¿ÐŸÐ»ÐµÐ¹ ÐºÐ»Ð°ÑÑÐŸÐ² ÐžÑÐ¿ÐŸÐ»ÑÐ·ÑÐ¹ÑÐµ
+# ÑÐŸÑÐŒÐ°Ñ "typeMismatch.$className.$propertyName" (ÐœÐ°Ð¿ÑÐžÐŒÐµÑ, typeMismatch.Book.author)
+typeMismatch.java.net.URL=ÐÐœÐ°ÑÐµÐœÐžÐµ Ð¿ÐŸÐ»Ñ {0} ÐœÐµ ÑÐ²Ð»ÑÐµÑÑÑ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐŒ URL
+typeMismatch.java.net.URI=ÐÐœÐ°ÑÐµÐœÐžÐµ Ð¿ÐŸÐ»Ñ {0} ÐœÐµ ÑÐ²Ð»ÑÐµÑÑÑ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐŒ URI
+typeMismatch.java.util.Date=ÐÐœÐ°ÑÐµÐœÐžÐµ Ð¿ÐŸÐ»Ñ {0} ÐœÐµ ÑÐ²Ð»ÑÐµÑÑÑ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÐŸÐ¹ ÐŽÐ°ÑÐŸÐ¹
+typeMismatch.java.lang.Double=ÐÐœÐ°ÑÐµÐœÐžÐµ Ð¿ÐŸÐ»Ñ {0} ÐœÐµ ÑÐ²Ð»ÑÐµÑÑÑ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐŒ ÑÐžÑÐ»ÐŸÐŒ
+typeMismatch.java.lang.Integer=ÐÐœÐ°ÑÐµÐœÐžÐµ Ð¿ÐŸÐ»Ñ {0} ÐœÐµ ÑÐ²Ð»ÑÐµÑÑÑ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐŒ ÑÐžÑÐ»ÐŸÐŒ
+typeMismatch.java.lang.Long=ÐÐœÐ°ÑÐµÐœÐžÐµ Ð¿ÐŸÐ»Ñ {0} ÐœÐµ ÑÐ²Ð»ÑÐµÑÑÑ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐŒ ÑÐžÑÐ»ÐŸÐŒ
+typeMismatch.java.lang.Short=ÐÐœÐ°ÑÐµÐœÐžÐµ Ð¿ÐŸÐ»Ñ {0} ÐœÐµ ÑÐ²Ð»ÑÐµÑÑÑ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐŒ ÑÐžÑÐ»ÐŸÐŒ
+typeMismatch.java.math.BigDecimal=ÐÐœÐ°ÑÐµÐœÐžÐµ Ð¿ÐŸÐ»Ñ {0} ÐœÐµ ÑÐ²Ð»ÑÐµÑÑÑ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐŒ ÑÐžÑÐ»ÐŸÐŒ
+typeMismatch.java.math.BigInteger=ÐÐœÐ°ÑÐµÐœÐžÐµ Ð¿ÐŸÐ»Ñ {0} ÐœÐµ ÑÐ²Ð»ÑÐµÑÑÑ ÐŽÐŸÐ¿ÑÑÑÐžÐŒÑÐŒ ÑÐžÑÐ»ÐŸÐŒ
Index: /branches/util-apps/DomainUtil/grails-app/i18n/messages_th.properties
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/i18n/messages_th.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/i18n/messages_th.properties	(revision 766)
@@ -0,0 +1,35 @@
+﻿default.doesnt.match.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบที่กำหนดไว้ใน [{3}]
+default.invalid.url.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบ URL
+default.invalid.creditCard.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบหมายเลขบัตรเครดิต
+default.invalid.email.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ถูกต้องตามรูปแบบอีเมล์
+default.invalid.range.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้มีค่าที่ถูกต้องในช่วงจาก [{3}] ถึง [{4}]
+default.invalid.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้มีขนาดที่ถูกต้องในช่วงจาก [{3}] ถึง [{4}]
+default.invalid.max.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีค่าเกิดกว่าค่ามากสุด [{3}]
+default.invalid.min.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีค่าน้อยกว่าค่าต่ำสุด  [{3}]
+default.invalid.max.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีขนาดเกินกว่าขนาดมากสุดของ [{3}]
+default.invalid.min.size.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] มีขนาดต่ำกว่าขนาดต่ำสุดของ  [{3}]
+default.invalid.validator.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ผ่านการทวนสอบค่าที่ตั้งขึ้น
+default.not.inlist.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่ได้อยู่ในรายการต่อไปนี้  [{3}]
+default.blank.message=คุณสมบัติ [{0}] ของคลาส [{1}] ไม่สามารถเป็นค่าว่างได้
+default.not.equal.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] ไม่สามารถเท่ากับ [{3}] ได้
+default.null.message=คุณสมบัติ [{0}] ของคลาส [{1}] ไม่สามารถเป็น null ได้
+default.not.unique.message=คุณสมบัติ [{0}] ของคลาส [{1}] ซึ่งมีค่าเป็น [{2}] จะต้องไม่ซ้ำ (unique)
+
+default.paginate.prev=ก่อนหน้า
+default.paginate.next=ถัดไป
+
+default.boolean.true=จริง
+default.boolean.false=เท็จ
+default.date.format=dd-MM-yyyy HH:mm:ss z
+default.number.format=0
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=คุณสมบัติ '{0}' จะต้องเป็นค่า URL ที่ถูกต้อง
+typeMismatch.java.net.URI=คุณสมบัติ '{0}' จะต้องเป็นค่า URI ที่ถูกต้อง
+typeMismatch.java.util.Date=คุณสมบัติ '{0}' จะต้องมีค่าเป็นวันที่
+typeMismatch.java.lang.Double=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Double
+typeMismatch.java.lang.Integer=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Integer
+typeMismatch.java.lang.Long=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Long
+typeMismatch.java.lang.Short=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท Short
+typeMismatch.java.math.BigDecimal=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท BigDecimal
+typeMismatch.java.math.BigInteger=คุณสมบัติ '{0}' จะต้องมีค่าเป็นจำนวนประเภท BigInteger
Index: /branches/util-apps/DomainUtil/grails-app/i18n/messages_zh_CN.properties
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/i18n/messages_zh_CN.properties	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/i18n/messages_zh_CN.properties	(revision 766)
@@ -0,0 +1,18 @@
+default.blank.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u4E0D\u80FD\u4E3A\u7A7A
+default.doesnt.match.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0E\u5B9A\u4E49\u7684\u6A21\u5F0F [{3}]\u4E0D\u5339\u914D
+default.invalid.creditCard.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684\u4FE1\u7528\u5361\u53F7
+default.invalid.email.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684\u7535\u5B50\u90AE\u4EF6\u5730\u5740
+default.invalid.max.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u6BD4\u6700\u5927\u503C [{3}]\u8FD8\u5927
+default.invalid.max.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u6BD4\u6700\u5927\u503C [{3}]\u8FD8\u5927
+default.invalid.min.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u6BD4\u6700\u5C0F\u503C [{3}]\u8FD8\u5C0F
+default.invalid.min.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u6BD4\u6700\u5C0F\u503C [{3}]\u8FD8\u5C0F
+default.invalid.range.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u5728\u5408\u6CD5\u7684\u8303\u56F4\u5185( [{3}] \uFF5E [{4}] )
+default.invalid.size.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u7684\u5927\u5C0F\u4E0D\u5728\u5408\u6CD5\u7684\u8303\u56F4\u5185( [{3}] \uFF5E [{4}] )
+default.invalid.url.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u662F\u4E00\u4E2A\u5408\u6CD5\u7684URL
+default.invalid.validator.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u672A\u80FD\u901A\u8FC7\u81EA\u5B9A\u4E49\u7684\u9A8C\u8BC1
+default.not.equal.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0E[{3}]\u4E0D\u76F8\u7B49
+default.not.inlist.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u4E0D\u5728\u5217\u8868\u7684\u53D6\u503C\u8303\u56F4\u5185
+default.not.unique.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u7684\u503C[{2}]\u5FC5\u987B\u662F\u552F\u4E00\u7684
+default.null.message=[{1}]\u7C7B\u7684\u5C5E\u6027[{0}]\u4E0D\u80FD\u4E3Anull
+default.paginate.next=\u4E0B\u9875
+default.paginate.prev=\u4E0A\u9875
Index: /branches/util-apps/DomainUtil/grails-app/views/error.gsp
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/views/error.gsp	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/views/error.gsp	(revision 766)
@@ -0,0 +1,54 @@
+<html>
+  <head>
+	  <title>Grails Runtime Exception</title>
+	  <style type="text/css">
+	  		.message {
+	  			border: 1px solid black;
+	  			padding: 5px;
+	  			background-color:#E9E9E9;
+	  		}
+	  		.stack {
+	  			border: 1px solid black;
+	  			padding: 5px;
+	  			overflow:auto;
+	  			height: 300px;
+	  		}
+	  		.snippet {
+	  			padding: 5px;
+	  			background-color:white;
+	  			border:1px solid black;
+	  			margin:3px;
+	  			font-family:courier;
+	  		}
+	  </style>
+  </head>
+
+  <body>
+    <h1>Grails Runtime Exception</h1>
+    <h2>Error Details</h2>
+
+  	<div class="message">
+		<strong>Error ${request.'javax.servlet.error.status_code'}:</strong> ${request.'javax.servlet.error.message'.encodeAsHTML()}<br/>
+		<strong>Servlet:</strong> ${request.'javax.servlet.error.servlet_name'}<br/>
+		<strong>URI:</strong> ${request.'javax.servlet.error.request_uri'}<br/>
+		<g:if test="${exception}">
+	  		<strong>Exception Message:</strong> ${exception.message?.encodeAsHTML()} <br />
+	  		<strong>Caused by:</strong> ${exception.cause?.message?.encodeAsHTML()} <br />
+	  		<strong>Class:</strong> ${exception.className} <br />
+	  		<strong>At Line:</strong> [${exception.lineNumber}] <br />
+	  		<strong>Code Snippet:</strong><br />
+	  		<div class="snippet">
+	  			<g:each var="cs" in="${exception.codeSnippet}">
+	  				${cs?.encodeAsHTML()}<br />
+	  			</g:each>
+	  		</div>
+		</g:if>
+  	</div>
+	<g:if test="${exception}">
+	    <h2>Stack Trace</h2>
+	    <div class="stack">
+	      <pre><g:each in="${exception.stackTraceLines}">${it.encodeAsHTML()}<br/></g:each></pre>
+	    </div>
+	</g:if>
+  </body>
+</html>
Index: /branches/util-apps/DomainUtil/grails-app/views/index.gsp
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/views/index.gsp	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/views/index.gsp	(revision 766)
@@ -0,0 +1,104 @@
+<html>
+    <head>
+        <title>Welcome to Grails</title>
+		<meta name="layout" content="main" />
+		<style type="text/css" media="screen">
+
+			#nav {
+				margin-top:20px;
+				margin-left:30px;
+				width:228px;
+				float:left;
+
+			}
+			.homePagePanel * {
+				margin:0px;
+			}
+			.homePagePanel .panelBody ul {
+				list-style-type:none;
+				margin-bottom:10px;
+			}
+			.homePagePanel .panelBody h1 {
+				text-transform:uppercase;
+				font-size:1.1em;
+				margin-bottom:10px;
+			}
+			.homePagePanel .panelBody {
+			    background: url(images/leftnav_midstretch.png) repeat-y top;
+				margin:0px;
+				padding:15px;
+			}
+			.homePagePanel .panelBtm {
+			    background: url(images/leftnav_btm.png) no-repeat top;
+				height:20px;
+				margin:0px;
+			}
+
+			.homePagePanel .panelTop {
+			    background: url(images/leftnav_top.png) no-repeat top;
+				height:11px;
+				margin:0px;
+			}
+			h2 {
+				margin-top:15px;
+				margin-bottom:15px;
+				font-size:1.2em;
+			}
+			#pageBody {
+				margin-left:280px;
+				margin-right:20px;
+			}
+		</style>
+    </head>
+    <body>
+		<div id="nav">
+			<div class="homePagePanel">
+				<div class="panelTop">
+
+				</div>
+				<div class="panelBody">
+					<h1>Application Status</h1>
+					<ul>
+						<li>App version: <g:meta name="app.version"></g:meta></li>
+						<li>Grails version: <g:meta name="app.grails.version"></g:meta></li>
+						<li>JVM version: ${System.getProperty('java.version')}</li>
+						<li>Controllers: ${grailsApplication.controllerClasses.size()}</li>
+						<li>Domains: ${grailsApplication.domainClasses.size()}</li>
+						<li>Services: ${grailsApplication.serviceClasses.size()}</li>
+						<li>Tag Libraries: ${grailsApplication.tagLibClasses.size()}</li>
+					</ul>
+					<h1>Installed Plugins</h1>
+					<ul>
+						<g:set var="pluginManager"
+						       value="${applicationContext.getBean('pluginManager')}"></g:set>
+
+						<g:each var="plugin" in="${pluginManager.allPlugins}">
+							<li>${plugin.name} - ${plugin.version}</li>
+						</g:each>
+
+					</ul>
+				</div>
+				<div class="panelBtm">
+				</div>
+			</div>
+
+
+		</div>
+		<div id="pageBody">
+	        <h1>Welcome to Grails</h1>
+	        <p>Congratulations, you have successfully started your first Grails application! At the moment
+	        this is the default page, feel free to modify it to either redirect to a controller or display whatever
+	        content you may choose. Below is a list of controllers that are currently deployed in this application,
+	        click on each to execute its default action:</p>
+
+	        <div id="controllerList" class="dialog">
+				<h2>Available Controllers:</h2>
+	            <ul>
+	              <g:each var="c" in="${grailsApplication.controllerClasses}">
+	                    <li class="controller"><g:link controller="${c.logicalPropertyName}">${c.fullName}</g:link></li>
+	              </g:each>
+	            </ul>
+	        </div>
+		</div>
+    </body>
+</html>
Index: /branches/util-apps/DomainUtil/grails-app/views/layouts/main.gsp
===================================================================
--- /branches/util-apps/DomainUtil/grails-app/views/layouts/main.gsp	(revision 766)
+++ /branches/util-apps/DomainUtil/grails-app/views/layouts/main.gsp	(revision 766)
@@ -0,0 +1,16 @@
+<html>
+    <head>
+        <title><g:layoutTitle default="Grails" /></title>
+        <link rel="stylesheet" href="${resource(dir:'css',file:'main.css')}" />
+        <link rel="shortcut icon" href="${resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
+        <g:layoutHead />
+        <g:javascript library="application" />
+    </head>
+    <body>
+        <div id="spinner" class="spinner" style="display:none;">
+            <img src="${resource(dir:'images',file:'spinner.gif')}" alt="Spinner" />
+        </div>
+        <div id="grailsLogo" class="logo"><a href="http://grails.org"><img src="${resource(dir:'images',file:'grails_logo.png')}" alt="Grails" border="0" /></a></div>
+        <g:layoutBody />
+    </body>
+</html>
Index: /branches/util-apps/DomainUtil/web-app/WEB-INF/applicationContext.xml
===================================================================
--- /branches/util-apps/DomainUtil/web-app/WEB-INF/applicationContext.xml	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/WEB-INF/applicationContext.xml	(revision 766)
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+	<bean id="grailsApplication" class="org.codehaus.groovy.grails.commons.GrailsApplicationFactoryBean">
+		<description>Grails application factory bean</description>
+        <property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
+        <property name="grailsResourceLoader" ref="grailsResourceLoader" />
+	</bean>
+
+	<bean id="pluginManager" class="org.codehaus.groovy.grails.plugins.GrailsPluginManagerFactoryBean">
+		<description>A bean that manages Grails plugins</description>
+        <property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
+        <property name="application" ref="grailsApplication" />
+	</bean>
+
+    <bean id="grailsConfigurator" class="org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator">
+        <constructor-arg>
+            <ref bean="grailsApplication" />
+        </constructor-arg>
+        <property name="pluginManager" ref="pluginManager" />
+    </bean>
+
+    <bean id="grailsResourceLoader" class="org.codehaus.groovy.grails.commons.GrailsResourceLoaderFactoryBean">
+        <property name="grailsResourceHolder" ref="grailsResourceHolder" />
+    </bean>
+
+    <bean id="grailsResourceHolder" scope="prototype" class="org.codehaus.groovy.grails.commons.spring.GrailsResourceHolder">
+        <property name="resources">
+              <value>classpath*:**/grails-app/**/*.groovy</value>
+        </property>
+    </bean>    
+    
+   <bean id="characterEncodingFilter"
+      class="org.springframework.web.filter.CharacterEncodingFilter">
+        <property name="encoding">
+          <value>utf-8</value>
+        </property>
+   </bean>    	
+</beans>
Index: /branches/util-apps/DomainUtil/web-app/WEB-INF/sitemesh.xml
===================================================================
--- /branches/util-apps/DomainUtil/web-app/WEB-INF/sitemesh.xml	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/WEB-INF/sitemesh.xml	(revision 766)
@@ -0,0 +1,14 @@
+<sitemesh>
+    <page-parsers>
+        <parser content-type="text/html"
+            class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
+        <parser content-type="text/html;charset=ISO-8859-1"
+            class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
+        <parser content-type="text/html;charset=UTF-8"
+            class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />            
+    </page-parsers>
+
+    <decorator-mappers>
+        <mapper class="org.codehaus.groovy.grails.web.sitemesh.GrailsLayoutDecoratorMapper" />
+    </decorator-mappers>
+</sitemesh>
Index: /branches/util-apps/DomainUtil/web-app/WEB-INF/tld/c.tld
===================================================================
--- /branches/util-apps/DomainUtil/web-app/WEB-INF/tld/c.tld	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/WEB-INF/tld/c.tld	(revision 766)
@@ -0,0 +1,563 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
+    version="2.0">
+    
+  <description>JSTL 1.1 core library</description>
+  <display-name>JSTL core</display-name>
+  <tlib-version>1.1</tlib-version>
+  <short-name>c</short-name>
+  <uri>http://java.sun.com/jsp/jstl/core</uri>
+
+  <validator>
+    <description>
+        Provides core validation features for JSTL tags.
+    </description>
+    <validator-class>
+        org.apache.taglibs.standard.tlv.JstlCoreTLV
+    </validator-class>
+  </validator>
+
+  <tag>
+    <description>
+        Catches any Throwable that occurs in its body and optionally
+        exposes it.
+    </description>
+    <name>catch</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.CatchTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+exception thrown from a nested action. The type of the
+scoped variable is the type of the exception thrown.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+	Simple conditional tag that establishes a context for
+	mutually exclusive conditional operations, marked by
+	&lt;when&gt; and &lt;otherwise&gt;
+    </description>
+    <name>choose</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.ChooseTag</tag-class>
+    <body-content>JSP</body-content>
+  </tag>
+
+  <tag>
+    <description>
+	Simple conditional tag, which evalutes its body if the
+	supplied condition is true and optionally exposes a Boolean
+	scripting variable representing the evaluation of this condition
+    </description>
+    <name>if</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.IfTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The test condition that determines whether or
+not the body content should be processed.
+        </description>
+        <name>test</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+	<type>boolean</type>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+resulting value of the test condition. The type
+of the scoped variable is Boolean.        
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Retrieves an absolute or relative URL and exposes its contents
+        to either the page, a String in 'var', or a Reader in 'varReader'.
+    </description>
+    <name>import</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ImportTag</tag-class>
+    <tei-class>org.apache.taglibs.standard.tei.ImportTEI</tei-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The URL of the resource to import.
+        </description>
+        <name>url</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+resource's content. The type of the scoped
+variable is String.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+resource's content. The type of the scoped
+variable is Reader.
+        </description>
+        <name>varReader</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the context when accessing a relative
+URL resource that belongs to a foreign
+context.
+        </description>
+        <name>context</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Character encoding of the content at the input
+resource.
+        </description>
+        <name>charEncoding</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+	The basic iteration tag, accepting many different
+        collection types and supporting subsetting and other
+        functionality
+    </description>
+    <name>forEach</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ForEachTag</tag-class>
+    <tei-class>org.apache.taglibs.standard.tei.ForEachTEI</tei-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Collection of items to iterate over.
+        </description>
+	<name>items</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>java.lang.Object</type>
+    </attribute>
+    <attribute>
+        <description>
+If items specified:
+Iteration begins at the item located at the
+specified index. First item of the collection has
+index 0.
+If items not specified:
+Iteration begins with index set at the value
+specified.
+        </description>
+	<name>begin</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+If items specified:
+Iteration ends at the item located at the
+specified index (inclusive).
+If items not specified:
+Iteration ends when index reaches the value
+specified.
+        </description>
+	<name>end</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration will only process every step items of
+the collection, starting with the first one.
+        </description>
+	<name>step</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+current item of the iteration. This scoped
+variable has nested visibility. Its type depends
+on the object of the underlying collection.
+        </description>
+	<name>var</name>
+	<required>false</required>
+	<rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+status of the iteration. Object exported is of type
+javax.servlet.jsp.jstl.core.LoopTagStatus. This scoped variable has nested
+visibility.
+        </description>
+	<name>varStatus</name>
+	<required>false</required>
+	<rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+	Iterates over tokens, separated by the supplied delimeters
+    </description>
+    <name>forTokens</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ForTokensTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+String of tokens to iterate over.
+        </description>
+	<name>items</name>
+	<required>true</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>java.lang.String</type>
+    </attribute>
+    <attribute>
+        <description>
+The set of delimiters (the characters that
+separate the tokens in the string).
+        </description>
+	<name>delims</name>
+	<required>true</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>java.lang.String</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration begins at the token located at the
+specified index. First token has index 0.
+        </description>
+	<name>begin</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration ends at the token located at the
+specified index (inclusive).
+        </description>
+	<name>end</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration will only process every step tokens
+of the string, starting with the first one.
+        </description>
+	<name>step</name>
+	<required>false</required>
+	<rtexprvalue>true</rtexprvalue>
+	<type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+current item of the iteration. This scoped
+variable has nested visibility.
+        </description>
+	<name>var</name>
+	<required>false</required>
+	<rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+status of the iteration. Object exported is of
+type
+javax.servlet.jsp.jstl.core.LoopTag
+Status. This scoped variable has nested
+visibility.
+        </description>
+	<name>varStatus</name>
+	<required>false</required>
+	<rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Like &lt;%= ... &gt;, but for expressions.
+    </description> 
+    <name>out</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.OutTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Expression to be evaluated.
+        </description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Default value if the resulting value is null.
+        </description>
+        <name>default</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Determines whether characters &lt;,&gt;,&amp;,'," in the
+resulting string should be converted to their
+corresponding character entity codes. Default value is
+true.
+        </description>
+        <name>escapeXml</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+
+  <tag>
+    <description>
+        Subtag of &lt;choose&gt; that follows &lt;when&gt; tags
+        and runs only if all of the prior conditions evaluated to
+        'false'
+    </description>
+    <name>otherwise</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.OtherwiseTag</tag-class>
+    <body-content>JSP</body-content>
+  </tag>
+
+  <tag>
+    <description>
+        Adds a parameter to a containing 'import' tag's URL.
+    </description>
+    <name>param</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ParamTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the query string parameter.
+        </description>
+        <name>name</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Value of the parameter.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Redirects to a new URL.
+    </description>
+    <name>redirect</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.RedirectTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The URL of the resource to redirect to.
+        </description>
+        <name>url</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the context when redirecting to a relative URL
+resource that belongs to a foreign context.
+        </description>
+        <name>context</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Removes a scoped variable (from a particular scope, if specified).
+    </description>
+    <name>remove</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.RemoveTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+Name of the scoped variable to be removed.
+        </description>
+        <name>var</name>
+        <required>true</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+ <tag>
+    <description>
+        Sets the result of an expression evaluation in a 'scope'
+    </description>
+    <name>set</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.SetTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the exported scoped variable to hold the value
+specified in the action. The type of the scoped variable is
+whatever type the value expression evaluates to.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Expression to be evaluated.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Target object whose property will be set. Must evaluate to
+a JavaBeans object with setter property property, or to a
+java.util.Map object.
+        </description>
+        <name>target</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the property to be set in the target object.
+        </description>
+        <name>property</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Creates a URL with optional query parameters.
+    </description>
+    <name>url</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.UrlTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+processed url. The type of the scoped variable is
+String.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+URL to be processed.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the context when specifying a relative URL
+resource that belongs to a foreign context.
+        </description>
+        <name>context</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+	Subtag of &lt;choose&gt; that includes its body if its
+	condition evalutes to 'true'
+    </description>
+    <name>when</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.WhenTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The test condition that determines whether or not the
+body content should be processed.
+        </description>
+        <name>test</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+	<type>boolean</type>
+    </attribute>
+  </tag>
+
+</taglib>
Index: /branches/util-apps/DomainUtil/web-app/WEB-INF/tld/fmt.tld
===================================================================
--- /branches/util-apps/DomainUtil/web-app/WEB-INF/tld/fmt.tld	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/WEB-INF/tld/fmt.tld	(revision 766)
@@ -0,0 +1,671 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
+    version="2.0">
+    
+  <description>JSTL 1.1 i18n-capable formatting library</description>
+  <display-name>JSTL fmt</display-name>
+  <tlib-version>1.1</tlib-version>
+  <short-name>fmt</short-name>
+  <uri>http://java.sun.com/jsp/jstl/fmt</uri>
+
+  <validator>
+    <description>
+        Provides core validation features for JSTL tags.
+    </description>
+    <validator-class>
+        org.apache.taglibs.standard.tlv.JstlFmtTLV
+    </validator-class>
+  </validator>
+
+  <tag>
+    <description>
+        Sets the request character encoding
+    </description>
+    <name>requestEncoding</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.RequestEncodingTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+Name of character encoding to be applied when
+decoding request parameters.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Stores the given locale in the locale configuration variable
+    </description>
+    <name>setLocale</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.SetLocaleTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+A String value is interpreted as the
+printable representation of a locale, which
+must contain a two-letter (lower-case)
+language code (as defined by ISO-639),
+and may contain a two-letter (upper-case)
+country code (as defined by ISO-3166).
+Language and country codes must be
+separated by hyphen (-) or underscore
+(_).        
+	</description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Vendor- or browser-specific variant.
+See the java.util.Locale javadocs for
+more information on variants.
+        </description>
+        <name>variant</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of the locale configuration variable.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Specifies the time zone for any time formatting or parsing actions
+        nested in its body
+    </description>
+    <name>timeZone</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.TimeZoneTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The time zone. A String value is interpreted as
+a time zone ID. This may be one of the time zone
+IDs supported by the Java platform (such as
+"America/Los_Angeles") or a custom time zone
+ID (such as "GMT-8"). See
+java.util.TimeZone for more information on
+supported time zone formats.
+        </description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Stores the given time zone in the time zone configuration variable
+    </description>
+    <name>setTimeZone</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.SetTimeZoneTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+The time zone. A String value is interpreted as
+a time zone ID. This may be one of the time zone
+IDs supported by the Java platform (such as
+"America/Los_Angeles") or a custom time zone
+ID (such as "GMT-8"). See java.util.TimeZone for
+more information on supported time zone
+formats.
+        </description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable which
+stores the time zone of type
+java.util.TimeZone.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var or the time zone configuration
+variable.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Loads a resource bundle to be used by its tag body
+    </description>
+    <name>bundle</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.BundleTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Resource bundle base name. This is the bundle's
+fully-qualified resource name, which has the same
+form as a fully-qualified class name, that is, it uses
+"." as the package component separator and does not
+have any file type (such as ".class" or ".properties")
+suffix.
+        </description>
+        <name>basename</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Prefix to be prepended to the value of the message
+key of any nested &lt;fmt:message&gt; action.
+        </description>
+        <name>prefix</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Loads a resource bundle and stores it in the named scoped variable or
+        the bundle configuration variable
+    </description>
+    <name>setBundle</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.SetBundleTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+Resource bundle base name. This is the bundle's
+fully-qualified resource name, which has the same
+form as a fully-qualified class name, that is, it uses
+"." as the package component separator and does not
+have any file type (such as ".class" or ".properties")
+suffix.
+        </description>
+        <name>basename</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable which stores
+the i18n localization context of type
+javax.servlet.jsp.jstl.fmt.LocalizationC
+ontext.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var or the localization context
+configuration variable.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Maps key to localized message and performs parametric replacement
+    </description>
+    <name>message</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.MessageTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Message key to be looked up.
+        </description>
+        <name>key</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Localization context in whose resource
+bundle the message key is looked up.
+        </description>
+        <name>bundle</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable
+which stores the localized message.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Supplies an argument for parametric replacement to a containing
+        &lt;message&gt; tag
+    </description>
+    <name>param</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.ParamTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Argument used for parametric replacement.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Formats a numeric value as a number, currency, or percentage
+    </description>
+    <name>formatNumber</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.FormatNumberTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Numeric value to be formatted.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Specifies whether the value is to be
+formatted as number, currency, or
+percentage.
+        </description>
+        <name>type</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Custom formatting pattern.
+        </description>
+        <name>pattern</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+ISO 4217 currency code. Applied only
+when formatting currencies (i.e. if type is
+equal to "currency"); ignored otherwise.
+        </description>
+        <name>currencyCode</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Currency symbol. Applied only when
+formatting currencies (i.e. if type is equal
+to "currency"); ignored otherwise.
+        </description>
+        <name>currencySymbol</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Specifies whether the formatted output
+will contain any grouping separators.
+        </description>
+        <name>groupingUsed</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Maximum number of digits in the integer
+portion of the formatted output.
+        </description>
+        <name>maxIntegerDigits</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Minimum number of digits in the integer
+portion of the formatted output.
+        </description>
+        <name>minIntegerDigits</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Maximum number of digits in the
+fractional portion of the formatted output.
+        </description>
+        <name>maxFractionDigits</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Minimum number of digits in the
+fractional portion of the formatted output.
+        </description>
+        <name>minFractionDigits</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable
+which stores the formatted result as a
+String.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Parses the string representation of a number, currency, or percentage
+    </description>
+    <name>parseNumber</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.ParseNumberTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+String to be parsed.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Specifies whether the string in the value
+attribute should be parsed as a number,
+currency, or percentage.
+        </description>
+        <name>type</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Custom formatting pattern that determines
+how the string in the value attribute is to be
+parsed.
+        </description>
+        <name>pattern</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Locale whose default formatting pattern (for
+numbers, currencies, or percentages,
+respectively) is to be used during the parse
+operation, or to which the pattern specified
+via the pattern attribute (if present) is
+applied.
+        </description>
+        <name>parseLocale</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Specifies whether just the integer portion of
+the given value should be parsed.
+        </description>
+        <name>integerOnly</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable which
+stores the parsed result (of type
+java.lang.Number).
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Formats a date and/or time using the supplied styles and pattern
+    </description>
+    <name>formatDate</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.FormatDateTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+Date and/or time to be formatted.
+        </description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Specifies whether the time, the date, or both
+the time and date components of the given
+date are to be formatted. 
+        </description>
+        <name>type</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Predefined formatting style for dates. Follows
+the semantics defined in class
+java.text.DateFormat. Applied only
+when formatting a date or both a date and
+time (i.e. if type is missing or is equal to
+"date" or "both"); ignored otherwise.
+        </description>
+        <name>dateStyle</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Predefined formatting style for times. Follows
+the semantics defined in class
+java.text.DateFormat. Applied only
+when formatting a time or both a date and
+time (i.e. if type is equal to "time" or "both");
+ignored otherwise.
+        </description>
+        <name>timeStyle</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Custom formatting style for dates and times.
+        </description>
+        <name>pattern</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Time zone in which to represent the formatted
+time.
+        </description>
+        <name>timeZone</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable which
+stores the formatted result as a String.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Parses the string representation of a date and/or time
+    </description>
+    <name>parseDate</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.fmt.ParseDateTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Date string to be parsed.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Specifies whether the date string in the
+value attribute is supposed to contain a
+time, a date, or both.
+        </description>
+        <name>type</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Predefined formatting style for days
+which determines how the date
+component of the date string is to be
+parsed. Applied only when formatting a
+date or both a date and time (i.e. if type
+is missing or is equal to "date" or "both");
+ignored otherwise.
+        </description>
+        <name>dateStyle</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Predefined formatting styles for times
+which determines how the time
+component in the date string is to be
+parsed. Applied only when formatting a
+time or both a date and time (i.e. if type
+is equal to "time" or "both"); ignored
+otherwise.
+        </description>
+        <name>timeStyle</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Custom formatting pattern which
+determines how the date string is to be
+parsed.
+        </description>
+        <name>pattern</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Time zone in which to interpret any time
+information in the date string.
+        </description>
+        <name>timeZone</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Locale whose predefined formatting styles
+for dates and times are to be used during
+the parse operation, or to which the
+pattern specified via the pattern
+attribute (if present) is applied.
+        </description>
+        <name>parseLocale</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable in
+which the parsing result (of type
+java.util.Date) is stored.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope of var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+</taglib>
Index: /branches/util-apps/DomainUtil/web-app/WEB-INF/tld/grails.tld
===================================================================
--- /branches/util-apps/DomainUtil/web-app/WEB-INF/tld/grails.tld	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/WEB-INF/tld/grails.tld	(revision 766)
@@ -0,0 +1,551 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+            http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
+        version="2.0">
+    <description>The Grails (Groovy on Rails) custom tag library</description>
+    <tlib-version>0.2</tlib-version>
+    <short-name>grails</short-name>
+    <uri>http://grails.codehaus.org/tags</uri>
+
+
+    <tag>
+        <name>link</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspLinkTag</tag-class>
+        <body-content>JSP</body-content>
+        <attribute>
+            <name>action</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>controller</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>id</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>url</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>params</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>true</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>form</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspFormTag</tag-class>
+        <body-content>JSP</body-content>
+        <attribute>
+            <name>action</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>controller</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>id</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>url</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>method</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>true</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>select</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspSelectTag</tag-class>
+        <body-content>JSP</body-content>
+        <attribute>
+            <name>name</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>value</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>optionKey</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>optionValue</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>true</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>datePicker</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspDatePickerTag</tag-class>
+        <body-content>empty</body-content>
+        <attribute>
+            <name>name</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>value</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>precision</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>false</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>currencySelect</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspCurrencySelectTag</tag-class>
+        <body-content>empty</body-content>
+        <attribute>
+            <name>name</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>value</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>true</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>localeSelect</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspLocaleSelectTag</tag-class>
+        <body-content>empty</body-content>
+        <attribute>
+            <name>name</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>value</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>true</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>timeZoneSelect</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspTimeZoneSelectTag</tag-class>
+        <body-content>empty</body-content>
+        <attribute>
+            <name>name</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>value</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>true</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>checkBox</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspCheckboxTag</tag-class>
+        <body-content>empty</body-content>
+        <attribute>
+            <name>name</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>value</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>true</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>hasErrors</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspHasErrorsTag</tag-class>
+        <body-content>JSP</body-content>
+        <attribute>
+            <name>model</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>bean</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>field</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>false</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>eachError</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspEachErrorTag</tag-class>
+        <body-content>JSP</body-content>
+        <attribute>
+            <name>model</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>bean</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>field</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>false</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>renderErrors</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspEachErrorTag</tag-class>
+        <body-content>JSP</body-content>
+        <attribute>
+            <name>model</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>bean</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>field</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>as</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>false</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>message</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspMessageTag</tag-class>
+        <body-content>JSP</body-content>
+        <attribute>
+            <name>code</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>error</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>default</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>false</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>remoteFunction</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspRemoteFunctionTag</tag-class>
+        <body-content>empty</body-content>
+        <attribute>
+            <name>before</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>after</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>action</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>controller</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>id</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>url</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>params</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>asynchronous</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>method</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>update</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onSuccess</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onFailure</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onComplete</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onLoading</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onLoaded</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onInteractive</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>true</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>remoteLink</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspRemoteLinkTag</tag-class>
+        <body-content>JSP</body-content>
+        <attribute>
+            <name>before</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>after</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>action</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>controller</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>id</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>url</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>params</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>asynchronous</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>method</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>update</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onSuccess</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onFailure</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onComplete</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onLoading</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onLoaded</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onInteractive</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>true</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>formRemote</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspFormRemoteTag</tag-class>
+        <body-content>JSP</body-content>
+        <attribute>
+            <name>before</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>after</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>action</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>controller</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>id</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>url</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>params</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>asynchronous</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>method</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>update</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onSuccess</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onFailure</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onComplete</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onLoading</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onLoaded</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>onInteractive</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>true</dynamic-attributes>
+    </tag>
+    <tag>
+        <name>invokeTag</name>
+        <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspInvokeGrailsTagLibTag</tag-class>
+        <body-content>JSP</body-content>
+        <variable>
+            <name-given>it</name-given>
+            <variable-class>java.lang.Object</variable-class>
+            <declare>true</declare>
+            <scope>NESTED</scope>
+        </variable>
+        <attribute>
+            <name>tagName</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <dynamic-attributes>true</dynamic-attributes>
+    </tag>
+</taglib>
+
Index: /branches/util-apps/DomainUtil/web-app/WEB-INF/tld/spring.tld
===================================================================
--- /branches/util-apps/DomainUtil/web-app/WEB-INF/tld/spring.tld	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/WEB-INF/tld/spring.tld	(revision 766)
@@ -0,0 +1,311 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
+
+<taglib>
+
+	<tlib-version>1.1.1</tlib-version>
+
+	<jsp-version>1.2</jsp-version>
+
+	<short-name>Spring</short-name>
+
+	<uri>http://www.springframework.org/tags</uri>
+
+	<description>Spring Framework JSP Tag Library. Authors: Rod Johnson, Juergen Hoeller</description>
+
+
+	<tag>
+
+		<name>htmlEscape</name>
+		<tag-class>org.springframework.web.servlet.tags.HtmlEscapeTag</tag-class>
+		<body-content>JSP</body-content>
+
+		<description>
+			Sets default HTML escape value for the current page.
+			Overrides a "defaultHtmlEscape" context-param in web.xml, if any.
+		</description>
+
+		<attribute>
+			<name>defaultHtmlEscape</name>
+			<required>true</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+	</tag>
+
+
+	<tag>
+
+		<name>escapeBody</name>
+		<tag-class>org.springframework.web.servlet.tags.EscapeBodyTag</tag-class>
+		<body-content>JSP</body-content>
+
+		<description>
+			Escapes its enclosed body content, applying HTML escaping and/or JavaScript escaping.
+			The HTML escaping flag participates in a page-wide or application-wide setting
+			(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
+		</description>
+
+		<attribute>
+			<name>htmlEscape</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>javaScriptEscape</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+	</tag>
+
+
+	<tag>
+
+		<name>message</name>
+		<tag-class>org.springframework.web.servlet.tags.MessageTag</tag-class>
+		<body-content>JSP</body-content>
+
+		<description>
+			Retrieves the message with the given code, or text if code isn't resolvable.
+			The HTML escaping flag participates in a page-wide or application-wide setting
+			(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
+		</description>
+
+		<attribute>
+			<name>code</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>arguments</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>text</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>var</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>scope</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>htmlEscape</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>javaScriptEscape</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+	</tag>
+
+
+	<tag>
+
+		<name>theme</name>
+		<tag-class>org.springframework.web.servlet.tags.ThemeTag</tag-class>
+		<body-content>JSP</body-content>
+
+		<description>
+			Retrieves the theme message with the given code, or text if code isn't resolvable.
+			The HTML escaping flag participates in a page-wide or application-wide setting
+			(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
+		</description>
+
+		<attribute>
+			<name>code</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>arguments</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>text</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>var</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>scope</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>htmlEscape</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>javaScriptEscape</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+	</tag>
+
+
+	<tag>
+
+		<name>hasBindErrors</name>
+		<tag-class>org.springframework.web.servlet.tags.BindErrorsTag</tag-class>
+		<body-content>JSP</body-content>
+
+		<description>
+			Provides Errors instance in case of bind errors.
+			The HTML escaping flag participates in a page-wide or application-wide setting
+			(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
+		</description>
+
+		<variable>
+			<name-given>errors</name-given>
+			<variable-class>org.springframework.validation.Errors</variable-class>
+		</variable>
+
+		<attribute>
+			<name>name</name>
+			<required>true</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>htmlEscape</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+	</tag>
+
+
+	<tag>
+
+		<name>nestedPath</name>
+		<tag-class>org.springframework.web.servlet.tags.NestedPathTag</tag-class>
+		<body-content>JSP</body-content>
+
+		<description>
+			Sets a nested path to be used by the bind tag's path.
+		</description>
+
+		<variable>
+			<name-given>nestedPath</name-given>
+			<variable-class>java.lang.String</variable-class>
+		</variable>
+
+		<attribute>
+			<name>path</name>
+			<required>true</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+	</tag>
+
+
+	<tag>
+
+		<name>bind</name>
+		<tag-class>org.springframework.web.servlet.tags.BindTag</tag-class>
+		<body-content>JSP</body-content>
+
+		<description>
+			Provides BindStatus object for the given bind path.
+			The HTML escaping flag participates in a page-wide or application-wide setting
+			(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
+		</description>
+
+		<variable>
+			<name-given>status</name-given>
+			<variable-class>org.springframework.web.servlet.support.BindStatus</variable-class>
+		</variable>
+
+		<attribute>
+			<name>path</name>
+			<required>true</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>ignoreNestedPath</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>htmlEscape</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+	</tag>
+
+
+	<tag>
+
+		<name>transform</name>
+		<tag-class>org.springframework.web.servlet.tags.TransformTag</tag-class>
+		<body-content>JSP</body-content>
+
+		<description>
+			Provides transformation of variables to Strings, using an appropriate
+			custom PropertyEditor from BindTag (can only be used inside BindTag).
+			The HTML escaping flag participates in a page-wide or application-wide setting
+			(i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml).
+		</description>
+
+		<attribute>
+			<name>value</name>
+			<required>true</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>var</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>scope</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+		<attribute>
+			<name>htmlEscape</name>
+			<required>false</required>
+			<rtexprvalue>true</rtexprvalue>
+		</attribute>
+
+	</tag>
+
+</taglib>
Index: /branches/util-apps/DomainUtil/web-app/css/main.css
===================================================================
--- /branches/util-apps/DomainUtil/web-app/css/main.css	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/css/main.css	(revision 766)
@@ -0,0 +1,270 @@
+html * {
+    margin: 0;
+    /*padding: 0; SELECT NOT DISPLAYED CORRECTLY IN FIREFOX */
+}
+
+/* GENERAL */
+
+.spinner {
+    padding: 5px;
+    position: absolute;
+    right: 0;
+}
+
+body {
+    background: #fff;
+    color: #333;
+    font: 11px verdana, arial, helvetica, sans-serif;
+}
+#grailsLogo {
+	padding:20px;
+}
+
+a:link, a:visited, a:hover {
+    color: #666;
+    font-weight: bold;
+    text-decoration: none;
+}
+
+h1 {
+    color: #48802c;
+    font-weight: normal;
+    font-size: 16px;
+    margin: .8em 0 .3em 0;
+}
+
+ul {
+    padding-left: 15px;
+}
+
+input, select, textarea {
+    background-color: #fcfcfc;
+    border: 1px solid #ccc;
+    font: 11px verdana, arial, helvetica, sans-serif;
+    margin: 2px 0;
+    padding: 2px 4px;
+}
+select {
+   padding: 2px 2px 2px 0;
+}
+textarea {
+	width: 250px;
+	height: 150px;
+	vertical-align: top;
+}
+
+input:focus, select:focus, textarea:focus {
+    border: 1px solid #b2d1ff;
+}
+
+.body {
+    float: left;
+    margin: 0 15px 10px 15px;
+}
+
+/* NAVIGATION MENU */
+
+.nav {
+    background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
+    border: 1px solid #ccc;
+    border-style: solid none solid none;
+    margin-top: 5px;
+    padding: 7px 12px;
+}
+
+.menuButton {
+    font-size: 10px;
+    padding: 0 5px;
+}
+.menuButton a {
+    color: #333;
+    padding: 4px 6px;
+}
+.menuButton a.home {
+    background: url(../images/skin/house.png) center left no-repeat;
+    color: #333;
+    padding-left: 25px;
+}
+.menuButton a.list {
+    background: url(../images/skin/database_table.png) center left no-repeat;
+    color: #333;
+    padding-left: 25px;
+}
+.menuButton a.create {
+    background: url(../images/skin/database_add.png) center left no-repeat;
+    color: #333;
+    padding-left: 25px;
+}
+
+/* MESSAGES AND ERRORS */
+
+.message {
+    background: #f3f8fc url(../images/skin/information.png) 8px 50% no-repeat;
+    border: 1px solid #b2d1ff;
+    color: #006dba;
+    margin: 10px 0 5px 0;
+    padding: 5px 5px 5px 30px
+}
+
+div.errors {
+    background: #fff3f3;
+    border: 1px solid red;
+    color: #cc0000;
+    margin: 10px 0 5px 0;
+    padding: 5px 0 5px 0;
+}
+div.errors ul {
+    list-style: none;
+    padding: 0;
+}
+div.errors li {
+	background: url(../images/skin/exclamation.png) 8px 0% no-repeat;
+    line-height: 16px;
+    padding-left: 30px;
+}
+
+td.errors select {
+    border: 1px solid red;
+}
+td.errors input {
+    border: 1px solid red;
+}
+
+/* TABLES */
+
+table {
+    border: 1px solid #ccc;
+    width: 100%
+}
+tr {
+    border: 0;
+}
+td, th {
+    font: 11px verdana, arial, helvetica, sans-serif;
+    line-height: 12px;
+    padding: 5px 6px;
+    text-align: left;
+    vertical-align: top;
+}
+th {
+    background: #fff url(../images/skin/shadow.jpg);
+    color: #666;
+    font-size: 11px;
+    font-weight: bold;
+    line-height: 17px;
+    padding: 2px 6px;
+}
+th a:link, th a:visited, th a:hover {
+    color: #333;
+    display: block;
+    font-size: 10px;
+    text-decoration: none;
+    width: 100%;
+}
+th.asc a, th.desc a {
+    background-position: right;
+    background-repeat: no-repeat;
+}
+th.asc a {
+    background-image: url(../images/skin/sorted_asc.gif);
+}
+th.desc a {
+    background-image: url(../images/skin/sorted_desc.gif);
+}
+
+.odd {
+    background: #f7f7f7;
+}
+.even {
+    background: #fff;
+}
+
+/* LIST */
+
+.list table {
+    border-collapse: collapse;
+}
+.list th, .list td {
+    border-left: 1px solid #ddd;
+}
+.list th:hover, .list tr:hover {
+    background: #b2d1ff;
+}
+
+/* PAGINATION */
+
+.paginateButtons {
+    background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
+    border: 1px solid #ccc;
+    border-top: 0;
+    color: #666;
+    font-size: 10px;
+    overflow: hidden;
+    padding: 10px 3px;
+}
+.paginateButtons a {
+    background: #fff;
+    border: 1px solid #ccc;
+    border-color: #ccc #aaa #aaa #ccc;
+    color: #666;
+    margin: 0 3px;
+    padding: 2px 6px;
+}
+.paginateButtons span {
+    padding: 2px 3px;
+}
+
+/* DIALOG */
+
+.dialog table {
+    padding: 5px 0;
+}
+
+.prop {
+    padding: 5px;
+}
+.prop .name {
+    text-align: left;
+    width: 15%;
+    white-space: nowrap;
+}
+.prop .value {
+    text-align: left;
+    width: 85%;
+}
+
+/* ACTION BUTTONS */
+
+.buttons {
+    background: #fff url(../images/skin/shadow.jpg) bottom repeat-x;
+    border: 1px solid #ccc;
+    color: #666;
+    font-size: 10px;
+    margin-top: 5px;
+    overflow: hidden;
+    padding: 0;
+}
+
+.buttons input {
+    background: #fff;
+    border: 0;
+    color: #333;
+    cursor: pointer;
+    font-size: 10px;
+    font-weight: bold;
+    margin-left: 3px;
+    overflow: visible;
+    padding: 2px 6px;
+}
+.buttons input.delete {
+    background: transparent url(../images/skin/database_delete.png) 5px 50% no-repeat;
+    padding-left: 28px;
+}
+.buttons input.edit {
+    background: transparent url(../images/skin/database_edit.png) 5px 50% no-repeat;
+    padding-left: 28px;
+}
+.buttons input.save {
+    background: transparent url(../images/skin/database_save.png) 5px 50% no-repeat;
+    padding-left: 28px;
+}
Index: /branches/util-apps/DomainUtil/web-app/js/application.js
===================================================================
--- /branches/util-apps/DomainUtil/web-app/js/application.js	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/js/application.js	(revision 766)
@@ -0,0 +1,13 @@
+var Ajax;
+if (Ajax && (Ajax != null)) {
+	Ajax.Responders.register({
+	  onCreate: function() {
+        if($('spinner') && Ajax.activeRequestCount>0)
+          Effect.Appear('spinner',{duration:0.5,queue:'end'});
+	  },
+	  onComplete: function() {
+        if($('spinner') && Ajax.activeRequestCount==0)
+          Effect.Fade('spinner',{duration:0.5,queue:'end'});
+	  }
+	});
+}
Index: /branches/util-apps/DomainUtil/web-app/js/prototype/animation.js
===================================================================
--- /branches/util-apps/DomainUtil/web-app/js/prototype/animation.js	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/js/prototype/animation.js	(revision 766)
@@ -0,0 +1,7 @@
+/*
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 0.10.0
+*/
+YAHOO.util.Anim=function(el,attributes,duration,method){if(el){this.init(el,attributes,duration,method);}};YAHOO.util.Anim.prototype={doMethod:function(attribute,start,end){return this.method(this.currentFrame,start,end-start,this.totalFrames);},setAttribute:function(attribute,val,unit){YAHOO.util.Dom.setStyle(this.getEl(),attribute,val+unit);},getAttribute:function(attribute){return parseFloat(YAHOO.util.Dom.getStyle(this.getEl(),attribute));},defaultUnit:'px',defaultUnits:{opacity:' '},init:function(el,attributes,duration,method){var isAnimated=false;var startTime=null;var endTime=null;var actualFrames=0;var defaultValues={};el=YAHOO.util.Dom.get(el);this.attributes=attributes||{};this.duration=duration||1;this.method=method||YAHOO.util.Easing.easeNone;this.useSeconds=true;this.currentFrame=0;this.totalFrames=YAHOO.util.AnimMgr.fps;this.getEl=function(){return el;};this.setDefault=function(attribute,val){if(val.constructor!=Array&&(val=='auto'||isNaN(val))){switch(attribute){case'width':val=el.clientWidth||el.offsetWidth;break;case'height':val=el.clientHeight||el.offsetHeight;break;case'left':if(YAHOO.util.Dom.getStyle(el,'position')=='absolute'){val=el.offsetLeft;}else{val=0;}break;case'top':if(YAHOO.util.Dom.getStyle(el,'position')=='absolute'){val=el.offsetTop;}else{val=0;}break;default:val=0;}}defaultValues[attribute]=val;};this.getDefault=function(attribute){return defaultValues[attribute];};this.isAnimated=function(){return isAnimated;};this.getStartTime=function(){return startTime;};this.animate=function(){if(this.isAnimated()){return false;}this.onStart.fire();this._onStart.fire();this.totalFrames=(this.useSeconds)?Math.ceil(YAHOO.util.AnimMgr.fps*this.duration):this.duration;YAHOO.util.AnimMgr.registerElement(this);var attributes=this.attributes;var el=this.getEl();var val;for(var attribute in attributes){val=this.getAttribute(attribute);this.setDefault(attribute,val);}isAnimated=true;actualFrames=0;startTime=new Date();};this.stop=function(){if(!this.isAnimated()){return false;}this.currentFrame=0;endTime=new Date();var data={time:endTime,duration:endTime-startTime,frames:actualFrames,fps:actualFrames/this.duration};isAnimated=false;actualFrames=0;this.onComplete.fire(data);};var onTween=function(){var start;var end=null;var val;var unit;var attributes=this['attributes'];for(var attribute in attributes){unit=attributes[attribute]['unit']||this.defaultUnits[attribute]||this.defaultUnit;if(typeof attributes[attribute]['from']!='undefined'){start=attributes[attribute]['from'];}else{start=this.getDefault(attribute);}if(typeof attributes[attribute]['to']!='undefined'){end=attributes[attribute]['to'];}else if(typeof attributes[attribute]['by']!='undefined'){if(start.constructor==Array){end=[];for(var i=0,len=start.length;i<len;++i){end[i]=start[i]+attributes[attribute]['by'][i];}}else{end=start+attributes[attribute]['by'];}}if(end!==null&&typeof end!='undefined'){val=this.doMethod(attribute,start,end);if((attribute=='width'||attribute=='height'||attribute=='opacity')&&val<0){val=0;}this.setAttribute(attribute,val,unit);}}actualFrames+=1;};this._onStart=new YAHOO.util.CustomEvent('_onStart',this);this.onStart=new YAHOO.util.CustomEvent('start',this);this.onTween=new YAHOO.util.CustomEvent('tween',this);this._onTween=new YAHOO.util.CustomEvent('_tween',this);this.onComplete=new YAHOO.util.CustomEvent('complete',this);this._onTween.subscribe(onTween);}};YAHOO.util.AnimMgr=new function(){var thread=null;var queue=[];var tweenCount=0;this.fps=200;this.delay=1;this.registerElement=function(tween){if(tween.isAnimated()){return false;}queue[queue.length]=tween;tweenCount+=1;this.start();};this.start=function(){if(thread===null){thread=setInterval(this.run,this.delay);}};this.stop=function(tween){if(!tween){clearInterval(thread);for(var i=0,len=queue.length;i<len;++i){if(queue[i].isAnimated()){queue[i].stop();}}queue=[];thread=null;tweenCount=0;}else{tween.stop();tweenCount-=1;if(tweenCount<=0){this.stop();}}};this.run=function(){for(var i=0,len=queue.length;i<len;++i){var tween=queue[i];if(!tween||!tween.isAnimated()){continue;}if(tween.currentFrame<tween.totalFrames||tween.totalFrames===null){tween.currentFrame+=1;if(tween.useSeconds){correctFrame(tween);}tween.onTween.fire();tween._onTween.fire();}else{YAHOO.util.AnimMgr.stop(tween);}}};var correctFrame=function(tween){var frames=tween.totalFrames;var frame=tween.currentFrame;var expected=(tween.currentFrame*tween.duration*1000/tween.totalFrames);var elapsed=(new Date()-tween.getStartTime());var tweak=0;if(elapsed<tween.duration*1000){tweak=Math.round((elapsed/expected-1)*tween.currentFrame);}else{tweak=frames-(frame+1);}if(tweak>0&&isFinite(tweak)){if(tween.currentFrame+tweak>=frames){tweak=frames-(frame+1);}tween.currentFrame+=tweak;}};};YAHOO.util.Bezier=new function(){this.getPosition=function(points,t){var n=points.length;var tmp=[];for(var i=0;i<n;++i){tmp[i]=[points[i][0],points[i][1]];}for(var j=1;j<n;++j){for(i=0;i<n-j;++i){tmp[i][0]=(1-t)*tmp[i][0]+t*tmp[parseInt(i+1,10)][0];tmp[i][1]=(1-t)*tmp[i][1]+t*tmp[parseInt(i+1,10)][1];}}return[tmp[0][0],tmp[0][1]];};};YAHOO.util.Easing=new function(){this.easeNone=function(t,b,c,d){return b+c*(t/=d);};this.easeIn=function(t,b,c,d){return b+c*((t/=d)*t*t);};this.easeOut=function(t,b,c,d){var ts=(t/=d)*t;var tc=ts*t;return b+c*(tc+-3*ts+3*t);};this.easeBoth=function(t,b,c,d){var ts=(t/=d)*t;var tc=ts*t;return b+c*(-2*tc+3*ts);};this.backIn=function(t,b,c,d){var ts=(t/=d)*t;var tc=ts*t;return b+c*(-3.4005*tc*ts+10.2*ts*ts+-6.2*tc+0.4*ts);};this.backOut=function(t,b,c,d){var ts=(t/=d)*t;var tc=ts*t;return b+c*(8.292*tc*ts+-21.88*ts*ts+22.08*tc+-12.69*ts+5.1975*t);};this.backBoth=function(t,b,c,d){var ts=(t/=d)*t;var tc=ts*t;return b+c*(0.402*tc*ts+-2.1525*ts*ts+-3.2*tc+8*ts+-2.05*t);};};YAHOO.util.Motion=function(el,attributes,duration,method){if(el){this.initMotion(el,attributes,duration,method);}};YAHOO.util.Motion.prototype=new YAHOO.util.Anim();YAHOO.util.Motion.prototype.defaultUnits.points='px';YAHOO.util.Motion.prototype.doMethod=function(attribute,start,end){var val=null;if(attribute=='points'){var translatedPoints=this.getTranslatedPoints();var t=this.method(this.currentFrame,0,100,this.totalFrames)/100;if(translatedPoints){val=YAHOO.util.Bezier.getPosition(translatedPoints,t);}}else{val=this.method(this.currentFrame,start,end-start,this.totalFrames);}return val;};YAHOO.util.Motion.prototype.getAttribute=function(attribute){var val=null;if(attribute=='points'){val=[this.getAttribute('left'),this.getAttribute('top')];if(isNaN(val[0])){val[0]=0;}if(isNaN(val[1])){val[1]=0;}}else{val=parseFloat(YAHOO.util.Dom.getStyle(this.getEl(),attribute));}return val;};YAHOO.util.Motion.prototype.setAttribute=function(attribute,val,unit){if(attribute=='points'){YAHOO.util.Dom.setStyle(this.getEl(),'left',val[0]+unit);YAHOO.util.Dom.setStyle(this.getEl(),'top',val[1]+unit);}else{YAHOO.util.Dom.setStyle(this.getEl(),attribute,val+unit);}};YAHOO.util.Motion.prototype.initMotion=function(el,attributes,duration,method){YAHOO.util.Anim.call(this,el,attributes,duration,method);attributes=attributes||{};attributes.points=attributes.points||{};attributes.points.control=attributes.points.control||[];this.attributes=attributes;var start;var end=null;var translatedPoints=null;this.getTranslatedPoints=function(){return translatedPoints;};var translateValues=function(val,self){var pageXY=YAHOO.util.Dom.getXY(self.getEl());val=[val[0]-pageXY[0]+start[0],val[1]-pageXY[1]+start[1]];return val;};var onStart=function(){start=this.getAttribute('points');var attributes=this.attributes;var control=attributes['points']['control']||[];if(control.length>0&&control[0].constructor!=Array){control=[control];}if(YAHOO.util.Dom.getStyle(this.getEl(),'position')=='static'){YAHOO.util.Dom.setStyle(this.getEl(),'position','relative');}if(typeof attributes['points']['from']!='undefined'){YAHOO.util.Dom.setXY(this.getEl(),attributes['points']['from']);start=this.getAttribute('points');}else if((start[0]===0||start[1]===0)){YAHOO.util.Dom.setXY(this.getEl(),YAHOO.util.Dom.getXY(this.getEl()));start=this.getAttribute('points');}var i,len;if(typeof attributes['points']['to']!='undefined'){end=translateValues(attributes['points']['to'],this);for(i=0,len=control.length;i<len;++i){control[i]=translateValues(control[i],this);}}else if(typeof attributes['points']['by']!='undefined'){end=[start[0]+attributes['points']['by'][0],start[1]+attributes['points']['by'][1]];for(i=0,len=control.length;i<len;++i){control[i]=[start[0]+control[i][0],start[1]+control[i][1]];}}if(end){translatedPoints=[start];if(control.length>0){translatedPoints=translatedPoints.concat(control);}translatedPoints[translatedPoints.length]=end;}};this._onStart.subscribe(onStart);};YAHOO.util.Scroll=function(el,attributes,duration,method){if(el){YAHOO.util.Anim.call(this,el,attributes,duration,method);}};YAHOO.util.Scroll.prototype=new YAHOO.util.Anim();YAHOO.util.Scroll.prototype.defaultUnits.scroll=' ';YAHOO.util.Scroll.prototype.doMethod=function(attribute,start,end){var val=null;if(attribute=='scroll'){val=[this.method(this.currentFrame,start[0],end[0]-start[0],this.totalFrames),this.method(this.currentFrame,start[1],end[1]-start[1],this.totalFrames)];}else{val=this.method(this.currentFrame,start,end-start,this.totalFrames);}return val;};YAHOO.util.Scroll.prototype.getAttribute=function(attribute){var val=null;var el=this.getEl();if(attribute=='scroll'){val=[el.scrollLeft,el.scrollTop];}else{val=parseFloat(YAHOO.util.Dom.getStyle(el,attribute));}return val;};YAHOO.util.Scroll.prototype.setAttribute=function(attribute,val,unit){var el=this.getEl();if(attribute=='scroll'){el.scrollLeft=val[0];el.scrollTop=val[1];}else{YAHOO.util.Dom.setStyle(el,attribute,val+unit);}};
Index: /branches/util-apps/DomainUtil/web-app/js/prototype/builder.js
===================================================================
--- /branches/util-apps/DomainUtil/web-app/js/prototype/builder.js	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/js/prototype/builder.js	(revision 766)
@@ -0,0 +1,136 @@
+// script.aculo.us builder.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Builder = {
+  NODEMAP: {
+    AREA: 'map',
+    CAPTION: 'table',
+    COL: 'table',
+    COLGROUP: 'table',
+    LEGEND: 'fieldset',
+    OPTGROUP: 'select',
+    OPTION: 'select',
+    PARAM: 'object',
+    TBODY: 'table',
+    TD: 'table',
+    TFOOT: 'table',
+    TH: 'table',
+    THEAD: 'table',
+    TR: 'table'
+  },
+  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
+  //       due to a Firefox bug
+  node: function(elementName) {
+    elementName = elementName.toUpperCase();
+
+    // try innerHTML approach
+    var parentTag = this.NODEMAP[elementName] || 'div';
+    var parentElement = document.createElement(parentTag);
+    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
+    } catch(e) {}
+    var element = parentElement.firstChild || null;
+
+    // see if browser added wrapping tags
+    if(element && (element.tagName.toUpperCase() != elementName))
+      element = element.getElementsByTagName(elementName)[0];
+
+    // fallback to createElement approach
+    if(!element) element = document.createElement(elementName);
+
+    // abort if nothing could be created
+    if(!element) return;
+
+    // attributes (or text)
+    if(arguments[1])
+      if(this._isStringOrNumber(arguments[1]) ||
+        (arguments[1] instanceof Array) ||
+        arguments[1].tagName) {
+          this._children(element, arguments[1]);
+        } else {
+          var attrs = this._attributes(arguments[1]);
+          if(attrs.length) {
+            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+              parentElement.innerHTML = "<" +elementName + " " +
+                attrs + "></" + elementName + ">";
+            } catch(e) {}
+            element = parentElement.firstChild || null;
+            // workaround firefox 1.0.X bug
+            if(!element) {
+              element = document.createElement(elementName);
+              for(attr in arguments[1])
+                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
+            }
+            if(element.tagName.toUpperCase() != elementName)
+              element = parentElement.getElementsByTagName(elementName)[0];
+          }
+        }
+
+    // text, or array of children
+    if(arguments[2])
+      this._children(element, arguments[2]);
+
+     return $(element);
+  },
+  _text: function(text) {
+     return document.createTextNode(text);
+  },
+
+  ATTR_MAP: {
+    'className': 'class',
+    'htmlFor': 'for'
+  },
+
+  _attributes: function(attributes) {
+    var attrs = [];
+    for(attribute in attributes)
+      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
+          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
+    return attrs.join(" ");
+  },
+  _children: function(element, children) {
+    if(children.tagName) {
+      element.appendChild(children);
+      return;
+    }
+    if(typeof children=='object') { // array can hold nodes and text
+      children.flatten().each( function(e) {
+        if(typeof e=='object')
+          element.appendChild(e);
+        else
+          if(Builder._isStringOrNumber(e))
+            element.appendChild(Builder._text(e));
+      });
+    } else
+      if(Builder._isStringOrNumber(children))
+        element.appendChild(Builder._text(children));
+  },
+  _isStringOrNumber: function(param) {
+    return(typeof param=='string' || typeof param=='number');
+  },
+  build: function(html) {
+    var element = this.node('div');
+    $(element).update(html.strip());
+    return element.down();
+  },
+  dump: function(scope) {
+    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
+
+    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
+      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
+      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
+      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
+      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
+      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
+
+    tags.each( function(tag){
+      scope[tag] = function() {
+        return Builder.node.apply(Builder, [tag].concat($A(arguments)));
+      };
+    });
+  }
+};
Index: /branches/util-apps/DomainUtil/web-app/js/prototype/controls.js
===================================================================
--- /branches/util-apps/DomainUtil/web-app/js/prototype/controls.js	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/js/prototype/controls.js	(revision 766)
@@ -0,0 +1,965 @@
+// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+//           (c) 2005-2009 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+//  Richard Livsey
+//  Rahul Bhargava
+//  Rob Wills
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// Autocompleter.Base handles all the autocompletion functionality
+// that's independent of the data source for autocompletion. This
+// includes drawing the autocompletion menu, observing keyboard
+// and mouse events, and similar.
+//
+// Specific autocompleters need to provide, at the very least,
+// a getUpdatedChoices function that will be invoked every time
+// the text inside the monitored textbox changes. This method
+// should get the text for which to provide autocompletion by
+// invoking this.getToken(), NOT by directly accessing
+// this.element.value. This is to allow incremental tokenized
+// autocompletion. Specific auto-completion logic (AJAX, etc)
+// belongs in getUpdatedChoices.
+//
+// Tokenized incremental autocompletion is enabled automatically
+// when an autocompleter is instantiated with the 'tokens' option
+// in the options parameter, e.g.:
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
+// will incrementally autocomplete with a comma as the token.
+// Additionally, ',' in the above example can be replaced with
+// a token array, e.g. { tokens: [',', '\n'] } which
+// enables autocompletion on multiple tokens. This is most
+// useful when one of the tokens is \n (a newline), as it
+// allows smart autocompletion after linebreaks.
+
+if(typeof Effect == 'undefined')
+  throw("controls.js requires including script.aculo.us' effects.js library");
+
+var Autocompleter = { };
+Autocompleter.Base = Class.create({
+  baseInitialize: function(element, update, options) {
+    element          = $(element);
+    this.element     = element;
+    this.update      = $(update);
+    this.hasFocus    = false;
+    this.changed     = false;
+    this.active      = false;
+    this.index       = 0;
+    this.entryCount  = 0;
+    this.oldElementValue = this.element.value;
+
+    if(this.setOptions)
+      this.setOptions(options);
+    else
+      this.options = options || { };
+
+    this.options.paramName    = this.options.paramName || this.element.name;
+    this.options.tokens       = this.options.tokens || [];
+    this.options.frequency    = this.options.frequency || 0.4;
+    this.options.minChars     = this.options.minChars || 1;
+    this.options.onShow       = this.options.onShow ||
+      function(element, update){
+        if(!update.style.position || update.style.position=='absolute') {
+          update.style.position = 'absolute';
+          Position.clone(element, update, {
+            setHeight: false,
+            offsetTop: element.offsetHeight
+          });
+        }
+        Effect.Appear(update,{duration:0.15});
+      };
+    this.options.onHide = this.options.onHide ||
+      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
+    if(typeof(this.options.tokens) == 'string')
+      this.options.tokens = new Array(this.options.tokens);
+    // Force carriage returns as token delimiters anyway
+    if (!this.options.tokens.include('\n'))
+      this.options.tokens.push('\n');
+
+    this.observer = null;
+
+    this.element.setAttribute('autocomplete','off');
+
+    Element.hide(this.update);
+
+    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
+    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
+  },
+
+  show: function() {
+    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+    if(!this.iefix &&
+      (Prototype.Browser.IE) &&
+      (Element.getStyle(this.update, 'position')=='absolute')) {
+      new Insertion.After(this.update,
+       '<iframe id="' + this.update.id + '_iefix" '+
+       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+      this.iefix = $(this.update.id+'_iefix');
+    }
+    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+  },
+
+  fixIEOverlapping: function() {
+    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
+    this.iefix.style.zIndex = 1;
+    this.update.style.zIndex = 2;
+    Element.show(this.iefix);
+  },
+
+  hide: function() {
+    this.stopIndicator();
+    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
+    if(this.iefix) Element.hide(this.iefix);
+  },
+
+  startIndicator: function() {
+    if(this.options.indicator) Element.show(this.options.indicator);
+  },
+
+  stopIndicator: function() {
+    if(this.options.indicator) Element.hide(this.options.indicator);
+  },
+
+  onKeyPress: function(event) {
+    if(this.active)
+      switch(event.keyCode) {
+       case Event.KEY_TAB:
+       case Event.KEY_RETURN:
+         this.selectEntry();
+         Event.stop(event);
+       case Event.KEY_ESC:
+         this.hide();
+         this.active = false;
+         Event.stop(event);
+         return;
+       case Event.KEY_LEFT:
+       case Event.KEY_RIGHT:
+         return;
+       case Event.KEY_UP:
+         this.markPrevious();
+         this.render();
+         Event.stop(event);
+         return;
+       case Event.KEY_DOWN:
+         this.markNext();
+         this.render();
+         Event.stop(event);
+         return;
+      }
+     else
+       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
+         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
+
+    this.changed = true;
+    this.hasFocus = true;
+
+    if(this.observer) clearTimeout(this.observer);
+      this.observer =
+        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
+  },
+
+  activate: function() {
+    this.changed = false;
+    this.hasFocus = true;
+    this.getUpdatedChoices();
+  },
+
+  onHover: function(event) {
+    var element = Event.findElement(event, 'LI');
+    if(this.index != element.autocompleteIndex)
+    {
+        this.index = element.autocompleteIndex;
+        this.render();
+    }
+    Event.stop(event);
+  },
+
+  onClick: function(event) {
+    var element = Event.findElement(event, 'LI');
+    this.index = element.autocompleteIndex;
+    this.selectEntry();
+    this.hide();
+  },
+
+  onBlur: function(event) {
+    // needed to make click events working
+    setTimeout(this.hide.bind(this), 250);
+    this.hasFocus = false;
+    this.active = false;
+  },
+
+  render: function() {
+    if(this.entryCount > 0) {
+      for (var i = 0; i < this.entryCount; i++)
+        this.index==i ?
+          Element.addClassName(this.getEntry(i),"selected") :
+          Element.removeClassName(this.getEntry(i),"selected");
+      if(this.hasFocus) {
+        this.show();
+        this.active = true;
+      }
+    } else {
+      this.active = false;
+      this.hide();
+    }
+  },
+
+  markPrevious: function() {
+    if(this.index > 0) this.index--;
+      else this.index = this.entryCount-1;
+    this.getEntry(this.index).scrollIntoView(true);
+  },
+
+  markNext: function() {
+    if(this.index < this.entryCount-1) this.index++;
+      else this.index = 0;
+    this.getEntry(this.index).scrollIntoView(false);
+  },
+
+  getEntry: function(index) {
+    return this.update.firstChild.childNodes[index];
+  },
+
+  getCurrentEntry: function() {
+    return this.getEntry(this.index);
+  },
+
+  selectEntry: function() {
+    this.active = false;
+    this.updateElement(this.getCurrentEntry());
+  },
+
+  updateElement: function(selectedElement) {
+    if (this.options.updateElement) {
+      this.options.updateElement(selectedElement);
+      return;
+    }
+    var value = '';
+    if (this.options.select) {
+      var nodes = $(selectedElement).select('.' + this.options.select) || [];
+      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
+    } else
+      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+
+    var bounds = this.getTokenBounds();
+    if (bounds[0] != -1) {
+      var newValue = this.element.value.substr(0, bounds[0]);
+      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
+      if (whitespace)
+        newValue += whitespace[0];
+      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
+    } else {
+      this.element.value = value;
+    }
+    this.oldElementValue = this.element.value;
+    this.element.focus();
+
+    if (this.options.afterUpdateElement)
+      this.options.afterUpdateElement(this.element, selectedElement);
+  },
+
+  updateChoices: function(choices) {
+    if(!this.changed && this.hasFocus) {
+      this.update.innerHTML = choices;
+      Element.cleanWhitespace(this.update);
+      Element.cleanWhitespace(this.update.down());
+
+      if(this.update.firstChild && this.update.down().childNodes) {
+        this.entryCount =
+          this.update.down().childNodes.length;
+        for (var i = 0; i < this.entryCount; i++) {
+          var entry = this.getEntry(i);
+          entry.autocompleteIndex = i;
+          this.addObservers(entry);
+        }
+      } else {
+        this.entryCount = 0;
+      }
+
+      this.stopIndicator();
+      this.index = 0;
+
+      if(this.entryCount==1 && this.options.autoSelect) {
+        this.selectEntry();
+        this.hide();
+      } else {
+        this.render();
+      }
+    }
+  },
+
+  addObservers: function(element) {
+    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
+    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
+  },
+
+  onObserverEvent: function() {
+    this.changed = false;
+    this.tokenBounds = null;
+    if(this.getToken().length>=this.options.minChars) {
+      this.getUpdatedChoices();
+    } else {
+      this.active = false;
+      this.hide();
+    }
+    this.oldElementValue = this.element.value;
+  },
+
+  getToken: function() {
+    var bounds = this.getTokenBounds();
+    return this.element.value.substring(bounds[0], bounds[1]).strip();
+  },
+
+  getTokenBounds: function() {
+    if (null != this.tokenBounds) return this.tokenBounds;
+    var value = this.element.value;
+    if (value.strip().empty()) return [-1, 0];
+    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
+    var offset = (diff == this.oldElementValue.length ? 1 : 0);
+    var prevTokenPos = -1, nextTokenPos = value.length;
+    var tp;
+    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
+      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
+      if (tp > prevTokenPos) prevTokenPos = tp;
+      tp = value.indexOf(this.options.tokens[index], diff + offset);
+      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
+    }
+    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
+  }
+});
+
+Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
+  var boundary = Math.min(newS.length, oldS.length);
+  for (var index = 0; index < boundary; ++index)
+    if (newS[index] != oldS[index])
+      return index;
+  return boundary;
+};
+
+Ajax.Autocompleter = Class.create(Autocompleter.Base, {
+  initialize: function(element, update, url, options) {
+    this.baseInitialize(element, update, options);
+    this.options.asynchronous  = true;
+    this.options.onComplete    = this.onComplete.bind(this);
+    this.options.defaultParams = this.options.parameters || null;
+    this.url                   = url;
+  },
+
+  getUpdatedChoices: function() {
+    this.startIndicator();
+
+    var entry = encodeURIComponent(this.options.paramName) + '=' +
+      encodeURIComponent(this.getToken());
+
+    this.options.parameters = this.options.callback ?
+      this.options.callback(this.element, entry) : entry;
+
+    if(this.options.defaultParams)
+      this.options.parameters += '&' + this.options.defaultParams;
+
+    new Ajax.Request(this.url, this.options);
+  },
+
+  onComplete: function(request) {
+    this.updateChoices(request.responseText);
+  }
+});
+
+// The local array autocompleter. Used when you'd prefer to
+// inject an array of autocompletion options into the page, rather
+// than sending out Ajax queries, which can be quite slow sometimes.
+//
+// The constructor takes four parameters. The first two are, as usual,
+// the id of the monitored textbox, and id of the autocompletion menu.
+// The third is the array you want to autocomplete from, and the fourth
+// is the options block.
+//
+// Extra local autocompletion options:
+// - choices - How many autocompletion choices to offer
+//
+// - partialSearch - If false, the autocompleter will match entered
+//                    text only at the beginning of strings in the
+//                    autocomplete array. Defaults to true, which will
+//                    match text at the beginning of any *word* in the
+//                    strings in the autocomplete array. If you want to
+//                    search anywhere in the string, additionally set
+//                    the option fullSearch to true (default: off).
+//
+// - fullSsearch - Search anywhere in autocomplete array strings.
+//
+// - partialChars - How many characters to enter before triggering
+//                   a partial match (unlike minChars, which defines
+//                   how many characters are required to do any match
+//                   at all). Defaults to 2.
+//
+// - ignoreCase - Whether to ignore case when autocompleting.
+//                 Defaults to true.
+//
+// It's possible to pass in a custom function as the 'selector'
+// option, if you prefer to write your own autocompletion logic.
+// In that case, the other options above will not apply unless
+// you support them.
+
+Autocompleter.Local = Class.create(Autocompleter.Base, {
+  initialize: function(element, update, array, options) {
+    this.baseInitialize(element, update, options);
+    this.options.array = array;
+  },
+
+  getUpdatedChoices: function() {
+    this.updateChoices(this.options.selector(this));
+  },
+
+  setOptions: function(options) {
+    this.options = Object.extend({
+      choices: 10,
+      partialSearch: true,
+      partialChars: 2,
+      ignoreCase: true,
+      fullSearch: false,
+      selector: function(instance) {
+        var ret       = []; // Beginning matches
+        var partial   = []; // Inside matches
+        var entry     = instance.getToken();
+        var count     = 0;
+
+        for (var i = 0; i < instance.options.array.length &&
+          ret.length < instance.options.choices ; i++) {
+
+          var elem = instance.options.array[i];
+          var foundPos = instance.options.ignoreCase ?
+            elem.toLowerCase().indexOf(entry.toLowerCase()) :
+            elem.indexOf(entry);
+
+          while (foundPos != -1) {
+            if (foundPos == 0 && elem.length != entry.length) {
+              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
+                elem.substr(entry.length) + "</li>");
+              break;
+            } else if (entry.length >= instance.options.partialChars &&
+              instance.options.partialSearch && foundPos != -1) {
+              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
+                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
+                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
+                  foundPos + entry.length) + "</li>");
+                break;
+              }
+            }
+
+            foundPos = instance.options.ignoreCase ?
+              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
+              elem.indexOf(entry, foundPos + 1);
+
+          }
+        }
+        if (partial.length)
+          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
+        return "<ul>" + ret.join('') + "</ul>";
+      }
+    }, options || { });
+  }
+});
+
+// AJAX in-place editor and collection editor
+// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
+
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+  setTimeout(function() {
+    Field.activate(field);
+  }, 1);
+};
+
+Ajax.InPlaceEditor = Class.create({
+  initialize: function(element, url, options) {
+    this.url = url;
+    this.element = element = $(element);
+    this.prepareOptions();
+    this._controls = { };
+    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
+    Object.extend(this.options, options || { });
+    if (!this.options.formId && this.element.id) {
+      this.options.formId = this.element.id + '-inplaceeditor';
+      if ($(this.options.formId))
+        this.options.formId = '';
+    }
+    if (this.options.externalControl)
+      this.options.externalControl = $(this.options.externalControl);
+    if (!this.options.externalControl)
+      this.options.externalControlOnly = false;
+    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
+    this.element.title = this.options.clickToEditText;
+    this._boundCancelHandler = this.handleFormCancellation.bind(this);
+    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
+    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
+    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
+    this._boundWrapperHandler = this.wrapUp.bind(this);
+    this.registerListeners();
+  },
+  checkForEscapeOrReturn: function(e) {
+    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
+    if (Event.KEY_ESC == e.keyCode)
+      this.handleFormCancellation(e);
+    else if (Event.KEY_RETURN == e.keyCode)
+      this.handleFormSubmission(e);
+  },
+  createControl: function(mode, handler, extraClasses) {
+    var control = this.options[mode + 'Control'];
+    var text = this.options[mode + 'Text'];
+    if ('button' == control) {
+      var btn = document.createElement('input');
+      btn.type = 'submit';
+      btn.value = text;
+      btn.className = 'editor_' + mode + '_button';
+      if ('cancel' == mode)
+        btn.onclick = this._boundCancelHandler;
+      this._form.appendChild(btn);
+      this._controls[mode] = btn;
+    } else if ('link' == control) {
+      var link = document.createElement('a');
+      link.href = '#';
+      link.appendChild(document.createTextNode(text));
+      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
+      link.className = 'editor_' + mode + '_link';
+      if (extraClasses)
+        link.className += ' ' + extraClasses;
+      this._form.appendChild(link);
+      this._controls[mode] = link;
+    }
+  },
+  createEditField: function() {
+    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
+    var fld;
+    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
+      fld = document.createElement('input');
+      fld.type = 'text';
+      var size = this.options.size || this.options.cols || 0;
+      if (0 < size) fld.size = size;
+    } else {
+      fld = document.createElement('textarea');
+      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
+      fld.cols = this.options.cols || 40;
+    }
+    fld.name = this.options.paramName;
+    fld.value = text; // No HTML breaks conversion anymore
+    fld.className = 'editor_field';
+    if (this.options.submitOnBlur)
+      fld.onblur = this._boundSubmitHandler;
+    this._controls.editor = fld;
+    if (this.options.loadTextURL)
+      this.loadExternalText();
+    this._form.appendChild(this._controls.editor);
+  },
+  createForm: function() {
+    var ipe = this;
+    function addText(mode, condition) {
+      var text = ipe.options['text' + mode + 'Controls'];
+      if (!text || condition === false) return;
+      ipe._form.appendChild(document.createTextNode(text));
+    };
+    this._form = $(document.createElement('form'));
+    this._form.id = this.options.formId;
+    this._form.addClassName(this.options.formClassName);
+    this._form.onsubmit = this._boundSubmitHandler;
+    this.createEditField();
+    if ('textarea' == this._controls.editor.tagName.toLowerCase())
+      this._form.appendChild(document.createElement('br'));
+    if (this.options.onFormCustomization)
+      this.options.onFormCustomization(this, this._form);
+    addText('Before', this.options.okControl || this.options.cancelControl);
+    this.createControl('ok', this._boundSubmitHandler);
+    addText('Between', this.options.okControl && this.options.cancelControl);
+    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
+    addText('After', this.options.okControl || this.options.cancelControl);
+  },
+  destroy: function() {
+    if (this._oldInnerHTML)
+      this.element.innerHTML = this._oldInnerHTML;
+    this.leaveEditMode();
+    this.unregisterListeners();
+  },
+  enterEditMode: function(e) {
+    if (this._saving || this._editing) return;
+    this._editing = true;
+    this.triggerCallback('onEnterEditMode');
+    if (this.options.externalControl)
+      this.options.externalControl.hide();
+    this.element.hide();
+    this.createForm();
+    this.element.parentNode.insertBefore(this._form, this.element);
+    if (!this.options.loadTextURL)
+      this.postProcessEditField();
+    if (e) Event.stop(e);
+  },
+  enterHover: function(e) {
+    if (this.options.hoverClassName)
+      this.element.addClassName(this.options.hoverClassName);
+    if (this._saving) return;
+    this.triggerCallback('onEnterHover');
+  },
+  getText: function() {
+    return this.element.innerHTML.unescapeHTML();
+  },
+  handleAJAXFailure: function(transport) {
+    this.triggerCallback('onFailure', transport);
+    if (this._oldInnerHTML) {
+      this.element.innerHTML = this._oldInnerHTML;
+      this._oldInnerHTML = null;
+    }
+  },
+  handleFormCancellation: function(e) {
+    this.wrapUp();
+    if (e) Event.stop(e);
+  },
+  handleFormSubmission: function(e) {
+    var form = this._form;
+    var value = $F(this._controls.editor);
+    this.prepareSubmission();
+    var params = this.options.callback(form, value) || '';
+    if (Object.isString(params))
+      params = params.toQueryParams();
+    params.editorId = this.element.id;
+    if (this.options.htmlResponse) {
+      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
+      Object.extend(options, {
+        parameters: params,
+        onComplete: this._boundWrapperHandler,
+        onFailure: this._boundFailureHandler
+      });
+      new Ajax.Updater({ success: this.element }, this.url, options);
+    } else {
+      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+      Object.extend(options, {
+        parameters: params,
+        onComplete: this._boundWrapperHandler,
+        onFailure: this._boundFailureHandler
+      });
+      new Ajax.Request(this.url, options);
+    }
+    if (e) Event.stop(e);
+  },
+  leaveEditMode: function() {
+    this.element.removeClassName(this.options.savingClassName);
+    this.removeForm();
+    this.leaveHover();
+    this.element.style.backgroundColor = this._originalBackground;
+    this.element.show();
+    if (this.options.externalControl)
+      this.options.externalControl.show();
+    this._saving = false;
+    this._editing = false;
+    this._oldInnerHTML = null;
+    this.triggerCallback('onLeaveEditMode');
+  },
+  leaveHover: function(e) {
+    if (this.options.hoverClassName)
+      this.element.removeClassName(this.options.hoverClassName);
+    if (this._saving) return;
+    this.triggerCallback('onLeaveHover');
+  },
+  loadExternalText: function() {
+    this._form.addClassName(this.options.loadingClassName);
+    this._controls.editor.disabled = true;
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        this._form.removeClassName(this.options.loadingClassName);
+        var text = transport.responseText;
+        if (this.options.stripLoadedTextTags)
+          text = text.stripTags();
+        this._controls.editor.value = text;
+        this._controls.editor.disabled = false;
+        this.postProcessEditField();
+      }.bind(this),
+      onFailure: this._boundFailureHandler
+    });
+    new Ajax.Request(this.options.loadTextURL, options);
+  },
+  postProcessEditField: function() {
+    var fpc = this.options.fieldPostCreation;
+    if (fpc)
+      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
+  },
+  prepareOptions: function() {
+    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
+    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
+    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
+      Object.extend(this.options, defs);
+    }.bind(this));
+  },
+  prepareSubmission: function() {
+    this._saving = true;
+    this.removeForm();
+    this.leaveHover();
+    this.showSaving();
+  },
+  registerListeners: function() {
+    this._listeners = { };
+    var listener;
+    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
+      listener = this[pair.value].bind(this);
+      this._listeners[pair.key] = listener;
+      if (!this.options.externalControlOnly)
+        this.element.observe(pair.key, listener);
+      if (this.options.externalControl)
+        this.options.externalControl.observe(pair.key, listener);
+    }.bind(this));
+  },
+  removeForm: function() {
+    if (!this._form) return;
+    this._form.remove();
+    this._form = null;
+    this._controls = { };
+  },
+  showSaving: function() {
+    this._oldInnerHTML = this.element.innerHTML;
+    this.element.innerHTML = this.options.savingText;
+    this.element.addClassName(this.options.savingClassName);
+    this.element.style.backgroundColor = this._originalBackground;
+    this.element.show();
+  },
+  triggerCallback: function(cbName, arg) {
+    if ('function' == typeof this.options[cbName]) {
+      this.options[cbName](this, arg);
+    }
+  },
+  unregisterListeners: function() {
+    $H(this._listeners).each(function(pair) {
+      if (!this.options.externalControlOnly)
+        this.element.stopObserving(pair.key, pair.value);
+      if (this.options.externalControl)
+        this.options.externalControl.stopObserving(pair.key, pair.value);
+    }.bind(this));
+  },
+  wrapUp: function(transport) {
+    this.leaveEditMode();
+    // Can't use triggerCallback due to backward compatibility: requires
+    // binding + direct element
+    this._boundComplete(transport, this.element);
+  }
+});
+
+Object.extend(Ajax.InPlaceEditor.prototype, {
+  dispose: Ajax.InPlaceEditor.prototype.destroy
+});
+
+Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
+  initialize: function($super, element, url, options) {
+    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
+    $super(element, url, options);
+  },
+
+  createEditField: function() {
+    var list = document.createElement('select');
+    list.name = this.options.paramName;
+    list.size = 1;
+    this._controls.editor = list;
+    this._collection = this.options.collection || [];
+    if (this.options.loadCollectionURL)
+      this.loadCollection();
+    else
+      this.checkForExternalText();
+    this._form.appendChild(this._controls.editor);
+  },
+
+  loadCollection: function() {
+    this._form.addClassName(this.options.loadingClassName);
+    this.showLoadingText(this.options.loadingCollectionText);
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        var js = transport.responseText.strip();
+        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
+          throw('Server returned an invalid collection representation.');
+        this._collection = eval(js);
+        this.checkForExternalText();
+      }.bind(this),
+      onFailure: this.onFailure
+    });
+    new Ajax.Request(this.options.loadCollectionURL, options);
+  },
+
+  showLoadingText: function(text) {
+    this._controls.editor.disabled = true;
+    var tempOption = this._controls.editor.firstChild;
+    if (!tempOption) {
+      tempOption = document.createElement('option');
+      tempOption.value = '';
+      this._controls.editor.appendChild(tempOption);
+      tempOption.selected = true;
+    }
+    tempOption.update((text || '').stripScripts().stripTags());
+  },
+
+  checkForExternalText: function() {
+    this._text = this.getText();
+    if (this.options.loadTextURL)
+      this.loadExternalText();
+    else
+      this.buildOptionList();
+  },
+
+  loadExternalText: function() {
+    this.showLoadingText(this.options.loadingText);
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        this._text = transport.responseText.strip();
+        this.buildOptionList();
+      }.bind(this),
+      onFailure: this.onFailure
+    });
+    new Ajax.Request(this.options.loadTextURL, options);
+  },
+
+  buildOptionList: function() {
+    this._form.removeClassName(this.options.loadingClassName);
+    this._collection = this._collection.map(function(entry) {
+      return 2 === entry.length ? entry : [entry, entry].flatten();
+    });
+    var marker = ('value' in this.options) ? this.options.value : this._text;
+    var textFound = this._collection.any(function(entry) {
+      return entry[0] == marker;
+    }.bind(this));
+    this._controls.editor.update('');
+    var option;
+    this._collection.each(function(entry, index) {
+      option = document.createElement('option');
+      option.value = entry[0];
+      option.selected = textFound ? entry[0] == marker : 0 == index;
+      option.appendChild(document.createTextNode(entry[1]));
+      this._controls.editor.appendChild(option);
+    }.bind(this));
+    this._controls.editor.disabled = false;
+    Field.scrollFreeActivate(this._controls.editor);
+  }
+});
+
+//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
+//**** This only  exists for a while,  in order to  let ****
+//**** users adapt to  the new API.  Read up on the new ****
+//**** API and convert your code to it ASAP!            ****
+
+Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
+  if (!options) return;
+  function fallback(name, expr) {
+    if (name in options || expr === undefined) return;
+    options[name] = expr;
+  };
+  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
+    options.cancelLink == options.cancelButton == false ? false : undefined)));
+  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
+    options.okLink == options.okButton == false ? false : undefined)));
+  fallback('highlightColor', options.highlightcolor);
+  fallback('highlightEndColor', options.highlightendcolor);
+};
+
+Object.extend(Ajax.InPlaceEditor, {
+  DefaultOptions: {
+    ajaxOptions: { },
+    autoRows: 3,                                // Use when multi-line w/ rows == 1
+    cancelControl: 'link',                      // 'link'|'button'|false
+    cancelText: 'cancel',
+    clickToEditText: 'Click to edit',
+    externalControl: null,                      // id|elt
+    externalControlOnly: false,
+    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
+    formClassName: 'inplaceeditor-form',
+    formId: null,                               // id|elt
+    highlightColor: '#ffff99',
+    highlightEndColor: '#ffffff',
+    hoverClassName: '',
+    htmlResponse: true,
+    loadingClassName: 'inplaceeditor-loading',
+    loadingText: 'Loading...',
+    okControl: 'button',                        // 'link'|'button'|false
+    okText: 'ok',
+    paramName: 'value',
+    rows: 1,                                    // If 1 and multi-line, uses autoRows
+    savingClassName: 'inplaceeditor-saving',
+    savingText: 'Saving...',
+    size: 0,
+    stripLoadedTextTags: false,
+    submitOnBlur: false,
+    textAfterControls: '',
+    textBeforeControls: '',
+    textBetweenControls: ''
+  },
+  DefaultCallbacks: {
+    callback: function(form) {
+      return Form.serialize(form);
+    },
+    onComplete: function(transport, element) {
+      // For backward compatibility, this one is bound to the IPE, and passes
+      // the element directly.  It was too often customized, so we don't break it.
+      new Effect.Highlight(element, {
+        startcolor: this.options.highlightColor, keepBackgroundImage: true });
+    },
+    onEnterEditMode: null,
+    onEnterHover: function(ipe) {
+      ipe.element.style.backgroundColor = ipe.options.highlightColor;
+      if (ipe._effect)
+        ipe._effect.cancel();
+    },
+    onFailure: function(transport, ipe) {
+      alert('Error communication with the server: ' + transport.responseText.stripTags());
+    },
+    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
+    onLeaveEditMode: null,
+    onLeaveHover: function(ipe) {
+      ipe._effect = new Effect.Highlight(ipe.element, {
+        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
+        restorecolor: ipe._originalBackground, keepBackgroundImage: true
+      });
+    }
+  },
+  Listeners: {
+    click: 'enterEditMode',
+    keydown: 'checkForEscapeOrReturn',
+    mouseover: 'enterHover',
+    mouseout: 'leaveHover'
+  }
+});
+
+Ajax.InPlaceCollectionEditor.DefaultOptions = {
+  loadingCollectionText: 'Loading options...'
+};
+
+// Delayed observer, like Form.Element.Observer,
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+Form.Element.DelayedObserver = Class.create({
+  initialize: function(element, delay, callback) {
+    this.delay     = delay || 0.5;
+    this.element   = $(element);
+    this.callback  = callback;
+    this.timer     = null;
+    this.lastValue = $F(this.element);
+    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
+  },
+  delayedListener: function(event) {
+    if(this.lastValue == $F(this.element)) return;
+    if(this.timer) clearTimeout(this.timer);
+    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
+    this.lastValue = $F(this.element);
+  },
+  onTimerEvent: function() {
+    this.timer = null;
+    this.callback(this.element, $F(this.element));
+  }
+});
Index: /branches/util-apps/DomainUtil/web-app/js/prototype/dragdrop.js
===================================================================
--- /branches/util-apps/DomainUtil/web-app/js/prototype/dragdrop.js	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/js/prototype/dragdrop.js	(revision 766)
@@ -0,0 +1,974 @@
+// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+if(Object.isUndefined(Effect))
+  throw("dragdrop.js requires including script.aculo.us' effects.js library");
+
+var Droppables = {
+  drops: [],
+
+  remove: function(element) {
+    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
+  },
+
+  add: function(element) {
+    element = $(element);
+    var options = Object.extend({
+      greedy:     true,
+      hoverclass: null,
+      tree:       false
+    }, arguments[1] || { });
+
+    // cache containers
+    if(options.containment) {
+      options._containers = [];
+      var containment = options.containment;
+      if(Object.isArray(containment)) {
+        containment.each( function(c) { options._containers.push($(c)) });
+      } else {
+        options._containers.push($(containment));
+      }
+    }
+
+    if(options.accept) options.accept = [options.accept].flatten();
+
+    Element.makePositioned(element); // fix IE
+    options.element = element;
+
+    this.drops.push(options);
+  },
+
+  findDeepestChild: function(drops) {
+    deepest = drops[0];
+
+    for (i = 1; i < drops.length; ++i)
+      if (Element.isParent(drops[i].element, deepest.element))
+        deepest = drops[i];
+
+    return deepest;
+  },
+
+  isContained: function(element, drop) {
+    var containmentNode;
+    if(drop.tree) {
+      containmentNode = element.treeNode;
+    } else {
+      containmentNode = element.parentNode;
+    }
+    return drop._containers.detect(function(c) { return containmentNode == c });
+  },
+
+  isAffected: function(point, element, drop) {
+    return (
+      (drop.element!=element) &&
+      ((!drop._containers) ||
+        this.isContained(element, drop)) &&
+      ((!drop.accept) ||
+        (Element.classNames(element).detect(
+          function(v) { return drop.accept.include(v) } ) )) &&
+      Position.within(drop.element, point[0], point[1]) );
+  },
+
+  deactivate: function(drop) {
+    if(drop.hoverclass)
+      Element.removeClassName(drop.element, drop.hoverclass);
+    this.last_active = null;
+  },
+
+  activate: function(drop) {
+    if(drop.hoverclass)
+      Element.addClassName(drop.element, drop.hoverclass);
+    this.last_active = drop;
+  },
+
+  show: function(point, element) {
+    if(!this.drops.length) return;
+    var drop, affected = [];
+
+    this.drops.each( function(drop) {
+      if(Droppables.isAffected(point, element, drop))
+        affected.push(drop);
+    });
+
+    if(affected.length>0)
+      drop = Droppables.findDeepestChild(affected);
+
+    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
+    if (drop) {
+      Position.within(drop.element, point[0], point[1]);
+      if(drop.onHover)
+        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
+
+      if (drop != this.last_active) Droppables.activate(drop);
+    }
+  },
+
+  fire: function(event, element) {
+    if(!this.last_active) return;
+    Position.prepare();
+
+    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
+      if (this.last_active.onDrop) {
+        this.last_active.onDrop(element, this.last_active.element, event);
+        return true;
+      }
+  },
+
+  reset: function() {
+    if(this.last_active)
+      this.deactivate(this.last_active);
+  }
+};
+
+var Draggables = {
+  drags: [],
+  observers: [],
+
+  register: function(draggable) {
+    if(this.drags.length == 0) {
+      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
+      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
+
+      Event.observe(document, "mouseup", this.eventMouseUp);
+      Event.observe(document, "mousemove", this.eventMouseMove);
+      Event.observe(document, "keypress", this.eventKeypress);
+    }
+    this.drags.push(draggable);
+  },
+
+  unregister: function(draggable) {
+    this.drags = this.drags.reject(function(d) { return d==draggable });
+    if(this.drags.length == 0) {
+      Event.stopObserving(document, "mouseup", this.eventMouseUp);
+      Event.stopObserving(document, "mousemove", this.eventMouseMove);
+      Event.stopObserving(document, "keypress", this.eventKeypress);
+    }
+  },
+
+  activate: function(draggable) {
+    if(draggable.options.delay) {
+      this._timeout = setTimeout(function() {
+        Draggables._timeout = null;
+        window.focus();
+        Draggables.activeDraggable = draggable;
+      }.bind(this), draggable.options.delay);
+    } else {
+      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
+      this.activeDraggable = draggable;
+    }
+  },
+
+  deactivate: function() {
+    this.activeDraggable = null;
+  },
+
+  updateDrag: function(event) {
+    if(!this.activeDraggable) return;
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
+    // Mozilla-based browsers fire successive mousemove events with
+    // the same coordinates, prevent needless redrawing (moz bug?)
+    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
+    this._lastPointer = pointer;
+
+    this.activeDraggable.updateDrag(event, pointer);
+  },
+
+  endDrag: function(event) {
+    if(this._timeout) {
+      clearTimeout(this._timeout);
+      this._timeout = null;
+    }
+    if(!this.activeDraggable) return;
+    this._lastPointer = null;
+    this.activeDraggable.endDrag(event);
+    this.activeDraggable = null;
+  },
+
+  keyPress: function(event) {
+    if(this.activeDraggable)
+      this.activeDraggable.keyPress(event);
+  },
+
+  addObserver: function(observer) {
+    this.observers.push(observer);
+    this._cacheObserverCallbacks();
+  },
+
+  removeObserver: function(element) {  // element instead of observer fixes mem leaks
+    this.observers = this.observers.reject( function(o) { return o.element==element });
+    this._cacheObserverCallbacks();
+  },
+
+  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
+    if(this[eventName+'Count'] > 0)
+      this.observers.each( function(o) {
+        if(o[eventName]) o[eventName](eventName, draggable, event);
+      });
+    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
+  },
+
+  _cacheObserverCallbacks: function() {
+    ['onStart','onEnd','onDrag'].each( function(eventName) {
+      Draggables[eventName+'Count'] = Draggables.observers.select(
+        function(o) { return o[eventName]; }
+      ).length;
+    });
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Draggable = Class.create({
+  initialize: function(element) {
+    var defaults = {
+      handle: false,
+      reverteffect: function(element, top_offset, left_offset) {
+        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
+        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
+          queue: {scope:'_draggable', position:'end'}
+        });
+      },
+      endeffect: function(element) {
+        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
+        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
+          queue: {scope:'_draggable', position:'end'},
+          afterFinish: function(){
+            Draggable._dragging[element] = false
+          }
+        });
+      },
+      zindex: 1000,
+      revert: false,
+      quiet: false,
+      scroll: false,
+      scrollSensitivity: 20,
+      scrollSpeed: 15,
+      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
+      delay: 0
+    };
+
+    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
+      Object.extend(defaults, {
+        starteffect: function(element) {
+          element._opacity = Element.getOpacity(element);
+          Draggable._dragging[element] = true;
+          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
+        }
+      });
+
+    var options = Object.extend(defaults, arguments[1] || { });
+
+    this.element = $(element);
+
+    if(options.handle && Object.isString(options.handle))
+      this.handle = this.element.down('.'+options.handle, 0);
+
+    if(!this.handle) this.handle = $(options.handle);
+    if(!this.handle) this.handle = this.element;
+
+    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
+      options.scroll = $(options.scroll);
+      this._isScrollChild = Element.childOf(this.element, options.scroll);
+    }
+
+    Element.makePositioned(this.element); // fix IE
+
+    this.options  = options;
+    this.dragging = false;
+
+    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
+    Event.observe(this.handle, "mousedown", this.eventMouseDown);
+
+    Draggables.register(this);
+  },
+
+  destroy: function() {
+    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
+    Draggables.unregister(this);
+  },
+
+  currentDelta: function() {
+    return([
+      parseInt(Element.getStyle(this.element,'left') || '0'),
+      parseInt(Element.getStyle(this.element,'top') || '0')]);
+  },
+
+  initDrag: function(event) {
+    if(!Object.isUndefined(Draggable._dragging[this.element]) &&
+      Draggable._dragging[this.element]) return;
+    if(Event.isLeftClick(event)) {
+      // abort on form elements, fixes a Firefox issue
+      var src = Event.element(event);
+      if((tag_name = src.tagName.toUpperCase()) && (
+        tag_name=='INPUT' ||
+        tag_name=='SELECT' ||
+        tag_name=='OPTION' ||
+        tag_name=='BUTTON' ||
+        tag_name=='TEXTAREA')) return;
+
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
+      var pos     = this.element.cumulativeOffset();
+      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
+
+      Draggables.activate(this);
+      Event.stop(event);
+    }
+  },
+
+  startDrag: function(event) {
+    this.dragging = true;
+    if(!this.delta)
+      this.delta = this.currentDelta();
+
+    if(this.options.zindex) {
+      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
+      this.element.style.zIndex = this.options.zindex;
+    }
+
+    if(this.options.ghosting) {
+      this._clone = this.element.cloneNode(true);
+      this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
+      if (!this._originallyAbsolute)
+        Position.absolutize(this.element);
+      this.element.parentNode.insertBefore(this._clone, this.element);
+    }
+
+    if(this.options.scroll) {
+      if (this.options.scroll == window) {
+        var where = this._getWindowScroll(this.options.scroll);
+        this.originalScrollLeft = where.left;
+        this.originalScrollTop = where.top;
+      } else {
+        this.originalScrollLeft = this.options.scroll.scrollLeft;
+        this.originalScrollTop = this.options.scroll.scrollTop;
+      }
+    }
+
+    Draggables.notify('onStart', this, event);
+
+    if(this.options.starteffect) this.options.starteffect(this.element);
+  },
+
+  updateDrag: function(event, pointer) {
+    if(!this.dragging) this.startDrag(event);
+
+    if(!this.options.quiet){
+      Position.prepare();
+      Droppables.show(pointer, this.element);
+    }
+
+    Draggables.notify('onDrag', this, event);
+
+    this.draw(pointer);
+    if(this.options.change) this.options.change(this);
+
+    if(this.options.scroll) {
+      this.stopScrolling();
+
+      var p;
+      if (this.options.scroll == window) {
+        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
+      } else {
+        p = Position.page(this.options.scroll);
+        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
+        p[1] += this.options.scroll.scrollTop + Position.deltaY;
+        p.push(p[0]+this.options.scroll.offsetWidth);
+        p.push(p[1]+this.options.scroll.offsetHeight);
+      }
+      var speed = [0,0];
+      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
+      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
+      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
+      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
+      this.startScrolling(speed);
+    }
+
+    // fix AppleWebKit rendering
+    if(Prototype.Browser.WebKit) window.scrollBy(0,0);
+
+    Event.stop(event);
+  },
+
+  finishDrag: function(event, success) {
+    this.dragging = false;
+
+    if(this.options.quiet){
+      Position.prepare();
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
+      Droppables.show(pointer, this.element);
+    }
+
+    if(this.options.ghosting) {
+      if (!this._originallyAbsolute)
+        Position.relativize(this.element);
+      delete this._originallyAbsolute;
+      Element.remove(this._clone);
+      this._clone = null;
+    }
+
+    var dropped = false;
+    if(success) {
+      dropped = Droppables.fire(event, this.element);
+      if (!dropped) dropped = false;
+    }
+    if(dropped && this.options.onDropped) this.options.onDropped(this.element);
+    Draggables.notify('onEnd', this, event);
+
+    var revert = this.options.revert;
+    if(revert && Object.isFunction(revert)) revert = revert(this.element);
+
+    var d = this.currentDelta();
+    if(revert && this.options.reverteffect) {
+      if (dropped == 0 || revert != 'failure')
+        this.options.reverteffect(this.element,
+          d[1]-this.delta[1], d[0]-this.delta[0]);
+    } else {
+      this.delta = d;
+    }
+
+    if(this.options.zindex)
+      this.element.style.zIndex = this.originalZ;
+
+    if(this.options.endeffect)
+      this.options.endeffect(this.element);
+
+    Draggables.deactivate(this);
+    Droppables.reset();
+  },
+
+  keyPress: function(event) {
+    if(event.keyCode!=Event.KEY_ESC) return;
+    this.finishDrag(event, false);
+    Event.stop(event);
+  },
+
+  endDrag: function(event) {
+    if(!this.dragging) return;
+    this.stopScrolling();
+    this.finishDrag(event, true);
+    Event.stop(event);
+  },
+
+  draw: function(point) {
+    var pos = this.element.cumulativeOffset();
+    if(this.options.ghosting) {
+      var r   = Position.realOffset(this.element);
+      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
+    }
+
+    var d = this.currentDelta();
+    pos[0] -= d[0]; pos[1] -= d[1];
+
+    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
+      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
+      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
+    }
+
+    var p = [0,1].map(function(i){
+      return (point[i]-pos[i]-this.offset[i])
+    }.bind(this));
+
+    if(this.options.snap) {
+      if(Object.isFunction(this.options.snap)) {
+        p = this.options.snap(p[0],p[1],this);
+      } else {
+      if(Object.isArray(this.options.snap)) {
+        p = p.map( function(v, i) {
+          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
+      } else {
+        p = p.map( function(v) {
+          return (v/this.options.snap).round()*this.options.snap }.bind(this));
+      }
+    }}
+
+    var style = this.element.style;
+    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
+      style.left = p[0] + "px";
+    if((!this.options.constraint) || (this.options.constraint=='vertical'))
+      style.top  = p[1] + "px";
+
+    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
+  },
+
+  stopScrolling: function() {
+    if(this.scrollInterval) {
+      clearInterval(this.scrollInterval);
+      this.scrollInterval = null;
+      Draggables._lastScrollPointer = null;
+    }
+  },
+
+  startScrolling: function(speed) {
+    if(!(speed[0] || speed[1])) return;
+    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
+    this.lastScrolled = new Date();
+    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
+  },
+
+  scroll: function() {
+    var current = new Date();
+    var delta = current - this.lastScrolled;
+    this.lastScrolled = current;
+    if(this.options.scroll == window) {
+      with (this._getWindowScroll(this.options.scroll)) {
+        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
+          var d = delta / 1000;
+          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
+        }
+      }
+    } else {
+      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
+      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
+    }
+
+    Position.prepare();
+    Droppables.show(Draggables._lastPointer, this.element);
+    Draggables.notify('onDrag', this);
+    if (this._isScrollChild) {
+      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
+      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
+      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
+      if (Draggables._lastScrollPointer[0] < 0)
+        Draggables._lastScrollPointer[0] = 0;
+      if (Draggables._lastScrollPointer[1] < 0)
+        Draggables._lastScrollPointer[1] = 0;
+      this.draw(Draggables._lastScrollPointer);
+    }
+
+    if(this.options.change) this.options.change(this);
+  },
+
+  _getWindowScroll: function(w) {
+    var T, L, W, H;
+    with (w.document) {
+      if (w.document.documentElement && documentElement.scrollTop) {
+        T = documentElement.scrollTop;
+        L = documentElement.scrollLeft;
+      } else if (w.document.body) {
+        T = body.scrollTop;
+        L = body.scrollLeft;
+      }
+      if (w.innerWidth) {
+        W = w.innerWidth;
+        H = w.innerHeight;
+      } else if (w.document.documentElement && documentElement.clientWidth) {
+        W = documentElement.clientWidth;
+        H = documentElement.clientHeight;
+      } else {
+        W = body.offsetWidth;
+        H = body.offsetHeight;
+      }
+    }
+    return { top: T, left: L, width: W, height: H };
+  }
+});
+
+Draggable._dragging = { };
+
+/*--------------------------------------------------------------------------*/
+
+var SortableObserver = Class.create({
+  initialize: function(element, observer) {
+    this.element   = $(element);
+    this.observer  = observer;
+    this.lastValue = Sortable.serialize(this.element);
+  },
+
+  onStart: function() {
+    this.lastValue = Sortable.serialize(this.element);
+  },
+
+  onEnd: function() {
+    Sortable.unmark();
+    if(this.lastValue != Sortable.serialize(this.element))
+      this.observer(this.element)
+  }
+});
+
+var Sortable = {
+  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
+
+  sortables: { },
+
+  _findRootElement: function(element) {
+    while (element.tagName.toUpperCase() != "BODY") {
+      if(element.id && Sortable.sortables[element.id]) return element;
+      element = element.parentNode;
+    }
+  },
+
+  options: function(element) {
+    element = Sortable._findRootElement($(element));
+    if(!element) return;
+    return Sortable.sortables[element.id];
+  },
+
+  destroy: function(element){
+    element = $(element);
+    var s = Sortable.sortables[element.id];
+
+    if(s) {
+      Draggables.removeObserver(s.element);
+      s.droppables.each(function(d){ Droppables.remove(d) });
+      s.draggables.invoke('destroy');
+
+      delete Sortable.sortables[s.element.id];
+    }
+  },
+
+  create: function(element) {
+    element = $(element);
+    var options = Object.extend({
+      element:     element,
+      tag:         'li',       // assumes li children, override with tag: 'tagname'
+      dropOnEmpty: false,
+      tree:        false,
+      treeTag:     'ul',
+      overlap:     'vertical', // one of 'vertical', 'horizontal'
+      constraint:  'vertical', // one of 'vertical', 'horizontal', false
+      containment: element,    // also takes array of elements (or id's); or false
+      handle:      false,      // or a CSS class
+      only:        false,
+      delay:       0,
+      hoverclass:  null,
+      ghosting:    false,
+      quiet:       false,
+      scroll:      false,
+      scrollSensitivity: 20,
+      scrollSpeed: 15,
+      format:      this.SERIALIZE_RULE,
+
+      // these take arrays of elements or ids and can be
+      // used for better initialization performance
+      elements:    false,
+      handles:     false,
+
+      onChange:    Prototype.emptyFunction,
+      onUpdate:    Prototype.emptyFunction
+    }, arguments[1] || { });
+
+    // clear any old sortable with same element
+    this.destroy(element);
+
+    // build options for the draggables
+    var options_for_draggable = {
+      revert:      true,
+      quiet:       options.quiet,
+      scroll:      options.scroll,
+      scrollSpeed: options.scrollSpeed,
+      scrollSensitivity: options.scrollSensitivity,
+      delay:       options.delay,
+      ghosting:    options.ghosting,
+      constraint:  options.constraint,
+      handle:      options.handle };
+
+    if(options.starteffect)
+      options_for_draggable.starteffect = options.starteffect;
+
+    if(options.reverteffect)
+      options_for_draggable.reverteffect = options.reverteffect;
+    else
+      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
+        element.style.top  = 0;
+        element.style.left = 0;
+      };
+
+    if(options.endeffect)
+      options_for_draggable.endeffect = options.endeffect;
+
+    if(options.zindex)
+      options_for_draggable.zindex = options.zindex;
+
+    // build options for the droppables
+    var options_for_droppable = {
+      overlap:     options.overlap,
+      containment: options.containment,
+      tree:        options.tree,
+      hoverclass:  options.hoverclass,
+      onHover:     Sortable.onHover
+    };
+
+    var options_for_tree = {
+      onHover:      Sortable.onEmptyHover,
+      overlap:      options.overlap,
+      containment:  options.containment,
+      hoverclass:   options.hoverclass
+    };
+
+    // fix for gecko engine
+    Element.cleanWhitespace(element);
+
+    options.draggables = [];
+    options.droppables = [];
+
+    // drop on empty handling
+    if(options.dropOnEmpty || options.tree) {
+      Droppables.add(element, options_for_tree);
+      options.droppables.push(element);
+    }
+
+    (options.elements || this.findElements(element, options) || []).each( function(e,i) {
+      var handle = options.handles ? $(options.handles[i]) :
+        (options.handle ? $(e).select('.' + options.handle)[0] : e);
+      options.draggables.push(
+        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
+      Droppables.add(e, options_for_droppable);
+      if(options.tree) e.treeNode = element;
+      options.droppables.push(e);
+    });
+
+    if(options.tree) {
+      (Sortable.findTreeElements(element, options) || []).each( function(e) {
+        Droppables.add(e, options_for_tree);
+        e.treeNode = element;
+        options.droppables.push(e);
+      });
+    }
+
+    // keep reference
+    this.sortables[element.identify()] = options;
+
+    // for onupdate
+    Draggables.addObserver(new SortableObserver(element, options.onUpdate));
+
+  },
+
+  // return all suitable-for-sortable elements in a guaranteed order
+  findElements: function(element, options) {
+    return Element.findChildren(
+      element, options.only, options.tree ? true : false, options.tag);
+  },
+
+  findTreeElements: function(element, options) {
+    return Element.findChildren(
+      element, options.only, options.tree ? true : false, options.treeTag);
+  },
+
+  onHover: function(element, dropon, overlap) {
+    if(Element.isParent(dropon, element)) return;
+
+    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
+      return;
+    } else if(overlap>0.5) {
+      Sortable.mark(dropon, 'before');
+      if(dropon.previousSibling != element) {
+        var oldParentNode = element.parentNode;
+        element.style.visibility = "hidden"; // fix gecko rendering
+        dropon.parentNode.insertBefore(element, dropon);
+        if(dropon.parentNode!=oldParentNode)
+          Sortable.options(oldParentNode).onChange(element);
+        Sortable.options(dropon.parentNode).onChange(element);
+      }
+    } else {
+      Sortable.mark(dropon, 'after');
+      var nextElement = dropon.nextSibling || null;
+      if(nextElement != element) {
+        var oldParentNode = element.parentNode;
+        element.style.visibility = "hidden"; // fix gecko rendering
+        dropon.parentNode.insertBefore(element, nextElement);
+        if(dropon.parentNode!=oldParentNode)
+          Sortable.options(oldParentNode).onChange(element);
+        Sortable.options(dropon.parentNode).onChange(element);
+      }
+    }
+  },
+
+  onEmptyHover: function(element, dropon, overlap) {
+    var oldParentNode = element.parentNode;
+    var droponOptions = Sortable.options(dropon);
+
+    if(!Element.isParent(dropon, element)) {
+      var index;
+
+      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
+      var child = null;
+
+      if(children) {
+        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
+
+        for (index = 0; index < children.length; index += 1) {
+          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
+            offset -= Element.offsetSize (children[index], droponOptions.overlap);
+          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
+            child = index + 1 < children.length ? children[index + 1] : null;
+            break;
+          } else {
+            child = children[index];
+            break;
+          }
+        }
+      }
+
+      dropon.insertBefore(element, child);
+
+      Sortable.options(oldParentNode).onChange(element);
+      droponOptions.onChange(element);
+    }
+  },
+
+  unmark: function() {
+    if(Sortable._marker) Sortable._marker.hide();
+  },
+
+  mark: function(dropon, position) {
+    // mark on ghosting only
+    var sortable = Sortable.options(dropon.parentNode);
+    if(sortable && !sortable.ghosting) return;
+
+    if(!Sortable._marker) {
+      Sortable._marker =
+        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
+          hide().addClassName('dropmarker').setStyle({position:'absolute'});
+      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
+    }
+    var offsets = dropon.cumulativeOffset();
+    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
+
+    if(position=='after')
+      if(sortable.overlap == 'horizontal')
+        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
+      else
+        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
+
+    Sortable._marker.show();
+  },
+
+  _tree: function(element, options, parent) {
+    var children = Sortable.findElements(element, options) || [];
+
+    for (var i = 0; i < children.length; ++i) {
+      var match = children[i].id.match(options.format);
+
+      if (!match) continue;
+
+      var child = {
+        id: encodeURIComponent(match ? match[1] : null),
+        element: element,
+        parent: parent,
+        children: [],
+        position: parent.children.length,
+        container: $(children[i]).down(options.treeTag)
+      };
+
+      /* Get the element containing the children and recurse over it */
+      if (child.container)
+        this._tree(child.container, options, child);
+
+      parent.children.push (child);
+    }
+
+    return parent;
+  },
+
+  tree: function(element) {
+    element = $(element);
+    var sortableOptions = this.options(element);
+    var options = Object.extend({
+      tag: sortableOptions.tag,
+      treeTag: sortableOptions.treeTag,
+      only: sortableOptions.only,
+      name: element.id,
+      format: sortableOptions.format
+    }, arguments[1] || { });
+
+    var root = {
+      id: null,
+      parent: null,
+      children: [],
+      container: element,
+      position: 0
+    };
+
+    return Sortable._tree(element, options, root);
+  },
+
+  /* Construct a [i] index for a particular node */
+  _constructIndex: function(node) {
+    var index = '';
+    do {
+      if (node.id) index = '[' + node.position + ']' + index;
+    } while ((node = node.parent) != null);
+    return index;
+  },
+
+  sequence: function(element) {
+    element = $(element);
+    var options = Object.extend(this.options(element), arguments[1] || { });
+
+    return $(this.findElements(element, options) || []).map( function(item) {
+      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
+    });
+  },
+
+  setSequence: function(element, new_sequence) {
+    element = $(element);
+    var options = Object.extend(this.options(element), arguments[2] || { });
+
+    var nodeMap = { };
+    this.findElements(element, options).each( function(n) {
+        if (n.id.match(options.format))
+            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
+        n.parentNode.removeChild(n);
+    });
+
+    new_sequence.each(function(ident) {
+      var n = nodeMap[ident];
+      if (n) {
+        n[1].appendChild(n[0]);
+        delete nodeMap[ident];
+      }
+    });
+  },
+
+  serialize: function(element) {
+    element = $(element);
+    var options = Object.extend(Sortable.options(element), arguments[1] || { });
+    var name = encodeURIComponent(
+      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
+
+    if (options.tree) {
+      return Sortable.tree(element, arguments[1]).children.map( function (item) {
+        return [name + Sortable._constructIndex(item) + "[id]=" +
+                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
+      }).flatten().join('&');
+    } else {
+      return Sortable.sequence(element, arguments[1]).map( function(item) {
+        return name + "[]=" + encodeURIComponent(item);
+      }).join('&');
+    }
+  }
+};
+
+// Returns true if child is contained within element
+Element.isParent = function(child, element) {
+  if (!child.parentNode || child == element) return false;
+  if (child.parentNode == element) return true;
+  return Element.isParent(child.parentNode, element);
+};
+
+Element.findChildren = function(element, only, recursive, tagName) {
+  if(!element.hasChildNodes()) return null;
+  tagName = tagName.toUpperCase();
+  if(only) only = [only].flatten();
+  var elements = [];
+  $A(element.childNodes).each( function(e) {
+    if(e.tagName && e.tagName.toUpperCase()==tagName &&
+      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
+        elements.push(e);
+    if(recursive) {
+      var grandchildren = Element.findChildren(e, only, recursive, tagName);
+      if(grandchildren) elements.push(grandchildren);
+    }
+  });
+
+  return (elements.length>0 ? elements.flatten() : []);
+};
+
+Element.offsetSize = function (element, type) {
+  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
+};
Index: /branches/util-apps/DomainUtil/web-app/js/prototype/effects.js
===================================================================
--- /branches/util-apps/DomainUtil/web-app/js/prototype/effects.js	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/js/prototype/effects.js	(revision 766)
@@ -0,0 +1,1123 @@
+// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+//  Justin Palmer (http://encytemedia.com/)
+//  Mark Pilgrim (http://diveintomark.org/)
+//  Martin Bialasinki
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// converts rgb() and #xxx to #xxxxxx format,
+// returns self (or first argument) if not convertable
+String.prototype.parseColor = function() {
+  var color = '#';
+  if (this.slice(0,4) == 'rgb(') {
+    var cols = this.slice(4,this.length-1).split(',');
+    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
+  } else {
+    if (this.slice(0,1) == '#') {
+      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
+      if (this.length==7) color = this.toLowerCase();
+    }
+  }
+  return (color.length==7 ? color : (arguments[0] || this));
+};
+
+/*--------------------------------------------------------------------------*/
+
+Element.collectTextNodes = function(element) {
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue :
+      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
+  }).flatten().join('');
+};
+
+Element.collectTextNodesIgnoreClass = function(element, className) {
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue :
+      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
+        Element.collectTextNodesIgnoreClass(node, className) : ''));
+  }).flatten().join('');
+};
+
+Element.setContentZoom = function(element, percent) {
+  element = $(element);
+  element.setStyle({fontSize: (percent/100) + 'em'});
+  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+  return element;
+};
+
+Element.getInlineOpacity = function(element){
+  return $(element).style.opacity || '';
+};
+
+Element.forceRerendering = function(element) {
+  try {
+    element = $(element);
+    var n = document.createTextNode(' ');
+    element.appendChild(n);
+    element.removeChild(n);
+  } catch(e) { }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+  _elementDoesNotExistError: {
+    name: 'ElementDoesNotExistError',
+    message: 'The specified DOM element does not exist, but is required for this effect to operate'
+  },
+  Transitions: {
+    linear: Prototype.K,
+    sinoidal: function(pos) {
+      return (-Math.cos(pos*Math.PI)/2) + .5;
+    },
+    reverse: function(pos) {
+      return 1-pos;
+    },
+    flicker: function(pos) {
+      var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
+      return pos > 1 ? 1 : pos;
+    },
+    wobble: function(pos) {
+      return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
+    },
+    pulse: function(pos, pulses) {
+      return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
+    },
+    spring: function(pos) {
+      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
+    },
+    none: function(pos) {
+      return 0;
+    },
+    full: function(pos) {
+      return 1;
+    }
+  },
+  DefaultOptions: {
+    duration:   1.0,   // seconds
+    fps:        100,   // 100= assume 66fps max.
+    sync:       false, // true for combining
+    from:       0.0,
+    to:         1.0,
+    delay:      0.0,
+    queue:      'parallel'
+  },
+  tagifyText: function(element) {
+    var tagifyStyle = 'position:relative';
+    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
+
+    element = $(element);
+    $A(element.childNodes).each( function(child) {
+      if (child.nodeType==3) {
+        child.nodeValue.toArray().each( function(character) {
+          element.insertBefore(
+            new Element('span', {style: tagifyStyle}).update(
+              character == ' ' ? String.fromCharCode(160) : character),
+              child);
+        });
+        Element.remove(child);
+      }
+    });
+  },
+  multiple: function(element, effect) {
+    var elements;
+    if (((typeof element == 'object') ||
+        Object.isFunction(element)) &&
+       (element.length))
+      elements = element;
+    else
+      elements = $(element).childNodes;
+
+    var options = Object.extend({
+      speed: 0.1,
+      delay: 0.0
+    }, arguments[2] || { });
+    var masterDelay = options.delay;
+
+    $A(elements).each( function(element, index) {
+      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
+    });
+  },
+  PAIRS: {
+    'slide':  ['SlideDown','SlideUp'],
+    'blind':  ['BlindDown','BlindUp'],
+    'appear': ['Appear','Fade']
+  },
+  toggle: function(element, effect, options) {
+    element = $(element);
+    effect  = (effect || 'appear').toLowerCase();
+    
+    return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({
+      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+    }, options || {}));
+  }
+};
+
+Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
+
+/* ------------- core effects ------------- */
+
+Effect.ScopedQueue = Class.create(Enumerable, {
+  initialize: function() {
+    this.effects  = [];
+    this.interval = null;
+  },
+  _each: function(iterator) {
+    this.effects._each(iterator);
+  },
+  add: function(effect) {
+    var timestamp = new Date().getTime();
+
+    var position = Object.isString(effect.options.queue) ?
+      effect.options.queue : effect.options.queue.position;
+
+    switch(position) {
+      case 'front':
+        // move unstarted effects after this effect
+        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+            e.startOn  += effect.finishOn;
+            e.finishOn += effect.finishOn;
+          });
+        break;
+      case 'with-last':
+        timestamp = this.effects.pluck('startOn').max() || timestamp;
+        break;
+      case 'end':
+        // start effect after last queued effect has finished
+        timestamp = this.effects.pluck('finishOn').max() || timestamp;
+        break;
+    }
+
+    effect.startOn  += timestamp;
+    effect.finishOn += timestamp;
+
+    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
+      this.effects.push(effect);
+
+    if (!this.interval)
+      this.interval = setInterval(this.loop.bind(this), 15);
+  },
+  remove: function(effect) {
+    this.effects = this.effects.reject(function(e) { return e==effect });
+    if (this.effects.length == 0) {
+      clearInterval(this.interval);
+      this.interval = null;
+    }
+  },
+  loop: function() {
+    var timePos = new Date().getTime();
+    for(var i=0, len=this.effects.length;i<len;i++)
+      this.effects[i] && this.effects[i].loop(timePos);
+  }
+});
+
+Effect.Queues = {
+  instances: $H(),
+  get: function(queueName) {
+    if (!Object.isString(queueName)) return queueName;
+
+    return this.instances.get(queueName) ||
+      this.instances.set(queueName, new Effect.ScopedQueue());
+  }
+};
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.Base = Class.create({
+  position: null,
+  start: function(options) {
+    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
+    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
+    this.currentFrame = 0;
+    this.state        = 'idle';
+    this.startOn      = this.options.delay*1000;
+    this.finishOn     = this.startOn+(this.options.duration*1000);
+    this.fromToDelta  = this.options.to-this.options.from;
+    this.totalTime    = this.finishOn-this.startOn;
+    this.totalFrames  = this.options.fps*this.options.duration;
+
+    this.render = (function() {
+      function dispatch(effect, eventName) {
+        if (effect.options[eventName + 'Internal'])
+          effect.options[eventName + 'Internal'](effect);
+        if (effect.options[eventName])
+          effect.options[eventName](effect);
+      }
+
+      return function(pos) {
+        if (this.state === "idle") {
+          this.state = "running";
+          dispatch(this, 'beforeSetup');
+          if (this.setup) this.setup();
+          dispatch(this, 'afterSetup');
+        }
+        if (this.state === "running") {
+          pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
+          this.position = pos;
+          dispatch(this, 'beforeUpdate');
+          if (this.update) this.update(pos);
+          dispatch(this, 'afterUpdate');
+        }
+      };
+    })();
+
+    this.event('beforeStart');
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ?
+        'global' : this.options.queue.scope).add(this);
+  },
+  loop: function(timePos) {
+    if (timePos >= this.startOn) {
+      if (timePos >= this.finishOn) {
+        this.render(1.0);
+        this.cancel();
+        this.event('beforeFinish');
+        if (this.finish) this.finish();
+        this.event('afterFinish');
+        return;
+      }
+      var pos   = (timePos - this.startOn) / this.totalTime,
+          frame = (pos * this.totalFrames).round();
+      if (frame > this.currentFrame) {
+        this.render(pos);
+        this.currentFrame = frame;
+      }
+    }
+  },
+  cancel: function() {
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ?
+        'global' : this.options.queue.scope).remove(this);
+    this.state = 'finished';
+  },
+  event: function(eventName) {
+    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+    if (this.options[eventName]) this.options[eventName](this);
+  },
+  inspect: function() {
+    var data = $H();
+    for(property in this)
+      if (!Object.isFunction(this[property])) data.set(property, this[property]);
+    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
+  }
+});
+
+Effect.Parallel = Class.create(Effect.Base, {
+  initialize: function(effects) {
+    this.effects = effects || [];
+    this.start(arguments[1]);
+  },
+  update: function(position) {
+    this.effects.invoke('render', position);
+  },
+  finish: function(position) {
+    this.effects.each( function(effect) {
+      effect.render(1.0);
+      effect.cancel();
+      effect.event('beforeFinish');
+      if (effect.finish) effect.finish(position);
+      effect.event('afterFinish');
+    });
+  }
+});
+
+Effect.Tween = Class.create(Effect.Base, {
+  initialize: function(object, from, to) {
+    object = Object.isString(object) ? $(object) : object;
+    var args = $A(arguments), method = args.last(),
+      options = args.length == 5 ? args[3] : null;
+    this.method = Object.isFunction(method) ? method.bind(object) :
+      Object.isFunction(object[method]) ? object[method].bind(object) :
+      function(value) { object[method] = value };
+    this.start(Object.extend({ from: from, to: to }, options || { }));
+  },
+  update: function(position) {
+    this.method(position);
+  }
+});
+
+Effect.Event = Class.create(Effect.Base, {
+  initialize: function() {
+    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
+  },
+  update: Prototype.emptyFunction
+});
+
+Effect.Opacity = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    // make this work on IE on elements without 'layout'
+    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+      this.element.setStyle({zoom: 1});
+    var options = Object.extend({
+      from: this.element.getOpacity() || 0.0,
+      to:   1.0
+    }, arguments[1] || { });
+    this.start(options);
+  },
+  update: function(position) {
+    this.element.setOpacity(position);
+  }
+});
+
+Effect.Move = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      x:    0,
+      y:    0,
+      mode: 'relative'
+    }, arguments[1] || { });
+    this.start(options);
+  },
+  setup: function() {
+    this.element.makePositioned();
+    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
+    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
+    if (this.options.mode == 'absolute') {
+      this.options.x = this.options.x - this.originalLeft;
+      this.options.y = this.options.y - this.originalTop;
+    }
+  },
+  update: function(position) {
+    this.element.setStyle({
+      left: (this.options.x  * position + this.originalLeft).round() + 'px',
+      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
+    });
+  }
+});
+
+// for backwards compatibility
+Effect.MoveBy = function(element, toTop, toLeft) {
+  return new Effect.Move(element,
+    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
+};
+
+Effect.Scale = Class.create(Effect.Base, {
+  initialize: function(element, percent) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      scaleX: true,
+      scaleY: true,
+      scaleContent: true,
+      scaleFromCenter: false,
+      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
+      scaleFrom: 100.0,
+      scaleTo:   percent
+    }, arguments[2] || { });
+    this.start(options);
+  },
+  setup: function() {
+    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+    this.elementPositioning = this.element.getStyle('position');
+
+    this.originalStyle = { };
+    ['top','left','width','height','fontSize'].each( function(k) {
+      this.originalStyle[k] = this.element.style[k];
+    }.bind(this));
+
+    this.originalTop  = this.element.offsetTop;
+    this.originalLeft = this.element.offsetLeft;
+
+    var fontSize = this.element.getStyle('font-size') || '100%';
+    ['em','px','%','pt'].each( function(fontSizeType) {
+      if (fontSize.indexOf(fontSizeType)>0) {
+        this.fontSize     = parseFloat(fontSize);
+        this.fontSizeType = fontSizeType;
+      }
+    }.bind(this));
+
+    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+
+    this.dims = null;
+    if (this.options.scaleMode=='box')
+      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+    if (/^content/.test(this.options.scaleMode))
+      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+    if (!this.dims)
+      this.dims = [this.options.scaleMode.originalHeight,
+                   this.options.scaleMode.originalWidth];
+  },
+  update: function(position) {
+    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+    if (this.options.scaleContent && this.fontSize)
+      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
+    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
+  },
+  finish: function(position) {
+    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
+  },
+  setDimensions: function(height, width) {
+    var d = { };
+    if (this.options.scaleX) d.width = width.round() + 'px';
+    if (this.options.scaleY) d.height = height.round() + 'px';
+    if (this.options.scaleFromCenter) {
+      var topd  = (height - this.dims[0])/2;
+      var leftd = (width  - this.dims[1])/2;
+      if (this.elementPositioning == 'absolute') {
+        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
+        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
+      } else {
+        if (this.options.scaleY) d.top = -topd + 'px';
+        if (this.options.scaleX) d.left = -leftd + 'px';
+      }
+    }
+    this.element.setStyle(d);
+  }
+});
+
+Effect.Highlight = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
+    this.start(options);
+  },
+  setup: function() {
+    // Prevent executing on elements not in the layout flow
+    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
+    // Disable background image during the effect
+    this.oldStyle = { };
+    if (!this.options.keepBackgroundImage) {
+      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
+      this.element.setStyle({backgroundImage: 'none'});
+    }
+    if (!this.options.endcolor)
+      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
+    if (!this.options.restorecolor)
+      this.options.restorecolor = this.element.getStyle('background-color');
+    // init color calculations
+    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
+    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
+  },
+  update: function(position) {
+    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
+      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
+  },
+  finish: function() {
+    this.element.setStyle(Object.extend(this.oldStyle, {
+      backgroundColor: this.options.restorecolor
+    }));
+  }
+});
+
+Effect.ScrollTo = function(element) {
+  var options = arguments[1] || { },
+  scrollOffsets = document.viewport.getScrollOffsets(),
+  elementOffsets = $(element).cumulativeOffset();
+
+  if (options.offset) elementOffsets[1] += options.offset;
+
+  return new Effect.Tween(null,
+    scrollOffsets.top,
+    elementOffsets[1],
+    options,
+    function(p){ scrollTo(scrollOffsets.left, p.round()); }
+  );
+};
+
+/* ------------- combination effects ------------- */
+
+Effect.Fade = function(element) {
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
+  var options = Object.extend({
+    from: element.getOpacity() || 1.0,
+    to:   0.0,
+    afterFinishInternal: function(effect) {
+      if (effect.options.to!=0) return;
+      effect.element.hide().setStyle({opacity: oldOpacity});
+    }
+  }, arguments[1] || { });
+  return new Effect.Opacity(element,options);
+};
+
+Effect.Appear = function(element) {
+  element = $(element);
+  var options = Object.extend({
+  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
+  to:   1.0,
+  // force Safari to render floated elements properly
+  afterFinishInternal: function(effect) {
+    effect.element.forceRerendering();
+  },
+  beforeSetup: function(effect) {
+    effect.element.setOpacity(effect.options.from).show();
+  }}, arguments[1] || { });
+  return new Effect.Opacity(element,options);
+};
+
+Effect.Puff = function(element) {
+  element = $(element);
+  var oldStyle = {
+    opacity: element.getInlineOpacity(),
+    position: element.getStyle('position'),
+    top:  element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height
+  };
+  return new Effect.Parallel(
+   [ new Effect.Scale(element, 200,
+      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
+     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
+     Object.extend({ duration: 1.0,
+      beforeSetupInternal: function(effect) {
+        Position.absolutize(effect.effects[0].element);
+      },
+      afterFinishInternal: function(effect) {
+         effect.effects[0].element.hide().setStyle(oldStyle); }
+     }, arguments[1] || { })
+   );
+};
+
+Effect.BlindUp = function(element) {
+  element = $(element);
+  element.makeClipping();
+  return new Effect.Scale(element, 0,
+    Object.extend({ scaleContent: false,
+      scaleX: false,
+      restoreAfterFinish: true,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping();
+      }
+    }, arguments[1] || { })
+  );
+};
+
+Effect.BlindDown = function(element) {
+  element = $(element);
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({
+    scaleContent: false,
+    scaleX: false,
+    scaleFrom: 0,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makeClipping().setStyle({height: '0px'}).show();
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping();
+    }
+  }, arguments[1] || { }));
+};
+
+Effect.SwitchOff = function(element) {
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
+  return new Effect.Appear(element, Object.extend({
+    duration: 0.4,
+    from: 0,
+    transition: Effect.Transitions.flicker,
+    afterFinishInternal: function(effect) {
+      new Effect.Scale(effect.element, 1, {
+        duration: 0.3, scaleFromCenter: true,
+        scaleX: false, scaleContent: false, restoreAfterFinish: true,
+        beforeSetup: function(effect) {
+          effect.element.makePositioned().makeClipping();
+        },
+        afterFinishInternal: function(effect) {
+          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
+        }
+      });
+    }
+  }, arguments[1] || { }));
+};
+
+Effect.DropOut = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left'),
+    opacity: element.getInlineOpacity() };
+  return new Effect.Parallel(
+    [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
+      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+    Object.extend(
+      { duration: 0.5,
+        beforeSetup: function(effect) {
+          effect.effects[0].element.makePositioned();
+        },
+        afterFinishInternal: function(effect) {
+          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
+        }
+      }, arguments[1] || { }));
+};
+
+Effect.Shake = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    distance: 20,
+    duration: 0.5
+  }, arguments[1] || {});
+  var distance = parseFloat(options.distance);
+  var split = parseFloat(options.duration) / 10.0;
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left') };
+    return new Effect.Move(element,
+      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+        effect.element.undoPositioned().setStyle(oldStyle);
+  }}); }}); }}); }}); }}); }});
+};
+
+Effect.SlideDown = function(element) {
+  element = $(element).cleanWhitespace();
+  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({
+    scaleContent: false,
+    scaleX: false,
+    scaleFrom: window.opera ? 0 : 1,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().setStyle({height: '0px'}).show();
+    },
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' });
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
+    }, arguments[1] || { })
+  );
+};
+
+Effect.SlideUp = function(element) {
+  element = $(element).cleanWhitespace();
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, window.opera ? 0 : 1,
+   Object.extend({ scaleContent: false,
+    scaleX: false,
+    scaleMode: 'box',
+    scaleFrom: 100,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().show();
+    },
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' });
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
+    }
+   }, arguments[1] || { })
+  );
+};
+
+// Bug in opera makes the TD containing this element expand for a instance after finish
+Effect.Squish = function(element) {
+  return new Effect.Scale(element, window.opera ? 1 : 0, {
+    restoreAfterFinish: true,
+    beforeSetup: function(effect) {
+      effect.element.makeClipping();
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping();
+    }
+  });
+};
+
+Effect.Grow = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.full
+  }, arguments[1] || { });
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();
+  var initialMoveX, initialMoveY;
+  var moveX, moveY;
+
+  switch (options.direction) {
+    case 'top-left':
+      initialMoveX = initialMoveY = moveX = moveY = 0;
+      break;
+    case 'top-right':
+      initialMoveX = dims.width;
+      initialMoveY = moveY = 0;
+      moveX = -dims.width;
+      break;
+    case 'bottom-left':
+      initialMoveX = moveX = 0;
+      initialMoveY = dims.height;
+      moveY = -dims.height;
+      break;
+    case 'bottom-right':
+      initialMoveX = dims.width;
+      initialMoveY = dims.height;
+      moveX = -dims.width;
+      moveY = -dims.height;
+      break;
+    case 'center':
+      initialMoveX = dims.width / 2;
+      initialMoveY = dims.height / 2;
+      moveX = -dims.width / 2;
+      moveY = -dims.height / 2;
+      break;
+  }
+
+  return new Effect.Move(element, {
+    x: initialMoveX,
+    y: initialMoveY,
+    duration: 0.01,
+    beforeSetup: function(effect) {
+      effect.element.hide().makeClipping().makePositioned();
+    },
+    afterFinishInternal: function(effect) {
+      new Effect.Parallel(
+        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
+          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
+          new Effect.Scale(effect.element, 100, {
+            scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
+            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
+        ], Object.extend({
+             beforeSetup: function(effect) {
+               effect.effects[0].element.setStyle({height: '0px'}).show();
+             },
+             afterFinishInternal: function(effect) {
+               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
+             }
+           }, options)
+      );
+    }
+  });
+};
+
+Effect.Shrink = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.none
+  }, arguments[1] || { });
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();
+  var moveX, moveY;
+
+  switch (options.direction) {
+    case 'top-left':
+      moveX = moveY = 0;
+      break;
+    case 'top-right':
+      moveX = dims.width;
+      moveY = 0;
+      break;
+    case 'bottom-left':
+      moveX = 0;
+      moveY = dims.height;
+      break;
+    case 'bottom-right':
+      moveX = dims.width;
+      moveY = dims.height;
+      break;
+    case 'center':
+      moveX = dims.width / 2;
+      moveY = dims.height / 2;
+      break;
+  }
+
+  return new Effect.Parallel(
+    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
+      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
+      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
+    ], Object.extend({
+         beforeStartInternal: function(effect) {
+           effect.effects[0].element.makePositioned().makeClipping();
+         },
+         afterFinishInternal: function(effect) {
+           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
+       }, options)
+  );
+};
+
+Effect.Pulsate = function(element) {
+  element = $(element);
+  var options    = arguments[1] || { },
+    oldOpacity = element.getInlineOpacity(),
+    transition = options.transition || Effect.Transitions.linear,
+    reverser   = function(pos){
+      return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
+    };
+
+  return new Effect.Opacity(element,
+    Object.extend(Object.extend({  duration: 2.0, from: 0,
+      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
+    }, options), {transition: reverser}));
+};
+
+Effect.Fold = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height };
+  element.makeClipping();
+  return new Effect.Scale(element, 5, Object.extend({
+    scaleContent: false,
+    scaleX: false,
+    afterFinishInternal: function(effect) {
+    new Effect.Scale(element, 1, {
+      scaleContent: false,
+      scaleY: false,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping().setStyle(oldStyle);
+      } });
+  }}, arguments[1] || { }));
+};
+
+Effect.Morph = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      style: { }
+    }, arguments[1] || { });
+
+    if (!Object.isString(options.style)) this.style = $H(options.style);
+    else {
+      if (options.style.include(':'))
+        this.style = options.style.parseStyle();
+      else {
+        this.element.addClassName(options.style);
+        this.style = $H(this.element.getStyles());
+        this.element.removeClassName(options.style);
+        var css = this.element.getStyles();
+        this.style = this.style.reject(function(style) {
+          return style.value == css[style.key];
+        });
+        options.afterFinishInternal = function(effect) {
+          effect.element.addClassName(effect.options.style);
+          effect.transforms.each(function(transform) {
+            effect.element.style[transform.style] = '';
+          });
+        };
+      }
+    }
+    this.start(options);
+  },
+
+  setup: function(){
+    function parseColor(color){
+      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
+      color = color.parseColor();
+      return $R(0,2).map(function(i){
+        return parseInt( color.slice(i*2+1,i*2+3), 16 );
+      });
+    }
+    this.transforms = this.style.map(function(pair){
+      var property = pair[0], value = pair[1], unit = null;
+
+      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
+        value = value.parseColor();
+        unit  = 'color';
+      } else if (property == 'opacity') {
+        value = parseFloat(value);
+        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+          this.element.setStyle({zoom: 1});
+      } else if (Element.CSS_LENGTH.test(value)) {
+          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
+          value = parseFloat(components[1]);
+          unit = (components.length == 3) ? components[2] : null;
+      }
+
+      var originalValue = this.element.getStyle(property);
+      return {
+        style: property.camelize(),
+        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
+        targetValue: unit=='color' ? parseColor(value) : value,
+        unit: unit
+      };
+    }.bind(this)).reject(function(transform){
+      return (
+        (transform.originalValue == transform.targetValue) ||
+        (
+          transform.unit != 'color' &&
+          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
+        )
+      );
+    });
+  },
+  update: function(position) {
+    var style = { }, transform, i = this.transforms.length;
+    while(i--)
+      style[(transform = this.transforms[i]).style] =
+        transform.unit=='color' ? '#'+
+          (Math.round(transform.originalValue[0]+
+            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
+          (Math.round(transform.originalValue[1]+
+            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
+          (Math.round(transform.originalValue[2]+
+            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
+        (transform.originalValue +
+          (transform.targetValue - transform.originalValue) * position).toFixed(3) +
+            (transform.unit === null ? '' : transform.unit);
+    this.element.setStyle(style, true);
+  }
+});
+
+Effect.Transform = Class.create({
+  initialize: function(tracks){
+    this.tracks  = [];
+    this.options = arguments[1] || { };
+    this.addTracks(tracks);
+  },
+  addTracks: function(tracks){
+    tracks.each(function(track){
+      track = $H(track);
+      var data = track.values().first();
+      this.tracks.push($H({
+        ids:     track.keys().first(),
+        effect:  Effect.Morph,
+        options: { style: data }
+      }));
+    }.bind(this));
+    return this;
+  },
+  play: function(){
+    return new Effect.Parallel(
+      this.tracks.map(function(track){
+        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
+        var elements = [$(ids) || $$(ids)].flatten();
+        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
+      }).flatten(),
+      this.options
+    );
+  }
+});
+
+Element.CSS_PROPERTIES = $w(
+  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
+  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
+  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
+  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
+  'fontSize fontWeight height left letterSpacing lineHeight ' +
+  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
+  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
+  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
+  'right textIndent top width wordSpacing zIndex');
+
+Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
+
+String.__parseStyleElement = document.createElement('div');
+String.prototype.parseStyle = function(){
+  var style, styleRules = $H();
+  if (Prototype.Browser.WebKit)
+    style = new Element('div',{style:this}).style;
+  else {
+    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
+    style = String.__parseStyleElement.childNodes[0].style;
+  }
+
+  Element.CSS_PROPERTIES.each(function(property){
+    if (style[property]) styleRules.set(property, style[property]);
+  });
+
+  if (Prototype.Browser.IE && this.include('opacity'))
+    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
+
+  return styleRules;
+};
+
+if (document.defaultView && document.defaultView.getComputedStyle) {
+  Element.getStyles = function(element) {
+    var css = document.defaultView.getComputedStyle($(element), null);
+    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
+      styles[property] = css[property];
+      return styles;
+    });
+  };
+} else {
+  Element.getStyles = function(element) {
+    element = $(element);
+    var css = element.currentStyle, styles;
+    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
+      results[property] = css[property];
+      return results;
+    });
+    if (!styles.opacity) styles.opacity = element.getOpacity();
+    return styles;
+  };
+}
+
+Effect.Methods = {
+  morph: function(element, style) {
+    element = $(element);
+    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
+    return element;
+  },
+  visualEffect: function(element, effect, options) {
+    element = $(element);
+    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
+    new Effect[klass](element, options);
+    return element;
+  },
+  highlight: function(element, options) {
+    element = $(element);
+    new Effect.Highlight(element, options);
+    return element;
+  }
+};
+
+$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
+  'pulsate shake puff squish switchOff dropOut').each(
+  function(effect) {
+    Effect.Methods[effect] = function(element, options){
+      element = $(element);
+      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
+      return element;
+    };
+  }
+);
+
+$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
+  function(f) { Effect.Methods[f] = Element[f]; }
+);
+
+Element.addMethods(Effect.Methods);
Index: /branches/util-apps/DomainUtil/web-app/js/prototype/prototype.js
===================================================================
--- /branches/util-apps/DomainUtil/web-app/js/prototype/prototype.js	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/js/prototype/prototype.js	(revision 766)
@@ -0,0 +1,4874 @@
+/*  Prototype JavaScript framework, version 1.6.1
+ *  (c) 2005-2009 Sam Stephenson
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.6.1',
+
+  Browser: (function(){
+    var ua = navigator.userAgent;
+    var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
+    return {
+      IE:             !!window.attachEvent && !isOpera,
+      Opera:          isOpera,
+      WebKit:         ua.indexOf('AppleWebKit/') > -1,
+      Gecko:          ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
+      MobileSafari:   /Apple.*Mobile.*Safari/.test(ua)
+    }
+  })(),
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    SelectorsAPI: !!document.querySelector,
+    ElementExtensions: (function() {
+      var constructor = window.Element || window.HTMLElement;
+      return !!(constructor && constructor.prototype);
+    })(),
+    SpecificElementExtensions: (function() {
+      if (typeof window.HTMLDivElement !== 'undefined')
+        return true;
+
+      var div = document.createElement('div');
+      var form = document.createElement('form');
+      var isSupported = false;
+
+      if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
+        isSupported = true;
+      }
+
+      div = form = null;
+
+      return isSupported;
+    })()
+  },
+
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+var Abstract = { };
+
+
+var Try = {
+  these: function() {
+    var returnValue;
+
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) { }
+    }
+
+    return returnValue;
+  }
+};
+
+/* Based on Alex Arnell's inheritance implementation. */
+
+var Class = (function() {
+  function subclass() {};
+  function create() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
+      this.initialize.apply(this, arguments);
+    }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;
+    klass.subclasses = [];
+
+    if (parent) {
+      subclass.prototype = parent.prototype;
+      klass.prototype = new subclass;
+      parent.subclasses.push(klass);
+    }
+
+    for (var i = 0; i < properties.length; i++)
+      klass.addMethods(properties[i]);
+
+    if (!klass.prototype.initialize)
+      klass.prototype.initialize = Prototype.emptyFunction;
+
+    klass.prototype.constructor = klass;
+    return klass;
+  }
+
+  function addMethods(source) {
+    var ancestor   = this.superclass && this.superclass.prototype;
+    var properties = Object.keys(source);
+
+    if (!Object.keys({ toString: true }).length) {
+      if (source.toString != Object.prototype.toString)
+        properties.push("toString");
+      if (source.valueOf != Object.prototype.valueOf)
+        properties.push("valueOf");
+    }
+
+    for (var i = 0, length = properties.length; i < length; i++) {
+      var property = properties[i], value = source[property];
+      if (ancestor && Object.isFunction(value) &&
+          value.argumentNames().first() == "$super") {
+        var method = value;
+        value = (function(m) {
+          return function() { return ancestor[m].apply(this, arguments); };
+        })(property).wrap(method);
+
+        value.valueOf = method.valueOf.bind(method);
+        value.toString = method.toString.bind(method);
+      }
+      this.prototype[property] = value;
+    }
+
+    return this;
+  }
+
+  return {
+    create: create,
+    Methods: {
+      addMethods: addMethods
+    }
+  };
+})();
+(function() {
+
+  var _toString = Object.prototype.toString;
+
+  function extend(destination, source) {
+    for (var property in source)
+      destination[property] = source[property];
+    return destination;
+  }
+
+  function inspect(object) {
+    try {
+      if (isUndefined(object)) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : String(object);
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  }
+
+  function toJSON(object) {
+    var type = typeof object;
+    switch (type) {
+      case 'undefined':
+      case 'function':
+      case 'unknown': return;
+      case 'boolean': return object.toString();
+    }
+
+    if (object === null) return 'null';
+    if (object.toJSON) return object.toJSON();
+    if (isElement(object)) return;
+
+    var results = [];
+    for (var property in object) {
+      var value = toJSON(object[property]);
+      if (!isUndefined(value))
+        results.push(property.toJSON() + ': ' + value);
+    }
+
+    return '{' + results.join(', ') + '}';
+  }
+
+  function toQueryString(object) {
+    return $H(object).toQueryString();
+  }
+
+  function toHTML(object) {
+    return object && object.toHTML ? object.toHTML() : String.interpret(object);
+  }
+
+  function keys(object) {
+    var results = [];
+    for (var property in object)
+      results.push(property);
+    return results;
+  }
+
+  function values(object) {
+    var results = [];
+    for (var property in object)
+      results.push(object[property]);
+    return results;
+  }
+
+  function clone(object) {
+    return extend({ }, object);
+  }
+
+  function isElement(object) {
+    return !!(object && object.nodeType == 1);
+  }
+
+  function isArray(object) {
+    return _toString.call(object) == "[object Array]";
+  }
+
+
+  function isHash(object) {
+    return object instanceof Hash;
+  }
+
+  function isFunction(object) {
+    return typeof object === "function";
+  }
+
+  function isString(object) {
+    return _toString.call(object) == "[object String]";
+  }
+
+  function isNumber(object) {
+    return _toString.call(object) == "[object Number]";
+  }
+
+  function isUndefined(object) {
+    return typeof object === "undefined";
+  }
+
+  extend(Object, {
+    extend:        extend,
+    inspect:       inspect,
+    toJSON:        toJSON,
+    toQueryString: toQueryString,
+    toHTML:        toHTML,
+    keys:          keys,
+    values:        values,
+    clone:         clone,
+    isElement:     isElement,
+    isArray:       isArray,
+    isHash:        isHash,
+    isFunction:    isFunction,
+    isString:      isString,
+    isNumber:      isNumber,
+    isUndefined:   isUndefined
+  });
+})();
+Object.extend(Function.prototype, (function() {
+  var slice = Array.prototype.slice;
+
+  function update(array, args) {
+    var arrayLength = array.length, length = args.length;
+    while (length--) array[arrayLength + length] = args[length];
+    return array;
+  }
+
+  function merge(array, args) {
+    array = slice.call(array, 0);
+    return update(array, args);
+  }
+
+  function argumentNames() {
+    var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
+      .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
+      .replace(/\s+/g, '').split(',');
+    return names.length == 1 && !names[0] ? [] : names;
+  }
+
+  function bind(context) {
+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+    var __method = this, args = slice.call(arguments, 1);
+    return function() {
+      var a = merge(args, arguments);
+      return __method.apply(context, a);
+    }
+  }
+
+  function bindAsEventListener(context) {
+    var __method = this, args = slice.call(arguments, 1);
+    return function(event) {
+      var a = update([event || window.event], args);
+      return __method.apply(context, a);
+    }
+  }
+
+  function curry() {
+    if (!arguments.length) return this;
+    var __method = this, args = slice.call(arguments, 0);
+    return function() {
+      var a = merge(args, arguments);
+      return __method.apply(this, a);
+    }
+  }
+
+  function delay(timeout) {
+    var __method = this, args = slice.call(arguments, 1);
+    timeout = timeout * 1000
+    return window.setTimeout(function() {
+      return __method.apply(__method, args);
+    }, timeout);
+  }
+
+  function defer() {
+    var args = update([0.01], arguments);
+    return this.delay.apply(this, args);
+  }
+
+  function wrap(wrapper) {
+    var __method = this;
+    return function() {
+      var a = update([__method.bind(this)], arguments);
+      return wrapper.apply(this, a);
+    }
+  }
+
+  function methodize() {
+    if (this._methodized) return this._methodized;
+    var __method = this;
+    return this._methodized = function() {
+      var a = update([this], arguments);
+      return __method.apply(null, a);
+    };
+  }
+
+  return {
+    argumentNames:       argumentNames,
+    bind:                bind,
+    bindAsEventListener: bindAsEventListener,
+    curry:               curry,
+    delay:               delay,
+    defer:               defer,
+    wrap:                wrap,
+    methodize:           methodize
+  }
+})());
+
+
+Date.prototype.toJSON = function() {
+  return '"' + this.getUTCFullYear() + '-' +
+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+    this.getUTCDate().toPaddedString(2) + 'T' +
+    this.getUTCHours().toPaddedString(2) + ':' +
+    this.getUTCMinutes().toPaddedString(2) + ':' +
+    this.getUTCSeconds().toPaddedString(2) + 'Z"';
+};
+
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+var PeriodicalExecuter = Class.create({
+  initialize: function(callback, frequency) {
+    this.callback = callback;
+    this.frequency = frequency;
+    this.currentlyExecuting = false;
+
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  execute: function() {
+    this.callback(this);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.execute();
+        this.currentlyExecuting = false;
+      } catch(e) {
+        this.currentlyExecuting = false;
+        throw e;
+      }
+    }
+  }
+});
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
+
+Object.extend(String.prototype, (function() {
+
+  function prepareReplacement(replacement) {
+    if (Object.isFunction(replacement)) return replacement;
+    var template = new Template(replacement);
+    return function(match) { return template.evaluate(match) };
+  }
+
+  function gsub(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = prepareReplacement(replacement);
+
+    if (Object.isString(pattern))
+      pattern = RegExp.escape(pattern);
+
+    if (!(pattern.length || pattern.source)) {
+      replacement = replacement('');
+      return replacement + source.split('').join(replacement) + replacement;
+    }
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  }
+
+  function sub(pattern, replacement, count) {
+    replacement = prepareReplacement(replacement);
+    count = Object.isUndefined(count) ? 1 : count;
+
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  }
+
+  function scan(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return String(this);
+  }
+
+  function truncate(length, truncation) {
+    length = length || 30;
+    truncation = Object.isUndefined(truncation) ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : String(this);
+  }
+
+  function strip() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  }
+
+  function stripTags() {
+    return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
+  }
+
+  function stripScripts() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  }
+
+  function extractScripts() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  }
+
+  function evalScripts() {
+    return this.extractScripts().map(function(script) { return eval(script) });
+  }
+
+  function escapeHTML() {
+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  }
+
+  function unescapeHTML() {
+    return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
+  }
+
+
+  function toQueryParams(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return { };
+
+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length > 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
+    });
+  }
+
+  function toArray() {
+    return this.split('');
+  }
+
+  function succ() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  }
+
+  function times(count) {
+    return count < 1 ? '' : new Array(count + 1).join(this);
+  }
+
+  function camelize() {
+    var parts = this.split('-'), len = parts.length;
+    if (len == 1) return parts[0];
+
+    var camelized = this.charAt(0) == '-'
+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
+      : parts[0];
+
+    for (var i = 1; i < len; i++)
+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
+
+    return camelized;
+  }
+
+  function capitalize() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  }
+
+  function underscore() {
+    return this.replace(/::/g, '/')
+               .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
+               .replace(/([a-z\d])([A-Z])/g, '$1_$2')
+               .replace(/-/g, '_')
+               .toLowerCase();
+  }
+
+  function dasherize() {
+    return this.replace(/_/g, '-');
+  }
+
+  function inspect(useDoubleQuotes) {
+    var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) {
+      if (character in String.specialChar) {
+        return String.specialChar[character];
+      }
+      return '\\u00' + character.charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+  }
+
+  function toJSON() {
+    return this.inspect(true);
+  }
+
+  function unfilterJSON(filter) {
+    return this.replace(filter || Prototype.JSONFilter, '$1');
+  }
+
+  function isJSON() {
+    var str = this;
+    if (str.blank()) return false;
+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+  }
+
+  function evalJSON(sanitize) {
+    var json = this.unfilterJSON();
+    try {
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  }
+
+  function include(pattern) {
+    return this.indexOf(pattern) > -1;
+  }
+
+  function startsWith(pattern) {
+    return this.indexOf(pattern) === 0;
+  }
+
+  function endsWith(pattern) {
+    var d = this.length - pattern.length;
+    return d >= 0 && this.lastIndexOf(pattern) === d;
+  }
+
+  function empty() {
+    return this == '';
+  }
+
+  function blank() {
+    return /^\s*$/.test(this);
+  }
+
+  function interpolate(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
+  }
+
+  return {
+    gsub:           gsub,
+    sub:            sub,
+    scan:           scan,
+    truncate:       truncate,
+    strip:          String.prototype.trim ? String.prototype.trim : strip,
+    stripTags:      stripTags,
+    stripScripts:   stripScripts,
+    extractScripts: extractScripts,
+    evalScripts:    evalScripts,
+    escapeHTML:     escapeHTML,
+    unescapeHTML:   unescapeHTML,
+    toQueryParams:  toQueryParams,
+    parseQuery:     toQueryParams,
+    toArray:        toArray,
+    succ:           succ,
+    times:          times,
+    camelize:       camelize,
+    capitalize:     capitalize,
+    underscore:     underscore,
+    dasherize:      dasherize,
+    inspect:        inspect,
+    toJSON:         toJSON,
+    unfilterJSON:   unfilterJSON,
+    isJSON:         isJSON,
+    evalJSON:       evalJSON,
+    include:        include,
+    startsWith:     startsWith,
+    endsWith:       endsWith,
+    empty:          empty,
+    blank:          blank,
+    interpolate:    interpolate
+  };
+})());
+
+var Template = Class.create({
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    if (object && Object.isFunction(object.toTemplateReplacements))
+      object = object.toTemplateReplacements();
+
+    return this.template.gsub(this.pattern, function(match) {
+      if (object == null) return (match[1] + '');
+
+      var before = match[1] || '';
+      if (before == '\\') return match[2];
+
+      var ctx = object, expr = match[3];
+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+      match = pattern.exec(expr);
+      if (match == null) return before;
+
+      while (match != null) {
+        var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1];
+        ctx = ctx[comp];
+        if (null == ctx || '' == match[3]) break;
+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+        match = pattern.exec(expr);
+      }
+
+      return before + String.interpret(ctx);
+    });
+  }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = (function() {
+  function each(iterator, context) {
+    var index = 0;
+    try {
+      this._each(function(value) {
+        iterator.call(context, value, index++);
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+    return this;
+  }
+
+  function eachSlice(number, iterator, context) {
+    var index = -number, slices = [], array = this.toArray();
+    if (number < 1) return array;
+    while ((index += number) < array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.collect(iterator, context);
+  }
+
+  function all(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!iterator.call(context, value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  }
+
+  function any(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result = false;
+    this.each(function(value, index) {
+      if (result = !!iterator.call(context, value, index))
+        throw $break;
+    });
+    return result;
+  }
+
+  function collect(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator.call(context, value, index));
+    });
+    return results;
+  }
+
+  function detect(iterator, context) {
+    var result;
+    this.each(function(value, index) {
+      if (iterator.call(context, value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  }
+
+  function findAll(iterator, context) {
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator.call(context, value, index))
+        results.push(value);
+    });
+    return results;
+  }
+
+  function grep(filter, iterator, context) {
+    iterator = iterator || Prototype.K;
+    var results = [];
+
+    if (Object.isString(filter))
+      filter = new RegExp(RegExp.escape(filter));
+
+    this.each(function(value, index) {
+      if (filter.match(value))
+        results.push(iterator.call(context, value, index));
+    });
+    return results;
+  }
+
+  function include(object) {
+    if (Object.isFunction(this.indexOf))
+      if (this.indexOf(object) != -1) return true;
+
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  }
+
+  function inGroupsOf(number, fillWith) {
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length < number) slice.push(fillWith);
+      return slice;
+    });
+  }
+
+  function inject(memo, iterator, context) {
+    this.each(function(value, index) {
+      memo = iterator.call(context, memo, value, index);
+    });
+    return memo;
+  }
+
+  function invoke(method) {
+    var args = $A(arguments).slice(1);
+    return this.map(function(value) {
+      return value[method].apply(value, args);
+    });
+  }
+
+  function max(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator.call(context, value, index);
+      if (result == null || value >= result)
+        result = value;
+    });
+    return result;
+  }
+
+  function min(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator.call(context, value, index);
+      if (result == null || value < result)
+        result = value;
+    });
+    return result;
+  }
+
+  function partition(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      (iterator.call(context, value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  }
+
+  function pluck(property) {
+    var results = [];
+    this.each(function(value) {
+      results.push(value[property]);
+    });
+    return results;
+  }
+
+  function reject(iterator, context) {
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator.call(context, value, index))
+        results.push(value);
+    });
+    return results;
+  }
+
+  function sortBy(iterator, context) {
+    return this.map(function(value, index) {
+      return {
+        value: value,
+        criteria: iterator.call(context, value, index)
+      };
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  }
+
+  function toArray() {
+    return this.map();
+  }
+
+  function zip() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (Object.isFunction(args.last()))
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      return iterator(collections.pluck(index));
+    });
+  }
+
+  function size() {
+    return this.toArray().length;
+  }
+
+  function inspect() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+
+
+
+
+
+
+
+
+
+  return {
+    each:       each,
+    eachSlice:  eachSlice,
+    all:        all,
+    every:      all,
+    any:        any,
+    some:       any,
+    collect:    collect,
+    map:        collect,
+    detect:     detect,
+    findAll:    findAll,
+    select:     findAll,
+    filter:     findAll,
+    grep:       grep,
+    include:    include,
+    member:     include,
+    inGroupsOf: inGroupsOf,
+    inject:     inject,
+    invoke:     invoke,
+    max:        max,
+    min:        min,
+    partition:  partition,
+    pluck:      pluck,
+    reject:     reject,
+    sortBy:     sortBy,
+    toArray:    toArray,
+    entries:    toArray,
+    zip:        zip,
+    size:       size,
+    inspect:    inspect,
+    find:       detect
+  };
+})();
+function $A(iterable) {
+  if (!iterable) return [];
+  if ('toArray' in Object(iterable)) return iterable.toArray();
+  var length = iterable.length || 0, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
+}
+
+function $w(string) {
+  if (!Object.isString(string)) return [];
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+Array.from = $A;
+
+
+(function() {
+  var arrayProto = Array.prototype,
+      slice = arrayProto.slice,
+      _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available
+
+  function each(iterator) {
+    for (var i = 0, length = this.length; i < length; i++)
+      iterator(this[i]);
+  }
+  if (!_each) _each = each;
+
+  function clear() {
+    this.length = 0;
+    return this;
+  }
+
+  function first() {
+    return this[0];
+  }
+
+  function last() {
+    return this[this.length - 1];
+  }
+
+  function compact() {
+    return this.select(function(value) {
+      return value != null;
+    });
+  }
+
+  function flatten() {
+    return this.inject([], function(array, value) {
+      if (Object.isArray(value))
+        return array.concat(value.flatten());
+      array.push(value);
+      return array;
+    });
+  }
+
+  function without() {
+    var values = slice.call(arguments, 0);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  }
+
+  function reverse(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
+  }
+
+  function uniq(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  }
+
+  function intersect(array) {
+    return this.uniq().findAll(function(item) {
+      return array.detect(function(value) { return item === value });
+    });
+  }
+
+
+  function clone() {
+    return slice.call(this, 0);
+  }
+
+  function size() {
+    return this.length;
+  }
+
+  function inspect() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  }
+
+  function toJSON() {
+    var results = [];
+    this.each(function(object) {
+      var value = Object.toJSON(object);
+      if (!Object.isUndefined(value)) results.push(value);
+    });
+    return '[' + results.join(', ') + ']';
+  }
+
+  function indexOf(item, i) {
+    i || (i = 0);
+    var length = this.length;
+    if (i < 0) i = length + i;
+    for (; i < length; i++)
+      if (this[i] === item) return i;
+    return -1;
+  }
+
+  function lastIndexOf(item, i) {
+    i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+    var n = this.slice(0, i).reverse().indexOf(item);
+    return (n < 0) ? n : i - n - 1;
+  }
+
+  function concat() {
+    var array = slice.call(this, 0), item;
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      item = arguments[i];
+      if (Object.isArray(item) && !('callee' in item)) {
+        for (var j = 0, arrayLength = item.length; j < arrayLength; j++)
+          array.push(item[j]);
+      } else {
+        array.push(item);
+      }
+    }
+    return array;
+  }
+
+  Object.extend(arrayProto, Enumerable);
+
+  if (!arrayProto._reverse)
+    arrayProto._reverse = arrayProto.reverse;
+
+  Object.extend(arrayProto, {
+    _each:     _each,
+    clear:     clear,
+    first:     first,
+    last:      last,
+    compact:   compact,
+    flatten:   flatten,
+    without:   without,
+    reverse:   reverse,
+    uniq:      uniq,
+    intersect: intersect,
+    clone:     clone,
+    toArray:   clone,
+    size:      size,
+    inspect:   inspect,
+    toJSON:    toJSON
+  });
+
+  var CONCAT_ARGUMENTS_BUGGY = (function() {
+    return [].concat(arguments)[0][0] !== 1;
+  })(1,2)
+
+  if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat;
+
+  if (!arrayProto.indexOf) arrayProto.indexOf = indexOf;
+  if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf;
+})();
+function $H(object) {
+  return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+  function initialize(object) {
+    this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+  }
+
+  function _each(iterator) {
+    for (var key in this._object) {
+      var value = this._object[key], pair = [key, value];
+      pair.key = key;
+      pair.value = value;
+      iterator(pair);
+    }
+  }
+
+  function set(key, value) {
+    return this._object[key] = value;
+  }
+
+  function get(key) {
+    if (this._object[key] !== Object.prototype[key])
+      return this._object[key];
+  }
+
+  function unset(key) {
+    var value = this._object[key];
+    delete this._object[key];
+    return value;
+  }
+
+  function toObject() {
+    return Object.clone(this._object);
+  }
+
+  function keys() {
+    return this.pluck('key');
+  }
+
+  function values() {
+    return this.pluck('value');
+  }
+
+  function index(value) {
+    var match = this.detect(function(pair) {
+      return pair.value === value;
+    });
+    return match && match.key;
+  }
+
+  function merge(object) {
+    return this.clone().update(object);
+  }
+
+  function update(object) {
+    return new Hash(object).inject(this, function(result, pair) {
+      result.set(pair.key, pair.value);
+      return result;
+    });
+  }
+
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
+
+  function toQueryString() {
+    return this.inject([], function(results, pair) {
+      var key = encodeURIComponent(pair.key), values = pair.value;
+
+      if (values && typeof values == 'object') {
+        if (Object.isArray(values))
+          return results.concat(values.map(toQueryPair.curry(key)));
+      } else results.push(toQueryPair(key, values));
+      return results;
+    }).join('&');
+  }
+
+  function inspect() {
+    return '#<Hash:{' + this.map(function(pair) {
+      return pair.map(Object.inspect).join(': ');
+    }).join(', ') + '}>';
+  }
+
+  function toJSON() {
+    return Object.toJSON(this.toObject());
+  }
+
+  function clone() {
+    return new Hash(this);
+  }
+
+  return {
+    initialize:             initialize,
+    _each:                  _each,
+    set:                    set,
+    get:                    get,
+    unset:                  unset,
+    toObject:               toObject,
+    toTemplateReplacements: toObject,
+    keys:                   keys,
+    values:                 values,
+    index:                  index,
+    merge:                  merge,
+    update:                 update,
+    toQueryString:          toQueryString,
+    inspect:                inspect,
+    toJSON:                 toJSON,
+    clone:                  clone
+  };
+})());
+
+Hash.from = $H;
+Object.extend(Number.prototype, (function() {
+  function toColorPart() {
+    return this.toPaddedString(2, 16);
+  }
+
+  function succ() {
+    return this + 1;
+  }
+
+  function times(iterator, context) {
+    $R(0, this, true).each(iterator, context);
+    return this;
+  }
+
+  function toPaddedString(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  }
+
+  function toJSON() {
+    return isFinite(this) ? this.toString() : 'null';
+  }
+
+  function abs() {
+    return Math.abs(this);
+  }
+
+  function round() {
+    return Math.round(this);
+  }
+
+  function ceil() {
+    return Math.ceil(this);
+  }
+
+  function floor() {
+    return Math.floor(this);
+  }
+
+  return {
+    toColorPart:    toColorPart,
+    succ:           succ,
+    times:          times,
+    toPaddedString: toPaddedString,
+    toJSON:         toJSON,
+    abs:            abs,
+    round:          round,
+    ceil:           ceil,
+    floor:          floor
+  };
+})());
+
+function $R(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+}
+
+var ObjectRange = Class.create(Enumerable, (function() {
+  function initialize(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  }
+
+  function _each(iterator) {
+    var value = this.start;
+    while (this.include(value)) {
+      iterator(value);
+      value = value.succ();
+    }
+  }
+
+  function include(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+
+  return {
+    initialize: initialize,
+    _each:      _each,
+    include:    include
+  };
+})());
+
+
+
+var Ajax = {
+  getTransport: function() {
+    return Try.these(
+      function() {return new XMLHttpRequest()},
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+    ) || false;
+  },
+
+  activeRequestCount: 0
+};
+
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responder) {
+    if (!this.include(responder))
+      this.responders.push(responder);
+  },
+
+  unregister: function(responder) {
+    this.responders = this.responders.without(responder);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (Object.isFunction(responder[callback])) {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) { }
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate:   function() { Ajax.activeRequestCount++ },
+  onComplete: function() { Ajax.activeRequestCount-- }
+});
+Ajax.Base = Class.create({
+  initialize: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
+      parameters:   '',
+      evalJSON:     true,
+      evalJS:       true
+    };
+    Object.extend(this.options, options || { });
+
+    this.options.method = this.options.method.toLowerCase();
+
+    if (Object.isString(this.options.parameters))
+      this.options.parameters = this.options.parameters.toQueryParams();
+    else if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
+  }
+});
+Ajax.Request = Class.create(Ajax.Base, {
+  _complete: false,
+
+  initialize: function($super, url, options) {
+    $super(options);
+    this.transport = Ajax.getTransport();
+    this.request(url);
+  },
+
+  request: function(url) {
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.clone(this.options.parameters);
+
+    if (!['get', 'post'].include(this.method)) {
+      params['_method'] = this.method;
+      this.method = 'post';
+    }
+
+    this.parameters = params;
+
+    if (params = Object.toQueryString(params)) {
+      if (this.method == 'get')
+        this.url += (this.url.include('?') ? '&' : '?') + params;
+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+        params += '&_=';
+    }
+
+    try {
+      var response = new Ajax.Response(this);
+      if (this.options.onCreate) this.options.onCreate(response);
+      Ajax.Responders.dispatch('onCreate', this, response);
+
+      this.transport.open(this.method.toUpperCase(), this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
+
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
+      this.setRequestHeaders();
+
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.body);
+
+      /* Force Firefox to handle ready state 4 for synchronous requests */
+      if (!this.options.asynchronous && this.transport.overrideMimeType)
+        this.onStateChange();
+
+    }
+    catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState > 1 && !((readyState == 4) && this._complete))
+      this.respondToReadyState(this.transport.readyState);
+  },
+
+  setRequestHeaders: function() {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'X-Prototype-Version': Prototype.Version,
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'post') {
+      headers['Content-type'] = this.options.contentType +
+        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+      /* Force "Connection: close" for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType &&
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+            headers['Connection'] = 'close';
+    }
+
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
+
+      if (Object.isFunction(extras.push))
+        for (var i = 0, length = extras.length; i < length; i += 2)
+          headers[extras[i]] = extras[i+1];
+      else
+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+    }
+
+    for (var name in headers)
+      this.transport.setRequestHeader(name, headers[name]);
+  },
+
+  success: function() {
+    var status = this.getStatus();
+    return !status || (status >= 200 && status < 300);
+  },
+
+  getStatus: function() {
+    try {
+      return this.transport.status || 0;
+    } catch (e) { return 0 }
+  },
+
+  respondToReadyState: function(readyState) {
+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
+
+    if (state == 'Complete') {
+      try {
+        this._complete = true;
+        (this.options['on' + response.status]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(response, response.headerJSON);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      var contentType = response.getHeader('Content-type');
+      if (this.options.evalJS == 'force'
+          || (this.options.evalJS && this.isSameOrigin() && contentType
+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    if (state == 'Complete') {
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  isSameOrigin: function() {
+    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+      protocol: location.protocol,
+      domain: document.domain,
+      port: location.port ? ':' + location.port : ''
+    }));
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name) || null;
+    } catch (e) { return null; }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+
+
+
+
+
+
+
+Ajax.Response = Class.create({
+  initialize: function(request){
+    this.request = request;
+    var transport  = this.transport  = request.transport,
+        readyState = this.readyState = transport.readyState;
+
+    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+      this.status       = this.getStatus();
+      this.statusText   = this.getStatusText();
+      this.responseText = String.interpret(transport.responseText);
+      this.headerJSON   = this._getHeaderJSON();
+    }
+
+    if(readyState == 4) {
+      var xml = transport.responseXML;
+      this.responseXML  = Object.isUndefined(xml) ? null : xml;
+      this.responseJSON = this._getResponseJSON();
+    }
+  },
+
+  status:      0,
+
+  statusText: '',
+
+  getStatus: Ajax.Request.prototype.getStatus,
+
+  getStatusText: function() {
+    try {
+      return this.transport.statusText || '';
+    } catch (e) { return '' }
+  },
+
+  getHeader: Ajax.Request.prototype.getHeader,
+
+  getAllHeaders: function() {
+    try {
+      return this.getAllResponseHeaders();
+    } catch (e) { return null }
+  },
+
+  getResponseHeader: function(name) {
+    return this.transport.getResponseHeader(name);
+  },
+
+  getAllResponseHeaders: function() {
+    return this.transport.getAllResponseHeaders();
+  },
+
+  _getHeaderJSON: function() {
+    var json = this.getHeader('X-JSON');
+    if (!json) return null;
+    json = decodeURIComponent(escape(json));
+    try {
+      return json.evalJSON(this.request.options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  },
+
+  _getResponseJSON: function() {
+    var options = this.request.options;
+    if (!options.evalJSON || (options.evalJSON != 'force' &&
+      !(this.getHeader('Content-type') || '').include('application/json')) ||
+        this.responseText.blank())
+          return null;
+    try {
+      return this.responseText.evalJSON(options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+  initialize: function($super, container, url, options) {
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
+    };
+
+    options = Object.clone(options);
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, json) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
+    }).bind(this);
+
+    $super(url, options);
+  },
+
+  updateContent: function(responseText) {
+    var receiver = this.container[this.success() ? 'success' : 'failure'],
+        options = this.options;
+
+    if (!options.evalScripts) responseText = responseText.stripScripts();
+
+    if (receiver = $(receiver)) {
+      if (options.insertion) {
+        if (Object.isString(options.insertion)) {
+          var insertion = { }; insertion[options.insertion] = responseText;
+          receiver.insert(insertion);
+        }
+        else options.insertion(receiver, responseText);
+      }
+      else receiver.update(responseText);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+  initialize: function($super, container, url, options) {
+    $super(options);
+    this.onComplete = this.options.onComplete;
+
+    this.frequency = (this.options.frequency || 2);
+    this.decay = (this.options.decay || 1);
+
+    this.updater = { };
+    this.container = container;
+    this.url = url;
+
+    this.start();
+  },
+
+  start: function() {
+    this.options.onComplete = this.updateComplete.bind(this);
+    this.onTimerEvent();
+  },
+
+  stop: function() {
+    this.updater.options.onComplete = undefined;
+    clearTimeout(this.timer);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+  },
+
+  updateComplete: function(response) {
+    if (this.options.decay) {
+      this.decay = (response.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = response.responseText;
+    }
+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
+  },
+
+  onTimerEvent: function() {
+    this.updater = new Ajax.Updater(this.container, this.url, this.options);
+  }
+});
+
+
+
+function $(element) {
+  if (arguments.length > 1) {
+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+      elements.push($(arguments[i]));
+    return elements;
+  }
+  if (Object.isString(element))
+    element = document.getElementById(element);
+  return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+  document._getElementsByXPath = function(expression, parentElement) {
+    var results = [];
+    var query = document.evaluate(expression, $(parentElement) || document,
+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0, length = query.snapshotLength; i < length; i++)
+      results.push(Element.extend(query.snapshotItem(i)));
+    return results;
+  };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+  Object.extend(Node, {
+    ELEMENT_NODE: 1,
+    ATTRIBUTE_NODE: 2,
+    TEXT_NODE: 3,
+    CDATA_SECTION_NODE: 4,
+    ENTITY_REFERENCE_NODE: 5,
+    ENTITY_NODE: 6,
+    PROCESSING_INSTRUCTION_NODE: 7,
+    COMMENT_NODE: 8,
+    DOCUMENT_NODE: 9,
+    DOCUMENT_TYPE_NODE: 10,
+    DOCUMENT_FRAGMENT_NODE: 11,
+    NOTATION_NODE: 12
+  });
+}
+
+
+(function(global) {
+
+  var SETATTRIBUTE_IGNORES_NAME = (function(){
+    var elForm = document.createElement("form");
+    var elInput = document.createElement("input");
+    var root = document.documentElement;
+    elInput.setAttribute("name", "test");
+    elForm.appendChild(elInput);
+    root.appendChild(elForm);
+    var isBuggy = elForm.elements
+      ? (typeof elForm.elements.test == "undefined")
+      : null;
+    root.removeChild(elForm);
+    elForm = elInput = null;
+    return isBuggy;
+  })();
+
+  var element = global.Element;
+  global.Element = function(tagName, attributes) {
+    attributes = attributes || { };
+    tagName = tagName.toLowerCase();
+    var cache = Element.cache;
+    if (SETATTRIBUTE_IGNORES_NAME && attributes.name) {
+      tagName = '<' + tagName + ' name="' + attributes.name + '">';
+      delete attributes.name;
+      return Element.writeAttribute(document.createElement(tagName), attributes);
+    }
+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+  };
+  Object.extend(global.Element, element || { });
+  if (element) global.Element.prototype = element.prototype;
+})(this);
+
+Element.cache = { };
+Element.idCounter = 1;
+
+Element.Methods = {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
+  toggle: function(element) {
+    element = $(element);
+    Element[Element.visible(element) ? 'hide' : 'show'](element);
+    return element;
+  },
+
+
+  hide: function(element) {
+    element = $(element);
+    element.style.display = 'none';
+    return element;
+  },
+
+  show: function(element) {
+    element = $(element);
+    element.style.display = '';
+    return element;
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+    return element;
+  },
+
+  update: (function(){
+
+    var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){
+      var el = document.createElement("select"),
+          isBuggy = true;
+      el.innerHTML = "<option value=\"test\">test</option>";
+      if (el.options && el.options[0]) {
+        isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION";
+      }
+      el = null;
+      return isBuggy;
+    })();
+
+    var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){
+      try {
+        var el = document.createElement("table");
+        if (el && el.tBodies) {
+          el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>";
+          var isBuggy = typeof el.tBodies[0] == "undefined";
+          el = null;
+          return isBuggy;
+        }
+      } catch (e) {
+        return true;
+      }
+    })();
+
+    var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () {
+      var s = document.createElement("script"),
+          isBuggy = false;
+      try {
+        s.appendChild(document.createTextNode(""));
+        isBuggy = !s.firstChild ||
+          s.firstChild && s.firstChild.nodeType !== 3;
+      } catch (e) {
+        isBuggy = true;
+      }
+      s = null;
+      return isBuggy;
+    })();
+
+    function update(element, content) {
+      element = $(element);
+
+      if (content && content.toElement)
+        content = content.toElement();
+
+      if (Object.isElement(content))
+        return element.update().insert(content);
+
+      content = Object.toHTML(content);
+
+      var tagName = element.tagName.toUpperCase();
+
+      if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) {
+        element.text = content;
+        return element;
+      }
+
+      if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) {
+        if (tagName in Element._insertionTranslations.tags) {
+          while (element.firstChild) {
+            element.removeChild(element.firstChild);
+          }
+          Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+            .each(function(node) {
+              element.appendChild(node)
+            });
+        }
+        else {
+          element.innerHTML = content.stripScripts();
+        }
+      }
+      else {
+        element.innerHTML = content.stripScripts();
+      }
+
+      content.evalScripts.bind(content).defer();
+      return element;
+    }
+
+    return update;
+  })(),
+
+  replace: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    else if (!Object.isElement(content)) {
+      content = Object.toHTML(content);
+      var range = element.ownerDocument.createRange();
+      range.selectNode(element);
+      content.evalScripts.bind(content).defer();
+      content = range.createContextualFragment(content.stripScripts());
+    }
+    element.parentNode.replaceChild(content, element);
+    return element;
+  },
+
+  insert: function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+          insertions = {bottom:insertions};
+
+    var content, insert, tagName, childNodes;
+
+    for (var position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      insert = Element._insertionTranslations[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+      if (position == 'top' || position == 'after') childNodes.reverse();
+      childNodes.each(insert.curry(element));
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  },
+
+  wrap: function(element, wrapper, attributes) {
+    element = $(element);
+    if (Object.isElement(wrapper))
+      $(wrapper).writeAttribute(attributes || { });
+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+    else wrapper = new Element('div', wrapper);
+    if (element.parentNode)
+      element.parentNode.replaceChild(wrapper, element);
+    wrapper.appendChild(element);
+    return wrapper;
+  },
+
+  inspect: function(element) {
+    element = $(element);
+    var result = '<' + element.tagName.toLowerCase();
+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+      var property = pair.first(), attribute = pair.last();
+      var value = (element[property] || '').toString();
+      if (value) result += ' ' + attribute + '=' + value.inspect(true);
+    });
+    return result + '>';
+  },
+
+  recursivelyCollect: function(element, property) {
+    element = $(element);
+    var elements = [];
+    while (element = element[property])
+      if (element.nodeType == 1)
+        elements.push(Element.extend(element));
+    return elements;
+  },
+
+  ancestors: function(element) {
+    return Element.recursivelyCollect(element, 'parentNode');
+  },
+
+  descendants: function(element) {
+    return Element.select(element, "*");
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  immediateDescendants: function(element) {
+    if (!(element = $(element).firstChild)) return [];
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    if (element) return [element].concat($(element).nextSiblings());
+    return [];
+  },
+
+  previousSiblings: function(element) {
+    return Element.recursivelyCollect(element, 'previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return Element.recursivelyCollect(element, 'nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return Element.previousSiblings(element).reverse()
+      .concat(Element.nextSiblings(element));
+  },
+
+  match: function(element, selector) {
+    if (Object.isString(selector))
+      selector = new Selector(selector);
+    return selector.match($(element));
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = Element.ancestors(element);
+    return Object.isNumber(expression) ? ancestors[expression] :
+      Selector.findElement(ancestors, expression, index);
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return Element.firstDescendant(element);
+    return Object.isNumber(expression) ? Element.descendants(element)[expression] :
+      Element.select(element, expression)[index || 0];
+  },
+
+  previous: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+    var previousSiblings = Element.previousSiblings(element);
+    return Object.isNumber(expression) ? previousSiblings[expression] :
+      Selector.findElement(previousSiblings, expression, index);
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+    var nextSiblings = Element.nextSiblings(element);
+    return Object.isNumber(expression) ? nextSiblings[expression] :
+      Selector.findElement(nextSiblings, expression, index);
+  },
+
+
+  select: function(element) {
+    var args = Array.prototype.slice.call(arguments, 1);
+    return Selector.findChildElements(element, args);
+  },
+
+  adjacent: function(element) {
+    var args = Array.prototype.slice.call(arguments, 1);
+    return Selector.findChildElements(element.parentNode, args).without(element);
+  },
+
+  identify: function(element) {
+    element = $(element);
+    var id = Element.readAttribute(element, 'id');
+    if (id) return id;
+    do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id));
+    Element.writeAttribute(element, 'id', id);
+    return id;
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      var t = Element._attributeTranslations.read;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name]) name = t.names[name];
+      if (name.include(':')) {
+        return (!element.attributes || !element.attributes[name]) ? null :
+         element.attributes[name].value;
+      }
+    }
+    return element.getAttribute(name);
+  },
+
+  writeAttribute: function(element, name, value) {
+    element = $(element);
+    var attributes = { }, t = Element._attributeTranslations.write;
+
+    if (typeof name == 'object') attributes = name;
+    else attributes[name] = Object.isUndefined(value) ? true : value;
+
+    for (var attr in attributes) {
+      name = t.names[attr] || attr;
+      value = attributes[attr];
+      if (t.values[attr]) name = t.values[attr](element, value);
+      if (value === false || value === null)
+        element.removeAttribute(name);
+      else if (value === true)
+        element.setAttribute(name, name);
+      else element.setAttribute(name, value);
+    }
+    return element;
+  },
+
+  getHeight: function(element) {
+    return Element.getDimensions(element).height;
+  },
+
+  getWidth: function(element) {
+    return Element.getDimensions(element).width;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    var elementClassName = element.className;
+    return (elementClassName.length > 0 && (elementClassName == className ||
+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    if (!Element.hasClassName(element, className))
+      element.className += (element.className ? ' ' : '') + className;
+    return element;
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    element.className = element.className.replace(
+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return Element[Element.hasClassName(element, className) ?
+      'removeClassName' : 'addClassName'](element, className);
+  },
+
+  cleanWhitespace: function(element) {
+    element = $(element);
+    var node = element.firstChild;
+    while (node) {
+      var nextNode = node.nextSibling;
+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+        element.removeChild(node);
+      node = nextNode;
+    }
+    return element;
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+
+    if (element.compareDocumentPosition)
+      return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+    if (ancestor.contains)
+      return ancestor.contains(element) && ancestor !== element;
+
+    while (element = element.parentNode)
+      if (element == ancestor) return true;
+
+    return false;
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var pos = Element.cumulativeOffset(element);
+    window.scrollTo(pos[0], pos[1]);
+    return element;
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value || value == 'auto') {
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
+    }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
+
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
+
+  setStyle: function(element, styles) {
+    element = $(element);
+    var elementStyle = element.style, match;
+    if (Object.isString(styles)) {
+      element.style.cssText += ';' + styles;
+      return styles.include('opacity') ?
+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+    }
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property]);
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+            property] = styles[property];
+
+    return element;
+  },
+
+  setOpacity: function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+    return element;
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    var display = Element.getStyle(element, 'display');
+    if (display != 'none' && display != null) // Safari bug
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    var originalDisplay = els.display;
+    els.visibility = 'hidden';
+    if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari
+      els.position = 'absolute';
+    els.display = 'block';
+    var originalWidth = element.clientWidth;
+    var originalHeight = element.clientHeight;
+    els.display = originalDisplay;
+    els.position = originalPosition;
+    els.visibility = originalVisibility;
+    return {width: originalWidth, height: originalHeight};
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      if (Prototype.Browser.Opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+    return element;
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+    return element;
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return element;
+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+    if (element._overflow !== 'hidden')
+      element.style.overflow = 'hidden';
+    return element;
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (!element._overflow) return element;
+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+    element._overflow = null;
+    return element;
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        if (element.tagName.toUpperCase() == 'BODY') break;
+        var p = Element.getStyle(element, 'position');
+        if (p !== 'static') break;
+      }
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (Element.getStyle(element, 'position') == 'absolute') return element;
+
+    var offsets = Element.positionedOffset(element);
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.width  = width + 'px';
+    element.style.height = height + 'px';
+    return element;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (Element.getStyle(element, 'position') == 'relative') return element;
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
+    return element;
+  },
+
+  cumulativeScrollOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  getOffsetParent: function(element) {
+    if (element.offsetParent) return $(element.offsetParent);
+    if (element == document.body) return $(element);
+
+    while ((element = element.parentNode) && element != document.body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return $(element);
+
+    return $(document.body);
+  },
+
+  viewportOffset: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      if (element.offsetParent == document.body &&
+        Element.getStyle(element, 'position') == 'absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  clonePosition: function(element, source) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || { });
+
+    source = $(source);
+    var p = Element.viewportOffset(source);
+
+    element = $(element);
+    var delta = [0, 0];
+    var parent = null;
+    if (Element.getStyle(element, 'position') == 'absolute') {
+      parent = Element.getOffsetParent(element);
+      delta = Element.viewportOffset(parent);
+    }
+
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+    return element;
+  }
+};
+
+Object.extend(Element.Methods, {
+  getElementsBySelector: Element.Methods.select,
+
+  childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+  write: {
+    names: {
+      className: 'class',
+      htmlFor:   'for'
+    },
+    values: { }
+  }
+};
+
+if (Prototype.Browser.Opera) {
+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+    function(proceed, element, style) {
+      switch (style) {
+        case 'left': case 'top': case 'right': case 'bottom':
+          if (proceed(element, 'position') === 'static') return null;
+        case 'height': case 'width':
+          if (!Element.visible(element)) return null;
+
+          var dim = parseInt(proceed(element, style), 10);
+
+          if (dim !== element['offset' + style.capitalize()])
+            return dim + 'px';
+
+          var properties;
+          if (style === 'height') {
+            properties = ['border-top-width', 'padding-top',
+             'padding-bottom', 'border-bottom-width'];
+          }
+          else {
+            properties = ['border-left-width', 'padding-left',
+             'padding-right', 'border-right-width'];
+          }
+          return properties.inject(dim, function(memo, property) {
+            var val = proceed(element, property);
+            return val === null ? memo : memo - parseInt(val, 10);
+          }) + 'px';
+        default: return proceed(element, style);
+      }
+    }
+  );
+
+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+    function(proceed, element, attribute) {
+      if (attribute === 'title') return element.title;
+      return proceed(element, attribute);
+    }
+  );
+}
+
+else if (Prototype.Browser.IE) {
+  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
+    function(proceed, element) {
+      element = $(element);
+      try { element.offsetParent }
+      catch(e) { return $(document.body) }
+      var position = element.getStyle('position');
+      if (position !== 'static') return proceed(element);
+      element.setStyle({ position: 'relative' });
+      var value = proceed(element);
+      element.setStyle({ position: position });
+      return value;
+    }
+  );
+
+  $w('positionedOffset viewportOffset').each(function(method) {
+    Element.Methods[method] = Element.Methods[method].wrap(
+      function(proceed, element) {
+        element = $(element);
+        try { element.offsetParent }
+        catch(e) { return Element._returnOffset(0,0) }
+        var position = element.getStyle('position');
+        if (position !== 'static') return proceed(element);
+        var offsetParent = element.getOffsetParent();
+        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+          offsetParent.setStyle({ zoom: 1 });
+        element.setStyle({ position: 'relative' });
+        var value = proceed(element);
+        element.setStyle({ position: position });
+        return value;
+      }
+    );
+  });
+
+  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
+    function(proceed, element) {
+      try { element.offsetParent }
+      catch(e) { return Element._returnOffset(0,0) }
+      return proceed(element);
+    }
+  );
+
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value && element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+        return element['offset' + style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    function stripAlpha(filter){
+      return filter.replace(/alpha\([^\)]*\)/gi,'');
+    }
+    element = $(element);
+    var currentStyle = element.currentStyle;
+    if ((currentStyle && !currentStyle.hasLayout) ||
+      (!currentStyle && element.style.zoom == 'normal'))
+        element.style.zoom = 1;
+
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      (filter = stripAlpha(filter)) ?
+        style.filter = filter : style.removeAttribute('filter');
+      return element;
+    } else if (value < 0.00001) value = 0;
+    style.filter = stripAlpha(filter) +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  Element._attributeTranslations = (function(){
+
+    var classProp = 'className';
+    var forProp = 'for';
+
+    var el = document.createElement('div');
+
+    el.setAttribute(classProp, 'x');
+
+    if (el.className !== 'x') {
+      el.setAttribute('class', 'x');
+      if (el.className === 'x') {
+        classProp = 'class';
+      }
+    }
+    el = null;
+
+    el = document.createElement('label');
+    el.setAttribute(forProp, 'x');
+    if (el.htmlFor !== 'x') {
+      el.setAttribute('htmlFor', 'x');
+      if (el.htmlFor === 'x') {
+        forProp = 'htmlFor';
+      }
+    }
+    el = null;
+
+    return {
+      read: {
+        names: {
+          'class':      classProp,
+          'className':  classProp,
+          'for':        forProp,
+          'htmlFor':    forProp
+        },
+        values: {
+          _getAttr: function(element, attribute) {
+            return element.getAttribute(attribute);
+          },
+          _getAttr2: function(element, attribute) {
+            return element.getAttribute(attribute, 2);
+          },
+          _getAttrNode: function(element, attribute) {
+            var node = element.getAttributeNode(attribute);
+            return node ? node.value : "";
+          },
+          _getEv: (function(){
+
+            var el = document.createElement('div');
+            el.onclick = Prototype.emptyFunction;
+            var value = el.getAttribute('onclick');
+            var f;
+
+            if (String(value).indexOf('{') > -1) {
+              f = function(element, attribute) {
+                attribute = element.getAttribute(attribute);
+                if (!attribute) return null;
+                attribute = attribute.toString();
+                attribute = attribute.split('{')[1];
+                attribute = attribute.split('}')[0];
+                return attribute.strip();
+              };
+            }
+            else if (value === '') {
+              f = function(element, attribute) {
+                attribute = element.getAttribute(attribute);
+                if (!attribute) return null;
+                return attribute.strip();
+              };
+            }
+            el = null;
+            return f;
+          })(),
+          _flag: function(element, attribute) {
+            return $(element).hasAttribute(attribute) ? attribute : null;
+          },
+          style: function(element) {
+            return element.style.cssText.toLowerCase();
+          },
+          title: function(element) {
+            return element.title;
+          }
+        }
+      }
+    }
+  })();
+
+  Element._attributeTranslations.write = {
+    names: Object.extend({
+      cellpadding: 'cellPadding',
+      cellspacing: 'cellSpacing'
+    }, Element._attributeTranslations.read.names),
+    values: {
+      checked: function(element, value) {
+        element.checked = !!value;
+      },
+
+      style: function(element, value) {
+        element.style.cssText = value ? value : '';
+      }
+    }
+  };
+
+  Element._attributeTranslations.has = {};
+
+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+  });
+
+  (function(v) {
+    Object.extend(v, {
+      href:        v._getAttr2,
+      src:         v._getAttr2,
+      type:        v._getAttr,
+      action:      v._getAttrNode,
+      disabled:    v._flag,
+      checked:     v._flag,
+      readonly:    v._flag,
+      multiple:    v._flag,
+      onload:      v._getEv,
+      onunload:    v._getEv,
+      onclick:     v._getEv,
+      ondblclick:  v._getEv,
+      onmousedown: v._getEv,
+      onmouseup:   v._getEv,
+      onmouseover: v._getEv,
+      onmousemove: v._getEv,
+      onmouseout:  v._getEv,
+      onfocus:     v._getEv,
+      onblur:      v._getEv,
+      onkeypress:  v._getEv,
+      onkeydown:   v._getEv,
+      onkeyup:     v._getEv,
+      onsubmit:    v._getEv,
+      onreset:     v._getEv,
+      onselect:    v._getEv,
+      onchange:    v._getEv
+    });
+  })(Element._attributeTranslations.read.values);
+
+  if (Prototype.BrowserFeatures.ElementExtensions) {
+    (function() {
+      function _descendants(element) {
+        var nodes = element.getElementsByTagName('*'), results = [];
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName !== "!") // Filter out comment nodes.
+            results.push(node);
+        return results;
+      }
+
+      Element.Methods.down = function(element, expression, index) {
+        element = $(element);
+        if (arguments.length == 1) return element.firstDescendant();
+        return Object.isNumber(expression) ? _descendants(element)[expression] :
+          Element.select(element, expression)[index || 0];
+      }
+    })();
+  }
+
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+else if (Prototype.Browser.WebKit) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+
+    if (value == 1)
+      if(element.tagName.toUpperCase() == 'IMG' && element.width) {
+        element.width++; element.width--;
+      } else try {
+        var n = document.createTextNode(' ');
+        element.appendChild(n);
+        element.removeChild(n);
+      } catch (e) { }
+
+    return element;
+  };
+
+  Element.Methods.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return Element._returnOffset(valueL, valueT);
+  };
+}
+
+if ('outerHTML' in document.documentElement) {
+  Element.Methods.replace = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) {
+      element.parentNode.replaceChild(content, element);
+      return element;
+    }
+
+    content = Object.toHTML(content);
+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+    if (Element._insertionTranslations.tags[tagName]) {
+      var nextSibling = element.next();
+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+      parent.removeChild(element);
+      if (nextSibling)
+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+      else
+        fragments.each(function(node) { parent.appendChild(node) });
+    }
+    else element.outerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+Element._returnOffset = function(l, t) {
+  var result = [l, t];
+  result.left = l;
+  result.top = t;
+  return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+  if (t) {
+    div.innerHTML = t[0] + html + t[1];
+    t[2].times(function() { div = div.firstChild });
+  } else div.innerHTML = html;
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: function(element, node) {
+    element.parentNode.insertBefore(node, element);
+  },
+  top: function(element, node) {
+    element.insertBefore(node, element.firstChild);
+  },
+  bottom: function(element, node) {
+    element.appendChild(node);
+  },
+  after: function(element, node) {
+    element.parentNode.insertBefore(node, element.nextSibling);
+  },
+  tags: {
+    TABLE:  ['<table>',                '</table>',                   1],
+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+    SELECT: ['<select>',               '</select>',                  1]
+  }
+};
+
+(function() {
+  var tags = Element._insertionTranslations.tags;
+  Object.extend(tags, {
+    THEAD: tags.TBODY,
+    TFOOT: tags.TBODY,
+    TH:    tags.TD
+  });
+})();
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    attribute = Element._attributeTranslations.has[attribute] || attribute;
+    var node = $(element).getAttributeNode(attribute);
+    return !!(node && node.specified);
+  }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+(function(div) {
+
+  if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) {
+    window.HTMLElement = { };
+    window.HTMLElement.prototype = div['__proto__'];
+    Prototype.BrowserFeatures.ElementExtensions = true;
+  }
+
+  div = null;
+
+})(document.createElement('div'))
+
+Element.extend = (function() {
+
+  function checkDeficiency(tagName) {
+    if (typeof window.Element != 'undefined') {
+      var proto = window.Element.prototype;
+      if (proto) {
+        var id = '_' + (Math.random()+'').slice(2);
+        var el = document.createElement(tagName);
+        proto[id] = 'x';
+        var isBuggy = (el[id] !== 'x');
+        delete proto[id];
+        el = null;
+        return isBuggy;
+      }
+    }
+    return false;
+  }
+
+  function extendElementWith(element, methods) {
+    for (var property in methods) {
+      var value = methods[property];
+      if (Object.isFunction(value) && !(property in element))
+        element[property] = value.methodize();
+    }
+  }
+
+  var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object');
+
+  if (Prototype.BrowserFeatures.SpecificElementExtensions) {
+    if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) {
+      return function(element) {
+        if (element && typeof element._extendedByPrototype == 'undefined') {
+          var t = element.tagName;
+          if (t && (/^(?:object|applet|embed)$/i.test(t))) {
+            extendElementWith(element, Element.Methods);
+            extendElementWith(element, Element.Methods.Simulated);
+            extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]);
+          }
+        }
+        return element;
+      }
+    }
+    return Prototype.K;
+  }
+
+  var Methods = { }, ByTag = Element.Methods.ByTag;
+
+  var extend = Object.extend(function(element) {
+    if (!element || typeof element._extendedByPrototype != 'undefined' ||
+        element.nodeType != 1 || element == window) return element;
+
+    var methods = Object.clone(Methods),
+        tagName = element.tagName.toUpperCase();
+
+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+    extendElementWith(element, methods);
+
+    element._extendedByPrototype = Prototype.emptyFunction;
+    return element;
+
+  }, {
+    refresh: function() {
+      if (!Prototype.BrowserFeatures.ElementExtensions) {
+        Object.extend(Methods, Element.Methods);
+        Object.extend(Methods, Element.Methods.Simulated);
+      }
+    }
+  });
+
+  extend.refresh();
+  return extend;
+})();
+
+Element.hasAttribute = function(element, attribute) {
+  if (element.hasAttribute) return element.hasAttribute(attribute);
+  return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      "FORM":     Object.clone(Form.Methods),
+      "INPUT":    Object.clone(Form.Element.Methods),
+      "SELECT":   Object.clone(Form.Element.Methods),
+      "TEXTAREA": Object.clone(Form.Element.Methods)
+    });
+  }
+
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
+
+  if (!tagName) Object.extend(Element.Methods, methods || { });
+  else {
+    if (Object.isArray(tagName)) tagName.each(extend);
+    else extend(tagName);
+  }
+
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = { };
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
+
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!Object.isFunction(value)) continue;
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = value.methodize();
+    }
+  }
+
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+      "FrameSet", "IFRAME": "IFrame"
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    var element = document.createElement(tagName);
+    var proto = element['__proto__'] || element.constructor.prototype;
+    element = null;
+    return proto;
+  }
+
+  var elementPrototype = window.HTMLElement ? HTMLElement.prototype :
+   Element.prototype;
+
+  if (F.ElementExtensions) {
+    copy(Element.Methods, elementPrototype);
+    copy(Element.Methods.Simulated, elementPrototype, true);
+  }
+
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (Object.isUndefined(klass)) continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
+
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+
+  if (Element.extend.refresh) Element.extend.refresh();
+  Element.cache = { };
+};
+
+
+document.viewport = {
+
+  getDimensions: function() {
+    return { width: this.getWidth(), height: this.getHeight() };
+  },
+
+  getScrollOffsets: function() {
+    return Element._returnOffset(
+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+      window.pageYOffset || document.documentElement.scrollTop  || document.body.scrollTop);
+  }
+};
+
+(function(viewport) {
+  var B = Prototype.Browser, doc = document, element, property = {};
+
+  function getRootElement() {
+    if (B.WebKit && !doc.evaluate)
+      return document;
+
+    if (B.Opera && window.parseFloat(window.opera.version()) < 9.5)
+      return document.body;
+
+    return document.documentElement;
+  }
+
+  function define(D) {
+    if (!element) element = getRootElement();
+
+    property[D] = 'client' + D;
+
+    viewport['get' + D] = function() { return element[property[D]] };
+    return viewport['get' + D]();
+  }
+
+  viewport.getWidth  = define.curry('Width');
+
+  viewport.getHeight = define.curry('Height');
+})(document.viewport);
+
+
+Element.Storage = {
+  UID: 1
+};
+
+Element.addMethods({
+  getStorage: function(element) {
+    if (!(element = $(element))) return;
+
+    var uid;
+    if (element === window) {
+      uid = 0;
+    } else {
+      if (typeof element._prototypeUID === "undefined")
+        element._prototypeUID = [Element.Storage.UID++];
+      uid = element._prototypeUID[0];
+    }
+
+    if (!Element.Storage[uid])
+      Element.Storage[uid] = $H();
+
+    return Element.Storage[uid];
+  },
+
+  store: function(element, key, value) {
+    if (!(element = $(element))) return;
+
+    if (arguments.length === 2) {
+      Element.getStorage(element).update(key);
+    } else {
+      Element.getStorage(element).set(key, value);
+    }
+
+    return element;
+  },
+
+  retrieve: function(element, key, defaultValue) {
+    if (!(element = $(element))) return;
+    var hash = Element.getStorage(element), value = hash.get(key);
+
+    if (Object.isUndefined(value)) {
+      hash.set(key, defaultValue);
+      value = defaultValue;
+    }
+
+    return value;
+  },
+
+  clone: function(element, deep) {
+    if (!(element = $(element))) return;
+    var clone = element.cloneNode(deep);
+    clone._prototypeUID = void 0;
+    if (deep) {
+      var descendants = Element.select(clone, '*'),
+          i = descendants.length;
+      while (i--) {
+        descendants[i]._prototypeUID = void 0;
+      }
+    }
+    return Element.extend(clone);
+  }
+});
+/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license.  Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create({
+  initialize: function(expression) {
+    this.expression = expression.strip();
+
+    if (this.shouldUseSelectorsAPI()) {
+      this.mode = 'selectorsAPI';
+    } else if (this.shouldUseXPath()) {
+      this.mode = 'xpath';
+      this.compileXPathMatcher();
+    } else {
+      this.mode = "normal";
+      this.compileMatcher();
+    }
+
+  },
+
+  shouldUseXPath: (function() {
+
+    var IS_DESCENDANT_SELECTOR_BUGGY = (function(){
+      var isBuggy = false;
+      if (document.evaluate && window.XPathResult) {
+        var el = document.createElement('div');
+        el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>';
+
+        var xpath = ".//*[local-name()='ul' or local-name()='UL']" +
+          "//*[local-name()='li' or local-name()='LI']";
+
+        var result = document.evaluate(xpath, el, null,
+          XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+
+        isBuggy = (result.snapshotLength !== 2);
+        el = null;
+      }
+      return isBuggy;
+    })();
+
+    return function() {
+      if (!Prototype.BrowserFeatures.XPath) return false;
+
+      var e = this.expression;
+
+      if (Prototype.Browser.WebKit &&
+       (e.include("-of-type") || e.include(":empty")))
+        return false;
+
+      if ((/(\[[\w-]*?:|:checked)/).test(e))
+        return false;
+
+      if (IS_DESCENDANT_SELECTOR_BUGGY) return false;
+
+      return true;
+    }
+
+  })(),
+
+  shouldUseSelectorsAPI: function() {
+    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
+
+    if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false;
+
+    if (!Selector._div) Selector._div = new Element('div');
+
+    try {
+      Selector._div.querySelector(this.expression);
+    } catch(e) {
+      return false;
+    }
+
+    return true;
+  },
+
+  compileMatcher: function() {
+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+        c = Selector.criteria, le, p, m, len = ps.length, name;
+
+    if (Selector._cache[e]) {
+      this.matcher = Selector._cache[e];
+      return;
+    }
+
+    this.matcher = ["this.matcher = function(root) {",
+                    "var r = root, h = Selector.handlers, c = false, n;"];
+
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i = 0; i<len; i++) {
+        p = ps[i].re;
+        name = ps[i].name;
+        if (m = e.match(p)) {
+          this.matcher.push(Object.isFunction(c[name]) ? c[name](m) :
+            new Template(c[name]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.matcher.push("return h.unique(n);\n}");
+    eval(this.matcher.join('\n'));
+    Selector._cache[this.expression] = this.matcher;
+  },
+
+  compileXPathMatcher: function() {
+    var e = this.expression, ps = Selector.patterns,
+        x = Selector.xpath, le, m, len = ps.length, name;
+
+    if (Selector._cache[e]) {
+      this.xpath = Selector._cache[e]; return;
+    }
+
+    this.matcher = ['.//*'];
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i = 0; i<len; i++) {
+        name = ps[i].name;
+        if (m = e.match(ps[i].re)) {
+          this.matcher.push(Object.isFunction(x[name]) ? x[name](m) :
+            new Template(x[name]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.xpath = this.matcher.join('');
+    Selector._cache[this.expression] = this.xpath;
+  },
+
+  findElements: function(root) {
+    root = root || document;
+    var e = this.expression, results;
+
+    switch (this.mode) {
+      case 'selectorsAPI':
+        if (root !== document) {
+          var oldId = root.id, id = $(root).identify();
+          id = id.replace(/([\.:])/g, "\\$1");
+          e = "#" + id + " " + e;
+        }
+
+        results = $A(root.querySelectorAll(e)).map(Element.extend);
+        root.id = oldId;
+
+        return results;
+      case 'xpath':
+        return document._getElementsByXPath(this.xpath, root);
+      default:
+       return this.matcher(root);
+    }
+  },
+
+  match: function(element) {
+    this.tokens = [];
+
+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
+    var le, p, m, len = ps.length, name;
+
+    while (e && le !== e && (/\S/).test(e)) {
+      le = e;
+      for (var i = 0; i<len; i++) {
+        p = ps[i].re;
+        name = ps[i].name;
+        if (m = e.match(p)) {
+          if (as[name]) {
+            this.tokens.push([name, Object.clone(m)]);
+            e = e.replace(m[0], '');
+          } else {
+            return this.findElements(document).include(element);
+          }
+        }
+      }
+    }
+
+    var match = true, name, matches;
+    for (var i = 0, token; token = this.tokens[i]; i++) {
+      name = token[0], matches = token[1];
+      if (!Selector.assertions[name](element, matches)) {
+        match = false; break;
+      }
+    }
+
+    return match;
+  },
+
+  toString: function() {
+    return this.expression;
+  },
+
+  inspect: function() {
+    return "#<Selector:" + this.expression.inspect() + ">";
+  }
+});
+
+if (Prototype.BrowserFeatures.SelectorsAPI &&
+ document.compatMode === 'BackCompat') {
+  Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){
+    var div = document.createElement('div'),
+     span = document.createElement('span');
+
+    div.id = "prototype_test_id";
+    span.className = 'Test';
+    div.appendChild(span);
+    var isIgnored = (div.querySelector('#prototype_test_id .test') !== null);
+    div = span = null;
+    return isIgnored;
+  })();
+}
+
+Object.extend(Selector, {
+  _cache: { },
+
+  xpath: {
+    descendant:   "//*",
+    child:        "/*",
+    adjacent:     "/following-sibling::*[1]",
+    laterSibling: '/following-sibling::*',
+    tagName:      function(m) {
+      if (m[1] == '*') return '';
+      return "[local-name()='" + m[1].toLowerCase() +
+             "' or local-name()='" + m[1].toUpperCase() + "']";
+    },
+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
+    id:           "[@id='#{1}']",
+    attrPresence: function(m) {
+      m[1] = m[1].toLowerCase();
+      return new Template("[@#{1}]").evaluate(m);
+    },
+    attr: function(m) {
+      m[1] = m[1].toLowerCase();
+      m[3] = m[5] || m[6];
+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+    },
+    pseudo: function(m) {
+      var h = Selector.xpath.pseudos[m[1]];
+      if (!h) return '';
+      if (Object.isFunction(h)) return h(m);
+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+    },
+    operators: {
+      '=':  "[@#{1}='#{3}']",
+      '!=': "[@#{1}!='#{3}']",
+      '^=': "[starts-with(@#{1}, '#{3}')]",
+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
+      '*=': "[contains(@#{1}, '#{3}')]",
+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
+    },
+    pseudos: {
+      'first-child': '[not(preceding-sibling::*)]',
+      'last-child':  '[not(following-sibling::*)]',
+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
+      'empty':       "[count(*) = 0 and (count(text()) = 0)]",
+      'checked':     "[@checked]",
+      'disabled':    "[(@disabled) and (@type!='hidden')]",
+      'enabled':     "[not(@disabled) and (@type!='hidden')]",
+      'not': function(m) {
+        var e = m[6], p = Selector.patterns,
+            x = Selector.xpath, le, v, len = p.length, name;
+
+        var exclusion = [];
+        while (e && le != e && (/\S/).test(e)) {
+          le = e;
+          for (var i = 0; i<len; i++) {
+            name = p[i].name
+            if (m = e.match(p[i].re)) {
+              v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m);
+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
+              e = e.replace(m[0], '');
+              break;
+            }
+          }
+        }
+        return "[not(" + exclusion.join(" and ") + ")]";
+      },
+      'nth-child':      function(m) {
+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+      },
+      'nth-last-child': function(m) {
+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+      },
+      'nth-of-type':    function(m) {
+        return Selector.xpath.pseudos.nth("position() ", m);
+      },
+      'nth-last-of-type': function(m) {
+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+      },
+      'first-of-type':  function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+      },
+      'last-of-type':   function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+      },
+      'only-of-type':   function(m) {
+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+      },
+      nth: function(fragment, m) {
+        var mm, formula = m[6], predicate;
+        if (formula == 'even') formula = '2n+0';
+        if (formula == 'odd')  formula = '2n+1';
+        if (mm = formula.match(/^(\d+)$/)) // digit only
+          return '[' + fragment + "= " + mm[1] + ']';
+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+          if (mm[1] == "-") mm[1] = -1;
+          var a = mm[1] ? Number(mm[1]) : 1;
+          var b = mm[2] ? Number(mm[2]) : 0;
+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
+          "((#{fragment} - #{b}) div #{a} >= 0)]";
+          return new Template(predicate).evaluate({
+            fragment: fragment, a: a, b: b });
+        }
+      }
+    }
+  },
+
+  criteria: {
+    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
+    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
+    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
+    attr: function(m) {
+      m[3] = (m[5] || m[6]);
+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
+    },
+    pseudo: function(m) {
+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
+    },
+    descendant:   'c = "descendant";',
+    child:        'c = "child";',
+    adjacent:     'c = "adjacent";',
+    laterSibling: 'c = "laterSibling";'
+  },
+
+  patterns: [
+    { name: 'laterSibling', re: /^\s*~\s*/ },
+    { name: 'child',        re: /^\s*>\s*/ },
+    { name: 'adjacent',     re: /^\s*\+\s*/ },
+    { name: 'descendant',   re: /^\s/ },
+
+    { name: 'tagName',      re: /^\s*(\*|[\w\-]+)(\b|$)?/ },
+    { name: 'id',           re: /^#([\w\-\*]+)(\b|$)/ },
+    { name: 'className',    re: /^\.([\w\-\*]+)(\b|$)/ },
+    { name: 'pseudo',       re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ },
+    { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ },
+    { name: 'attr',         re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }
+  ],
+
+  assertions: {
+    tagName: function(element, matches) {
+      return matches[1].toUpperCase() == element.tagName.toUpperCase();
+    },
+
+    className: function(element, matches) {
+      return Element.hasClassName(element, matches[1]);
+    },
+
+    id: function(element, matches) {
+      return element.id === matches[1];
+    },
+
+    attrPresence: function(element, matches) {
+      return Element.hasAttribute(element, matches[1]);
+    },
+
+    attr: function(element, matches) {
+      var nodeValue = Element.readAttribute(element, matches[1]);
+      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
+    }
+  },
+
+  handlers: {
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        a.push(node);
+      return a;
+    },
+
+    mark: function(nodes) {
+      var _true = Prototype.emptyFunction;
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = _true;
+      return nodes;
+    },
+
+    unmark: (function(){
+
+      var PROPERTIES_ATTRIBUTES_MAP = (function(){
+        var el = document.createElement('div'),
+            isBuggy = false,
+            propName = '_countedByPrototype',
+            value = 'x'
+        el[propName] = value;
+        isBuggy = (el.getAttribute(propName) === value);
+        el = null;
+        return isBuggy;
+      })();
+
+      return PROPERTIES_ATTRIBUTES_MAP ?
+        function(nodes) {
+          for (var i = 0, node; node = nodes[i]; i++)
+            node.removeAttribute('_countedByPrototype');
+          return nodes;
+        } :
+        function(nodes) {
+          for (var i = 0, node; node = nodes[i]; i++)
+            node._countedByPrototype = void 0;
+          return nodes;
+        }
+    })(),
+
+    index: function(parentNode, reverse, ofType) {
+      parentNode._countedByPrototype = Prototype.emptyFunction;
+      if (reverse) {
+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
+          var node = nodes[i];
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+        }
+      } else {
+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+      }
+    },
+
+    unique: function(nodes) {
+      if (nodes.length == 0) return nodes;
+      var results = [], n;
+      for (var i = 0, l = nodes.length; i < l; i++)
+        if (typeof (n = nodes[i])._countedByPrototype == 'undefined') {
+          n._countedByPrototype = Prototype.emptyFunction;
+          results.push(Element.extend(n));
+        }
+      return Selector.handlers.unmark(results);
+    },
+
+    descendant: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, node.getElementsByTagName('*'));
+      return results;
+    },
+
+    child: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        for (var j = 0, child; child = node.childNodes[j]; j++)
+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
+      }
+      return results;
+    },
+
+    adjacent: function(nodes) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        var next = this.nextElementSibling(node);
+        if (next) results.push(next);
+      }
+      return results;
+    },
+
+    laterSibling: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, Element.nextSiblings(node));
+      return results;
+    },
+
+    nextElementSibling: function(node) {
+      while (node = node.nextSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    previousElementSibling: function(node) {
+      while (node = node.previousSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    tagName: function(nodes, root, tagName, combinator) {
+      var uTagName = tagName.toUpperCase();
+      var results = [], h = Selector.handlers;
+      if (nodes) {
+        if (combinator) {
+          if (combinator == "descendant") {
+            for (var i = 0, node; node = nodes[i]; i++)
+              h.concat(results, node.getElementsByTagName(tagName));
+            return results;
+          } else nodes = this[combinator](nodes);
+          if (tagName == "*") return nodes;
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName.toUpperCase() === uTagName) results.push(node);
+        return results;
+      } else return root.getElementsByTagName(tagName);
+    },
+
+    id: function(nodes, root, id, combinator) {
+      var targetNode = $(id), h = Selector.handlers;
+
+      if (root == document) {
+        if (!targetNode) return [];
+        if (!nodes) return [targetNode];
+      } else {
+        if (!root.sourceIndex || root.sourceIndex < 1) {
+          var nodes = root.getElementsByTagName('*');
+          for (var j = 0, node; node = nodes[j]; j++) {
+            if (node.id === id) return [node];
+          }
+        }
+      }
+
+      if (nodes) {
+        if (combinator) {
+          if (combinator == 'child') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (targetNode.parentNode == node) return [targetNode];
+          } else if (combinator == 'descendant') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Element.descendantOf(targetNode, node)) return [targetNode];
+          } else if (combinator == 'adjacent') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Selector.handlers.previousElementSibling(targetNode) == node)
+                return [targetNode];
+          } else nodes = h[combinator](nodes);
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node == targetNode) return [targetNode];
+        return [];
+      }
+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+    },
+
+    className: function(nodes, root, className, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      return Selector.handlers.byClassName(nodes, root, className);
+    },
+
+    byClassName: function(nodes, root, className) {
+      if (!nodes) nodes = Selector.handlers.descendant([root]);
+      var needle = ' ' + className + ' ';
+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+        nodeClassName = node.className;
+        if (nodeClassName.length == 0) continue;
+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+          results.push(node);
+      }
+      return results;
+    },
+
+    attrPresence: function(nodes, root, attr, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var results = [];
+      for (var i = 0, node; node = nodes[i]; i++)
+        if (Element.hasAttribute(node, attr)) results.push(node);
+      return results;
+    },
+
+    attr: function(nodes, root, attr, value, operator, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var handler = Selector.operators[operator], results = [];
+      for (var i = 0, node; node = nodes[i]; i++) {
+        var nodeValue = Element.readAttribute(node, attr);
+        if (nodeValue === null) continue;
+        if (handler(nodeValue, value)) results.push(node);
+      }
+      return results;
+    },
+
+    pseudo: function(nodes, name, value, root, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      return Selector.pseudos[name](nodes, value, root);
+    }
+  },
+
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    getIndices: function(a, b, total) {
+      if (a == 0) return b > 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._countedByPrototype) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == "-") m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j < l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (node.tagName == '!' || node.firstChild) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._countedByPrototype) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled && (!node.type || node.type !== 'hidden'))
+          results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
+    }
+  },
+
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
+    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
+    '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
+     '-').include('-' + (v || "").toUpperCase() + '-'); }
+  },
+
+  split: function(expression) {
+    var expressions = [];
+    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    return expressions;
+  },
+
+  matchElements: function(elements, expression) {
+    var matches = $$(expression), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._countedByPrototype) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
+
+  findElement: function(elements, expression, index) {
+    if (Object.isNumber(expression)) {
+      index = expression; expression = false;
+    }
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    expressions = Selector.split(expressions.join(','));
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i < l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l > 1) ? h.unique(results) : results;
+  }
+});
+
+if (Prototype.Browser.IE) {
+  Object.extend(Selector.handlers, {
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        if (node.tagName !== "!") a.push(node);
+      return a;
+    }
+  });
+}
+
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
+
+var Form = {
+  reset: function(form) {
+    form = $(form);
+    form.reset();
+    return form;
+  },
+
+  serializeElements: function(elements, options) {
+    if (typeof options != 'object') options = { hash: !!options };
+    else if (Object.isUndefined(options.hash)) options.hash = true;
+    var key, value, submitted = false, submit = options.submit;
+
+    var data = elements.inject({ }, function(result, element) {
+      if (!element.disabled && element.name) {
+        key = element.name; value = $(element).getValue();
+        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
+            submit !== false && (!submit || key == submit) && (submitted = true)))) {
+          if (key in result) {
+            if (!Object.isArray(result[key])) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
+
+    return options.hash ? data : Object.toQueryString(data);
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, options) {
+    return Form.serializeElements(Form.getElements(form), options);
+  },
+
+  getElements: function(form) {
+    var elements = $(form).getElementsByTagName('*'),
+        element,
+        arr = [ ],
+        serializers = Form.Element.Serializers;
+    for (var i = 0; element = elements[i]; i++) {
+      arr.push(element);
+    }
+    return arr.inject([], function(elements, child) {
+      if (serializers[child.tagName.toLowerCase()])
+        elements.push(Element.extend(child));
+      return elements;
+    })
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
+
+    if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
+      var input = inputs[i];
+      if ((typeName && input.type != typeName) || (name && input.name != name))
+        continue;
+      matchingInputs.push(Element.extend(input));
+    }
+
+    return matchingInputs;
+  },
+
+  disable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('disable');
+    return form;
+  },
+
+  enable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
+  },
+
+  findFirstElement: function(form) {
+    var elements = $(form).getElements().findAll(function(element) {
+      return 'hidden' != element.type && !element.disabled;
+    });
+    var firstByIndex = elements.findAll(function(element) {
+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+    }).sortBy(function(element) { return element.tabIndex }).first();
+
+    return firstByIndex ? firstByIndex : elements.find(function(element) {
+      return /^(?:input|select|textarea)$/i.test(element.tagName);
+    });
+  },
+
+  focusFirstElement: function(form) {
+    form = $(form);
+    form.findFirstElement().activate();
+    return form;
+  },
+
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || { });
+
+    var params = options.parameters, action = form.readAttribute('action') || '';
+    if (action.blank()) action = window.location.href;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (Object.isString(params)) params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') && !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(action, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+
+Form.Element = {
+  focus: function(element) {
+    $(element).focus();
+    return element;
+  },
+
+  select: function(element) {
+    $(element).select();
+    return element;
+  }
+};
+
+Form.Element.Methods = {
+
+  serialize: function(element) {
+    element = $(element);
+    if (!element.disabled && element.name) {
+      var value = element.getValue();
+      if (value != undefined) {
+        var pair = { };
+        pair[element.name] = value;
+        return Object.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    return Form.Element.Serializers[method](element);
+  },
+
+  setValue: function(element, value) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    Form.Element.Serializers[method](element, value);
+    return element;
+  },
+
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
+
+  present: function(element) {
+    return $(element).value != '';
+  },
+
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select && (element.tagName.toLowerCase() != 'input' ||
+          !(/^(?:button|reset|submit)$/i.test(element.type))))
+        element.select();
+    } catch (e) { }
+    return element;
+  },
+
+  disable: function(element) {
+    element = $(element);
+    element.disabled = true;
+    return element;
+  },
+
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = {
+  input: function(element, value) {
+    switch (element.type.toLowerCase()) {
+      case 'checkbox':
+      case 'radio':
+        return Form.Element.Serializers.inputSelector(element, value);
+      default:
+        return Form.Element.Serializers.textarea(element, value);
+    }
+  },
+
+  inputSelector: function(element, value) {
+    if (Object.isUndefined(value)) return element.checked ? element.value : null;
+    else element.checked = !!value;
+  },
+
+  textarea: function(element, value) {
+    if (Object.isUndefined(value)) return element.value;
+    else element.value = value;
+  },
+
+  select: function(element, value) {
+    if (Object.isUndefined(value))
+      return this[element.type == 'select-one' ?
+        'selectOne' : 'selectMany'](element);
+    else {
+      var opt, currentValue, single = !Object.isArray(value);
+      for (var i = 0, length = element.length; i < length; i++) {
+        opt = element.options[i];
+        currentValue = this.optionValue(opt);
+        if (single) {
+          if (currentValue == value) {
+            opt.selected = true;
+            return;
+          }
+        }
+        else opt.selected = value.include(currentValue);
+      }
+    }
+  },
+
+  selectOne: function(element) {
+    var index = element.selectedIndex;
+    return index >= 0 ? this.optionValue(element.options[index]) : null;
+  },
+
+  selectMany: function(element) {
+    var values, length = element.length;
+    if (!length) return null;
+
+    for (var i = 0, values = []; i < length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) values.push(this.optionValue(opt));
+    }
+    return values;
+  },
+
+  optionValue: function(opt) {
+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+  initialize: function($super, element, frequency, callback) {
+    $super(callback, frequency);
+    this.element   = $(element);
+    this.lastValue = this.getValue();
+  },
+
+  execute: function() {
+    var value = this.getValue();
+    if (Object.isString(this.lastValue) && Object.isString(value) ?
+        this.lastValue != value : String(this.lastValue) != String(value)) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+  initialize: function(element, callback) {
+    this.element  = $(element);
+    this.callback = callback;
+
+    this.lastValue = this.getValue();
+    if (this.element.tagName.toLowerCase() == 'form')
+      this.registerFormCallbacks();
+    else
+      this.registerCallback(this.element);
+  },
+
+  onElementEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  },
+
+  registerFormCallbacks: function() {
+    Form.getElements(this.element).each(this.registerCallback, this);
+  },
+
+  registerCallback: function(element) {
+    if (element.type) {
+      switch (element.type.toLowerCase()) {
+        case 'checkbox':
+        case 'radio':
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
+          break;
+        default:
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
+          break;
+      }
+    }
+  }
+});
+
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+(function() {
+
+  var Event = {
+    KEY_BACKSPACE: 8,
+    KEY_TAB:       9,
+    KEY_RETURN:   13,
+    KEY_ESC:      27,
+    KEY_LEFT:     37,
+    KEY_UP:       38,
+    KEY_RIGHT:    39,
+    KEY_DOWN:     40,
+    KEY_DELETE:   46,
+    KEY_HOME:     36,
+    KEY_END:      35,
+    KEY_PAGEUP:   33,
+    KEY_PAGEDOWN: 34,
+    KEY_INSERT:   45,
+
+    cache: {}
+  };
+
+  var docEl = document.documentElement;
+  var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
+    && 'onmouseleave' in docEl;
+
+  var _isButton;
+  if (Prototype.Browser.IE) {
+    var buttonMap = { 0: 1, 1: 4, 2: 2 };
+    _isButton = function(event, code) {
+      return event.button === buttonMap[code];
+    };
+  } else if (Prototype.Browser.WebKit) {
+    _isButton = function(event, code) {
+      switch (code) {
+        case 0: return event.which == 1 && !event.metaKey;
+        case 1: return event.which == 1 && event.metaKey;
+        default: return false;
+      }
+    };
+  } else {
+    _isButton = function(event, code) {
+      return event.which ? (event.which === code + 1) : (event.button === code);
+    };
+  }
+
+  function isLeftClick(event)   { return _isButton(event, 0) }
+
+  function isMiddleClick(event) { return _isButton(event, 1) }
+
+  function isRightClick(event)  { return _isButton(event, 2) }
+
+  function element(event) {
+    event = Event.extend(event);
+
+    var node = event.target, type = event.type,
+     currentTarget = event.currentTarget;
+
+    if (currentTarget && currentTarget.tagName) {
+      if (type === 'load' || type === 'error' ||
+        (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
+          && currentTarget.type === 'radio'))
+            node = currentTarget;
+    }
+
+    if (node.nodeType == Node.TEXT_NODE)
+      node = node.parentNode;
+
+    return Element.extend(node);
+  }
+
+  function findElement(event, expression) {
+    var element = Event.element(event);
+    if (!expression) return element;
+    var elements = [element].concat(element.ancestors());
+    return Selector.findElement(elements, expression, 0);
+  }
+
+  function pointer(event) {
+    return { x: pointerX(event), y: pointerY(event) };
+  }
+
+  function pointerX(event) {
+    var docElement = document.documentElement,
+     body = document.body || { scrollLeft: 0 };
+
+    return event.pageX || (event.clientX +
+      (docElement.scrollLeft || body.scrollLeft) -
+      (docElement.clientLeft || 0));
+  }
+
+  function pointerY(event) {
+    var docElement = document.documentElement,
+     body = document.body || { scrollTop: 0 };
+
+    return  event.pageY || (event.clientY +
+       (docElement.scrollTop || body.scrollTop) -
+       (docElement.clientTop || 0));
+  }
+
+
+  function stop(event) {
+    Event.extend(event);
+    event.preventDefault();
+    event.stopPropagation();
+
+    event.stopped = true;
+  }
+
+  Event.Methods = {
+    isLeftClick: isLeftClick,
+    isMiddleClick: isMiddleClick,
+    isRightClick: isRightClick,
+
+    element: element,
+    findElement: findElement,
+
+    pointer: pointer,
+    pointerX: pointerX,
+    pointerY: pointerY,
+
+    stop: stop
+  };
+
+
+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+    m[name] = Event.Methods[name].methodize();
+    return m;
+  });
+
+  if (Prototype.Browser.IE) {
+    function _relatedTarget(event) {
+      var element;
+      switch (event.type) {
+        case 'mouseover': element = event.fromElement; break;
+        case 'mouseout':  element = event.toElement;   break;
+        default: return null;
+      }
+      return Element.extend(element);
+    }
+
+    Object.extend(methods, {
+      stopPropagation: function() { this.cancelBubble = true },
+      preventDefault:  function() { this.returnValue = false },
+      inspect: function() { return '[object Event]' }
+    });
+
+    Event.extend = function(event, element) {
+      if (!event) return false;
+      if (event._extendedByPrototype) return event;
+
+      event._extendedByPrototype = Prototype.emptyFunction;
+      var pointer = Event.pointer(event);
+
+      Object.extend(event, {
+        target: event.srcElement || element,
+        relatedTarget: _relatedTarget(event),
+        pageX:  pointer.x,
+        pageY:  pointer.y
+      });
+
+      return Object.extend(event, methods);
+    };
+  } else {
+    Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__;
+    Object.extend(Event.prototype, methods);
+    Event.extend = Prototype.K;
+  }
+
+  function _createResponder(element, eventName, handler) {
+    var registry = Element.retrieve(element, 'prototype_event_registry');
+
+    if (Object.isUndefined(registry)) {
+      CACHE.push(element);
+      registry = Element.retrieve(element, 'prototype_event_registry', $H());
+    }
+
+    var respondersForEvent = registry.get(eventName);
+    if (Object.isUndefined(respondersForEvent)) {
+      respondersForEvent = [];
+      registry.set(eventName, respondersForEvent);
+    }
+
+    if (respondersForEvent.pluck('handler').include(handler)) return false;
+
+    var responder;
+    if (eventName.include(":")) {
+      responder = function(event) {
+        if (Object.isUndefined(event.eventName))
+          return false;
+
+        if (event.eventName !== eventName)
+          return false;
+
+        Event.extend(event, element);
+        handler.call(element, event);
+      };
+    } else {
+      if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED &&
+       (eventName === "mouseenter" || eventName === "mouseleave")) {
+        if (eventName === "mouseenter" || eventName === "mouseleave") {
+          responder = function(event) {
+            Event.extend(event, element);
+
+            var parent = event.relatedTarget;
+            while (parent && parent !== element) {
+              try { parent = parent.parentNode; }
+              catch(e) { parent = element; }
+            }
+
+            if (parent === element) return;
+
+            handler.call(element, event);
+          };
+        }
+      } else {
+        responder = function(event) {
+          Event.extend(event, element);
+          handler.call(element, event);
+        };
+      }
+    }
+
+    responder.handler = handler;
+    respondersForEvent.push(responder);
+    return responder;
+  }
+
+  function _destroyCache() {
+    for (var i = 0, length = CACHE.length; i < length; i++) {
+      Event.stopObserving(CACHE[i]);
+      CACHE[i] = null;
+    }
+  }
+
+  var CACHE = [];
+
+  if (Prototype.Browser.IE)
+    window.attachEvent('onunload', _destroyCache);
+
+  if (Prototype.Browser.WebKit)
+    window.addEventListener('unload', Prototype.emptyFunction, false);
+
+
+  var _getDOMEventName = Prototype.K;
+
+  if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
+    _getDOMEventName = function(eventName) {
+      var translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
+      return eventName in translations ? translations[eventName] : eventName;
+    };
+  }
+
+  function observe(element, eventName, handler) {
+    element = $(element);
+
+    var responder = _createResponder(element, eventName, handler);
+
+    if (!responder) return element;
+
+    if (eventName.include(':')) {
+      if (element.addEventListener)
+        element.addEventListener("dataavailable", responder, false);
+      else {
+        element.attachEvent("ondataavailable", responder);
+        element.attachEvent("onfilterchange", responder);
+      }
+    } else {
+      var actualEventName = _getDOMEventName(eventName);
+
+      if (element.addEventListener)
+        element.addEventListener(actualEventName, responder, false);
+      else
+        element.attachEvent("on" + actualEventName, responder);
+    }
+
+    return element;
+  }
+
+  function stopObserving(element, eventName, handler) {
+    element = $(element);
+
+    var registry = Element.retrieve(element, 'prototype_event_registry');
+
+    if (Object.isUndefined(registry)) return element;
+
+    if (eventName && !handler) {
+      var responders = registry.get(eventName);
+
+      if (Object.isUndefined(responders)) return element;
+
+      responders.each( function(r) {
+        Element.stopObserving(element, eventName, r.handler);
+      });
+      return element;
+    } else if (!eventName) {
+      registry.each( function(pair) {
+        var eventName = pair.key, responders = pair.value;
+
+        responders.each( function(r) {
+          Element.stopObserving(element, eventName, r.handler);
+        });
+      });
+      return element;
+    }
+
+    var responders = registry.get(eventName);
+
+    if (!responders) return;
+
+    var responder = responders.find( function(r) { return r.handler === handler; });
+    if (!responder) return element;
+
+    var actualEventName = _getDOMEventName(eventName);
+
+    if (eventName.include(':')) {
+      if (element.removeEventListener)
+        element.removeEventListener("dataavailable", responder, false);
+      else {
+        element.detachEvent("ondataavailable", responder);
+        element.detachEvent("onfilterchange",  responder);
+      }
+    } else {
+      if (element.removeEventListener)
+        element.removeEventListener(actualEventName, responder, false);
+      else
+        element.detachEvent('on' + actualEventName, responder);
+    }
+
+    registry.set(eventName, responders.without(responder));
+
+    return element;
+  }
+
+  function fire(element, eventName, memo, bubble) {
+    element = $(element);
+
+    if (Object.isUndefined(bubble))
+      bubble = true;
+
+    if (element == document && document.createEvent && !element.dispatchEvent)
+      element = document.documentElement;
+
+    var event;
+    if (document.createEvent) {
+      event = document.createEvent('HTMLEvents');
+      event.initEvent('dataavailable', true, true);
+    } else {
+      event = document.createEventObject();
+      event.eventType = bubble ? 'ondataavailable' : 'onfilterchange';
+    }
+
+    event.eventName = eventName;
+    event.memo = memo || { };
+
+    if (document.createEvent)
+      element.dispatchEvent(event);
+    else
+      element.fireEvent(event.eventType, event);
+
+    return Event.extend(event);
+  }
+
+
+  Object.extend(Event, Event.Methods);
+
+  Object.extend(Event, {
+    fire:          fire,
+    observe:       observe,
+    stopObserving: stopObserving
+  });
+
+  Element.addMethods({
+    fire:          fire,
+
+    observe:       observe,
+
+    stopObserving: stopObserving
+  });
+
+  Object.extend(document, {
+    fire:          fire.methodize(),
+
+    observe:       observe.methodize(),
+
+    stopObserving: stopObserving.methodize(),
+
+    loaded:        false
+  });
+
+  if (window.Event) Object.extend(window.Event, Event);
+  else window.Event = Event;
+})();
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */
+
+  var timer;
+
+  function fireContentLoadedEvent() {
+    if (document.loaded) return;
+    if (timer) window.clearTimeout(timer);
+    document.loaded = true;
+    document.fire('dom:loaded');
+  }
+
+  function checkReadyState() {
+    if (document.readyState === 'complete') {
+      document.stopObserving('readystatechange', checkReadyState);
+      fireContentLoadedEvent();
+    }
+  }
+
+  function pollDoScroll() {
+    try { document.documentElement.doScroll('left'); }
+    catch(e) {
+      timer = pollDoScroll.defer();
+      return;
+    }
+    fireContentLoadedEvent();
+  }
+
+  if (document.addEventListener) {
+    document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
+  } else {
+    document.observe('readystatechange', checkReadyState);
+    if (window == top)
+      timer = pollDoScroll.defer();
+  }
+
+  Event.observe(window, 'load', fireContentLoadedEvent);
+})();
+
+Element.addMethods();
+
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
+  },
+
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
+
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
+
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
+  }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+var Position = {
+  includeScrollOffsets: false,
+
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (y >= this.offset[1] &&
+            y <  this.offset[1] + element.offsetHeight &&
+            x >= this.offset[0] &&
+            x <  this.offset[0] + element.offsetWidth);
+  },
+
+  withinIncludingScrolloffsets: function(element, x, y) {
+    var offsetcache = Element.cumulativeScrollOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (this.ycomp >= this.offset[1] &&
+            this.ycomp <  this.offset[1] + element.offsetHeight &&
+            this.xcomp >= this.offset[0] &&
+            this.xcomp <  this.offset[0] + element.offsetWidth);
+  },
+
+  overlap: function(mode, element) {
+    if (!mode) return 0;
+    if (mode == 'vertical')
+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+        element.offsetHeight;
+    if (mode == 'horizontal')
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+        element.offsetWidth;
+  },
+
+
+  cumulativeOffset: Element.Methods.cumulativeOffset,
+
+  positionedOffset: Element.Methods.positionedOffset,
+
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
+
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
+  },
+
+  realOffset: Element.Methods.cumulativeScrollOffset,
+
+  offsetParent: Element.Methods.getOffsetParent,
+
+  page: Element.Methods.viewportOffset,
+
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames && !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+          (classNames && classNames.all(function(name) {
+            return !name.toString().blank() && cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
+    }
+    return elements;
+  };
+
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
+
+  toString: function() {
+    return $A(this).join(' ');
+  }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+/*--------------------------------------------------------------------------*/
Index: /branches/util-apps/DomainUtil/web-app/js/prototype/rico.js
===================================================================
--- /branches/util-apps/DomainUtil/web-app/js/prototype/rico.js	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/js/prototype/rico.js	(revision 766)
@@ -0,0 +1,2691 @@
+/**
+  *
+  *  Copyright 2005 Sabre Airline Solutions
+  *
+  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
+  *  file except in compliance with the License. You may obtain a copy of the License at
+  *
+  *         http://www.apache.org/licenses/LICENSE-2.0
+  *
+  *  Unless required by applicable law or agreed to in writing, software distributed under the
+  *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+  *  either express or implied. See the License for the specific language governing permissions
+  *  and limitations under the License.
+  **/
+
+
+//-------------------- rico.js
+var Rico = {
+  Version: '1.1-beta2'
+}
+
+Rico.ArrayExtensions = new Array();
+
+if (Object.prototype.extend) {
+   // in prototype.js...
+   Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Object.prototype.extend;
+}
+
+if (Array.prototype.push) {
+   // in prototype.js...
+   Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.push;
+}
+
+if (!Array.prototype.remove) {
+   Array.prototype.remove = function(dx) {
+      if( isNaN(dx) || dx > this.length )
+         return false;
+      for( var i=0,n=0; i<this.length; i++ )
+         if( i != dx )
+            this[n++]=this[i];
+      this.length-=1;
+   };
+  Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.remove;
+}
+
+if (!Array.prototype.removeItem) {
+   Array.prototype.removeItem = function(item) {
+      for ( var i = 0 ; i < this.length ; i++ )
+         if ( this[i] == item ) {
+            this.remove(i);
+            break;
+         }
+   };
+  Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.removeItem;
+}
+
+if (!Array.prototype.indices) {
+   Array.prototype.indices = function() {
+      var indexArray = new Array();
+      for ( index in this ) {
+         var ignoreThis = false;
+         for ( var i = 0 ; i < Rico.ArrayExtensions.length ; i++ ) {
+            if ( this[index] == Rico.ArrayExtensions[i] ) {
+               ignoreThis = true;
+               break;
+            }
+         }
+         if ( !ignoreThis )
+            indexArray[ indexArray.length ] = index;
+      }
+      return indexArray;
+   }
+  Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.indices;
+}
+
+// Create the loadXML method and xml getter for Mozilla
+if ( window.DOMParser &&
+	  window.XMLSerializer &&
+	  window.Node && Node.prototype && Node.prototype.__defineGetter__ ) {
+
+   if (!Document.prototype.loadXML) {
+      Document.prototype.loadXML = function (s) {
+         var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
+         while (this.hasChildNodes())
+            this.removeChild(this.lastChild);
+
+         for (var i = 0; i < doc2.childNodes.length; i++) {
+            this.appendChild(this.importNode(doc2.childNodes[i], true));
+         }
+      };
+	}
+
+	Document.prototype.__defineGetter__( "xml",
+	   function () {
+		   return (new XMLSerializer()).serializeToString(this);
+	   }
+	 );
+}
+
+document.getElementsByTagAndClassName = function(tagName, className) {
+  if ( tagName == null )
+     tagName = '*';
+
+  var children = document.getElementsByTagName(tagName) || document.all;
+  var elements = new Array();
+
+  if ( className == null )
+    return children;
+
+  for (var i = 0; i < children.length; i++) {
+    var child = children[i];
+    var classNames = child.className.split(' ');
+    for (var j = 0; j < classNames.length; j++) {
+      if (classNames[j] == className) {
+        elements.push(child);
+        break;
+      }
+    }
+  }
+
+  return elements;
+}
+
+
+//-------------------- ricoAccordion.js
+
+Rico.Accordion = Class.create();
+
+Rico.Accordion.prototype = {
+
+   initialize: function(container, options) {
+      this.container            = $(container);
+      this.lastExpandedTab      = null;
+      this.accordionTabs        = new Array();
+      this.setOptions(options);
+      this._attachBehaviors();
+
+      this.container.style.borderBottom = '1px solid ' + this.options.borderColor;
+
+      // set the initial visual state...
+      for ( var i=1 ; i < this.accordionTabs.length ; i++ )
+      {
+         this.accordionTabs[i].collapse();
+         this.accordionTabs[i].content.style.display = 'none';
+      }
+      this.lastExpandedTab = this.accordionTabs[0];
+      this.lastExpandedTab.content.style.height = this.options.panelHeight + "px";
+      this.lastExpandedTab.showExpanded();
+      this.lastExpandedTab.titleBar.style.fontWeight = this.options.expandedFontWeight;
+   },
+
+   setOptions: function(options) {
+      this.options = {
+         expandedBg          : '#63699c',
+         hoverBg             : '#63699c',
+         collapsedBg         : '#6b79a5',
+         expandedTextColor   : '#ffffff',
+         expandedFontWeight  : 'bold',
+         hoverTextColor      : '#ffffff',
+         collapsedTextColor  : '#ced7ef',
+         collapsedFontWeight : 'normal',
+         hoverTextColor      : '#ffffff',
+         borderColor         : '#1f669b',
+         panelHeight         : 200,
+         onHideTab           : null,
+         onShowTab           : null
+      }.extend(options || {});
+   },
+
+   showTabByIndex: function( anIndex, animate ) {
+      var doAnimate = arguments.length == 1 ? true : animate;
+      this.showTab( this.accordionTabs[anIndex], doAnimate );
+   },
+
+   showTab: function( accordionTab, animate ) {
+
+      var doAnimate = arguments.length == 1 ? true : animate;
+
+      if ( this.options.onHideTab )
+         this.options.onHideTab(this.lastExpandedTab);
+
+      this.lastExpandedTab.showCollapsed(); 
+      var accordion = this;
+      var lastExpandedTab = this.lastExpandedTab;
+
+      this.lastExpandedTab.content.style.height = (this.options.panelHeight - 1) + 'px';
+      accordionTab.content.style.display = '';
+
+      accordionTab.titleBar.style.fontWeight = this.options.expandedFontWeight;
+
+      if ( doAnimate ) {
+         new Effect.AccordionSize( this.lastExpandedTab.content,
+                                   accordionTab.content,
+                                   1,
+                                   this.options.panelHeight,
+                                   100, 10,
+                                   { complete: function() {accordion.showTabDone(lastExpandedTab)} } );
+         this.lastExpandedTab = accordionTab;
+      }
+      else {
+         this.lastExpandedTab.content.style.height = "1px";
+         accordionTab.content.style.height = this.options.panelHeight + "px";
+         this.lastExpandedTab = accordionTab;
+         this.showTabDone(lastExpandedTab);
+      }
+   },
+
+   showTabDone: function(collapsedTab) {
+      collapsedTab.content.style.display = 'none';
+      this.lastExpandedTab.showExpanded();
+      if ( this.options.onShowTab )
+         this.options.onShowTab(this.lastExpandedTab);
+   },
+
+   _attachBehaviors: function() {
+      var panels = this._getDirectChildrenByTag(this.container, 'DIV');
+      for ( var i = 0 ; i < panels.length ; i++ ) {
+
+         var tabChildren = this._getDirectChildrenByTag(panels[i],'DIV');
+         if ( tabChildren.length != 2 )
+            continue; // unexpected
+
+         var tabTitleBar   = tabChildren[0];
+         var tabContentBox = tabChildren[1];
+         this.accordionTabs.push( new Rico.Accordion.Tab(this,tabTitleBar,tabContentBox) );
+      }
+   },
+
+   _getDirectChildrenByTag: function(e, tagName) {
+      var kids = new Array();
+      var allKids = e.childNodes;
+      for( var i = 0 ; i < allKids.length ; i++ )
+         if ( allKids[i] && allKids[i].tagName && allKids[i].tagName == tagName )
+            kids.push(allKids[i]);
+      return kids;
+   }
+
+};
+
+Rico.Accordion.Tab = Class.create();
+
+Rico.Accordion.Tab.prototype = {
+
+   initialize: function(accordion, titleBar, content) {
+      this.accordion = accordion;
+      this.titleBar  = titleBar;
+      this.content   = content;
+      this._attachBehaviors();
+   },
+
+   collapse: function() {
+      this.showCollapsed();
+      this.content.style.height = "1px";
+   },
+
+   showCollapsed: function() {
+      this.expanded = false;
+      this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
+      this.titleBar.style.color           = this.accordion.options.collapsedTextColor;
+      this.titleBar.style.fontWeight      = this.accordion.options.collapsedFontWeight;
+      this.content.style.overflow = "hidden";
+   },
+
+   showExpanded: function() {
+      this.expanded = true;
+      this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
+      this.titleBar.style.color           = this.accordion.options.expandedTextColor;
+      this.content.style.overflow         = "visible";
+   },
+
+   titleBarClicked: function(e) {
+      if ( this.accordion.lastExpandedTab == this )
+         return;
+      this.accordion.showTab(this);
+   },
+
+   hover: function(e) {
+		this.titleBar.style.backgroundColor = this.accordion.options.hoverBg;
+		this.titleBar.style.color           = this.accordion.options.hoverTextColor;
+   },
+
+   unhover: function(e) {
+      if ( this.expanded ) {
+         this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
+         this.titleBar.style.color           = this.accordion.options.expandedTextColor;
+      }
+      else {
+         this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
+         this.titleBar.style.color           = this.accordion.options.collapsedTextColor;
+      }
+   },
+
+   _attachBehaviors: function() {
+      this.content.style.border = "1px solid " + this.accordion.options.borderColor;
+      this.content.style.borderTopWidth    = "0px";
+      this.content.style.borderBottomWidth = "0px";
+      this.content.style.margin            = "0px";
+
+      this.titleBar.onclick     = this.titleBarClicked.bindAsEventListener(this);
+      this.titleBar.onmouseover = this.hover.bindAsEventListener(this);
+      this.titleBar.onmouseout  = this.unhover.bindAsEventListener(this);
+   }
+
+};
+
+
+//-------------------- ricoAjaxEngine.js
+
+Rico.AjaxEngine = Class.create();
+
+Rico.AjaxEngine.prototype = {
+
+   initialize: function() {
+      this.ajaxElements = new Array();
+      this.ajaxObjects  = new Array();
+      this.requestURLS  = new Array();
+   },
+
+   registerAjaxElement: function( anId, anElement ) {
+      if ( arguments.length == 1 )
+         anElement = $(anId);
+      this.ajaxElements[anId] = anElement;
+   },
+
+   registerAjaxObject: function( anId, anObject ) {
+      this.ajaxObjects[anId] = anObject;
+   },
+
+   registerRequest: function (requestLogicalName, requestURL) {
+      this.requestURLS[requestLogicalName] = requestURL;
+   },
+
+   sendRequest: function(requestName) {
+      var requestURL = this.requestURLS[requestName];
+      if ( requestURL == null )
+         return;
+
+      var queryString = "";
+      
+      if ( arguments.length > 1 ) {
+      	 if(typeof(arguments[1]) == "object" && arguments[1].length != undefined) {
+	      	 queryString = this._createQueryString(arguments[1], 0);
+      	 }
+      	 else {
+	         queryString = this._createQueryString(arguments, 1);
+	     }         
+       }
+             
+      new Ajax.Request(requestURL, this._requestOptions(queryString));
+   },
+
+   sendRequestWithData: function(requestName, xmlDocument) {
+      var requestURL = this.requestURLS[requestName];
+      if ( requestURL == null )
+         return;
+
+      var queryString = "";
+      if ( arguments.length > 2 ) {
+      	 if(typeof(arguments[2]) == "object" && arguments[2].length != undefined) {
+	      	 queryString = this._createQueryString(arguments[2], 0);
+      	 }
+      	 else {
+	         queryString = this._createQueryString(arguments, 2);
+	     }         
+       }             
+
+      new Ajax.Request(requestURL + "?" + queryString, this._requestOptions(null,xmlDocument));
+   },
+
+   sendRequestAndUpdate: function(requestName,container,options) {
+      var requestURL = this.requestURLS[requestName];
+      if ( requestURL == null )
+         return;
+
+      var queryString = "";
+      if ( arguments.length > 3 ) {
+      	 if(typeof(arguments[3]) == "object" && arguments[3].length != undefined) {
+	      	 queryString = this._createQueryString(arguments[3], 0);
+      	 }
+      	 else {
+	         queryString = this._createQueryString(arguments, 3);
+	     }         
+       }  
+             
+      var updaterOptions = this._requestOptions(queryString);
+      updaterOptions.onComplete = null;
+      updaterOptions.extend(options);
+
+      new Ajax.Updater(container, requestURL, updaterOptions);
+   },
+
+   sendRequestWithDataAndUpdate: function(requestName,xmlDocument,container,options) {
+      var requestURL = this.requestURLS[requestName];
+      if ( requestURL == null )
+         return;
+
+      var queryString = "";
+      if ( arguments.length > 4 ) {
+      	 if(typeof(arguments[4]) == "object" && arguments[4].length != undefined) {
+	      	 queryString = this._createQueryString(arguments[4], 0);
+      	 }
+      	 else {
+	         queryString = this._createQueryString(arguments, 4);
+	     }         
+       }
+
+
+      var updaterOptions = this._requestOptions(queryString,xmlDocument);
+      updaterOptions.onComplete = null;
+      updaterOptions.extend(options);
+
+      new Ajax.Updater(container, requestURL + "?" + queryString, updaterOptions);
+   },
+
+   // Private -- not part of intended engine API --------------------------------------------------------------------
+
+   _requestOptions: function(queryString,xmlDoc) {
+      var self = this;
+
+      var requestHeaders = ['X-Rico-Version', Rico.Version ];
+      var sendMethod = "post"
+      if ( arguments[1] )
+         requestHeaders.push( 'Content-type', 'text/xml' );
+      else
+         sendMethod = "get";
+
+      return { requestHeaders: requestHeaders,
+               parameters:     queryString,
+               postBody:       arguments[1] ? xmlDoc : null,
+               method:         sendMethod,
+               onComplete:     self._onRequestComplete.bind(self) };
+   },
+
+   _createQueryString: function( theArgs, offset ) {
+   	  var self = this;
+      var queryString = ""
+      for ( var i = offset ; i < theArgs.length ; i++ ) {
+          if ( i != offset )
+            queryString += "&";
+
+          var anArg = theArgs[i];
+                  
+          if ( anArg.name != undefined && anArg.value != undefined ) {
+            queryString += anArg.name +  "=" + escape(anArg.value);
+          }
+          else {
+             var ePos  = anArg.indexOf('=');
+             var argName  = anArg.substring( 0, ePos );
+             var argValue = anArg.substring( ePos + 1 );
+             queryString += argName + "=" + escape(argValue);
+          }
+      }
+
+      return queryString;
+   },
+   _onRequestComplete : function(request) {
+
+      //!!TODO: error handling infrastructure?? 
+      if (request.status != 200)
+        return;
+
+      var response = request.responseXML.getElementsByTagName("ajax-response");
+      if (response == null || response.length != 1)
+         return;
+      this._processAjaxResponse( response[0].childNodes );
+   },
+
+   _processAjaxResponse: function( xmlResponseElements ) {
+      for ( var i = 0 ; i < xmlResponseElements.length ; i++ ) {
+         var responseElement = xmlResponseElements[i];
+
+         // only process nodes of type element.....
+         if ( responseElement.nodeType != 1 )
+            continue;
+
+         var responseType = responseElement.getAttribute("type");
+         var responseId   = responseElement.getAttribute("id");
+
+         if ( responseType == "object" )
+            this._processAjaxObjectUpdate( this.ajaxObjects[ responseId ], responseElement );
+         else if ( responseType == "element" )
+            this._processAjaxElementUpdate( this.ajaxElements[ responseId ], responseElement );
+         else
+            alert('unrecognized AjaxResponse type : ' + responseType );
+      }
+   },
+
+   _processAjaxObjectUpdate: function( ajaxObject, responseElement ) {
+      ajaxObject.ajaxUpdate( responseElement );
+   },
+
+   _processAjaxElementUpdate: function( ajaxElement, responseElement ) {
+      ajaxElement.innerHTML = RicoUtil.getContentAsString(responseElement);
+   }
+
+}
+
+var ajaxEngine = new Rico.AjaxEngine();
+
+
+//-------------------- ricoColor.js
+Rico.Color = Class.create();
+
+Rico.Color.prototype = {
+
+   initialize: function(red, green, blue) {
+      this.rgb = { r: red, g : green, b : blue };
+   },
+
+   setRed: function(r) {
+      this.rgb.r = r;
+   },
+
+   setGreen: function(g) {
+      this.rgb.g = g;
+   },
+
+   setBlue: function(b) {
+      this.rgb.b = b;
+   },
+
+   setHue: function(h) {
+
+      // get an HSB model, and set the new hue...
+      var hsb = this.asHSB();
+      hsb.h = h;
+
+      // convert back to RGB...
+      this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+   },
+
+   setSaturation: function(s) {
+      // get an HSB model, and set the new hue...
+      var hsb = this.asHSB();
+      hsb.s = s;
+
+      // convert back to RGB and set values...
+      this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+   },
+
+   setBrightness: function(b) {
+      // get an HSB model, and set the new hue...
+      var hsb = this.asHSB();
+      hsb.b = b;
+
+      // convert back to RGB and set values...
+      this.rgb = Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
+   },
+
+   darken: function(percent) {
+      var hsb  = this.asHSB();
+      this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
+   },
+
+   brighten: function(percent) {
+      var hsb  = this.asHSB();
+      this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
+   },
+
+   blend: function(other) {
+      this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
+      this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
+      this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
+   },
+
+   isBright: function() {
+      var hsb = this.asHSB();
+      return this.asHSB().b > 0.5;
+   },
+
+   isDark: function() {
+      return ! this.isBright();
+   },
+
+   asRGB: function() {
+      return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
+   },
+
+   asHex: function() {
+      return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart();
+   },
+
+   asHSB: function() {
+      return Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
+   },
+
+   toString: function() {
+      return this.asHex();
+   }
+
+};
+
+Rico.Color.createFromHex = function(hexCode) {
+
+   if ( hexCode.indexOf('#') == 0 )
+      hexCode = hexCode.substring(1);
+   var red   = hexCode.substring(0,2);
+   var green = hexCode.substring(2,4);
+   var blue  = hexCode.substring(4,6);
+   return new Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
+}
+
+/**
+ * Factory method for creating a color from the background of
+ * an HTML element.
+ */
+Rico.Color.createColorFromBackground = function(elem) {
+
+   var actualColor = RicoUtil.getElementsComputedStyle($(elem), "backgroundColor", "background-color");
+
+   if ( actualColor == "transparent" && elem.parent )
+      return Rico.Color.createColorFromBackground(elem.parent);
+
+   if ( actualColor == null )
+      return new Rico.Color(255,255,255);
+
+   if ( actualColor.indexOf("rgb(") == 0 ) {
+      var colors = actualColor.substring(4, actualColor.length - 1 );
+      var colorArray = colors.split(",");
+      return new Rico.Color( parseInt( colorArray[0] ),
+                            parseInt( colorArray[1] ),
+                            parseInt( colorArray[2] )  );
+
+   }
+   else if ( actualColor.indexOf("#") == 0 ) {
+      var redPart   = parseInt(actualColor.substring(1,3), 16);
+      var greenPart = parseInt(actualColor.substring(3,5), 16);
+      var bluePart  = parseInt(actualColor.substring(5), 16);
+      return new Rico.Color( redPart, greenPart, bluePart );
+   }
+   else
+      return new Rico.Color(255,255,255);
+}
+
+Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
+
+   var red   = 0;
+	var green = 0;
+	var blue  = 0;
+
+   if (saturation == 0) {
+      red = parseInt(brightness * 255.0 + 0.5);
+	   green = red;
+	   blue = red;
+	}
+	else {
+      var h = (hue - Math.floor(hue)) * 6.0;
+      var f = h - Math.floor(h);
+      var p = brightness * (1.0 - saturation);
+      var q = brightness * (1.0 - saturation * f);
+      var t = brightness * (1.0 - (saturation * (1.0 - f)));
+
+      switch (parseInt(h)) {
+         case 0:
+            red   = (brightness * 255.0 + 0.5);
+            green = (t * 255.0 + 0.5);
+            blue  = (p * 255.0 + 0.5);
+            break;
+         case 1:
+            red   = (q * 255.0 + 0.5);
+            green = (brightness * 255.0 + 0.5);
+            blue  = (p * 255.0 + 0.5);
+            break;
+         case 2:
+            red   = (p * 255.0 + 0.5);
+            green = (brightness * 255.0 + 0.5);
+            blue  = (t * 255.0 + 0.5);
+            break;
+         case 3:
+            red   = (p * 255.0 + 0.5);
+            green = (q * 255.0 + 0.5);
+            blue  = (brightness * 255.0 + 0.5);
+            break;
+         case 4:
+            red   = (t * 255.0 + 0.5);
+            green = (p * 255.0 + 0.5);
+            blue  = (brightness * 255.0 + 0.5);
+            break;
+          case 5:
+            red   = (brightness * 255.0 + 0.5);
+            green = (p * 255.0 + 0.5);
+            blue  = (q * 255.0 + 0.5);
+            break;
+	    }
+	}
+
+   return { r : parseInt(red), g : parseInt(green) , b : parseInt(blue) };
+}
+
+Rico.Color.RGBtoHSB = function(r, g, b) {
+
+   var hue;
+   var saturaton;
+   var brightness;
+
+   var cmax = (r > g) ? r : g;
+   if (b > cmax)
+      cmax = b;
+
+   var cmin = (r < g) ? r : g;
+   if (b < cmin)
+      cmin = b;
+
+   brightness = cmax / 255.0;
+   if (cmax != 0)
+      saturation = (cmax - cmin)/cmax;
+   else
+      saturation = 0;
+
+   if (saturation == 0)
+      hue = 0;
+   else {
+      var redc   = (cmax - r)/(cmax - cmin);
+    	var greenc = (cmax - g)/(cmax - cmin);
+    	var bluec  = (cmax - b)/(cmax - cmin);
+
+    	if (r == cmax)
+    	   hue = bluec - greenc;
+    	else if (g == cmax)
+    	   hue = 2.0 + redc - bluec;
+      else
+    	   hue = 4.0 + greenc - redc;
+
+    	hue = hue / 6.0;
+    	if (hue < 0)
+    	   hue = hue + 1.0;
+   }
+
+   return { h : hue, s : saturation, b : brightness };
+}
+
+
+//-------------------- ricoCorner.js
+
+Rico.Corner = {
+
+   round: function(e, options) {
+      var e = $(e);
+      this._setOptions(options);
+
+      var color = this.options.color;
+      if ( this.options.color == "fromElement" )
+         color = this._background(e);
+
+      var bgColor = this.options.bgColor;
+      if ( this.options.bgColor == "fromParent" )
+         bgColor = this._background(e.offsetParent);
+
+      this._roundCornersImpl(e, color, bgColor);
+   },
+
+   _roundCornersImpl: function(e, color, bgColor) {
+      if(this.options.border)
+         this._renderBorder(e,bgColor);
+      if(this._isTopRounded())
+         this._roundTopCorners(e,color,bgColor);
+      if(this._isBottomRounded())
+         this._roundBottomCorners(e,color,bgColor);
+   },
+
+   _renderBorder: function(el,bgColor) {
+      var borderValue = "1px solid " + this._borderColor(bgColor);
+      var borderL = "border-left: "  + borderValue;
+      var borderR = "border-right: " + borderValue;
+      var style   = "style='" + borderL + ";" + borderR +  "'";
+      el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>"
+   },
+
+   _roundTopCorners: function(el, color, bgColor) {
+      var corner = this._createCorner(bgColor);
+      for(var i=0 ; i < this.options.numSlices ; i++ )
+         corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));
+      el.style.paddingTop = 0;
+      el.insertBefore(corner,el.firstChild);
+   },
+
+   _roundBottomCorners: function(el, color, bgColor) {
+      var corner = this._createCorner(bgColor);
+      for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- )
+         corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));
+      el.style.paddingBottom = 0;
+      el.appendChild(corner);
+   },
+
+   _createCorner: function(bgColor) {
+      var corner = document.createElement("div");
+      corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor);
+      return corner;
+   },
+
+   _createCornerSlice: function(color,bgColor, n, position) {
+      var slice = document.createElement("span");
+
+      var inStyle = slice.style;
+      inStyle.backgroundColor = color;
+      inStyle.display  = "block";
+      inStyle.height   = "1px";
+      inStyle.overflow = "hidden";
+      inStyle.fontSize = "1px";
+
+      var borderColor = this._borderColor(color,bgColor);
+      if ( this.options.border && n == 0 ) {
+         inStyle.borderTopStyle    = "solid";
+         inStyle.borderTopWidth    = "1px";
+         inStyle.borderLeftWidth   = "0px";
+         inStyle.borderRightWidth  = "0px";
+         inStyle.borderBottomWidth = "0px";
+         inStyle.height            = "0px"; // assumes css compliant box model
+         inStyle.borderColor       = borderColor;
+      }
+      else if(borderColor) {
+         inStyle.borderColor = borderColor;
+         inStyle.borderStyle = "solid";
+         inStyle.borderWidth = "0px 1px";
+      }
+
+      if ( !this.options.compact && (n == (this.options.numSlices-1)) )
+         inStyle.height = "2px";
+
+      this._setMargin(slice, n, position);
+      this._setBorder(slice, n, position);
+
+      return slice;
+   },
+
+   _setOptions: function(options) {
+      this.options = {
+         corners : "all",
+         color   : "fromElement",
+         bgColor : "fromParent",
+         blend   : true,
+         border  : false,
+         compact : false
+      }.extend(options || {});
+
+      this.options.numSlices = this.options.compact ? 2 : 4;
+      if ( this._isTransparent() )
+         this.options.blend = false;
+   },
+
+   _whichSideTop: function() {
+      if ( this._hasString(this.options.corners, "all", "top") )
+         return "";
+
+      if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 )
+         return "";
+
+      if (this.options.corners.indexOf("tl") >= 0)
+         return "left";
+      else if (this.options.corners.indexOf("tr") >= 0)
+          return "right";
+      return "";
+   },
+
+   _whichSideBottom: function() {
+      if ( this._hasString(this.options.corners, "all", "bottom") )
+         return "";
+
+      if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 )
+         return "";
+
+      if(this.options.corners.indexOf("bl") >=0)
+         return "left";
+      else if(this.options.corners.indexOf("br")>=0)
+         return "right";
+      return "";
+   },
+
+   _borderColor : function(color,bgColor) {
+      if ( color == "transparent" )
+         return bgColor;
+      else if ( this.options.border )
+         return this.options.border;
+      else if ( this.options.blend )
+         return this._blend( bgColor, color );
+      else
+         return "";
+   },
+
+
+   _setMargin: function(el, n, corners) {
+      var marginSize = this._marginSize(n);
+      var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+
+      if ( whichSide == "left" ) {
+         el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px";
+      }
+      else if ( whichSide == "right" ) {
+         el.style.marginRight = marginSize + "px"; el.style.marginLeft  = "0px";
+      }
+      else {
+         el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px";
+      }
+   },
+
+   _setBorder: function(el,n,corners) {
+      var borderSize = this._borderSize(n);
+      var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+
+      if ( whichSide == "left" ) {
+         el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px";
+      }
+      else if ( whichSide == "right" ) {
+         el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth  = "0px";
+      }
+      else {
+         el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+      }
+   },
+
+   _marginSize: function(n) {
+      if ( this._isTransparent() )
+         return 0;
+
+      var marginSizes          = [ 5, 3, 2, 1 ];
+      var blendedMarginSizes   = [ 3, 2, 1, 0 ];
+      var compactMarginSizes   = [ 2, 1 ];
+      var smBlendedMarginSizes = [ 1, 0 ];
+
+      if ( this.options.compact && this.options.blend )
+         return smBlendedMarginSizes[n];
+      else if ( this.options.compact )
+         return compactMarginSizes[n];
+      else if ( this.options.blend )
+         return blendedMarginSizes[n];
+      else
+         return marginSizes[n];
+   },
+
+   _borderSize: function(n) {
+      var transparentBorderSizes = [ 5, 3, 2, 1 ];
+      var blendedBorderSizes     = [ 2, 1, 1, 1 ];
+      var compactBorderSizes     = [ 1, 0 ];
+      var actualBorderSizes      = [ 0, 2, 0, 0 ];
+
+      if ( this.options.compact && (this.options.blend || this._isTransparent()) )
+         return 1;
+      else if ( this.options.compact )
+         return compactBorderSizes[n];
+      else if ( this.options.blend )
+         return blendedBorderSizes[n];
+      else if ( this.options.border )
+         return actualBorderSizes[n];
+      else if ( this._isTransparent() )
+         return transparentBorderSizes[n];
+      return 0;
+   },
+
+   _hasString: function(str) { for(var i=1 ; i<arguments.length ; i++) if (str.indexOf(arguments[i]) >= 0) return true; return false; },
+   _blend: function(c1, c2) { var cc1 = Rico.Color.createFromHex(c1); cc1.blend(Rico.Color.createFromHex(c2)); return cc1; },
+   _background: function(el) { try { return Rico.Color.createColorFromBackground(el).asHex(); } catch(err) { return "#ffffff"; } },
+   _isTransparent: function() { return this.options.color == "transparent"; },
+   _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); },
+   _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); },
+   _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; }
+}
+
+
+//-------------------- ricoDragAndDrop.js
+Rico.DragAndDrop = Class.create();
+
+Rico.DragAndDrop.prototype = {
+
+   initialize: function() {
+      this.dropZones                = new Array();
+      this.draggables               = new Array();
+      this.currentDragObjects       = new Array();
+      this.dragElement              = null;
+      this.lastSelectedDraggable    = null;
+      this.currentDragObjectVisible = false;
+      this.interestedInMotionEvents = false;
+   },
+
+   registerDropZone: function(aDropZone) {
+      this.dropZones[ this.dropZones.length ] = aDropZone;
+   },
+
+   deregisterDropZone: function(aDropZone) {
+      var newDropZones = new Array();
+      var j = 0;
+      for ( var i = 0 ; i < this.dropZones.length ; i++ ) {
+         if ( this.dropZones[i] != aDropZone )
+            newDropZones[j++] = this.dropZones[i];
+      }
+
+      this.dropZones = newDropZones;
+   },
+
+   clearDropZones: function() {
+      this.dropZones = new Array();
+   },
+
+   registerDraggable: function( aDraggable ) {
+      this.draggables[ this.draggables.length ] = aDraggable;
+      this._addMouseDownHandler( aDraggable );
+   },
+
+   clearSelection: function() {
+      for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
+         this.currentDragObjects[i].deselect();
+      this.currentDragObjects = new Array();
+      this.lastSelectedDraggable = null;
+   },
+
+   hasSelection: function() {
+      return this.currentDragObjects.length > 0;
+   },
+
+   setStartDragFromElement: function( e, mouseDownElement ) {
+      this.origPos = RicoUtil.toDocumentPosition(mouseDownElement);
+      this.startx = e.screenX - this.origPos.x
+      this.starty = e.screenY - this.origPos.y
+      //this.startComponentX = e.layerX ? e.layerX : e.offsetX;
+      //this.startComponentY = e.layerY ? e.layerY : e.offsetY;
+      //this.adjustedForDraggableSize = false;
+
+      this.interestedInMotionEvents = this.hasSelection();
+      this._terminateEvent(e);
+   },
+
+   updateSelection: function( draggable, extendSelection ) {
+      if ( ! extendSelection )
+         this.clearSelection();
+
+      if ( draggable.isSelected() ) {
+         this.currentDragObjects.removeItem(draggable);
+         draggable.deselect();
+         if ( draggable == this.lastSelectedDraggable )
+            this.lastSelectedDraggable = null;
+      }
+      else {
+         this.currentDragObjects[ this.currentDragObjects.length ] = draggable;
+         draggable.select();
+         this.lastSelectedDraggable = draggable;
+      }
+   },
+
+   _mouseDownHandler: function(e) {
+      if ( arguments.length == 0 )
+         e = event;
+
+      // if not button 1 ignore it...
+      var nsEvent = e.which != undefined;
+      if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
+         return;
+
+      var eventTarget      = e.target ? e.target : e.srcElement;
+      var draggableObject  = eventTarget.draggable;
+
+      var candidate = eventTarget;
+      while (draggableObject == null && candidate.parentNode) {
+         candidate = candidate.parentNode;
+         draggableObject = candidate.draggable;
+      }
+   
+      if ( draggableObject == null )
+         return;
+
+      this.updateSelection( draggableObject, e.ctrlKey );
+
+      // clear the drop zones postion cache...
+      if ( this.hasSelection() )
+         for ( var i = 0 ; i < this.dropZones.length ; i++ )
+            this.dropZones[i].clearPositionCache();
+
+      this.setStartDragFromElement( e, draggableObject.getMouseDownHTMLElement() );
+   },
+
+
+   _mouseMoveHandler: function(e) {
+      var nsEvent = e.which != undefined;
+      if ( !this.interestedInMotionEvents ) {
+         this._terminateEvent(e);
+         return;
+      }
+
+      if ( ! this.hasSelection() )
+         return;
+
+      if ( ! this.currentDragObjectVisible )
+         this._startDrag(e);
+
+      if ( !this.activatedDropZones )
+         this._activateRegisteredDropZones();
+
+      //if ( !this.adjustedForDraggableSize )
+      //   this._adjustForDraggableSize(e);
+
+      this._updateDraggableLocation(e);
+      this._updateDropZonesHover(e);
+
+      this._terminateEvent(e);
+   },
+
+   _makeDraggableObjectVisible: function(e)
+   {
+      if ( !this.hasSelection() )
+         return;
+
+      var dragElement;
+      if ( this.currentDragObjects.length > 1 )
+         dragElement = this.currentDragObjects[0].getMultiObjectDragGUI(this.currentDragObjects);
+      else
+         dragElement = this.currentDragObjects[0].getSingleObjectDragGUI();
+
+      // go ahead and absolute position it...
+      if ( RicoUtil.getElementsComputedStyle(dragElement, "position")  != "absolute" )
+         dragElement.style.position = "absolute";
+
+      // need to parent him into the document...
+      if ( dragElement.parentNode == null || dragElement.parentNode.nodeType == 11 )
+         document.body.appendChild(dragElement);
+
+      this.dragElement = dragElement;
+      this._updateDraggableLocation(e);
+
+      this.currentDragObjectVisible = true;
+   },
+
+   /**
+   _adjustForDraggableSize: function(e) {
+      var dragElementWidth  = this.dragElement.offsetWidth;
+      var dragElementHeight = this.dragElement.offsetHeight;
+      if ( this.startComponentX > dragElementWidth )
+         this.startx -= this.startComponentX - dragElementWidth + 2;
+      if ( e.offsetY ) {
+         if ( this.startComponentY > dragElementHeight )
+            this.starty -= this.startComponentY - dragElementHeight + 2;
+      }
+      this.adjustedForDraggableSize = true;
+   },
+   **/
+
+   _updateDraggableLocation: function(e) {
+      var dragObjectStyle = this.dragElement.style;
+      dragObjectStyle.left = (e.screenX - this.startx) + "px"
+      dragObjectStyle.top  = (e.screenY - this.starty) + "px";
+   },
+
+   _updateDropZonesHover: function(e) {
+      var n = this.dropZones.length;
+      for ( var i = 0 ; i < n ; i++ ) {
+         if ( ! this._mousePointInDropZone( e, this.dropZones[i] ) )
+            this.dropZones[i].hideHover();
+      }
+
+      for ( var i = 0 ; i < n ; i++ ) {
+         if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
+            if ( this.dropZones[i].canAccept(this.currentDragObjects) )
+               this.dropZones[i].showHover();
+         }
+      }
+   },
+
+   _startDrag: function(e) {
+      for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
+         this.currentDragObjects[i].startDrag();
+
+      this._makeDraggableObjectVisible(e);
+   },
+
+   _mouseUpHandler: function(e) {
+      if ( ! this.hasSelection() )
+         return;
+
+      var nsEvent = e.which != undefined;
+      if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
+         return;
+
+      this.interestedInMotionEvents = false;
+
+      if ( this.dragElement == null ) {
+         this._terminateEvent(e);
+         return;
+      }
+
+      if ( this._placeDraggableInDropZone(e) )
+         this._completeDropOperation(e);
+      else {
+         this._terminateEvent(e);
+         new Effect.Position( this.dragElement,
+                              this.origPos.x,
+                              this.origPos.y,
+                              200,
+                              20,
+                              { complete : this._doCancelDragProcessing.bind(this) } );
+      }
+   },
+
+   _completeDropOperation: function(e) {
+      if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
+         if ( this.dragElement.parentNode != null )
+            this.dragElement.parentNode.removeChild(this.dragElement);
+      }
+
+      this._deactivateRegisteredDropZones();
+      this._endDrag();
+      this.clearSelection();
+      this.dragElement = null;
+      this.currentDragObjectVisible = false;
+      this._terminateEvent(e);
+   },
+
+   _doCancelDragProcessing: function() {
+      this._cancelDrag();
+
+      if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
+         if ( this.dragElement.parentNode != null ) {
+            this.dragElement.parentNode.removeChild(this.dragElement);
+         }
+      }
+
+      this._deactivateRegisteredDropZones();
+      this.dragElement = null;
+      this.currentDragObjectVisible = false;
+   },
+
+   _placeDraggableInDropZone: function(e) {
+      var foundDropZone = false;
+      var n = this.dropZones.length;
+      for ( var i = 0 ; i < n ; i++ ) {
+         if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
+            if ( this.dropZones[i].canAccept(this.currentDragObjects) ) {
+               this.dropZones[i].hideHover();
+               this.dropZones[i].accept(this.currentDragObjects);
+               foundDropZone = true;
+               break;
+            }
+         }
+      }
+
+      return foundDropZone;
+   },
+
+   _cancelDrag: function() {
+      for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
+         this.currentDragObjects[i].cancelDrag();
+   },
+
+   _endDrag: function() {
+      for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
+         this.currentDragObjects[i].endDrag();
+   },
+
+   _mousePointInDropZone: function( e, dropZone ) {
+
+      var absoluteRect = dropZone.getAbsoluteRect();
+
+      return e.clientX  > absoluteRect.left  &&
+             e.clientX  < absoluteRect.right &&
+             e.clientY  > absoluteRect.top   &&
+             e.clientY  < absoluteRect.bottom;
+   },
+
+   _addMouseDownHandler: function( aDraggable )
+   {
+      var htmlElement = aDraggable.getMouseDownHTMLElement();
+      if ( htmlElement != null ) {
+         htmlElement.draggable = aDraggable;
+         this._addMouseDownEvent( htmlElement );
+      }
+   },
+
+   _activateRegisteredDropZones: function() {
+      var n = this.dropZones.length;
+      for ( var i = 0 ; i < n ; i++ ) {
+         var dropZone = this.dropZones[i];
+         if ( dropZone.canAccept(this.currentDragObjects) )
+            dropZone.activate();
+      }
+
+      this.activatedDropZones = true;
+   },
+
+   _deactivateRegisteredDropZones: function() {
+      var n = this.dropZones.length;
+      for ( var i = 0 ; i < n ; i++ )
+         this.dropZones[i].deactivate();
+      this.activatedDropZones = false;
+   },
+
+   _addMouseDownEvent: function( htmlElement ) {
+      if ( typeof document.implementation != "undefined" &&
+         document.implementation.hasFeature("HTML",   "1.0") &&
+         document.implementation.hasFeature("Events", "2.0") &&
+         document.implementation.hasFeature("CSS",    "2.0") ) {
+         htmlElement.addEventListener("mousedown", this._mouseDownHandler.bindAsEventListener(this), false);
+      }
+      else {
+         htmlElement.attachEvent( "onmousedown", this._mouseDownHandler.bindAsEventListener(this) );
+      }
+   },
+
+   _terminateEvent: function(e) {
+      if ( e.stopPropagation != undefined )
+         e.stopPropagation();
+      else if ( e.cancelBubble != undefined )
+         e.cancelBubble = true;
+
+      if ( e.preventDefault != undefined )
+         e.preventDefault();
+      else
+         e.returnValue = false;
+   },
+
+   initializeEventHandlers: function() {
+      if ( typeof document.implementation != "undefined" &&
+         document.implementation.hasFeature("HTML",   "1.0") &&
+         document.implementation.hasFeature("Events", "2.0") &&
+         document.implementation.hasFeature("CSS",    "2.0") ) {
+         document.addEventListener("mouseup",   this._mouseUpHandler.bindAsEventListener(this),  false);
+         document.addEventListener("mousemove", this._mouseMoveHandler.bindAsEventListener(this), false);
+      }
+      else {
+         document.attachEvent( "onmouseup",   this._mouseUpHandler.bindAsEventListener(this) );
+         document.attachEvent( "onmousemove", this._mouseMoveHandler.bindAsEventListener(this) );
+      }
+   }
+}
+
+//var dndMgr = new Rico.DragAndDrop();
+//dndMgr.initializeEventHandlers();
+
+
+//-------------------- ricoDraggable.js
+Rico.Draggable = Class.create();
+
+Rico.Draggable.prototype = {
+
+   initialize: function( type, htmlElement ) {
+      this.type          = type;
+      this.htmlElement   = $(htmlElement);
+      this.selected      = false;
+   },
+
+   /**
+    *   Returns the HTML element that should have a mouse down event
+    *   added to it in order to initiate a drag operation
+    *
+    **/
+   getMouseDownHTMLElement: function() {
+      return this.htmlElement;
+   },
+
+   select: function() {
+      this.selected = true;
+
+      if ( this.showingSelected )
+         return;
+
+      var htmlElement = this.getMouseDownHTMLElement();
+
+      var color = Rico.Color.createColorFromBackground(htmlElement);
+      color.isBright() ? color.darken(0.033) : color.brighten(0.033);
+
+      this.saveBackground = RicoUtil.getElementsComputedStyle(htmlElement, "backgroundColor", "background-color");
+      htmlElement.style.backgroundColor = color.asHex();
+      this.showingSelected = true;
+   },
+
+   deselect: function() {
+      this.selected = false;
+      if ( !this.showingSelected )
+         return;
+
+      var htmlElement = this.getMouseDownHTMLElement();
+
+      htmlElement.style.backgroundColor = this.saveBackground;
+      this.showingSelected = false;
+   },
+
+   isSelected: function() {
+      return this.selected;
+   },
+
+   startDrag: function() {
+   },
+
+   cancelDrag: function() {
+   },
+
+   endDrag: function() {
+   },
+
+   getSingleObjectDragGUI: function() {
+      return this.htmlElement;
+   },
+
+   getMultiObjectDragGUI: function( draggables ) {
+      return this.htmlElement;
+   },
+
+   getDroppedGUI: function() {
+      return this.htmlElement;
+   },
+
+   toString: function() {
+      return this.type + ":" + this.htmlElement + ":";
+   }
+
+}
+
+
+//-------------------- ricoDropzone.js
+Rico.Dropzone = Class.create();
+
+Rico.Dropzone.prototype = {
+
+   initialize: function( htmlElement ) {
+      this.htmlElement  = $(htmlElement);
+      this.absoluteRect = null;
+   },
+
+   getHTMLElement: function() {
+      return this.htmlElement;
+   },
+
+   clearPositionCache: function() {
+      this.absoluteRect = null;
+   },
+
+   getAbsoluteRect: function() {
+      if ( this.absoluteRect == null ) {
+         var htmlElement = this.getHTMLElement();
+         var pos = RicoUtil.toViewportPosition(htmlElement);
+
+         this.absoluteRect = {
+            top:    pos.y,
+            left:   pos.x,
+            bottom: pos.y + htmlElement.offsetHeight,
+            right:  pos.x + htmlElement.offsetWidth
+         };
+      }
+      return this.absoluteRect;
+   },
+
+   activate: function() {
+      var htmlElement = this.getHTMLElement();
+      if (htmlElement == null  || this.showingActive)
+         return;
+
+      this.showingActive = true;
+      this.saveBackgroundColor = htmlElement.style.backgroundColor;
+
+      var fallbackColor = "#ffea84";
+      var currentColor = Rico.Color.createColorFromBackground(htmlElement);
+      if ( currentColor == null )
+         htmlElement.style.backgroundColor = fallbackColor;
+      else {
+         currentColor.isBright() ? currentColor.darken(0.2) : currentColor.brighten(0.2);
+         htmlElement.style.backgroundColor = currentColor.asHex();
+      }
+   },
+
+   deactivate: function() {
+      var htmlElement = this.getHTMLElement();
+      if (htmlElement == null || !this.showingActive)
+         return;
+
+      htmlElement.style.backgroundColor = this.saveBackgroundColor;
+      this.showingActive = false;
+      this.saveBackgroundColor = null;
+   },
+
+   showHover: function() {
+      var htmlElement = this.getHTMLElement();
+      if ( htmlElement == null || this.showingHover )
+         return;
+
+      this.saveBorderWidth = htmlElement.style.borderWidth;
+      this.saveBorderStyle = htmlElement.style.borderStyle;
+      this.saveBorderColor = htmlElement.style.borderColor;
+
+      this.showingHover = true;
+      htmlElement.style.borderWidth = "1px";
+      htmlElement.style.borderStyle = "solid";
+      //htmlElement.style.borderColor = "#ff9900";
+      htmlElement.style.borderColor = "#ffff00";
+   },
+
+   hideHover: function() {
+      var htmlElement = this.getHTMLElement();
+      if ( htmlElement == null || !this.showingHover )
+         return;
+
+      htmlElement.style.borderWidth = this.saveBorderWidth;
+      htmlElement.style.borderStyle = this.saveBorderStyle;
+      htmlElement.style.borderColor = this.saveBorderColor;
+      this.showingHover = false;
+   },
+
+   canAccept: function(draggableObjects) {
+      return true;
+   },
+
+   accept: function(draggableObjects) {
+      var htmlElement = this.getHTMLElement();
+      if ( htmlElement == null )
+         return;
+
+      n = draggableObjects.length;
+      for ( var i = 0 ; i < n ; i++ )
+      {
+         var theGUI = draggableObjects[i].getDroppedGUI();
+         if ( RicoUtil.getElementsComputedStyle( theGUI, "position" ) == "absolute" )
+         {
+            theGUI.style.position = "static";
+            theGUI.style.top = "";
+            theGUI.style.top = "";
+         }
+         htmlElement.appendChild(theGUI);
+      }
+   }
+}
+
+
+//-------------------- ricoEffects.js
+
+/**
+  *  Use the Effect namespace for effects.  If using scriptaculous effects
+  *  this will already be defined, otherwise we'll just create an empty
+  *  object for it...
+ **/
+if ( window.Effect == undefined )
+   Effect = {};
+
+Effect.SizeAndPosition = Class.create();
+Effect.SizeAndPosition.prototype = {
+
+   initialize: function(element, x, y, w, h, duration, steps, options) {
+      this.element = $(element);
+      this.x = x;
+      this.y = y;
+      this.w = w;
+      this.h = h;
+      this.duration = duration;
+      this.steps    = steps;
+      this.options  = arguments[7] || {};
+
+      this.sizeAndPosition();
+   },
+
+   sizeAndPosition: function() {
+      if (this.isFinished()) {
+         if(this.options.complete) this.options.complete(this);
+         return;
+      }
+
+      if (this.timer)
+         clearTimeout(this.timer);
+
+      var stepDuration = Math.round(this.duration/this.steps) ;
+
+      // Get original values: x,y = top left corner;  w,h = width height
+      var currentX = this.element.offsetLeft;
+      var currentY = this.element.offsetTop;
+      var currentW = this.element.offsetWidth;
+      var currentH = this.element.offsetHeight;
+
+      // If values not set, or zero, we do not modify them, and take original as final as well
+      this.x = (this.x) ? this.x : currentX;
+      this.y = (this.y) ? this.y : currentY;
+      this.w = (this.w) ? this.w : currentW;
+      this.h = (this.h) ? this.h : currentH;
+
+      // how much do we need to modify our values for each step?
+      var difX = this.steps >  0 ? (this.x - currentX)/this.steps : 0;
+      var difY = this.steps >  0 ? (this.y - currentY)/this.steps : 0;
+      var difW = this.steps >  0 ? (this.w - currentW)/this.steps : 0;
+      var difH = this.steps >  0 ? (this.h - currentH)/this.steps : 0;
+
+      this.moveBy(difX, difY);
+      this.resizeBy(difW, difH);
+
+      this.duration -= stepDuration;
+      this.steps--;
+
+      this.timer = setTimeout(this.sizeAndPosition.bind(this), stepDuration);
+   },
+
+   isFinished: function() {
+      return this.steps <= 0;
+   },
+
+   moveBy: function( difX, difY ) {
+      var currentLeft = this.element.offsetLeft;
+      var currentTop  = this.element.offsetTop;
+      var intDifX     = parseInt(difX);
+      var intDifY     = parseInt(difY);
+
+      var style = this.element.style;
+      if ( intDifX != 0 )
+         style.left = (currentLeft + intDifX) + "px";
+      if ( intDifY != 0 )
+         style.top  = (currentTop + intDifY) + "px";
+   },
+
+   resizeBy: function( difW, difH ) {
+      var currentWidth  = this.element.offsetWidth;
+      var currentHeight = this.element.offsetHeight;
+      var intDifW       = parseInt(difW);
+      var intDifH       = parseInt(difH);
+
+      var style = this.element.style;
+      if ( intDifW != 0 )
+         style.width   = (currentWidth  + intDifW) + "px";
+      if ( intDifH != 0 )
+         style.height  = (currentHeight + intDifH) + "px";
+   }
+}
+
+Effect.Size = Class.create();
+Effect.Size.prototype = {
+
+   initialize: function(element, w, h, duration, steps, options) {
+      new Effect.SizeAndPosition(element, null, null, w, h, duration, steps, options);
+  }
+}
+
+Effect.Position = Class.create();
+Effect.Position.prototype = {
+
+   initialize: function(element, x, y, duration, steps, options) {
+      new Effect.SizeAndPosition(element, x, y, null, null, duration, steps, options);
+  }
+}
+
+Effect.Round = Class.create();
+Effect.Round.prototype = {
+
+   initialize: function(tagName, className, options) {
+      var elements = document.getElementsByTagAndClassName(tagName,className);
+      for ( var i = 0 ; i < elements.length ; i++ )
+         Rico.Corner.round( elements[i], options );
+   }
+};
+
+Effect.FadeTo = Class.create();
+Effect.FadeTo.prototype = {
+
+   initialize: function( element, opacity, duration, steps, options) {
+      this.element  = $(element);
+      this.opacity  = opacity;
+      this.duration = duration;
+      this.steps    = steps;
+      this.options  = arguments[4] || {};
+      this.fadeTo();
+   },
+
+   fadeTo: function() {
+      if (this.isFinished()) {
+         if(this.options.complete) this.options.complete(this);
+         return;
+      }
+
+      if (this.timer)
+         clearTimeout(this.timer);
+
+      var stepDuration = Math.round(this.duration/this.steps) ;
+      var currentOpacity = this.getElementOpacity();
+      var delta = this.steps > 0 ? (this.opacity - currentOpacity)/this.steps : 0;
+
+      this.changeOpacityBy(delta);
+      this.duration -= stepDuration;
+      this.steps--;
+
+      this.timer = setTimeout(this.fadeTo.bind(this), stepDuration);
+   },
+
+   changeOpacityBy: function(v) {
+      var currentOpacity = this.getElementOpacity();
+      var newOpacity = Math.max(0, Math.min(currentOpacity+v, 1));
+      this.element.ricoOpacity = newOpacity;
+
+      this.element.style.filter = "alpha(opacity:"+Math.round(newOpacity*100)+")";
+      this.element.style.opacity = newOpacity; /*//*/;
+   },
+
+   isFinished: function() {
+      return this.steps <= 0;
+   },
+
+   getElementOpacity: function() {
+      if ( this.element.ricoOpacity == undefined ) {
+         var opacity;
+         if ( this.element.currentStyle ) {
+            opacity = this.element.currentStyle.opacity;
+         }
+         else if ( document.defaultView.getComputedStyle != undefined ) {
+            var computedStyle = document.defaultView.getComputedStyle;
+            opacity = computedStyle(this.element, null).getPropertyValue('opacity');
+         }
+
+         this.element.ricoOpacity = opacity != undefined ? opacity : 1.0;
+      }
+
+      return parseFloat(this.element.ricoOpacity);
+   }
+}
+
+Effect.AccordionSize = Class.create();
+
+Effect.AccordionSize.prototype = {
+
+   initialize: function(e1, e2, start, end, duration, steps, options) {
+      this.e1       = $(e1);
+      this.e2       = $(e2);
+      this.start    = start;
+      this.end      = end;
+      this.duration = duration;
+      this.steps    = steps;
+      this.options  = arguments[6] || {};
+
+      this.accordionSize();
+   },
+
+   accordionSize: function() {
+
+      if (this.isFinished()) {
+         // just in case there are round errors or such...
+         this.e1.style.height = this.start + "px";
+         this.e2.style.height = this.end + "px";
+
+         if(this.options.complete)
+            this.options.complete(this);
+         return;
+      }
+
+      if (this.timer)
+         clearTimeout(this.timer);
+
+      var stepDuration = Math.round(this.duration/this.steps) ;
+
+      var diff = this.steps > 0 ? (parseInt(this.e1.offsetHeight) - this.start)/this.steps : 0;
+      this.resizeBy(diff);
+
+      this.duration -= stepDuration;
+      this.steps--;
+
+      this.timer = setTimeout(this.accordionSize.bind(this), stepDuration);
+   },
+
+   isFinished: function() {
+      return this.steps <= 0;
+   },
+
+   resizeBy: function(diff) {
+      var h1Height = this.e1.offsetHeight;
+      var h2Height = this.e2.offsetHeight;
+      var intDiff = parseInt(diff);
+      if ( diff != 0 ) {
+         this.e1.style.height = (h1Height - intDiff) + "px";
+         this.e2.style.height = (h2Height + intDiff) + "px";
+      }
+   }
+
+};
+
+
+//-------------------- ricoLiveGrid.js
+
+// Rico.LiveGridMetaData -----------------------------------------------------
+
+Rico.LiveGridMetaData = Class.create();
+
+Rico.LiveGridMetaData.prototype = {
+
+   initialize: function( pageSize, totalRows, columnCount, options ) {
+      this.pageSize  = pageSize;
+      this.totalRows = totalRows;
+      this.setOptions(options);
+      this.scrollArrowHeight = 16;
+      this.columnCount = columnCount;
+   },
+
+   setOptions: function(options) {
+      this.options = {
+         largeBufferSize    : 7.0,   // 7 pages
+         nearLimitFactor    : 0.2    // 20% of buffer
+      }.extend(options || {});
+   },
+
+   getPageSize: function() {
+      return this.pageSize;
+   },
+
+   getTotalRows: function() {
+      return this.totalRows;
+   },
+
+   setTotalRows: function(n) {
+      this.totalRows = n;
+   },
+
+   getLargeBufferSize: function() {
+      return parseInt(this.options.largeBufferSize * this.pageSize);
+   },
+
+   getLimitTolerance: function() {
+      return parseInt(this.getLargeBufferSize() * this.options.nearLimitFactor);
+   }
+};
+
+// Rico.LiveGridScroller -----------------------------------------------------
+
+Rico.LiveGridScroller = Class.create();
+
+Rico.LiveGridScroller.prototype = {
+
+   initialize: function(liveGrid, viewPort) {
+      this.isIE = navigator.userAgent.toLowerCase().indexOf("msie") >= 0;
+      this.liveGrid = liveGrid;
+      this.metaData = liveGrid.metaData;
+      this.createScrollBar();
+      this.scrollTimeout = null;
+      this.lastScrollPos = 0;
+      this.viewPort = viewPort;
+      this.rows = new Array();
+   },
+
+   isUnPlugged: function() {
+      return this.scrollerDiv.onscroll == null;
+   },
+
+   plugin: function() {
+      this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
+   },
+
+   unplug: function() {
+      this.scrollerDiv.onscroll = null;
+   },
+
+   sizeIEHeaderHack: function() {
+      if ( !this.isIE ) return;
+      var headerTable = $(this.liveGrid.tableId + "_header");
+      if ( headerTable )
+         headerTable.rows[0].cells[0].style.width =
+            (headerTable.rows[0].cells[0].offsetWidth + 1) + "px";
+   },
+
+   createScrollBar: function() {
+      var visibleHeight = this.liveGrid.viewPort.visibleHeight();
+      // create the outer div...
+      this.scrollerDiv  = document.createElement("div");
+      var scrollerStyle = this.scrollerDiv.style;
+      scrollerStyle.borderRight = "1px solid #ababab"; // hard coded color!!!
+      scrollerStyle.position    = "relative";
+      scrollerStyle.left        = this.isIE ? "-6px" : "-3px";
+      scrollerStyle.width       = "19px";
+      scrollerStyle.height      = visibleHeight + "px";
+      scrollerStyle.overflow    = "auto";
+
+      // create the inner div...
+      this.heightDiv = document.createElement("div");
+      this.heightDiv.style.width  = "1px";
+
+      this.heightDiv.style.height = parseInt(visibleHeight *
+                        this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px" ;
+      this.scrollerDiv.appendChild(this.heightDiv);
+      this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
+
+     var table = this.liveGrid.table;
+     table.parentNode.parentNode.insertBefore( this.scrollerDiv, table.parentNode.nextSibling );
+   },
+
+   updateSize: function() {
+      var table = this.liveGrid.table;
+      var visibleHeight = this.viewPort.visibleHeight();
+      this.heightDiv.style.height = parseInt(visibleHeight *
+                                  this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px";
+   },
+
+   rowToPixel: function(rowOffset) {
+      return (rowOffset / this.metaData.getTotalRows()) * this.heightDiv.offsetHeight
+   },
+   
+   moveScroll: function(rowOffset) {
+      this.scrollerDiv.scrollTop = this.rowToPixel(rowOffset);
+      if ( this.metaData.options.onscroll )
+         this.metaData.options.onscroll( this.liveGrid, rowOffset );    
+   },
+
+   handleScroll: function() {
+     if ( this.scrollTimeout )
+         clearTimeout( this.scrollTimeout );
+
+      var contentOffset = parseInt(this.scrollerDiv.scrollTop / this.viewPort.rowHeight);
+      this.liveGrid.requestContentRefresh(contentOffset);
+      this.viewPort.scrollTo(this.scrollerDiv.scrollTop);
+      
+      if ( this.metaData.options.onscroll )
+         this.metaData.options.onscroll( this.liveGrid, contentOffset );
+
+      this.scrollTimeout = setTimeout( this.scrollIdle.bind(this), 1200 );
+   },
+
+   scrollIdle: function() {
+      if ( this.metaData.options.onscrollidle )
+         this.metaData.options.onscrollidle();
+   }
+};
+
+// Rico.LiveGridBuffer -----------------------------------------------------
+
+Rico.LiveGridBuffer = Class.create();
+
+Rico.LiveGridBuffer.prototype = {
+
+   initialize: function(metaData, viewPort) {
+      this.startPos = 0;
+      this.size     = 0;
+      this.metaData = metaData;
+      this.rows     = new Array();
+      this.updateInProgress = false;
+      this.viewPort = viewPort;
+      this.maxBufferSize = metaData.getLargeBufferSize() * 2;
+      this.maxFetchSize = metaData.getLargeBufferSize();
+      this.lastOffset = 0;
+   },
+
+   getBlankRow: function() {
+      if (!this.blankRow ) {
+         this.blankRow = new Array();
+         for ( var i=0; i < this.metaData.columnCount ; i++ ) 
+            this.blankRow[i] = "&nbsp;";
+     }
+     return this.blankRow;
+   },
+   
+   loadRows: function(ajaxResponse) {
+      var rowsElement = ajaxResponse.getElementsByTagName('rows')[0];
+      this.updateUI = rowsElement.getAttribute("update_ui") == "true"
+      var newRows = new Array()
+      var trs = rowsElement.getElementsByTagName("tr");
+      for ( var i=0 ; i < trs.length; i++ ) {
+         var row = newRows[i] = new Array(); 
+         var cells = trs[i].getElementsByTagName("td");
+         for ( var j=0; j < cells.length ; j++ ) {
+            var cell = cells[j];
+            var convertSpaces = cell.getAttribute("convert_spaces") == "true";
+            var cellContent = RicoUtil.getContentAsString(cell);
+            row[j] = convertSpaces ? this.convertSpaces(cellContent) : cellContent;
+            if (!row[j]) 
+               row[j] = '&nbsp;';
+         }
+      }
+      return newRows;
+   },
+      
+   update: function(ajaxResponse, start) {
+     var newRows = this.loadRows(ajaxResponse);
+      if (this.rows.length == 0) { // initial load
+         this.rows = newRows;
+         this.size = this.rows.length;
+         this.startPos = start;
+         return;
+      }
+      if (start > this.startPos) { //appending
+         if (this.startPos + this.rows.length < start) {
+            this.rows =  newRows;
+            this.startPos = start;//
+         } else {
+              this.rows = this.rows.concat( newRows.slice(0, newRows.length));
+            if (this.rows.length > this.maxBufferSize) {
+               var fullSize = this.rows.length;
+               this.rows = this.rows.slice(this.rows.length - this.maxBufferSize, this.rows.length)
+               this.startPos = this.startPos +  (fullSize - this.rows.length);
+            }
+         }
+      } else { //prepending
+         if (start + newRows.length < this.startPos) {
+            this.rows =  newRows;
+         } else {
+            this.rows = newRows.slice(0, this.startPos).concat(this.rows);
+            if (this.rows.length > this.maxBufferSize) 
+               this.rows = this.rows.slice(0, this.maxBufferSize)
+         }
+         this.startPos =  start;
+      }
+      this.size = this.rows.length;
+   },
+   
+   clear: function() {
+      this.rows = new Array();
+      this.startPos = 0;
+      this.size = 0;
+   },
+
+   isOverlapping: function(start, size) {
+      return ((start < this.endPos()) && (this.startPos < start + size)) || (this.endPos() == 0)
+   },
+
+   isInRange: function(position) {
+      return (position >= this.startPos) && (position + this.metaData.getPageSize() <= this.endPos()); 
+             //&& this.size()  != 0;
+   },
+
+   isNearingTopLimit: function(position) {
+      return position - this.startPos < this.metaData.getLimitTolerance();
+   },
+
+   endPos: function() {
+      return this.startPos + this.rows.length;
+   },
+   
+   isNearingBottomLimit: function(position) {
+      return this.endPos() - (position + this.metaData.getPageSize()) < this.metaData.getLimitTolerance();
+   },
+
+   isAtTop: function() {
+      return this.startPos == 0;
+   },
+
+   isAtBottom: function() {
+      return this.endPos() == this.metaData.getTotalRows();
+   },
+
+   isNearingLimit: function(position) {
+      return ( !this.isAtTop()    && this.isNearingTopLimit(position)) ||
+             ( !this.isAtBottom() && this.isNearingBottomLimit(position) )
+   },
+
+   getFetchSize: function(offset) {
+      var adjustedOffset = this.getFetchOffset(offset);
+      var adjustedSize = 0;
+      if (adjustedOffset >= this.startPos) { //apending
+         var endFetchOffset = this.maxFetchSize  + adjustedOffset;
+         if (endFetchOffset > this.metaData.totalRows)
+            endFetchOffset = this.metaData.totalRows;
+         adjustedSize = endFetchOffset - adjustedOffset;   
+      } else {//prepending
+         var adjustedSize = this.startPos - adjustedOffset;
+         if (adjustedSize > this.maxFetchSize)
+            adjustedSize = this.maxFetchSize;
+      }
+      return adjustedSize;
+   }, 
+
+   getFetchOffset: function(offset) {
+      var adjustedOffset = offset;
+      if (offset > this.startPos)  //apending
+         adjustedOffset = (offset > this.endPos()) ? offset :  this.endPos(); 
+      else { //prepending
+         if (offset + this.maxFetchSize >= this.startPos) {
+            var adjustedOffset = this.startPos - this.maxFetchSize;
+            if (adjustedOffset < 0)
+               adjustedOffset = 0;
+         }
+      }
+      this.lastOffset = adjustedOffset;
+      return adjustedOffset;
+   },
+
+   getRows: function(start, count) {
+      var begPos = start - this.startPos
+      var endPos = begPos + count
+
+      // er? need more data...
+      if ( endPos > this.size )
+         endPos = this.size
+
+      var results = new Array()
+      var index = 0;
+      for ( var i=begPos ; i < endPos; i++ ) {
+         results[index++] = this.rows[i]
+      }
+      return results
+   },
+
+   convertSpaces: function(s) {
+      return s.split(" ").join("&nbsp;");
+   }
+
+};
+
+
+//Rico.GridViewPort --------------------------------------------------
+Rico.GridViewPort = Class.create();
+
+Rico.GridViewPort.prototype = {
+
+   initialize: function(table, rowHeight, visibleRows, buffer, liveGrid) {
+      this.lastDisplayedStartPos = 0;
+      this.div = table.parentNode;
+      this.table = table
+      this.rowHeight = rowHeight;
+      this.div.style.height = this.rowHeight * visibleRows;
+      this.div.style.overflow = "hidden";
+      this.buffer = buffer;
+      this.liveGrid = liveGrid;
+      this.visibleRows = visibleRows + 1;
+      this.lastPixelOffset = 0;
+      this.startPos = 0;
+   },
+
+   populateRow: function(htmlRow, row) {
+      for (var j=0; j < row.length; j++) {
+         htmlRow.cells[j].innerHTML = row[j]
+      }
+   },
+   
+   bufferChanged: function() {
+      this.refreshContents( parseInt(this.lastPixelOffset / this.rowHeight));
+   },
+   
+   clearRows: function() {
+      if (!this.isBlank) {
+         for (var i=0; i < this.visibleRows; i++)
+            this.populateRow(this.table.rows[i], this.buffer.getBlankRow());
+         this.isBlank = true;
+      }
+   },
+   
+   clearContents: function() {   
+      this.clearRows();
+      this.scrollTo(0);
+      this.startPos = 0;
+      this.lastStartPos = -1;   
+   },
+   
+   refreshContents: function(startPos) {
+      if (startPos == this.lastRowPos && !this.isPartialBlank && !this.isBlank) {
+         return;
+      }
+      if ((startPos + this.visibleRows < this.buffer.startPos)  
+          || (this.buffer.startPos + this.buffer.size < startPos) 
+          || (this.buffer.size == 0)) {
+         this.clearRows();
+         return;
+      }
+      this.isBlank = false;
+      var viewPrecedesBuffer = this.buffer.startPos > startPos
+      var contentStartPos = viewPrecedesBuffer ? this.buffer.startPos: startPos;
+   
+      var contentEndPos = (this.buffer.startPos + this.buffer.size < startPos + this.visibleRows) 
+                                 ? this.buffer.startPos + this.buffer.size
+                                 : startPos + this.visibleRows;       
+      var rowSize = contentEndPos - contentStartPos;
+      var rows = this.buffer.getRows(contentStartPos, rowSize ); 
+      var blankSize = this.visibleRows - rowSize;
+      var blankOffset = viewPrecedesBuffer ? 0: rowSize;
+      var contentOffset = viewPrecedesBuffer ? blankSize: 0;
+
+      for (var i=0; i < rows.length; i++) {//initialize what we have
+        this.populateRow(this.table.rows[i + contentOffset], rows[i]);
+      }       
+      for (var i=0; i < blankSize; i++) {// blank out the rest 
+        this.populateRow(this.table.rows[i + blankOffset], this.buffer.getBlankRow());
+      }
+      this.isPartialBlank = blankSize > 0;
+      this.lastRowPos = startPos;   
+   },
+
+   scrollTo: function(pixelOffset) {      
+      if (this.lastPixelOffset == pixelOffset)
+         return;
+
+      this.refreshContents(parseInt(pixelOffset / this.rowHeight))
+      this.div.scrollTop = pixelOffset % this.rowHeight        
+      
+      this.lastPixelOffset = pixelOffset;
+   },
+   
+   visibleHeight: function() {
+      return parseInt(this.div.style.height);
+   }
+   
+};
+
+
+Rico.LiveGridRequest = Class.create();
+Rico.LiveGridRequest.prototype = {
+   initialize: function( requestOffset, options ) {
+      this.requestOffset = requestOffset;
+   }
+};
+
+// Rico.LiveGrid -----------------------------------------------------
+
+Rico.LiveGrid = Class.create();
+
+Rico.LiveGrid.prototype = {
+
+   initialize: function( tableId, visibleRows, totalRows, url, options ) {
+      if ( options == null )
+         options = {};
+
+      this.tableId     = tableId; 
+      this.table       = $(tableId);
+      var columnCount  = this.table.rows[0].cells.length
+      this.metaData    = new Rico.LiveGridMetaData(visibleRows, totalRows, columnCount, options);
+      this.buffer      = new Rico.LiveGridBuffer(this.metaData);
+
+      var rowCount = this.table.rows.length;
+      this.viewPort =  new Rico.GridViewPort(this.table, 
+                                            this.table.offsetHeight/rowCount,
+                                            visibleRows,
+                                            this.buffer, this);
+      this.scroller    = new Rico.LiveGridScroller(this,this.viewPort);
+      
+      this.additionalParms       = options.requestParameters || [];
+      
+      options.sortHandler = this.sortHandler.bind(this);
+
+      if ( $(tableId + '_header') )
+         this.sort = new Rico.LiveGridSort(tableId + '_header', options)
+
+      this.processingRequest = null;
+      this.unprocessedRequest = null;
+
+      this.initAjax(url);
+      if ( options.prefetchBuffer || options.prefetchOffset > 0) {
+         var offset = 0;
+         if (options.offset ) {
+            offset = options.offset;            
+            this.scroller.moveScroll(offset);
+            this.viewPort.scrollTo(this.scroller.rowToPixel(offset));            
+         }
+         if (options.sortCol) {
+             this.sortCol = options.sortCol;
+             this.sortDir = options.sortDir;
+         }
+         this.requestContentRefresh(offset);
+      }
+   },
+
+   resetContents: function() {
+      this.scroller.moveScroll(0);
+      this.buffer.clear();
+      this.viewPort.clearContents();
+   },
+   
+   sortHandler: function(column) {
+      this.sortCol = column.name;
+      this.sortDir = column.currentSort;
+
+      this.resetContents();
+      this.requestContentRefresh(0) 
+   },
+   
+   setRequestParams: function() {
+      this.additionalParms = [];
+      for ( var i=0 ; i < arguments.length ; i++ )
+         this.additionalParms[i] = arguments[i];
+   },
+
+   setTotalRows: function( newTotalRows ) {
+      this.resetContents();
+      this.metaData.setTotalRows(newTotalRows);
+      this.scroller.updateSize();
+   },
+
+   initAjax: function(url) {
+      ajaxEngine.registerRequest( this.tableId + '_request', url );
+      ajaxEngine.registerAjaxObject( this.tableId + '_updater', this );
+   },
+
+   invokeAjax: function() {
+   },
+
+   handleTimedOut: function() {
+      //server did not respond in 4 seconds... assume that there could have been
+      //an error or something, and allow requests to be processed again...
+      this.processingRequest = null;
+      this.processQueuedRequest();
+   },
+
+   fetchBuffer: function(offset) {
+      if ( this.buffer.isInRange(offset) &&
+         !this.buffer.isNearingLimit(offset)) {
+         return;
+      }
+      if (this.processingRequest) {
+          this.unprocessedRequest = new Rico.LiveGridRequest(offset);
+         return;
+      }
+      var bufferStartPos = this.buffer.getFetchOffset(offset);
+      this.processingRequest = new Rico.LiveGridRequest(offset);
+      this.processingRequest.bufferOffset = bufferStartPos;   
+      var fetchSize = this.buffer.getFetchSize(offset);
+      var partialLoaded = false;
+      var callParms = []; 
+      callParms.push(this.tableId + '_request');
+      callParms.push('id='        + this.tableId);
+      callParms.push('page_size=' + fetchSize);
+      callParms.push('offset='    + bufferStartPos);
+      if ( this.sortCol) {
+         callParms.push('sort_col='    + this.sortCol);
+         callParms.push('sort_dir='    + this.sortDir);
+      }
+      
+      for( var i=0 ; i < this.additionalParms.length ; i++ )
+         callParms.push(this.additionalParms[i]);
+      ajaxEngine.sendRequest.apply( ajaxEngine, callParms );
+        
+      this.timeoutHandler = setTimeout( this.handleTimedOut.bind(this), 20000 ); //todo: make as option
+   },
+
+   requestContentRefresh: function(contentOffset) {
+      this.fetchBuffer(contentOffset);
+   },
+
+   ajaxUpdate: function(ajaxResponse) {
+      try {
+         clearTimeout( this.timeoutHandler );
+         this.buffer.update(ajaxResponse,this.processingRequest.bufferOffset);
+         this.viewPort.bufferChanged();
+      }
+      catch(err) {}
+      finally {this.processingRequest = null; }
+      this.processQueuedRequest();
+   },
+
+   processQueuedRequest: function() {
+      if (this.unprocessedRequest != null) {
+         this.requestContentRefresh(this.unprocessedRequest.requestOffset);
+         this.unprocessedRequest = null
+      }  
+   }
+ 
+};
+
+
+//-------------------- ricoLiveGridSort.js
+Rico.LiveGridSort = Class.create();
+
+Rico.LiveGridSort.prototype = {
+
+   initialize: function(headerTableId, options) {
+      this.headerTableId = headerTableId;
+      this.headerTable   = $(headerTableId);
+      this.setOptions(options);
+      this.applySortBehavior();
+
+      if ( this.options.sortCol ) {
+         this.setSortUI( this.options.sortCol, this.options.sortDir );
+      }
+   },
+
+   setSortUI: function( columnName, sortDirection ) {
+      var cols = this.options.columns;
+      for ( var i = 0 ; i < cols.length ; i++ ) {
+         if ( cols[i].name == columnName ) {
+            this.setColumnSort(i, sortDirection);
+            break;
+         }
+      }
+   },
+
+   setOptions: function(options) {
+      this.options = {
+         sortAscendImg:    'images/sort_asc.gif',
+         sortDescendImg:   'images/sort_desc.gif',
+         imageWidth:       9,
+         imageHeight:      5,
+         ajaxSortURLParms: []
+      }.extend(options);
+
+      // preload the images...
+      new Image().src = this.options.sortAscendImg;
+      new Image().src = this.options.sortDescendImg;
+
+      this.sort = options.sortHandler;
+      if ( !this.options.columns )
+         this.options.columns = this.introspectForColumnInfo();
+      else {
+         // allow client to pass { columns: [ ["a", true], ["b", false] ] }
+         // and convert to an array of Rico.TableColumn objs...
+         this.options.columns = this.convertToTableColumns(this.options.columns);
+      }
+   },
+
+   applySortBehavior: function() {
+      var headerRow   = this.headerTable.rows[0];
+      var headerCells = headerRow.cells;
+      for ( var i = 0 ; i < headerCells.length ; i++ ) {
+         this.addSortBehaviorToColumn( i, headerCells[i] );
+      }
+   },
+
+   addSortBehaviorToColumn: function( n, cell ) {
+      if ( this.options.columns[n].isSortable() ) {
+         cell.id            = this.headerTableId + '_' + n;
+         cell.style.cursor  = 'pointer';
+         cell.onclick       = this.headerCellClicked.bindAsEventListener(this);
+         cell.innerHTML     = cell.innerHTML + '<span id="' + this.headerTableId + '_img_' + n + '">'
+                           + '&nbsp;&nbsp;&nbsp;</span>';
+      }
+   },
+
+   // event handler....
+   headerCellClicked: function(evt) {
+      var eventTarget = evt.target ? evt.target : evt.srcElement;
+      var cellId = eventTarget.id;
+      var columnNumber = parseInt(cellId.substring( cellId.lastIndexOf('_') + 1 ));
+      var sortedColumnIndex = this.getSortedColumnIndex();
+      if ( sortedColumnIndex != -1 ) {
+         if ( sortedColumnIndex != columnNumber ) {
+            this.removeColumnSort(sortedColumnIndex);
+            this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);
+         }
+         else
+            this.toggleColumnSort(sortedColumnIndex);
+      }
+      else
+         this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);
+
+      if (this.options.sortHandler) {
+         this.options.sortHandler(this.options.columns[columnNumber]);
+      }
+   },
+
+   removeColumnSort: function(n) {
+      this.options.columns[n].setUnsorted();
+      this.setSortImage(n);
+   },
+
+   setColumnSort: function(n, direction) {
+      this.options.columns[n].setSorted(direction);
+      this.setSortImage(n);
+   },
+
+   toggleColumnSort: function(n) {
+      this.options.columns[n].toggleSort();
+      this.setSortImage(n);
+   },
+
+   setSortImage: function(n) {
+      var sortDirection = this.options.columns[n].getSortDirection();
+
+      var sortImageSpan = $( this.headerTableId + '_img_' + n );
+      if ( sortDirection == Rico.TableColumn.UNSORTED )
+         sortImageSpan.innerHTML = '&nbsp;&nbsp;';
+      else if ( sortDirection == Rico.TableColumn.SORT_ASC )
+         sortImageSpan.innerHTML = '&nbsp;&nbsp;<img width="'  + this.options.imageWidth    + '" ' +
+                                                     'height="'+ this.options.imageHeight   + '" ' +
+                                                     'src="'   + this.options.sortAscendImg + '"/>';
+      else if ( sortDirection == Rico.TableColumn.SORT_DESC )
+         sortImageSpan.innerHTML = '&nbsp;&nbsp;<img width="'  + this.options.imageWidth    + '" ' +
+                                                     'height="'+ this.options.imageHeight   + '" ' +
+                                                     'src="'   + this.options.sortDescendImg + '"/>';
+   },
+
+   getSortedColumnIndex: function() {
+      var cols = this.options.columns;
+      for ( var i = 0 ; i < cols.length ; i++ ) {
+         if ( cols[i].isSorted() )
+            return i;
+      }
+
+      return -1;
+   },
+
+   introspectForColumnInfo: function() {
+      var columns = new Array();
+      var headerRow   = this.headerTable.rows[0];
+      var headerCells = headerRow.cells;
+      for ( var i = 0 ; i < headerCells.length ; i++ )
+         columns.push( new Rico.TableColumn( this.deriveColumnNameFromCell(headerCells[i],i), true ) );
+      return columns;
+   },
+
+   convertToTableColumns: function(cols) {
+      var columns = new Array();
+      for ( var i = 0 ; i < cols.length ; i++ )
+         columns.push( new Rico.TableColumn( cols[i][0], cols[i][1] ) );
+   },
+
+   deriveColumnNameFromCell: function(cell,columnNumber) {
+      var cellContent = cell.innerText != undefined ? cell.innerText : cell.textContent;
+      return cellContent ? cellContent.toLowerCase().split(' ').join('_') : "col_" + columnNumber;
+   }
+};
+
+Rico.TableColumn = Class.create();
+
+Rico.TableColumn.UNSORTED  = 0;
+Rico.TableColumn.SORT_ASC  = "ASC";
+Rico.TableColumn.SORT_DESC = "DESC";
+
+Rico.TableColumn.prototype = {
+   initialize: function(name, sortable) {
+      this.name        = name;
+      this.sortable    = sortable;
+      this.currentSort = Rico.TableColumn.UNSORTED;
+   },
+
+   isSortable: function() {
+      return this.sortable;
+   },
+
+   isSorted: function() {
+      return this.currentSort != Rico.TableColumn.UNSORTED;
+   },
+
+   getSortDirection: function() {
+      return this.currentSort;
+   },
+
+   toggleSort: function() {
+      if ( this.currentSort == Rico.TableColumn.UNSORTED || this.currentSort == Rico.TableColumn.SORT_DESC )
+         this.currentSort = Rico.TableColumn.SORT_ASC;
+      else if ( this.currentSort == Rico.TableColumn.SORT_ASC )
+         this.currentSort = Rico.TableColumn.SORT_DESC;
+   },
+
+   setUnsorted: function(direction) {
+      this.setSorted(Rico.TableColumn.UNSORTED);
+   },
+
+   setSorted: function(direction) {
+      // direction must by one of Rico.TableColumn.UNSORTED, .SORT_ASC, or .SET_DESC...
+      this.currentSort = direction;
+   }
+
+};
+
+
+//-------------------- ricoUtil.js
+
+var RicoUtil = {
+
+   getElementsComputedStyle: function ( htmlElement, cssProperty, mozillaEquivalentCSS) {
+      if ( arguments.length == 2 )
+         mozillaEquivalentCSS = cssProperty;
+
+      var el = $(htmlElement);
+      if ( el.currentStyle )
+         return el.currentStyle[cssProperty];
+      else
+         return document.defaultView.getComputedStyle(el, null).getPropertyValue(mozillaEquivalentCSS);
+   },
+
+   createXmlDocument : function() {
+      if (document.implementation && document.implementation.createDocument) {
+         var doc = document.implementation.createDocument("", "", null);
+
+         if (doc.readyState == null) {
+            doc.readyState = 1;
+            doc.addEventListener("load", function () {
+               doc.readyState = 4;
+               if (typeof doc.onreadystatechange == "function")
+                  doc.onreadystatechange();
+            }, false);
+         }
+
+         return doc;
+      }
+
+      if (window.ActiveXObject)
+          return Try.these(
+            function() { return new ActiveXObject('MSXML2.DomDocument')   },
+            function() { return new ActiveXObject('Microsoft.DomDocument')},
+            function() { return new ActiveXObject('MSXML.DomDocument')    },
+            function() { return new ActiveXObject('MSXML3.DomDocument')   }
+          ) || false;
+
+      return null;
+   },
+
+   getContentAsString: function( parentNode ) {
+      return parentNode.xml != undefined ? 
+         this._getContentAsStringIE(parentNode) :
+         this._getContentAsStringMozilla(parentNode);
+   },
+
+   _getContentAsStringIE: function(parentNode) {
+      var contentStr = "";
+      for ( var i = 0 ; i < parentNode.childNodes.length ; i++ )
+         contentStr += parentNode.childNodes[i].xml;
+      return contentStr;
+   },
+
+   _getContentAsStringMozilla: function(parentNode) {
+      var xmlSerializer = new XMLSerializer();
+      var contentStr = "";
+      for ( var i = 0 ; i < parentNode.childNodes.length ; i++ )
+         contentStr += xmlSerializer.serializeToString(parentNode.childNodes[i]);
+      return contentStr;
+   },
+
+   toViewportPosition: function(element) {
+      return this._toAbsolute(element,true);
+   },
+
+   toDocumentPosition: function(element) {
+      return this._toAbsolute(element,false);
+   },
+
+   /**
+    *  Compute the elements position in terms of the window viewport
+    *  so that it can be compared to the position of the mouse (dnd)
+    *  This is additions of all the offsetTop,offsetLeft values up the
+    *  offsetParent hierarchy, ...taking into account any scrollTop,
+    *  scrollLeft values along the way...
+    *
+    * IE has a bug reporting a correct offsetLeft of elements within a
+    * a relatively positioned parent!!!
+    **/
+   _toAbsolute: function(element,accountForDocScroll) {
+
+      if ( navigator.userAgent.toLowerCase().indexOf("msie") == -1 )
+         return this._toAbsoluteMozilla(element,accountForDocScroll);
+
+      var x = 0;
+      var y = 0;
+      var parent = element;
+      while ( parent ) {
+
+         var borderXOffset = 0;
+         var borderYOffset = 0;
+         if ( parent != element ) {
+            var borderXOffset = parseInt(this.getElementsComputedStyle(parent, "borderLeftWidth" ));
+            var borderYOffset = parseInt(this.getElementsComputedStyle(parent, "borderTopWidth" ));
+            borderXOffset = isNaN(borderXOffset) ? 0 : borderXOffset;
+            borderYOffset = isNaN(borderYOffset) ? 0 : borderYOffset;
+         }
+
+         x += parent.offsetLeft - parent.scrollLeft + borderXOffset;
+         y += parent.offsetTop - parent.scrollTop + borderYOffset;
+         parent = parent.offsetParent;
+      }
+
+      if ( accountForDocScroll ) {
+         x -= this.docScrollLeft();
+         y -= this.docScrollTop();
+      }
+
+      return { x:x, y:y };
+   },
+
+   /**
+    *  Mozilla did not report all of the parents up the hierarchy via the
+    *  offsetParent property that IE did.  So for the calculation of the
+    *  offsets we use the offsetParent property, but for the calculation of
+    *  the scrollTop/scrollLeft adjustments we navigate up via the parentNode
+    *  property instead so as to get the scroll offsets...
+    *
+    **/
+   _toAbsoluteMozilla: function(element,accountForDocScroll) {
+      var x = 0;
+      var y = 0;
+      var parent = element;
+      while ( parent ) {
+         x += parent.offsetLeft;
+         y += parent.offsetTop;
+         parent = parent.offsetParent;
+      }
+
+      parent = element;
+      while ( parent &&
+              parent != document.body &&
+              parent != document.documentElement ) {
+         if ( parent.scrollLeft  )
+            x -= parent.scrollLeft;
+         if ( parent.scrollTop )
+            y -= parent.scrollTop;
+         parent = parent.parentNode;
+      }
+
+      if ( accountForDocScroll ) {
+         x -= this.docScrollLeft();
+         y -= this.docScrollTop();
+      }
+
+      return { x:x, y:y };
+   },
+
+   docScrollLeft: function() {
+      if ( window.pageXOffset )
+         return window.pageXOffset;
+      else if ( document.documentElement && document.documentElement.scrollLeft )
+         return document.documentElement.scrollLeft;
+      else if ( document.body )
+         return document.body.scrollLeft;
+      else
+         return 0;
+   },
+
+   docScrollTop: function() {
+      if ( window.pageYOffset )
+         return window.pageYOffset;
+      else if ( document.documentElement && document.documentElement.scrollTop )
+         return document.documentElement.scrollTop;
+      else if ( document.body )
+         return document.body.scrollTop;
+      else
+         return 0;
+   }
+
+};
Index: /branches/util-apps/DomainUtil/web-app/js/prototype/scriptaculous.js
===================================================================
--- /branches/util-apps/DomainUtil/web-app/js/prototype/scriptaculous.js	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/js/prototype/scriptaculous.js	(revision 766)
@@ -0,0 +1,68 @@
+// script.aculo.us scriptaculous.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// 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.
+//
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Scriptaculous = {
+  Version: '1.8.3',
+  require: function(libraryName) {
+    try{
+      // inserting via DOM fails in Safari 2.0, so brute force approach
+      document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
+    } catch(e) {
+      // for xhtml+xml served content, fall back to DOM methods
+      var script = document.createElement('script');
+      script.type = 'text/javascript';
+      script.src = libraryName;
+      document.getElementsByTagName('head')[0].appendChild(script);
+    }
+  },
+  REQUIRED_PROTOTYPE: '1.6.0.3',
+  load: function() {
+    function convertVersionString(versionString) {
+      var v = versionString.replace(/_.*|\./g, '');
+      v = parseInt(v + '0'.times(4-v.length));
+      return versionString.indexOf('_') > -1 ? v-1 : v;
+    }
+
+    if((typeof Prototype=='undefined') ||
+       (typeof Element == 'undefined') ||
+       (typeof Element.Methods=='undefined') ||
+       (convertVersionString(Prototype.Version) <
+        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
+       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
+        Scriptaculous.REQUIRED_PROTOTYPE);
+
+    var js = /scriptaculous\.js(\?.*)?$/;
+    $$('head script[src]').findAll(function(s) {
+      return s.src.match(js);
+    }).each(function(s) {
+      var path = s.src.replace(js, ''),
+      includes = s.src.match(/\?.*load=([a-z,]*)/);
+      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
+       function(include) { Scriptaculous.require(path+include+'.js') });
+    });
+  }
+};
+
+Scriptaculous.load();
Index: /branches/util-apps/DomainUtil/web-app/js/prototype/slider.js
===================================================================
--- /branches/util-apps/DomainUtil/web-app/js/prototype/slider.js	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/js/prototype/slider.js	(revision 766)
@@ -0,0 +1,275 @@
+// script.aculo.us slider.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 Marty Haught, Thomas Fuchs
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+if (!Control) var Control = { };
+
+// options:
+//  axis: 'vertical', or 'horizontal' (default)
+//
+// callbacks:
+//  onChange(value)
+//  onSlide(value)
+Control.Slider = Class.create({
+  initialize: function(handle, track, options) {
+    var slider = this;
+
+    if (Object.isArray(handle)) {
+      this.handles = handle.collect( function(e) { return $(e) });
+    } else {
+      this.handles = [$(handle)];
+    }
+
+    this.track   = $(track);
+    this.options = options || { };
+
+    this.axis      = this.options.axis || 'horizontal';
+    this.increment = this.options.increment || 1;
+    this.step      = parseInt(this.options.step || '1');
+    this.range     = this.options.range || $R(0,1);
+
+    this.value     = 0; // assure backwards compat
+    this.values    = this.handles.map( function() { return 0 });
+    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
+    this.options.startSpan = $(this.options.startSpan || null);
+    this.options.endSpan   = $(this.options.endSpan || null);
+
+    this.restricted = this.options.restricted || false;
+
+    this.maximum   = this.options.maximum || this.range.end;
+    this.minimum   = this.options.minimum || this.range.start;
+
+    // Will be used to align the handle onto the track, if necessary
+    this.alignX = parseInt(this.options.alignX || '0');
+    this.alignY = parseInt(this.options.alignY || '0');
+
+    this.trackLength = this.maximumOffset() - this.minimumOffset();
+
+    this.handleLength = this.isVertical() ?
+      (this.handles[0].offsetHeight != 0 ?
+        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
+      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
+        this.handles[0].style.width.replace(/px$/,""));
+
+    this.active   = false;
+    this.dragging = false;
+    this.disabled = false;
+
+    if (this.options.disabled) this.setDisabled();
+
+    // Allowed values array
+    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
+    if (this.allowedValues) {
+      this.minimum = this.allowedValues.min();
+      this.maximum = this.allowedValues.max();
+    }
+
+    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
+    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+    this.eventMouseMove = this.update.bindAsEventListener(this);
+
+    // Initialize handles in reverse (make sure first handle is active)
+    this.handles.each( function(h,i) {
+      i = slider.handles.length-1-i;
+      slider.setValue(parseFloat(
+        (Object.isArray(slider.options.sliderValue) ?
+          slider.options.sliderValue[i] : slider.options.sliderValue) ||
+         slider.range.start), i);
+      h.makePositioned().observe("mousedown", slider.eventMouseDown);
+    });
+
+    this.track.observe("mousedown", this.eventMouseDown);
+    document.observe("mouseup", this.eventMouseUp);
+    document.observe("mousemove", this.eventMouseMove);
+
+    this.initialized = true;
+  },
+  dispose: function() {
+    var slider = this;
+    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
+    Event.stopObserving(document, "mouseup", this.eventMouseUp);
+    Event.stopObserving(document, "mousemove", this.eventMouseMove);
+    this.handles.each( function(h) {
+      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
+    });
+  },
+  setDisabled: function(){
+    this.disabled = true;
+  },
+  setEnabled: function(){
+    this.disabled = false;
+  },
+  getNearestValue: function(value){
+    if (this.allowedValues){
+      if (value >= this.allowedValues.max()) return(this.allowedValues.max());
+      if (value <= this.allowedValues.min()) return(this.allowedValues.min());
+
+      var offset = Math.abs(this.allowedValues[0] - value);
+      var newValue = this.allowedValues[0];
+      this.allowedValues.each( function(v) {
+        var currentOffset = Math.abs(v - value);
+        if (currentOffset <= offset){
+          newValue = v;
+          offset = currentOffset;
+        }
+      });
+      return newValue;
+    }
+    if (value > this.range.end) return this.range.end;
+    if (value < this.range.start) return this.range.start;
+    return value;
+  },
+  setValue: function(sliderValue, handleIdx){
+    if (!this.active) {
+      this.activeHandleIdx = handleIdx || 0;
+      this.activeHandle    = this.handles[this.activeHandleIdx];
+      this.updateStyles();
+    }
+    handleIdx = handleIdx || this.activeHandleIdx || 0;
+    if (this.initialized && this.restricted) {
+      if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
+        sliderValue = this.values[handleIdx-1];
+      if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
+        sliderValue = this.values[handleIdx+1];
+    }
+    sliderValue = this.getNearestValue(sliderValue);
+    this.values[handleIdx] = sliderValue;
+    this.value = this.values[0]; // assure backwards compat
+
+    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
+      this.translateToPx(sliderValue);
+
+    this.drawSpans();
+    if (!this.dragging || !this.event) this.updateFinished();
+  },
+  setValueBy: function(delta, handleIdx) {
+    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
+      handleIdx || this.activeHandleIdx || 0);
+  },
+  translateToPx: function(value) {
+    return Math.round(
+      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
+      (value - this.range.start)) + "px";
+  },
+  translateToValue: function(offset) {
+    return ((offset/(this.trackLength-this.handleLength) *
+      (this.range.end-this.range.start)) + this.range.start);
+  },
+  getRange: function(range) {
+    var v = this.values.sortBy(Prototype.K);
+    range = range || 0;
+    return $R(v[range],v[range+1]);
+  },
+  minimumOffset: function(){
+    return(this.isVertical() ? this.alignY : this.alignX);
+  },
+  maximumOffset: function(){
+    return(this.isVertical() ?
+      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
+        this.track.style.height.replace(/px$/,"")) - this.alignY :
+      (this.track.offsetWidth != 0 ? this.track.offsetWidth :
+        this.track.style.width.replace(/px$/,"")) - this.alignX);
+  },
+  isVertical:  function(){
+    return (this.axis == 'vertical');
+  },
+  drawSpans: function() {
+    var slider = this;
+    if (this.spans)
+      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
+    if (this.options.startSpan)
+      this.setSpan(this.options.startSpan,
+        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
+    if (this.options.endSpan)
+      this.setSpan(this.options.endSpan,
+        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
+  },
+  setSpan: function(span, range) {
+    if (this.isVertical()) {
+      span.style.top = this.translateToPx(range.start);
+      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
+    } else {
+      span.style.left = this.translateToPx(range.start);
+      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
+    }
+  },
+  updateStyles: function() {
+    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
+    Element.addClassName(this.activeHandle, 'selected');
+  },
+  startDrag: function(event) {
+    if (Event.isLeftClick(event)) {
+      if (!this.disabled){
+        this.active = true;
+
+        var handle = Event.element(event);
+        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
+        var track = handle;
+        if (track==this.track) {
+          var offsets  = this.track.cumulativeOffset();
+          this.event = event;
+          this.setValue(this.translateToValue(
+           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
+          ));
+          var offsets  = this.activeHandle.cumulativeOffset();
+          this.offsetX = (pointer[0] - offsets[0]);
+          this.offsetY = (pointer[1] - offsets[1]);
+        } else {
+          // find the handle (prevents issues with Safari)
+          while((this.handles.indexOf(handle) == -1) && handle.parentNode)
+            handle = handle.parentNode;
+
+          if (this.handles.indexOf(handle)!=-1) {
+            this.activeHandle    = handle;
+            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
+            this.updateStyles();
+
+            var offsets  = this.activeHandle.cumulativeOffset();
+            this.offsetX = (pointer[0] - offsets[0]);
+            this.offsetY = (pointer[1] - offsets[1]);
+          }
+        }
+      }
+      Event.stop(event);
+    }
+  },
+  update: function(event) {
+   if (this.active) {
+      if (!this.dragging) this.dragging = true;
+      this.draw(event);
+      if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+      Event.stop(event);
+   }
+  },
+  draw: function(event) {
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
+    var offsets = this.track.cumulativeOffset();
+    pointer[0] -= this.offsetX + offsets[0];
+    pointer[1] -= this.offsetY + offsets[1];
+    this.event = event;
+    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
+    if (this.initialized && this.options.onSlide)
+      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
+  },
+  endDrag: function(event) {
+    if (this.active && this.dragging) {
+      this.finishDrag(event, true);
+      Event.stop(event);
+    }
+    this.active = false;
+    this.dragging = false;
+  },
+  finishDrag: function(event, success) {
+    this.active = false;
+    this.dragging = false;
+    this.updateFinished();
+  },
+  updateFinished: function() {
+    if (this.initialized && this.options.onChange)
+      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
+    this.event = null;
+  }
+});
Index: /branches/util-apps/DomainUtil/web-app/js/prototype/sound.js
===================================================================
--- /branches/util-apps/DomainUtil/web-app/js/prototype/sound.js	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/js/prototype/sound.js	(revision 766)
@@ -0,0 +1,59 @@
+// script.aculo.us sound.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// Based on code created by Jules Gravinese (http://www.webveteran.com/)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+Sound = {
+  tracks: {},
+  _enabled: true,
+  template:
+    new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
+  enable: function(){
+    Sound._enabled = true;
+  },
+  disable: function(){
+    Sound._enabled = false;
+  },
+  play: function(url){
+    if(!Sound._enabled) return;
+    var options = Object.extend({
+      track: 'global', url: url, replace: false
+    }, arguments[1] || {});
+
+    if(options.replace && this.tracks[options.track]) {
+      $R(0, this.tracks[options.track].id).each(function(id){
+        var sound = $('sound_'+options.track+'_'+id);
+        sound.Stop && sound.Stop();
+        sound.remove();
+      });
+      this.tracks[options.track] = null;
+    }
+
+    if(!this.tracks[options.track])
+      this.tracks[options.track] = { id: 0 };
+    else
+      this.tracks[options.track].id++;
+
+    options.id = this.tracks[options.track].id;
+    $$('body')[0].insert(
+      Prototype.Browser.IE ? new Element('bgsound',{
+        id: 'sound_'+options.track+'_'+options.id,
+        src: options.url, loop: 1, autostart: true
+      }) : Sound.template.evaluate(options));
+  }
+};
+
+if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
+  if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
+    Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>');
+  else if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('Windows Media') != -1 }))
+    Sound.template = new Template('<object id="sound_#{track}_#{id}" type="application/x-mplayer2" data="#{url}"></object>');
+  else if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('RealPlayer') != -1 }))
+    Sound.template = new Template('<embed type="audio/x-pn-realaudio-plugin" style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>');
+  else
+    Sound.play = function(){};
+} 
Index: /branches/util-apps/DomainUtil/web-app/js/prototype/unittest.js
===================================================================
--- /branches/util-apps/DomainUtil/web-app/js/prototype/unittest.js	(revision 766)
+++ /branches/util-apps/DomainUtil/web-app/js/prototype/unittest.js	(revision 766)
@@ -0,0 +1,568 @@
+// script.aculo.us unittest.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
+
+// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2009 Jon Tirsen (http://www.tirsen.com)
+//           (c) 2005-2009 Michael Schuerig (http://www.schuerig.de/michael/)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// experimental, Firefox-only
+Event.simulateMouse = function(element, eventName) {
+  var options = Object.extend({
+    pointerX: 0,
+    pointerY: 0,
+    buttons:  0,
+    ctrlKey:  false,
+    altKey:   false,
+    shiftKey: false,
+    metaKey:  false
+  }, arguments[2] || {});
+  var oEvent = document.createEvent("MouseEvents");
+  oEvent.initMouseEvent(eventName, true, true, document.defaultView, 
+    options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, 
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
+  
+  if(this.mark) Element.remove(this.mark);
+  this.mark = document.createElement('div');
+  this.mark.appendChild(document.createTextNode(" "));
+  document.body.appendChild(this.mark);
+  this.mark.style.position = 'absolute';
+  this.mark.style.top = options.pointerY + "px";
+  this.mark.style.left = options.pointerX + "px";
+  this.mark.style.width = "5px";
+  this.mark.style.height = "5px;";
+  this.mark.style.borderTop = "1px solid red;";
+  this.mark.style.borderLeft = "1px solid red;";
+  
+  if(this.step)
+    alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
+  
+  $(element).dispatchEvent(oEvent);
+};
+
+// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
+// You need to downgrade to 1.0.4 for now to get this working
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
+Event.simulateKey = function(element, eventName) {
+  var options = Object.extend({
+    ctrlKey: false,
+    altKey: false,
+    shiftKey: false,
+    metaKey: false,
+    keyCode: 0,
+    charCode: 0
+  }, arguments[2] || {});
+
+  var oEvent = document.createEvent("KeyEvents");
+  oEvent.initKeyEvent(eventName, true, true, window, 
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
+    options.keyCode, options.charCode );
+  $(element).dispatchEvent(oEvent);
+};
+
+Event.simulateKeys = function(element, command) {
+  for(var i=0; i<command.length; i++) {
+    Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
+  }
+};
+
+var Test = {};
+Test.Unit = {};
+
+// security exception workaround
+Test.Unit.inspect = Object.inspect;
+
+Test.Unit.Logger = Class.create();
+Test.Unit.Logger.prototype = {
+  initialize: function(log) {
+    this.log = $(log);
+    if (this.log) {
+      this._createLogTable();
+    }
+  },
+  start: function(testName) {
+    if (!this.log) return;
+    this.testName = testName;
+    this.lastLogLine = document.createElement('tr');
+    this.statusCell = document.createElement('td');
+    this.nameCell = document.createElement('td');
+    this.nameCell.className = "nameCell";
+    this.nameCell.appendChild(document.createTextNode(testName));
+    this.messageCell = document.createElement('td');
+    this.lastLogLine.appendChild(this.statusCell);
+    this.lastLogLine.appendChild(this.nameCell);
+    this.lastLogLine.appendChild(this.messageCell);
+    this.loglines.appendChild(this.lastLogLine);
+  },
+  finish: function(status, summary) {
+    if (!this.log) return;
+    this.lastLogLine.className = status;
+    this.statusCell.innerHTML = status;
+    this.messageCell.innerHTML = this._toHTML(summary);
+    this.addLinksToResults();
+  },
+  message: function(message) {
+    if (!this.log) return;
+    this.messageCell.innerHTML = this._toHTML(message);
+  },
+  summary: function(summary) {
+    if (!this.log) return;
+    this.logsummary.innerHTML = this._toHTML(summary);
+  },
+  _createLogTable: function() {
+    this.log.innerHTML =
+    '<div id="logsummary"></div>' +
+    '<table id="logtable">' +
+    '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
+    '<tbody id="loglines"></tbody>' +
+    '</table>';
+    this.logsummary = $('logsummary');
+    this.loglines = $('loglines');
+  },
+  _toHTML: function(txt) {
+    return txt.escapeHTML().replace(/\n/g,"<br/>");
+  },
+  addLinksToResults: function(){ 
+    $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
+      td.title = "Run only this test";
+      Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
+    });
+    $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
+      td.title = "Run all tests";
+      Event.observe(td, 'click', function(){ window.location.search = "";});
+    });
+  }
+};
+
+Test.Unit.Runner = Class.create();
+Test.Unit.Runner.prototype = {
+  initialize: function(testcases) {
+    this.options = Object.extend({
+      testLog: 'testlog'
+    }, arguments[1] || {});
+    this.options.resultsURL = this.parseResultsURLQueryParameter();
+    this.options.tests      = this.parseTestsQueryParameter();
+    if (this.options.testLog) {
+      this.options.testLog = $(this.options.testLog) || null;
+    }
+    if(this.options.tests) {
+      this.tests = [];
+      for(var i = 0; i < this.options.tests.length; i++) {
+        if(/^test/.test(this.options.tests[i])) {
+          this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
+        }
+      }
+    } else {
+      if (this.options.test) {
+        this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
+      } else {
+        this.tests = [];
+        for(var testcase in testcases) {
+          if(/^test/.test(testcase)) {
+            this.tests.push(
+               new Test.Unit.Testcase(
+                 this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, 
+                 testcases[testcase], testcases["setup"], testcases["teardown"]
+               ));
+          }
+        }
+      }
+    }
+    this.currentTest = 0;
+    this.logger = new Test.Unit.Logger(this.options.testLog);
+    setTimeout(this.runTests.bind(this), 1000);
+  },
+  parseResultsURLQueryParameter: function() {
+    return window.location.search.parseQuery()["resultsURL"];
+  },
+  parseTestsQueryParameter: function(){
+    if (window.location.search.parseQuery()["tests"]){
+        return window.location.search.parseQuery()["tests"].split(',');
+    };
+  },
+  // Returns:
+  //  "ERROR" if there was an error,
+  //  "FAILURE" if there was a failure, or
+  //  "SUCCESS" if there was neither
+  getResult: function() {
+    var hasFailure = false;
+    for(var i=0;i<this.tests.length;i++) {
+      if (this.tests[i].errors > 0) {
+        return "ERROR";
+      }
+      if (this.tests[i].failures > 0) {
+        hasFailure = true;
+      }
+    }
+    if (hasFailure) {
+      return "FAILURE";
+    } else {
+      return "SUCCESS";
+    }
+  },
+  postResults: function() {
+    if (this.options.resultsURL) {
+      new Ajax.Request(this.options.resultsURL, 
+        { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
+    }
+  },
+  runTests: function() {
+    var test = this.tests[this.currentTest];
+    if (!test) {
+      // finished!
+      this.postResults();
+      this.logger.summary(this.summary());
+      return;
+    }
+    if(!test.isWaiting) {
+      this.logger.start(test.name);
+    }
+    test.run();
+    if(test.isWaiting) {
+      this.logger.message("Waiting for " + test.timeToWait + "ms");
+      setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
+    } else {
+      this.logger.finish(test.status(), test.summary());
+      this.currentTest++;
+      // tail recursive, hopefully the browser will skip the stackframe
+      this.runTests();
+    }
+  },
+  summary: function() {
+    var assertions = 0;
+    var failures = 0;
+    var errors = 0;
+    var messages = [];
+    for(var i=0;i<this.tests.length;i++) {
+      assertions +=   this.tests[i].assertions;
+      failures   +=   this.tests[i].failures;
+      errors     +=   this.tests[i].errors;
+    }
+    return (
+      (this.options.context ? this.options.context + ': ': '') + 
+      this.tests.length + " tests, " + 
+      assertions + " assertions, " + 
+      failures   + " failures, " +
+      errors     + " errors");
+  }
+};
+
+Test.Unit.Assertions = Class.create();
+Test.Unit.Assertions.prototype = {
+  initialize: function() {
+    this.assertions = 0;
+    this.failures   = 0;
+    this.errors     = 0;
+    this.messages   = [];
+  },
+  summary: function() {
+    return (
+      this.assertions + " assertions, " + 
+      this.failures   + " failures, " +
+      this.errors     + " errors" + "\n" +
+      this.messages.join("\n"));
+  },
+  pass: function() {
+    this.assertions++;
+  },
+  fail: function(message) {
+    this.failures++;
+    this.messages.push("Failure: " + message);
+  },
+  info: function(message) {
+    this.messages.push("Info: " + message);
+  },
+  error: function(error) {
+    this.errors++;
+    this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
+  },
+  status: function() {
+    if (this.failures > 0) return 'failed';
+    if (this.errors > 0) return 'error';
+    return 'passed';
+  },
+  assert: function(expression) {
+    var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
+    try { expression ? this.pass() : 
+      this.fail(message); }
+    catch(e) { this.error(e); }
+  },
+  assertEqual: function(expected, actual) {
+    var message = arguments[2] || "assertEqual";
+    try { (expected == actual) ? this.pass() :
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertInspect: function(expected, actual) {
+    var message = arguments[2] || "assertInspect";
+    try { (expected == actual.inspect()) ? this.pass() :
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertEnumEqual: function(expected, actual) {
+    var message = arguments[2] || "assertEnumEqual";
+    try { $A(expected).length == $A(actual).length && 
+      expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
+        this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + 
+          ', actual ' + Test.Unit.inspect(actual)); }
+    catch(e) { this.error(e); }
+  },
+  assertNotEqual: function(expected, actual) {
+    var message = arguments[2] || "assertNotEqual";
+    try { (expected != actual) ? this.pass() : 
+      this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertIdentical: function(expected, actual) { 
+    var message = arguments[2] || "assertIdentical"; 
+    try { (expected === actual) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
+    catch(e) { this.error(e); } 
+  },
+  assertNotIdentical: function(expected, actual) { 
+    var message = arguments[2] || "assertNotIdentical"; 
+    try { !(expected === actual) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
+    catch(e) { this.error(e); } 
+  },
+  assertNull: function(obj) {
+    var message = arguments[1] || 'assertNull';
+    try { (obj==null) ? this.pass() : 
+      this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertMatch: function(expected, actual) {
+    var message = arguments[2] || 'assertMatch';
+    var regex = new RegExp(expected);
+    try { (regex.exec(actual)) ? this.pass() :
+      this.fail(message + ' : regex: "' +  Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertHidden: function(element) {
+    var message = arguments[1] || 'assertHidden';
+    this.assertEqual("none", element.style.display, message);
+  },
+  assertNotNull: function(object) {
+    var message = arguments[1] || 'assertNotNull';
+    this.assert(object != null, message);
+  },
+  assertType: function(expected, actual) {
+    var message = arguments[2] || 'assertType';
+    try { 
+      (actual.constructor == expected) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + (actual.constructor) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertNotOfType: function(expected, actual) {
+    var message = arguments[2] || 'assertNotOfType';
+    try { 
+      (actual.constructor != expected) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + (actual.constructor) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertInstanceOf: function(expected, actual) {
+    var message = arguments[2] || 'assertInstanceOf';
+    try { 
+      (actual instanceof expected) ? this.pass() : 
+      this.fail(message + ": object was not an instance of the expected type"); }
+    catch(e) { this.error(e); } 
+  },
+  assertNotInstanceOf: function(expected, actual) {
+    var message = arguments[2] || 'assertNotInstanceOf';
+    try { 
+      !(actual instanceof expected) ? this.pass() : 
+      this.fail(message + ": object was an instance of the not expected type"); }
+    catch(e) { this.error(e); } 
+  },
+  assertRespondsTo: function(method, obj) {
+    var message = arguments[2] || 'assertRespondsTo';
+    try {
+      (obj[method] && typeof obj[method] == 'function') ? this.pass() : 
+      this.fail(message + ": object doesn't respond to [" + method + "]"); }
+    catch(e) { this.error(e); }
+  },
+  assertReturnsTrue: function(method, obj) {
+    var message = arguments[2] || 'assertReturnsTrue';
+    try {
+      var m = obj[method];
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
+      m() ? this.pass() : 
+      this.fail(message + ": method returned false"); }
+    catch(e) { this.error(e); }
+  },
+  assertReturnsFalse: function(method, obj) {
+    var message = arguments[2] || 'assertReturnsFalse';
+    try {
+      var m = obj[method];
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
+      !m() ? this.pass() : 
+      this.fail(message + ": method returned true"); }
+    catch(e) { this.error(e); }
+  },
+  assertRaise: function(exceptionName, method) {
+    var message = arguments[2] || 'assertRaise';
+    try { 
+      method();
+      this.fail(message + ": exception expected but none was raised"); }
+    catch(e) {
+      ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); 
+    }
+  },
+  assertElementsMatch: function() {
+    var expressions = $A(arguments), elements = $A(expressions.shift());
+    if (elements.length != expressions.length) {
+      this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
+      return false;
+    }
+    elements.zip(expressions).all(function(pair, index) {
+      var element = $(pair.first()), expression = pair.last();
+      if (element.match(expression)) return true;
+      this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
+    }.bind(this)) && this.pass();
+  },
+  assertElementMatches: function(element, expression) {
+    this.assertElementsMatch([element], expression);
+  },
+  benchmark: function(operation, iterations) {
+    var startAt = new Date();
+    (iterations || 1).times(operation);
+    var timeTaken = ((new Date())-startAt);
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
+    return timeTaken;
+  },
+  _isVisible: function(element) {
+    element = $(element);
+    if(!element.parentNode) return true;
+    this.assertNotNull(element);
+    if(element.style && Element.getStyle(element, 'display') == 'none')
+      return false;
+    
+    return this._isVisible(element.parentNode);
+  },
+  assertNotVisible: function(element) {
+    this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
+  },
+  assertVisible: function(element) {
+    this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
+  },
+  benchmark: function(operation, iterations) {
+    var startAt = new Date();
+    (iterations || 1).times(operation);
+    var timeTaken = ((new Date())-startAt);
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
+    return timeTaken;
+  }
+};
+
+Test.Unit.Testcase = Class.create();
+Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
+  initialize: function(name, test, setup, teardown) {
+    Test.Unit.Assertions.prototype.initialize.bind(this)();
+    this.name           = name;
+    
+    if(typeof test == 'string') {
+      test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
+      test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
+      this.test = function() {
+        eval('with(this){'+test+'}');
+      }
+    } else {
+      this.test = test || function() {};
+    }
+    
+    this.setup          = setup || function() {};
+    this.teardown       = teardown || function() {};
+    this.isWaiting      = false;
+    this.timeToWait     = 1000;
+  },
+  wait: function(time, nextPart) {
+    this.isWaiting = true;
+    this.test = nextPart;
+    this.timeToWait = time;
+  },
+  run: function() {
+    try {
+      try {
+        if (!this.isWaiting) this.setup.bind(this)();
+        this.isWaiting = false;
+        this.test.bind(this)();
+      } finally {
+        if(!this.isWaiting) {
+          this.teardown.bind(this)();
+        }
+      }
+    }
+    catch(e) { this.error(e); }
+  }
+});
+
+// *EXPERIMENTAL* BDD-style testing to please non-technical folk
+// This draws many ideas from RSpec http://rspec.rubyforge.org/
+
+Test.setupBDDExtensionMethods = function(){
+  var METHODMAP = {
+    shouldEqual:     'assertEqual',
+    shouldNotEqual:  'assertNotEqual',
+    shouldEqualEnum: 'assertEnumEqual',
+    shouldBeA:       'assertType',
+    shouldNotBeA:    'assertNotOfType',
+    shouldBeAn:      'assertType',
+    shouldNotBeAn:   'assertNotOfType',
+    shouldBeNull:    'assertNull',
+    shouldNotBeNull: 'assertNotNull',
+    
+    shouldBe:        'assertReturnsTrue',
+    shouldNotBe:     'assertReturnsFalse',
+    shouldRespondTo: 'assertRespondsTo'
+  };
+  var makeAssertion = function(assertion, args, object) { 
+   	this[assertion].apply(this,(args || []).concat([object]));
+  };
+  
+  Test.BDDMethods = {};   
+  $H(METHODMAP).each(function(pair) { 
+    Test.BDDMethods[pair.key] = function() { 
+       var args = $A(arguments); 
+       var scope = args.shift(); 
+       makeAssertion.apply(scope, [pair.value, args, this]); }; 
+  });
+  
+  [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
+    function(p){ Object.extend(p, Test.BDDMethods) }
+  );
+};
+
+Test.context = function(name, spec, log){
+  Test.setupBDDExtensionMethods();
+  
+  var compiledSpec = {};
+  var titles = {};
+  for(specName in spec) {
+    switch(specName){
+      case "setup":
+      case "teardown":
+        compiledSpec[specName] = spec[specName];
+        break;
+      default:
+        var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
+        var body = spec[specName].toString().split('\n').slice(1);
+        if(/^\{/.test(body[0])) body = body.slice(1);
+        body.pop();
+        body = body.map(function(statement){ 
+          return statement.strip()
+        });
+        compiledSpec[testName] = body.join('\n');
+        titles[testName] = specName;
+    }
+  }
+  new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
+};
