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, '&').replace(/</g, '<').replace(/>/g, '>'); + 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', ' '], - ['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