diff --git a/resources/static/dialog/controllers/authenticate_controller.js b/resources/static/dialog/controllers/authenticate_controller.js
index f064f0ca5782fa0310a8d9997d4d9beb0ecef915..b270a65d7412058fe78f728e0c2b6cdfdec3b48b 100644
--- a/resources/static/dialog/controllers/authenticate_controller.js
+++ b/resources/static/dialog/controllers/authenticate_controller.js
@@ -43,10 +43,11 @@
       errors = bid.Errors,
       validation = bid.Validation,
       tooltip = bid.Tooltip,
+      dom = bid.DOM,
       lastEmail = "";
 
   function getEmail() {
-    return $("#email").val().trim();
+    return dom.getInner("#email").trim();
   }
 
   function checkEmail(el, event) {
@@ -89,18 +90,18 @@
 
   function authenticate(el, event) {
     var email = getEmail(),
-        pass = $("#password").val(),
+        pass = dom.getInner("#password"),
         self = this;
 
     cancelEvent(event);
 
     if (!validation.emailAndPassword(email, pass)) return;
 
-    user.authenticate(email, pass, 
+    user.authenticate(email, pass,
       function onComplete(authenticated) {
         if (authenticated) {
           self.close("authenticated", {
-            email: email 
+            email: email
           });
         } else {
           bid.Tooltip.showTooltip("#cannot_authenticate");
@@ -149,7 +150,7 @@
     self.publish("enter_password");
     self.submit = authenticate;
     animateSwap(".start:visible,.newuser:visible,.forgot:visible", ".returning", function() {
-      $("#password").focus();  
+      dom.getElements("#password")[0].focus();
     });
   }
 
@@ -157,7 +158,7 @@
     cancelEvent(event);
 
     this.submit = resetPassword;
-    $("#email").attr("disabled", "disabled");
+    dom.setAttr("#email", "disabled", "disabled");
 
     animateSwap(".start:visible,.newuser:visible,.returning:visible", ".forgot");
   }
@@ -165,8 +166,8 @@
   function cancelForgotPassword(el, event) {
     cancelEvent(event);
 
-    $("#email").removeAttr("disabled");
-    enterPasswordState.call(this); 
+    dom.removeAttr("#email", "disabled");
+    enterPasswordState.call(this);
   }
 
   function createUserState(el, event) {
@@ -193,7 +194,7 @@
       });
 
       this.submit = checkEmail;
-      // If we already have an email address, check if it is valid, if so, show 
+      // If we already have an email address, check if it is valid, if so, show
       // password.
       if (options.email) this.submit();
     },
diff --git a/resources/static/dialog/controllers/dialog_controller.js b/resources/static/dialog/controllers/dialog_controller.js
index c2d3562fe66d8c0d458b0f8d46051e1538979028..e919c3b45f944a541ef2c1b0412bbb7261ac2850 100644
--- a/resources/static/dialog/controllers/dialog_controller.js
+++ b/resources/static/dialog/controllers/dialog_controller.js
@@ -1,5 +1,5 @@
-/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */                                             
-/*global setupChannel:true, BrowserID: true, PageController: true, OpenAjax: true */ 
+/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
+/*global setupChannel:true, BrowserID: true, PageController: true, OpenAjax: true */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
@@ -45,11 +45,12 @@
   var bid = BrowserID,
       user = bid.User,
       errors = bid.Errors,
+      dom = bid.DOM,
       offline = false,
       win = window,
       subscriptions = [],
       hub = OpenAjax.hub;
-      
+
   function subscribe(message, cb) {
      subscriptions.push(hub.subscribe(message, cb));
   }
@@ -84,12 +85,12 @@
         var subscription;
 
         while(subscription = subscriptions.pop()) {
-          hub.unsubscribe(subscription);  
+          hub.unsubscribe(subscription);
         }
 
         this._super();
       },
-        
+
       getVerifiedEmail: function(origin_url, onsuccess, onerror) {
         var self=this;
         self.onsuccess = onsuccess;
@@ -101,11 +102,11 @@
         }
 
         user.setOrigin(origin_url);
-        $("#sitename").text(user.getHostname());
+        dom.setInner("#sitename", user.getHostname());
 
         self.doCheckAuth();
 
-        $(win).bind("unload", function() {
+        dom.bindEvent(win, "unload", function() {
           bid.Storage.setStagedOnBehalfOf("");
           self.doCancel();
         });
@@ -113,7 +114,7 @@
 
 
       stateMachine: function() {
-        var self=this, 
+        var self=this,
             el = this.element;
 
         subscribe("offline", function(msg, info) {
@@ -135,7 +136,7 @@
 
         subscribe("authenticated", function(msg, info) {
           //self.doEmailSelected(info.email);
-          // XXX benadida, lloyd - swap these two if you want to experiment with 
+          // XXX benadida, lloyd - swap these two if you want to experiment with
           // generating assertions directly from signin.
           self.syncEmails();
         });
@@ -217,8 +218,8 @@
 
       doPickEmail: function() {
         this.element.pickemail({
-          // XXX ideal is to get rid of this and have a User function 
-          // that takes care of getting email addresses AND the last used email 
+          // XXX ideal is to get rid of this and have a User function
+          // that takes care of getting email addresses AND the last used email
           // for this site.
           origin: user.getHostname()
         });
@@ -230,7 +231,7 @@
 
       doForgotPassword: function(email) {
         this.element.forgotpassword({
-          email: email  
+          email: email
         });
       },
 
@@ -255,8 +256,8 @@
 
       doAssertionGenerated: function(assertion) {
         var self=this;
-        // Clear onerror before the call to onsuccess - the code to onsuccess 
-        // calls window.close, which would trigger the onerror callback if we 
+        // Clear onerror before the call to onsuccess - the code to onsuccess
+        // calls window.close, which would trigger the onerror callback if we
         // tried this afterwards.
         self.onerror = null;
         self.onsuccess(assertion);
@@ -269,20 +270,20 @@
 
       syncEmails: function() {
         var self = this;
-        user.syncEmails(self.doPickEmail.bind(self), 
+        user.syncEmails(self.doPickEmail.bind(self),
           self.getErrorDialog(errors.signIn));
       },
 
       doCheckAuth: function() {
         var self=this;
-        user.checkAuthenticationAndSync(function onSuccess() {}, 
+        user.checkAuthenticationAndSync(function onSuccess() {},
           function onComplete(authenticated) {
             if (authenticated) {
               self.doPickEmail();
             } else {
               self.doAuthenticate();
             }
-          }, 
+          },
           self.getErrorDialog(errors.checkAuthentication));
     }
 
diff --git a/resources/static/dialog/controllers/page_controller.js b/resources/static/dialog/controllers/page_controller.js
index 70113e8ad0d3165f32734415893fd530125303a6..9ff9f3386ebb3bc0ebfad7a502a7fcd99bdf9d24 100644
--- a/resources/static/dialog/controllers/page_controller.js
+++ b/resources/static/dialog/controllers/page_controller.js
@@ -1,4 +1,4 @@
-/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */                                             
+/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */
 /*global BrowserID: true*/
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
@@ -37,7 +37,9 @@
 (function() {
 "use strict";
 
-  var ANIMATION_TIME = 250;
+  var ANIMATION_TIME = 250,
+      bid = BrowserID,
+      dom = bid.DOM;
 
 
   $.Controller.extend("PageController", {
@@ -67,53 +69,60 @@
       }
 
       // XXX move all of these, bleck.
-      $("form").bind("submit", me.onSubmit.bind(me));
-      $("#cancel").click(me.onCancel.bind(me));
-      $("#back").click(me.onBack.bind(me));
-      $("#thisIsNotMe").click(me.close.bind(me, "notme"));
+      dom.bindEvent("form", "submit", me.onSubmit.bind(me));
+      dom.bindEvent("#cancel", "click", me.onCancel.bind(me));
+      dom.bindEvent("#back", "click", me.onBack.bind(me));
+      dom.bindEvent("#thisIsNotMe", "click", me.close.bind(me, "notme"));
     },
 
     destroy: function() {
-      $("form").unbind("submit");
-      $("input").unbind("keyup");
-      $("#cancel").unbind("click");
-      $("#back").unbind("click");
-      $("#thisIsNotMe").unbind("click");
+      dom.unbindEvent("form", "submit");
+      dom.unbindEvent("input", "keyup");
+      dom.unbindEvent("#cancel", "click");
+      dom.unbindEvent("#back", "click");
+      dom.unbindEvent("#thisIsNotMe", "click");
 
-      $("body").removeClass("waiting");
+      dom.removeClass("body", "waiting");
 
       this._super();
     },
 
     renderTemplates: function(target, body, body_vars) {
       if (body) {
-        var bodyHtml = $.View("//dialog/views/" + body, body_vars);
+        var bodyHtml = new EJS({url: "/dialog/views/" + body}).render(body_vars);
         target = $(target + " .contents");
-        target.html(bodyHtml).find("input").eq(0).focus(); 
+        target.html(bodyHtml).find("input").eq(0).focus();
       }
     },
 
     renderDialog: function(body, body_vars) {
       this.renderTemplates("#formWrap", body, body_vars);
-      $("body").removeClass("error").removeClass("waiting").addClass("form");
+      dom.removeClass("body", "error");
+      dom.removeClass("body", "waiting");
+      dom.addClass("body", "form");
       $("#wait, #error").stop().fadeOut(ANIMATION_TIME);
     },
 
     renderWait: function(body, body_vars) {
       this.renderTemplates("#wait", body, body_vars);
-      $("body").removeClass("error").removeClass("form").addClass("waiting").css('opacity', 1);
+      dom.removeClass("body", "error");
+      dom.removeClass("body", "form");
+      dom.addClass("body", "waiting");
+      $("body").css('opacity', 1);
       $("#wait").stop().hide().fadeIn(ANIMATION_TIME);
     },
 
     renderError: function(body, body_vars) {
       this.renderTemplates("#error", body, body_vars);
-      $("body").removeClass("waiting").removeClass("form").addClass("error");
+      dom.removeClass("body", "waiting");
+      dom.removeClass("body", "form");
+      dom.addClass("body", "error");
       $("#error").stop().css('opacity', 1).hide().fadeIn(ANIMATION_TIME);
 
       /**
        * What a big steaming pile, use CSS animations for this!
        */
-      $("#openMoreInfo").click(function(event) {
+      dom.bindEvent("#openMoreInfo", "click", function(event) {
         event.preventDefault();
 
         $("#moreInfo").slideDown();
@@ -142,7 +151,7 @@
     doWait: function(info) {
       this.renderWait("wait.ejs", info);
 
-      $("body").addClass("waiting");
+      dom.addClass("body", "waiting");
     },
 
     close: function(message, data) {
@@ -155,7 +164,7 @@
     /**
      * Get a curried function to an error dialog.
      * @method getErrorDialog
-     * @method {object} action - info to use for the error dialog.  Should have 
+     * @method {object} action - info to use for the error dialog.  Should have
      * two fields, message, description.
      */
     getErrorDialog: function(action) {
diff --git a/resources/static/dialog/controllers/pickemail_controller.js b/resources/static/dialog/controllers/pickemail_controller.js
index eda8675067f2cc817c7529d2451532bfaf7d1b78..e3cb978f95d2e7e465feefd0426728b0c204b0ca 100644
--- a/resources/static/dialog/controllers/pickemail_controller.js
+++ b/resources/static/dialog/controllers/pickemail_controller.js
@@ -1,5 +1,5 @@
-/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */                                             
-/*global _: true, BrowserID: true, PageController: true */ 
+/*jshint brgwser:true, jQuery: true, forin: true, laxbreak:true */
+/*global _: true, BrowserID: true, PageController: true */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
@@ -42,6 +42,7 @@
       user = bid.User,
       errors = bid.Errors,
       storage = bid.Storage,
+      dom = bid.DOM,
       body = $("body"),
       animationComplete = body.innerWidth() < 640,
       assertion;
@@ -104,7 +105,7 @@
   }
 
   function getAssertion(email) {
-    // Kick of the assertion fetching/keypair generating while we are showing 
+    // Kick of the assertion fetching/keypair generating while we are showing
     // the animation, hopefully this minimizes the delay the user notices.
     var self=this;
     user.getAssertion(email, function(assert) {
@@ -122,7 +123,7 @@
            animationComplete = true;
            tryClose.call(self);
          });
-      }); 
+      });
     }
     else {
       tryClose.call(self);
@@ -133,7 +134,7 @@
   function signIn(element, event) {
     cancelEvent(event);
     var self=this,
-        email = $("input[type=radio]:checked").val();
+        email = dom.getInner("input[type=radio]:checked");
 
     var valid = checkEmail.call(self, email);
     if (valid) {
@@ -145,7 +146,7 @@
   }
 
   function addEmail(element, event) {
-    var email = $("#newEmail").val(),
+    var email = dom.getInner("#newEmail"),
         self=this;
 
     cancelEvent(event);
@@ -183,18 +184,18 @@
         bodyTemplate: "pickemail.ejs",
         bodyVars: {
           identities: user.getStoredEmailKeypairs(),
-          // XXX ideal is to get rid of this and have a User function 
-          // that takes care of getting email addresses AND the last used email 
+          // XXX ideal is to get rid of this and have a User function
+          // that takes care of getting email addresses AND the last used email
           // for this site.
           siteemail: storage.site.get(origin, "email"),
           remember: storage.site.get(origin, "remember") || false
         }
       });
 
-      $("body").css("opacity", "1");
+      body.css("opacity", "1");
 
-      if($("#selectEmail input[type=radio]:visible").length === 0) {
-        // If there is only one email address, the radio button is never shown, 
+      if(dom.getElements("#selectEmail input[type=radio]:visible").length === 0) {
+        // If there is only one email address, the radio button is never shown,
         // instead focus the sign in button so that the user can click enter.
         // issue #412
         $("#signInButton").focus();
diff --git a/resources/static/dialog/dialog.js b/resources/static/dialog/dialog.js
index 3651dacb8524211c0e7dbfb553e797d58b7070fa..c141b8f060929fc5130b8cc6e65404b9b1ee24be 100644
--- a/resources/static/dialog/dialog.js
+++ b/resources/static/dialog/dialog.js
@@ -41,15 +41,15 @@ window.console = window.console || {
 
 steal.plugins(
 	'jquery/controller',			// a widget factory
-	'jquery/controller/subscribe',	// subscribe to OpenAjax.hub
-	'jquery/view/ejs',				// client side templates
-	'jquery/controller/view')		// lookup views with the controller's name
+	'jquery/controller/subscribe')	// subscribe to OpenAjax.hub
 
 	.resources('jschannel',
                'base64',
                'underscore-min',
                'channel',
+               'ejs',
                'browserid',
+               'dom-jquery',
                'storage',
                'tooltip',
                'validation',
@@ -66,15 +66,15 @@ steal.plugins(
                'checkregistration',
                'pickemail')					// loads files in controllers folder
 
-	.views('authenticate.ejs',
+/*	.views('authenticate.ejs',
            'confirmemail.ejs',
            'pickemail.ejs',
            'wait.ejs',
            'error.ejs',
            'offline.ejs'
           ).
-
-          then(function() {
+*/
+          .then(function() {
             $(function() {
               $('body').dialog().show();
             });
diff --git a/resources/static/dialog/resources/dom-jquery.js b/resources/static/dialog/resources/dom-jquery.js
new file mode 100644
index 0000000000000000000000000000000000000000..25d2a691f5a384485e2de772c920b92541c654d4
--- /dev/null
+++ b/resources/static/dialog/resources/dom-jquery.js
@@ -0,0 +1,300 @@
+/**
+* Written by Shane Tomlinson - original source at:
+* https://github.com/stomlinson/AFrame-JS/blob/master/src/adapters/jquery.js
+* which is licensed under the Creative Commons Attribution 3.0 License.
+*
+* A DOM Manipulation adapter for jQuery.
+* @class BrowserID.DOM
+* @static
+*/
+BrowserID.DOM = ( function() {
+    var jQuery = typeof( window ) !== 'undefined' && window.jQuery;
+    var DOM = {
+        /**
+        * Get a set of elements that match the selector
+        * @method getElements
+        * @param {selector || element} selector - if a string, a selector to search for.
+        * @return {array} array of elements
+        */
+        getElements: function( selector ) {
+            return jQuery( selector );
+        },
+
+        /**
+        * Get a set of descendent elements that match the selector
+        * @method getDescendentElements
+        * @param {string} selector - The selector to search for.
+        * @param {element} root - root node to search from
+        * @return {array} array of elements
+        */
+        getDescendentElements: function( selector, root ) {
+            return jQuery( root ).find( selector );
+        },
+
+        /**
+        * Get a set of descendent elements that match the selector, include the root node if it
+        *   matches the selector
+        * @method getElementsIncludeRoot
+        * @param {string} selector - The selector to search for.
+        * @param {element} root - root node to search from
+        * @return {array} array of elements
+        */
+        getElementsIncludeRoot: function( selector, root ) {
+            root = jQuery( root );
+            var set = root.find( selector );
+            if( root.is( selector ) ) {
+                set = root.add( set );
+            }
+            return set;
+        },
+
+        /**
+        * Get the children for an element
+        * @method getChildren
+        * @param {selector || element} selector - element to get children for
+        * @return {array} an array of children
+        */
+        getChildren: function( selector ) {
+            return jQuery( selector ).children();
+        },
+
+        /**
+        * Get the nth child element
+        * @method getNthChild
+        * @param {selector || element} selector - element to get children for
+        * @param {number} index - index of the child to get
+        * @return {element} the nth child if it exists.
+        */
+        getNthChild: function( selector, index ) {
+            return jQuery( selector ).children()[ index ];
+        },
+
+        /**
+        * Iterate over a set of elements
+        * @method forEach
+        * @param {Elements} elements - elements to iterate over
+        * @param {function} callback - callback to call.  Callback called with: callback( element, index );
+        * @param {context} context - context to callback in
+        */
+        forEach: function( elements, callback, context ) {
+            jQuery( elements ).each( function( index, element ) {
+                callback.call( context, element, index );
+            } );
+        },
+
+        /**
+        * Remove an element
+        * @method removeElement
+        * @param {selector || element} selector - element to remove
+        */
+        removeElement: function( selector ) {
+            jQuery( selector ).remove();
+        },
+
+        /**
+        * Bind to an elements DOM Event
+        * @method bindEvent
+        * @param {selector || element} element to bind on
+        * @param {string} eventName - name of event
+        * @param {function} callback - callback to call
+        */
+        bindEvent: function( element, eventName, callback ) {
+            return jQuery( element ).bind( eventName, callback );
+        },
+
+        /**
+        * Unbind an already bound DOM Event from an element.
+        * @method unbindEvent
+        * @param {selector || element} element to unbind from
+        * @param {string} eventName - name of event
+        * @param {function} callback - callback
+        */
+        unbindEvent: function( element, eventName, callback ) {
+            return jQuery( element ).unbind( eventName, callback );
+        },
+
+        /**
+        * Fire a DOM event on an element
+        * @method fireEvent
+        * @param {selector || element} element
+        * @param {string} type - event to fire
+        */
+        fireEvent: function( element, type ) {
+            return jQuery( element ).trigger( type );
+        },
+
+        /**
+        * Set the inner value of an element, including input elements
+        * @method setInner
+        * @param {selector || element} element - element to set
+        * @param {string} value - value to set
+        */
+        setInner: function( element, value ) {
+            var target = jQuery( element );
+            if( isValBased( target ) ) {
+                target.val( value );
+            }
+            else {
+                target.html( value );
+            }
+
+        },
+
+        /**
+        * Get the inner value of an element, including input elements
+        * @method getInner
+        * @param {selector || element} element
+        * @return {string} inner value of the element
+        */
+        getInner: function( element ) {
+            var target = jQuery( element );
+            var retval = '';
+
+            if( isValBased( target ) ) {
+                retval = target.val();
+            }
+            else {
+                retval = target.html();
+            }
+            return retval;
+        },
+
+        /**
+        * Set an element's attribute.
+        * @method setAttr
+        * @param {selector || element} element
+        * @param {string} attrName - the attribute name
+        * @param {string} value - value to set
+        */
+        setAttr: function( element, attrName, value ) {
+            jQuery( element ).attr( attrName, value );
+        },
+
+        /**
+        * Get an element's attribute.
+        * @method getAttr
+        * @param {selector || element} element
+        * @param {string} attrName - the attribute name
+        * @return {string} attribute's value
+        */
+        getAttr: function( element, attrName ) {
+            return jQuery( element ).attr( attrName );
+        },
+
+        /**
+        * Check if an element has an attribute
+        * @method hasAttr
+        * @param {selector || element} element
+        * @param {string} attrName - the attribute name
+        * @return {boolean} true if the element has the attribute, false otw.
+        */
+        hasAttr: function( element, attrName ) {
+            var val = jQuery( element )[ 0 ].getAttribute( attrName );
+            return val !== null;
+        },
+
+        /**
+        * Remove an attribute from an element.
+        * @method removeAttr
+        * @param {selector || element} element
+        * @param {string} attrName - the attribute to remove
+        */
+        removeAttr: function( element, attrName ) {
+            return jQuery( element ).removeAttr( attrName );
+        },
+
+        /**
+        * Add a class to an element
+        * @method addClass
+        * @param {selector || element} element
+        * @param {string} className
+        */
+        addClass: function( element, className ) {
+            jQuery( element ).addClass( className );
+        },
+
+        /**
+        * Remove a class from an element
+        * @method removeClass
+        * @param {selector || element} element
+        * @param {string} className
+        */
+        removeClass: function( element, className ) {
+            jQuery( element ).removeClass( className );
+        },
+
+        /**
+        * Check if an element has a class
+        * @method hasClass
+        * @param {selector || element} element
+        * @param {string} className
+        * @return {boolean} true if element has class, false otw.
+        */
+        hasClass: function( element, className ) {
+            return jQuery( element ).hasClass( className );
+        },
+
+        /**
+        * Create an element
+        * @method createElement
+        * @param {string} type - element type
+        * @param {string} html (optional) - inner HTML
+        * @return {element} created element
+        */
+        createElement: function( type, html ) {
+            var element = jQuery( '<' + type + '/>' );
+            if( html ) {
+                BrowserID.DOM.setInner( element, html );
+            }
+            return element;
+        },
+
+        /**
+        * Append an element as the last child of another element
+        * @method appendTo
+        * @param {selector || element} elementToInsert
+        * @param {selector || element} elementToAppendTo
+        */
+        appendTo: function( elementToInsert, elementToAppendTo ) {
+            jQuery( elementToInsert ).appendTo( jQuery( elementToAppendTo ) );
+        },
+
+        /**
+        * Insert an element before another element
+        * @method insertBefore
+        * @param {selector || element} elementToInsert
+        * @param {selector || element} elementToInsertBefore
+        */
+        insertBefore: function( elementToInsert, elementToInsertBefore ) {
+            jQuery( elementToInsert ).insertBefore( elementToInsertBefore );
+        },
+
+        /**
+        * Insert as the nth child of an element
+        * @method insertAsNthChild
+        * @param {selector || element} elementToInsert
+        * @param {selector || element} parent
+        * @param {number} index
+        */
+        insertAsNthChild: function( elementToInsert, parent, index ) {
+            var children = jQuery( parent ).children();
+            if( index === children.length ) {
+                elementToInsert.appendTo( parent );
+            }
+            else {
+                var insertBefore = children.eq( index );
+                elementToInsert.insertBefore( insertBefore );
+            }
+
+        }
+
+
+    };
+
+    function isValBased( target ) {
+        return target.is( 'input' ) || target.is( 'textarea' );
+    }
+
+    return DOM;
+
+}() );
diff --git a/resources/static/dialog/resources/ejs.js b/resources/static/dialog/resources/ejs.js
new file mode 100644
index 0000000000000000000000000000000000000000..49d95252400213f7c1f2105344541485ebcb50aa
--- /dev/null
+++ b/resources/static/dialog/resources/ejs.js
@@ -0,0 +1,505 @@
+(function(){
+    
+
+var rsplit = function(string, regex) {
+	var result = regex.exec(string),retArr = new Array(), first_idx, last_idx, first_bit;
+	while (result != null)
+	{
+		first_idx = result.index; last_idx = regex.lastIndex;
+		if ((first_idx) != 0)
+		{
+			first_bit = string.substring(0,first_idx);
+			retArr.push(string.substring(0,first_idx));
+			string = string.slice(first_idx);
+		}		
+		retArr.push(result[0]);
+		string = string.slice(result[0].length);
+		result = regex.exec(string);	
+	}
+	if (! string == '')
+	{
+		retArr.push(string);
+	}
+	return retArr;
+},
+chop =  function(string){
+    return string.substr(0, string.length - 1);
+},
+extend = function(d, s){
+    for(var n in s){
+        if(s.hasOwnProperty(n))  d[n] = s[n]
+    }
+}
+
+
+EJS = function( options ){
+	options = typeof options == "string" ? {view: options} : options
+    this.set_options(options);
+	if(options.precompiled){
+		this.template = {};
+		this.template.process = options.precompiled;
+		EJS.update(this.name, this);
+		return;
+	}
+    if(options.element)
+	{
+		if(typeof options.element == 'string'){
+			var name = options.element
+			options.element = document.getElementById(  options.element )
+			if(options.element == null) throw name+'does not exist!'
+		}
+		if(options.element.value){
+			this.text = options.element.value
+		}else{
+			this.text = options.element.innerHTML
+		}
+		this.name = options.element.id
+		this.type = '['
+	}else if(options.url){
+        options.url = EJS.endExt(options.url, this.extMatch);
+		this.name = this.name ? this.name : options.url;
+        var url = options.url
+        //options.view = options.absolute_url || options.view || options.;
+		var template = EJS.get(this.name /*url*/, this.cache);
+		if (template) return template;
+	    if (template == EJS.INVALID_PATH) return null;
+        try{
+            this.text = EJS.request( url+(this.cache ? '' : '?'+Math.random() ));
+        }catch(e){}
+
+		if(this.text == null){
+            throw( {type: 'EJS', message: 'There is no template at '+url}  );
+		}
+		//this.name = url;
+	}
+	var template = new EJS.Compiler(this.text, this.type);
+
+	template.compile(options, this.name);
+
+	
+	EJS.update(this.name, this);
+	this.template = template;
+};
+/* @Prototype*/
+EJS.prototype = {
+	/**
+	 * Renders an object with extra view helpers attached to the view.
+	 * @param {Object} object data to be rendered
+	 * @param {Object} extra_helpers an object with additonal view helpers
+	 * @return {String} returns the result of the string
+	 */
+    render : function(object, extra_helpers){
+        object = object || {};
+        this._extra_helpers = extra_helpers;
+		var v = new EJS.Helpers(object, extra_helpers || {});
+		return this.template.process.call(object, object,v);
+	},
+    update : function(element, options){
+        if(typeof element == 'string'){
+			element = document.getElementById(element)
+		}
+		if(options == null){
+			_template = this;
+			return function(object){
+				EJS.prototype.update.call(_template, element, object)
+			}
+		}
+		if(typeof options == 'string'){
+			params = {}
+			params.url = options
+			_template = this;
+			params.onComplete = function(request){
+				var object = eval( request.responseText )
+				EJS.prototype.update.call(_template, element, object)
+			}
+			EJS.ajax_request(params)
+		}else
+		{
+			element.innerHTML = this.render(options)
+		}
+    },
+	out : function(){
+		return this.template.out;
+	},
+    /**
+     * Sets options on this view to be rendered with.
+     * @param {Object} options
+     */
+	set_options : function(options){
+        this.type = options.type || EJS.type;
+		this.cache = options.cache != null ? options.cache : EJS.cache;
+		this.text = options.text || null;
+		this.name =  options.name || null;
+		this.ext = options.ext || EJS.ext;
+		this.extMatch = new RegExp(this.ext.replace(/\./, '\.'));
+	}
+};
+EJS.endExt = function(path, match){
+	if(!path) return null;
+	match.lastIndex = 0
+	return path+ (match.test(path) ? '' : this.ext )
+}
+
+
+
+
+/* @Static*/
+EJS.Scanner = function(source, left, right) {
+	
+    extend(this,
+        {left_delimiter: 	left +'%',
+         right_delimiter: 	'%'+right,
+         double_left: 		left+'%%',
+         double_right:  	'%%'+right,
+         left_equal: 		left+'%=',
+         left_comment: 	left+'%#'})
+
+	this.SplitRegexp = left=='[' ? /(\[%%)|(%%\])|(\[%=)|(\[%#)|(\[%)|(%\]\n)|(%\])|(\n)/ : new RegExp('('+this.double_left+')|(%%'+this.double_right+')|('+this.left_equal+')|('+this.left_comment+')|('+this.left_delimiter+')|('+this.right_delimiter+'\n)|('+this.right_delimiter+')|(\n)') ;
+	
+	this.source = source;
+	this.stag = null;
+	this.lines = 0;
+};
+
+EJS.Scanner.to_text = function(input){
+	if(input == null || input === undefined)
+        return '';
+    if(input instanceof Date)
+		return input.toDateString();
+	if(input.toString) 
+        return input.toString();
+	return '';
+};
+
+EJS.Scanner.prototype = {
+  scan: function(block) {
+     scanline = this.scanline;
+	 regex = this.SplitRegexp;
+	 if (! this.source == '')
+	 {
+	 	 var source_split = rsplit(this.source, /\n/);
+	 	 for(var i=0; i<source_split.length; i++) {
+		 	 var item = source_split[i];
+			 this.scanline(item, regex, block);
+		 }
+	 }
+  },
+  scanline: function(line, regex, block) {
+	 this.lines++;
+	 var line_split = rsplit(line, regex);
+ 	 for(var i=0; i<line_split.length; i++) {
+	   var token = line_split[i];
+       if (token != null) {
+		   	try{
+	         	block(token, this);
+		 	}catch(e){
+				throw {type: 'EJS.Scanner', line: this.lines};
+			}
+       }
+	 }
+  }
+};
+
+
+EJS.Buffer = function(pre_cmd, post_cmd) {
+	this.line = new Array();
+	this.script = "";
+	this.pre_cmd = pre_cmd;
+	this.post_cmd = post_cmd;
+	for (var i=0; i<this.pre_cmd.length; i++)
+	{
+		this.push(pre_cmd[i]);
+	}
+};
+EJS.Buffer.prototype = {
+	
+  push: function(cmd) {
+	this.line.push(cmd);
+  },
+
+  cr: function() {
+	this.script = this.script + this.line.join('; ');
+	this.line = new Array();
+	this.script = this.script + "\n";
+  },
+
+  close: function() {
+	if (this.line.length > 0)
+	{
+		for (var i=0; i<this.post_cmd.length; i++){
+			this.push(pre_cmd[i]);
+		}
+		this.script = this.script + this.line.join('; ');
+		line = null;
+	}
+  }
+ 	
+};
+
+
+EJS.Compiler = function(source, left) {
+    this.pre_cmd = ['var ___ViewO = [];'];
+	this.post_cmd = new Array();
+	this.source = ' ';	
+	if (source != null)
+	{
+		if (typeof source == 'string')
+		{
+		    source = source.replace(/\r\n/g, "\n");
+            source = source.replace(/\r/g,   "\n");
+			this.source = source;
+		}else if (source.innerHTML){
+			this.source = source.innerHTML;
+		} 
+		if (typeof this.source != 'string'){
+			this.source = "";
+		}
+	}
+	left = left || '<';
+	var right = '>';
+	switch(left) {
+		case '[':
+			right = ']';
+			break;
+		case '<':
+			break;
+		default:
+			throw left+' is not a supported deliminator';
+			break;
+	}
+	this.scanner = new EJS.Scanner(this.source, left, right);
+	this.out = '';
+};
+EJS.Compiler.prototype = {
+  compile: function(options, name) {
+  	options = options || {};
+	this.out = '';
+	var put_cmd = "___ViewO.push(";
+	var insert_cmd = put_cmd;
+	var buff = new EJS.Buffer(this.pre_cmd, this.post_cmd);		
+	var content = '';
+	var clean = function(content)
+	{
+	    content = content.replace(/\\/g, '\\\\');
+        content = content.replace(/\n/g, '\\n');
+        content = content.replace(/"/g,  '\\"');
+        return content;
+	};
+	this.scanner.scan(function(token, scanner) {
+		if (scanner.stag == null)
+		{
+			switch(token) {
+				case '\n':
+					content = content + "\n";
+					buff.push(put_cmd + '"' + clean(content) + '");');
+					buff.cr();
+					content = '';
+					break;
+				case scanner.left_delimiter:
+				case scanner.left_equal:
+				case scanner.left_comment:
+					scanner.stag = token;
+					if (content.length > 0)
+					{
+						buff.push(put_cmd + '"' + clean(content) + '")');
+					}
+					content = '';
+					break;
+				case scanner.double_left:
+					content = content + scanner.left_delimiter;
+					break;
+				default:
+					content = content + token;
+					break;
+			}
+		}
+		else {
+			switch(token) {
+				case scanner.right_delimiter:
+					switch(scanner.stag) {
+						case scanner.left_delimiter:
+							if (content[content.length - 1] == '\n')
+							{
+								content = chop(content);
+								buff.push(content);
+								buff.cr();
+							}
+							else {
+								buff.push(content);
+							}
+							break;
+						case scanner.left_equal:
+							buff.push(insert_cmd + "(EJS.Scanner.to_text(" + content + ")))");
+							break;
+					}
+					scanner.stag = null;
+					content = '';
+					break;
+				case scanner.double_right:
+					content = content + scanner.right_delimiter;
+					break;
+				default:
+					content = content + token;
+					break;
+			}
+		}
+	});
+	if (content.length > 0)
+	{
+		// Chould be content.dump in Ruby
+		buff.push(put_cmd + '"' + clean(content) + '")');
+	}
+	buff.close();
+	this.out = buff.script + ";";
+	var to_be_evaled = '/*'+name+'*/this.process = function(_CONTEXT,_VIEW) { try { with(_VIEW) { with (_CONTEXT) {'+this.out+" return ___ViewO.join('');}}}catch(e){e.lineNumber=null;throw e;}};";
+	
+	try{
+		eval(to_be_evaled);
+	}catch(e){
+		if(typeof JSLINT != 'undefined'){
+			JSLINT(this.out);
+			for(var i = 0; i < JSLINT.errors.length; i++){
+				var error = JSLINT.errors[i];
+				if(error.reason != "Unnecessary semicolon."){
+					error.line++;
+					var e = new Error();
+					e.lineNumber = error.line;
+					e.message = error.reason;
+					if(options.view)
+						e.fileName = options.view;
+					throw e;
+				}
+			}
+		}else{
+			throw e;
+		}
+	}
+  }
+};
+
+
+//type, cache, folder
+/**
+ * Sets default options for all views
+ * @param {Object} options Set view with the following options
+ * <table class="options">
+				<tbody><tr><th>Option</th><th>Default</th><th>Description</th></tr>
+				<tr>
+					<td>type</td>
+					<td>'<'</td>
+					<td>type of magic tags.  Options are '&lt;' or '['
+					</td>
+				</tr>
+				<tr>
+					<td>cache</td>
+					<td>true in production mode, false in other modes</td>
+					<td>true to cache template.
+					</td>
+				</tr>
+	</tbody></table>
+ * 
+ */
+EJS.config = function(options){
+	EJS.cache = options.cache != null ? options.cache : EJS.cache;
+	EJS.type = options.type != null ? options.type : EJS.type;
+	EJS.ext = options.ext != null ? options.ext : EJS.ext;
+	
+	var templates_directory = EJS.templates_directory || {}; //nice and private container
+	EJS.templates_directory = templates_directory;
+	EJS.get = function(path, cache){
+		if(cache == false) return null;
+		if(templates_directory[path]) return templates_directory[path];
+  		return null;
+	};
+	
+	EJS.update = function(path, template) { 
+		if(path == null) return;
+		templates_directory[path] = template ;
+	};
+	
+	EJS.INVALID_PATH =  -1;
+};
+EJS.config( {cache: true, type: '<', ext: '.ejs' } );
+
+
+
+/**
+ * @constructor
+ * By adding functions to EJS.Helpers.prototype, those functions will be available in the 
+ * views.
+ * @init Creates a view helper.  This function is called internally.  You should never call it.
+ * @param {Object} data The data passed to the view.  Helpers have access to it through this._data
+ */
+EJS.Helpers = function(data, extras){
+	this._data = data;
+    this._extras = extras;
+    extend(this, extras );
+};
+/* @prototype*/
+EJS.Helpers.prototype = {
+    /**
+     * Renders a new view.  If data is passed in, uses that to render the view.
+     * @param {Object} options standard options passed to a new view.
+     * @param {optional:Object} data
+     * @return {String}
+     */
+	view: function(options, data, helpers){
+        if(!helpers) helpers = this._extras
+		if(!data) data = this._data;
+		return new EJS(options).render(data, helpers);
+	},
+    /**
+     * For a given value, tries to create a human representation.
+     * @param {Object} input the value being converted.
+     * @param {Object} null_text what text should be present if input == null or undefined, defaults to ''
+     * @return {String} 
+     */
+	to_text: function(input, null_text) {
+	    if(input == null || input === undefined) return null_text || '';
+	    if(input instanceof Date) return input.toDateString();
+		if(input.toString) return input.toString().replace(/\n/g, '<br />').replace(/''/g, "'");
+		return '';
+	}
+};
+    EJS.newRequest = function(){
+	   var factories = [function() { return new ActiveXObject("Msxml2.XMLHTTP"); },function() { return new XMLHttpRequest(); },function() { return new ActiveXObject("Microsoft.XMLHTTP"); }];
+	   for(var i = 0; i < factories.length; i++) {
+	        try {
+	            var request = factories[i]();
+	            if (request != null)  return request;
+	        }
+	        catch(e) { continue;}
+	   }
+	}
+	
+	EJS.request = function(path){
+	   var request = new EJS.newRequest()
+	   request.open("GET", path, false);
+	   
+	   try{request.send(null);}
+	   catch(e){return null;}
+	   
+	   if ( request.status == 404 || request.status == 2 ||(request.status == 0 && request.responseText == '') ) return null;
+	   
+	   return request.responseText
+	}
+	EJS.ajax_request = function(params){
+		params.method = ( params.method ? params.method : 'GET')
+		
+		var request = new EJS.newRequest();
+		request.onreadystatechange = function(){
+			if(request.readyState == 4){
+				if(request.status == 200){
+					params.onComplete(request)
+				}else
+				{
+					params.onComplete(request)
+				}
+			}
+		}
+		request.open(params.method, params.url)
+		request.send(null)
+	}
+
+
+})();
\ No newline at end of file
diff --git a/resources/static/test/qunit/qunit.js b/resources/static/test/qunit/qunit.js
index c3722bed4afa027eb93cb038aabae29b696d60c5..68a0fe179559cf033fd635f0a7df5727dd78caa8 100644
--- a/resources/static/test/qunit/qunit.js
+++ b/resources/static/test/qunit/qunit.js
@@ -1,6 +1,7 @@
 steal("/dialog/resources/browserid.js",
       "/test/qunit/mocks/mocks.js",
       "/test/qunit/mocks/xhr.js",
+      "/dialog/resources/ejs.js",
       "/dialog/resources/browser-support.js",
       "/dialog/resources/error-messages.js",
       "/dialog/resources/error-display.js",
@@ -9,39 +10,32 @@ steal("/dialog/resources/browserid.js",
       "/dialog/resources/validation.js",
       "/dialog/resources/underscore-min.js"
       )
-  .plugins(
-    "jquery", 
+  .plugins("jquery",
     "jquery/controller",
     "jquery/controller/subscribe",
-    "jquery/controller/view",
-    "jquery/view/ejs",
     "funcunit/qunit")
-	.views('testBodyTemplate.ejs',
-         'wait.ejs',
-         'pickemail.ejs',
-         'offline.ejs',
-         'error.ejs')
-  .then("js/browserid_unit_test")
-  .then("js/page_helpers_unit_test")
-  .then("include_unit_test")
-  .then("relay/relay_unit_test")
-  .then("pages/add_email_address_test")
-  .then("pages/verify_email_address_test")
-  .then("pages/forgot_unit_test")
-  .then("pages/signin_unit_test")
-  .then("pages/signup_unit_test")
-  .then("pages/manage_account_unit_test")
-  .then("resources/tooltip_unit_test")
-  .then("resources/error-display_unit_test")
-  .then("resources/channel_unit_test")
-  .then("resources/browser-support_unit_test")
-  .then("resources/validation_unit_test")
-  .then("resources/storage_unit_test")
-  .then("resources/network_unit_test")
-  .then("resources/user_unit_test")
-  .then("controllers/page_controller_unit_test")
-  .then("controllers/pickemail_controller_unit_test")
-  .then("controllers/dialog_controller_unit_test")
-  .then("controllers/checkregistration_controller_unit_test")
-  .then("controllers/authenticate_controller_unit_test")
+  .then("/dialog/resources/dom-jquery.js",
+        "js/browserid_unit_test",
+        "js/page_helpers_unit_test",
+        "include_unit_test",
+        "relay/relay_unit_test",
+        "pages/add_email_address_test",
+        "pages/verify_email_address_test",
+        "pages/forgot_unit_test",
+        "pages/signin_unit_test",
+        "pages/signup_unit_test",
+        "pages/manage_account_unit_test",
+        "resources/tooltip_unit_test",
+        "resources/error-display_unit_test",
+        "resources/channel_unit_test",
+        "resources/browser-support_unit_test",
+        "resources/validation_unit_test",
+        "resources/storage_unit_test",
+        "resources/network_unit_test",
+        "resources/user_unit_test",
+        "controllers/page_controller_unit_test",
+        "controllers/pickemail_controller_unit_test",
+        "controllers/dialog_controller_unit_test",
+        "controllers/checkregistration_controller_unit_test",
+        "controllers/authenticate_controller_unit_test");