From 765bf4ef1107a16ddbf84dbccecde762dd3df4f6 Mon Sep 17 00:00:00 2001 From: "Mark M. Florida" <mflorida@gmail.com> Date: Tue, 17 May 2016 16:48:21 -0500 Subject: [PATCH] =?UTF-8?q?XNAT-4210:=20Site=20Setup=20page=20works=20as?= =?UTF-8?q?=20requested=20(initialized:true)=20is=20set=20before=20showing?= =?UTF-8?q?=20the=20confirmation=20dialog,=20also=20moved=20site=20setup?= =?UTF-8?q?=20functions=20to=20/scripts/xnat/app/siteSetup.js;=20XNAT-4192?= =?UTF-8?q?:=20session=20timer=20works=20on=20all=20pages=20and=20DOESN'T?= =?UTF-8?q?=20RELY=20ON=20YUI=20ANYMORE;=20more=20reliable=20lookup=20of?= =?UTF-8?q?=20Spawner=E2=84=A2=20widget=20methods;=20added=20hooks=20in=20?= =?UTF-8?q?form=20input=20widgets=20to=20lookup=20a=20value=20via=20AJAX?= =?UTF-8?q?=20using=20syntax:=20'value:=20=3F$=20/path/to/data=20|=20prop.?= =?UTF-8?q?name';?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/webapp/WEB-INF/tags/page/xnat.tag | 3 +- src/main/webapp/scripts/globals.js | 23 ++ .../lib/jquery-plugins/jquery.hasClasses.js | 76 +++++ src/main/webapp/scripts/timeLeft.js | 278 +++++++++++------- src/main/webapp/scripts/xmodal-v1/xmodal.js | 12 +- src/main/webapp/scripts/xnat/app/siteSetup.js | 171 +++++++++++ src/main/webapp/scripts/xnat/spawner.js | 24 +- src/main/webapp/scripts/xnat/ui/input.js | 20 -- src/main/webapp/scripts/xnat/ui/panel.js | 54 ++-- src/main/webapp/scripts/xnat/ui/templates.js | 137 +++++---- src/main/webapp/scripts/xnat/xhr.js | 21 +- src/main/webapp/setup/index.jsp | 9 +- 12 files changed, 600 insertions(+), 228 deletions(-) create mode 100644 src/main/webapp/scripts/lib/jquery-plugins/jquery.hasClasses.js create mode 100644 src/main/webapp/scripts/xnat/app/siteSetup.js diff --git a/src/main/webapp/WEB-INF/tags/page/xnat.tag b/src/main/webapp/WEB-INF/tags/page/xnat.tag index 368cdd1e..924ed878 100644 --- a/src/main/webapp/WEB-INF/tags/page/xnat.tag +++ b/src/main/webapp/WEB-INF/tags/page/xnat.tag @@ -67,6 +67,7 @@ <link rel="stylesheet" type="text/css" href="${SITE_ROOT}/scripts/lib/jquery-plugins/chosen/chosen.min.css?${versionString}"> <script src="${SITE_ROOT}/scripts/lib/jquery-plugins/chosen/chosen.jquery.min.js"></script> <script src="${SITE_ROOT}/scripts/lib/jquery-plugins/jquery.maskedinput.min.js"></script> + <script src="${SITE_ROOT}/scripts/lib/jquery-plugins/jquery.hasClasses.js"></script> <script src="${SITE_ROOT}/scripts/lib/jquery-plugins/jquery.dataAttr.js"></script> <script src="${SITE_ROOT}/scripts/lib/jquery-plugins/jquery.form.js"></script> @@ -209,7 +210,7 @@ <script src="${SITE_ROOT}/scripts/xnat/spawner.js"></script> - <%--<script src="${SITE_ROOT}/scripts/timeLeft.js"></script>--%> + <script src="${SITE_ROOT}/scripts/timeLeft.js"></script> ${headBottom} diff --git a/src/main/webapp/scripts/globals.js b/src/main/webapp/scripts/globals.js index 032b9005..be8df8d6 100644 --- a/src/main/webapp/scripts/globals.js +++ b/src/main/webapp/scripts/globals.js @@ -305,6 +305,29 @@ function setExtendedObject(obj, str, val){ return newObj; } +// loops over object string using dot notation: +// var myVal = lookupObjectValue(XNAT, 'data.siteConfig.siteId'); +// --> myVal == 'myXnatSiteId' +function lookupObjectValue(root, objStr){ + var val = ''; + if (!objStr) { + objStr = root; + root = window; + } + root = root || window; + objStr.toString().trim().split('.').forEach(function(part, i){ + part = part.trim(); + // start at the root object + if (i === 0) { + val = root[part] || ''; + } + else { + if (!val) return false; + val = val[part]; + } + }); + return val; +} // return the last item in an array-like object function getLast(arr){ diff --git a/src/main/webapp/scripts/lib/jquery-plugins/jquery.hasClasses.js b/src/main/webapp/scripts/lib/jquery-plugins/jquery.hasClasses.js new file mode 100644 index 00000000..d40c5408 --- /dev/null +++ b/src/main/webapp/scripts/lib/jquery-plugins/jquery.hasClasses.js @@ -0,0 +1,76 @@ +/*! + * jQuery.hasClasses plugin + * + * Check if an element has ALL classes + * or ANY of a number of classes + * + * usage: + * $('#el-id').hasClasses('foo || bar') <- has EITHER 'foo' or 'bar' + * $('#el-id').hasClasses('|| foo bar') <- has EITHER 'foo' or 'bar' + * $('#el-id').hasClasses('any: foo bar') <- has EITHER 'foo' or 'bar' + * + * $('#el-id').hasClasses('foo && bar') <- must have BOTH 'foo' AND 'bar' + * $('#el-id').hasClasses('&& foo bar') <- must have BOTH 'foo' AND 'bar' + * $('#el-id').hasClasses('all: foo bar') <- must have BOTH 'foo' AND 'bar' + * + * or call hasAnyClass() or hasAllClasses directly + * $('#el-id').hasAnyClass('foo bar') <- has EITHER 'foo' or 'bar' + * $('#el-id').hasAllClasses('foo bar') <- must have BOTH 'foo' AND 'bar' + * + */ + +(function($){ + + // wrap jQuery's .hasClass in a local function + function hasClassName($element, className) { + return $element.hasClass(className); + } + + // remove 'all:', 'any:', delimeters '||' and '&&' + // and any bogus commas or periods + function splitClassNames(classNames){ + return classNames.replace(/\.|,|any:|all:|\|\||&&/g,' ').trim().split(/\s+/); + } + + $.fn.hasAnyClass = function(classNames) { + var i = -1, + classes = splitClassNames(classNames), + len = classes.length; + while (++i < len) { + if (hasClassName(this, classes[i])) { + return true; + } + } + return false; + }; + + $.fn.hasAllClasses = function(classNames) { + var i = -1, + classes = splitClassNames(classNames), + len = classes.length, + matches = 0; + while (++i < len) { + if (hasClassName(this, classes[i])) { + matches++; + } + } + return matches === len; + }; + + $.fn.hasClasses = function( classNames ){ + var any = false; + // forgive stupidity if || is included in an 'all:' check + if (classNames.indexOf('all:') === 0){ + any = false; + } + else if (classNames.indexOf('any:') === 0){ + any = true; + } + else if (classNames.indexOf('||') > -1){ + any = true; + } + // 'all' is default check if no 'any' indicators are present + return any ? this.hasAnyClass(classNames) : this.hasAllClasses(classNames); + }; + +})(jQuery); diff --git a/src/main/webapp/scripts/timeLeft.js b/src/main/webapp/scripts/timeLeft.js index 0572060e..b32ce67f 100644 --- a/src/main/webapp/scripts/timeLeft.js +++ b/src/main/webapp/scripts/timeLeft.js @@ -5,7 +5,10 @@ if (typeof XNAT.app.timeout == 'undefined'){ XNAT.app.timeout={} } // wrap it in an IIFE and pass the 'XNAT.app.timeout' object as the argument. // Is this the best way to do this? I don't know. -(function(timeout){ +$(function(){ + + var timeout = XNAT.app.timeout; + /* * The SESSION_EXPIRATION_TIME cookie returned from the server is double quoted for some reason * so unquote it before parsing it out. @@ -22,6 +25,58 @@ if (typeof XNAT.app.timeout == 'undefined'){ XNAT.app.timeout={} } return ret; }; + + + timeout.warningDialog = (function() { + + // var timeLeft = timeout.settings.expirationTime.timeLeft; + + // dialog.find('.body.content > .inner').html("Your "+XNAT.app.siteId+" session will expire in:</br></br> " + timeLeft.hoursPart + " hours " + // + timeout.zeroPad(timeLeft.minutesPart) + " minutes " + +timeout.zeroPad(timeLeft.secondsPart) + ' seconds.' + + // '</br></br>Click "Renew" to reset session timer.'); + + var z = 99999; + + var dialog = xmodal.open({ + id: 'session-timeout-warning', + classes: 'keep static', + width: 320, + height: 200, + title: false, + content: 'Your ' + XNAT.app.siteId + ' session will expire in: <br><br>' + + '<span class="mono timeout-hours"></span> hours ' + + '<span class="mono timeout-minutes"></span> minutes ' + + '<span class="mono timeout-seconds"></span> seconds.' + + '</br></br>Click "Renew" to reset session timer.', + okLabel: 'Renew', + okAction: function(obj){ + timeout.handleOk(); + obj.$modal.hide(); + }, + okClose: false + }); + + dialog.$mask.hide().css('z-index', z-1); + dialog.$modal.hide().css('z-index', z); + + dialog.hours = dialog.$modal.find('span.timeout-hours'); + dialog.minutes = dialog.$modal.find('span.timeout-minutes'); + dialog.seconds = dialog.$modal.find('span.timeout-seconds'); + + return dialog; + + })(); + + timeout.warningDialog.show = function(){ + timeout.warningDialog.$mask.show(); + timeout.warningDialog.$modal.show(); + }; + + timeout.warningDialog.hide = function(){ + timeout.warningDialog.$mask.hide(); + timeout.warningDialog.$modal.hide(); + }; + /** * These cookies are available across tabs and windows: * - expirationTime : Stores a tuple that is (0|1, maximum idle time) @@ -142,19 +197,19 @@ if (typeof XNAT.app.timeout == 'undefined'){ XNAT.app.timeout={} } timeout.synchronizingCookies.hasRedirected.clear(); }; - timeout.disableButtons = function(dialog) { - var buttons = dialog.getButtons(); - for (var i = 0; i < buttons.length; i++) { - buttons[i].set('disabled', true); - } - }; - - timeout.enableButtons = function(dialog) { - var buttons = dialog.getButtons(); - for (var i = 0; i < buttons.length; i++) { - buttons[i].set('disabled', false); - } - }; + // timeout.disableButtons = function(dialog) { + // var buttons = dialog.getButtons(); + // for (var i = 0; i < buttons.length; i++) { + // buttons[i].set('disabled', true); + // } + // }; + // + // timeout.enableButtons = function(dialog) { + // var buttons = dialog.getButtons(); + // for (var i = 0; i < buttons.length; i++) { + // buttons[i].set('disabled', false); + // } + // }; /** * If a user double-clicks a button in YUI's SimpleDialog @@ -162,23 +217,23 @@ if (typeof XNAT.app.timeout == 'undefined'){ XNAT.app.timeout={} } * So we have to disable the buttons after each click and * enable them again when the dialog is shown. */ - timeout.hideWarningDialog = function(dialog) { - timeout.disableButtons(dialog); + timeout.hideWarningDialog = function() { + // timeout.disableButtons(dialog); timeout.synchronizingCookies.dialogDisplay.set("false"); - dialog.hide(); + timeout.warningDialog.hide(); }; - timeout.showWarningDialog = function(dialog) { - timeout.enableButtons(dialog); + timeout.showWarningDialog = function() { + // timeout.enableButtons(); timeout.synchronizingCookies.dialogDisplay.set("true"); - dialog.show(); + timeout.warningDialog.show(); }; /** * If the user wants to extend the session, hide the dialog and "touch" the server */ timeout.handleOk = function () { - timeout.hideWarningDialog(timeout.warningDialog); + // timeout.hideWarningDialog(); timeout.touchCallback.startTime = new Date().getTime(); XNAT.xhr.get(XNAT.url.restUrl('/xapi/siteConfig/buildInfo'), timeout.touchCallback); $('applet').css('visibility', 'visible'); @@ -189,8 +244,8 @@ if (typeof XNAT.app.timeout == 'undefined'){ XNAT.app.timeout={} } * to all tabs and ensure that they all close their dialogs. */ timeout.handleCancel = function () { - timeout.hideWarningDialog(timeout.warningDialog); - timeout.settings.warningDisplayedOnce = true; + timeout.hideWarningDialog(); + // timeout.settings.warningDisplayedOnce = true; // don't make it any more complicated than necessary - just show the thing $('applet').css('visibility', 'visible'); }; @@ -206,7 +261,7 @@ if (typeof XNAT.app.timeout == 'undefined'){ XNAT.app.timeout={} } } else { timeout.refreshSynchronizingCookies(); - timeout.settings.warningDisplayedOnce = false; + // timeout.settings.warningDisplayedOnce = false; } }, failure: function () { @@ -221,35 +276,35 @@ if (typeof XNAT.app.timeout == 'undefined'){ XNAT.app.timeout={} } return (y < 10) ? '0'+y : ''+y ; }; - /** - * The warning dialog - */ - timeout.warningDialog = new YAHOO.widget.SimpleDialog("session_timeout_dialog", { - width: "300px", - close: true, - fixedcenter: true, - // z-index is manhandled in xnat.css - // but we need to set it here as a base z-index for the other YUI dialogs - zIndex: 5001, - constraintoviewport: true, - modal: true, - icon: YAHOO.widget.SimpleDialog.ICON_WARN, - visible: true, - draggable: true, - hideAfterSubmit: true, - buttons: [ - { text: 'Renew', handler: timeout.handleOk, isDefault: true }, - { text: 'Close', handler: timeout.handleCancel } - ] - }); + // /** + // * The warning dialog + // */ + // timeout.warningDialog = new YAHOO.widget.SimpleDialog("session_timeout_dialog", { + // width: "300px", + // close: true, + // fixedcenter: true, + // // z-index is manhandled in xnat.css + // // but we need to set it here as a base z-index for the other YUI dialogs + // zIndex: 5001, + // constraintoviewport: true, + // modal: true, + // icon: YAHOO.widget.SimpleDialog.ICON_WARN, + // visible: true, + // draggable: true, + // hideAfterSubmit: true, + // buttons: [ + // { text: 'Renew', handler: timeout.handleOk, isDefault: true }, + // { text: 'Close', handler: timeout.handleCancel } + // ] + // }); timeout.initWarningDialog = function(dialog) { - dialog.manager = this; - dialog.render(document.body); - dialog.setHeader("Session Timeout Warning"); - dialog.setBody(""); - dialog.bringToTop(); - dialog.hide(); + // dialog.manager = this; + // dialog.render(document.body); + // dialog.setHeader("Session Timeout Warning"); + // dialog.setBody(""); + // dialog.bringToTop(); + // dialog.hide(); }; /** @@ -318,23 +373,23 @@ if (typeof XNAT.app.timeout == 'undefined'){ XNAT.app.timeout={} } * 4. The dialog should not be displayed and is not displayed. Hide the dialog anyway * in case another tab as been opened while the popup was open in this one. */ - timeout.updateMessageOrHide = function(dialog) { - if (timeout.synchronizingCookies.dialogDisplay.get() === "true" && timeout.settings.warningDisplayedOnce) { - var timeLeft = timeout.settings.expirationTime.timeLeft; - dialog.setBody("Your "+XNAT.app.siteId+" session will expire in:</br></br> " + timeLeft.hoursPart + " hours " - + timeout.zeroPad(timeLeft.minutesPart) + " minutes " + +timeout.zeroPad(timeLeft.secondsPart) + ' seconds.' + - '</br></br>Click "Renew" to reset session timer.'); - } - else if (timeout.synchronizingCookies.dialogDisplay.get() === "true" && !timeout.settings.warningDisplayedOnce) { - timeout.settings.warningDisplayedOnce = true; - timeout.showWarningDialog(dialog); - timeout.updateMessageOrHide(dialog); - } - else if (timeout.synchronizingCookies.dialogDisplay.get() === "false" && timeout.settings.warningDisplayedOnce) { - timeout.hideWarningDialog(dialog); + timeout.updateMessageOrHide = function() { + if (timeout.synchronizingCookies.dialogDisplay.get() === "true") { + // var timeLeft = timeout.settings.expirationTime.timeLeft; + // dialog.hours = timeLeft.hours + timeout.showWarningDialog(); + // timeout.updateMessageOrHide(); + // } + // else if (timeout.synchronizingCookies.dialogDisplay.get() === "true" && !timeout.settings.warningDisplayedOnce) { + // timeout.settings.warningDisplayedOnce = true; + // timeout.showWarningDialog(); + // timeout.updateMessageOrHide(); } - else if (timeout.synchronizingCookies.dialogDisplay.get() === "false" && !timeout.settings.warningDisplayedOnce) { - timeout.hideWarningDialog(dialog); + // else if (timeout.synchronizingCookies.dialogDisplay.get() === "false" && timeout.settings.warningDisplayedOnce) { + // timeout.hideWarningDialog(); + // } + else if (timeout.synchronizingCookies.dialogDisplay.get() === "false") { + timeout.hideWarningDialog(); } }; @@ -374,12 +429,23 @@ if (typeof XNAT.app.timeout == 'undefined'){ XNAT.app.timeout={} } */ timeout.sessionCountdown = function() { + var dialog = timeout.warningDialog; var timeLeft = timeout.settings.expirationTime.timeLeft; var $timeLeft = $('#timeLeft'); - $timeLeft.text(timeLeft.hoursPart + ":" + timeout.zeroPad(timeLeft.minutesPart) + ":" + timeout.zeroPad(timeLeft.secondsPart)); + var hours = timeLeft.hoursPart; + var mins = timeout.zeroPad(timeLeft.minutesPart); + var secs = timeout.zeroPad(timeLeft.secondsPart); + + $timeLeft.text(hours + ":" + mins + ":" + secs); + + // Update the text in the dialog too so it's always in synch + dialog.hours.text(hours); + dialog.minutes.text(mins); + dialog.seconds.text(secs); - if ((timeLeft.secondsLeft < timeout.settings.popupTime) && (!timeout.settings.warningDisplayedOnce)) { + if ((timeLeft.secondsLeft < timeout.settings.popupTime)) { + timeout.warningDialog.show(); timeout.synchronizingCookies.dialogDisplay.set("true"); } @@ -395,40 +461,46 @@ if (typeof XNAT.app.timeout == 'undefined'){ XNAT.app.timeout={} } } }; -})(XNAT.app.timeout); - -/** - * Initialize the synchronizing cookies and warning dialog and kick off the - * counter. - */ -XNAT.app.timeout.refreshSynchronizingCookies(); -XNAT.app.timeout.initWarningDialog(XNAT.app.timeout.warningDialog); -// only run the timer if *not* a guest user (if an authenticated user) -if ((!!Cookies.get('guest')) && (Cookies.get('guest') === 'false')) { - setInterval( - function(){ - XNAT.app.timeout.syncSessionExpirationCookieWithLocal(); - XNAT.app.timeout.updateMessageOrHide(XNAT.app.timeout.warningDialog); - XNAT.app.timeout.sessionCountdown(); - }, - XNAT.app.timeout.settings.timerInterval - ); -} - -(function(){ - - var hash = window.location.hash.toLowerCase(); - - // force debug mode to 'stick' if set explicitly 'on' or 'off' - var debugOn = /(debug=on|debug=true)/.test(hash.toLowerCase()); - var debugOff = /(debug=off|debug=false)/.test(hash.toLowerCase()); - - if (debugOn) { Cookies.set('debug','on') } - else if (debugOff) { Cookies.remove('debug') } - // if debugging, reset the timer every minute - if (debugOn || window.debug || isFalse(getQueryStringValue('timeout')) || /(on|true)/.test(Cookies.get('debug'))) { - setInterval(XNAT.app.timeout.handleOk, 60*1000); + /** + * Initialize the synchronizing cookies and warning dialog and kick off the + * counter. + */ + XNAT.app.timeout.refreshSynchronizingCookies(); + // XNAT.app.timeout.initWarningDialog(); + // only run the timer if *not* a guest user (if an authenticated user) + if ((!!Cookies.get('guest')) && (Cookies.get('guest') === 'false')) { + setInterval( + function(){ + XNAT.app.timeout.syncSessionExpirationCookieWithLocal(); + XNAT.app.timeout.updateMessageOrHide(); + XNAT.app.timeout.sessionCountdown(); + }, + XNAT.app.timeout.settings.timerInterval + ); } -})(); + // attach event handler to elements with 'renew-session' class + $('body').on('click', '#timeLeftRenew, .renew-session', function(){ + timeout.handleOk(); + }); + + // (function(){ + // + // var hash = window.location.hash.toLowerCase(); + // + // // force debug mode to 'stick' if set explicitly 'on' or 'off' + // var debugOn = /(debug=on|debug=true)/.test(hash.toLowerCase()); + // var debugOff = /(debug=off|debug=false)/.test(hash.toLowerCase()); + // + // if (debugOn) { Cookies.set('debug','on') } + // else if (debugOff) { Cookies.remove('debug') } + // + // // if debugging, reset the timer every 2 minutes + // if (debugOn || window.debug || isFalse(getQueryStringValue('timeout')) || /(on|true)/.test(Cookies.get('debug'))) { + // setInterval(XNAT.app.timeout.handleOk, 120*1000); + // } + // + // })(); + +}); diff --git a/src/main/webapp/scripts/xmodal-v1/xmodal.js b/src/main/webapp/scripts/xmodal-v1/xmodal.js index b1399e3c..11792284 100644 --- a/src/main/webapp/scripts/xmodal-v1/xmodal.js +++ b/src/main/webapp/scripts/xmodal-v1/xmodal.js @@ -1004,14 +1004,18 @@ if (typeof jQuery == 'undefined') { // fade out then remove the modal $modal.fadeOut(fade, function(){ - $(this).remove(); + // then if there's a mask that goes with it // get rid of that too if ($mask.length){ - $mask.fadeOut(fade/3, function(){ - $(this).remove(); - }); + $mask.hide(); + } + // don't remove 'static' modals we want to 'keep' + if (!$modal.hasClasses('keep || static')) { + $modal.remove(); + $mask.remove(); } + if (!$(xmodal.dialog.open).length) { $body.removeClass('open'); $html.removeClass('noscroll'); diff --git a/src/main/webapp/scripts/xnat/app/siteSetup.js b/src/main/webapp/scripts/xnat/app/siteSetup.js new file mode 100644 index 00000000..eb0119d3 --- /dev/null +++ b/src/main/webapp/scripts/xnat/app/siteSetup.js @@ -0,0 +1,171 @@ +/*! + * Spawn form input elements + */ + +var XNAT = getObject(XNAT); + +(function(factory){ + if (typeof define === 'function' && define.amd) { + define(factory); + } + else if (typeof exports === 'object') { + module.exports = factory(); + } + else { + return factory(); + } +}(function(){ + + var siteSetup, undefined; + + XNAT.app = getObject(XNAT.app || {}); + + XNAT.app.siteSetup = siteSetup = + getObject(XNAT.app.siteSetup || {}); + + var multiform = { + count: 0, + errors: 0 + }; + + siteSetup.multiform = multiform; + + // use app.siteSetup.form for Spawner 'kind' + + // creates a panel that submits all forms contained within + siteSetup.form = function(opts, callback){ + + opts = cloneObject(opts); + opts.element = opts.element || opts.config || {}; + + var inner = spawn('div.panel-body', opts.element), + + submitBtn = spawn('button', { + type: 'submit', + classes: 'btn submit save pull-right', + html: 'Save All' + }), + + resetBtn = spawn('button', { + type: 'button', + classes: 'btn btn-sm btn-default revert pull-right', + html: 'Discard Changes', + onclick: function(e){ + e.preventDefault(); + $(this).closest('form.multi-form').find('form').each(function(){ + $(this).triggerHandler('reload-data'); + }); + return false; + } + }), + + defaults = spawn('button', { + type: 'button', + classes: 'btn btn-link defaults pull-left', + html: 'Default Settings' + }), + + footer = [ + submitBtn, + ['span.pull-right', ' '], + resetBtn, + // defaults, + ['div.clear'] + ], + + multiForm = spawn('form', { + name: opts.name, + classes: 'xnat-form-panel multi-form panel panel-default', + method: opts.method || 'POST', + action: opts.action ? XNAT.url.rootUrl(opts.action) : '#!', + onsubmit: function(e){ + + e.preventDefault(); + var $forms = $(this).find('form'); + + var loader = xmodal.loading.open('#multi-save'); + + // reset error count on new submission + multiform.errors = 0; + + // how many child forms are there? + multiform.count = $forms.length; + + // submit ALL enclosed forms + $forms.each(function(){ + $(this).addClass('silent').trigger('submit'); + }); + + multiform.errors = $forms.filter('.error').length; + + if (multiform.errors) { + xmodal.closeAll(); + xmodal.message('Error', 'Please correct the highlighted errors and re-submit the form.'); + return false; + } + + XNAT.xhr.postJSON({ + url: XNAT.url.rootUrl('/xapi/siteConfig/batch'), + data: JSON.stringify({initialized:true}), + success: function(){ + xmodal.message({ + title: false, + esc: false, + content: 'Your XNAT site is ready to use. Click "OK" to continue to the home page.', + action: function(){ + // window.location.href = XNAT.url.rootUrl('/setup?init=true'); + window.location.href = XNAT.url.rootUrl('/'); + //$forms.each.triggerHandler('reload-data'); + } + }); + } + }).fail(function(e, txt, jQxhr){ + xmodal.loading.close(loader.$modal); + xmodal.message({ + title: 'Error', + content: [ + 'An error occurred during initialization', + e, + txt + ].join(': <br>') + }) + }); + + xmodal.loading.close(loader.$modal); + return false; + + } + }, [ + ['div.panel-heading', [ + ['h3.panel-title', opts.title || opts.label] + ]], + + // 'inner' is where the NEXT spawned item will render + inner, + + ['div.panel-footer', opts.footer || footer] + + ]); + + // add an id to the outer panel element if present + if (opts.id || opts.element.id) { + multiForm.id = opts.id || (opts.element.id + '-panel'); + } + + return { + target: inner, + element: multiForm, + spawned: multiForm, + get: function(){ + return multiForm + } + } + }; + siteSetup.form.init = siteSetup.form; + + // this script has loaded + siteSetup.loaded = true; + + return XNAT.app.siteSetup = siteSetup; + +})); diff --git a/src/main/webapp/scripts/xnat/spawner.js b/src/main/webapp/scripts/xnat/spawner.js index ccff298b..98a068fd 100644 --- a/src/main/webapp/scripts/xnat/spawner.js +++ b/src/main/webapp/scripts/xnat/spawner.js @@ -43,7 +43,7 @@ var XNAT = getObject(XNAT); forOwn(obj, function(item, prop){ - var kind, method, spawnedElement, $spawnedElement; + var kind, methodName, method, spawnedElement, $spawnedElement; // save the config properties in a new object prop = getObject(prop); @@ -78,26 +78,29 @@ var XNAT = getObject(XNAT); } } else { + // check for a matching XNAT.ui method to call: method = - // XNAT.ui.kind.init() - eval(NAMESPACE + '.' + kind + '.init') || - - // XNAT.ui.kind() - eval(NAMESPACE + '.' + kind) || // XNAT.kind.init() - eval('XNAT.' + kind + '.init') || + lookupObjectValue(XNAT, kind + '.init') || // XNAT.kind() - eval('XNAT.' + kind) || + lookupObjectValue(XNAT, kind) || + + // XNAT.ui.kind.init() + lookupObjectValue(NAMESPACE + '.' + kind + '.init') || + + // XNAT.ui.kind() + lookupObjectValue(NAMESPACE + '.' + kind) || // kind.init() - eval(kind + '.init') || + lookupObjectValue(kind + '.init') || // kind() - eval(kind); + lookupObjectValue(kind) || + null; // only spawn elements with defined methods if (isFunction(method)) { @@ -199,3 +202,4 @@ var XNAT = getObject(XNAT); return XNAT.spawner = spawner; })); + diff --git a/src/main/webapp/scripts/xnat/ui/input.js b/src/main/webapp/scripts/xnat/ui/input.js index bd52cf5f..0b9476a7 100644 --- a/src/main/webapp/scripts/xnat/ui/input.js +++ b/src/main/webapp/scripts/xnat/ui/input.js @@ -45,26 +45,6 @@ var XNAT = getObject(XNAT); return val; } - function lookupObjectValue(root, objStr){ - var val = ''; - if (!objStr) { - objStr = root; - root = window; - } - root = root || window; - objStr.toString().trim().split('.').forEach(function(part, i){ - part = part.trim(); - // start at the root object - if (i === 0) { - val = root[part] || {}; - } - else { - val = val[part]; - } - }); - return val; - } - // ======================================== // MAIN FUNCTION diff --git a/src/main/webapp/scripts/xnat/ui/panel.js b/src/main/webapp/scripts/xnat/ui/panel.js index 9c5eda3b..78eb3742 100644 --- a/src/main/webapp/scripts/xnat/ui/panel.js +++ b/src/main/webapp/scripts/xnat/ui/panel.js @@ -35,30 +35,7 @@ var XNAT = getObject(XNAT || {}); }); return obj.data; } - - // another way to do this without using eval() - // is to loop over object string using dot notation: - // var myVal = lookupObjectValue(XNAT, 'data.siteConfig.siteId'); - // --> myVal == 'myXnatSiteId' - function lookupObjectValue(root, objStr){ - var val = ''; - if (!objStr) { - objStr = root; - root = window; - } - root = root || window; - objStr.toString().trim().split('.').forEach(function(part, i){ - // start at the root object - if (i === 0) { - val = root[part] || {}; - } - else { - val = val[part]; - } - }); - return val; - } - + // string that indicates to look for a namespaced object value var doLookupString = '??'; @@ -274,14 +251,25 @@ var XNAT = getObject(XNAT || {}); opts.callback = opts.callback || callback || diddly; + // is this form part of a multiForm? + multiform.parent = $formPanel.closest('form.multi-form'); + + if (multiform.parent) { + multiform.count = $(multiform.parent).find('form').length + } + + multiform.errors = 0; + // intercept the form submit to do it via REST instead $formPanel.on('submit', function(e){ e.preventDefault(); - var $form = $(this), + var $form = $(this).removeClass('error'), errors = 0, - valid = true; + valid = true, + silent = $form.hasClass('silent'), + multiform = {}; $form.dataAttr('errors', 0); @@ -298,7 +286,8 @@ var XNAT = getObject(XNAT || {}); $form.dataAttr('errors', errors); if (!valid) { - if (!multiform.count) { + $form.addClass('error'); + if (!silent) { xmodal.message('Error','Please enter values for the required items and re-submit the form.'); } multiform.errors++; // keep track of errors for multi-form submission @@ -359,7 +348,7 @@ var XNAT = getObject(XNAT || {}); } // don't mess with modals for multiforms - if (!multiform.count){ + if (!silent){ xmodal.loading.close('#form-save'); xmodal.message('Data saved successfully.', { action: function(){ @@ -399,6 +388,7 @@ var XNAT = getObject(XNAT || {}); } } }; + panel.form.init = panel.form; // creates a panel that submits all forms contained within panel.multiForm = function(opts, callback){ @@ -462,9 +452,7 @@ var XNAT = getObject(XNAT || {}); // submit ALL enclosed forms $forms.each(function(){ - //if (!multiform.errors) { - $(this).trigger('submit'); - //} + $(this).addClass('silent').trigger('submit'); }); if (multiform.errors) { @@ -561,7 +549,8 @@ var XNAT = getObject(XNAT || {}); } }; - + panel.element.init = panel.element; + panel.subhead = function(opts){ opts = cloneObject(opts); opts.html = opts.html || opts.text || opts.label; @@ -736,6 +725,7 @@ var XNAT = getObject(XNAT || {}); panel.select.multi = function panelSelectMulti(opts){ return panel.select.menu(opts, true) }; + panel.select.multi.init = panel.select.multi; panel.selectMenu = function panelSelectMenu(opts){ opts = cloneObject(opts); diff --git a/src/main/webapp/scripts/xnat/ui/templates.js b/src/main/webapp/scripts/xnat/ui/templates.js index f54247df..490b0442 100644 --- a/src/main/webapp/scripts/xnat/ui/templates.js +++ b/src/main/webapp/scripts/xnat/ui/templates.js @@ -55,28 +55,21 @@ var XNAT = getObject(XNAT); el.value = val; return val; } + - // another way to do this without using eval() - // is to loop over object string using dot notation: - // var myVal = lookupObjectValue(XNAT, 'data.siteConfig.siteId'); - // --> myVal == 'myXnatSiteId' - function lookupObjectValue(root, objStr){ - var val = ''; - if (!objStr) { - objStr = root; - root = window; - } - root = root || window; - objStr.toString().trim().split('.').forEach(function(part, i){ - // start at the root object - if (i === 0) { - val = root[part] || {}; - } - else { - val = val[part]; + // retrieve value via REST and put it in the element + function ajaxValue(el, url, prop){ + var opts = { + url: XNAT.url.rootUrl(url), + success: function(data){ + if (prop && isPlainObject(data)) { + data = lookupObjectValue(data, prop.trim()); + } + el.value = data; + // $$(el).val(data); } - }); - return val; + }; + return $.get(opts); } @@ -198,53 +191,97 @@ var XNAT = getObject(XNAT); // or spawn a new one element = element || spawn('input', opts.element); + // cache a jQuery object + var $element = $(element); + // set the value of individual form elements // look up a namespaced object value if the value starts with '??' var doLookup = '??'; if (opts.value && opts.value.toString().indexOf(doLookup) === 0) { - element.value = lookupValue(opts.value.split(doLookup)[1]); + element.value = lookupValue(opts.value.split(doLookup)[1].trim()); } - if (opts.load) { - if (opts.load.lookup) { - lookupValue(element, opts.load.lookup); - } - 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 (opts.load.prop) { - opts.load.prop.split('.').forEach(function(part){ - data = data[part] || {}; - }); - // data = lookupObjectValue(opts.load.prop); - } - $(element).changeVal(data).dataAttr('value', data); - } - }) - } + // get value via REST/ajax if value starts with ?: + // value: ?$ /path/to/data | obj.prop.name + var ajaxPrefix = '?$'; + var ajaxUrl = ''; + var ajaxProp = ''; + if (opts.value && opts.value.toString().indexOf(ajaxPrefix) === 0) { + ajaxUrl = (opts.value.split(ajaxPrefix)[1]||'').split('|')[0]; + ajaxProp = opts.value.split('|')[1] || ''; + 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.trigger('change'); + + // add value to [data-value] attribute + // (except for textareas - that could get ugly + $element.not('textarea').dataAttr('value', element.value); + + var inner = [element]; + + var hiddenInput; + // check buttons if value is true if (/checkbox|radio/i.test(opts.type||'')) { + + // add a hidden input to capture the checkbox/radio value + hiddenInput = spawn('input', { + type: 'hidden', + name: element.name, + value: element.checked + }); + element.checked = /true|checked/i.test((opts.checked || element.value).toString()); + + // change the value of the hidden input onclick element.onclick = function(){ - this.value = this.checked.toString(); - console.log('clicked'); - } + hiddenInput.value = this.checked.toString(); + }; + + // change name of checkbox/radio to avoid conflicts + element.name = element.name + '-controller'; + + // and add a class for easy selection + addClassName(element, 'controller'); + + // and add the hidden input + inner.push(hiddenInput); + } + // add the description after the input + inner.push(['div.description', opts.description||opts.body||opts.html]); + return template.panelElement(opts, [ ['label.element-label|for='+element.id||opts.id, opts.label], - ['div.element-wrapper', [ - - element, - - ['div.description', opts.description||opts.body||opts.html] - ]] + ['div.element-wrapper', inner] ]); }; // ======================================== diff --git a/src/main/webapp/scripts/xnat/xhr.js b/src/main/webapp/scripts/xnat/xhr.js index 9be48d60..b285ff42 100755 --- a/src/main/webapp/scripts/xnat/xhr.js +++ b/src/main/webapp/scripts/xnat/xhr.js @@ -371,6 +371,14 @@ var XNAT = getObject(XNAT||{}), }; }); + // only do JSON.stringify on Arrays or Objects + function safeStringify(val){ + if ($.isArray(val) || $.isPlainObject(val)) { + return JSON.stringify(val); + } + return ''; + } + function processJSON(data, stringify){ var output = {}; $.each(data, function(prop, val){ @@ -384,7 +392,7 @@ var XNAT = getObject(XNAT||{}), } }); if (stringify) { - return JSON.stringify(output); + return safeStringify(output); } return output; } @@ -397,7 +405,7 @@ var XNAT = getObject(XNAT||{}), // XNAT.xhr.formToJSON(form, true) xhr.formToJSON = formToJSON; - $.fn.toJSON = function(stringify){ + $.fn.formToJSON = $.fn.toJSON = function(stringify){ return formToJSON(this, stringify); }; @@ -413,6 +421,11 @@ var XNAT = getObject(XNAT||{}), return $el; } + // can the value be reasonably used as a string? + function stringable(val){ + return /string|number|boolean/.test(typeof val); + } + // set form element values from an object map function setValues(form, dataObj){ // cache and check if form exists @@ -425,7 +438,7 @@ var XNAT = getObject(XNAT||{}), val = dataObj.join(', '); } else { - val = /string|number/i.test(typeof dataObj) ? dataObj+'' : dataObj[this.name] || ''; + val = stringable(dataObj) ? dataObj+'' : dataObj[this.name] || ''; } changeValue(this, val); }); @@ -434,7 +447,7 @@ var XNAT = getObject(XNAT||{}), var $textarea = $(this); $textarea.innerText = (function(){ var val = dataObj[this.name]; - return /string|number/i.test(typeof val) ? val+'' : JSON.stringify(val); + return stringable(val) ? val+'' : safeStringify(val); })(); }); return $form; diff --git a/src/main/webapp/setup/index.jsp b/src/main/webapp/setup/index.jsp index 15e1d5f4..daff6dd0 100644 --- a/src/main/webapp/setup/index.jsp +++ b/src/main/webapp/setup/index.jsp @@ -83,13 +83,14 @@ </div> <!-- /#site-setup-panels --> + <script src="<c:url value="/scripts/xnat/app/siteSetup.js"/>"></script> <script> - XNAT.app.setupComplete = function(){ - XNAT.xhr.form('#site-setup', {}); - }; - +// XNAT.app.setupComplete = function(){ +// XNAT.xhr.form('#site-setup', {}); +// }; +// XNAT.xhr.get({ url: XNAT.url.rootUrl('/page/admin/data/config/site-setup.yaml'), //url: XNAT.url.rootUrl('/xapi/spawner/resolve/siteAdmin/siteSetup'), -- GitLab