From 2abc41b51e3b378516cc8d28305384219795f552 Mon Sep 17 00:00:00 2001
From: "Mark M. Florida" <mflorida@gmail.com>
Date: Fri, 27 May 2016 21:51:10 -0500
Subject: [PATCH] Refactored tabs.js to be 'universal' (instead of just for the
 Admin UI); fixed display (or lack thereof) of descriptions on select menus;
 minor style tweaks.

---
 src/main/webapp/page/admin/content.jsp        |  60 ++++++---
 .../webapp/page/admin/spawner/content.jsp     |  10 +-
 src/main/webapp/page/admin/style.css          |  10 +-
 .../webapp/scripts/xnat/admin/siteInfo.js     |   2 +-
 src/main/webapp/scripts/xnat/spawner.js       |  44 ++++--
 src/main/webapp/scripts/xnat/ui/panel.js      |  35 ++---
 src/main/webapp/scripts/xnat/ui/tabs.js       | 127 +++++++++---------
 src/main/webapp/scripts/xnat/ui/templates.js  |  86 +++++-------
 src/main/webapp/style/app.css                 |  25 ++--
 9 files changed, 226 insertions(+), 173 deletions(-)

diff --git a/src/main/webapp/page/admin/content.jsp b/src/main/webapp/page/admin/content.jsp
index 17d49aa4..9c0c9d6f 100755
--- a/src/main/webapp/page/admin/content.jsp
+++ b/src/main/webapp/page/admin/content.jsp
@@ -21,42 +21,65 @@
                 </header>
 
                 <!-- Admin tab container -->
-                <div id="admin-config-tabs" class="content-tabs xnat-tab-container">
+                <div id="admin-config-tabs">
+
+                    <div class="content-tabs xnat-tab-container">
+
+                        <%--
+                        <div class="xnat-nav-tabs side pull-left">
+                            <!-- ================== -->
+                            <!-- Admin tab flippers -->
+                            <!-- ================== -->
+                        </div>
+                        <div class="xnat-tab-content side pull-right">
+                            <!-- ================== -->
+                            <!-- Admin tab panes    -->
+                            <!-- ================== -->
+                        </div>
+                        --%>
 
-                    <div class="xnat-nav-tabs side pull-left">
-                        <!-- ================== -->
-                        <!-- Admin tab flippers -->
-                        <!-- ================== -->
-                    </div>
-
-                    <div class="xnat-tab-content side pull-right">
-                        <!-- ================== -->
-                        <!-- Admin tab panes    -->
-                        <!-- ================== -->
                     </div>
 
                 </div>
 
                 <c:import url="/xapi/siteConfig" var="siteConfig"/>
                 <c:import url="/xapi/notifications" var="notifications"/>
+
                 <script>
                     (function(){
 
-                        XNAT.data = extend({}, XNAT.data, {
-                            siteConfig: ${siteConfig},
-                            notifications: ${notifications}
-                        });
-                        // get rid of the 'targetSource' property
-                        delete XNAT.data.siteConfig.targetSource;
+                        XNAT.data = getObject(XNAT.data);
+
+                        <%-- safety check --%>
+                        <c:if test="${not empty siteConfig}">
+                            XNAT.data.siteConfig = ${siteConfig};
+                            // get rid of the 'targetSource' property
+                            delete XNAT.data.siteConfig.targetSource;
+                        </c:if>
+
+                        <%-- can't use empty/undefined object --%>
+                        <c:if test="${not empty notifications}">
+                            XNAT.data.notifications = ${notifications};
+                        </c:if>
 
                         var jsonUrl = XNAT.url.rootUrl('/xapi/spawner/resolve/siteAdmin/adminPage');
 
                         $.get({
                             url: jsonUrl,
                             success: function(data){
+
+                                // these properties need to be set before spawning 'tabs' widgets
+                                XNAT.tabs.container = $('#admin-config-tabs').find('div.content-tabs');
+                                XNAT.tabs.layout = 'left';
+
+                                // SPAWN THE TABS
                                 var adminTabs = XNAT.spawner.spawn(data);
-                                adminTabs.render('#admin-config-tabs > .xnat-tab-content');
+
+                                adminTabs.render(XNAT.tabs.container, 500);
+
+                                // SAVE THE JSON
                                 XNAT.app.adminTabs = adminTabs;
+
                             }
                         });
 
@@ -84,3 +107,4 @@
     </div>
 
 </pg:restricted>
+
diff --git a/src/main/webapp/page/admin/spawner/content.jsp b/src/main/webapp/page/admin/spawner/content.jsp
index 0736b8bb..277f3b87 100644
--- a/src/main/webapp/page/admin/spawner/content.jsp
+++ b/src/main/webapp/page/admin/spawner/content.jsp
@@ -22,15 +22,19 @@
 
             <div data-name="spawnerElements" class="panel-element" style="overflow:visible;">
 
+                <div class="description" style="margin:20px 5px 0">View and manage XNAT Spawner elements.</div>
+
                     <%--<label class="element-label" for="!?"></label>--%>
                     <%--<div class="element-wrapper">--%>
 
-                <table id="spawner-element-list" class="xnat-table alt1 clean" style="width:100%;border:none;">
+                <style type="text/css">
+                    #spawner-element-list td { padding: 4px; }
+                </style>
+
+                <table id="spawner-element-list" class="xnat-table highlight alt1 clean" style="width:100%;border:none;">
                     <!-- list of available namespaces will show here -->
                 </table>
 
-                <div class="description" style="margin:20px 5px 0">View and manage XNAT Spawner elements.</div>
-
                     <%--</div>--%>
 
             </div>
diff --git a/src/main/webapp/page/admin/style.css b/src/main/webapp/page/admin/style.css
index b2f6ed2c..946b2f1f 100644
--- a/src/main/webapp/page/admin/style.css
+++ b/src/main/webapp/page/admin/style.css
@@ -74,7 +74,7 @@ body.xnat .panel-default { border: 1px solid #c8c8c8; }
     padding: 12px 16px;
     color: #222; /background: /* #e9e9e9 */ inherit;
 }
-.panel-default .panel-body { padding: 12px; }
+.panel-default .panel-body { padding: 12px 30px; }
 .panel-default .panel-footer  { padding: 12px; background: #f0f0f0; border-top: #c8c8c8; }
 .panel-title { font-weight: normal; font-size: 20px; line-height: inherit; }
 
@@ -100,7 +100,13 @@ body.xnat .panel-default { border: 1px solid #c8c8c8; }
     border-bottom: 1px solid #c8c8c8;
 }
 
-.panel .panel-element textarea { width: 80%; font-family: Courier, monospace; font-weight: normal; }
+.panel .panel-element textarea {
+    width: 80%; padding: 5px 7px;
+    font-family: Courier, monospace; font-weight: normal;
+}
+
+/* inputs shouldn't be wider than textareas */
+.panel .panel-element input { max-width: 80%; }
 
 /* ELEMENT GROUP ITEMS */
 .panel .panel-element-group .group-item .element-label { width: auto; }
diff --git a/src/main/webapp/scripts/xnat/admin/siteInfo.js b/src/main/webapp/scripts/xnat/admin/siteInfo.js
index 070fe46d..3f102f37 100644
--- a/src/main/webapp/scripts/xnat/admin/siteInfo.js
+++ b/src/main/webapp/scripts/xnat/admin/siteInfo.js
@@ -9,7 +9,7 @@
         sdtPage.click(changeSiteDescriptionType);
         sdtText.click(changeSiteDescriptionType);
         changeSiteDescriptionType(XNAT.data.siteConfig.siteDescriptionType);
-    }, 100);
+    }, 1);
 
     function changeSiteDescriptionType(eventOrValue){
 
diff --git a/src/main/webapp/scripts/xnat/spawner.js b/src/main/webapp/scripts/xnat/spawner.js
index dc4efb74..7faa5a4f 100644
--- a/src/main/webapp/scripts/xnat/spawner.js
+++ b/src/main/webapp/scripts/xnat/spawner.js
@@ -27,6 +27,8 @@ var XNAT = getObject(XNAT);
     XNAT.spawner = spawner =
         getObject(XNAT.spawner || {});
 
+    spawner.counter = 0;
+    
     // keep track of items that spawned
     spawner.spawnedElements = [];
 
@@ -43,8 +45,11 @@ var XNAT = getObject(XNAT);
     spawner.spawn = function _spawn(obj){
 
         var frag  = document.createDocumentFragment(),
-            $frag = $(frag);
+            $frag = $(frag),
+            undefined;
 
+        spawner.counter++;
+        
         forOwn(obj, function(item, prop){
 
             var kind, methodName, method, spawnedElement, $spawnedElement;
@@ -82,7 +87,7 @@ var XNAT = getObject(XNAT);
 
                 // pass 'content' (not contentS) property to add
                 // stuff directly to spawned element
-                prop.content = prop.content || prop.children || prop.inner || '';
+                prop.content = prop.content || prop.children || '';
 
                 try {
                     spawnedElement =
@@ -143,6 +148,9 @@ var XNAT = getObject(XNAT);
                 }
             }
 
+            // give up if no spawnedElement
+            if (!spawnedElement) return;
+
             // spawn child elements from...
             // 'contents' or 'content' or 'children' or
             // a property matching the value of either 'contains' or 'kind'
@@ -211,18 +219,34 @@ var XNAT = getObject(XNAT);
             return $frag.contents();    
         };
 
-        _spawn.render = function(element, empty){
-            var $el = $$(element);
-            // empty the container element before spawning?
-            if (empty) {
-                $el.empty();
+        _spawn.done = function(callback){
+            if (isFunction(callback)) {
+                callback()
             }
-            $el.append(frag);
-            return spawn;
+            return _spawn;
         };
 
-        _spawn.foo = '(spawn.foo)';
+        _spawn.render = function(container, wait, callback){
+
+            var $container = $$(container);
 
+            wait = wait !== undefined ? wait : 100;
+
+            $container.hide().append(frag);
+            $container.fadeIn(wait);
+
+            setTimeout(function(){
+                if (isFunction(callback)) {
+                    callback()
+                }
+            }, wait);
+
+            return _spawn;
+
+        };
+
+        _spawn.foo = '(spawn.foo)';
+        
         return _spawn;
 
     };
diff --git a/src/main/webapp/scripts/xnat/ui/panel.js b/src/main/webapp/scripts/xnat/ui/panel.js
index c79640bb..89094323 100644
--- a/src/main/webapp/scripts/xnat/ui/panel.js
+++ b/src/main/webapp/scripts/xnat/ui/panel.js
@@ -732,26 +732,35 @@ var XNAT = getObject(XNAT || {});
         var _menu;
 
         opts = cloneObject(opts);
-        opts.element = opts.element || opts.config || {};
-        opts.element.name = opts.element.name || opts.name || '';
-        opts.element.id = opts.element.id || opts.id || toDashed(opts.element.name);
+
+        opts.name = opts.name || opts.id || randomID('select-', false);
+        opts.id = opts.id || toDashed(opts.name||'');
+        opts.element = extend({
+            id: opts.id,
+            name: opts.name,
+            className: opts.className||'',
+            title: opts.title||opts.name||opts.id||'',
+            value: opts.value||''
+        }, opts.element);
+        
         if (multi) {
             opts.element.multiple = true;
         }
-        _menu = spawn('select', opts.element, [['option|value=!', 'Select']]);
+
+        _menu = spawn('select', opts.element, [['option', 'Select']]);
 
         if (opts.options){
             forOwn(opts.options, function(name, prop){
                 _menu.appendChild(spawn('option', {
-                    value: prop.value,
+                    html: prop.html || prop.text || prop.label || prop.value || prop,
+                    value: prop.value || name,
                     selected: prop.selected || (prop.value === opts.value)
-                }, prop.label));
+                }));
             });
         }
-        return XNAT.ui.template.panelInput({
-            label: opts.label,
-            name: opts.name
-        }, _menu).spawned;
+
+        return XNAT.ui.template.panelInput(opts, _menu).spawned;
+
     };
     panel.select.init = panel.select.menu;
     panel.select.single = panel.select.menu;
@@ -761,12 +770,6 @@ var XNAT = getObject(XNAT || {});
     };
     panel.select.multi.init = panel.select.multi;
 
-    panel.selectMenu = function panelSelectMenu(opts){
-        opts = cloneObject(opts);
-        return XNAT.ui.template.panelSelect(opts).spawned;
-    };
-
-
     function footerButton(text, type, disabled, classes){
         var button = {
             type: type || 'button',
diff --git a/src/main/webapp/scripts/xnat/ui/tabs.js b/src/main/webapp/scripts/xnat/ui/tabs.js
index d750f8bb..b819acb6 100755
--- a/src/main/webapp/scripts/xnat/ui/tabs.js
+++ b/src/main/webapp/scripts/xnat/ui/tabs.js
@@ -34,35 +34,27 @@ var XNAT = getObject(XNAT || {});
     // ==================================================
     // SET UP ONE TAB GROUP
     // add a single tab group to the groups
-    tab.group = function(obj, container){
+    tab.group = function(obj){
         var id = toDashed(obj.id || obj.name);
         if (!id) return; // a tab group MUST have an id
-        var group = spawn('ul.nav.tab-group', { id: id }, [
+        return spawn('ul.nav.tab-group', { id: id }, [
             ['li.label', (obj.label || obj.title || obj.text || 'Tab Group')]
         ]);
-        if (container) {
-            $$(container).append(group);
-        }
-        return group;
     };
     // ==================================================
 
 
     // ==================================================
     // SET UP TAB GROUPS
-    tab.groups = function(obj, container, empty){
-        var groups = [],
-            $container = $$(container);
+    tab.groups = function(obj){
+        var groups = [];
         $.each(obj, function(name, label){
             groups.push(tab.group({
                 id: toDashed(name),
                 label: label
             }));
         });
-        if (empty) {
-            $container.empty();
-        }
-        $container.append(groups);
+        // console.log(groups);
         return groups;
     };
     // ==================================================
@@ -70,57 +62,28 @@ var XNAT = getObject(XNAT || {});
 
     // save the id of the active tab
     XNAT.ui.tab.active = '';
+    tab.activate = function(name, container){
 
-    function activateTab(tab, id){
-
-        var $tab  = $(tab),
-            $tabs = $(tab).closest('div.xnat-tab-container');
-
-        // first deactivate ALL tabs and panes
-        $tabs
-            .find('div.tab-pane')
-            .hide()
-            .removeClass('active');
-
-        $tabs
-            .find('li.tab')
-            .removeClass('active');
-
-        // then activate THIS tab and pane
-
-        $tab.addClass('active');
-
-        $('#' + id)
-            .show()
-            .addClass('active');
-
-        XNAT.ui.tab.active = id;
-
-    }
-    
+    };
 
     // ==================================================
     // CREATE A SINGLE TAB
     tab.init = function _tab(obj){
 
-        var $group, _flipper, _pane;
+        var $group, groupId, tabId, _flipper, _pane;
+
+        obj = cloneObject(obj);
+        obj.config = cloneObject(obj.config);
 
-        obj = getObject(obj);
-        obj.config = getObject(obj.config);
-        obj.config.id = obj.config.id || obj.id || (toDashed(obj.name) + '-content');
-        obj.config.data = extend({ name: obj.name }, obj.config.data);
+        tabId = toDashed(obj.id || obj.name || '');
 
         _flipper = spawn('li.tab', {
-            // onclick event handler attached
-            // directly to tab flipper
-            onclick: function(){
-                activateTab(this, obj.config.id)
-            }
+            data: { tab: tabId }
         }, [
             ['a', {
                 title: obj.label,
                 // href: '#'+obj.config.id,
-                href: '#!',
+                href: '#' + tabId,
                 html: obj.label
             }]
         ]);
@@ -137,6 +100,12 @@ var XNAT = getObject(XNAT || {});
         }
         tab.paneFooter = paneFooter;
 
+        obj.config.data =
+            extend(true, {}, obj.config.data, {
+                name: obj.name||'',
+                tab: tabId
+            });
+
         _pane = spawn('div.tab-pane', obj.config);
 
         if (obj.active) {
@@ -145,15 +114,19 @@ var XNAT = getObject(XNAT || {});
             tab.active = _pane.id;
         }
 
+        groupId = toDashed(obj.group||'other');
+
         // un-hide the group that this tab is in
         // (groups are hidden until there is a tab for them)
-        $group = $('#' + (toDashed(obj.group || 'other')) + '.tab-group');
-        
+        $group = $('#' + groupId + '.tab-group');
+
         $group.show();
         
         // add all the flippers
         $group.append(_flipper);
 
+        // console.log($group[0]);
+
         function render(element){
             $$(element).append(_pane);
             $$(element).append(paneFooter());
@@ -176,29 +149,58 @@ var XNAT = getObject(XNAT || {});
     };
     // ==================================================
 
-
+    
     // ==================================================
     // MAIN FUNCTION
-    tabs.init = function _tabs(obj){
+    tabs.init = function tabsInit(obj){
+
+        var layout, container, $container, 
+            navTabs, tabContent;
+
+        // set container and layout before spawning:
+        // XNAT.tabs.container = 'div.foo';
+        container = tabs.container || 'div.xnat-tab-container';
 
-        var spawned = spawn('div.tabs');
+        layout = tabs.layout || 'left';
+
+        navTabs = spawn('div.xnat-nav-tabs');
+        tabContent = spawn('div.xnat-tab-content');
+
+        if (layout === 'left') {
+            navTabs.className += ' side pull-left';
+            tabContent.className += ' side pull-right';
+        }
+
+        $container = $$(container);
+
+        $container.append(navTabs);
+        $container.append(tabContent);
 
         // set up the group elements
-        tab.groups(obj.meta.tabGroups, '#admin-config-tabs > .xnat-nav-tabs');
+        $(navTabs).append(tab.groups(obj.meta.tabGroups));
+
+        // bind tab click events
+        $container.on('click', 'li.tab', function(){
+            var clicked = $(this).data('tab');
+            // de-activate all tabs and panes
+            $container.find('[data-tab]').removeClass('active');
+            // activate the clicked tab and pane
+            $container.find('[data-tab="' + clicked + '"]').addClass('active');
+        });
 
         function render(element){
-            $$(element).append(spawned);
-            return spawned;
+            $$(element).append(tabContent);
+            return tabContent;
         }
 
         function get(){
-            return spawned;
+            return tabContent;
         }
 
         return {
             // contents: obj.tabs||obj.contents||obj.content||'',
-            element: spawned,
-            spawned: spawned,
+            element: tabContent,
+            spawned: tabContent,
             render: render,
             get: get
         };
@@ -211,3 +213,4 @@ var XNAT = getObject(XNAT || {});
     return tabs;
 
 }));
+
diff --git a/src/main/webapp/scripts/xnat/ui/templates.js b/src/main/webapp/scripts/xnat/ui/templates.js
index 58609549..635ed915 100644
--- a/src/main/webapp/scripts/xnat/ui/templates.js
+++ b/src/main/webapp/scripts/xnat/ui/templates.js
@@ -224,30 +224,6 @@ var XNAT = getObject(XNAT);
             ajaxValue(element, ajaxUrl.trim(), ajaxProp.trim());
         }
 
-        // if (opts.load) {
-        //     if (opts.load.lookup) {
-        //         lookupValue(element, opts.load.lookup.trim());
-        //     }
-        //     else if (opts.load.url){
-        //         $.ajax({
-        //             method: opts.load.method || 'GET',
-        //             url: XNAT.url.restUrl(opts.load.url),
-        //             success: function(data){
-        //                 // get value from specific object path
-        //                 if (isPlainObject(data) && opts.load.prop) {
-        //                     data = lookupObjectValue(data, opts.load.prop);
-        //                     // opts.load.prop.split('.').forEach(function(part){
-        //                     //     data = data[part] || {};
-        //                     // });
-        //                     // data = lookupObjectValue(opts.load.prop);
-        //                 }
-        //                 $(element).changeVal(data);
-        //                 $(element).not('textarea').dataAttr('value', data);
-        //             }
-        //         })
-        //     }
-        // }
-
         // trigger an 'onchange' event
         $element.change();
 
@@ -302,35 +278,39 @@ var XNAT = getObject(XNAT);
 
     // ========================================
     // select element for form panels
-    template.panelSelect = function(opts){
-        opts = cloneObject(opts);
-        opts.name = opts.name || opts.id || randomID('select-', false);
-        opts.id = opts.id || toDashed(opts.name||'');
-        opts.element = extend({
-            id: opts.id,
-            name: opts.name,
-            className: opts.className||'',
-            //size: 25,
-            title: opts.title||opts.name||opts.id||'',
-            value: opts.value||''
-        }, opts.element);
-
-        var _select = spawn('select', opts.element, [['option|value=!', 'Select']]);
-        
-        // add the options
-        $.each(opts.options||{}, function(name, prop){
-            var _option = spawn('option', extend(true, {
-                html: prop.html || prop.text || prop.label || prop.value || prop,
-                value: prop.value || name
-            }, prop.element));
-            // select the option if it's the select element's value
-            if (prop.value === opts.value){
-                _option.selected = true;
-            }
-            _select.appendChild(_option)    
-        });
-        return template.panelInput(opts, _select);
-    };
+    // template.panelSelect = function(opts){
+    //    
+    //     opts = cloneObject(opts);
+    //    
+    //     opts.name = opts.name || opts.id || randomID('select-', false);
+    //     opts.id = opts.id || toDashed(opts.name||'');
+    //     opts.element = extend({
+    //         id: opts.id,
+    //         name: opts.name,
+    //         className: opts.className||'',
+    //         //size: 25,
+    //         title: opts.title||opts.name||opts.id||'',
+    //         value: opts.value||''
+    //     }, opts.element);
+    //
+    //     var _select = spawn('select', opts.element, [['option', 'Select']]);
+    //    
+    //     // add the options
+    //     $.each(opts.options||{}, function(name, prop){
+    //         var _option = spawn('option', extend(true, {
+    //             html: prop.html || prop.text || prop.label || prop.value || prop,
+    //             value: prop.value || name
+    //         }, prop.element));
+    //         // select the option if it's the select element's value
+    //         if (prop.value === opts.value){
+    //             _option.selected = true;
+    //         }
+    //         _select.appendChild(_option)    
+    //     });
+    //    
+    //     return template.panelInput(opts, _select);
+    //
+    // };
     // ========================================
 
 
diff --git a/src/main/webapp/style/app.css b/src/main/webapp/style/app.css
index 5dc84a0f..77508130 100644
--- a/src/main/webapp/style/app.css
+++ b/src/main/webapp/style/app.css
@@ -61,21 +61,28 @@ img {
 input[type="text"],
 input[type="password"],
 input[type="number"] {
-    padding: 4px 5px 3px 5px;
-    border: 1px solid #b0b0b0;
+    padding: 5px 7px;
+    border: 1px solid #c0c0c0;
 }
 
 textarea {
     padding: 4px;
     font-family: Arial, Helvetica, sans-serif;
-    font-size: 12px;
-    border: 1px solid #b0b0b0;
+    font-size: 13px;
+    border: 1px solid #c0c0c0;
 }
 
-select {
-    /* */
+input:focus,
+textarea:focus {
+    border-color: #2fa4e7;
+    box-shadow: 0 0 2px #2fa4e7;
+    outline: none;
 }
 
+select:focus { outline: none; }
+
+select {  }
+
 /* xnat.css */
 /***********************************************/
 /* XNAT styles                                 */
@@ -961,7 +968,7 @@ hr {
 /* //////////  FORM ELEMENTS  ////////// */
 input {
     font-family: Arial, Helvetica, sans-serif;
-    font-size: 12px;
+    font-size: 13px;
 }
 
 /* 'required' items */
@@ -983,9 +990,10 @@ body [type="reset"] {
     border-radius: 3px;
     font-family: Arial, Helvetica, sans-serif;
     font-size: 13px;
+    white-space: nowrap;
     letter-spacing: 0.02em;
     text-decoration: none;
-    box-shadow: 0px 1px 1px #ccc;
+    box-shadow: 0 1px 1px #ccc;
 }
 
 /* style for button hover */
@@ -3578,3 +3586,4 @@ textarea.xml-formatted {
 .pad5h { padding-left: 5px; padding-right: 5px; }
 .pad10h { padding-left: 10px; padding-right: 10px; }
 .pad20h { padding-left: 20px; padding-right: 20px; }
+
-- 
GitLab