From df6a4a2a81c0993298afc968bb7b9545e77873df Mon Sep 17 00:00:00 2001
From: "Mark M. Florida" <mflorida@gmail.com>
Date: Sat, 14 May 2016 11:11:53 -0500
Subject: [PATCH] Fixed saving of Spawner elements on "Manage Spawner" page.

---
 .../xnat/spawner/site-admin-elements.yaml     | 60 ++++++++++++++-----
 src/main/webapp/page/admin/content.jsp        |  7 +--
 .../webapp/page/admin/spawner/content.jsp     |  2 +-
 .../page/admin/spawner/spawner-admin.js       | 48 +++++++--------
 src/main/webapp/page/admin/tabs.js            |  2 +-
 src/main/webapp/scripts/lib/spawn/spawn.js    | 40 +++++++++----
 src/main/webapp/scripts/utils.js              |  2 +-
 src/main/webapp/scripts/xnat/xhr.js           | 17 +++++-
 8 files changed, 117 insertions(+), 61 deletions(-)

diff --git a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
index 84bcb3dc..0a28fd6a 100644
--- a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
+++ b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
@@ -16,6 +16,7 @@ siteId:
     label: Site ID
     validation: required id onblur
     description: "The id used to refer to this site (also used to generate database ids). The Site ID must start with a letter and contain only letters, numbers and underscores. It should be a short, one-word name or acronym which describes your site. No spaces or non-alphanumeric characters."
+
 siteDescriptionType:
     kind: panel.display  # make this a radio button group
     id: siteDescriptionType
@@ -23,46 +24,59 @@ siteDescriptionType:
     label: Site Description
     value: Select a site description option below
     # onchange: # some javascript that toggles display of the elements below appropriately
+
 siteDescriptionOptionText:
     kind: panel.input.checkbox  # make this a radio button group
     id: siteDescriptionOptionText
     label: "Text (Markdown)"
+
 siteDescriptionOptionPage:
     kind: panel.input.checkbox  # make this a radio button group
     id: siteDescriptionOptionPage
     label: "Page"
+
 siteDescriptionPage:
     kind: panel.input.text
     id: siteDescriptionPage
     name: siteDescriptionPage
     label: " "
     description: "Specify a velocity template file to display on the login page"
+
 siteDescriptionText:
     kind: panel.textarea
     id: siteDescriptionText
     name: siteDescriptionText
     label: " "
     description: "Specify a simple text description of this site"
+    element:
+        style:
+            width: 300px
+            height: 150px
+
 siteLoginLanding:
     kind: panel.input.text
     id: siteLoginLanding
     name: siteLoginLanding
     label: Site Login Landing
+
 siteLandingLayout:
     kind: panel.input.text
     id: siteLandingLayout
     name: siteLandingLayout
     label: Site Landing Layout
+
 siteHome:
     kind: panel.input.text
     id: siteHome
     name: siteHome
     label: Site Home
+
 siteHomeLayout:
     kind: panel.input.text
     id: siteHomeLayout
     name: siteHomeLayout
     label: Site Home Layout
+
 siteUrl:
     kind: panel.input.text
     id: siteUrl
@@ -70,21 +84,24 @@ siteUrl:
     label: Site Url
     validation: required id
     description: ""
+
 adminEmail:
     kind: panel.input.email
     id: adminEmail
     name: adminEmail
     label: Site Administrator Email Address
-    url: /xapi/siteConfig/adminEmail
-    value: ''
+    value: ?? XNAT.data.siteConfig.adminEmail
 
 fileSystemSettingsWarning:
     someInfo:
         tag: div.message
         element:
-            html: "These settings must be defined during initial application configuration, and are seldom - if ever - changed."
+            html: >
+                These settings must be defined during initial application configuration,
+                and are seldom, if ever, changed.
             style:
                 fontWeight: bold
+
 archiveRootPath:
     kind: panel.input.text
     id: archiveRootPath
@@ -92,7 +109,8 @@ archiveRootPath:
     label: Archive Root Path
     validation: required path
     description: ""
-    value: ??XNAT.data.siteConfig.archiveRootPath
+    value: ?? XNAT.data.siteConfig.archiveRootPath
+
 cachePath:
     kind: panel.input.text
     id: cachePath
@@ -100,7 +118,8 @@ cachePath:
     label: Cache Path
     validation: required path
     description: ""
-    value: ??XNAT.data.siteConfig.cachePath
+    value: ?? XNAT.data.siteConfig.cachePath
+
 prearchivePath:
     kind: panel.input.text
     id: prearchivePath
@@ -108,7 +127,8 @@ prearchivePath:
     label: Prearchive Path
     validation: required path
     description: ""
-    value: ??XNAT.data.siteConfig.prearchivePath
+    value: ?? XNAT.data.siteConfig.prearchivePath
+
 ftpPath:
     kind: panel.input.text
     id: ftpPath
@@ -210,41 +230,49 @@ userLoginsSessionControls:
         lookup: XNAT.data.siteConfig
         refresh: /xapi/siteConfig
     contents:
+
         sessionTimeout:
             kind: panel.input.number
             id: sessionTimeout
             name: aliasTokenTimeout
             label: Session Timeout
-            value: 15
-            description: "Interval for timing out alias tokens. Uses PostgreSQL interval notation: http://www.postgresql.org/docs/9.0/static/functions-datetime.html"
+            value: ?? XNAT.data.siteConfig.sessionTimeout || 15
+            description: >
+                Interval for timing out alias tokens. Uses
+                <a target="_blank" href="http://www.postgresql.org/docs/9.0/static/functions-datetime.html">PostgreSQL interval notation</a>
+
         sessionTimeoutMessage:
             kind: panel.input.text
             id: sessionTimeoutMessage
             name: sessionTimeoutMessage
             label: Session Timeout Message
-            value: "Your session has timed out."
+            value: ?? XNAT.data.siteConfig.sessionTimeoutMessage || "Your session has timed out."
             description: Alert message provided to users after a session timeout and logout.
+
         allowResumeOnNextLogin:
             kind: panel.input.checkbox
-            id: allowResumeOnNextLogin
+            id: allow-resume-on-next-login
             name: allowResumeOnNextLogin
             label: Allow Resume On Next Login?
-            value: true
+            value: ?? XNAT.data.siteConfig.allowResumeOnNextLogin
             description: Allow user to resume where they left off, if logging back in after a session timeout?
+
         maximumConcurrentSessions:
             kind: panel.input.number
             id: maximumConcurrentSessions
             name: security.sessions.concurrent_max
             label: Maximum Concurrent Sessions
-            value: 5
+            value: ?? XNAT.data.siteConfig.maximumConcurrentSessions || 5
             description: The maximum number of permitted sessions a user can have open simultaneously
+
         loginFailureMessage:
             kind: panel.input.text
             id: loginFailureMessage
             name: UI.login_failure_message
             label: Login Failure Message
-            value: Login failed
+            value: || Login failed
             description: Text to show when a user fails to login
+
         maximumFailedLogins:
             kind: panel.input.number
             id: maximumFailedLogins
@@ -252,6 +280,7 @@ userLoginsSessionControls:
             label: Maximum Failed Logins
             value: -1
             description: Number of failed login attempts before accounts are temporarily locked. (-1 disables feature)
+
         failedLoginLockoutDuration:
             kind: panel.input.number
             id: failedLoginLockoutDuration
@@ -259,6 +288,7 @@ userLoginsSessionControls:
             label: Failed Login Lockout Duration
             value: 0
             description: Number of milliseconds to lock user accounts that have exceeded the max_failed_logins count. Select (3600000 for 1 hour, 86400000 for 24 hours)
+
         userInactivityLockout:
             kind: panel.input.number
             id: userInactivityLockout
@@ -1011,9 +1041,9 @@ misc:
 #################################################
 ####  Root Site Admin Spawner Config Object  ####
 #################################################
-siteAdmin:
+adminPage:
     kind: tabs
-    name: siteAdmin
+    name: adminPage
     label: Administer XNAT
     meta:
         ${tabGroups}
diff --git a/src/main/webapp/page/admin/content.jsp b/src/main/webapp/page/admin/content.jsp
index 2457534d..ec8aeace 100755
--- a/src/main/webapp/page/admin/content.jsp
+++ b/src/main/webapp/page/admin/content.jsp
@@ -32,9 +32,6 @@
 
                 </div>
 
-                <%--<script src="${sessionScope.siteRoot}/scripts/lib/jquery-plugins/jquery.form.js"></script>--%>
-                <%--<script src="${sessionScope.siteRoot}/scripts/lib/yamljs/dist/yaml.js"></script>--%>
-
                 <c:import url="/xapi/siteConfig" var="siteConfig"/>
 
                 <script>
@@ -47,8 +44,8 @@
                         delete XNAT.data.siteConfig.targetSource;
 
 
-                        var jsonUrl = XNAT.url.rootUrl('/page/admin/data/config/site-admin-sample-new.yaml');
-                        // var jsonUrl = XNAT.url.rootUrl('/xapi/spawner/resolve/siteAdmin/siteAdmin');
+                        // var jsonUrl = XNAT.url.rootUrl('/page/admin/data/config/site-admin-sample-new.yaml');
+                        var jsonUrl = XNAT.url.rootUrl('/xapi/spawner/resolve/siteAdmin/adminPage');
 
                         $.get({
                             url: jsonUrl,
diff --git a/src/main/webapp/page/admin/spawner/content.jsp b/src/main/webapp/page/admin/spawner/content.jsp
index 11d0a5b1..a2b46a58 100644
--- a/src/main/webapp/page/admin/spawner/content.jsp
+++ b/src/main/webapp/page/admin/spawner/content.jsp
@@ -19,7 +19,7 @@
 
             <div class="panel-body">
 
-            <div data-name="spawnerElements" class="panel-element">
+            <div data-name="spawnerElements" class="panel-element" style="overflow:visible;">
 
             <%--<label class="element-label" for="!?"></label>--%>
             <%--<div class="element-wrapper">--%>
diff --git a/src/main/webapp/page/admin/spawner/spawner-admin.js b/src/main/webapp/page/admin/spawner/spawner-admin.js
index 4b63c32a..141bd144 100644
--- a/src/main/webapp/page/admin/spawner/spawner-admin.js
+++ b/src/main/webapp/page/admin/spawner/spawner-admin.js
@@ -13,7 +13,7 @@
     spawn('button|type=button', {
         html: 'View JSON',
         onclick: function(){
-            XNAT.xhr.get(XNAT.url.rootUrl('/xapi/spawner/resolve/siteAdmin/siteAdmin'), function(data){
+            XNAT.xhr.get(XNAT.url.rootUrl('/xapi/spawner/resolve/siteAdmin/adminPage'), function(data){
                 showJSON(data);
             });
         },
@@ -73,7 +73,7 @@ XNAT.xhr.getJSON({
             ]]);
 
             var idsMenu = spawn('select#spawner-ns-ids', [['option|value=!', 'Select an Element']]);
-
+            
             tds.push(['td', [
 
                 idsMenu,
@@ -91,22 +91,22 @@ XNAT.xhr.getJSON({
                         }
                         var elementUrl = XNAT.url.rootUrl('/xapi/spawner/element/' + NS + '/' + getId);
                         $.get(elementUrl, function(data){
-                            var _textarea = spawn('textarea.mono', {
+                            var _textarea = spawn('textarea.yaml.mono', {
                                 name: getId,
                                 html: data.yaml,
                                 style: { width: '500px', height: '250px' }
                             });
                             _textarea.innerHTML = (data.yaml);
                             var _table = XNAT.table({className: 'xnat-table borderless'}).init([
+                                // [ [['b.label', 'Label: ']], data.label ],
+                                // we could use spawn arg arrays (above), but HTML (below) is fine
                                 ['<b class="label">Namespace:</b> ', data.namespace],
                                 ['<b class="label">Element ID:</b> ', data.elementId],
-                                [ [['b.label', 'Label: ']], data.label ],
-                                //['<b class="label">Label:</b> ', data.label],
-                                [ [['b.label', 'Created: ']], new Date(data.created).toString() ],
-                                // ['<b class="label">Created:</b> ', new Date(data.created).toString()],
+                                ['<b class="label">Label:</b> ', data.label],
+                                ['<b class="label">Created:</b> ', new Date(data.created).toString()],
                                 ['<b class="label">Modified:</b> ', new Date(data.timestamp).toString()]
                             ]);
-                            // anothe way to add a row of data
+                            // another way to add a row of data
                             _table.tr().td([['b.label', 'YAML: ']]).td([_textarea]);
                             //_table.tr().td('<b>YAML:</b> ').td([_textarea]);
                             xmodal.open({
@@ -114,25 +114,20 @@ XNAT.xhr.getJSON({
                                 width: 700,
                                 height: 550,
                                 maximize: true,
+                                enter: false,
+                                esc: false,
                                 title: 'Element ID: ' + getId,
                                 content: _table.get().outerHTML,
                                 beforeShow: function(obj){
                                     console.log(obj)
                                 },
                                 okLabel: 'Save Changes',
-                                okAction: function(){
+                                okAction: function(obj){
                                     XNAT.xhr.put({
-                                        // url: XNAT.url.csrfUrl(elementUrl),
-                                        url: (elementUrl),
-                                        data: {
-                                            // namespace: data.namespace,
-                                            // elementId: data.elementId,
-                                            yaml: _textarea.value
-                                        },
-                                        //dataType: 'text/x-yaml',
-                                        //contentType: 'application/json',
+                                        url: elementUrl,
+                                        data: obj.$modal.find('textarea.yaml').val(),
                                         contentType: 'text/x-yaml',
-                                        processData:  false,
+                                        // processData:  false,
                                         success: function(){
                                             xmodal.message('Data saved.')
                                         },
@@ -150,14 +145,14 @@ XNAT.xhr.getJSON({
             // spawn and push the row
             items.push(spawn('tr', tds));
 
-            (function(){
-                XNAT.xhr.get(XNAT.url.rootUrl('/xapi/spawner/ids/' + NS), function(ids){
-                    $.each(ids, function(){
-                        var id = this;
-                        idsMenu.appendChild(spawn('option', { value: id, html: id }))
-                    });
+            // populate menu with spawner elements for 'NS' namespace
+            XNAT.xhr.get(XNAT.url.rootUrl('/xapi/spawner/ids/' + NS), function(ids){
+                $.each(ids, function(){
+                    var id = this;
+                    idsMenu.appendChild(spawn('option', { value: id, html: id }))
                 });
-            })()
+                chosenInit(idsMenu, { width: '250px' });
+            });
 
         });
 
@@ -165,3 +160,4 @@ XNAT.xhr.getJSON({
 
     }
 });
+
diff --git a/src/main/webapp/page/admin/tabs.js b/src/main/webapp/page/admin/tabs.js
index 2d11966a..916bf95f 100644
--- a/src/main/webapp/page/admin/tabs.js
+++ b/src/main/webapp/page/admin/tabs.js
@@ -19,7 +19,7 @@
     // get the JSON and do the setup
     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');
+    // var jsonUrl = XNAT.url.rootUrl('/xapi/spawner/resolve/siteAdmin/adminPage');
 
     // get the siteConfig object first
     // doing this in JSP for better(?) performance
diff --git a/src/main/webapp/scripts/lib/spawn/spawn.js b/src/main/webapp/scripts/lib/spawn/spawn.js
index 228aa1ef..f5b42515 100644
--- a/src/main/webapp/scripts/lib/spawn/spawn.js
+++ b/src/main/webapp/scripts/lib/spawn/spawn.js
@@ -37,10 +37,22 @@
 
     // boolean element attributes
     var boolAttrs = [
-        'disabled',
-        'selected',
         'checked',
-        'multiple'
+        'selected',
+        'disabled',
+        'hidden',
+        'multiple',
+        'readonly',
+        'async',
+        'autofocus',
+        'autoplay',
+        'controls',
+        'defer',
+        'ismap',
+        'loop',
+        'open',
+        'required',
+        'scoped'
     ];
 
     // which "type" values create an <input> element?
@@ -65,6 +77,12 @@
     });
 
 
+    // can the value be reasonably used as a string?
+    function stringable(val){
+        return /string|number|boolean/.test(typeof val);
+    }
+
+
     function parseAttrs(el, attrs){
         // allow ';' or ',' for attribute delimeter
         (attrs.split(/;|,/) || []).forEach(function(att, i){
@@ -93,7 +111,7 @@
                 el.appendChild(fn.apply(el, child));
             }
             // ...or an HTML string (or number)...
-            else if (/(string|number)/.test(typeof child)){
+            else if (stringable(child)){
                 el.innerHTML += child;
             }
             // ...or 'appendable' nodes
@@ -128,9 +146,11 @@
     function spawn(tag, opts, children){
 
         var el, $el, parts, id, classes, tagParts, attrs, isVoid,
-        // property names to skip later
-            skip = ['innerHTML', 'html', 'append', 'appendTo',
-                'classes', 'className', 'attr', 'style', 'data', 'fn'],
+            // property names to skip later
+            skip = [
+                'innerHTML', 'html', 'attr', 'append', 'appendTo',
+                'classes', 'className', 'style', 'data', 'fn'
+            ],
             errors = []; // collect errors
 
         // deal with passing an array as the only argument
@@ -207,7 +227,7 @@
             // if 'opts' is a string,
             // set el's innerHTML and
             // return the element
-            if (/(string|number)/.test(typeof opts)){
+            if (stringable(opts)){
                 el.innerHTML += opts;
                 return el;
             }
@@ -220,7 +240,7 @@
             }
             // or if 'children' is a string
             // set THAT to the innerHTML
-            else if (/(string|number)/.test(typeof children)){
+            else if (stringable(children)){
                 el.innerHTML += children;
                 children = null;
             }
@@ -438,7 +458,7 @@
 
         // allow use of only 2 arguments
         // with the HTML text being the second
-        if (/(string|number)/.test(typeof opts)){
+        if (stringable(opts)){
             el.innerHTML += (opts+'');
             return el;
         }
diff --git a/src/main/webapp/scripts/utils.js b/src/main/webapp/scripts/utils.js
index f3791d4b..877df576 100755
--- a/src/main/webapp/scripts/utils.js
+++ b/src/main/webapp/scripts/utils.js
@@ -401,7 +401,7 @@ function chosenInit(select, opts, width){
             placeholder_text_multiple: 'Select...',
             search_contains: true
         };
-    if (width) { defaults.width = (width + 'px').replace(/pxpx$/,'px') }
+    if (width) { defaults.width = (width + 'px').replace(/(px)*$/,'px') }
     $select.each(function(){
         var $this = $(this),
             dataChosenOpts = {};
diff --git a/src/main/webapp/scripts/xnat/xhr.js b/src/main/webapp/scripts/xnat/xhr.js
index fa9e30f6..9be48d60 100755
--- a/src/main/webapp/scripts/xnat/xhr.js
+++ b/src/main/webapp/scripts/xnat/xhr.js
@@ -167,7 +167,7 @@ var XNAT = getObject(XNAT||{}),
     // (url, data, opts, success) // url string, data object, config object, success callback
     xhr.request = xhr.req = xhr.ajax = function( /* url/opts, data/opts/callback, opts/callback, callback */ ){
 
-        var opts = {};
+        var opts = {}, $ajax;
 
         if (arguments[0] instanceof RequestOfType){
             opts = arguments[0];
@@ -234,10 +234,23 @@ var XNAT = getObject(XNAT||{}),
         // just do jQuery $.ajax() call
         if (!opts.yui || opts.jquery){
 
+            $ajax = $.ajax(opts);
+            
+            // save jQuery's fail method
+            $ajax.$fail = $ajax.fail;
+            
+            // remap the arguments for consistency with .done()
+            $ajax.fail = function(callback){
+                return $ajax.$fail(function(jqXHR, textStatus, errorThrown) {
+                    callback(errorThrown, textStatus, jqXHR);
+                    return $ajax;
+                });
+            };
+
             // reset XNAT.xhr.cache to false
             xhr.cache = false;
 
-            return $.ajax(opts);
+            return $ajax;
 
         }
 
-- 
GitLab