diff --git a/src/main/webapp/WEB-INF/tags/page/xnat.tag b/src/main/webapp/WEB-INF/tags/page/xnat.tag
index 5169186ca6c128a5d9a23b2498301bd9b639d75b..dd6dfea352a6b2b9bd2040f2299200b36ff6be40 100644
--- a/src/main/webapp/WEB-INF/tags/page/xnat.tag
+++ b/src/main/webapp/WEB-INF/tags/page/xnat.tag
@@ -567,19 +567,19 @@ ${bodyTop}
                 jq('.main_header').height(hdr_logo_height + 10);
             }
     
-            ## Commented out 2016/09/02 (XNAT-4501).  I don't think we want to do this (See home page when this takes effect)
-            ##// adjust width of main nav if logo is wider than 175px
-            ##var hdr_logo_width = header_logo$.width();
-            ##if (hdr_logo_width > 175) {
-            ##    jq('#main_nav').width(932 - hdr_logo_width - 20);
-            ##}
+            //Commented out 2016/09/02 (XNAT-4501).  I don't think we want to do this (See home page when this takes effect)
+            // adjust width of main nav if logo is wider than 175px
+            //var hdr_logo_width = header_logo$.width();
+            //if (hdr_logo_width > 175) {
+            //    jq('#main_nav').width(932 - hdr_logo_width - 20);
+            //}
     
             //
             //var recent_proj_height = jq('#min_projects_list > div').height();
             var recent_proj_height = 67;
             //jq('#min_projects_list, #min_expt_list').height(recent_proj_height * 5).css({'min-width':349,'overflow-y':'scroll'});
     
-        }
+        };
 
         // initialize the advanced search method toggler
         XNAT.app.searchMethodToggler = function(parent$){
@@ -587,9 +587,9 @@ ${bodyTop}
             parent$ = $$(parent$ || 'body');
 
             var INPUTS = 'input, select, textarea, :input',
-                    SEARCH_METHOD_CKBOXES = 'input.search-method',
-                    searchGroups$ = parent$.find('div.search-group'),
-                    searchMethodInputs$ = parent$.find(SEARCH_METHOD_CKBOXES);
+                SEARCH_METHOD_CKBOXES = 'input.search-method',
+                searchGroups$ = parent$.find('div.search-group'),
+                searchMethodInputs$ = parent$.find(SEARCH_METHOD_CKBOXES);
 
             // disable 'by-id' search groups by default
             searchGroups$.filter('.by-id').addClass('disabled').find(INPUTS).not(SEARCH_METHOD_CKBOXES).changeVal('')
diff --git a/src/main/webapp/scripts/xnat/spawner.js b/src/main/webapp/scripts/xnat/spawner.js
index ed0a1cbb9f137f810d5e1e623617f6cfa15fe220..f587a363189c30f14d96e4d58739505d1eb24a99 100644
--- a/src/main/webapp/scripts/xnat/spawner.js
+++ b/src/main/webapp/scripts/xnat/spawner.js
@@ -53,10 +53,20 @@ var XNAT = getObject(XNAT);
         
         forOwn(obj, function(item, prop){
 
-            var kind, methodName, method, spawnedElement, $spawnedElement;
-            
-            // save the config properties in a new object
-            prop = cloneObject(prop);
+            var kind, element, method, spawnedElement, $spawnedElement;
+
+            // 'prop' can be a new or existing DOM element
+            if (prop instanceof Element) {
+                element = prop;
+                prop = {
+                    kind: 'element',
+                    element: element
+                };
+            }
+            else {
+                // save the config properties in a new object
+                prop = cloneObject(prop);
+            }
 
             prop.element = prop.element || prop.config || {};
 
@@ -65,12 +75,12 @@ var XNAT = getObject(XNAT);
             // lastly use the object's own name
             prop.name = prop.name || item;
 
-            prop.id = prop.id || prop.element.id || toDashed(prop.name);
+            //prop.id = prop.id || prop.element.id || toDashed(prop.name);
 
             // accept 'kind' or 'type' property name
             // but 'kind' will take priority
             // with a fallback to a generic div
-            kind = prop.kind || prop.type || 'div.spawned';
+            kind = prop.kind || prop.type || null;
 
             // make 'href' 'src' and 'action' properties
             // start at the site root if starting with '/'
@@ -93,8 +103,14 @@ var XNAT = getObject(XNAT);
                 prop.content = prop.content || prop.children || '';
 
                 try {
-                    spawnedElement =
-                        spawn(prop.tag || prop.element.tag || 'div', prop.element, prop.content);
+                    // if setting up Spawner elements in JS, allow a
+                    // DOM element to be passed in the 'element' property
+                    if (prop.element instanceof Element) {
+                        spawnedElement = prop.element;
+                    }
+                    else {
+                        spawnedElement = spawn(prop.tag || prop.element.tag || 'span', prop.element, prop.content);
+                    }
 
                     // convert relative URIs for href, src, and action attributes
                     if (spawnedElement.href) {
@@ -118,6 +134,9 @@ var XNAT = getObject(XNAT);
                     spawner.notSpawned.push(prop);
                 }
             }
+            else if (/^(text|html)$/i.test(kind)) {
+                $frag.append(prop.content||prop.html||prop.text)
+            }
             else {
 
                 // check for a matching XNAT.ui method to call:
@@ -135,6 +154,9 @@ var XNAT = getObject(XNAT);
                     // XNAT.ui.kind()
                     lookupObjectValue(NAMESPACE + '.' + kind) ||
 
+                    // XNAT.element.kind()
+                    lookupObjectValue(XNAT, 'element.' + kind) ||
+
                     // kind.init()
                     lookupObjectValue(kind + '.init') ||
 
diff --git a/src/main/webapp/scripts/xnat/ui/panel.js b/src/main/webapp/scripts/xnat/ui/panel.js
index 7f031905bf2799aae89f2846c887c77683440212..efea821715fbbd44cc901a887479e19741af7905 100644
--- a/src/main/webapp/scripts/xnat/ui/panel.js
+++ b/src/main/webapp/scripts/xnat/ui/panel.js
@@ -512,11 +512,12 @@ var XNAT = getObject(XNAT || {});
                 $$(form).serializeArray().forEach(function(item) {
                     if (!item.name) return;
                     var name = item.name.replace(/^:/,'');
+                    var val = firstDefined(item.value+'', '');
                     if (typeof json[name] == 'undefined') {
-                        json[name] = item.value || '';
+                        json[name] = val;
                     }
                     else {
-                        json[name] = [].concat(json[name], item.value||[]) ;
+                        json[name] = [].concat(json[name], val||[]) ;
                     }
                 });
                 return json;
@@ -909,7 +910,7 @@ var XNAT = getObject(XNAT || {});
             className: opts.className || opts.classes || '',
             name: opts.name,
             id: opts.id || toDashed(opts.name),
-            value: opts.value || ''
+            value: firstDefined(opts.value+'', '')
         }, opts.element);
         addClassName(opts.element, 'hidden');
         if (opts.validation || opts.validate) {
@@ -975,12 +976,17 @@ var XNAT = getObject(XNAT || {});
         if (opts.id) opts.element.id = opts.id;
         if (opts.name) opts.element.name = opts.name;
 
-        opts.element.html =
-            opts.element.html ||
-            opts.element.value ||
-            opts.value ||
-            opts.text ||
-            opts.html || '';
+        var val1 = opts.element.value;
+        var val2 = opts.value;
+
+        opts.element.value = firstDefined(val1, val2, '');
+
+        opts.element.html = firstDefined(
+            opts.element.html+'',
+            opts.element.value+'',
+            opts.text+'',
+            opts.html+'',
+            '');
     
         opts.element.html = lookupValue(opts.element.html);
         opts.element.title = 'Double-click to open in code editor.';
@@ -1033,7 +1039,7 @@ var XNAT = getObject(XNAT || {});
             name: opts.name,
             className: opts.className||'',
             title: opts.title||opts.name||opts.id||'',
-            value: opts.value||''
+            value: firstDefined(opts.value+'', '')
         }, opts.element);
         
         if (multi) {
diff --git a/src/main/webapp/scripts/xnat/xhr.js b/src/main/webapp/scripts/xnat/xhr.js
index 9ea911b3f7280ab38ea799dcede2d9f66e1ada87..ff51eae79c990831bacb28221ef6c6e012e154a0 100755
--- a/src/main/webapp/scripts/xnat/xhr.js
+++ b/src/main/webapp/scripts/xnat/xhr.js
@@ -456,6 +456,13 @@ var XNAT = getObject(XNAT||{}),
             var $this = $(this);
             var val = lookupObjectValue(dataObj, this.name||this.title);
 
+            // don't set values of inputs with EXISTING
+            // values that start with "@?"
+            // -- those get parsed on submission
+            if (!val && this.value && /^(@\?)/.test(this.value)) {
+                return;
+            }
+
             if (Array.isArray(val)) {
                 val = val.join(', ');
                 $this.addClass('array-list')
@@ -536,7 +543,16 @@ var XNAT = getObject(XNAT||{}),
         // don't pass 'callback' property into the AJAX request
         delete opts.callback;
 
-        var inputs = $form.find(':input').not('button, [type="submit"]').toArray();
+        var $inputs = $form.find(':input').not('button, [type="submit"]');
+
+        // inputs with a value that starts with
+        // @? will get values from another source
+        $inputs.filter('[value^="@?"]').each(function(){
+            var source = this.value.replace(/^@\?[:=\s]*/, '');
+            this.value = eval(source);
+        });
+
+        var inputs = $inputs.toArray();
 
         if (/POST|PUT/i.test(opts.method)) {
             if ($form.hasClass('json') || /json/i.test(opts.contentType||'')){
@@ -545,6 +561,9 @@ var XNAT = getObject(XNAT||{}),
                 opts.processData = false;
                 opts.contentType = 'application/json';
             }
+            else {
+                opts.data = $form.find(':input').not('.ignore').serialize();
+            }
             opts.success = function(data){
                 callback.apply($form, arguments);
                 // repopulate 'real' data after success
diff --git a/src/main/webapp/setup/index.jsp b/src/main/webapp/setup/index.jsp
index 7810ad1a44772dcb9f518c1afe55790e89ac818c..53265527213fb6630c010cae5f8a5f5d7bba0508 100644
--- a/src/main/webapp/setup/index.jsp
+++ b/src/main/webapp/setup/index.jsp
@@ -23,11 +23,16 @@
                     <pg:restricted msg="${message}">
 
                         <c:import url="/xapi/siteConfig" var="siteConfig"/>
+                        <c:import url="/xapi/notifications" var="notifications"/>
 
                         <script>
+                            var XNAT = getObject(XNAT);
                             XNAT.data = extend({}, XNAT.data, {
-                                siteConfig: ${siteConfig}
+                                siteConfig: ${siteConfig},
+                                notifications: ${notifications}
                             });
+                            XNAT.data['/xapi/siteConfig'] = XNAT.data.siteConfig;
+                            XNAT.data['/xapi/notifications'] = XNAT.data.notifications;
                             // get rid of the 'targetSource' property
                             delete XNAT.data.siteConfig.targetSource;
                         </script>
@@ -57,9 +62,9 @@
 
                         <script>
 
-                            //                        XNAT.app.setupComplete = function(){
-                            //                            XNAT.xhr.form('#site-setup', {});
-                            //                        };
+                            //XNAT.app.setupComplete = function(){
+                            //    XNAT.xhr.form('#site-setup', {});
+                            //};
 
                             XNAT.xhr.get({
                                 url:     XNAT.url.rootUrl('/setup/site-setup.yaml'),
@@ -68,8 +73,12 @@
                                     if (typeof data === 'string') {
                                         data = YAML.parse(data);
                                     }
-                                    var setupPanels = XNAT.spawner.spawn(data);
-                                    setupPanels.render('#site-setup-panels');
+
+                                    console.log(data);
+
+                                    XNAT.spawner
+                                        .spawn(data)
+                                        .render('#site-setup-panels');
                                 }
                             });
 
diff --git a/src/main/webapp/setup/site-setup.yaml b/src/main/webapp/setup/site-setup.yaml
index 239e2a19c8e1490e47f843b547bc532636342713..f5c764ef4459ebea0d4c98021b4d667e38fbd4e6 100644
--- a/src/main/webapp/setup/site-setup.yaml
+++ b/src/main/webapp/setup/site-setup.yaml
@@ -25,7 +25,6 @@ siteSetup:
             method: POST
             url: /xapi/siteConfig
             contentType: json
-            load: XNAT.data.siteConfig
             contents:
 
                 siteId:
@@ -38,7 +37,7 @@ siteSetup:
                         The id used to refer to this site (also used to generate ids). The Site ID must start
                         with a letter and contain only letters, numbers and underscores. It should be a short,
                         one-word name or acronym which describes your site.
-                    validation: required id
+                    validation: required id-strict
 
                 siteUrl:
                     kind: panel.input.text
@@ -63,30 +62,44 @@ siteSetup:
                     description: Email address for site administrator.
                     validation: required email
 
-# this is buggy - disabling for now
-#                notificationRecipients:
-#                    type: panel.form
-#                    classes: hidden
-#                    header: false
-#                    footer: false
-#                    method: POST
-#                    url: /xapi/notifications
-#                    contentType: json
-#                    contents:
-#                        adminEmail:
-#                            tag: input.disabled | disabled, type=hidden, data-value-from=#site-admin-email, name=adminEmail
-#                            value: !? $('#site-admin-email').val()
-#                        # copy adminEmail value to these properties:
-#                        help:
-#                            tag: input| type=hidden, data-value-from=#site-admin-email, name=notifications.helpContactInfo
-#                        errorMessages:
-#                            tag: input| type=hidden, data-value-from=#site-admin-email, name=notifications.emailRecipientErrorMessages
-#                        issueReports:
-#                            tag: input| type=hidden, data-value-from=#site-admin-email, name=notifications.emailRecipientIssueReports
-#                        newUserAlert:
-#                            tag: input| type=hidden, data-value-from=#site-admin-email, name=notifications.emailRecipientNewUserAlert
-#                        update:
-#                            tag: input| type=hidden, data-value-from=#site-admin-email, name=notifications.emailRecipientUpdate
+        # this seems to work now...
+        # copy values from admin email to recipient list
+        # =========================
+        # EMAIL RECIPIENTS
+        notificationRecipients:
+            type: panel.form
+            classes: hidden
+            header: false
+            footer: false
+            method: POST
+            url: /xapi/notifications
+            contentType: json
+            contents:
+                vars:
+                    tag: script
+                    content: >
+                        window.siteAdminEmailInput = $('#site-admin-email')[0]
+                # copy adminEmail value to these properties:
+                helpContactInfo:
+                    kind: input.hidden
+                    name: helpContactInfo
+                    value: @?=window.siteAdminEmailInput.value
+                errorMessages:
+                    kind: input.hidden
+                    name: emailRecipientErrorMessages
+                    value: @?=window.siteAdminEmailInput.value
+                issueReports:
+                    kind: input.hidden
+                    name: emailRecipientIssueReports
+                    value: @?=window.siteAdminEmailInput.value
+                newUserAlert:
+                    kind: input.hidden
+                    name: emailRecipientNewUserAlert
+                    value: @?=window.siteAdminEmailInput.value
+                update:
+                    kind: input.hidden
+                    name: emailRecipientUpdate
+                    value: @?=window.siteAdminEmailInput.value
 
         # =========================
         # DATA STORAGE
@@ -194,12 +207,12 @@ siteSetup:
                     text: Mail Server Settings
 
                 smtpAuth:
-                    kind: panel.input.checkbox
+                    kind: panel.input.switchbox
                     name: mail.smtp.auth
                     label: SMTP Auth?
 
                 smtpStartTls:
-                    kind: panel.input.checkbox
+                    kind: panel.input.switchbox
                     name: mail.smtp.starttls.enable
                     label: Start TLS?
 
@@ -220,15 +233,15 @@ siteSetup:
             contentType: json
             contents:
 
-               requireLogin:
-                   kind: panel.input.checkbox
+                requireLogin:
+                   kind: panel.input.switchbox
                    id: requireLogin
                    name: requireLogin
                    label: Require User Login
                    description: "If checked, then only registered users will be able to access your site. If false, anyone visiting your site will automatically be logged in as 'guest' with access to public data."
 
-               autoEnableUserRegistration:
-                   kind: panel.input.checkbox
+                autoEnableUserRegistration:
+                   kind: panel.input.switchbox
                    id: autoEnableUserRegistration
                    name: userRegistration
                    label: "Auto-enable User Registration?"
@@ -237,8 +250,8 @@ siteSetup:
                        projects immediately. If false, the site administrator will be required to manually enable user accounts. Either way the administrator
                        receives an email notification when a user registers.
 
-               enableCsrfToken:
-                   kind: panel.input.checkbox
+                enableCsrfToken:
+                   kind: panel.input.switchbox
                    id: enableCsrfToken
                    name: enableCsrfToken
                    label: Require CSRF Token?