changeset 2119:e0517f25919e

Improved robustness of Orthanc Explorer wrt. query/retrieve (maybe fix issue #24)
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sun, 06 Nov 2016 11:39:04 +0100
parents 9cf176bc21ad
children 4b02ec79728a
files NEWS OrthancExplorer/explorer.html OrthancExplorer/explorer.js OrthancExplorer/query-retrieve.js
diffstat 4 files changed, 417 insertions(+), 317 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Nov 02 12:16:03 2016 +0100
+++ b/NEWS	Sun Nov 06 11:39:04 2016 +0100
@@ -22,6 +22,7 @@
 Maintenance
 -----------
 
+* Improved robustness of Orthanc Explorer wrt. query/retrieve (maybe fix issue 24)
 * Fix serious performance issue with C-FIND
 * Fix extraction of the symbolic name of the private tags
 * Performance warning if runtime debug assertions are turned on
--- a/OrthancExplorer/explorer.html	Wed Nov 02 12:16:03 2016 +0100
+++ b/OrthancExplorer/explorer.html	Sun Nov 06 11:39:04 2016 +0100
@@ -302,7 +302,7 @@
 
     <div data-role="page" id="query-retrieve" >
       <div data-role="header" >
-	<h1><span class="orthanc-name"></span>DICOM Query/Retrieve (1/3)</h1>
+	<h1><span class="orthanc-name"></span>DICOM Query/Retrieve (1/4)</h1>
         <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a>
       </div>
       <div data-role="content">
@@ -368,7 +368,7 @@
 
     <div data-role="page" id="query-retrieve-2" >
       <div data-role="header" >
-	<h1><span class="orthanc-name"></span>DICOM Query/Retrieve (2/3)</h1>
+	<h1><span class="orthanc-name"></span>DICOM Query/Retrieve (2/4)</h1>
         <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a>
         <a href="#query-retrieve" data-icon="search" class="ui-btn-right" data-direction="reverse">Query/Retrieve</a>
       </div>
@@ -381,17 +381,43 @@
 
     <div data-role="page" id="query-retrieve-3" >
       <div data-role="header" >
-	<h1><span class="orthanc-name"></span>DICOM Query/Retrieve (3/3)</h1>
+	<h1><span class="orthanc-name"></span>DICOM Query/Retrieve (3/4)</h1>
         <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a>
         <a href="#query-retrieve" data-icon="search" class="ui-btn-right" data-direction="reverse">Query/Retrieve</a>
       </div>
       <div data-role="content">
-        <ul data-role="listview" data-inset="true" data-filter="true" data-split-icon="arrow-d" data-split-theme="b">
+        <ul data-role="listview" data-inset="true" data-filter="true">
         </ul>
       </div>
     </div>
 
 
+    <div data-role="page" id="query-retrieve-4" >
+      <div data-role="header" >
+	<h1><span class="orthanc-name"></span>DICOM Query/Retrieve (4/4)</h1>
+        <a href="#find-patients" data-icon="home" class="ui-btn-left" data-direction="reverse">Patients</a>
+        <a href="#query-retrieve" data-icon="search" class="ui-btn-right" data-direction="reverse">Query/Retrieve</a>
+      </div>
+
+      <div data-role="content">
+        <form data-ajax="false" id="retrieve-form">
+          <div data-role="fieldcontain">
+	    <label for="retrieve-target">Target AET:</label>
+            <input type="text" name="retrieve-target" id="retrieve-target"></input>
+	  </div>
+
+          <fieldset class="ui-grid-b">
+	    <div class="ui-block-a"></div>
+	    <div class="ui-block-b">
+              <button id="retrieve-submit" type="submit" data-theme="b">Retrieve</button>
+            </div>
+	    <div class="ui-block-c"></div>
+	  </fieldset>
+        </form>
+      </div>
+    </div>
+
+
     <div id="peer-store" style="display:none;" class="ui-body-c">
       <p align="center"><b>Sending to Orthanc peer...</b></p>
       <p><img src="libs/images/ajax-loader.gif" alt="" /></p>
--- a/OrthancExplorer/explorer.js	Wed Nov 02 12:16:03 2016 +0100
+++ b/OrthancExplorer/explorer.js	Sun Nov 06 11:39:04 2016 +0100
@@ -30,6 +30,36 @@
 };
 
 
+function DeepCopy(obj)
+{
+  return jQuery.extend(true, {}, obj);
+}
+
+
+function ChangePage(page, options)
+{
+  var first = true;
+  if (options) {
+    for (var key in options) {
+      var value = options[key];
+      if (first) {
+        page += '?';
+        first = false;
+      } else {
+        page += '&';
+      }
+      
+      page += key + '=' + value;
+    }
+  }
+
+  window.location.replace('explorer.html#' + page);
+  /*$.mobile.changePage('#' + page, {
+    changeHash: true
+  });*/
+}
+
+
 function Refresh()
 {
   if (currentPage == 'patient')
@@ -369,8 +399,10 @@
 function RefreshPatient()
 {
   if ($.mobile.pageData) {
-    GetResource('/patients/' + $.mobile.pageData.uuid, function(patient) {
-      GetResource('/patients/' + $.mobile.pageData.uuid + '/studies', function(studies) {
+    var pageData = DeepCopy($.mobile.pageData);
+
+    GetResource('/patients/' + pageData.uuid, function(patient) {
+      GetResource('/patients/' + pageData.uuid + '/studies', function(studies) {
         SortOnDicomTag(studies, 'StudyDate', false, true);
 
         $('#patient-info li').remove();
@@ -399,7 +431,7 @@
 
         // Check whether this patient is protected
         $.ajax({
-          url: '../patients/' + $.mobile.pageData.uuid + '/protected',
+          url: '../patients/' + pageData.uuid + '/protected',
           type: 'GET',
           dataType: 'text',
           async: false,
@@ -411,7 +443,7 @@
         });
 
         currentPage = 'patient';
-        currentUuid = $.mobile.pageData.uuid;
+        currentUuid = pageData.uuid;
       });
     });
   }
@@ -421,9 +453,11 @@
 function RefreshStudy()
 {
   if ($.mobile.pageData) {
-    GetResource('/studies/' + $.mobile.pageData.uuid, function(study) {
+    var pageData = DeepCopy($.mobile.pageData);
+
+    GetResource('/studies/' + pageData.uuid, function(study) {
       GetResource('/patients/' + study.ParentPatient, function(patient) {
-        GetResource('/studies/' + $.mobile.pageData.uuid + '/series', function(series) {
+        GetResource('/studies/' + pageData.uuid + '/series', function(series) {
           SortOnDicomTag(series, 'SeriesDate', false, true);
 
           $('#study .patient-link').attr('href', '#patient?uuid=' + patient.ID);
@@ -451,7 +485,7 @@
           target.listview('refresh');
 
           currentPage = 'study';
-          currentUuid = $.mobile.pageData.uuid;
+          currentUuid = pageData.uuid;
         });
       });
     });
@@ -462,10 +496,12 @@
 function RefreshSeries() 
 {
   if ($.mobile.pageData) {
-    GetResource('/series/' + $.mobile.pageData.uuid, function(series) {
+    var pageData = DeepCopy($.mobile.pageData);
+
+    GetResource('/series/' + pageData.uuid, function(series) {
       GetResource('/studies/' + series.ParentStudy, function(study) {
         GetResource('/patients/' + study.ParentPatient, function(patient) {
-          GetResource('/series/' + $.mobile.pageData.uuid + '/instances', function(instances) {
+          GetResource('/series/' + pageData.uuid + '/instances', function(instances) {
             Sort(instances, function(x) { return x.IndexInSeries; }, true, false);
 
             $('#series .patient-link').attr('href', '#patient?uuid=' + patient.ID);
@@ -492,7 +528,7 @@
             target.listview('refresh');
 
             currentPage = 'series';
-            currentUuid = $.mobile.pageData.uuid;
+            currentUuid = pageData.uuid;
           });
         });
       });
@@ -556,7 +592,9 @@
 function RefreshInstance()
 {
   if ($.mobile.pageData) {
-    GetResource('/instances/' + $.mobile.pageData.uuid, function(instance) {
+    var pageData = DeepCopy($.mobile.pageData);
+
+    GetResource('/instances/' + pageData.uuid, function(instance) {
       GetResource('/series/' + instance.ParentSeries, function(series) {
         GetResource('/studies/' + series.ParentStudy, function(study) {
           GetResource('/patients/' + study.ParentPatient, function(patient) {
@@ -585,7 +623,7 @@
             SetupAnonymizedOrModifiedFrom('#instance-modified-from', instance, 'instance', 'ModifiedFrom');
 
             currentPage = 'instance';
-            currentUuid = $.mobile.pageData.uuid;
+            currentUuid = pageData.uuid;
           });
         });
       });
@@ -704,7 +742,9 @@
 
 $('#instance-preview').live('click', function(e) {
   if ($.mobile.pageData) {
-    var pdf = '../instances/' + $.mobile.pageData.uuid + '/pdf';
+    var pageData = DeepCopy($.mobile.pageData);
+
+    var pdf = '../instances/' + pageData.uuid + '/pdf';
     $.ajax({
       url: pdf,
       cache: false,
@@ -712,11 +752,11 @@
         window.location.assign(pdf);
       },
       error: function() {
-        GetResource('/instances/' + $.mobile.pageData.uuid + '/frames', function(frames) {
+        GetResource('/instances/' + pageData.uuid + '/frames', function(frames) {
           if (frames.length == 1)
           {
             // Viewing a single-frame image
-            jQuery.slimbox('../instances/' + $.mobile.pageData.uuid + '/preview', '', {
+            jQuery.slimbox('../instances/' + pageData.uuid + '/preview', '', {
               overlayFadeDuration : 1,
               resizeDuration : 1,
               imageFadeDuration : 1
@@ -728,7 +768,7 @@
 
             var images = [];
             for (var i = 0; i < frames.length; i++) {
-              images.push([ '../instances/' + $.mobile.pageData.uuid + '/frames/' + i + '/preview' ]);
+              images.push([ '../instances/' + pageData.uuid + '/frames/' + i + '/preview' ]);
             }
 
             jQuery.slimbox(images, 0, {
@@ -748,8 +788,10 @@
 
 $('#series-preview').live('click', function(e) {
   if ($.mobile.pageData) {
-    GetResource('/series/' + $.mobile.pageData.uuid, function(series) {
-      GetResource('/series/' + $.mobile.pageData.uuid + '/instances', function(instances) {
+    var pageData = DeepCopy($.mobile.pageData);
+
+    GetResource('/series/' + pageData.uuid, function(series) {
+      GetResource('/series/' + pageData.uuid + '/instances', function(instances) {
         Sort(instances, function(x) { return x.IndexInSeries; }, true, false);
 
         var images = [];
@@ -858,6 +900,8 @@
 
 $('#instance-store,#series-store,#study-store,#patient-store').live('click', function(e) {
   ChooseDicomModality(function(modality, peer) {
+    var pageData = DeepCopy($.mobile.pageData);
+
     var url;
     var loading;
 
@@ -878,7 +922,7 @@
         url: url,
         type: 'POST',
         dataType: 'text',
-        data: $.mobile.pageData.uuid,
+        data: pageData.uuid,
         async: true,  // Necessary to block UI
         beforeSend: function() {
           $.blockUI({ message: $(loading) });
--- a/OrthancExplorer/query-retrieve.js	Wed Nov 02 12:16:03 2016 +0100
+++ b/OrthancExplorer/query-retrieve.js	Sun Nov 06 11:39:04 2016 +0100
@@ -1,294 +1,323 @@
-function JavascriptDateToDicom(date)
-{
-  var s = date.toISOString();
-  return s.substring(0, 4) + s.substring(5, 7) + s.substring(8, 10);
-}
-
-function GenerateDicomDate(days)
-{
-  var today = new Date();
-  var other = new Date(today);
-  other.setDate(today.getDate() + days);
-  return JavascriptDateToDicom(other);
-}
-
-
-$('#query-retrieve').live('pagebeforeshow', function() {
-  $.ajax({
-    url: '../modalities',
-    dataType: 'json',
-    async: false,
-    cache: false,
-    success: function(modalities) {
-      var target = $('#qr-server');
-      $('option', target).remove();
-
-      for (var i = 0; i < modalities.length; i++) {
-        var option = $('<option>').text(modalities[i]);
-        target.append(option);
-      }
-
-      target.selectmenu('refresh');
-    }
-  });
-
-  var target = $('#qr-date');
-  $('option', target).remove();
-  target.append($('<option>').attr('value', '*').text('Any date'));
-  target.append($('<option>').attr('value', GenerateDicomDate(0)).text('Today'));
-  target.append($('<option>').attr('value', GenerateDicomDate(-1)).text('Yesterday'));
-  target.append($('<option>').attr('value', GenerateDicomDate(-7) + '-').text('Last 7 days'));
-  target.append($('<option>').attr('value', GenerateDicomDate(-31) + '-').text('Last 31 days'));
-  target.append($('<option>').attr('value', GenerateDicomDate(-31 * 3) + '-').text('Last 3 months'));
-  target.append($('<option>').attr('value', GenerateDicomDate(-365) + '-').text('Last year'));
-  target.selectmenu('refresh');
-});
-
-
-$('#qr-echo').live('click', function() {
-  var server = $('#qr-server').val();
-  var message = 'Error: The C-Echo has failed!';
-
-  $.ajax({
-    url: '../modalities/' + server + '/echo',
-    type: 'POST', 
-    cache: false,
-    async: false,
-    success: function() {
-      message = 'The C-Echo has succeeded!';
-    }
-  });
-
-  $('<div>').simpledialog2({
-    mode: 'button',
-    headerText: 'Echo result',
-    headerClose: true,
-    buttonPrompt: message,
-    animate: false,
-    buttons : {
-      'OK': { click: function () { } }
-    }
-  });
-
-  return false;
-});
-
-
-$('#qr-submit').live('click', function() {
-  var query = {
-    'Level' : 'Study',
-    'Query' : {
-      'AccessionNumber' : '*',
-      'PatientBirthDate' : '*',
-      'PatientID' : '*',
-      'PatientName' : '*',
-      'PatientSex' : '*',
-      'SpecificCharacterSet' : 'ISO_IR 192',  // UTF-8
-      'StudyDate' : $('#qr-date').val(),
-      'StudyDescription' : '*'
-    }
-  };
-
-  var field = $('#qr-fields input:checked').val();
-  query['Query'][field] = $('#qr-value').val().toUpperCase();
-
-  var modalities = '';
-  $('#qr-modalities input:checked').each(function() {
-    var s = $(this).attr('name');
-    if (modalities == '*')
-      modalities = s;
-    else
-      modalities += '\\' + s;
-  });
-
-  if (modalities.length > 0) {
-    query['Query']['ModalitiesInStudy'] = modalities;
-  }
-
-
-  var server = $('#qr-server').val();
-  $.ajax({
-    url: '../modalities/' + server + '/query',
-    type: 'POST', 
-    data: JSON.stringify(query),
-    dataType: 'json',
-    async: false,
-    error: function() {
-      alert('Error during query (C-Find)');
-    },
-    success: function(result) {
-      window.location.assign('explorer.html#query-retrieve-2?server=' + server + '&uuid=' + result['ID']);
-    }
-  });
-
-  return false;
-});
-
-
-
-function Retrieve(url)
-{
-  $.ajax({
-    url: '../system',
-    dataType: 'json',
-    async: false,
-    success: function(system) {
-      $('<div>').simpledialog2({
-        mode: 'button',
-        headerText: 'Target',
-        headerClose: true,
-        buttonPrompt: 'Enter Application Entity Title (AET):',
-        buttonInput: true,
-        buttonInputDefault: system['DicomAet'],
-        buttons : {
-          'OK': {
-            click: function () { 
-              var aet = $.mobile.sdLastInput;
-              if (aet.length == 0)
-                aet = system['DicomAet'];
-
-              $.ajax({
-                url: url,
-                type: 'POST',
-                async: true,  // Necessary to block UI
-                dataType: 'text',
-                data: aet,
-                beforeSend: function() {
-                  $.blockUI({ message: $('#info-retrieve') });
-                },
-                complete: function(s) {
-                  $.unblockUI();
-                },
-                error: function() {
-                  alert('Error during retrieve');
-                }
-              });
-            }
-          }
-        }
-      });
-    }
-  });
-}
-
-
-
-
-$('#query-retrieve-2').live('pagebeforeshow', function() {
-  if ($.mobile.pageData) {
-    var uri = '../queries/' + $.mobile.pageData.uuid + '/answers';
-
-    $.ajax({
-      url: uri,
-      dataType: 'json',
-      async: false,
-      success: function(answers) {
-        var target = $('#query-retrieve-2 ul');
-        $('li', target).remove();
-
-        for (var i = 0; i < answers.length; i++) {
-          $.ajax({
-            url: uri + '/' + answers[i] + '/content?simplify',
-            dataType: 'json',
-            async: false,
-            success: function(study) {
-              var series = '#query-retrieve-3?server=' + $.mobile.pageData.server + '&uuid=' + study['StudyInstanceUID'];
-              var info = $('<a>').attr('href', series).html(
-                ('<h3>{0} - {1}</h3>' + 
-                 '<p>Accession number: <b>{2}</b></p>' +
-                 '<p>Birth date: <b>{3}</b></p>' +
-                 '<p>Patient sex: <b>{4}</b></p>' +
-                 '<p>Study description: <b>{5}</b></p>' +
-                 '<p>Study date: <b>{6}</b></p>').format(
-                   study['PatientID'],
-                   study['PatientName'],
-                   study['AccessionNumber'],
-                   FormatDicomDate(study['PatientBirthDate']),
-                   study['PatientSex'],
-                   study['StudyDescription'],
-                   FormatDicomDate(study['StudyDate'])));
-
-              var studyUri = uri + '/' + answers[i] + '/retrieve';
-              var retrieve = $('<a>').text('Retrieve').click(function() {
-                Retrieve(studyUri);
-              });
-
-              target.append($('<li>').append(info).append(retrieve));
-            }
-          });
-        }
-
-        target.listview('refresh');
-      }
-    });
-  }
-});
-
-
-$('#query-retrieve-3').live('pagebeforeshow', function() {
-  if ($.mobile.pageData) {
-    var query = {
-      'Level' : 'Series',
-      'Query' : {
-        'Modality' : '*',
-        'ProtocolName' : '*',
-        'SeriesDescription' : '*',
-        'SeriesInstanceUID' : '*',
-        'StudyInstanceUID' : $.mobile.pageData.uuid
-      }
-    };
-
-    $.ajax({
-      url: '../modalities/' + $.mobile.pageData.server + '/query',
-      type: 'POST', 
-      data: JSON.stringify(query),
-      dataType: 'json',
-      async: false,
-      error: function() {
-        alert('Error during query (C-Find)');
-      },
-      success: function(answer) {
-        var uri = '../queries/' + answer['ID'] + '/answers';
-
-        $.ajax({
-          url: uri,
-          dataType: 'json',
-          async: false,
-          success: function(answers) {
-            
-            var target = $('#query-retrieve-3 ul');
-            $('li', target).remove();
-
-            for (var i = 0; i < answers.length; i++) {
-              $.ajax({
-                url: uri + '/' + answers[i] + '/content?simplify',
-                dataType: 'json',
-                async: false,
-                success: function(series) {
-                  var info = $('<a>').html(
-                    ('<h3>{0}</h3>'  + 
-                     '<p>Modality: <b>{1}</b></p>' +
-                     '<p>Protocol name: <b>{2}</b></p>'
-                    ).format(
-                      series['SeriesDescription'],
-                      series['Modality'],
-                      series['ProtocolName']
-                    ));
-
-                  var seriesUri = uri + '/' + answers[i] + '/retrieve';
-                  var retrieve = $('<a>').text('Retrieve').click(function() {
-                    Retrieve(seriesUri);
-                  });
-
-                  target.append($('<li>').append(info).append(retrieve));
-                }
-              });
-            }
-
-            target.listview('refresh');
-          }
-        });
-      }
-    });
-  }
-});
+function JavascriptDateToDicom(date)
+{
+  var s = date.toISOString();
+  return s.substring(0, 4) + s.substring(5, 7) + s.substring(8, 10);
+}
+
+function GenerateDicomDate(days)
+{
+  var today = new Date();
+  var other = new Date(today);
+  other.setDate(today.getDate() + days);
+  return JavascriptDateToDicom(other);
+}
+
+
+$('#query-retrieve').live('pagebeforeshow', function() {
+  $.ajax({
+    url: '../modalities',
+    dataType: 'json',
+    async: false,
+    cache: false,
+    success: function(modalities) {
+      var target = $('#qr-server');
+      $('option', target).remove();
+
+      for (var i = 0; i < modalities.length; i++) {
+        var option = $('<option>').text(modalities[i]);
+        target.append(option);
+      }
+
+      target.selectmenu('refresh');
+    }
+  });
+
+  var target = $('#qr-date');
+  $('option', target).remove();
+  target.append($('<option>').attr('value', '*').text('Any date'));
+  target.append($('<option>').attr('value', GenerateDicomDate(0)).text('Today'));
+  target.append($('<option>').attr('value', GenerateDicomDate(-1)).text('Yesterday'));
+  target.append($('<option>').attr('value', GenerateDicomDate(-7) + '-').text('Last 7 days'));
+  target.append($('<option>').attr('value', GenerateDicomDate(-31) + '-').text('Last 31 days'));
+  target.append($('<option>').attr('value', GenerateDicomDate(-31 * 3) + '-').text('Last 3 months'));
+  target.append($('<option>').attr('value', GenerateDicomDate(-365) + '-').text('Last year'));
+  target.selectmenu('refresh');
+});
+
+
+$('#qr-echo').live('click', function() {
+  var server = $('#qr-server').val();
+  var message = 'Error: The C-Echo has failed!';
+
+  $.ajax({
+    url: '../modalities/' + server + '/echo',
+    type: 'POST', 
+    cache: false,
+    async: false,
+    success: function() {
+      message = 'The C-Echo has succeeded!';
+    }
+  });
+
+  $('<div>').simpledialog2({
+    mode: 'button',
+    headerText: 'Echo result',
+    headerClose: true,
+    buttonPrompt: message,
+    animate: false,
+    buttons : {
+      'OK': { click: function () { } }
+    }
+  });
+
+  return false;
+});
+
+
+$('#qr-submit').live('click', function() {
+  var query = {
+    'Level' : 'Study',
+    'Query' : {
+      'AccessionNumber' : '*',
+      'PatientBirthDate' : '*',
+      'PatientID' : '*',
+      'PatientName' : '*',
+      'PatientSex' : '*',
+      'SpecificCharacterSet' : 'ISO_IR 192',  // UTF-8
+      'StudyDate' : $('#qr-date').val(),
+      'StudyDescription' : '*'
+    }
+  };
+
+  var field = $('#qr-fields input:checked').val();
+  query['Query'][field] = $('#qr-value').val().toUpperCase();
+
+  var modalities = '';
+  $('#qr-modalities input:checked').each(function() {
+    var s = $(this).attr('name');
+    if (modalities == '*')
+      modalities = s;
+    else
+      modalities += '\\' + s;
+  });
+
+  if (modalities.length > 0) {
+    query['Query']['ModalitiesInStudy'] = modalities;
+  }
+
+
+  var server = $('#qr-server').val();
+  $.ajax({
+    url: '../modalities/' + server + '/query',
+    type: 'POST', 
+    data: JSON.stringify(query),
+    dataType: 'json',
+    async: false,
+    error: function() {
+      alert('Error during query (C-Find)');
+    },
+    success: function(result) {
+      ChangePage('query-retrieve-2', {
+        'server' : server,
+        'uuid' : result['ID']
+      });
+    }
+  });
+
+  return false;
+});
+
+
+
+$('#query-retrieve-2').live('pagebeforeshow', function() {
+  if ($.mobile.pageData) {
+    var pageData = DeepCopy($.mobile.pageData);
+
+    var uri = '../queries/' + pageData.uuid + '/answers';
+
+    $.ajax({
+      url: uri,
+      dataType: 'json',
+      async: false,
+      success: function(answers) {
+        var target = $('#query-retrieve-2 ul');
+        $('li', target).remove();
+
+        for (var i = 0; i < answers.length; i++) {
+          $.ajax({
+            url: uri + '/' + answers[i] + '/content?simplify',
+            dataType: 'json',
+            async: false,
+            success: function(study) {
+              var series = '#query-retrieve-3?server=' + pageData.server + '&uuid=' + study['StudyInstanceUID'];
+              var info = $('<a>').attr('href', series).html(
+                ('<h3>{0} - {1}</h3>' + 
+                 '<p>Accession number: <b>{2}</b></p>' +
+                 '<p>Birth date: <b>{3}</b></p>' +
+                 '<p>Patient sex: <b>{4}</b></p>' +
+                 '<p>Study description: <b>{5}</b></p>' +
+                 '<p>Study date: <b>{6}</b></p>').format(
+                   study['PatientID'],
+                   study['PatientName'],
+                   study['AccessionNumber'],
+                   FormatDicomDate(study['PatientBirthDate']),
+                   study['PatientSex'],
+                   study['StudyDescription'],
+                   FormatDicomDate(study['StudyDate'])));
+
+              var answerId = answers[i];
+              var retrieve = $('<a>').text('Retrieve all study').click(function() {
+                ChangePage('query-retrieve-4', {
+                  'query' : pageData.uuid,
+                  'answer' : answerId,
+                  'server' : pageData.server
+                });
+              });
+
+              target.append($('<li>').append(info).append(retrieve));
+            }
+          });
+        }
+
+        target.listview('refresh');
+      }
+    });
+  }
+});
+
+
+$('#query-retrieve-3').live('pagebeforeshow', function() {
+  if ($.mobile.pageData) {
+    var pageData = DeepCopy($.mobile.pageData);
+
+    var query = {
+      'Level' : 'Series',
+      'Query' : {
+        'Modality' : '*',
+        'ProtocolName' : '*',
+        'SeriesDescription' : '*',
+        'SeriesInstanceUID' : '*',
+        'StudyInstanceUID' : pageData.uuid
+      }
+    };
+
+    $.ajax({
+      url: '../modalities/' + pageData.server + '/query',
+      type: 'POST', 
+      data: JSON.stringify(query),
+      dataType: 'json',
+      async: false,
+      error: function() {
+        alert('Error during query (C-Find)');
+      },
+      success: function(answer) {
+        var queryUuid = answer['ID'];
+        var uri = '../queries/' + answer['ID'] + '/answers';
+
+        $.ajax({
+          url: uri,
+          dataType: 'json',
+          async: false,
+          success: function(answers) {
+            
+            var target = $('#query-retrieve-3 ul');
+            $('li', target).remove();
+
+            for (var i = 0; i < answers.length; i++) {
+              $.ajax({
+                url: uri + '/' + answers[i] + '/content?simplify',
+                dataType: 'json',
+                async: false,
+                success: function(series) {
+                  var info = $('<a>').html(
+                    ('<h3>{0}</h3>'  + 
+                     '<p>Modality: <b>{1}</b></p>' +
+                     '<p>Protocol name: <b>{2}</b></p>'
+                    ).format(
+                      series['SeriesDescription'],
+                      series['Modality'],
+                      series['ProtocolName']
+                    ));
+
+                  var answerId = answers[i];
+                  info.click(function() {
+                    ChangePage('query-retrieve-4', {
+                      'query' : queryUuid,
+                      'study' : pageData.uuid,
+                      'answer' : answerId,
+                      'server' : pageData.server
+                    });
+                  });
+
+                  target.append($('<li>').attr('data-icon', 'arrow-d').append(info));
+                }
+              });
+            }
+
+            target.listview('refresh');
+          }
+        });
+      }
+    });
+  }
+});
+
+
+
+$('#query-retrieve-4').live('pagebeforeshow', function() {
+  if ($.mobile.pageData) {
+    var pageData = DeepCopy($.mobile.pageData);
+    var uri = '../queries/' + pageData.query + '/answers/' + pageData.answer + '/retrieve';
+
+    $.ajax({
+      url: '../system',
+      dataType: 'json',
+      async: false,
+      cache: false,
+      success: function(system) {
+        $('#retrieve-target').val(system['DicomAet']);
+
+        $('#retrieve-form').submit(function(event) {
+          event.preventDefault();
+
+          var aet = $('#retrieve-target').val();
+          if (aet.length == 0) {
+            aet = system['DicomAet'];
+          }
+
+          $.ajax({
+            url: uri,
+            type: 'POST',
+            async: true,  // Necessary to block UI
+            dataType: 'text',
+            data: aet,
+            beforeSend: function() {
+              $.blockUI({ message: $('#info-retrieve') });
+            },
+            complete: function(s) {
+              $.unblockUI();
+            },
+            success: function() {
+              if (pageData.study) {
+                // Go back to the list of series
+                ChangePage('query-retrieve-3', {
+                  'server' : pageData.server,
+                  'uuid' : pageData.study
+                });
+              } else {
+                // Go back to the list of studies
+                ChangePage('query-retrieve-2', {
+                  'server' : pageData.server,
+                  'uuid' : pageData.query
+                });
+              }
+            },
+            error: function() {
+              alert('Error during retrieve');
+            }
+          });
+        });
+      }
+    });
+  }
+});