diff --git a/src/main/webapp/WEB-INF/tags/page/xnat.tag b/src/main/webapp/WEB-INF/tags/page/xnat.tag
index 924ed878e7c1ada7207dc064bd86d0b340210098..a2c8cbe11e18d1eeea65e3ad2656a34cc80d75d8 100644
--- a/src/main/webapp/WEB-INF/tags/page/xnat.tag
+++ b/src/main/webapp/WEB-INF/tags/page/xnat.tag
@@ -670,23 +670,27 @@ ${bodyTop}
 
     loadjs(scriptUrl('xnat/event.js'), function(){
 
+        var clicker = XNAT.event.click('#header_logo, #xnat_power > a');
+
         // shift-click the header or footer XNAT logo to TOGGLE debug mode on/off
+        clicker.shiftKey(function(e){
+            e.preventDefault();
+            if (Cookies.get('debug') === 'on'){
+                Cookies.set('debug', 'off');
+                window.location.hash = 'debug=off';
+            }
+            else {
+                Cookies.set('debug', 'on');
+                window.location.hash = 'debug=on';
+            }
+            window.location.reload();
+        });
+
         // alt-shift-click to open the Swagger page in a new window
-        XNAT.event.click('#header_logo, #xnat_power > a')
-            .shiftKey(function(e){
-                e.preventDefault();
-                if (Cookies.get('debug') === 'on'){
-                    window.location.hash = 'debug=off';
-                }
-                else {
-                    window.location.hash = 'debug=on';
-                }
-                window.location.reload();
-            })
-            .altShift(function(e){
-                e.preventDefault();
-                XNAT.ui.popup(XNAT.url.rootUrl('/xapi/swagger-ui.html'));
-            });
+        clicker.altShift(function(e){
+            e.preventDefault();
+            XNAT.ui.popup(XNAT.url.rootUrl('/xapi/swagger-ui.html'));
+        });
 
     })
 
diff --git a/src/main/webapp/page/admin/content.jsp b/src/main/webapp/page/admin/content.jsp
index ec8aeace85c4ad74be93ae8f903027166a2da393..7f9c30cf28e45fd5dc13c285525acb7a23c8571a 100755
--- a/src/main/webapp/page/admin/content.jsp
+++ b/src/main/webapp/page/admin/content.jsp
@@ -44,8 +44,9 @@
                         delete XNAT.data.siteConfig.targetSource;
 
 
-                        // var jsonUrl = XNAT.url.rootUrl('/page/admin/data/config/site-admin-sample-new.yaml');
+//                      var jsonUrl = XNAT.url.rootUrl('/page/admin/data/config/site-admin-sample-new.yaml');
                         var jsonUrl = XNAT.url.rootUrl('/xapi/spawner/resolve/siteAdmin/adminPage');
+//                        var jsonUrl = XNAT.url.rootUrl('/page/admin/data/site-admin-page.json');
 
                         $.get({
                             url: jsonUrl,
diff --git a/src/main/webapp/page/admin/data/config/site-setup.yaml b/src/main/webapp/page/admin/data/config/site-setup.yaml
index 547033e1307a52e15045ed9575bedc1585ce65ac..d9879a40a8f060b1ab478561c2b8ce1d2c452081 100644
--- a/src/main/webapp/page/admin/data/config/site-setup.yaml
+++ b/src/main/webapp/page/admin/data/config/site-setup.yaml
@@ -1,6 +1,5 @@
 siteSetup:
     kind: app.siteSetup.form
-#    kind: panel.multiForm
     name: siteSetup
     label: XNAT Initial Setup
 #    method: GET
@@ -21,7 +20,7 @@ siteSetup:
                     marginBottom: 24px
                 html: >
                     The settings below need to be configured before this XNAT system
-                    can be used. Please set the properties below and submit the form continue.
+                    can be used. Please set the properties below and submit the form to continue.
 
         # ====================
         # PANEL
@@ -33,9 +32,8 @@ siteSetup:
             method: POST
             action: /xapi/siteConfig/batch
             contentType: json
-            load:
-                lookup: XNAT.data.siteConfig
-                refresh: /xapi/siteConfig
+            load: ?? XNAT.data.siteConfig
+            refresh: /xapi/siteConfig
             contents:
 
                 siteId:
@@ -82,9 +80,8 @@ siteSetup:
             method: POST
             action: /xapi/siteConfig/batch
             contentType: json
-            load:
-                lookup: XNAT.data.siteConfig
-                refresh: /xapi/siteConfig
+            load: ?? XNAT.data.siteConfig
+            refresh: /xapi/siteConfig
             contents:
 
                 archivePath:
@@ -145,16 +142,16 @@ siteSetup:
             method: POST
             action: /xapi/siteConfig/smtpServer
             contentType: json
-            load:
-                #lookup: XNAT.data.siteConfig.smtpServer
-                refresh: /xapi/siteConfig/smtpServer
+#            load: ?? XNAT.data.siteConfig.smtpServer
+            refresh: /xapi/siteConfig/smtpServer
             contents:
 
                 host:
                     kind: panel.input.text
                     name: host
                     label: Host
-                    value: ?? XNAT.data.siteConfig.smtpServer.host
+                    value: ?? XNAT:data:siteConfig:smtpServer:host
+#                    value: ""
                     placeholder: localhost
                     validation: required
 
@@ -162,7 +159,8 @@ siteSetup:
                     kind: panel.input.number
                     name: port
                     label: Port
-                    value: ?? XNAT.data.siteConfig.smtpServer.port
+                    value: ?? XNAT:data:siteConfig:smtpServer:port
+#                    value: ""
                     placeholder: 25
                     validation: required number
 
@@ -170,19 +168,22 @@ siteSetup:
                     kind: panel.input.text
                     name: username
                     label: Username
-                    value: ?? XNAT.data.siteConfig.smtpServer.username
+                    value: ?? XNAT:data:siteConfig:smtpServer:username
+#                    value: ""
 
                 password:
                     kind: panel.input.password
                     name: password
                     label: Password
-                    value: ?? XNAT.data.siteConfig.smtpServer.password
+                    value: ?? XNAT:data:siteConfig:smtpServer:password
+#                    value: ""
 
                 protocol:
                     kind: panel.input.text
                     name: protocol
                     label: Protocol
-                    value: ?? XNAT.data.siteConfig.smtpServer.protocol
+                    value: ?? XNAT:data:siteConfig:smtpServer:protocol
+#                    value: ""
 
                 mailServerProperties:
                     kind: panel.subhead
@@ -192,16 +193,20 @@ siteSetup:
                     kind: panel.input.checkbox
                     name: mail.smtp.auth
                     label: SMTP Auth?
-                    value: ?? XNAT.data.siteConfig.smtpServer['mail.smtp.auth']
+                    value: ?? XNAT:data:siteConfig:smtpServer:mail.smtp.auth
+#                    value: ""
 
                 smtpStartTls:
                     kind: panel.input.checkbox
                     name: mail.smtp.starttls.enable
                     label: Smart TLS?
-                    value: ?? XNAT.data.siteConfig.smtpServer['mail.smtp.starttls.enable']
+                    value: ?? XNAT:data:siteConfig:smtpServer:mail.smtp.starttls.enable
+#                    value: ""
 
                 smtpSSLTrust:
                     kind: panel.input.text
                     name: mail.smtp.ssl.trust
                     label: SSL Trust
-                    value: ?? XNAT.data.siteConfig.smtpServer['mail.smtp.ssl.trust']
+                    value: ?? XNAT:data:siteConfig:smtpServer:mail.smtp.ssl.trust
+#                    value: ""
+
diff --git a/src/main/webapp/page/admin/spawner/spawner-admin.js b/src/main/webapp/page/admin/spawner/spawner-admin.js
index 30464ee17213dbfef1eaf7cedf1b45164551ca32..7645623f608631cbedceec1a0a4d0e504f936c3c 100644
--- a/src/main/webapp/page/admin/spawner/spawner-admin.js
+++ b/src/main/webapp/page/admin/spawner/spawner-admin.js
@@ -123,6 +123,7 @@ XNAT.xhr.getJSON({
                                     console.log(obj)
                                 },
                                 okLabel: 'Save Changes',
+                                okClose: false,
                                 okAction: function(obj){
                                     XNAT.xhr.put({
                                         url: elementUrl,
diff --git a/src/main/webapp/scripts/xmodal-v1/xmodal.js b/src/main/webapp/scripts/xmodal-v1/xmodal.js
index 117922845f2ab21c7a3661d9cadf3b48e471e166..d81ab12c7d335b17c28af83fd4e8d74a40012c66 100644
--- a/src/main/webapp/scripts/xmodal-v1/xmodal.js
+++ b/src/main/webapp/scripts/xmodal-v1/xmodal.js
@@ -1352,6 +1352,11 @@ if (typeof jQuery == 'undefined') {
                 opts.id = arg3.id || arg2;
             }
 
+            // don't open a second loader with the same id
+            if (xmodal.modals._ids.indexOf(opts.id) > 0) {
+                return false;
+            }
+
             return xmodal.open(opts);
 
         };
diff --git a/src/main/webapp/scripts/xnat/app/siteSetup.js b/src/main/webapp/scripts/xnat/app/siteSetup.js
index eb0119d3a5a251e87cd4082e003bc022b131d27b..e14b12b3cc7d011db8b5716775bb60dbe49b9165 100644
--- a/src/main/webapp/scripts/xnat/app/siteSetup.js
+++ b/src/main/webapp/scripts/xnat/app/siteSetup.js
@@ -32,6 +32,18 @@ var XNAT = getObject(XNAT);
     
     // use app.siteSetup.form for Spawner 'kind'
 
+    // call 'test' until it returns true
+    function waitForIt(interval, test, callback){
+        var waiting = setInterval(function(){
+            if (test()) {
+                var called = callback();
+                clearInterval(waiting);
+                return called;
+            }
+        }, interval || 10);
+        return waiting;
+    }
+
     // creates a panel that submits all forms contained within
     siteSetup.form = function(opts, callback){
 
@@ -85,53 +97,118 @@ var XNAT = getObject(XNAT);
 
                     var loader = xmodal.loading.open('#multi-save');
 
-                    // reset error count on new submission
-                    multiform.errors = 0;
+                    // reset success count on new submission
+                    multiform.success = 0;
 
                     // how many child forms are there?
                     multiform.count = $forms.length;
 
+                    // set error count to form count and subtract
+                    // as submissions are successful
+                    multiform.errors = 0;
+
                     // submit ALL enclosed forms
                     $forms.each(function(){
-                        $(this).addClass('silent').trigger('submit');
+                        var $form = $(this).addClass('json silent');
+                        XNAT.xhr.form($form, {
+                            contentType: 'application/json',
+                            validate: function(){
+
+                                var $form = $(this);
+                                var errors = 0;
+                                var validation = true;
+
+                                $form.dataAttr('errors', 0);
+
+                                $form.find(':input.required').each(function(){
+                                    var $input = $(this);
+                                    $input.removeClass('invalid');
+                                    if ($input.val() === '') {
+                                        errors++;
+                                        validation = false;
+                                        $input.addClass('invalid');
+                                    }
+                                });
+
+                                $form.dataAttr('errors', errors);
+
+                                if (!validation) {
+                                    $form.dataAttr('status','error').addClass('error');
+                                    multiform.errors++;
+                                    //don't show a dialog for each individual form
+                                    //if (!$form.hasClass('silent')) {
+                                        xmodal.message('Error','Please enter values for the required items and re-submit the form.');
+                                    //}
+                                }
+
+                                return validation;
+
+                            },
+                            //contentType: 'json',
+                            success: function(){
+                                $form
+                                    .dataAttr('status', 'success')
+                                    .removeClass('error')
+                                    .addClass('success');
+                                multiform.success++
+                            },
+                            error: function(){
+                                $form
+                                    .dataAttr('status', 'error')
+                                    .removeClass('success')
+                                    .addClass('error');
+                                multiform.errors++
+                            }
+                        });
+                        // $(this).addClass('silent').trigger('submit');
                     });
 
-                    multiform.errors = $forms.filter('.error').length;
+                    // multiform.errors = $forms.filter('.error').length;
+
+                    function initialize(a, b, c){
+
+                        XNAT.xhr.postJSON({
+                            url: XNAT.url.rootUrl('/xapi/siteConfig/batch'),
+                            data: JSON.stringify({initialized:true}),
+                            success: function(){
+                                xmodal.message({
+                                    title: false,
+                                    esc: false,
+                                    content: 'Your XNAT site is ready to use. Click "OK" to continue to the home page.',
+                                    action: function(){
+                                        // window.location.href = XNAT.url.rootUrl('/setup?init=true');
+                                        window.location.href = XNAT.url.rootUrl('/');
+                                        //$forms.each.triggerHandler('reload-data');
+                                    }
+                                });
+                            }
+                        }).fail(function(e, txt, jQxhr){
+                            xmodal.message({
+                                title: 'Error',
+                                content: [
+                                    'An error occurred during initialization',
+                                    e,
+                                    txt
+                                ].join(': <br>')
+                            })
+                        }).always(function(){
+                            xmodal.loading.close(loader.$modal);
+                        });
+                    }
 
-                    if (multiform.errors) {
-                        xmodal.closeAll();
-                        xmodal.message('Error', 'Please correct the highlighted errors and re-submit the form.');
-                        return false;
+                    function errorCheck(){
+                        return multiform.errors || multiform.success === multiform.count;
                     }
 
-                    XNAT.xhr.postJSON({
-                        url: XNAT.url.rootUrl('/xapi/siteConfig/batch'),
-                        data: JSON.stringify({initialized:true}),
-                        success: function(){
-                            xmodal.message({
-                                title: false,
-                                esc: false,
-                                content: 'Your XNAT site is ready to use. Click "OK" to continue to the home page.',
-                                action: function(){
-                                    // window.location.href = XNAT.url.rootUrl('/setup?init=true');
-                                    window.location.href = XNAT.url.rootUrl('/');
-                                    //$forms.each.triggerHandler('reload-data');
-                                }
-                            });
+                    waitForIt(100, errorCheck, function(){
+                        if (multiform.errors) {
+                            xmodal.closeAll();
+                            xmodal.message('Error', 'Please correct the highlighted errors and re-submit the form.');
+                            return false;
                         }
-                    }).fail(function(e, txt, jQxhr){
-                        xmodal.loading.close(loader.$modal);
-                        xmodal.message({
-                            title: 'Error',
-                            content: [
-                                'An error occurred during initialization',
-                                e,
-                                txt
-                            ].join(': <br>')
-                        })
+                        initialize();
                     });
 
-                    xmodal.loading.close(loader.$modal);
                     return false;
 
                 }
diff --git a/src/main/webapp/scripts/xnat/spawner.js b/src/main/webapp/scripts/xnat/spawner.js
index 98a068fd5da4f6840ad2236538c2992af0296dfd..be902f8ab51016857d8cc33ec9ecad824fa94172 100644
--- a/src/main/webapp/scripts/xnat/spawner.js
+++ b/src/main/webapp/scripts/xnat/spawner.js
@@ -33,6 +33,10 @@ var XNAT = getObject(XNAT);
     // keep track of items that didn't spawn
     spawner.notSpawned = [];
 
+    function setRoot(url){
+        url = url.replace(/^(\.\/+)/, '/');
+        return XNAT.url.rootUrl(url)
+    }
 
     // ==================================================
     // MAIN FUNCTION
@@ -60,6 +64,12 @@ var XNAT = getObject(XNAT);
             // with a fallback to a generic div
             kind = prop.kind || prop.type || 'div.spawned';
 
+            // make 'href' 'src' and 'action' properties
+            // start at the site root if starting with './'
+            if (prop.config.href) {
+                prop.config.href = setRoot(prop.config.href)
+            }
+
             // do a raw spawn() if 'kind' is 'element'
             // or if there's a tag property
             if (kind === 'element' || prop.tag) {
diff --git a/src/main/webapp/scripts/xnat/ui/panel.js b/src/main/webapp/scripts/xnat/ui/panel.js
index d9220e9b5872099083387caa717f88ae3a417401..54b24abfe724ba50669b7fd12aeff1f8a6fb1447 100644
--- a/src/main/webapp/scripts/xnat/ui/panel.js
+++ b/src/main/webapp/scripts/xnat/ui/panel.js
@@ -35,37 +35,15 @@ var XNAT = getObject(XNAT || {});
         });
         return obj.data;
     }
-
-    // another way to do this without using eval()
-    // is to loop over object string using dot notation:
-    // var myVal = lookupObjectValue(XNAT, 'data.siteConfig.siteId');
-    // --> myVal == 'myXnatSiteId'
-    function lookupObjectValue(root, objStr){
-        var val = '';
-        if (!objStr) {
-            objStr = root;
-            root = window;
-        }
-        root = root || window;
-        objStr.toString().trim().split('.').forEach(function(part, i){
-            // start at the root object
-            if (i === 0) {
-                val = root[part] || {};
-            }
-            else {
-                val = val[part];
-            }
-        });
-        return val;
-    }
-
+    
     // string that indicates to look for a namespaced object value
     var doLookupString = '??';
 
     function doLookup(input){
         if (!input) return '';
-        if (input.toString().trim().indexOf(doLookupString) === 0){
-            return lookupObjectValue(window, input.split(doLookupString)[1]);
+        if (input.toString().indexOf(doLookupString) === 0){
+            input = input.split(doLookupString)[1].trim();
+            return lookupObjectValue(window, input);
         }
         return input;
     }
@@ -139,9 +117,11 @@ var XNAT = getObject(XNAT || {});
                     ['h3.panel-title', opts.title || opts.label]
                 ]],
 
-                // target is where the next spawned item will render
+                
+                // target is where this form's "contents" will be inserted
                 _target,
 
+                
                 (hideFooter ? ['div.hidden'] : ['div.panel-footer', opts.footer || _footer])
 
             ]);
@@ -151,96 +131,117 @@ var XNAT = getObject(XNAT || {});
             _formPanel.id = (opts.id || opts.element.id) + '-panel';
         }
 
+        // cache a jQuery-wrapped element
+        var $formPanel = $(_formPanel);
+
         // set form element values from an object map
         function setValues(form, dataObj){
-            // pass a single argument to work with this form
-            if (!dataObj) {
-                dataObj = form;
-                form = _formPanel;
-            }
             // find all form inputs with a name attribute
             $$(form).find(':input[name]').each(function(){
-                var val = '';
-                if (Array.isArray(dataObj)) {
-                    val = dataObj.join(', ');
+                var val = dataObj[this.name];
+                if (!val) return;
+                if (Array.isArray(val)) {
+                    val = val.join(', ');
                 }
                 else {
-                    val = /string|number/i.test(typeof dataObj) ? dataObj : dataObj[this.name] || '';
+                    val = stringable(val) ? val : JSON.stringify(val);
                 }
                 $(this).changeVal(val);
             });
-            if (xmodal && xmodal.loading && xmodal.loading.close){
-                xmodal.loading.close();
+            if (xmodal && xmodal.loading && xmodal.loading.closeAll){
+                xmodal.loading.closeAll();
             }
         }
 
         // populate the data fields if this panel is in the 'active' tab
         // (only getting values for the active tab should cut down on requests)
-        function loadData(obj){
+        function loadData(form, obj){
 
-            if (!obj) {
-                obj = opts.load || {};
+            obj = cloneObject(obj);
+
+            xmodal.loading.open('#load-data');
+
+            // need a form to put the data into!
+            if (!form) {
+                xmodal.loading.close('#load-data');
+                return;
             }
 
-            obj = cloneObject(obj);
+            // if 'load' starts with ??, do lookup
+            var doLookup = '??';
+
+            if (obj.load && obj.load.toString().indexOf(doLookup) === 0) {
+                obj.load = (obj.load.split(doLookup)[1]||'').trim().split('|')[0];
+                obj.prop = obj.prop || obj.load.split('|')[1] || '';
+                setValues(form, lookupObjectValue(window, obj.load, obj.prop));
+                xmodal.loading.close('#load-data');
+                return form;
 
-            obj.form = obj.form || obj.target || obj.element || _formPanel;
+            }
+            
+            // if 'load' starts with '!?' do an eval()
+            var doEval = '!?';
+            if (obj.load && obj.load.toString().indexOf(doEval) === 0) {
+                obj.load = (obj.load.split(doEval)[1]||'').trim();
+                setValues(form, eval(obj.load));
+                xmodal.loading.close('#load-data');
+                return form;
 
-            // need a form to put the data into
-            if (!obj.form) return;
+            }
+            
+            //////////
+            // REST
+            //////////
 
-            // // if there's a 'refresh' url, make that obj.url
-            // if (obj.refresh) obj.url = obj.refresh;
+            // if 'load' starts with $?, do ajax request
+            var ajaxPrefix = '$?';
+            var ajaxUrl = '';
+            var ajaxProp = '';
 
-            // if we pass data in a 'lookup' property, just use that
-            // to avoid doing a server request
 
-            if (obj.lookup && !obj.url) {
-                if (Array.isArray(obj.lookup)) {
-                    obj.lookup = obj.lookup[0];
-                }
-                else {
-                    try {
-                        obj.lookup = eval(obj.lookup);
-                    }
-                    catch (e) {
-                        if (console && console.log) console.log(e);
-                        obj.lookup = ''
-                    }
-                }
-                setValues(obj.form, obj.lookup);
-                return obj.form;
+            if (obj.refresh) {
+                ajaxUrl = obj.refresh;
+            }
+            // value: $? /path/to/data | obj:prop:name
+            else if (obj.load && obj.load.toString().indexOf(ajaxPrefix) === 0) {
+                ajaxUrl = obj.load;
             }
 
-            // otherwise try to get the data values via ajax
+            ajaxUrl = (ajaxUrl.split(ajaxPrefix)[1]||'').trim().split('|')[0];
+            ajaxProp = ajaxUrl.split('|')[1] || '';
 
             // need a url to get the data
-            if (!obj.url) return obj.form;
+            if (!ajaxUrl || !stringable(ajaxUrl)) {
+                xmodal.loading.close('#load-data');
+                return form;
+            }
 
-            obj.method = obj.method || 'GET';
+            // force GET method
+            obj.method = 'GET';
 
             // setup the ajax request
             // override values with an
             // 'ajax' or 'xhr' property
             obj.ajax = extend(true, {
                 method: obj.method,
-                url: XNAT.url.restUrl(obj.url)
+                url: XNAT.url.rootUrl(ajaxUrl)
             }, obj.ajax || obj.xhr);
 
-            // allow use of 'prop' or 'root' for the root property name
-            obj.prop = obj.prop || obj.root;
-
             obj.ajax.success = function(data){
-                var prop = data;
-                // if there's a property to target,
-                // specify the 'prop' property
-                if (obj.prop){
-                    obj.prop.split('.').forEach(function(part){
-                        prop = prop[part];
-                    });
+                if (ajaxProp){
+                    data = data[ajaxProp];
                 }
-                $(obj.form).dataAttr('status', 'clean');
-                setValues(prop);
+                $(form).dataAttr('status', 'clean');
+                setValues(form, data);
+            };
+
+            obj.ajax.error = function(){
+                $(form).dataAttr('status', 'error');
+            };
+
+
+            obj.ajax.complete = function(){
+                xmodal.loading.close('#load-data');
             };
 
             // return the ajax thing for method chaining
@@ -248,11 +249,9 @@ var XNAT = getObject(XNAT || {});
 
         }
 
-        //if (opts.load){
-        //    loadData(opts.load);
-        //}
-
-        var $formPanel = $(_formPanel);
+        // if (opts.load) {
+        //     loadData(_formPanel, opts)
+        // }
 
         // keep an eye on the inputs
         $formPanel.find(':input').on('change', function(){
@@ -262,14 +261,16 @@ var XNAT = getObject(XNAT || {});
         opts.onload = opts.onload || callback;
 
         $formPanel.on('reload-data', function(){
-            xmodal.loading.open();
-            opts.load.url = opts.load.url || opts.load.refresh;
-            loadData(opts.load);
+            loadData(this, {
+                refresh: opts.refresh || opts.load || opts.url
+            });
         });
 
         // click 'Discard Changes' button to reload data
         _resetBtn.onclick = function(){
-            $formPanel.triggerHandler('reload-data');
+            if (!/^#/.test($formPanel.attr('action')||'#')){
+                $formPanel.triggerHandler('reload-data');
+            }
         };
 
         opts.callback = opts.callback || callback || diddly;
@@ -294,6 +295,11 @@ var XNAT = getObject(XNAT || {});
                 silent = $form.hasClass('silent'),
                 multiform = {};
 
+            // don't submit forms with 'action' starting with '#'
+            if (/^#/.test($form.attr('action')||'#')) {
+                return false;
+            }
+
             $form.dataAttr('errors', 0);
 
             // validate inputs before moving on
@@ -313,13 +319,12 @@ var XNAT = getObject(XNAT || {});
                 if (!silent) {
                     xmodal.message('Error','Please enter values for the required items and re-submit the form.');
                 }
-                multiform.errors++; // keep track of errors for multi-form submission
                 return false;
             }
 
-            // don't open loading dialog for multiform submit
+            // only open loading dialog for standard (non-multi) submit
             if (!multiform.count){
-                xmodal.loading.open('#form-save');
+                var saveLoader = xmodal.loading.open('#form-save');
             }
 
             // var ajaxSubmitOpts = {
@@ -356,29 +361,16 @@ var XNAT = getObject(XNAT || {});
             var ajaxConfig = {
                 method: opts.method,
                 url: this.action,
-                success: function(data){
+                success: function(){
                     var obj = {};
-                    // if a data object is returned,
-                    // just use that
-                    if (data) {
-                        // HACK!
-                        // wrap the returned data in an array so the
-                        // loadData() function handles it properly
-                        obj.lookup = [data];
-                    }
-                    else {
-                        obj.url = opts.refresh;
-                    }
-
-                    // don't mess with modals for multiforms
+                    // actually, NEVER use returned data...
+                    // ALWAYS reload from the server
+                    obj.refresh = opts.refresh || opts.reload || opts.url || opts.load;
                     if (!silent){
-                        xmodal.loading.close('#form-save');
+                        xmodal.loading.close(saveLoader.$modal);
                         xmodal.message('Data saved successfully.', {
                             action: function(){
-                                loadData(obj);
-                                if (callback && isFunction(callback)) {
-
-                                }
+                                loadData($form, obj);
                             }
                         });
                     }
@@ -401,8 +393,10 @@ var XNAT = getObject(XNAT || {});
 
         // this object is returned to the XNAT.spawner() method
         return {
-            load: loadData,
-            setValues: setValues,
+            load: function(){
+                loadData(_formPanel, opts)
+            },
+            // setValues: setValues,
             target: _target,
             element: _formPanel,
             spawned: _formPanel,
@@ -646,6 +640,13 @@ var XNAT = getObject(XNAT || {});
         return XNAT.ui.template.panelInput(opts).spawned;
     };
 
+    panel.input.date = function panelInputPassword(opts){
+        opts = cloneObject(opts);
+        opts.type = 'date';
+        addClassName(opts, 'date');
+        return XNAT.ui.template.panelInput(opts).spawned;
+    };
+
     panel.input.checkbox = function panelInputCheckbox(opts){
         opts = cloneObject(opts);
         opts.type = 'checkbox';
@@ -708,7 +709,7 @@ var XNAT = getObject(XNAT || {});
         var textarea = spawn('textarea', opts.element);
         return XNAT.ui.template.panelDisplay(opts, textarea).spawned;
     };
-    panel.input.textares = panel.textarea;
+    panel.input.textarea = panel.textarea;
 
     //////////////////////////////////////////////////
     // SELECT MENU PANEL ELEMENTS
diff --git a/src/main/webapp/scripts/xnat/ui/templates.js b/src/main/webapp/scripts/xnat/ui/templates.js
index ca152a22485b59620076542e3b1a8651ffccb816..df5dadf4d40bc78fcda445acc23f3aa560752117 100644
--- a/src/main/webapp/scripts/xnat/ui/templates.js
+++ b/src/main/webapp/scripts/xnat/ui/templates.js
@@ -264,6 +264,8 @@ var XNAT = getObject(XNAT);
         // check buttons if value is true
         if (/checkbox|radio/i.test(opts.type||'')) {
 
+            element.checked = /true|checked/i.test((opts.checked || element.value).toString());
+
             // add a hidden input to capture the checkbox/radio value
             hiddenInput = spawn('input', {
                 type: 'hidden',
@@ -271,8 +273,6 @@ var XNAT = getObject(XNAT);
                 value: element.checked
             });
 
-            element.checked = /true|checked/i.test((opts.checked || element.value).toString());
-
             // change the value of the hidden input onclick
             element.onclick = function(){
                 hiddenInput.value = this.checked.toString();
diff --git a/src/main/webapp/scripts/xnat/xhr.js b/src/main/webapp/scripts/xnat/xhr.js
index b285ff42d3d7b77cb300329ceafb0c6df96d683c..e0df7ab2fcb9b2d2efa6986dce28268c751973f6 100755
--- a/src/main/webapp/scripts/xnat/xhr.js
+++ b/src/main/webapp/scripts/xnat/xhr.js
@@ -72,7 +72,23 @@ var XNAT = getObject(XNAT||{}),
     //extend(xhr, url);
 
     xhr.$ = getObject(xhr.$||{});
-
+    // adding shortcut methods: put and delete AJAX calls for clarity
+    $.each(["put", "delete"], function(i, method) {
+        $[method] = function(url, data, callback, type) {
+            if ($.isFunction(data)) {
+                type = type || callback;
+                callback = data;
+                data = undefined;
+            }
+            return $.ajax({
+                url: url,
+                type: method,
+                dataType: type,
+                data: data,
+                success: callback
+            });
+        };
+    });
     // Direct maps to jQuery's AJAX methods.
     // Why use these instead of jQuery directly?
     // For flexibility to allow XNAT's AJAX
@@ -80,9 +96,10 @@ var XNAT = getObject(XNAT||{}),
     xhr.$.ajax      = xhr.ajax$      = $.ajax;
     xhr.$.get       = xhr.get$       = $.get;
     xhr.$.post      = xhr.post$      = $.post;
+    xhr.$.put       = xhr.put$       = $.put;
+    xhr.$.delete    = xhr.delete$    = $.delete;
     xhr.$.getJSON   = xhr.getJSON$   = $.getJSON;
     xhr.$.getScript = xhr.getScript$ = $.getScript;
-
     xhr.$.load = xhr.load$ = function(selector, url, data, success){
         $$(selector).load(url, data, success);
     };
@@ -381,9 +398,9 @@ var XNAT = getObject(XNAT||{}),
 
     function processJSON(data, stringify){
         var output = {};
-        $.each(data, function(prop, val){
-            prop = val.name || prop;
-            val  = (val.value || val) || '';
+        forEach(data, function(item){
+            var prop = item.name;
+            var val  = item.value;
             if (typeof output[prop] == 'undefined') {
                 output[prop] = val;
             }
@@ -420,12 +437,7 @@ var XNAT = getObject(XNAT||{}),
         }
         return $el;
     }
-
-    // can the value be reasonably used as a string?
-    function stringable(val){
-        return /string|number|boolean/.test(typeof val);
-    }
-
+    
     // set form element values from an object map
     function setValues(form, dataObj){
         // cache and check if form exists
@@ -455,11 +467,7 @@ var XNAT = getObject(XNAT||{}),
 
     // this could be a handy jQuery method
     $.fn.setValues = function(dataObj){
-        // only run on form or div elements
-        // (gotta draw the line somewhere)
-        if (/form|div/i.test(this.tagName||'')) {
-            setValues(this, dataObj);
-        }
+        setValues(this, dataObj);
         return this;
     };
 
@@ -467,12 +475,24 @@ var XNAT = getObject(XNAT||{}),
 
         var $form = $$(form),
             _form = $form[0], // raw DOM element
+            validation = true,
             callback = diddly;
 
         opts = cloneObject(opts);
-        opts.url = XNAT.url.restUrl(opts.url || _form.action);
+        opts.url = XNAT.url.rootUrl(opts.url || $form.attr('action'));
         opts.method = opts.method || _form.method || 'GET';
 
+        if ($.isFunction(opts.validate)) {
+            validation = opts.validate.apply(_form, opts);
+            if (!validation) {
+                $form.removeClass('valid').addClass('invalid');
+                return validation;
+            }    
+            else {
+                $form.removeClass('invalid').addClass('valid');
+            }
+        }
+        
         // set opts.callback:false to prevent the
         // 'standard' method callback from running
         if (opts.callback !== false) {
@@ -490,7 +510,8 @@ var XNAT = getObject(XNAT||{}),
             opts.success = function(data){
                 callback.apply($form, arguments);
                 // repopulate 'real' data after success
-                setValues($form, data);
+                // DON'T TRUST RETURNED DATA
+                //setValues($form, data);
             }
         }
         // populate form fields from returned
@@ -498,7 +519,8 @@ var XNAT = getObject(XNAT||{}),
         else if (/GET/i.test(opts.method)){
             opts.success = function(data){
                 callback.apply($form, arguments);
-                setValues($form, data);
+                // DON'T TRUST RETURNED DATA
+                //setValues($form, data);
             };
         }
 
@@ -507,11 +529,20 @@ var XNAT = getObject(XNAT||{}),
 
     };
 
+    // $('form.foo').submitJSON();
+    $.fn.submitJSON = function(opts){
+        $(this).addClass('json');
+        return xhr.form(this, extend(true, {
+            method: this.method || 'POST',
+            processData: false,
+            contentType: 'application/json'
+        }, opts))
+    };
+
     // intercept form submissions with 'ajax' or 'json' class
-    $('body').on('submit', 'form.ajax, form.json', function(e){
-        e.preventDefault();
-        xhr.form(this);
-        return false;
+    // using namespaced event handler submit.json
+    $('body').on('submit-json, submit-ajax', 'form.ajax, form.json', function(opts){
+        return xhr.form(this, opts);
     });
 
     // special case for YUI 'GET' request
@@ -608,3 +639,4 @@ var XNAT = getObject(XNAT||{}),
     xhr.loaded = true;
 
 })(XNAT, jQuery, YAHOO);
+
diff --git a/src/main/webapp/xdat-templates/navigations/powered_by.vm b/src/main/webapp/xdat-templates/navigations/powered_by.vm
index 6c7489f99b64283d0137cbb9776221d28da0629a..f6f478e1fa74086a59e847240c26aca3bfc76e87 100644
--- a/src/main/webapp/xdat-templates/navigations/powered_by.vm
+++ b/src/main/webapp/xdat-templates/navigations/powered_by.vm
@@ -7,24 +7,27 @@
 
     loadjs(scriptUrl('xnat/event.js'), function(){
 
-        // shift-click the header or footer XNAT logo to ENABLE debug mode
-        // alt-shift-click to DISABLE debug mode
-        // ctrl-alt-click to open the Swagger page in a new window
-        XNAT.event.click('#header_logo, #xnat_power > a')
-            .shiftKey(function(e){
-                e.preventDefault();
-                window.location.hash = 'debug=on'
-                window.location.reload();
-            })
-            .altShift(function(e){
-                e.preventDefault();
-                window.location.hash = 'debug=off'
-                window.location.reload();
-            })
-            .ctrlAlt(function(e){
-                e.preventDefault();
-                XNAT.ui.popup('/xapi/swagger-ui.html');
-            });
+        var clicker = XNAT.event.click('#header_logo, #xnat_power > a');
+
+        // shift-click the header or footer XNAT logo to TOGGLE debug mode on/off
+        clicker.shiftKey(function(e){
+            e.preventDefault();
+            if (Cookies.get('debug') === 'on'){
+                Cookies.set('debug', 'off');
+                window.location.hash = 'debug=off';
+            }
+            else {
+                Cookies.set('debug', 'on');
+                window.location.hash = 'debug=on';
+            }
+            window.location.reload();
+        });
+
+        // alt-shift-click to open the Swagger page in a new window
+        clicker.altShift(function(e){
+            e.preventDefault();
+            XNAT.ui.popup(XNAT.url.rootUrl('/xapi/swagger-ui.html'));
+        });
 
     })