Changeset 4631


Ignore:
Timestamp:
Nov 21, 2017, 2:35:09 PM (6 years ago)
Author:
Nicklas Nordborg
Message:

References #1006: External data filtering and mapping wizard

Moving things from Reggie to Relax that makes the wizard interfae work. There is still a lot of functionality that we don't need right now.

Location:
extensions/net.sf.basedb.relax/trunk
Files:
9 added
3 edited

Legend:

Unmodified
Added
Removed
  • extensions/net.sf.basedb.relax/trunk/META-INF/servlets.xml

    r4350 r4631  
    55    <servlet-class>net.sf.basedb.relax.servlet.InstallServlet</servlet-class>
    66  </servlet>
     7  <servlet>
     8    <servlet-name>Session</servlet-name>
     9    <servlet-class>net.sf.basedb.relax.servlet.SessionServlet</servlet-class>
     10  </servlet>
     11  <servlet>
     12    <servlet-name>Export</servlet-name>
     13    <servlet-class>net.sf.basedb.relax.servlet.ExportServlet</servlet-class>
     14  </servlet>
    715</servlets>
  • extensions/net.sf.basedb.relax/trunk/resources/css/relax.css

    r4362 r4631  
    2727  display: none;
    2828}
     29
     30.wizard
     31{
     32  margin: 0em;
     33  box-sizing: border-box;
     34}
     35
     36.wizard .step
     37{
     38  position: relative;
     39  margin-bottom: 1em;
     40  display: none;
     41}
     42
     43.wizard.disabled
     44{
     45  pointer-events: none;
     46}
     47
     48.wizard.disabled .step, .wizard .step.disabled, .wizard.disabled .step-form
     49{
     50  filter: url(filters.svg#grayscale); /* Firfox, etc */
     51  filter: gray; /* IE */
     52  opacity: 0.75;
     53}
     54
     55.wizard .step-no
     56{
     57  position: absolute;
     58  top: 0;
     59  left: 0;
     60  bottom: 0;
     61  width: 2rem;
     62  text-align: center;
     63  font-size: 150%;
     64  font-weight: bold;
     65  padding-top: 2px;
     66  color: #FFFFFF;
     67  background-color: #224488;
     68}
     69
     70.wizard .step-title
     71{
     72  margin-left: 2rem;
     73  padding: 2px 4px 2px 4px;
     74  border-bottom-width: 1px;
     75  border-top-width: 1px;
     76  background-color: #E8E8E8;
     77  color: #224488;
     78  font-weight: bold;
     79}
     80
     81.wizard .step-title .helpicon
     82{
     83  margin-left: 1em;
     84}
     85
     86.wizard .step-content
     87{
     88  margin-left: 2rem;
     89  border-bottom-width: 1px;
     90}
     91
     92.wizard .step.disabled .step-no
     93{
     94  cursor: pointer;
     95}
     96
     97.wizard .step.disabled .step-content
     98{
     99  pointer-events: none;
     100}
     101
     102.wizard .step.disabled.auto-hide .step-no
     103{
     104  font-size: 100%;
     105}
     106
     107.wizard .step.disabled.auto-hide .step-content
     108{
     109  display: none;
     110}
     111
     112
     113.wizard .step-form
     114{
     115  width: 100%;
     116  border-collapse: collapse;
     117}
     118
     119
     120.wizard .step-form > tbody > tr > td
     121{
     122  padding: 1px 2px 1px 4px;
     123  vertical-align: baseline;
     124}
     125
     126.wizard .step-form > tbody > tr.align-top > td
     127{
     128  vertical-align: top;
     129}
     130
     131.wizard .step-form > tbody > tr.align-middle > td
     132{
     133  vertical-align: middle;
     134}
     135
     136.wizard .step-form .prompt
     137{
     138  width: 18em;
     139  min-width: 18em;
     140  font-weight: bold;
     141  white-space: nowrap;
     142}
     143
     144.wizard .step-form .subprompt
     145{
     146  width: 18em;
     147  min-width: 18em;
     148  text-align: right;
     149  font-weight: normal;
     150  white-space: nowrap;
     151}
     152
     153.wizard .step-form .section-header td:not(.help)
     154{
     155  background-color: #E8E8E8;
     156  white-space: nowrap;
     157}
     158
     159.wizard .step-form .section-header td
     160{
     161  padding: 3px 2px 3px 4px;
     162}
     163
     164.wizard .step-form .section-header td:first-child
     165{
     166  font-weight: bold;
     167}
     168
     169.wizard .step-form .section-header td:first-child:before
     170{
     171  content: '› ';
     172  color: #999999;
     173}
     174
     175.wizard .step-form .info-section tr:not(.section-header) td:not(.help)
     176{
     177  background-color: rgba(232, 232, 232, 0.5);
     178}
     179
     180.wizard .step-form .info-section .prompt
     181{
     182  font-weight: normal;
     183  padding-left: 2em;
     184  text-align: left;
     185}
     186
     187.wizard .step-form .info-section .info
     188{
     189  font-style: italic;
     190}
     191
     192.wizard .step-form .info-section .item-description
     193{
     194  white-space: pre-wrap;
     195  height: 6em;
     196  max-height: 6em;
     197  overflow: auto;
     198}
     199
     200.wizard .step-form .input
     201{
     202  width: 36em;
     203}
     204
     205.wizard .step-form .input select, .wizard .step-form .input input[type="text"], .wizard .step-form .input textarea
     206{
     207  width: 95%;
     208}
     209
     210.wizard .step-form .status
     211{
     212  width: 20px;
     213  background-repeat: no-repeat;
     214  background-position: 50% 50%;
     215}
     216
     217.wizard .step-form .align-top .status
     218{
     219  background-position: 50% 2px;
     220  transform-origin: 50% 10px;
     221}
     222
     223.wizard .step-form .status.invalid
     224{
     225  background-image: url('../images/error.png');
     226}
     227
     228.wizard.flash-error .step-form  .status.invalid
     229{
     230  animation: enlarge 1s infinite;
     231}
     232
     233.wizard.flash-error .step-form tr.invalid, .wizard.flash-error .step-form td.invalid
     234{
     235  animation: flash 1s infinite;
     236}
     237
     238@keyframes flash
     239{
     240  0% { background-color: inherit; }
     241  50% { background-color: #FFCCCC; }
     242  100% { background-color: inherit; }
     243}
     244
     245@keyframes enlarge
     246{
     247  0% { transform: scale(1,1); }
     248  50% { transform: scale(2,2); }
     249  100% { transform: scale(1,1); }
     250}
     251
     252.wizard .step-form .status.warning
     253{
     254  background-image: url('../images/warning.png');
     255}
     256.wizard .step-form .status.valid
     257{
     258  background-image: url('../images/ok.png');
     259}
     260
     261.wizard .step-form .status.checking
     262{
     263  background-image: url('../images/loading-small.gif');
     264}
     265
     266.wizard .step-form .status.flag
     267{
     268  background-image: url('../images/flag.png');
     269}
     270
     271.wizard .step-form .status.new
     272{
     273  background-image: url('../images/new.png');
     274}
     275
     276.wizard .step-form .help
     277{
     278  background-color: rgba(208, 208, 208, 0.5);
     279  font-style: italic;
     280}
     281
     282.wizard .step-form .help .message
     283{
     284  color: #C80000;
     285  font-weight: bold;
     286  padding-right: 6px;
     287  display: none;
     288}
     289
     290.wizard .navigation
     291{
     292  margin-left: 2.5em;
     293  margin-top: 1em;
     294  margin-bottom: 5em;
     295  border-spacing: 4px 0;
     296}
     297
     298.wizard .navigation #gorestart
     299{
     300  pointer-events: all !important;
     301}
     302
     303.navigation .basicbutton
     304{
     305  display: none;
     306}
     307
     308.navigation .message
     309{
     310  color: #CC0000;
     311  font-weight: bold;
     312  padding-left: 0.5em;
     313}
     314
     315.progress-bar
     316{
     317  display: none;
     318  margin-top: 0.0em;
     319  margin-left: 5em;
     320  width: 200px;
     321  white-space: nowrap;
     322}
     323
     324.progress-bar::after
     325{
     326  content: attr(value);
     327  vertical-align: top;
     328  padding-left: 0.5em;
     329}
     330
     331.progress-bar span
     332{
     333  display: inline-block;
     334  height: 1.4em;
     335}
     336
     337.success li
     338{
     339  background-position: 0 50%;
     340  background-repeat: no-repeat;
     341  padding-left: 20px;
     342  list-style-type: none;
     343  line-height: 1.75;
     344}
     345
     346.success li.ok
     347{
     348  background-image: url('../images/ok.png');
     349}
     350
     351.success li.warning
     352{
     353  background-image: url('../images/warning.png');
     354}
     355
     356.success li.error
     357{
     358  background-image: url('../images/error.png');
     359  background-color: transparent !important;
     360  color: #990000 !important;
     361}
  • extensions/net.sf.basedb.relax/trunk/resources/relax.js

    r4610 r4631  
    1818  var numLiveAsyncRequests = 0;
    1919  var hasFatalError = false;
     20  var currentStep = 1;
     21  var hasSentRegistration = false;
     22  var forceConfirm = false;
     23  var noConfirmOnFirstStep = true;
    2024 
    2125  /**
     
    103107    }
    104108  }
     109 
     110  wizard.isValid = function(element)
     111  {
     112    return Data.int(element, 'valid', 1);
     113  }
     114
     115  wizard.initFileSelectionField = function(element)
     116  {
     117    element = Doc.element(element);
     118    Events.addEventHandler(element, 'change', wizard.fileChanged);
     119    Events.sendChangeEvent(element);
     120  }
     121 
     122  wizard.fileChanged = function(event)
     123  {
     124    var target = event.currentTarget;
     125   
     126    var isValid = false;
     127    var isWarning = false;
     128   
     129    // Reset current status
     130    wizard.setInputStatus(target);
     131   
     132    var path = target.value;
     133    if (path == '')
     134    {
     135      // No file, is this an error or a warning?
     136      var optional = target.className.indexOf('required') == -1;
     137      wizard.setInputStatus(target, optional ? 'warning' : 'invalid', 'Missing');
     138      isValid = optional;
     139      isWarning = optional;
     140    }
     141    else
     142    {
     143      Wizard.setInputStatus(target, 'valid');
     144      isValid = true;
     145    }
     146   
     147    Data.set(target, 'valid', isValid ? 1 : 0);
     148    Data.set(target, 'warning', isWarning ? 1 : 0);
     149   
     150    if (!isValid && target.focus) target.focus();
     151  }
     152 
     153  /**
     154    Change display status for an input field.
     155    @param prefix ID prefix to locate tags
     156    @param clazz The message clazz (valid, invalid, warning) or empty
     157    @param message The message (may be empty)
     158  */
     159  wizard.setInputStatus = function(prefix, clazz, message)
     160  {
     161    if (prefix.id) prefix = prefix.id;
     162   
     163    var statusTag = Doc.element(prefix + '.status');
     164   
     165    if (statusTag)
     166    {
     167      statusTag.className = 'status ' + (clazz || '');
     168      var trTag = statusTag.parentNode;
     169      var trClass = Data.get(trTag, 'original-class');
     170      if (!trClass)
     171      {
     172        trClass = trTag.className || 'foo';
     173        Data.set(trTag, 'original-class', trClass);
     174      }
     175      trTag.className = trClass + ' ' + (clazz || '');
     176    }
     177   
     178    var msgTag = Doc.element(prefix + '.message');
     179    if (msgTag)
     180    {
     181      if (message)
     182      {
     183        msgTag.innerHTML = message;
     184        Doc.show(msgTag, Data.get(msgTag, 'display', 'inline'));
     185      }
     186      else
     187      {
     188        msgTag.innerHTML = message;
     189        Doc.hide(msgTag);
     190      }
     191    }
     192    else if (statusTag)
     193    {
     194      statusTag.title = message || '';
     195    }
     196  }
     197 
     198  /**
     199    Event handler for input fields, that go to the
     200    next wizard step if TAB or ENTER key is pressed.
     201    A short delay is used to allow other event handler
     202    to run first (eg. for validation).
     203  */
     204  wizard.goNextOnTabOrEnter = function(event)
     205  {
     206    if (event.keyCode == 9 || event.keyCode == 13)
     207    {
     208      setTimeout(function() { wizard.goNext(true) }, 200);
     209    }
     210  }
     211 
     212  /**
     213    Event handler for input fields, that go to the
     214    next wizard step if TAB key is pressed.
     215    A short delay is used to allow other event handler
     216    to run first (eg. for validation).
     217  */
     218  wizard.goNextOnTab = function(event)
     219  {
     220    if (event.keyCode == 9)
     221    {
     222      setTimeout(function() { wizard.goNext(true) }, 200);
     223    }
     224  }
     225
     226  /**
     227    Event handler for input fields, that changes
     228    focus to the next element. The next element
     229    should be defined by 'data-next-focus' attribute.
     230    It is recommended that this is consisistent with TAB.
     231  */
     232  wizard.focusOnEnter = function(event)
     233  {
     234    if (event.keyCode == 13)
     235    {
     236      var nextFocus = Doc.element(Data.get(event.currentTarget, 'next-focus'));
     237      if (nextFocus && nextFocus.focus) nextFocus.focus();
     238    }
     239  }
     240 
     241  // Event handler for the 'Next' navigation button
     242  wizard.goNextOnClick = function(event)
     243  {
     244    wizard.goNext(false);
     245  }
     246 
     247  /**
     248    Go to the next step in the wizard. This method will first
     249    submit 'wizard-validate' event to the current step. If at
     250    least one event handler calls 'Event.preventDefault()' the
     251    processing is aborted.
     252   
     253    Otherwise, the current step is disabled and the 'wizard-initialize'
     254    event is sent to the next step. The event handler is responsible
     255    for calling 'Wizard.setCurrentStep' to display the next step.
     256  */
     257  wizard.goNext = function(auto)
     258  {
     259    var verify = Doc.element('verifyGoNext');
     260    if (verify && !verify.checked) return;
     261   
     262    var details = { 'auto': auto };
     263   
     264    // Send 'wizard-validate' event to current step
     265    var step = Doc.element('step-'+currentStep);
     266    var evt = document.createEvent('CustomEvent');
     267    evt.initCustomEvent('wizard-validate', true, true, details);
     268    if (!step.dispatchEvent(evt))
     269    {
     270      wizard.notifyError();
     271      return;
     272    }
     273 
     274    // Hide all navigation buttons and disable form controls in current step
     275    internal.hideButtons('navigation');
     276    internal.disableStep(step);
     277   
     278    // Send 'wizard-initialize' to the next step
     279    var nextStep = Doc.element('step-'+(currentStep+1));
     280    var evt = document.createEvent('CustomEvent');
     281    evt.initCustomEvent('wizard-initialize', true, true, details);
     282    nextStep.dispatchEvent(evt);
     283  }
     284 
     285  wizard.notifyError = function()
     286  {
     287    Doc.addClass('wizard', 'flash-error');
     288    setTimeout(internal.stopFlashing, 2000);
     289  }
     290 
     291  internal.stopFlashing = function()
     292  {
     293    Doc.removeClass('wizard', 'flash-error');
     294  }
     295
     296  /**
     297    Finalize the registration of the wizard. This method will
     298    first submit 'wizard-validate' to the current step. If at
     299    least one event handler calls 'Event.preventDefault()' the
     300    processing is aborted.
     301   
     302    Otherwise, the current step is disabled and the 'wizard-submit'
     303    event is sent to the 'wizard'. The event handler is responsible
     304    for finalizing the registration and displaying a proper
     305    message, for example, by calling Wizard.showFinalMessage()
     306   
     307    The 'data' is passed on to the 'wizard-validate' and
     308    'wizard-submit' events as the Event.details object.
     309  */
     310  wizard.goRegister = function(data)
     311  {
     312    // Check if verification is enabled
     313    var verify = Doc.element('verifyGoNext');
     314    if (verify && !verify.checked) return;
     315   
     316    // Send 'wizard-validate' event to current step
     317    var step = Doc.element('step-'+currentStep);
     318    var evt = document.createEvent('CustomEvent');
     319    evt.initCustomEvent('wizard-validate', true, true, data);
     320    if (!step.dispatchEvent(evt))
     321    {
     322      wizard.notifyError();
     323      return;
     324    }
     325 
     326    internal.hideButtons('navigation');
     327    internal.disableStep(step);
     328   
     329    // Send 'wizard-submit' to the next step
     330    var wiz = Doc.element('wizard');
     331    hasSentRegistration = true;
     332    var evt = document.createEvent('CustomEvent');
     333    evt.initCustomEvent('wizard-submit', true, true, data);
     334    wiz.dispatchEvent(evt);
     335  }
     336 
     337  wizard.setNoConfirmOnFirstStep = function(cf)
     338  {
     339    noConfirmOnFirstStep = cf;
     340  }
     341 
     342  // Ask the user for confirmation and then restart the wizard
     343  wizard.cancelWizard = function()
     344  {
     345    forceConfirm = true;
     346    wizard.restartWizard();
     347  }
     348 
     349  // Restart the wizard without asking the user for confirmation
     350  wizard.restartWizard = function()
     351  {
     352    var url = location.href;
     353    if (url.indexOf('&restart=1') == -1) url += '&restart=1';
     354    location.href = url;
     355  }
     356 
     357  wizard.setCurrentStep = function(stepNo)
     358  {
     359    currentStep = stepNo;
     360    internal.enableStep(Doc.element('step-'+currentStep));
     361  }
     362
     363  internal.disableStep = function(step)
     364  {
     365    step = Doc.element(step);
     366    internal.disableAllFormElements(step);
     367    Doc.addClass(step, 'disabled');
     368    Doc.hide('gonext-message');
     369    var stepNo = step.getElementsByClassName('step-no')[0];
     370    Events.addEventHandler(stepNo, 'click', internal.togglePastStep, {'step-id': step.id});
     371    stepNo.title = 'Show/hide this section';
     372  }
     373 
     374  internal.enableStep = function(step)
     375  {
     376    step = Doc.element(step);
     377    Doc.removeClass(step, 'disabled');
     378    Doc.show(step);
     379  }
     380 
     381  // Toggle visibility of a past step
     382  // The step is hidden if the 'auto-hide' class is present
     383  internal.togglePastStep = function(event)
     384  {
     385    var step = Data.get(event.currentTarget, 'step-id');
     386    Doc.addOrRemoveClass(step, 'auto-hide');
     387  }
    105388
    106389 
     
    156439  {
    157440    wizard.setWizardStatus('loading', msg  || 'Working, please wait...');
    158   }
     441    if (progress)
     442    {
     443      var progressBar = Doc.element('wizard-progress');
     444      progressBar.classList.add('progress-bar');
     445
     446      // Get/create child elements
     447      var done = Doc.element('wizard-progress-done');
     448      if (!done)
     449      {
     450        done = document.createElement('span');
     451        done.id = 'wizard-progress-done';
     452        done.className = 'progress-bar-done bg-filled-100 topborder bottomborder leftborder';
     453        progressBar.appendChild(done);
     454      }
     455      var remain = Doc.element('wizard-progress-remain');
     456      if (!remain)
     457      {
     458        remain = document.createElement('span');
     459        remain.id = 'wizard-progress-remain';
     460        remain.className = 'progress-bar-remain topborder bottomborder rightborder';
     461        progressBar.appendChild(remain);
     462      }
     463       
     464      progressBar.setAttribute('value', '0%');
     465      done.style.width = '0%';
     466      remain.style.width = '100%';
     467      Doc.show(progressBar);
     468     
     469      // Initial request for updating the progress bar
     470      progressTimer = setTimeout(internal.requestProgressUpdate, 1500, progress);
     471    }
     472  }
     473 
     474  /**
     475    Make a request for the progress information. The 'progress'
     476    parameter is the name of a progress bar.
     477  */
     478  internal.requestProgressUpdate = function(progress)
     479  {
     480    if (hasFatalError) return;
     481    var url = '../Session.servlet?ID='+App.getSessionId();
     482    url += '&cmd=GetProgress';
     483    url += '&name='+encodeURIComponent(progress);
     484   
     485    var request = Ajax.getXmlHttpRequest();
     486    request.open("GET", url, true);
     487    Ajax.setReadyStateHandler(request, internal.onProgressReport, internal.onProgressReport);
     488    request.send(null);
     489  }
     490 
     491  /**
     492    Callback when receiving a progress update.
     493  */
     494  internal.onProgressReport = function(request)
     495  {
     496    if (hasFatalError) return;
     497    if (debug) App.debug(Strings.encodeTags(request.responseText));
     498    var response;
     499    var error = false;
     500    try
     501    {
     502      response = JSON.parse(request.responseText);
     503      if (response.status != 'ok')
     504      {
     505        error = response.message || response.stacktrace || 'Unexpected error';
     506      }
     507    }
     508    catch (ex)
     509    {
     510      error = ex;
     511    }
     512   
     513    if (error)
     514    {
     515      return;
     516    }
     517 
     518    var progress = response.progress;
     519    var progressBar = Doc.element('wizard-progress');
     520 
     521    if (!progress)
     522    {
     523      // Give up if no progress information
     524      Doc.hide(progressBar);
     525      return;
     526    }
     527   
     528    var done = Doc.element('wizard-progress-done');
     529    var remain = Doc.element('wizard-progress-remain');
     530 
     531    progressBar.setAttribute('value', progress.percent+'%');
     532    done.style.width = progress.percent+'%';
     533    remain.style.width = (100-progress.percent)+'%';
     534   
     535    if (progress.message)
     536    {
     537      Doc.element('wizard-status').innerHTML = Strings.encodeTags(progress.message);
     538    }
     539   
     540    if (progress.percent < 100)
     541    {
     542      // Continue to request updates until we 100% has been reached
     543      progressTimer = setTimeout(internal.requestProgressUpdate, 1500, progress.name);
     544    }
     545    else
     546    {
     547      progressTimer = null;
     548      Doc.hide(progressBar);
     549    }
     550  }
     551
    159552 
    160553  wizard.showStatusMessage = function(msg)
     
    172565    Doc.hide('wizard-progress');
    173566  }
    174    
     567 
     568  wizard.showGoNextConfirmation = function(verify, msg)
     569  {
     570    var check = '';
     571    if (verify)
     572    {
     573      check = '<input type="checkbox" name="verifyGoNext" id="verifyGoNext">';
     574      Doc.addClass('gonext', 'disabled');
     575      Doc.addClass('goregister', 'disabled');
     576    }
     577   
     578    var gonext = Doc.element('gonext-message');
     579    gonext.innerHTML = check + msg;
     580    Doc.show(gonext);
     581   
     582    if (verify)
     583    {
     584      Events.addEventHandler('verifyGoNext', 'click', internal.verifyInternalOnClick);
     585    }
     586  }
     587 
     588  wizard.hideGoNextConfirmation = function()
     589  {
     590    var gonext = Doc.element('gonext-message');
     591    gonext.innerHTML = '';
     592    Doc.hide(gonext);
     593    Doc.removeClass('gonext', 'disabled');
     594    Doc.removeClass('goregister', 'disabled');
     595  }
     596 
     597  internal.verifyInternalOnClick = function(event)
     598  {
     599    var frm = document.forms['reggie'];
     600    var checked = event.currentTarget.checked;
     601    Doc.addOrRemoveClass('goregister', 'disabled', !checked);
     602    Doc.addOrRemoveClass('gonext', 'disabled', !checked);
     603  }
     604
     605  /**
     606    Show final registration messages in a list.
     607  */
     608  wizard.showFinalMessage = function(messages)
     609  {
     610    var msg = internal.generateMessageList(messages);
     611    wizard.setWizardStatus('success', msg.html);
     612    if (progressTimer) clearTimeout(progressTimer);
     613    return { 'errors': msg.errors, 'warnings': msg.warnings };
     614  }
     615
     616  internal.generateMessageList = function(messages)
     617  {
     618    var msg = '<ul>';
     619    var numWarnings = 0;
     620    var numErrors = 0;
     621    for (var i = 0; i < messages.length; i++)
     622    {
     623      var msgLine = messages[i];
     624      if (msgLine.indexOf('[Warning]') >= 0)
     625      {
     626        msg += '<li class="warning">' + Strings.encodeTags(msgLine.replace('[Warning]', ''));
     627        numWarnings++;
     628      }
     629      else if (msgLine.indexOf('[Error]') >= 0)
     630      {
     631        msg += '<li class="error">' + Strings.encodeTags(msgLine.replace('[Error]', ''));
     632        numErrors++;
     633      }
     634      else
     635      {
     636        msg += '<li class="ok">' + Strings.encodeTags(msgLine);
     637      }
     638    }
     639    msg += '</ul>';
     640    return { 'html': msg, 'errors': numErrors, 'warnings': numWarnings };
     641  }
     642
    175643  /**
    176644    Initialize the wizard:
Note: See TracChangeset for help on using the changeset viewer.