diff --git a/src/main/webapp/scripts/lib/spawn/spawn.js b/src/main/webapp/scripts/lib/spawn/spawn.js
index e15024775c74933586e077a8ccdfeef36f9dcde0..6c8f176f182f4fb014a7f643fa0a521022cd17af 100644
--- a/src/main/webapp/scripts/lib/spawn/spawn.js
+++ b/src/main/webapp/scripts/lib/spawn/spawn.js
@@ -106,16 +106,16 @@
         });
     }
 
+
     function hasClassName(el, className){
-        var elClasses = el.className.split(/\s+/);
+        var elClasses = (el.className||'').split(/\s+/); // existing classes
         return elClasses.indexOf(className.trim()) > -1;
     }
 
 
     // add new element class without destroying existing class
     function addClassName(el, newClass){
-        el.className = el.className || '';
-        var classes = el.className.split(/\s+/); // existing classes
+        var classes = (el.className||'').split(/\s+/); // existing classes
         var newClasses = [].concat(newClass||[]).join(' ').split(/\s+/);
         // don't add duplicate classes
         newClasses.forEach(function(cls){
@@ -124,8 +124,12 @@
                 classes.push(cls);
             }
         });
+        classes = classes.join(' ').trim();
         // set the className and return the string
-        return el.className = classes.join(' ').trim();
+        if (classes) {
+            el.className = classes;
+        }
+        return classes;
     }
 
 
@@ -185,7 +189,7 @@
         var el, $el, parts, id, classes, tagParts, attrs, isVoid,
             // property names to skip later
             skip = [
-                'innerHTML', 'html', 'attr', 'config', 
+                'innerHTML', 'html', 'attr', 'config',
                 'kind', 'tag', 'tagName',
                 'prepend', 'append', 'appendTo',
                 'classes', 'className', 'addClass',
@@ -199,13 +203,13 @@
             opts = tag[1];
             tag = tag[0];
         }
-        
+
         // handle passing a config object as the only argument
         if (!children && !opts && typeof tag !== 'string') {
             opts = tag;
             tag = null;
         }
-        
+
         tag = tag || opts.tag || opts.tagName || 'span';
 
         if (tag === '!'){
@@ -434,7 +438,7 @@
         }
 
         if (opts.after){
-            // don't append element twice if 
+            // don't append element twice if
             // there's 'before' AND 'after'
             if (!opts.before) {
                 appendChildren(frag, opts.after, spawn);
@@ -442,7 +446,7 @@
             }
             el = frag;
         }
-        
+
         if (errors.length){
             if (hasConsole) console.log(errors);
         }
@@ -460,7 +464,7 @@
         };
 
         if (typeof opts.done == 'function') {
-            opts.done.call(el);    
+            opts.done.call(el);
         }
 
         return el;
diff --git a/src/main/webapp/scripts/utils.js b/src/main/webapp/scripts/utils.js
index f65aa438aa768897051fa560598d8d4c209d67a9..f394ccea730194c039987d910122b71a88386840 100755
--- a/src/main/webapp/scripts/utils.js
+++ b/src/main/webapp/scripts/utils.js
@@ -102,6 +102,43 @@ function replaceEach(str, replacements, regex_params){
 }
 
 
+function hasClassName(el, className){
+    var elClasses = (el.className||'').split(/\s+/); // existing classes
+    return elClasses.indexOf(className.trim()) > -1;
+}
+
+
+// add new element class without destroying existing class
+function addClassName(el, newClass){
+    var classes = (el.className||'').split(/\s+/); // existing classes
+    var newClasses = [].concat(newClass||[]).join(' ').split(/\s+/);
+    // don't add duplicate classes
+    newClasses.forEach(function(cls){
+        if (!cls) return;
+        if (!hasClassName(el, cls)) {
+            classes.push(cls);
+        }
+    });
+    classes = classes.join(' ').trim();
+    // set the className and return the string
+    if (classes) {
+        el.className = classes;
+    }
+    return classes;
+}
+
+
+// add new data object item to be used for [data-] attribute(s)
+function addDataObjects(el, attrs){
+    el.data = el.data || {};
+    forOwn(attrs, function(name, prop){
+        el.data[name] = prop;
+    });
+    // set the data attributes and return the new data object
+    return el.data;
+}
+
+
 // make sure the ajax calls are NOT cached
 //$.ajaxSetup({cache:false});
 
@@ -678,7 +715,7 @@ function compareByText( obj1, obj2 ){
 
 // simplest accordion of all
 $.fn.superSimpleAccordion = function(){
-    
+
     var container = $(this).show();
     var h3s = container.find('h3');
     var divs = h3s.next('div');
@@ -704,5 +741,5 @@ $.fn.superSimpleAccordion = function(){
         '#accordion h3.active { background: #1A75BB; color: #fff; } ' +
         '#accordion .content { padding: 1em; border: 1px solid #d0d0d0; }' +
         '</style>');
-    
+
 };
diff --git a/src/main/webapp/scripts/xnat/admin/dicomScpManager.js b/src/main/webapp/scripts/xnat/admin/dicomScpManager.js
index 10885b22a6c53e560f0ee0eee9e1c517188ab1c5..006b9ccd913b3cfb6d8aef4a8402bbd18140c06c 100644
--- a/src/main/webapp/scripts/xnat/admin/dicomScpManager.js
+++ b/src/main/webapp/scripts/xnat/admin/dicomScpManager.js
@@ -21,7 +21,7 @@ var XNAT = getObject(XNAT || {});
     var dicomScpManager, undefined,
         rootUrl = XNAT.url.rootUrl;
 
-    XNAT.admin = 
+    XNAT.admin =
         getObject(XNAT.admin || {});
 
     XNAT.admin.dicomScpManager = dicomScpManager =
@@ -209,24 +209,29 @@ var XNAT = getObject(XNAT || {});
         // TODO: move event listeners to parent elements - events will bubble up
         // ^-- this will reduce the number of event listeners
         function enabledCheckbox(item){
-            var ckbox = spawn('input.enabled', {
+            var enabled = !!item.enabled;
+            var ckbox = spawn('input.dicom-scp-enabled', {
                 type: 'checkbox',
-                checked: !!item.enabled,
+                checked: enabled,
+                value: enabled,
+                data: { id: item.id, name: item.aeTitle },
                 onchange: function(){
                     // save the status when clicked
-                    var enabled = this.checked;
+                    var checkbox = this;
+                    enabled = checkbox.checked;
                     XNAT.xhr.put({
                         url: scpUrl(item.id + '/enabled/' + enabled),
                         success: function(){
                             var status = (enabled ? ' enabled' : ' disabled');
+                            checkbox.value = enabled;
                             XNAT.ui.banner.top(1000, '<b>' + item.aeTitle + '</b> ' + status, 'success');
-                            console.log(item.id + (enabled ? ' enabled' : ' disabled'))
+                            console.log(item.aeTitle + status)
                         }
                     });
                 }
             });
             return spawn('div.center', [
-                spawn('label.switchbox', [
+                spawn('label.switchbox|title=' + item.aeTitle, [
                     ckbox,
                     ['span.switchbox-outer', [['span.switchbox-inner']]]
                 ])
@@ -275,13 +280,13 @@ var XNAT = getObject(XNAT || {});
                 }
             }, 'Delete');
         }
-        
+
         dicomScpManager.getAll().done(function(data){
             data.forEach(function(item){
                 scpTable.tr({ title: item.aeTitle, data: { id: item.id, port: item.port }})
-                        .td([editLink(item, item.aeTitle)])
-                        .td([['div.mono.center', item.port]])
-                        .td([enabledCheckbox(item)])
+                        .td([editLink(item, item.aeTitle)]).addClass('aeTitle')
+                        .td([['div.mono.center', item.port]]).addClass('port')
+                        .td([enabledCheckbox(item)]).addClass('status')
                         .td([['div.center', [editButton(item), spacer(10), deleteButton(item)]]]);
             });
 
@@ -296,19 +301,19 @@ var XNAT = getObject(XNAT || {});
         });
 
         dicomScpManager.$table = $(scpTable.table);
-        
+
         return scpTable.table;
     };
 
     dicomScpManager.init = function(container){
-        
+
         var $manager = $$(container||'div#dicom-scp-manager');
 
         dicomScpManager.$container = $manager;
 
         $manager.append(dicomScpManager.table());
         // dicomScpManager.table($manager);
-        
+
         var newReceiver = spawn('button.new-dicomscp-receiver.btn.btn-sm.submit', {
             html: 'New DICOM SCP Receiver',
             onclick: function(){
@@ -348,7 +353,7 @@ var XNAT = getObject(XNAT || {});
             newReceiver,
             ['div.clear.clearfix']
         ]));
-        
+
         return {
             element: $manager[0],
             spawned: $manager[0],
diff --git a/src/main/webapp/scripts/xnat/ui/panel.js b/src/main/webapp/scripts/xnat/ui/panel.js
index a81f17bba63d28369aac354ef4f9fa7f31d32216..bcc141ad457941df46514cab57712cb5471f120c 100644
--- a/src/main/webapp/scripts/xnat/ui/panel.js
+++ b/src/main/webapp/scripts/xnat/ui/panel.js
@@ -21,36 +21,6 @@ var XNAT = getObject(XNAT || {});
     XNAT.ui.panel = panel =
         getObject(XNAT.ui.panel || {});
 
-    function hasClassName(el, className){
-        var elClasses = el.className.split(/\s+/);
-        return elClasses.indexOf(className.trim()) > -1;
-    }
-
-    // add new element class without destroying existing class
-    function addClassName(el, newClass){
-        el.className = el.className || '';
-        var classes = el.className.split(/\s+/); // existing classes
-        var newClasses = newClass.split(/\s+/);
-        // don't add duplicate classes
-        newClasses.forEach(function(cls){
-            if (!hasClassName(el, cls)) {
-                classes.push(cls);
-            }
-        });
-        // set the className and return the string
-        return el.className = classes.join(' ').trim();
-    }
-
-    // add new data object item to be used for [data-] attribute(s)
-    function addDataObjects(el, attrs){
-        el.data = el.data || {};
-        forOwn(attrs, function(name, prop){
-            el.data[name] = prop;
-        });
-        // set the data attributes and return the new data object
-        return el.data;
-    }
-
     function setDisabled(elements, disabled){
         $$(elements).each(function(idx){
             var _disabled = !!disabled;
@@ -116,7 +86,7 @@ var XNAT = getObject(XNAT || {});
         opts.title = opts.title || opts.label || opts.header;
 
         var _target = spawn('div.panel-body', opts.element),
-                
+
             hideHeader = (isDefined(opts.header) && (opts.header === false || /^-/.test(opts.title))),
 
             hideFooter = (isDefined(opts.footer) && (opts.footer === false || /^-/.test(opts.footer))),
@@ -358,7 +328,7 @@ var XNAT = getObject(XNAT || {});
             addClassName(_formPanel, 'validate');
             addDataObjects()
         }
-        
+
         // cache a jQuery-wrapped element
         var $formPanel = $(_formPanel);
 
@@ -709,10 +679,10 @@ var XNAT = getObject(XNAT || {});
             }
         }
     };
-    
+
     // setup a dialog box that contains stuff
     panel.dialogForm = panel.formDialog = function(opts){
-        var dialog = new xmodal.Modal    
+        var dialog = new xmodal.Modal
     };
 
     panel.info = function(opts){};
@@ -843,7 +813,7 @@ var XNAT = getObject(XNAT || {});
         }
 
     };
-    
+
     panel.display = function(opts){
         return XNAT.ui.template.panelDisplay(opts).spawned;
     };
@@ -907,12 +877,11 @@ var XNAT = getObject(XNAT || {});
         opts = cloneObject(opts);
         opts.element = extend(true, {
             type: 'hidden',
-            className: opts.className || opts.classes || '',
             name: opts.name,
             id: opts.id || toDashed(opts.name),
             value: firstDefined(opts.value+'', '')
         }, opts.element);
-        addClassName(opts.element, 'hidden');
+        addClassName(opts.element, [opts.className, opts.classes, opts.addClass, 'hidden']);
         if (opts.validation || opts.validate) {
             addDataObjects(opts.element, {
                 validate: opts.validation || opts.validate
@@ -987,7 +956,7 @@ var XNAT = getObject(XNAT || {});
             opts.text+'',
             opts.html+'',
             '');
-    
+
         opts.element.html = lookupValue(opts.element.html);
         opts.element.title = 'Double-click to open in code editor.';
 
@@ -1005,7 +974,7 @@ var XNAT = getObject(XNAT || {});
         }
 
         opts.element.rows = opts.rows || opts.element.rows || 10;
-        
+
         var textarea = spawn('textarea', opts.element);
 
         return XNAT.ui.template.panelDisplay(opts, textarea).spawned;
@@ -1023,7 +992,7 @@ var XNAT = getObject(XNAT || {});
     //////////////////////////////////////////////////
     // SELECT MENU PANEL ELEMENTS
     //////////////////////////////////////////////////
-    
+
     panel.select = {};
 
     panel.select.menu = function panelSelectMenu(opts, multi){
@@ -1037,11 +1006,12 @@ var XNAT = getObject(XNAT || {});
         opts.element = extend({
             id: opts.id,
             name: opts.name,
-            className: opts.className||'',
             title: opts.title||opts.name||opts.id||'',
             value: firstDefined(opts.value+'', '')
         }, opts.element);
-        
+
+        addClassName(opts.element, [opts.className, opts.classes, opts.addClass]);
+
         if (multi) {
             opts.element.multiple = true;
         }
@@ -1069,7 +1039,7 @@ var XNAT = getObject(XNAT || {});
     //////////////////////////////////////////////////
     // DATA PANELS - RETRIEVE/DISPLAY DATA
     //////////////////////////////////////////////////
-    
+
     panel.dataTable = function(opts){
 
         opts = cloneObject(opts);
@@ -1085,7 +1055,7 @@ var XNAT = getObject(XNAT || {});
         panelTable.target.appendChild(dataTable.table);
 
         return panelTable;
-        
+
     };
 
     panel.dataList = function(opts){
@@ -1108,7 +1078,7 @@ var XNAT = getObject(XNAT || {});
     return XNAT.ui.panel = panel;
 
 
-    
+
     // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
     // STOP EVERYTHING!!!!!
     // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
diff --git a/src/main/webapp/scripts/xnat/ui/table.js b/src/main/webapp/scripts/xnat/ui/table.js
index 711a971af9f87f40971849b65fe3700280b76ba8..97aa14c640f6d4455fc142dfa0b4cbdee6033e62 100755
--- a/src/main/webapp/scripts/xnat/ui/table.js
+++ b/src/main/webapp/scripts/xnat/ui/table.js
@@ -28,20 +28,6 @@ var XNAT = getObject(XNAT);
         element = window.spawn,
         undefined;
 
-    // add new element class without destroying existing class
-    function addClassName(el, newClass){
-        el.className = [].concat(el.className||[], newClass).join(' ').trim();
-        return el.className;
-    }
-
-    // add new data object item to be used for [data-] attribute(s)
-    function addDataObjects(obj, attrs){
-        obj.data = obj.data || {};
-        forOwn(attrs, function(name, prop){
-            obj.data[name] = prop;
-        });
-        return obj.data;
-    }
 
     /**
      * Constructor function for XNAT.table()
@@ -70,11 +56,15 @@ var XNAT = getObject(XNAT);
             };
 
             this.setLast = function(el){
-                this.last.parent =
+                this.last.parent = this.last.child =
                     this.last[el.tagName.toLowerCase()] =
                         el;
             };
 
+            this.getLast = function(){
+                return this.last.child;
+            };
+
             this._rows = [];
             this._cols = 0; // how many columns?
 
@@ -84,10 +74,27 @@ var XNAT = getObject(XNAT);
 
     }
 
-
     // alias prototype for less typing
     Table.p = Table.prototype;
 
+    // return last item to use with jQuery methods
+    // XNAT.table().tr().$('attr', ['title', 'foo']).td('Bar').$({ addClass: 'bar' }).getHTML();
+    // <table><tr title="foo"><td class="bar">Bar</td></tr></table>
+    // yes, the HTML is shorter and simpler, but also harder to generate programmatically
+    Table.p.$ = function(method, args){
+        var $el = $(this.getLast());
+        var methods = isPlainObject(method) ? method : null;
+        args = args || [];
+        if (!methods) {
+            methods = {};
+            // force an object if not already
+            methods[method] = args;
+        }
+        forOwn(methods, function(name, arg){
+            $el[name].apply($el, [].concat(arg));
+        });
+        return this;
+    };
 
     // jQuery methods we'd like to use:
     var $methods = [
@@ -98,9 +105,8 @@ var XNAT = getObject(XNAT);
     ];
 
     $methods.forEach(function(method){
-        Table.p[method] = function(){
-            var $el = this.last$();
-            $el[method].apply($el, arguments);
+        Table.p[method] = function(args){
+            this.$(method, args);
             return this;
         }
     });
@@ -115,6 +121,7 @@ var XNAT = getObject(XNAT);
     Table.p.td = function(opts, content){
         var td = element('td', opts, content);
         this.last.td = td;
+        this.last.child = td;
         this.last.tr.appendChild(td);
         return this;
     };
@@ -122,6 +129,7 @@ var XNAT = getObject(XNAT);
     Table.p.th = function(opts, content){
         var th = element('th', opts, content);
         this.last.th = th;
+        this.last.child = th;
         this.last.tr.appendChild(th);
         this._cols++; // do this here?
         return this;
@@ -143,7 +151,8 @@ var XNAT = getObject(XNAT);
             this.last.parent.appendChild(tr);
         }
         this.last.tr = tr;
-        //this.setLast(tr);
+        this.last.child = tr;
+        // this.setLast(tr);
         // nullify last <th> and <td> elements since this is a new row
         this.last.th = this.last.td = null;
         return this;
@@ -197,13 +206,23 @@ var XNAT = getObject(XNAT);
     Table.p.thead = function(opts, data){
         var head = element('thead', opts);
         this.table.appendChild(head);
+        // this.last.child = head;
         this.setLast(head);
         return this;
     };
 
+    Table.p.tfoot = function(opts, data){
+        var foot = element('tfoot', opts);
+        this.table.appendChild(foot);
+        // this.last.child = foot;
+        this.setLast(foot);
+        return this;
+    };
+
     Table.p.tbody = function(opts, data){
         var body = element('tbody', opts);
         this.table.appendChild(body);
+        // this.last.child = body;
         this.setLast(body);
         return this;
     };
@@ -318,18 +337,18 @@ var XNAT = getObject(XNAT);
 
     };
 
-    Table.p.render = function(element, empty){
-        var $element;
-        if (element) {
-            $element = $$(element);
+    Table.p.render = function(container, empty){
+        var $container;
+        if (container) {
+            $container = $$(container);
             if (empty){
-                $element.empty();
+                $container.empty();
             }
-            $element.append(this.table);
+            $container.append(this.table);
         }
         return this.table;
     };
-    
+
     // 'opts' are options for the <table> element
     // 'config' is for other configurable stuff
     table = function(opts, config){
diff --git a/src/main/webapp/scripts/xnat/ui/templates.js b/src/main/webapp/scripts/xnat/ui/templates.js
index 3d61b218d68bda21380655cdba18120bc7c8d06d..904219c1882be5f1e9a5f9ef8b945d94c49273b9 100644
--- a/src/main/webapp/scripts/xnat/ui/templates.js
+++ b/src/main/webapp/scripts/xnat/ui/templates.js
@@ -21,24 +21,9 @@ var XNAT = getObject(XNAT);
 
     XNAT.ui = getObject(XNAT.ui || {});
 
-    XNAT.ui.template = template = 
+    XNAT.ui.template = template =
         XNAT.ui.template || {};
 
-    // add new element class without destroying existing class
-    function addClassName(el, newClass){
-        el.className = [].concat(el.className||[], newClass).join(' ').trim();
-        return el.className;
-    }
-
-    // add new data object item to be used for [data-] attribute(s)
-    function addDataObjects(obj, attrs){
-        obj.data = obj.data || {};
-        forOwn(attrs, function(name, prop){
-            obj.data[name] = prop;
-        });
-        return obj.data;
-    }
-
     function lookupValue(el, lookup){
         if (!lookup) {
             lookup = el;
@@ -92,7 +77,7 @@ var XNAT = getObject(XNAT);
     };
     // ========================================
 
-    
+
     // ========================================
     // generic panel element
     template.panelElement = function(opts, content){
@@ -126,27 +111,30 @@ var XNAT = getObject(XNAT);
     // ========================================
     // display only element for form panels
     template.panelDisplay = function(opts, element){
-        
+
         opts = cloneObject(opts);
         opts.id = opts.id||toDashed(opts.name||'');
         opts.label = opts.label||'';
-        
+
         // pass in an element or create a new 'div' element
-        element = 
+        element =
             element || spawn('div', extend(true, {
                 id: opts.id,
-                className: opts.className||'',
                 title: opts.title||opts.name||opts.id,
                 html: opts.value||opts.html||opts.text||opts.body||''
             }, opts.element));
-        
+
+        if (opts.className || opts.classes || opts.addClass) {
+            addClassName(element, [opts.className, opts.classes, opts.addClass]);
+        }
+
         return template.panelElement(opts, [
 
             // only add a label if specified
             (opts.label ? ['label.element-label|for='+element.id||opts.id, opts.label] : ''),
 
             ['div.element-wrapper', [].concat(
-                
+
                 (opts.beforeElement ? opts.beforeElement : []),
 
                 element ,
@@ -158,7 +146,7 @@ var XNAT = getObject(XNAT);
             )]
         ]);
     };
-    // ========================================    
+    // ========================================
 
 
     // ========================================
@@ -172,14 +160,17 @@ var XNAT = getObject(XNAT);
             type: opts.type||'text',
             id: opts.id,
             name: opts.name,
-            className: opts.className||'',
             size: opts.size || 25,
             title: opts.title||opts.label||opts.name||opts.id,
             value: opts.value||''
         }, opts.element);
 
+        if (opts.className || opts.classes || opts.addClass) {
+            addClassName(opts.element, [opts.className, opts.classes, opts.addClass]);
+        }
+
         opts.data = opts.data || {};
-        
+
         if (opts.element.type !== 'password'){
             opts.data.value = opts.data.value || opts.value;
         }
@@ -190,7 +181,7 @@ var XNAT = getObject(XNAT);
         }
 
         addDataObjects(opts.element, opts.data);
-        
+
         if (opts.placeholder) {
             opts.element.placeholder = opts.placeholder;
         }
@@ -204,7 +195,7 @@ var XNAT = getObject(XNAT);
 
         // set the value of individual form elements
         var hasValue = isDefined(opts.value);
-        
+
         // look up a namespaced object value if the value starts with '??'
         var doLookup = '??';
         if (hasValue && opts.value.toString().indexOf(doLookup) === 0) {
@@ -303,13 +294,13 @@ var XNAT = getObject(XNAT);
                 hiddenInput.value = this.checked ? (this.value || this.checked || 'true') : 'false';
                 $hiddenInput.toggleClass('dirty');
             };
-            
+
             // copy name to title
             element.title = element.name;
-            
+
             // remove name of checkbox/radio to avoid conflicts
             element.name = '';
-            
+
             // add a class for easy selection
             addClassName(element, 'controller');
             addClassName(opts, 'controller');
@@ -337,8 +328,8 @@ var XNAT = getObject(XNAT);
             ['div.element-wrapper', elements]
         ]);
     };
-    
-    
+
+
     template.codeEditor = function(opts, contents){
         // options for the 'div.editor-content' element
         opts = extend(true, opts, {