function getVar (&$modx, $varName) {
    $sql = "select varValue from Config where varName = ?";
    $stmt = $modx->prepare($sql);
    $result = $stmt->execute(array ($varName));
    if (! $result) {
        $msg = $stmt->errorInfo();
        echo 'Error in getVar of snippet "getProjectsJS". ' . $msg[2] . PHP_EOL;
    }
    $stmt->setFetchMode(PDO::FETCH_NUM);
    while ($row = $stmt->fetch()) {
        $output = $row[0];
    }
    return $output;
}

function getSortedFileNames (&$modx, $varName, $match) {
/* This function gets a directory path from a Config variable ($varName). It then
   filters the file names in that directory using the $match parameter. It returns 
   a sorted array of file names from that directory that pass the filter. The match
   parameter allows filtering for only files of a particular type. */
    $dir = getVar ($modx, $varName); /* Get the directory path from a Config variable */
    if (is_dir ($dir)) { /* Make sure it's a directory */
        if ($dh = opendir ($dir)) { /* Open the directory */
            while (($fileName = readdir($dh)) !== false) { /* Read the directory */
                if (preg_match ($match, $fileName)) { /* filter the file name returned */
                    $fileNames[] = $fileName; /* "push" the name into the array */
                }
            }
            closedir ($dh);
        }
        sort ($fileNames);
    }
    return ($fileNames);
}

$output = '<script type="text/javascript">' . PHP_EOL;
$output .= 'var lastArtist = "' . getVar($modx, "lastArtist") . '";' . PHP_EOL;
$output .= 'var lastProject = "' . getVar($modx, "lastProject") . '";' . PHP_EOL;
$output .= 'var lastType = "' . getVar($modx, "lastType") . '";' . PHP_EOL . PHP_EOL;

$output .= 'var previewHttpPath = "' . getVar($modx, "previewHttpPath") . '";' . PHP_EOL;
$output .= 'var multitrackHttpPath = "' . getVar($modx, "multitrackHttpPath") . '";' . PHP_EOL . PHP_EOL;

$output .= <<<JS
var artists = [];
var artistSorts = [];
var artistAnchors = [];
var projects = [];
var projectTypes = [];
var generalStyles = [];
var specificStyles = [];
var titles = [];
var dateAddeds = [];
var previewURLs = [];
var wimpyURLs = [];
var explicitLyricss = [];
var numTrackss = [];
var moreInfoTIDs = [];
var discussFIDs = [];
var showFIDs = [];
var actives = [];
var styleAnchors = [];
var comments = [];

var dlArtists = [];
var dlProjects = [];
var dlProjectTypes = [];
var dlDownloadURLs = [];
var dlDownloadSizes = [];

var previews = [];

var downloads = [];
var downloadSizes = [];


JS;

/* Issue a select for the Projects data */
$sql =  <<<SQL
select * from Artists, Projects, SectionComments
    where
        Artists.artist = Projects.artist and
        SectionComments.artist = Projects.artist and
        SectionComments.generalStyle = Projects.generalStyle and
        SectionComments.specificStyle = Projects.specificStyle and
        SectionComments.title = Projects.title
    order by
        Artists.artistSort,
        Projects.project,
        Projects.projectType
SQL;
$stmt = $modx->prepare($sql);
$result = $stmt->execute();
if (! $result) {
    $msg = $stmt->errorInfo();
    echo 'Error in snippet ms-mtk-getProjectsJS. ' . $msg[2];
}
$stmt->setFetchMode(PDO::FETCH_ASSOC);

/* Loop through the results and generate JavaScript array assignment(push) statements. */
while ($row = $stmt->fetch()) {
    $output .= 'artists.push ("' . $row["artist"] . '");' . PHP_EOL;
    $output .= 'artistSorts.push ("' . $row["artistSort"] . '");' . PHP_EOL;
    $output .= 'artistAnchors.push ("' . $row["artistAnchor"] . '");' . PHP_EOL;
    $output .= 'projects.push ("' . $row["project"] . '");' . PHP_EOL;
    $output .= 'projectTypes.push ("' . $row["projectType"] . '");' . PHP_EOL;
    $output .= 'generalStyles.push ("' . $row["generalStyle"] . '");' . PHP_EOL;
    $output .= 'specificStyles.push ("' . $row["specificStyle"] . '");' . PHP_EOL;
    $output .= 'titles.push ("' . $row["title"] . '");' . PHP_EOL;
    $output .= 'dateAddeds.push ("' . $row["dateAdded"] . '");' . PHP_EOL;
    $output .= 'previewURLs.push ("' . $row["previewURL"] . '");' . PHP_EOL;
    $output .= 'wimpyURLs.push ("' . $row["wimpyURL"] . '");' . PHP_EOL;
    $output .= 'explicitLyricss.push ("' . $row["explicitLyrics"] . '");' . PHP_EOL;
    $output .= 'numTrackss.push ("' . $row["numTracks"] . '");' . PHP_EOL;
    $output .= 'moreInfoTIDs.push ("' . $row["moreInfoTID"] . '");' . PHP_EOL;
    $output .= 'discussFIDs.push ("' . $row["discussFID"] . '");' . PHP_EOL;
    $output .= 'showFIDs.push ("' . $row["showFID"] . '");' . PHP_EOL;
    $output .= 'actives.push ("' . $row["active"] . '");' . PHP_EOL;
    $row["comment"] = str_replace ('"', '\"', $row["comment"]);
    $output .= 'comments.push ("' . $row["comment"] . '");' . PHP_EOL;
    $output .= PHP_EOL;
}

$sql = "select * from Downloads order by artist, project, projectType";
$stmt = $modx->prepare($sql);
$result = $stmt->execute();
if (! $result) {
    $msg = $stmt->errorInfo();
    echo 'Error in snippet "ms-mtk-getProjectsJS" ' . $msg[2];
}
$stmt->setFetchMode(PDO::FETCH_ASSOC);
while ($row = $stmt->fetch()) {
    $output .= 'dlArtists.push ("' . $row["artist"] . '");' . PHP_EOL;
    $output .= 'dlProjects.push ("' . $row["project"] . '");' . PHP_EOL;
    $output .= 'dlProjectTypes.push ("' . $row["projectType"] . '");' . PHP_EOL;
    $output .= 'dlDownloadURLs.push ("' . $row["DLURL"] . '");' . PHP_EOL;
    $output .= 'dlDownloadSizes.push ("' . $row["DLSize"] . '");' . PHP_EOL . PHP_EOL;
}

/* Generate JavaScript "push" statements for the preview file names. */
$fileNames = getSortedFileNames($modx, "previewFileSysPath", '/\.mp3$/');
for ($idx=0; $idx<count($fileNames); $idx++) {
    $output .= 'previews.push ("' . $fileNames[$idx] . '");' . PHP_EOL;
}
$output .= PHP_EOL;

/* Generate JavaScript "push" statements for the download/multitrack file names & sizes. */
$fileNames = getSortedFileNames ($modx, "multitrackFileSysPath", '/\.zip$/');
$downloadDir = getVar ($modx, 'multitrackFileSysPath');
if (substr ($downloadDir, (strlen($downloadDir) - 1), 1) != '/') {
    $downloadDir .= '/'; /* If last char isn't "/", append one. */
}
for ($idx=0; $idx<count($fileNames); $idx++) {
    $output .= 'downloads.push ("' . $fileNames[$idx] . '");' . PHP_EOL;
    $output .= 'downloadSizes.push ("' . round(filesize($downloadDir . $fileNames[$idx]) / 1048576) . '");' . PHP_EOL;
}
$output .= PHP_EOL;

/* Add the static JavaScript with a heredoc. */
$output .= <<<JS
function showDownloads () {
    document.location.href="#openDownloads";
    document.getElementById("downloadsList").focus();
}

function showPreviews () {
    document.location.href="#openPreviews";
    document.getElementById("previewsList").focus();
}

function unHighlightAll () {
// Un-highlight all entries in the scroll list.
    var numSites = document.getElementById("projectList").childElementCount / 2;
    for (idx2=0; idx2<numSites; idx2++) {
        document.getElementById("r" + idx2).style.backgroundColor = "white";
    }
}

function selectRow (idx1) {
// Select row "idx1" in the scrollable grid. Highlight that entry and
// put its corresponding data in the form fields below.
    var idx2;
    var idx3;

    // Unhighlight all entries in the scrollable grid.
    unHighlightAll();

    // Highlight the idx1 entry. That would be either the one just clicked or the
    // last one added or updated. The init function calls for that.
    document.getElementById("r" + idx1).style.backgroundColor = "gray";

    // Put artist/project/type into hidden fields on delForm.
    document.getElementById("delArtist").value = artists[idx1];
    document.getElementById("delProject").value = projects[idx1];
    document.getElementById("delProjectType").value = projectTypes[idx1];

    // Populate the add/update form with the variables for this artist/project/type
    document.getElementById("artist").value = artists[idx1];
    document.getElementById("artistSort").value = artistSorts[idx1];
    document.getElementById("artistAnchor").value = artistAnchors[idx1];
    document.getElementById("project").value = projects[idx1];
    document.getElementById("projectType").value = projectTypes[idx1];
    document.getElementById("dateAdded").value = dateAddeds[idx1];
    generalStyles[idx1] = generalStyles[idx1].replace("&amp;", "&");
    document.getElementById("generalStyle").value = generalStyles[idx1];
    document.getElementById("specificStyle").value = specificStyles[idx1];
    document.getElementById("albumTitle").value = titles[idx1];
    document.getElementById("previewURL").value = previewURLs[idx1];
    document.getElementById("wimpyURL").value = wimpyURLs[idx1];
    document.getElementById("explicitLyrics").value = explicitLyricss[idx1];
    document.getElementById("numTracks").value = numTrackss[idx1];
    document.getElementById("moreInfoTID").value = moreInfoTIDs[idx1];
    document.getElementById("discussFID").value = discussFIDs[idx1];
    document.getElementById("showFID").value = showFIDs[idx1];
    document.getElementById("active").value = actives[idx1];
    document.getElementById("comment").value = comments[idx1];
    // Don't set urlIdentifier. It's extracted from the first download
    // Don't set numDLs. It comes from counting the number of downloads.
    // Don't set isNew. It's a scratch variable only used in generation.

    // Clear all download fields since the previous one may have had more
    // downloads than the one just clicked on.
    for (idx3=1; idx3<10; idx3++) {
        document.getElementById("DLURL" + idx3).value = "";
        document.getElementById("dlSize" + idx3).value = "";
    }

    // Now set the download fields when artist/project/projectType match.
    idx3 = 1; // idx3 indexes the fields on the form
    for (idx2=0; idx2<dlArtists.length; idx2++) {
        if ((dlArtists[idx2] == artists[idx1]) && (dlProjects[idx2] == projects[idx1]) && (dlProjectTypes[idx2] == projectTypes[idx1])) {
            document.getElementById("DLURL" + idx3).value = dlDownloadURLs[idx2];
            document.getElementById("dlSize" + idx3).value = dlDownloadSizes[idx2];
            idx3++;
        }
    }

    // Finally, set all fields with an edit check to white background in case an add was started, 
    // an error occurred and then the user clicked on an entry. Kind of a corner case but it 
    // happened to me early on.
    document.getElementById("artist").style.backgroundColor = "white";
    document.getElementById("artistSort").style.backgroundColor = "white";
    document.getElementById("artistAnchor").style.backgroundColor = "white";
    document.getElementById("specificStyle").style.backgroundColor = "white";
    document.getElementById("project").style.backgroundColor = "white";
    document.getElementById("dateAdded").style.backgroundColor = "white";
    document.getElementById("previewURL").style.backgroundColor = "white";
    document.getElementById("wimpyURL").style.backgroundColor = "white";
    document.getElementById("discussFID").style.backgroundColor = "white";
    document.getElementById("numTracks").style.backgroundColor = "white";
    document.getElementById("DLURL1").style.backgroundColor = "white";
    document.getElementById("dlSize1").style.backgroundColor = "white";
}

function downloadClick (clicked_obj) {
// This function gets the click event from one of the rows in downloadsList
    var clickedObjId;
    var idx1;
    var idx2;

    // Get the ID of the row ("d0", "d1", "d2", etc.)
    clickedObjId = clicked_obj.id;

    // Strip the first character, "d", to make it into a number.
    idx1 = clickedObjId.substring (1);

    // Unhighlight all rows
    for (idx2=0; idx2<downloads.length; idx2++) {
        document.getElementById("d" + idx2).style.backgroundColor = "white";
    }

    // Highlight the clicked row
    document.getElementById("d" + idx1).style.backgroundColor = "gray";

    // Put the download into both DLURL1 and the size into dlSize1
    document.getElementById("DLURL1").value = multitrackHttpPath + downloads[idx1];
    document.getElementById("dlSize1").value = downloadSizes[idx1];

    // Put focus on the DIV that contains this row. This is so keystroke
    // navigation will work.
    document.getElementById("downloadsList").focus();
}

function previewClick (clicked_obj) {
// This function gets the click event from one of the rows in previewsList
    var clickedObjId;
    var idx1;
    var idx2;

    // Get the ID of the row ("p0", "p1", "p2", etc.)
    clickedObjId = clicked_obj.id;

    // Strip the first character, "p", to make it into a number.
    idx1 = clickedObjId.substring (1);

    // Unhighlight all rows
    for (idx2=0; idx2<previews.length; idx2++) {
        document.getElementById("p" + idx2).style.backgroundColor = "white";
    }

    // Highlight the clicked row
    document.getElementById("p" + idx1).style.backgroundColor = "gray";

    // Put the preview into both previewURL and wimpyURL
    document.getElementById("previewURL").value = previewHttpPath + previews[idx1];
    document.getElementById("wimpyURL").value = previewHttpPath + previews[idx1];

    // Put focus on the DIV that contains this row. This is so keystroke
    // navigation will work.
    document.getElementById("previewsList").focus();
}

function projectClick (clicked_obj) {
// This function gets the click directly from one of the grid rows
    var clickedObjId;
    var idx

    // Get the ID of the row ("r0", "r1", "r2", etc.)
    clickedObjId = clicked_obj.id;

    // Strip the first character, "r", to make it into a number.
    idx = clickedObjId.substring (1);

    // Call the routine to highlight the row & populate the form fields
    selectRow (idx);

    // Put focus on the DIV that contains this row. This is so keystroke
    // navigation will work.
    document.getElementById("projectList").focus();
}

function downloadsKeyPress (e) {
// Handle keypress event for the DIV named "downloadsList". This DIV can
// receive keypresses but the spans inside it can't.
    var idx = 0;
    var found = false;
    var target = String.fromCharCode(e.charCode);
    target = target.toUpperCase();
    while ((!found) && (idx < downloads.length)) {
        if (downloads[idx].substring(0,1) < target) {
            idx++;
        } else {
            document.getElementById("d" + idx).scrollIntoView(true);
            found = true;
        }
    }
}

function previewsKeyPress (e) {
// Handle keypress event for the DIV named "previewsList". This DIV can
// receive keypresses but the spans inside it can't.
    var idx = 0;
    var found = false;
    var target = String.fromCharCode(e.charCode);
    target = target.toUpperCase();
    while ((!found) && (idx < previews.length)) {
        if (previews[idx].substring(0,1) < target) {
            idx++;
        } else {
            document.getElementById("p" + idx).scrollIntoView(true);
            found = true;
        }
    }
}

function projectsKeyPress (e) {
// Handle keypress event for the DIV named "projectList". This DIV can
// receive keypresses but the spans inside it can't.
    var idx = 0;
    var found = false;
    var target = String.fromCharCode(e.charCode);
    target = target.toUpperCase();
    while ((!found) && (idx < artists.length)) {
        if (artistSorts[idx].substring(0,1) < target) {
            idx++;
        } else {
            document.getElementById("r" + idx).scrollIntoView(true);
            found = true;
        }
    }
}

function checkOneRequired (fieldName) {
// Edit check. Return true if parm "fieldName" has something in it.
    if (document.getElementById(fieldName).value == "") {
        document.getElementById(fieldName).style.backgroundColor = "red";
        return 1;
    } else {
        document.getElementById(fieldName).style.backgroundColor = "white";
        return 0;
    }
}

function checkRequired () {
// Check that there's something, anything, in these fields
    var cnt = 0;

    cnt += checkOneRequired ("artist");
    cnt += checkOneRequired ("artistSort");
    cnt += checkOneRequired ("artistAnchor");
    cnt += checkOneRequired ("specificStyle");
    cnt += checkOneRequired ("project");
    cnt += checkOneRequired ("dateAdded");
    cnt += checkOneRequired ("previewURL");
    cnt += checkOneRequired ("wimpyURL");
    cnt += checkOneRequired ("discussFID");
    cnt += checkOneRequired ("numTracks");
    cnt += checkOneRequired ("DLURL1");
    cnt += checkOneRequired ("dlSize1");

    if (cnt == 0) {
        return true;
    } else {
        if (cnt == 1) {
            alert ("A required field has not been filled in.");
        } else {
            alert ("Required fields have not been filled in.");
        }
        return false;
    }
}

function checkForDelete () {
// Check that something to delete has been selected.
    if ((document.getElementById("delArtist").value == "") ||
        (document.getElementById("delProject").value == "") ||
        (document.getElementById("delProjectType").value == "")) {

        alert ("Select a row from the artist/project/type grid to delete a project.");
        return false;
    } else {
        return (confirm ("Are you sure?"));
    }
}

function makeArtistSortAnchor () {
// Called when the text in the "artist" field changes.
    var txt = document.getElementById("artist").value;
    txt = txt.replace(/ /g, "");
    txt = txt.replace(/,/g, "");
    txt = txt.replace(/\./g, "");
    txt = txt.replace(/\'/g, "");
    txt = txt.replace(/\&amp;/g, "");
    document.getElementById("artistAnchor").value = txt;

    var txt = document.getElementById("artist").value;
    txt = txt.toUpperCase();
    txt = txt.replace(/^A /, "");
    txt = txt.replace(/^THE /, "");
    document.getElementById("artistSort").value = txt;
}

function getDateString () {
// Return a date in MySQL format. 
    var d = new Date();
    var mm = d.getMonth() + 1;
    if (mm < 10) {
        mm = "0" + mm;
    }
    var dd = d.getDate();
    if (dd < 10) {
        dd = "0" + dd;
    }
    var hh = d.getHours();
    if (hh < 10) {
        hh = "0" + hh;
    }
    var min = d.getMinutes();
    if (min < 10) {
        min = "0" + min;
    }
    var ss = d.getSeconds();
    if (ss < 10) {
        ss = "0" + ss;
    }

    return d.getFullYear() + "-" + mm + "-" + dd + " " + hh + ":" + min + ":" + ss;
}

function clearProject () {
// Clear the project portion of what's currently on the screen.
    var d;
    var idx;

    document.getElementById("project").value = "";
    document.getElementById("projectType").value = "Full";
    document.getElementById("dateAdded").value = getDateString();
    document.getElementById("active").value = "1";
    document.getElementById("previewURL").value = "";
    document.getElementById("wimpyURL").value = "";
    document.getElementById("explicitLyrics").value = "0";
    document.getElementById("moreInfoTID").value = "0";
    document.getElementById("discussFID").value = "0";
    document.getElementById("showFID").value = 1;
    document.getElementById("numTracks").value = "";
    for (idx=1; idx<10; idx++) {
        document.getElementById("DLURL" + idx).value = "";
        document.getElementById("dlSize" + idx).value = "";
    }
    document.getElementById("project").focus();
}

function clearAll () {
// Clear all form fields.

    // Clear the artist related fields.
    document.getElementById("artist").value = "";
    document.getElementById("artistSort").value = "";
    document.getElementById("artistAnchor").value = "";
    document.getElementById("albumTitle").value = "";
    document.getElementById("specificStyle").value = "";
    document.getElementById("comment").value = "";

    // Clear the project related fields
    clearProject();

    // Unhighlight any selected entry in the scrollable grid
    // then clear the "del" field so an entry won't get 
    // inadvertantly deleted.
    unHighlightAll ();
    document.getElementById("delArtist").value = "";
    document.getElementById("delProject").value = "";
    document.getElementById("delProjectType").value = "";

    // Put the cursor in artist.
    document.getElementById("artist").focus();
}

function getSizeDLURL1 () {
// Get the size of the file name in DLURL1.
    var fileName;
    var fileSize;
    var idx;
    var size;

    // Get the file name and strip the path from it.
    fileName = document.getElementById ("DLURL1").value;
    while (fileName.indexOf ("/") > -1) {
        fileName = fileName.substr (1); // Strip the leading character
    }

    // Search the lists for fileName. If found, set size to downloadSizes[idx]
    size = 0;
    for (idx=0; idx<downloads.length; idx++) {
        if (fileName == downloads[idx]) {
            size = downloadSizes[idx];
        }
    }

    // Return the file size.
    return size;
}

function flipOne (fieldId, srep1, srep2) {
// Flip one field from "Full" to "Excerpt" or "Excerpt" to "Full" for 
// field IDs DLURL1, previewURL & wimpyURL.
    var field = document.getElementById(fieldId).value;
    if (field.search("cambridge-mt.com")) {
        if (field.search(srep1) > 0) {
            field = field.replace(srep1, srep2);
        } else {
            field = field.replace(srep2, srep1);
        }
        document.getElementById(fieldId).value = field;
    }
}

function flipType () {
// Flip as much as we can from "Full" to "Excerpt" or "Excerpt" to "Full"
    if (document.getElementById("projectType").value == "Full") {
        document.getElementById("projectType").value = "Excerpt";
        document.getElementById("showFID").value = 0;
        document.getElementById("numTracks").focus();
    } else {
        document.getElementById("projectType").value = "Full";
        document.getElementById("showFID").value = 1;
        document.getElementById("discussFID").focus();
    }
    document.getElementById("dateAdded").value = getDateString();
    document.getElementById("moreInfoTID").value = "0";
    document.getElementById("discussFID").value = "0";
    document.getElementById("numTracks").value = "";
    flipOne ("DLURL1", "_Full.zip", ".zip");
    flipOne ("previewURL", "_Full_Preview", "_Preview");
    flipOne ("wimpyURL", "_Full_Preview", "_Preview");
    document.getElementById("dlSize1").value = getSizeDLURL1();
}

function loadArtistProjectType () {
// Load the artist/project/type scrollable grid from the artists/projects/projectTypes arrays.

    // Load the zero entry. It has different CSS than the rest.
    var html = "<span id=\"r0\" onClick=\"projectClick(this);\" class=\"bgWhite\">" +
               "<span class=\"firstLeft w400 \" >" + artists[0] + "</span>" +
               "<span class=\"firstCtr w600 \" >" + projects[0] + "</span>" +
               "<span class=\"firstRight w60 \" >" + projectTypes[0] + "</span>" +
               "</span><br>";

    // Load the rest of the grid
    for (var idx=1; idx<artists.length; idx++) {
        html += "<span id=\"r" + idx + "\" onClick=\"projectClick(this);\" class=\"bgWhite\">" +
                "<span class=\"nextLeft w400 \" >" + artists[idx] + "</span>" +
                "<span class=\"nextCtr w600 \" >" + projects[idx] + "</span>" +
                "<span class=\"nextRight w60 \" >" + projectTypes[idx] + "</span>" +
                "</span><br>";
    }

    // Do the actual load to the HTML enelemt.
    document.getElementById("projectList").innerHTML = html;
}

function init () {
// Initialize the form.

    // First load the scrollable grid at the top of the page
    loadArtistProjectType ();
    
    // lastArtist, lastProject & lastType contain the last thing that
    // was added or updated. Find out what index/entry they are in 
    // in the artists/projects/projectTypes arrays. If the last
    // operation was a delete lastArtist/lastProject/lastType will
    // be null & we won't find anything.
    var idx = 0;
    var foundIdx = -1;
    while (idx<artists.length) {
        if (artists[idx] == lastArtist) {
            if (projects[idx] == lastProject) {
                if (projectTypes[idx] == lastType) {
                    foundIdx = idx;
                }
            }
        }
        idx++;
    }

    // If it was found scroll that row into view and init projects
    // fields below the grid. This will allow a Full or Excerpt
    // project to more easily be turned into the other kind.
    if (foundIdx > -1) {
        document.getElementById("r" + foundIdx).scrollIntoView(true);
        selectRow (foundIdx);
    } else {
        // There probably wasn't a lastAtist/lastProject/lastType. Just init the date/time.
        document.getElementById("dateAdded").value = getDateString();
    }
    document.getElementById("projectList").focus(); // Put focus on the scrolling DIV

    // Load the previewsList div (a popup)
    html = "<span id=\"p0\" onClick=\"previewClick(this);\" onDblClick=\"document.location.href='#close';\" class=\"bgWhite firstLeft w400\">" +
           previews[0] + "</span><br>";
    for (var idx=1; idx<previews.length; idx++) {
        html += "<span id=\"p" + idx + "\" onClick=\"previewClick(this);\" onDblClick=\"document.location.href='#close';\" class=\"bgWhite nextLeft w400\">" + 
                previews[idx] + "</span><br>";
    }
    // Do the actual load to the HTML enelemt.
    document.getElementById("previewsList").innerHTML = html;

    // Load the downloadsList div (a popup)
    html = "<span id=\"d0\" onClick=\"downloadClick(this);\" onDblClick=\"document.location.href='#close';\" class=\"bgWhite firstLeft w400\">" + 
           downloads[0] + "</span><br>";
    for (var idx=1; idx<downloads.length; idx++) {
        html += "<span id=\"d" + idx + "\" onClick=\"downloadClick(this);\" onDblClick=\"document.location.href='#close';\" class=\"bgWhite nextLeft w400\">" +
                downloads[idx] + "</span><br>";
    }
    // Do the actual load to the HTML enelemt.
    document.getElementById("downloadsList").innerHTML = html;

}
JS;
$output .= PHP_EOL;

$output .= '</script>' . PHP_EOL;

/* Put the JavaScript into the page header */
$modx->regClientStartupScript($output);