From 2e288f858569efd08688f5ff430d598954610684 Mon Sep 17 00:00:00 2001
From: "Mark M. Florida" <mflorida@gmail.com>
Date: Tue, 10 May 2016 08:02:02 -0500
Subject: [PATCH] Modified CSS and HTML structure to make the the user bar and
 main nav use the full window width; removed the gray background from the
 body; other miscellaneous updates.

---
 src/main/webapp/scripts/globals.js            |  23 +++
 .../scripts/lib/spawn/spawn.examples.js       | 128 +++++++++++++++
 src/main/webapp/scripts/lib/spawn/spawn.js    | 153 +++++++++++++++---
 src/main/webapp/scripts/utils.js              |  30 ++++
 .../webapp/scripts/xnat/app/customPage.js     |  19 ++-
 src/main/webapp/scripts/xnat/element.js       |  16 +-
 src/main/webapp/scripts/xnat/url.js           |   2 +-
 src/main/webapp/scripts/xnat/xhr.js           |  43 ++++-
 src/main/webapp/style/app.css                 | 127 ++++++++++-----
 .../xnat-templates/navigations/DefaultTop.vm  |  12 +-
 .../xnat-templates/navigations/bodyOpen.vm    |   1 -
 11 files changed, 480 insertions(+), 74 deletions(-)
 create mode 100644 src/main/webapp/scripts/lib/spawn/spawn.examples.js
 mode change 100644 => 100755 src/main/webapp/scripts/utils.js
 mode change 100644 => 100755 src/main/webapp/scripts/xnat/xhr.js

diff --git a/src/main/webapp/scripts/globals.js b/src/main/webapp/scripts/globals.js
index 57e89054..42efbd16 100644
--- a/src/main/webapp/scripts/globals.js
+++ b/src/main/webapp/scripts/globals.js
@@ -1131,3 +1131,26 @@ function loadCSS( url, parent ){
     parent = parent ? document.querySelector(parent) : document.querySelector('head');
     parent.appendChild(scriptElement(url, min, name));
 }
+
+function prettifyJSON(data, indent) {
+    var json;
+    if (typeof data != 'string') {
+        json = JSON.stringify(data, null, indent||2);
+    }
+    json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+    return '<pre class="json">' + json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
+        var cls = 'number';
+        if (/^"/.test(match)) {
+            if (/:$/.test(match)) {
+                cls = 'key';
+            } else {
+                cls = 'string';
+            }
+        } else if (/true|false/.test(match)) {
+            cls = 'boolean';
+        } else if (/null/.test(match)) {
+            cls = 'null';
+        }
+        return '<span class="' + cls + '">' + match + '</span>';
+    }) + '</pre>';
+}
diff --git a/src/main/webapp/scripts/lib/spawn/spawn.examples.js b/src/main/webapp/scripts/lib/spawn/spawn.examples.js
new file mode 100644
index 00000000..60f5555f
--- /dev/null
+++ b/src/main/webapp/scripts/lib/spawn/spawn.examples.js
@@ -0,0 +1,128 @@
+/*!
+ * List of options for spawn() function
+ */
+
+(function(spawn){
+
+    if (!spawn) { return }
+
+    var examples = {
+
+        _about: {
+            info:    'The spawn() function can be used to generate HTML elements on-the-fly, deeply nested if necessary',
+            why:     'Why is using spawn() more convenient or efficient that just writing HTML?',
+            because: 'The content that is spawned could contain JavaScript variables or generate content dynamically.'
+        },
+
+        minimal: {
+            about:  'Creates an empty div with class "foo".',
+            input:  ['div.foo'],
+            output: '<div class="foo"></div>'
+        },
+
+        withText: {
+            about:  'Creates a p element with id "fox" and text content.',
+            input:  ['p#fox', 'The quick brown fox... did some stuff.'],
+            output: '<p id="fox">The quick brown fox... did some stuff.</p>'
+        },
+
+        nested: {
+            about:  'Creates nested spawned objects using an array of "Spawn Arrays" as the second argument ' +
+                    '(or third if passing other options or attributes).',
+            _more:  '"Spawn Arrays" are arrays containing arguments to pass to a recursively-called spawn() function.',
+            input:  ['div.container', {
+                style: 'padding: 10px'
+            }, [
+                ['p.foo', 'Text that is foo.'],
+                ['p.bar', 'Other text that is bar.']
+            ]],
+            output:
+                '<div class="container">' +
+                    '<p class="foo">Text that is foo.</p>' +
+                    '<p class="bar">Other text that is bar.</p>' +
+                '</div>',
+            why: 'Why is this more convenient or efficient that just writing HTML?',
+            because: 'The content that is spawned may contain JavaScript variables or generate content dynamically.'
+        },
+
+        nestedDeep: {
+            about:  'Nest Spawn Arrays within Spawn Arrays to create a deeply nested HTML hierarchy.',
+            tip1:   'IDs and classes can be added using CSS selector syntax. Other attributes need to be added separately',
+            tip2:   'Use a | (pipe) character to add other attributes to the element. Separate multiple attributes with a , (comma) or ; (semicolon)',
+            input:  ['div#outer.stuff|title="The Outer Div."', [
+                ['p.inner1', [
+                    'Text content can go here and will be inserted before the next item in the array.',
+                    ['p.inner2a', '<b>HTML content is also fine here in the .inner2 paragragraph.</b>'],
+                    ['small.inner2b', 'Maybe you want some small text after the p.inner2 element.'],
+                    ['script', 'console.log("Why would you want to add a script tag here? You can if you want. It will be executed immediately.")']
+                ]]
+            ]],
+            output:
+                '<div id="outer" class="stuff" title="The Outer Div">' +
+                    '<p class="inner1">' +
+                        'Text content can go here and will be inserted before the next item in the array.' +
+                        '<p class="inner2a"><b>HTML content is also fine here in the .inner2 paragragraph.</b></p>' +
+                        '<small class="inner2b">Maybe you want some small text after the p.inner2 element.</small>' +
+                        // '<script>console.log("Why would you want to add a script tag here? You can if you want. It will be executed immediately.")</script>' +
+                    '</p>' +
+                '</div>'
+        },
+
+        configObj1: {
+            about: 'You can pass an object as the second argument to add element properties, attributes, event handlers, and jQuery methods.',
+            input: ['button', {
+                type: 'button',
+                name: 'one-button',
+                html: 'The Button', // 'html' is a shortcut for the 'innerHTML' property
+                onclick: function(){ alert('You clicked ' + this.innerHTML + '.')}, // in this case, the this keyword references the element being spawned
+                $: { // the $ property attaches jQuery methods to the spawned element
+                    addClass: 'a-button',
+                    click: function(){ alert('A second click event handler was added with jQuery.') }
+                }
+            }]
+        },
+
+        configObj2: {
+            about: 'This demonstrates calling a jQuery method more than once by using an array instead of an object for the $ property.',
+            input: ['a.clicker.link', {
+                html: '<br>Going nowhere.</br>',
+                href: '/take/me/somewhere',
+                $: [
+                    ['click', function(e){ e.preventDefault(); alert("Don't go there."); }],
+                    ['click', function(e){ e.preventDefault(); alert("I didn't."); }]
+                ]
+            }]
+        }
+
+    };
+
+    function isPlainObject(obj){
+        return Object.prototype.toString.call(obj) === '[object Object]';
+    }
+
+    // execute spawn() on each example
+    // and add it to the 'spawned' property
+    function createSpawn(){
+        var spawned = [];
+        forOwn(examples, function(item, obj){
+            if (/^[_0-9]/.test(item)) return; // skip array items and objects with a leading underscore
+            if (!isPlainObject(obj)) return;
+            obj.spawned = spawn(obj.input);
+            obj.htmlOut = obj.spawned.outerHTML;
+            spawned.push(obj.spawned);
+        });
+        return spawned;
+    }
+
+    // save an array of spawned elements
+    examples.spawned = createSpawn();
+
+    // spawn.examples.createSpawn();
+    examples.createSpawn = function(){
+        return createSpawn();
+    };
+
+    spawn.examples = examples;
+
+})(window.spawn);
+
diff --git a/src/main/webapp/scripts/lib/spawn/spawn.js b/src/main/webapp/scripts/lib/spawn/spawn.js
index 31b52898..228aa1ef 100644
--- a/src/main/webapp/scripts/lib/spawn/spawn.js
+++ b/src/main/webapp/scripts/lib/spawn/spawn.js
@@ -53,7 +53,8 @@
         'url',
         'checkbox',
         'radio',
-        'hidden'
+        'hidden',
+        'file'
     ];
 
     // use these as a shortcut to create <input> elements:
@@ -97,7 +98,16 @@
             }
             // ...or 'appendable' nodes
             else {
-                el.appendChild(child);
+                try {
+                    el.appendChild(child);
+                }
+                catch (e) {
+                    // try appending with jQuery
+                    // if native .appendChild() fails
+                    if (window.jQuery) {
+                        jQuery(el).append(child);
+                    }
+                }
             }
         });
     }
@@ -118,13 +128,21 @@
     function spawn(tag, opts, children){
 
         var el, $el, parts, id, classes, tagParts, attrs, isVoid,
-            skip = ['innerHTML', 'html', 'append',  // properties to skip later
-                'classes', 'attr', 'data', 'fn'],
+        // property names to skip later
+            skip = ['innerHTML', 'html', 'append', 'appendTo',
+                'classes', 'className', 'attr', 'style', 'data', 'fn'],
             errors = []; // collect errors
 
+        // deal with passing an array as the only argument
+        if (Array.isArray(tag)){
+            children = tag[2];
+            opts = tag[1];
+            tag = tag[0];
+        }
+
         if (tag === '!'){
             el = doc.createDocumentFragment();
-            appendChildren(el, opts, spawn);
+            appendChildren(el, opts||'', spawn);
             return el;
         }
 
@@ -209,8 +227,8 @@
         }
 
         // allow use of 'classes' property for classNames
-        if (opts.className || opts.classes){
-            el.className = [].concat(opts.className||[], opts.classes||[]).join(' ').trim();
+        if (opts.className || opts.classes || opts.addClass){
+            el.className = [].concat(opts.className||[], opts.classes||[], opts.addClass||[]).join(' ').trim();
         }
 
         // add attributes and properties to element
@@ -235,6 +253,20 @@
             });
         }
 
+        // handle style object
+        if (opts.style){
+            // it's either a string
+            if (typeof opts.style == 'string') {
+                el.style = opts.style;
+            }
+            // or an object
+            else {
+                forOwn(opts.style, function(prop, val){
+                    el.style[prop] = val;
+                });
+            }
+        }
+
         // only add innerHTML and children for non-void elements
         if (!isVoid){
 
@@ -278,6 +310,15 @@
             }
         }
 
+        // shortcut for jQuery's .appendTo() method
+        // set the 'appendTo' property to a selector,
+        // element or jQuery object
+        if (opts.appendTo && window.jQuery){
+            // using brackets to clarify that we're
+            // using the .appendTo() jQuery method
+            jQuery(el)['appendTo'](opts.appendTo);
+        }
+
         // execute element methods last...
         // attach object or array of methods
         // to 'fn' property - this can be an
@@ -292,17 +333,48 @@
         }
 
         // execute jQuery methods from the `$` property
-        if (opts.$ && window.$){
-            $el = $(el);
-            forOwn(opts.$, function(method, args){
-                $el[method].apply($el, [].concat(args));
-            });
+        if (opts.$ && window.jQuery){
+            $el = jQuery(el);
+            // use an array to call the same method more than once
+            if (Array.isArray(opts.$)){
+                forEach(opts.$, function(item){
+                    $el[item[0]].apply($el, [].concat(item[1]))
+                });
+            }
+            else {
+                forOwn(opts.$, function(method, args){
+                    $el[method].apply($el, [].concat(args));
+                });
+            }
+        }
+
+        var frag = document.createDocumentFragment();
+
+        if (opts.after){
+            frag.appendChild(el);
+            appendChildren(frag, opts.after, spawn);
+            el = frag;
+        }
+        
+        if (opts.before){
+            appendChildren(frag, opts.before, spawn);
+            frag.appendChild(el);
+            el = frag;
         }
 
         if (errors.length){
             if (hasConsole) console.log(errors);
         }
 
+        // alias for convenience
+        el.html = el.outerHTML;
+
+        el.element = el;
+
+        el.get = function(){
+            return el;
+        };
+
         return el;
 
     }
@@ -375,8 +447,8 @@
             opts = {};
         }
 
-        if (opts.classes || opts.className){
-            el.className = [].concat(el.className||[], opts.classes||[], opts.className||[]).join(' ');
+        if (opts.classes || opts.className || opts.addClass){
+            el.className = [].concat(el.className||[], opts.classes||[], opts.className||[], opts.addClass||[]).join(' ');
         }
 
         if (opts.html){
@@ -385,7 +457,7 @@
 
         // add attributes and properties to element
         forOwn(opts, function(prop, val){
-            if (/^(tag|html|classes|attr|data|$)$/.test(prop)) return;
+            if (/^(tag|html|classes|addClass|attr|append|appendChild|data|fn|$)$/.test(prop)) return;
             el[prop] = val;
         });
 
@@ -408,12 +480,37 @@
             appendChildren(el, content, spawnElement);
         }
 
-        if (opts.$){
-            $el = $(el);
-            forOwn(opts.$, function(method, args){
-                $el[method].apply($el, [].concat(args));
+        // special handling of 'append' and 'appendChild'
+        if (opts.appendChild){
+            appendChildren(el, opts.appendChild, spawnElement);
+        }
+        if (opts.append){
+            appendChildren(el, opts.append, spawnElement);
+        }
+
+        // call any element methods
+        if (opts.fn){
+            [].concat(opts.fn).forEach(function(fn){
+                forOwn(fn, function(f, args){
+                    el[f].apply(el, [].concat(args));
+                });
             });
-            //delete opts.$;
+        }
+
+        // execute jQuery methods from the `$` property
+        if (opts.$ && window.jQuery){
+            $el = jQuery(el);
+            // use an array to call the same method more than once
+            if (Array.isArray(opts.$)){
+                forEach(opts.$, function(item){
+                    $el[item[0]].apply($el, [].concat(item[1]))
+                });
+            }
+            else {
+                forOwn(opts.$, function(method, args){
+                    $el[method].apply($el, [].concat(args));
+                });
+            }
         }
 
         return el;
@@ -819,6 +916,22 @@
     // export to the global window object
     window.spawn = spawn;
 
+    // if jQuery is present, add it to the jQuery
+    // prototype for chaining and the jQuery object
+    // to get a jQuery-wrapped spawned element
+    if (window.jQuery){
+
+        jQuery.fn.spawn = function(){
+            this.append(spawn.apply(this, arguments));
+            return this;
+        };
+
+        jQuery.spawn = function(){
+            return jQuery(spawn.apply(null, arguments));
+        }
+
+    }
+
     //
     // utility functions:
     //
diff --git a/src/main/webapp/scripts/utils.js b/src/main/webapp/scripts/utils.js
old mode 100644
new mode 100755
index 2b7a89d9..f3791d4b
--- a/src/main/webapp/scripts/utils.js
+++ b/src/main/webapp/scripts/utils.js
@@ -622,3 +622,33 @@ function compareByText( obj1, obj2 ){
     return convertToText(obj1) === convertToText(obj2);
 }
 
+// simplest accordion of all
+$.fn.superSimpleAccordion = function(){
+    
+    var container = $(this).show();
+    var h3s = container.find('h3');
+    var divs = h3s.next('div');
+
+    divs.addClass('content').hide();
+    container.find('.active').show();
+    h3s.on('click', function(){
+        var content = $(this).next('div');
+        if (content.is(':hidden')) {
+            h3s.removeClass('active');
+            $(this).addClass('active');
+            divs.removeClass('active').slideUp();
+            content.addClass('active').slideDown();
+        }
+    });
+
+    // add styling to <head> element
+    $('head').append('<style type="text/css" id="accordion-styles">' +
+        '#accordion { font-family: Arial, Helvetica, sans-serif; font-size: 13px; line-height: 17px; }' +
+        '#accordion .active { display: block; }' +
+        '#accordion h3 { font-size: 13px; font-weight: bold; color: #222; padding: 5px 10px; border: 1px solid #d0d0d0; }' +
+        '#accordion h3:hover { cursor: pointer; }' +
+        '#accordion h3.active { background: #04519b; color: #fff; } ' +
+        '#accordion .content { padding: 1em; border: 1px solid #d0d0d0; }' +
+        '</style>');
+    
+};
diff --git a/src/main/webapp/scripts/xnat/app/customPage.js b/src/main/webapp/scripts/xnat/app/customPage.js
index b2290809..adaa953b 100644
--- a/src/main/webapp/scripts/xnat/app/customPage.js
+++ b/src/main/webapp/scripts/xnat/app/customPage.js
@@ -20,9 +20,10 @@ XNAT.app = getObject(XNAT.app||{});
 
     var customPage = getObject(XNAT.app.customPage||{});
 
-    customPage.getName = function(){
+    customPage.getName = function(end){
         var name = getQueryStringValue('view');
-        name = name || getUrlHashValue('#view=');
+        name = name || getUrlHashValue('#view=', end);
+        name = name || getUrlHashValue('#/', end);
         name = name || getUrlHash();
         return customPage.name = name;
     };
@@ -32,9 +33,17 @@ XNAT.app = getObject(XNAT.app||{});
     customPage.getPage = function(name, container){
 
         var pagePaths = [],
-            themePaths = [];
-
-        name = name || customPage.getName();
+            themePaths = [],
+            end = ';';
+
+        // use an array for the name param
+        // to specify a start AND end for the page string
+        if (Array.isArray(name)){
+            end  = name[1] || end;
+            name = name[0] || '';
+        }
+        
+        name = name || customPage.getName(end);
 
         // don't even bother if there's no name
         if (!name) return;
diff --git a/src/main/webapp/scripts/xnat/element.js b/src/main/webapp/scripts/xnat/element.js
index 797fe83e..e908f25f 100644
--- a/src/main/webapp/scripts/xnat/element.js
+++ b/src/main/webapp/scripts/xnat/element.js
@@ -148,11 +148,11 @@ var XNAT = getObject(XNAT||{});
     // chainable spawner
     // XNAT.element('div').p()._b('Bold text. ')._i('Italic text.');
     // -> <div><p><b>Bold text. </b><i>Italic text.</i></p></div>
-    XNAT.element = XNAT.el = element = function(tag, opts, content){
+    element = function(tag, opts, content){
         return new Element(tag, opts, content);
     };
     //////////////////////////////////////////////////////////////////////
-
+    
     // copy value from 'target' to 'source'
     element.copyValue = function(target, source){
         var sourceValue = $$(source).val();
@@ -262,4 +262,16 @@ var XNAT = getObject(XNAT||{});
 
     });
 
+
+    //////////////////////////////////////////////////////////////////////
+    // chainable spawner
+    // XNAT.element('div').p()._b('Bold text. ')._i('Italic text.');
+    // -> <div><p><b>Bold text. </b><i>Italic text.</i></p></div>
+    XNAT.element = XNAT.el = element;
+    // also add to XNAT.ui namespace
+    XNAT.ui = getObject(XNAT.ui||{});
+    XNAT.ui.element = XNAT.element;
+    //////////////////////////////////////////////////////////////////////
+
+
 })(XNAT);
diff --git a/src/main/webapp/scripts/xnat/url.js b/src/main/webapp/scripts/xnat/url.js
index 27c8a4d6..54bb44f7 100644
--- a/src/main/webapp/scripts/xnat/url.js
+++ b/src/main/webapp/scripts/xnat/url.js
@@ -365,7 +365,7 @@ var XNAT = getObject(XNAT||{});
         // XNAT.xhr.cache === false (the default)
         if (xhr && !xhr.cache) {
             if (isUndefined(cacheParam) || isTrue(cacheParam)){
-                params.XNAT_XHR = randomID(Date.now(), false);
+                params.XNAT_XHR = randomID('x', false) + '-' + Date.now();
             }
         }
 
diff --git a/src/main/webapp/scripts/xnat/xhr.js b/src/main/webapp/scripts/xnat/xhr.js
old mode 100644
new mode 100755
index ebf00570..c323e5e8
--- a/src/main/webapp/scripts/xnat/xhr.js
+++ b/src/main/webapp/scripts/xnat/xhr.js
@@ -68,7 +68,8 @@ var XNAT = getObject(XNAT||{}),
     //////////////////////////////////////////////////
 
     // add XNAT.url methods to XNAT.xhr
-    extend(xhr, url);
+    // No, don't do that.
+    //extend(xhr, url);
 
     xhr.$ = getObject(xhr.$||{});
 
@@ -331,7 +332,9 @@ var XNAT = getObject(XNAT||{}),
         'getJSON' : { method: 'GET', dataType: 'json', format: 'json' },
         'getHTML' : { method: 'GET', dataType: 'html', format: 'html' },
         'getXML' : { method: 'GET', dataType: 'xml', format: 'xml' },
-        'getText' : { method: 'GET', dataType: 'text', format: 'text' }
+        'getText' : { method: 'GET', dataType: 'text', format: 'text' },
+        'putJSON' : { method: 'PUT', contentType: 'application/json', processData: false },
+        'postJSON' : { method: 'POST', contentType: 'application/json', processData: false }
     };
 
     xhr.shorthands._delete = xhr.shorthands.delete_ = xhr.shorthands['delete'];
@@ -363,13 +366,45 @@ var XNAT = getObject(XNAT||{}),
     // get the remote HTML
     xhr.loadHTML = function( $container, url, data, callback ){
 
-        // need $container and url at the very least
+        var obj = {
+                method: 'GET',
+                dataType: 'html'
+            },
+            success = null;
+
+        // need $container and url arguments at the very least
         if (arguments.length < 2){
             return new Error('XNAT.xhr.loadHTML() requires the $container and url arguments.');
         }
 
+        if (isPlainObject(url)){
+            obj = extend(true, {}, url, obj);
+        }
+        else {
+            obj.url = url;
+            obj.data = data;
+        }
+
+        // if there's a 'success' property, call it
+        // after appending the HTML
+        if (isFunction(obj.success)){
+            success = obj.success;
+        }
+
+        obj.success = function(html){
+            $$($container).empty().append(html);
+            if (success){
+                success.apply(this, arguments);
+            }
+            if (isFunction(callback)){
+                callback.apply(this, arguments);
+            }
+        };
+
+        return xhr.request(obj);
+
         // make sure we've got a jQuery object and pass to jQuery's .load() method
-        $$($container).load(url, data, callback);
+        //$$($container).load(url, data, callback);
 
     };
 
diff --git a/src/main/webapp/style/app.css b/src/main/webapp/style/app.css
index 5e6d1dbc..bae49796 100644
--- a/src/main/webapp/style/app.css
+++ b/src/main/webapp/style/app.css
@@ -687,6 +687,13 @@ table.ruled tr:nth-child(2n+1) {
     font-family: Courier, monospace;
 }
 
+textarea.full {
+    width: 100%; height: 100%;
+    position: absolute;
+    top: 0; bottom: 0;
+    left: 0; right: 0;
+}
+
 /* ALERTS & MESSAGES TO USERS */
 div.alert,
 div.error,
@@ -902,7 +909,7 @@ body,
 html body,
 * body {
     margin: 0;
-    background: #f0f0f0;
+    background: #fff;
     font-family: Arial, Helvetica, sans-serif !important;
     font-size: 12px;
     line-height: 15px;
@@ -1093,11 +1100,11 @@ select[multiple] {
 }
 
 /*#wrapper_table {*/
-    /*min-width: 940px;*/
+/*min-width: 940px;*/
 /*}*/
 
 /*.ltie9 #wrapper_table {*/
-    /*width: 940px;*/
+/*width: 940px;*/
 /*}*/
 
 /* search results page */
@@ -1154,9 +1161,9 @@ select[multiple] {
 }
 
 /*@media screen and (min-width: 1000px) {*/
-    /*#login_welcome {*/
-        /*margin: 0 100px 0 0;*/
-    /*}*/
+/*#login_welcome {*/
+/*margin: 0 100px 0 0;*/
+/*}*/
 /*}*/
 
 #login_box,
@@ -1207,8 +1214,8 @@ select[multiple] {
 }
 
 /*#login_container {*/
-    /*margin: 0 auto;*/
-    /*max-width: 1200px;*/
+/*margin: 0 auto;*/
+/*max-width: 1200px;*/
 /*}*/
 
 #register_forgot a {
@@ -1216,13 +1223,13 @@ select[multiple] {
 }
 
 /*#login_container #header {*/
-    /*position: absolute;*/
-    /*left: 20px;*/
+/*position: absolute;*/
+/*left: 20px;*/
 /*}*/
 
 /*#login_container #layout_content,*/
 /*#login_container #layout_content2 {*/
-    /*margin-top: 90px;*/
+/*margin-top: 90px;*/
 /*}*/
 
 #site_description {
@@ -1259,6 +1266,11 @@ span.sep {
     background: #033769;
 }
 
+#user_bar > .inner {
+    width: 1160px;
+    margin: 0 auto;
+}
+
 #user_bar.no_menu {
     /*width: 100%;*/
 }
@@ -1358,9 +1370,9 @@ span.sep {
 
 /* .ie #searchValue { position: relative ; top: 1px ; } */
 /*#quickSearchForm a {*/
-    /*padding: 2px 8px;*/
-    /*font-size: 12px;*/
-    /*font-weight: bold;*/
+/*padding: 2px 8px;*/
+/*font-size: 12px;*/
+/*font-weight: bold;*/
 /*}*/
 
 #quickSearchForm button {
@@ -1676,44 +1688,55 @@ div.containerTitle.withColor {
     border-bottom: 1px solid #033769;
 }
 
+#main_nav > .inner {
+    width: 1160px;
+    margin: 0 auto;
+    position: relative;
+    overflow: visible;
+}
+
 #main_nav.hidden {
     overflow: hidden;
 }
 
-#main_nav > ul li {
+#main_nav ul.nav li {
     float: left;
     display: block;
     position: relative;
 }
 
-#main_nav > ul,
-#main_nav > ul ul {
+#main_nav ul.nav,
+#main_nav ul.nav ul {
     margin: 0;
     padding: 0;
     list-style: none;
     /*background: #fff;*/
 }
 
-#main_nav > ul a {
+#main_nav ul.nav a {
     display: block;
     text-decoration: none;
     vertical-align: middle;
     white-space: nowrap;
 }
 
-#main_nav > ul a:hover {
+#main_nav ul.nav a:hover {
     cursor: pointer !important;
 }
 
-#main_nav > ul > li {
+#main_nav ul.nav > li {
     border-right: 1px solid #033769;
 }
 
+#main_nav ul.nav > li:first-of-type {
+    border-left: 1px solid #033769;
+}
+
 /*#main_nav li:hover {*/
-    /*!*background: url('../images/nav_bkgd_hover.png') center center repeat-x;*!*/
+/*!*background: url('../images/nav_bkgd_hover.png') center center repeat-x;*!*/
 /*}*/
 
-#main_nav > ul > li > a {
+#main_nav ul.nav > li > a {
     padding: 0 15px;
     line-height: 40px;
     background-position: right center;
@@ -1721,23 +1744,23 @@ div.containerTitle.withColor {
     color: #fff;
 }
 
-#main_nav > ul > li > a:hover {
+#main_nav ul.nav > li > a:hover {
     /*color: #000;*/
     background-color: #044687;
 }
 
-#main_nav > ul > li.more > a {
+#main_nav ul.nav > li.more > a {
     padding-right: 30px;
     background-image: url('../images/nav_more_light.png');
 }
 
 /* second-level menus */
-#main_nav > ul > li.more > ul {
+#main_nav ul.nav > li.more > ul {
     top: 40px;
 }
 
 /* all subnav */
-#main_nav > ul ul {
+#main_nav ul.nav ul {
     display: none;
     position: absolute;
     left: 0;
@@ -1748,15 +1771,15 @@ div.containerTitle.withColor {
 }
 
 /* make sure all sub-nav menus are wide enough */
-#main_nav > ul li li {
+#main_nav ul.nav li li {
     width: 100%;
 }
 
 /*#main_nav li li:hover {*/
-    /*background: !* #D8ECFF *! !* #EDF5FF *! #e5f2ff right center no-repeat;*/
+/*background: !* #D8ECFF *! !* #EDF5FF *! #e5f2ff right center no-repeat;*/
 /*}*/
 
-#main_nav > ul li li a {
+#main_nav ul.nav li li a {
     padding: 0 30px 0 15px;
     line-height: 25px;
     font-size: 13px;
@@ -1767,23 +1790,23 @@ div.containerTitle.withColor {
     color: #222;
 }
 
-#main_nav > ul li li a:hover {
+#main_nav ul.nav li li a:hover {
     background-color: #e5f2ff;
 }
 
-#main_nav li li.more > a {
+#main_nav ul.nav li li.more > a {
     background-image: url('../images/subnav_more_light.png');
 }
 
 /* third-level menus */
-#main_nav > ul li li ul {
+#main_nav ul.nav li li ul {
     display: none;
     top: 0;
     margin-left: -2px;
     border: 1px solid #c0c0c0;
 }
 
-#main_nav > ul ul,
+#main_nav ul.nav ul,
 .shadowed {
     -moz-box-shadow: 0 2px 12px rgba(96, 96, 96, 0.8);
     -webkit-box-shadow: 0 2px 12px rgba(96, 96, 96, 0.8);
@@ -2577,6 +2600,10 @@ span.spacer {
     display: none !important;
 }
 
+.w10 { display: inline-block; width: 10px; }
+.w20 { display: inline-block; width: 20px; }
+.w40 { display: inline-block; width: 40px; }
+
 /* configuration tabs */
 .mgmt_container {
     padding: 10px;
@@ -2694,7 +2721,8 @@ span.spacer {
     border-bottom: none;
 }
 
-.xnat-table tr.highlight:hover {
+.xnat-table tr.highlight:hover,
+.xnat-table.highlight tr:hover {
     background: #e5f2ff;
 }
 
@@ -2760,7 +2788,14 @@ span.spacer {
 }
 
 .xnat-table.clean tr:hover {
-    background: #e5f2ff;
+    /*background: #e5f2ff;*/
+}
+
+.xnat-table.borderless,
+.xnat-table.borderless tr,
+.xnat-table.borderless th,
+.xnat-table.borderless td {
+    border: none;
 }
 
 /* overrides for Chosen menus (if used) */
@@ -3430,6 +3465,7 @@ div.submit div.actions {
 }
 
 hr {
+    display: block;
     margin: 20px 0;
 }
 
@@ -3437,13 +3473,20 @@ hr.h20 {
     margin: 10px 0;
 }
 
+hr.light {
+    color: #f0f0f0;
+    border-color: #f0f0f0;
+}
+
 
 /* accommodate smaller viewports */
 /***********************************************/
 @media screen and (max-width: 1200px) {
 
     #page_wrapper,
-    #xnat_power {
+    #xnat_power,
+    #main_nav > .inner,
+    #user_bar > .inner {
         width: 960px;
     }
 
@@ -3452,7 +3495,7 @@ hr.h20 {
     /*#layout_content,*/
     /*#home-data,*/
     /*#xnat_power {*/
-        /*width: 920px;*/
+    /*width: 920px;*/
     /*}*/
 
     #projects-container,
@@ -3483,3 +3526,13 @@ hr.h20 {
 
 }
 
+pre.json { outline: 1px solid #f0f0f0; padding: 20px; }
+pre.json .string { color: green; }
+pre.json .number { color: darkorange; }
+pre.json .boolean { color: blue; }
+pre.json .null { color: magenta; }
+pre.json .key { color: red; }
+textarea.xml-formatted {
+    display: block; padding: 10px; margin: 10px; width: 95%; height: 95%;
+    overflow-x: scroll; overflow-y: scroll; border: 1px solid #ccc;
+}
diff --git a/src/main/webapp/xnat-templates/navigations/DefaultTop.vm b/src/main/webapp/xnat-templates/navigations/DefaultTop.vm
index 6b94dca3..529763fc 100644
--- a/src/main/webapp/xnat-templates/navigations/DefaultTop.vm
+++ b/src/main/webapp/xnat-templates/navigations/DefaultTop.vm
@@ -47,6 +47,8 @@
     </div>
 </div><!-- /user_bar -->
 
+<script src="$content.getURI("scripts/timeLeft.js")"></script>
+
 #if ($sessionCount > 1 || $sessionIpCount > 1 )
 ##If you want fewer warnings, you can eliminate $sessionCount > 1 so it will not display a warning for multiple sessions on the same IP, or increase it to $sessionCount > X where X is the maximum number of sessions you can have on the same IP before you get a warning.
 <script type="text/javascript">
@@ -95,13 +97,15 @@
 #end
 
 <div id="main_nav">
+    <div class="inner">
 
-    <ul class="nav">
+    <ul class="nav" style="border-left: 1px solid #033769;">
         #addGlobalCustomScreens("topBar")
     </ul>
 
     $navigation.setTemplate("XNATQuickSearch.vm")
 
+    </div>
 </div>
 <!-- /main_nav -->
 
@@ -111,7 +115,7 @@
     (function() {
 
         // cache it
-        var main_nav$ = jq('#main_nav > ul');
+        var main_nav$ = jq('#main_nav ul.nav');
 
 //        function subnav(items){
 //            var lis = [];
@@ -223,6 +227,8 @@
 </script>
 <!-- end main_nav interactions -->
 
+<div id="page_wrapper">
+
 <div id="header" class="main_header"><div class="pad">
 
     #if($siteConfig.getProperty("siteWideAlertStatus","")=="2")
@@ -322,8 +328,6 @@
 
 </script>
 
-<script src="$content.getURI("scripts/timeLeft.js")"></script>
-
 <div id="tp_fm"></div>
 
 <!-- END: xnat-templates/navigations/DefaultTop.vm -->
diff --git a/src/main/webapp/xnat-templates/navigations/bodyOpen.vm b/src/main/webapp/xnat-templates/navigations/bodyOpen.vm
index 9af76d41..3583e1fe 100644
--- a/src/main/webapp/xnat-templates/navigations/bodyOpen.vm
+++ b/src/main/webapp/xnat-templates/navigations/bodyOpen.vm
@@ -2,4 +2,3 @@
 ## open <head> tag in htmlOpen.vm file
 </head>
 <body id="page_body" class="yui-skin-sam">
-<div id="page_wrapper">
-- 
GitLab