For work, I built an educational software tool for radiologists. The application provides radiologists a dashboard for viewing their exams. They are able to cycle through each exam and see all the exam data in a popup (modal) window.
The problem that I ran into is that the application had a 2-3 second delay on the staging/production server. The logic behind the exam pop-up window uses a single AJAX call that queries the exam data and then passes that data from the controller and into a view. From everything I’ve learned, this is as straight forward, simple as it gets far as AJAX calls go.
The reason for the delay is unknown. I think it had to do with the vendor’s rabbitMQ’s messaging bus, however, I am not certain. While the groups scratch their heads trying to figure the speed disreprency, I set out to implement a work-around.
Since a lot of the exam data is already being used in the dashboard, I figured I could eager load the rest of the exam data into hidden fields. With all the data there, I then use the code below to dynamically create the exam pop-up windows without ever doing any AJAX calls, thus fixing the speed issue.
/* User selects an exam */
function openExam ( examId ) {
openExamModal ( examId );
populateExamModal ( examId , null );
}
/* User clicks on the Left (previous) arrow */
function openPrevExam ( examId ) {
openExamModal ( examId );
populateExamModal ( examId , 'prev' );
}
/* User clicks on the Right (next) arrow */
function openNextExam ( examId ) {
openExamModal ( examId );
populateExamModal ( examId , 'next' );
}
/* Dynamically populate the modal */
function populateExamModal ( examId , action ) {
var arrayOfExamIds = getExamIdsFromDashboard ();
var index = getExamIndex ( examId , arrayOfExamIds );
if ( isValidExam ( index )) {
var isOpeningNewExam = isPrevOrNextButtonClicked ( action );
if ( isOpeningNewExam ) {
index = getNextExamIndex ( index , action );
examId = arrayOfExamIds [ index ];
}
showCurrentIndex ( examId , index , arrayOfExamIds . length );
displayReportAndDiff ( examId , null , null );
displayLevenshteinDistance ( index , examId );
if ( isOpeningNewExam ) {
openExamModal ( examId );
}
}
}
function displayReportAndDiff ( examId , reportOneId , reportTwoId ) {
var prelimId = getReportOneId ( examId );
var finalReportId = getReportTwoId ( examId );
reportOneId = reportOneId === null ? prelimId : reportOneId ;
reportTwoId = reportTwoId === null ? finalReportId : reportTwoId ;
var reportImpression1 = getReportImpression ( reportOneId );
var reportImpression2 = getReportImpression ( reportTwoId );
var reportBody1 = getReportBody ( reportOneId );
var reportBody2 = getReportBody ( reportTwoId );
if ( isThereTwoReports ( reportOneId , reportTwoId )) {
displayReport ( examId , reportOneId , reportImpression1 . html (), reportBody1 . html (), 'Div1' );
displayReport ( examId , reportTwoId , reportImpression2 . html (), reportBody2 . html (), 'Div2' );
var reportDiff = getTheDiff ( reportBody1 . text (), reportBody2 . text ());
var impressionDiff = getTheDiff ( reportImpression1 . text (), reportImpression2 . text ());
var reportDiffDiv = outputTheReportBodyDiff ( examId );
var impressionDiffDiv = outputTheReportImpressionDiff ( examId );
reportDiffDiv . html ( reportDiff );
impressionDiffDiv . html ( impressionDiff );
}
}
/* Get all exam ids listed in table */
function getExamIdsFromDashboard () {
var table = getTheDashboardTable ();
var examIdColumn = null ;
var examIds = [];
for ( var r = 0 , n = table . rows . length ; r < n ; r ++ ) {
for ( var c = 0 , m = table . rows [ r ]. cells . length ; c < m ; c ++ ) {
var value = table . rows [ r ]. cells [ c ]. innerHTML ;
// find the exam id column
var isExamIdColumn = r == 0 && value == "Hidden Exam Id" ;
// get the index for exam id column
if ( isExamIdColumn ) {
examIdColumn = c ;
}
// use index to reference the exam id value to access its value
if ( r > 0 && c === examIdColumn ) {
if ( value != "" && ! isNaN ( value )) {
examIds . push ( value );
}
}
}
}
return examIds ;
}
function displayReport ( examId , reportId , reportImpression , reportBody , div ) {
/* form elements containing report information */
var getTheStatus = getTheReportStatus ( reportId );
var outputTheStatus = outputTheReportStatus ( examId , div );
var outputTheImpression = outputTheReportImpression ( examId , div );
var outputTheBody = outputTheReportBody ( examId , div );
/* populate the placeholder fields */
outputTheStatus . html ( getTheStatus . text ());
outputTheImpression . html ( reportImpression );
outputTheBody . html ( reportBody );
}
/* show user the # of exams they have iterated through */
function showCurrentIndex ( examId , index , totalExams ) {
var displayInfo = getTheIndexOfTheExam ( examId );
displayInfo . innerText = ( index + 1 ) + "/" + totalExams ;
}
function openExamModal ( examId ) {
var examModal = $ ( '#examModal' + examId );
examModal . modal ( 'toggle' );
}
function isThereAnExam ( inputValue ) {
return inputValue != null && inputValue != "" ;
}
function isThereTwoReports ( reportOneId , reportTwoId ) {
return reportOneId != null
&& reportTwoId != null
&& reportOneId !== ""
&& reportTwoId !== ""
&& reportOneId !== reportTwoId ;
}
/* make sure the current exam exists */
function isValidExam ( index ) {
return index != - 1 ;
}
function isPrevOrNextButtonClicked ( action ) {
return action === "next" || action === "prev" ;
}
/* Increment the index + or - 1 */
function getNextExamIndex ( index , action ) {
switch ( action ) {
case "next" :
index = index + 1 ;
break ;
case "prev" :
index = index - 1 ;
break ;
default :
break ;
}
return index ;
}
/* Increment the index - 1 */
function getPrevExamId ( index , examIdArray ) {
return examIdArray [ index - 1 ];
}
/* find location examId exists in the Exam ID array */
function getExamIndex ( selectedExamId , examIdArray ) {
return jQuery . inArray ( selectedExamId , examIdArray );
}
function getTheIndexOfTheExam ( examId ) {
return document . getElementById ( 'currentIndex' + examId );
}
function getReportOneId ( examId ) {
return $ ( '.' + examId + ' .left-side .fa-check-circle-o .reportId' ). text ();
}
function getReportTwoId ( examId ) {
return $ ( '.' + examId + ' .right-side .fa-check-circle-o .reportId' ). text ();
}
function getReportImpression ( reportId ) {
return $ ( '#impression' + reportId );
}
function getTheDashboardTable () {
return document . getElementById ( 'dashboard-table' );
}
function getReportBody ( reportOne ) {
return $ ( '#report' + reportOne );
}
function getTheReportStatus ( reportId ) {
return $ ( '#reportStatus' + reportId );
}
function outputTheReportBodyDiff ( examId ) {
return $ ( '#reportDiff' + examId );
}
function outputTheReportImpressionDiff ( examId ) {
return $ ( '#impressionDiff' + examId );
}
function outputTheReportStatus ( examId , div ) {
return $ ( '#examModal' + examId + ' .reportStatus' + div );
}
function outputTheReportImpression ( examId , div ) {
return $ ( '#examModal' + examId + ' .reportImpression' + div );
}
function outputTheReportBody ( examId , div ) {
return $ ( '#examModal' + examId + ' .reportBody' + div );
}
function toggleArrow ( examId ) {
$ ( '#expand-details-icon-' + examId ). toggleClass ( "fa-arrow-circle-up fa-arrow-circle-down" );
}
/* Googles diff match patch */
function getTheDiff ( string1 , string2 ) {
var dmp = new diff_match_patch ();
dmp . Diff_Timeout = 0 ;
dmp . Diff_EditCost = 4 ;
var ms_start = ( new Date ()). getTime ();
var d = dmp . diff_main ( string1 , string2 );
var ms_end = ( new Date ()). getTime ();
dmp . diff_cleanupEfficiency ( d );
return dmp . diff_prettyHtml ( d );
}
/* dynamically populate the side by side reports and the diff text */
function selectReportLeftSide ( examId , reportOneId ) {
var selectedReport = $ ( '#leftIconLink' + reportOneId );
if ( isValidReportLink ( selectedReport )) {
var reportTwoId = getReportTwoId ( examId );
removeCheckedIcon ( examId , '.left-side' );
addCheckedIcon ( selectedReport );
displayReportAndDiff ( examId , reportOneId , reportTwoId );
}
}
/* dynamically populate the side by side reports and the diff text */
function selectReportRightSide ( examId , reportTwoId ) {
var selectedReport = $ ( '#rightIconLink' + reportTwoId );
if ( isValidReportLink ( selectedReport )) {
var reportOneId = getReportOneId ( examId );
removeCheckedIcon ( examId , '.right-side ' );
addCheckedIcon ( selectedReport );
displayReportAndDiff ( examId , reportOneId , reportTwoId );
}
}
function removeCheckedIcon ( examId , className ) {
var checkedIcon = $ ( '.' + examId + ' ' + className + ' .fa-check-circle-o' );
checkedIcon . removeClass ( 'fa-check-circle-o' );
checkedIcon . addClass ( 'fa-circle-o' );
}
function addCheckedIcon ( selectedReport ) {
selectedReport . removeClass ( "fa-circle-o" );
selectedReport . addClass ( "fa-check-circle-o" )
}
function isValidReportLink ( selectedReport ) {
return selectedReport . hasClass ( "fa-circle-o" );
}
function copyToClipboardClickHandler ( inputId ) {
var input = $ ( "#" + inputId );
if ( isThereAnExam ( input . val ())) {
input . focus ();
input . select ();
document . execCommand ( "copy" );
}
}
Conclusion
This is not an ideal solution. I much rather off-load the exam business logic into a separate controller action and view. To me, that would make the code far more readible and easier to maintain. However, sometimes a developer doesn’t have the luxury of ideal solutions.