• Status: Solved
  • Priority: High
  • Security: Public
  • Views: 507
  • Last Modified:

Adding download link for downloading excel file retrieved from database

I've got an excel file stored in base64, that is retrieved in a jquery ajax.get call. The base64 data is then converted back into .xlsx file, and attached to a download <a> tag, for the user to click and download the .xlsx file. I get 2 errors: the first error is: jquery.min.js:3 Uncaught TypeError: Cannot read property 'createDocumentFragment' of null.

The 2nd error happens when I click on the download link: Uncaught DOMException: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.
Not sure if 2nd error is related to the 1st one or not. My code is as following:

HTML code:

<a id="downloadlink" onClick="return DownloadExcel()">Download Excel File</a> 

Open in new window


JS code:

//download excel func
function DownloadExcel( file, filename) {
    //debugger;
    var blob = base64toBlob(file, "data:application/vnd.ms-excel;")
    if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
        return navigator.msSaveOrOpenBlob(blob, filename);
    }
    else{
        console.log("inside else of downloadexcel");
      var objectUrl = URL.createObjectURL(blob);
        var downloadLink = $("#downloadlink");
        downloadLink.href = objectUrl; //uri;
        downloadLink.download = filename;
       // document.body.appendChild(downloadLink);
       $(document).append(downloadLink);
    }
  }
  
  function base64toBlob(b64Data, contentType, sliceSize) {
    contentType = contentType || '';
    sliceSize = sliceSize || 512;
  
    var byteCharacters = atob(b64Data);
    var byteArrays = [];
  
    for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
      var slice = byteCharacters.slice(offset, offset + sliceSize);
  
      var byteNumbers = new Array(slice.length);
      for (var i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i);
      }
  
      var byteArray = new Uint8Array(byteNumbers);
  
      byteArrays.push(byteArray);
    }
    var blob = new Blob(byteArrays, {
      type: "application/vnd.ms-excel"
    });
    return blob;
  }
  // /download excel func

  $(document).ready(function(){ 

    $.ajax({
            url: "https://myserver.com/files/file.cfc?fileid=12",
            type: "GET",
            dataType: "json",
            cache: false,
            success: function(response){
                if(response.length != 0){
                    console.log("Successful file retrieval!");
                  
                   DownloadExcel( response[0].filecontent, response[0].filename  );    
                   
                }
                else{
                    console.log("Error in file retrieval!");
                    console.log( response );
                }
            },
            error: function( response ){
                console.log("There was an API server error!");
                console.log( response );
            }
        });
  });

Open in new window

0
roger v
Asked:
roger v
  • 4
  • 3
2 Solutions
 
roger vAuthor Commented:
Update: The 2nd error that was being generated is related to the 1st error: I confirmed this by removing the code to attach the event to <a>, and instead did the conversion of the base64 file into .xlsx, and the conversion part works and the .xlsx file is downloaded without error to user computer. Now I need to figure out a way to actually trigger this download upon user clicking a <a href> tag.
0
 
roger vAuthor Commented:
OK, I think I got it! I changed the way I'm building my <a href> tag. I did it this way and it worked:

 $(document).ready(function(){ 

    $.ajax({
            url: "https://myserver.com/files/file.cfc?fileid=12",
            type: "GET",
            dataType: "json",
            cache: false,
            success: function(response){
                if(response.length != 0){
                    console.log("Successful file retrieval!");
                  $("<a></a>").attr("href", "javascript:void(0)").html($("<span></span>").text(linkText)).appendTo($("#spanattachments"));
                    $("#spanattachments").on('click', 'a', function(event){
                        event.preventDefault();
                        DownloadExcel();
                        return false;
                    });
                   function DownloadExcel() {
                    //window.open(dataURI);
                    var blob = base64toBlob(response[0].filecontent, "data:application/vnd.ms-excel;")
                    if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
                        return navigator.msSaveOrOpenBlob(blob, response[0].filename);
                    }
                    else{
                      var objectUrl = URL.createObjectURL(blob);
                        var downloadLink = document.createElement("a");
                        downloadLink.href = objectUrl; //uri;
                        downloadLink.download = response[0].filename;
                        document.body.appendChild(downloadLink);
                        downloadLink.click();
                        document.body.removeChild(downloadLink);
                    }
                    //window.location.href = objectUrl;//"data:application/vnd.ms-excel;base64" + bindata
                  }
                  
                  function base64toBlob(b64Data, contentType, sliceSize) {
                    contentType = contentType || '';
                    sliceSize = sliceSize || 512;
                  
                    var byteCharacters = atob(b64Data);
                    var byteArrays = [];
                  
                    for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
                      var slice = byteCharacters.slice(offset, offset + sliceSize);
                  
                      var byteNumbers = new Array(slice.length);
                      for (var i = 0; i < slice.length; i++) {
                        byteNumbers[i] = slice.charCodeAt(i);
                      }
                  
                      var byteArray = new Uint8Array(byteNumbers);
                  
                      byteArrays.push(byteArray);
                    }
                    var blob = new Blob(byteArrays, {
                      type: "application/vnd.ms-excel"
                    });
                    return blob;
                  }          
       else{
                    console.log("Error in file retrieval!");
                    console.log( response );
                }
            },
            error: function( response ){
                console.log("There was an API server error!");
                console.log( response );
            }
        });
  });

Open in new window


I'm sure some of the code inside the else statement of DownloadExcel() might be redundant, but it works.
0
 
Julian HansenCommented:
Ok a few things wrong with the script.
First a logic error

In your <a id="downloadlink"></a> you have linked onclick="DownloadExcel()"

You don't want to be doing this. Lets look at why.

In your AJAX (which is invoked at document load) You make an AJAX call to fetch your base64 encoded file - you then call the DownloadExcel function passing the name and content to the function.

The DownloadExcel() function sets up the object reference for the download - which will be invoked when you click the link - no need for a click handler so first step is to fix the link
<a id="downloadlink">Download Excel File</a> 

Open in new window


Next problem is in DownloadExcel() itself you have this
var downloadLink = $("#downloadlink");
downloadLink.href = objectUrl; //uri;

Open in new window

The first line is assigning downloadlink to the jQuery object associated with the element with id downloadlink
On the next line you are accessing the DOM object property href - which is not defined on the jQuery object. jQuery returns a Nodelist (of elements) - to access the native properties you have to index the nodelist to get the element you want - in this case 0 OR you need to use the jQuery equivalent.
So Either
var downloadLink = $("#downloadlink")[0];
downloadLink.href = objectUrl; //uri;

Open in new window

OR
var downloadLink = $("#downloadlink");
downloadLink.attr('href', objectUr)l; //uri;

Open in new window

Either is good.

The next bit is not a bug so much as being superfluous - the code above will now have primed the <a> tag for download so no need to do any appending - this can come out

      $(document).append(downloadLink);

Open in new window


So to summarise

a) Fix your <a> link - remove the onlick
b) Fix or you DownloadExcel - sample below
function DownloadExcel( file, filename) {
  var blob = base64toBlob(file, "data:application/vnd.ms-excel;")
  
  if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
    return navigator.msSaveOrOpenBlob(blob, filename);
  }
  else{
    var objectUrl = URL.createObjectURL(blob);
    var downloadLink = $("#downloadlink")[0];
    downloadLink.href = objectUrl; //uri;
    downloadLink.download = filename;
  }
}

Open in new window

1
Cloud Class® Course: Microsoft Exchange Server

The MCTS: Microsoft Exchange Server 2010 certification validates your skills in supporting the maintenance and administration of the Exchange servers in an enterprise environment. Learn everything you need to know with this course.

 
roger vAuthor Commented:
Thanks for the great explanation! I've changed the way I'm building the <a href> tag and it's working now. But I'll go through your answer and try that out as well. thanks as always!

roger
0
 
Julian HansenCommented:
I have put a working sample here
1
 
Julian HansenCommented:
Your original approach was much better - the new way is a bit of a hack I would go with what you had and just make those small adjustments.

Why - your AJAX complete has become very "heavy" - the functions that are now local with respect to the complete handler are potentially functions you want to reuse. I always try to make my complete handlers very lightweight and put the supplementary code in a library or module.
1
 
roger vAuthor Commented:
Roger Wilco!
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

  • 4
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now