From fc0e61977f989eab507ff5b2596d596ff28190d4 Mon Sep 17 00:00:00 2001
From: "Mark M. Florida" <mflorida@gmail.com>
Date: Sun, 8 May 2016 22:00:33 -0500
Subject: [PATCH] Enabled loading and saving of data from form panels with use
 of 'load', 'method', and 'action' properties - see 'panel.form' elements in
 the 'site-admin-sample-new.yaml' file for examples. Pulling in the whole
 siteConfig object for populating properties in the admin ui. Other small
 javascript and CSS tweaks.

---
 src/main/webapp/page/admin/content.jsp        |  10 ++
 .../data/config/site-admin-sample-new.yaml    |  35 ++---
 src/main/webapp/page/admin/style.css          |   6 +-
 src/main/webapp/page/admin/tabs.js            |  37 +++--
 src/main/webapp/scripts/globals.js            |  23 ++++
 src/main/webapp/scripts/xnat/spawner.js       |   5 +
 src/main/webapp/scripts/xnat/ui/panel.js      | 129 +++++++++++++++++-
 src/main/webapp/scripts/xnat/url.js           |   2 +-
 src/main/webapp/style/app.css                 |  16 +++
 9 files changed, 231 insertions(+), 32 deletions(-)

diff --git a/src/main/webapp/page/admin/content.jsp b/src/main/webapp/page/admin/content.jsp
index ea2c52b7..e8ea27c2 100755
--- a/src/main/webapp/page/admin/content.jsp
+++ b/src/main/webapp/page/admin/content.jsp
@@ -30,6 +30,16 @@
 
                 </div>
 
+                <script src="${sessionScope.siteRoot}/scripts/lib/jquery-plugins/jquery.form.js"></script>
+
+                <c:import url="/xapi/siteConfig" var="siteConfig"/>
+
+                <script>
+                    XNAT.data = extend({}, XNAT.data, {
+                        siteConfig: ${siteConfig}
+                    });
+                </script>
+
                 <script src="${sessionScope.siteRoot}/scripts/xnat/ui/templates.js"></script>
                 <script src="${sessionScope.siteRoot}/page/admin/tabs.js"></script>
 
diff --git a/src/main/webapp/page/admin/data/config/site-admin-sample-new.yaml b/src/main/webapp/page/admin/data/config/site-admin-sample-new.yaml
index 7766cc32..2cc8643f 100644
--- a/src/main/webapp/page/admin/data/config/site-admin-sample-new.yaml
+++ b/src/main/webapp/page/admin/data/config/site-admin-sample-new.yaml
@@ -1,4 +1,4 @@
-root:
+siteAdmin:
     kind: tabs
     name: siteAdmin
     label: Administer XNAT
@@ -31,6 +31,11 @@ root:
                     kind: panel.form
                     name: siteInfo
                     label: Site Information
+                    method: POST
+                    action: /xapi/siteConfig/batch
+                    load:
+                        data: XNAT.data.siteConfig
+
                     contents:
 
                         # panel input
@@ -118,7 +123,12 @@ root:
                     kind: panel.form
                     method: POST
                     action: /xapi/notifications/all
-                    name: emailServerSettings
+                    load: # load data
+                        url: /xapi/siteConfig
+#                        method: GET      # defaults to GET if not set
+                        prop: smtpServer # which root property? gets the root object if not set
+#                        data: XNAT.data.siteConfig.smtpServer
+                    name:  emailServerSettings
                     label: Mail Server Settings
                     contents:
                         hostname:
@@ -128,18 +138,15 @@ root:
                             value: ''
                             element:
                                 placeholder: localhost
-                            data:
+                            #data:
                                 # METHOD:/url/for/data:object.path
-                                get: GET|/xapi/siteConfig|smtpServer.host
-                                set: POST|/xapi/notifications/host
+                                #get: GET|/xapi/siteConfig|smtpServer.host
+                                #set: POST|/xapi/notifications/host
                         port:
                             kind: panel.input.number
                             name: port
                             label: Port
                             value: ''
-                            data:
-                                get: GET|/xapi/siteConfig|smtpServer.port
-                                set: POST|/xapi/notifications/port
                             element:
                                 placeholder: 587
                         username:
@@ -148,9 +155,6 @@ root:
                             label: Username
                             url: /xapi/notifications/username
                             value: ''
-                            data:
-                                get: GET|/xapi/siteConfig|smtpServer.username
-                                set: POST|/xapi/notifications/username
                             element:
                                 placeholder: admin@localhost
                         password:
@@ -158,17 +162,14 @@ root:
                             name: password
                             label: Password
                             value: ''
-                            data:
-                                get: GET|/xapi/siteConfig|smtpServer.username
-                                set: POST|/xapi/notifications/password
                         protocol:
                             kind: panel.input.text
                             name: protocol
                             label: Protocol
                             value: ''
-                            data:
-                                get: GET|/xapi/siteConfig|smtpServer.protocol
-                                set: POST|/xapi/notifications/protocol
+                            #data:
+                                #get: GET|/xapi/siteConfig|smtpServer.protocol
+                                #set: POST|/xapi/notifications/protocol
                         # subhead breaks up panel items
                         mailServerProperties:
                             kind: panel.subhead
diff --git a/src/main/webapp/page/admin/style.css b/src/main/webapp/page/admin/style.css
index d77aa64d..5c2da1ea 100644
--- a/src/main/webapp/page/admin/style.css
+++ b/src/main/webapp/page/admin/style.css
@@ -81,7 +81,11 @@ body.xnat .panel-title { font-weight: normal; }
 /* PANEL ELEMENTS */
 .panel .panel-element { margin: 15px 0; clear: both; overflow: auto; }
 .panel .panel-element > * { box-sizing: border-box; }
-.panel .panel-element .element-label { width: 35%; padding-right: 10px; float: left; color: #222; text-align: right; }
+.panel .panel-element .element-label {
+    width: 35%; padding-right: 10px; float: left;
+    color: #222; font-weight: bold; text-align: right;
+    line-height: 24px; vertical-align: middle;
+}
 .panel .panel-element .element-wrapper { display: inline-block; width: 65%; float: right; }
 .panel .panel-element .element-wrapper .description { margin: 10px 0; }
 .panel .panel-element span.before { right: 10px; }
diff --git a/src/main/webapp/page/admin/tabs.js b/src/main/webapp/page/admin/tabs.js
index d65fc590..64274ffb 100644
--- a/src/main/webapp/page/admin/tabs.js
+++ b/src/main/webapp/page/admin/tabs.js
@@ -1,5 +1,5 @@
 (function(){
-
+    
     var $head = $('head');
 
     // // append the css to the head
@@ -20,15 +20,30 @@
     var jsonUrl = XNAT.url.rootUrl('/page/admin/data/config/site-admin-sample-new.yaml');
     //var jsonUrl = XNAT.url.rootUrl('/page/admin/data/config/site-admin-sample-new.json');
     //var jsonUrl = XNAT.url.rootUrl('/xapi/spawner/resolve/siteAdmin/siteAdmin');
-    $.get({
-        url: jsonUrl,
-        // dataType: 'text',
-        success: function(data){
-            var json = YAML.parse(data);
-            var adminTabs = XNAT.spawner.spawn(json);
-            adminTabs.render('#admin-config-tabs > .xnat-tab-content');
-            XNAT.app.adminTabs = adminTabs;
-        }
-    });
+    
+    // get the siteConfig object first
+    // doing this in JSP for better(?) performance
+    //XNAT.xhr.get(XNAT.url.restUrl('/xapi/siteConfig'), function(siteConfig){
+
+        // put those values in an object named XNAT.data.siteConfig
+      //  XNAT.data = extend({}, XNAT.data, {
+        //    siteConfig: siteConfig
+        //});
+
+        // THEN render the tabs...
+        $.get({
+            url: jsonUrl,
+            // dataType: 'text',
+            success: function(data){
+                var json = YAML.parse(data);
+                var adminTabs = XNAT.spawner.spawn(json);
+                adminTabs.render('#admin-config-tabs > .xnat-tab-content');
+                XNAT.app.adminTabs = adminTabs;
+                // xmodal.loading.closeAll();
+            }
+
+        });
+
+    //});
 
 })();
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/xnat/spawner.js b/src/main/webapp/scripts/xnat/spawner.js
index ed97ed2f..ccff298b 100644
--- a/src/main/webapp/scripts/xnat/spawner.js
+++ b/src/main/webapp/scripts/xnat/spawner.js
@@ -141,6 +141,11 @@ var XNAT = getObject(XNAT);
             if (prop.before) {
                 $frag.prepend(prop.before)
             }
+            
+            // if there's a .load() method, fire that
+            if (isFunction(spawnedElement.load)) {
+                spawnedElement.load();
+            }
 
         });
 
diff --git a/src/main/webapp/scripts/xnat/ui/panel.js b/src/main/webapp/scripts/xnat/ui/panel.js
index 10817f38..3031cb37 100644
--- a/src/main/webapp/scripts/xnat/ui/panel.js
+++ b/src/main/webapp/scripts/xnat/ui/panel.js
@@ -68,6 +68,7 @@ var XNAT = getObject(XNAT || {});
         }
     };
 
+
     // creates a panel that's a form that can be submitted
     panel.form = function panelForm(opts){
 
@@ -78,10 +79,12 @@ var XNAT = getObject(XNAT || {});
 
             hideFooter = (isDefined(opts.footer) && (opts.footer === false || /^-/.test(opts.footer))),
 
+            _resetBtn = spawn('button.btn.btn-sm.btn-default.revert.pull-right|type=button', 'Discard Changes'),
+
             _footer = [
                 ['button.btn.btn-sm.btn-primary.save.pull-right|type=submit', 'Submit'],
                 ['span.pull-right', '&nbsp;&nbsp;&nbsp;'],
-                ['button.btn.btn-sm.btn-default.revert.pull-right|type=button', 'Discard Changes'],
+                _resetBtn,
                 ['button.btn.btn-sm.btn-link.defaults.pull-left', 'Default Settings'],
                 ['div.clear']
             ],
@@ -105,11 +108,133 @@ var XNAT = getObject(XNAT || {});
         if (opts.id || opts.element.id) {
             _formPanel.id = (opts.id || opts.element.id) + '-panel';
         }
+
+        // set form element values from an object map
+        function setValues(form, dataObj){
+            // pass a single argument to work with this form
+            if (!dataObj) {
+                dataObj = form;
+                form = _formPanel;
+            }
+            // find all form inputs with a name attribute
+            $$(form).find(':input[name]').each(function(){
+                $(this).changeVal(dataObj[this.name]||'');
+            });
+            if (xmodal && xmodal.loading && xmodal.loading.close){
+                xmodal.loading.close();
+            }
+        }
+
+        // populate the data fields if this panel is in the 'active' tab
+        // (only getting values for the active tab should cut down on requests)
+        function loadData(obj){
+
+            if (!obj) {
+                obj = opts.load || {};
+            }
+
+            obj = extend(true, {}, obj);
+
+            obj.form = obj.form || obj.target || obj.element || _formPanel;
+
+            // need a form to put the data into
+            if (!obj.form) return;
+
+            // if we pass data in a 'data' property, just use that
+            // to avoid doing a server request
+
+            if (obj.data) {
+                setValues(obj.form, eval(obj.data));
+                return obj.form;
+            }
+
+            // otherwise try to get the data values via ajax
+
+            // need a url to get the data
+            if (!obj.url) return obj.form;
+
+            obj.method = obj.method || 'GET';
+
+            // setup the ajax request
+            // override values with an
+            // 'ajax' or 'xhr' property
+            obj.ajax = extend(true, {
+                method: obj.method,
+                url: XNAT.url.restUrl(obj.url)
+            }, obj.ajax || obj.xhr);
+
+            // allow use of 'prop' or 'root' for the root property name
+            obj.prop = obj.prop || obj.root;
+
+            obj.ajax.success = function(data){
+                var prop = data;
+                // if there's a property to target,
+                // specify the 'prop' property
+                if (obj.prop){
+                    obj.prop.split('.').forEach(function(part){
+                        prop = prop[part];
+                    });
+                }
+                setValues(prop);
+            };
+
+            // return the ajax thing for method chaining
+            return XNAT.xhr.request(obj.ajax);
+
+        }
+
+        //if (opts.load){
+        //    loadData(opts.load);
+        //}
+
+        // click 'Discard Changes' butotn to reload data
+        _resetBtn.onclick = function(){
+            xmodal.loading.open();
+            loadData(opts.load);
+        };
         
         // intercept the form submit to do it via REST instead
-        
+        $(_formPanel).on('submit', function(e){
+
+            e.preventDefault();
+
+            xmodal.loading.open();
+
+            var ajaxSubmitOpts = {
 
+                target:        '#server-response',  // target element(s) to be updated with server response
+                beforeSubmit:  function(){},  // pre-submit callback
+                success:       function(){},  // post-submit callback
+
+                // other available options:
+                url:       '/url/for/submit', // override for form's 'action' attribute
+                type:      'get or post (or put?)', // 'get' or 'post', override for form's 'method' attribute
+                dataType:  null,        // 'xml', 'script', or 'json' (expected server response type)
+                clearForm: true,        // clear all form fields after successful submit
+                resetForm: true,        // reset the form after successful submit
+
+                // $.ajax options can be used here too, for example:
+                timeout:   3000
+
+            };
+
+            $(this).ajaxSubmit({
+                success: function(){
+                    xmodal.loading.close();
+                    xmodal.message('Data saved successfully.', {
+                        action: loadData()
+                    });
+                }
+            });
+
+            return false;
+
+        });
+        
+        // this object is returned to the XNAT.spawner() method
         return {
+            load: loadData,
+            setValues: setValues,
             target: _target,
             element: _formPanel,
             spawned: _formPanel,
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/style/app.css b/src/main/webapp/style/app.css
index 688e936e..e5c446c7 100644
--- a/src/main/webapp/style/app.css
+++ b/src/main/webapp/style/app.css
@@ -3449,6 +3449,7 @@ div.submit div.actions {
 }
 
 hr {
+    display: block;
     margin: 20px 0;
 }
 
@@ -3456,6 +3457,11 @@ hr.h20 {
     margin: 10px 0;
 }
 
+hr.light {
+    color: #f0f0f0;
+    border-color: #f0f0f0;
+}
+
 
 /* accommodate smaller viewports */
 /***********************************************/
@@ -3502,3 +3508,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;
+}
-- 
GitLab