diff --git a/src/main/webapp/scripts/utils.js b/src/main/webapp/scripts/utils.js
index eddd98e0d2d49dec2e1a5ace96b76fedd0b035c4..afd533031af01737400c998076e75ad55afdb9fb 100755
--- a/src/main/webapp/scripts/utils.js
+++ b/src/main/webapp/scripts/utils.js
@@ -294,8 +294,14 @@ jQuery.fn.tableSort = function(){
     if ($table.hasClass('sort-ready')) return this;
     $table.find('tr').each(function(i){
         // add a hidden 'index' cell to each row to reset sorting
-        $(this).prepend('<td class="index hidden" style="display:none;">' + i + '</td>');
+        var $tr = $(this);
+        // but only if an index column is not already present
+        if ($tr.find('> th, > td').first().hasClass('index')) return;
+        $tr.prepend('<td class="index hidden" style="display:none;">' + i + '</td>');
     });
+    $table.find('th').not('.sort').filter(function(){
+        return this.innerHTML.trim() > '';
+    }).addClass('sort');
     $table.find('th.sort')
           .append('<i>&nbsp;</i>')
           // wrapInner('<a href="#" class="nolink" title="click to sort on this column"/>').
@@ -343,9 +349,9 @@ $(function(){
     // this enables sorting for ALL columns
     $('table.sortable, table.sort').not('.sort-ready').each(function(){
         var $table = $(this);
-        $table.find('th').filter(function(){
-            return this.innerHTML.trim() > '';
-        }).addClass('sort');
+        // $table.find('th').filter(function(){
+        //     return this.innerHTML.trim() > '';
+        // }).addClass('sort');
         $table.tableSort();
     });
     // even if it's not available on DOM ready
diff --git a/src/main/webapp/scripts/xnat/ui/panel.js b/src/main/webapp/scripts/xnat/ui/panel.js
index d08f8cc8454b745093a4a9a305e07b1f5e650a40..922bc1ab12cbfa1087dad5e03612cb3a5e475e9a 100644
--- a/src/main/webapp/scripts/xnat/ui/panel.js
+++ b/src/main/webapp/scripts/xnat/ui/panel.js
@@ -120,11 +120,13 @@ var XNAT = getObject(XNAT || {});
         opts.title = opts.title || opts.label || opts.header;
         opts.name = opts.name || opts.element.name || opts.id || opts.element.id || randomID('form-', false);
 
+        // data-* attributes to add to panel
         addDataObjects(opts, {
-            panel: toDashed(opts.name)
+            panel: toDashed(opts.name),
+            method: (opts.method || 'POST').toLowerCase()
         });
 
-        var _target = spawn('div.panel-body', opts.element),
+        var _target = spawn('div.panel-body'),
 
             hideHeader = (isDefined(opts.header) && (opts.header === false || /^-/.test(opts.title))),
 
@@ -141,10 +143,11 @@ var XNAT = getObject(XNAT || {});
                 ['div.clear']
             ],
 
-            _formPanel = spawn('form.validate.xnat-form-panel.panel.panel-default', {
+            // TODO: use opts.element for the panel itself
+            _formPanel = spawn('form.xnat-form-panel.panel.panel-default', {
                 id: toDashed(opts.id || opts.element.id || opts.name) + '-panel',
                 name: opts.name,
-                method: opts.method || 'POST',
+                //method: opts.method || 'POST',
                 action: opts.action ? XNAT.url.rootUrl(opts.action) : '#!',
                 addClass: opts.classes || '',
                 data: opts.data
@@ -160,6 +163,12 @@ var XNAT = getObject(XNAT || {});
                 (hideFooter ? ['div.hidden'] : ['div.panel-footer', opts.footer || _footer])
 
             ]);
+
+        // if there's a 'validation' (or 'validate') property, add 'validate' class
+        if (opts.validation || opts.validate) {
+            addClassName(_formPanel, 'validate');
+            addDataObjects()
+        }
         
         // cache a jQuery-wrapped element
         var $formPanel = $(_formPanel);
@@ -827,6 +836,89 @@ var XNAT = getObject(XNAT || {});
     };
     panel.select.multi.init = panel.select.multi;
 
+
+
+    //////////////////////////////////////////////////
+    // DATA PANELS - RETRIEVE/DISPLAY DATA
+    //////////////////////////////////////////////////
+
+    panel.data = {};
+
+    panel.data.table = function(opts){
+        // initialize the table
+        opts = cloneObject(opts);
+        opts.element = opts.element || {};
+        addClassName(opts.element, 'data-table xnat-table');
+        if (opts.sortable) {
+            if (opts.sortable === true) {
+                addClassName(opts.element, 'sortable');
+            }
+            else {
+                opts.sortable = opts.sortable.split(',').map(function(item){return item.trim()});
+            }
+        }
+        opts.element.style = {
+            width: opts.width || '100%'
+        };
+        var dataTable = XNAT.table(opts.element);
+        // request data for table rows
+        XNAT.xhr.get({
+            url: XNAT.url.rootUrl(opts.load||opts.url),
+            dataType: opts.dataType || 'json',
+            success: function(data){
+                var props = [];
+                if (opts.items) {
+                    dataTable.tr();
+                    forOwn(opts.items, function(name, val){
+                        props.push(name);
+                        dataTable.th(val);
+                        if (opts.sortable === true || opts.sortable.indexOf(name) !== -1) {
+                            addClassName(dataTable.last.th, 'sort');
+                        }
+                    });
+                }
+                else {
+                    forOwn(data[0], function(name, val){
+                        props.push(name);
+                    });
+                }
+                data.forEach(function(item){
+                    dataTable.tr();
+                    props.forEach(function(name){
+                        dataTable.td({ className: name }, item[name]);
+                    });
+                });
+                if (opts.container) {
+                    $$(opts.container).append(dataTable.table);
+                }
+            }
+        });
+        return {
+            element: dataTable.table,
+            spawned: dataTable.table,
+            get: function(){
+                return dataTable.table
+            }
+        };
+    };
+
+    panel.data.list = function(opts){
+        // initialize the table
+        opts = cloneObject(opts);
+        opts.element = opts.element || {};
+        addClassName(opts.element, 'data-list');
+        var dataList = spawn('ul', opts.element);
+        XNAT.xhr.get({
+            url: XNAT.url.rootUrl(opts.load||opts.url),
+            dataType: opts.dataType || 'json',
+            success: function(data){
+
+            }
+        });
+
+
+    };
+
     return XNAT.ui.panel = panel;
 
 
@@ -844,29 +936,6 @@ var XNAT = getObject(XNAT || {});
     // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
 
-    function footerButton(text, type, disabled, classes){
-        var button = {
-            type: type || 'button',
-            html: text || 'Submit'
-        };
-        button.classes = [classes || '', 'btn btn-sm'];
-        if (type === 'link') {
-            button.classes.push('btn-link')
-        }
-        else if (/submit|primary/.test(type)) {
-            button.classes.push('btn-primary')
-        }
-        else {
-            button.classes.push('btn-default')
-        }
-        if (disabled) {
-            button.classes.push('disabled');
-            button.disabled = 'disabled'
-        }
-        return spawn('button', button);
-    }
-
-
     /**
      * Panel widget with default 'Submit' and 'Revert' buttons
      * @param opts
@@ -1057,144 +1126,6 @@ var XNAT = getObject(XNAT || {});
 
     }
 
-    // return a single panel element
-    function panelElement(item){
-
-        var elements = [],
-            element, tag,
-            children = '',
-            before   = [],
-            after    = [],
-            kind     = item.kind || '',
-            obj      = {};
-
-        // input (or other) element
-        tag = item.tag || item.kind || 'div';
-
-        if (kind === 'element-group' && item.elements.length) {
-            element = groupElements(item.elements);
-            //element = spawn(tag, [radioToggle(item)]);
-        }
-        else {
-            if (item.name) {
-                obj.name = item.name
-            }
-
-            if (item.type) {
-                obj.type = item.type;
-            }
-        }
-
-        if (item.id) {
-            obj.id = item.id;
-        }
-
-        if (tag === 'input' && !item.type) {
-            obj.type = 'text';
-        }
-
-        // 'checkbox' kind
-        if (kind === 'checkbox') {
-            tag = 'input';
-            obj.type = 'checkbox';
-        }
-
-        // set a default 'size' value for text inputs
-        if (tag === 'input' && /text|email|url/.test(item.type || obj.type || '')) {
-            obj.size = '25';
-        }
-
-        if (item.label) {
-            obj.title = item.label;
-        }
-
-        obj.data = item.data ? extend(true, {}, item.data) : {};
-
-        if (item.value) {
-            obj.value = item.value;
-            obj.data.value = item.value;
-        }
-
-        if (item.checked) {
-            obj.checked = true;
-            obj.data.state = 'checked';
-        }
-
-        if (item.info) {
-            obj.data.info = item.info;
-        }
-
-        if (item.attr || item.attributes) {
-            obj.attr = item.attr || item.attributes || {};
-        }
-
-        if (/form-table|inputTable|input-table/i.test(kind)) {
-            element = XNAT.ui.inputTable(item.tableData).get();
-        }
-        else {
-            obj.innerHTML = [].concat(item.innerHTML || item.html || []).join('\n');
-        }
-
-        if (item.before) {
-            console.log('before');
-            before = item.before;
-            //elements.push(spawn('span.before', item.before))
-        }
-
-        if (item.after) {
-            console.log('after');
-            after = item.after;
-            //elements.push(spawn('span.after', item.after))
-        }
-
-        if (kind !== 'hidden') {
-            // enable the 'Save' and 'Discard Changes' buttons on change
-            obj.onchange = function(){
-                var $panel = $(this).closest('.panel');
-                setDisabled([$panel.find('.save'), $panel.find('.revert')], false);
-            };
-        }
-
-        if (kind === 'select' && item.options) {
-            children = item.options.map(function(option){
-                var obj = {};
-                obj.value = option.value;
-                obj.html = option.label;
-                if (isDefined(item.value)) {
-                    if (item.value === obj.value) {
-                        obj.selected = true;
-                    }
-                }
-                return ['option', obj]
-            });
-        }
-
-        element = element || spawn(tag, obj, children);
-
-        if (!elements.length) {
-            elements = [].concat(before, element, after);
-        }
-        // add a description if present
-        if (item.description) {
-            elements.push(spawn('div.description', item.description))
-        }
-
-        // element setup
-        return elements;
-
-    }
-
-    function elementLabel(label, id){
-        var obj = {
-            innerHTML: label || ''
-        };
-        if (id) {
-            obj.attr = {
-                'for': id
-            }
-        }
-        return spawn('label.element-label', obj);
-    }
 
     // create elements that are part of an 'element-group'
     // returns Spawn arguments array
@@ -1209,29 +1140,7 @@ var XNAT = getObject(XNAT || {});
             return [tag, [].concat(label, panelElement(item))];
         });
     }
-
-    // create elements from the 'elements' array
-    // returns Spawn arguments array
-    function setupElements(items){
-        return items.map(function(item){
-            var label = '';
-            var tag = 'div.panel-element';
-            switch (item.kind) {
-                case 'hidden':
-                    tag = 'div.hidden';
-                    break;
-                case 'element-group':
-                    tag += '.element-group';
-                    break;
-            }
-            if (item.label) {
-                label = elementLabel(item.label, item.id)
-            }
-            tag += '|data-name=' + item.name;
-            return [tag, [label, ['div.element-wrapper', panelElement(item)]]];
-        });
-    }
-
+    
     function discardChanges(form){
 
         var $form = $$(form);
diff --git a/src/main/webapp/scripts/xnat/ui/table.js b/src/main/webapp/scripts/xnat/ui/table.js
index 28a841f02a36c8e490d5eaf170c9472154ec06c8..5a645866d1561676b29942bc306b2e242645dff0 100755
--- a/src/main/webapp/scripts/xnat/ui/table.js
+++ b/src/main/webapp/scripts/xnat/ui/table.js
@@ -74,7 +74,7 @@ var XNAT = getObject(XNAT);
         };
 
         this._rows = [];
-        this.cols = this.columns = [];
+        this._cols = 0; // how many columns?
 
     }
 
@@ -106,59 +106,65 @@ var XNAT = getObject(XNAT);
     // object to set the properties
     // and use append or innerHTML
     // to add the cell content
-    Table.p.td = function(content){
-        var td = element('td', content);
+    Table.p.td = function(opts, content){
+        var td = element('td', opts, content);
+        this.last.td = td;
         this.last.tr.appendChild(td);
         return this;
     };
 
-    Table.p.th = function(content){
-        var th = element('th', content);
+    Table.p.th = function(opts, content){
+        var th = element('th', opts, content);
+        this.last.th = th;
         this.last.tr.appendChild(th);
         return this;
     };
 
     Table.p.tr = function(opts, data){
+        var _this = this;
         var tr = element('tr', opts);
         //data = data || this.data || null;
         if (data) {
+            this.last.tr = tr;
             [].concat(data).forEach(function(item){
-                tr.appendChild(element('td', item))
+                _this.td(item);                
             });
         }
         // only add <tr> elements to <table>, <thead>, <tbody>, and <tfoot>
-        if (/(table|thead|tbody|tfoot)/.test(this.last.parent.tagName.toLowerCase())) {
+        if (/(table|thead|tbody|tfoot)/i.test(this.last.parent.tagName)) {
             this.last.parent.appendChild(tr);
         }
         this.last.tr = 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;
     };
 
     // create a row with <tr> and <td> elements
     // in the <tbody>
     Table.p.row = function(data, opts){
-        var tr = element('tr', opts);
+        // var tr = element('tr', opts);
         data = data || [];
-        [].concat(data).forEach(function(item){
-            tr.appendChild(element('td', item));
-        });
-        (this.last.tbody || this.table).appendChild(tr);
+        this.tr(opts, data);
+        // (this.last.tbody || this.table).appendChild(tr);
+        // nullify last <th> and <td> elements since this is a new row
+        // this.last.th = this.last.td = null;
         return this;
     };
 
     // create *multiple* <td> elements
     Table.p.tds = function(items, opts){
-        var last_tr = this.last.tr;
+        var _this = this;
+        // var last_tr = this.last.tr;
         [].concat(items).forEach(function(item){
-            var td;
-            if (isPlainObject(item)) {
-                td = element('td', '', extend(true, item, opts));
+            if (stringable(item)) {
+                _this.td(opts, item);
             }
+            // if 'item' isn't stringable, it will be an object
             else {
-                td = element('td', item, opts);
+                _this.td(extend(true, {}, opts, item));
             }
-            last_tr.appendChild(td);
         });
         // don't reset 'last' so we
         // keep using the parent <tr>
@@ -167,9 +173,11 @@ var XNAT = getObject(XNAT);
 
     Table.p.rows = function(data, opts){
         var _this = this,
-            rows  = [];
+            rows  = [],
+            cols = data[0].length; // first array length determines how many columns
         data = data || [];
         data.forEach(function(row){
+            row = row.slice(0, cols);
             rows.push(_this.tr(opts, row))
         });
         this._rows = rows;
@@ -285,6 +293,7 @@ var XNAT = getObject(XNAT);
         // set the number of columns based on
         // the header or first row of data
         cols = (header) ? header.length : (obj.data[0] || []).length;
+        this._cols = cols;
 
         // add the header
         if (header) {
@@ -344,7 +353,7 @@ var XNAT = getObject(XNAT);
             tableData = opts;
             opts = data;
         }
-        addClassName(opts, 'data-table');
+        addClassName(opts, 'xnat-table data-table');
         var newTable = new Table(opts);
         return newTable.init(tableData);
     };