Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Next challenges

Recent achievements

  • Global
  • Personal

Recognition

  • Give kudos
  • Received
  • Given

Leaderboard

  • Global

Trophy case

Kudos (beta program)

Kudos logo

You've been invited into the Kudos (beta program) private group. Chat with others in the program, or give feedback to Atlassian.

View group

It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

How to upload attachment in pure Javascript? (with HTML macro) Edited

Dear,
In the HTML macro on a confluence page, I managed to get data from an attached excel file in a javascript variable. I could then do all sorts of manipulation. However, I did not manage to upload the processed file back to the attachments. Here is what I did already:

<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.16.0/xlsx.full.min.js"></script>
var xldata; var workbook; var firstSheet; var result; var fresult=[]; var wb; var ws; var outData;
var url2="http://xxxxxxxx.com/download/attachments/xxxxxxx/Test.xlsx";
var request2 = new XMLHttpRequest();
request2.responseType = "arraybuffer";
request2.open("GET", url2, true);
request2.send(null);
request2.onload = function() {xldata = new Uint8Array(request2.response);}

 

With the javascript from the XLSX library, I could turn this into a new excel file.

// read the data from xldata into an XLSX object
workbook = XLSX.read(xldata , {type: 'array'});
// get a handle to the first sheet
firstSheet = workbook.Sheets[workbook.SheetNames[0]];
// put the data of the first sheet into a javascript variable result
result = XLSX.utils.sheet_to_json(firstSheet, { header: 1 });
//put the array into a structure (given the first row was a HEADER row)
result.forEach(function(row,i){if (i>0){var trow={}; row.forEach(function(elm,j){trow[result[0][j]]=elm});fresult.push(trow)}});

//create a new workbook
wb=XLSX.utils.book_new();
// create a new worksheet into the workbook and append a sheet
ws=XLSX.utils.json_to_sheet(fresult);
XLSX.utils.book_append_sheet(wb,ws,"NieuwLanden");
document.getElementById("landen").innerHTML="fresult: \n"+JSON.stringify(fresult);

 

So far so good, everything is working as expected!
There are two ways I can turn that workbook object into a file

// create variable with the "file" data
var outData = XLSX.write(wb, {bookType:'xlsx', type: 'binary'});
// or create a BLOB (that is automatically downloaded).
var data = XLSX.writeFile(wb, "nnew.xlsx");


The downloaded file is exactly what I expect it to be. A new excel book with the same data.
I would want to have this now - in pure javascript - uploaded to the attachments.
Here is what I tried.

var path="https://xxxxxxxxxxxx.com/rest/api/content/xxxxxxxxxx/child/attachment"
var request3 = new XMLHttpRequest();
request3.open("POST",path,true);
request3.setRequestHeader("X-Atlassian-Token", "nocheck");
request3.send(outData);

Then I get the error
XHR failed loading: POST "https://...../child/attachment"
POST "https://....../child/attachment" net::ERR_CONNECTION_TIMED_OUT


I have no idea what I do wrong....

2 answers

1 accepted

I have finally found the issue. It was twofold.

1. formdata was not properly formed: a) data needed to be in file format, and 2/ I needed to add the 'minorEdit' property

2. In our company, confluence is residing on the intranet with http:// (and not https://). Although now I just use /rest/api/...

So here is what I needed to do:

var outData = XLSX.write(wb, {bookType:'xlsx', type: 'binary'});
var convert = s => {var buf = new ArrayBuffer(s.length); var view = new Uint8Array(buf);for (var i=0; i<s.length; ++i) {view[i] = s.charCodeAt(i) & 0xFF;} return buf}
var dataArray = convert(outData);
var thefile=new File([dataArray],"database.xlsx",{type:"binary"});
path="/rest/api/content/xxxxxxx/child/attachment/xxxxxx/data";

var fdata=new FormData();
fdata.append('file', thefile);
fdata.append('comment', "latest update with fetch");
fdata.append('minorEdit', "true");

var r1=await fetch(path,{
method:"POST",
headers:{"X-Atlassian-Token":"nocheck"},
body:fdata
})
var resp1=await (r1.json());

Now it all works perfectly

0 votes
Bill Bailey Community Leader May 14, 2020

When you say HTML macro, what do you mean exactly? User macro? Is this hosted on the Confluence page in question?

Guessing  your are not using a user macro, it would be the better route as you can mix Velocity (which gives you access to Java objects within Confluence), with Javascript (which gives you client-side functionality).

Dear Bill,

When I'm saying HTML macro, I'm talking about using the existing HTML macro on a Confluence Page, which I can access as a user....

With this, I put pure Javascript in the body of the macro. With the code provided above, I can read an excel attachment, attached to a given Confluence page, and do all sorts of data manipulation in javascript, which is working really well. I can show them on the confluence page as I wish. Also, with the inline-javascript, I can even create an excel file, and download it. Again really nice.

I'm trying to find a solution where I can upload that file, from within the HTML macro body. I'm looking for a solution where no server-side changes need to be made...

The XMLHttpRequest shown above allows me to "READ" the attachment, but I do not know how to create a XMLHttpRequest to "SAVE" a new attachement.

Kind regards,

Peter

Bill Bailey Community Leader May 14, 2020

I might be a security issue that is preventing it from writing. I have a macro that is on a page that does write attachments to another page in Confluence. It is a user macro that uses JavaScript to do the write, but is triggered using a button (taking advantage of the built-in AUI).

Clicking the button then triggers some JavaScript to do the writing of the attachment) using this function

function httpPostFetch(url, data) {
fetch(url, {
method: 'POST',
headers: {
'X-Atlassian-Token': 'no-check'
},
body: data
})
.then(function(response) {
console.log("RESP: " + response.status);
if (response.status == 200) {
AJS.toInit(function() {
AJS.dialog2("#demo-dialog").show();
## Hides the dialog
AJS.$("#dialog-submit-button").click(function (e) {
e.preventDefault();
AJS.dialog2("#demo-dialog").hide();
});
});
}
});
}

 Any JavaScript in your user macro needs to be surrounded by:

<script type="text/javascript">

....
</script>

A user macro has the advantage of allowing it to be configurable via parameters and easily reusable, plus you update the code in one place. And now you have the best of both worlds with server-side access and client-side code.

Dear Bill,

Thank you for your response.

1/ yes I know it needs to be surrounded by <script>. I just copy pasted the main parts. As said, the program works to a point that I can manipulate the data out of the attached excel in javascript, and I can even create a new excel workbook with the manipulated data that can be downloaded to the local drive. So it is up-and-running, and does already about 95% of what I want.

2/ A user macro has for sure advantages, but I want a solution without having to go through the corporate process of getting a server-side macro added.

3/ I assume AUI does the upload. How would the attached code would have to be modified to do it without, i.e. work by using a XMLHttpRequest ?

Dear Bill,

I played a bit with your proposal, but so far without success.

Am I doing something wrong with the data to upload? Are there specific constraints on data? To upload a file, do I need to put it in a new FormData with 'file' and 'data' specified? I tried with the following: 

formdata = new FormData();
formdata.append('file', 'ttest.xlsx');
formdata.append('data', outData);
fetch(path,{method: "POST", "X-Atlassian-Token": "nocheck", "Content-Type":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=utf-8", body:formdata})

Kind regards,

Peter

Samuel Zhang Atlassian Team Dec 13, 2020

Hi Peter,

This may no longer be an issue for you, but if anyone else comes across this situation, the formData.append method allows a filename to be specified.

See this code example for a pure Javascript implementation:

formdata = new FormData();
formdata.append('file', outData, 'test.xlsx');
formdata.append('minorEdit', true);
fetch(path,{method: "POST", "X-Atlassian-Token": "nocheck", body: formdata})

In Node.JS, this could look like:

const FormData = require('form-data');

formdata = new FormData();
formdata.append('file', fileBuffer, { filename: 'test.xlsx'} );
formdata.append('minorEdit', true);
fetch(path,
{
method: "POST",
headers: { "X-Atlassian-Token": "nocheck", ...formdata.getHeaders() }
body: formdata
}
);

Cheers,

Samuel 

Suggest an answer

Log in or Sign up to answer
TAGS
Community showcase
Published in Confluence

Announcing Team Calendars in Confluence Data Center

Hi Community! We're thrilled to share that Team Calendars for Confluence is now a built-in feature for Confluence Data Center releases 7.11 and beyond.  A long time favorite,  Team Cale...

198 views 0 6
Read article

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you