Weasyprint django - generates an empty PDF - javascript

Good afternoon,
I have the following problem in my app:
I am working on generating pdfs via WEASYPRINT for some of my views. My app is a search engine that aggregates external information, what I am looking for is:
That the user makes a search and the results are shown.
When he clicks on report, he can download a pdf with the search results. (via AJAX without having to reload the page).
So far this works perfect using xhtml2pdf, but I want to change it to WEASYPRINT because it allows more flexibility in the design of the pdf.
As I said there is an ajax function that sends the data to the pdf generation view, it receives them and generates a pdf with converting the html to pdf and sends a response that with a ".done()' function in javascript activates the download of the pdf. The problem is that this pdf is shown empty because there must be some decoding problem, or so I think.
Views.py
def ViewPDF(request, *args, **kwargs):
response = request.POST.get('nombre', None)
hits = request.POST.get('hits', None)
response2 = request.POST.get('query1', None)
info = {'searched': str(response2), 'customer': request.user.customer.name, 'type_of_search': '',
'lists_covered': 'OFAC', 'Date_of_search': str(request.POST.get('date', None)), 'hits': hits}
if response is not None:
# json_search = json.loads(response2)
if hits == 'YES':
json_data = json.loads(response)
info['type_of_search'] = 'basic search'
data = []
for i in range(len(json_data)):
data.append({'body': {
'Name': json_data[i]['name'],
'Description': json_data[i]['notes'],
'Occupation': json_data[i]['ocupation'],
'Place_of_Birth': json_data[i]['POB'],
'Date_of_Birth': json_data[i]['DOB'],
'Position': json_data[i]['other'],
'Citizenship': json_data[i]['nationality']
}})
data = {'info': info, 'body': data}
template = get_template('core/reports/free_search.html')
html = template.render(data)
pdf = HTML(string=html).write_pdf()
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Transfer-Encoding'] = 'utf-8'
if request.is_ajax():
return response
main.js
var url = '/pdf/' + query['s_pk']
$report0 = $('#report');
$report0.on('click', function () {
$.ajax({
url: url,
type: 'POST',
data: {
query1: query['search'],
nombre: JSON.stringify(query['responseData']),
date: $time,
hits: 'YES',
'csrfmiddlewaretoken': $("input[name=csrfmiddlewaretoken]").val()
}
}).done(function (response) {
console.log(response)
let blob = new Blob([response]);
let link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = query['search'] + '.pdf';
link.click();
});
The response to the request
enter image description here
I guess it has something to do with encoding, but I would appreaciate some help here... Where do you think is the problem?
Thanks in advance.

I managed to do it in the following way

Related

Flask-wtf with trix-editor - How do I upload images

I'm using Flask with one of my wtforms TextAreaFields mapped to Trix-Editor. All works well except for images using the built toolbar attach button.
I'd like to save the images to a directory on the backend and have a link to it in the trix-editor text. I'm saving this to a database.
I can make this work by adding an <input type='file'/>in my template like so:
{{ form.description }}
<trix-editor input="description"></trix-editor>
<input type="file"/>
and the following javascript which I found somewhere as an example.
document.addEventListener('DOMContentLoaded', ()=> {
let contentEl = document.querySelector('[name="description"]');
let editorEl = document.querySelector('trix-editor');
document.querySelector('input[type=file]').addEventListener('change', ({ target })=> {
let reader = new FileReader();
reader.addEventListener('load', ()=> {
let image = document.createElement('img');
image.src = reader.result;
let tmp = document.createElement('div');
tmp.appendChild(image);
editorEl.editor.insertHTML(tmp.innerHTML);
target.value = '';
}, false);
reader.readAsDataURL(target.files[0]);
});
// document.querySelector('[role="dump"]').addEventListener('click', ()=> {
// document.querySelector('textarea').value = contentEl.value;
// });
});
This saves the image embedded in the text. I don't want that because large images will take up a lot of space in the database and slow down loading of the editor when I load this data back into it from the database.
It is also ugly having the extra button when Trix has an attachment button in it's toolbar. So, I'd like to be able to click the toolbar button and have it upload or if that is too hard, have the built in toolbar button save the image embedded.
To save the images to a folder instead of embedded, the Trix-editor website says to use this javascript https://trix-editor.org/js/attachments.js
In this javascript I have to provide a HOST so I use
var HOST = "http://localhost:5000/upload/"
and I set up a route in my flask file:
#tickets.post('/_upload/')
def upload():
path = current_app.config['UPLOAD_DIRECTORY']
if request.method == 'POST':
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
flash('No selected file')
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
session["id"] = filename
file.save(os.path.join(path, filename))
return send_from_directory(path, filename)
I can select an image and it shows in the editor and it uploads to the directory on my backend as expected. But when I save the form the location of the image is not in in the document text (should be in there as something like <img src="uploads/image.png>
On the python console I see
"POST /_upload/ HTTP/1.1" 404 -
I can make this go away if I change the return on my route to something like return "200" But all the examples I have seen about uploading files have this or a render_template. I don't want to render a template so I'm using this although I don't really understand what it does.
I'm assuming I need to return something the javasript can use to embed the image link in the document. But I'm a total newbie (like you didn't figure that out already) so I don't know what to do for the return statement (assuming this is where the problem lies).
If anyone else is trying to figure this out this is what I ended up doing.
Still needs a but of tweaking but works.
First I modified the example javascript for uploading to use Fetch instead of XMLHttpRequest
const editor = document.querySelector('trix-editor');
(function() {
HOST = '/_upload/'
addEventListener("trix-attachment-add", function(event) {
if (event.attachment.file) {
uploadFileAttachment(event.attachment)
}
// get rid of the progress bar as Fetch does not support progress yet
// this code originally used XMLHttpRequest instead of Fetch
event.attachment.setUploadProgress(100)
})
function uploadFileAttachment(attachment) {
uploadFile(attachment.file, setAttributes)
function setAttributes(attributes) {
attachment.setAttributes(attributes)
alert(attributes)
}
}
function uploadFile(file, successCallback) {
var key = createStorageKey(file)
var formData = createFormData(key, file)
fetch(HOST, {method: 'POST', body: formData}).then(function(response){
response.json().then(function(data){
alert(data.file, data.status)
if (data.status == 204) {
var attributes = {
url: HOST + key,
href: HOST + key + "?content-disposition=attachment"
}
console.log(attributes)
successCallback(attributes)
}
})
})
}
function createStorageKey(file) {
var date = new Date()
var day = date.toISOString().slice(0,10)
var name = date.getTime() + "-" + file.name
return [day, name ].join("/")
}
function createFormData(key, file) {
var data = new FormData()
data.append("key", key)
data.append("Content-Type", file.type)
data.append("file", file)
return data
}
})();
Then modified my Flask route (which I'll refactor, this was just slapped together to make it work):
def upload():
path = current_app.config['UPLOAD_DIRECTORY']
new_path = request.form["key"].split('/')[0]
file_upload_name = os.path.join(path, request.form["key"])
print(file_upload_name)
upload_path = os.path.join(path, new_path)
if request.method == 'POST':
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
flash('No selected file')
return redirect(request.url)
if file and allowed_file(file.filename):
if not os.path.exists(upload_path):
os.mkdir(upload_path)
filename = secure_filename(file.filename)
session["id"] = filename
attachment = os.path.join(upload_path, filename)
file.save(attachment)
file.close()
os.rename(attachment, file_upload_name)
print(os.listdir(upload_path))
return jsonify({'file': attachment, 'status': 204})
return f'Nothing to see here'
Anyway, I hope that helps as it took me ages to figure out.

Javascript - Editing Form with Fetch

I'm trying to edit an existing form on my site, and save the edits using Javascript (without requiring a refresh of the page). I'm using Django as well.
So far, when the user clicks 'edit' on the page, the form appropriately appears, showing the information already saved there. But when I click 'save' I get a 404 error.
The issue is in the Javascript function edit_post. I'm not sure if I have used stringify correctly either, I'm new to using Javascript with Django. Any help is appreciated.
function edit_handeler(element) {
id = element.getAttribute("data-id");
document.querySelector(`#post-edit-${id}`).style.display = "block";
document.querySelector(`#post-content-${id}`).style.display = "none";
// everything above this works and opens up the form for editing
edit_btn = document.querySelector(`#edit-btn-${id}`);
edit_btn.textContent = "Save";
edit_btn.setAttribute("class", "text-success edit");
if (edit_btn.textContent == "Save") {
edit_post(id, document.querySelector(`#post-edit-${id}`).value); //here
edit_btn.textContent = "Edit";
edit_btn.setAttribute("class", "text-primary edit");
}}
function edit_post(id, post) {
const body = document.querySelector(`#post-content-${id}`).value;
fetch("/edit_post/", {
method: "POST",
body: JSON.stringify({
body:body
})
}).then((res) => {
document.querySelector(`#post-content-${id}`).textContent = post;
document.querySelector(`#post-content-${id}`).style.display = "block";
document.querySelector(`#post-edit-${id}`).style.display = "none";
document.querySelector(`#post-edit-${id}`).value = post.trim();
});
}
Relevant html - this is inside a card, for the post itself in the html file:
<span id="post-content-{{i.id}}" class="post">{{i.text}}</span> <br>
<textarea data-id="{{i.id}}" id="post-edit-{{i.id}}"
style="display:none;" class="form-control textarea" row="3">{{i.text}}</textarea>
<button class="btn-btn primary" data-id="{{i.id}}" id="edit-btn-{{i.id}}"
onclick="edit_handeler(this)" >Edit</button> <br><br>
views.py
def edit_post(request, pk):
post = Post.objects.get(id=pk)
form = PostForm(instance=post)
if request.method == "POST":
form = PostForm(request.POST, instance=post)
if form.is_valid():
form.save()
return JsonResponse({}, status=201) # this works to edit and save to db
else:
if request.method == "GET":
form = PostForm(instance=post)
form_for_post = {'form': PostForm()}
return render(request, "network/make_post.html", {
"post": post,
"form_for_post": form,
})
urls.py (relevant ones)
path('edit_post/<str:pk>/', views.edit_post, name="edit_post"),
path('edit_post/', views.edit_post),
path("profile/<str:username>", views.profile, name="profile"),
Assuming you're running your django server locally and you're getting a 404 returned from your fetch request, then that means the url path does not exist. Either your django server isn't live or the url you're supplying the fetch request with is incorrect. If /edit_post/ is your desired endpoint, try fetching with the servers full request URL
Something like this
http://localhost:8000/edit_post
Replace 8000 with whatever port your server is running at.
Your fetch body is structured properly btw.

How to download a document generated with python-docx in Django project?

I'm trying to figure how to download a word document generated with python-docx in my django app (I'm still learning and this is the first time I working with documents); with the help of ajax I send all the information needed to the view and call a function that uses that information and returns the document, then I'm trying to send this document as response in order to download it with the help of a "Download" button (or show web browser download dialog) in the same template from where I'm submitting the data, but here is where I'm stuck.
to send this document as response in order to download it with the help of a "Download" button (or show web browser download dialog) in the same template from where I'm submitting the data, but here is where I'm stuck.
What I have until now is:
1) In javascript I'm sending the information as follows:
data = {
categoria: cat,
familia: fam,
Gcas: gcas,
FI: FI,
FF: FF,
Test: test,
Grafica: grafica
},
$.ajax({
type: 'post',
headers: {
"X-CSRFToken": csrftoken
},
url: url,
data: { json_data: JSON.stringify(data) },
success: function (response) {
$('#instrucciones').hide(); //Hide a div with a message
$('#btndesc').show(); //Show the button to download the file generated
}
});
return false;
}
2) In my Django view:
def Documento(request):
if request.method == "GET":
context={}
context['form'] = catForm
return render(request, 'report/report_base.html', context)
if request.method == 'POST':
#Data from ajax
datos = request.POST.get('json_data')
jsondata = json.loads(datos)
Gcas = jsondata['Gcas']
FI = jsondata['FI']
FF = jsondata['FF']
grafica = jsondata['Grafica']
#Using function to create the report
Reporte = ReporteWord(Gcas, FI, FF, grafica)
#Response
response = HttpResponse(content_type='application/vnd.openxmlformats-
officedocument.wordprocessingml.document')
response['Content-Disposition'] = 'attachment; filename = "Reporte.docx"'
response['Content-Encoding'] = 'UTF-8'
Reporte.save(response)
return response
3) My function to create the document looks like:
def ReporteWord( gcas, FI, FF, Chart):
#Cargamos el template
template = finders.find('otros/Template_reporte.docx')
document = Document(template)
#Header
logo = finders.find('otros/logo.png')
header = document.sections[0].header
paragraph = header.paragraphs[0]
r = paragraph.add_run()
r.add_picture(logo)
#Adding title
titulo = document.add_heading('', 0)
titulo.add_run('Mi reporte').bold = True
titulo.style.font.size=Pt(13)
.
Many other steps to add more content
.
.
#IF I SAVE THE FILE NORMALLY ALL WORKS FINE
#document.save(r'C:\tests\new_demo.docx')
return document
I'll be very grateful for any idea or suggestion, many thanks in advance.
NOTE: I've reviewed these answers (and others) without luck.
Q1, Q2, Q3, Q4
UPDATE: Thanks to the feedback received I finally found how to generate the document and show the download dialog:
As was suggested the best way to achieve its using the view and not ajax, so the final updates in the code are:
a) Update view to work as show in feedback
b) JavaScript - Ajax control for POST method was removed and now all is handled directly with python (no extra code needed)
1) View:
def Reporte(request):
if request.method == "GET":
context={}
context['form'] = catForm
return render(request, 'reportes/reporte_base.html', context)
if request.method == 'POST':
#Getting data needed after submit the form in the page
GcasID = request.POST.get('GCASS')
FI = request.POST.get('dp1')
FF = request.POST.get('dp2')
Grafica = request.POST.get('options')
#Function to obtain complete code from GcasID
Gcas = GcasNumber(GcasID)
#Report creation
Reporte = ReporteWord(Gcas, FI, FF, Grafica)
#PART UPDATED TO SHOW DOWNLOAD REPORT DIALOG
bio = io.BytesIO()
Reporte.save(bio) # save to memory stream
bio.seek(0) # rewind the stream
response = HttpResponse(
bio.getvalue(), # use the stream's contents
content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
)
response["Content-Disposition"] = 'attachment; filename = "Reporte.docx"'
response["Content-Encoding"] = "UTF-8"
return response
With those changes now when I press "Create report" (submit button of form) all works as expected (as a plus no more libraries are necessary). At the end as you suggested its easier do it in this way than using ajax.
Many thanks to all for your kind help.
Python-docx's Document.save() method accepts a stream instead of a filename. Thus, you can initialize an io.BytesIO() object to save the document into, then dump that to the user.
Reporte = ReporteWord(Gcas, FI, FF, grafica)
bio = io.BytesIO()
Reporte.save(bio) # save to memory stream
bio.seek(0) # rewind the stream
response = HttpResponse(
bio.getvalue(), # use the stream's contents
content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
)
response["Content-Disposition"] = 'attachment; filename = "Reporte.docx"'
response["Content-Encoding"] = "UTF-8"
return response
This will work if you use a regular link or a form to submit the request, but since you're using $.ajax, you may need to do additional work on the browser end to have the client download the file. It would be easier not to use $.ajax.
Yep, a cleaner options, as stated by wardk would be, using https://python-docx.readthedocs.org/:
from docx import Document
from django.http import HttpResponse
def download_docx(request):
document = Document()
document.add_heading('Document Title', 0)
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document')
response['Content-Disposition'] = 'attachment; filename=download.docx'
document.save(response)
return response
Know more

Get PDF from WebAPI and download from UI, but data gets corrupted

I call a Web API Controller from my UI which then gets a report from SSRS. It inserts the bytes in the content of the response and sends it to the UI where it gets downloaded as a PDF.
Inside my Web API Controller I write the report bytes to a test PDF file to inspect the contents of the pdf and to see if the data is correct, which it is. But when the PDF gets downloaded from my UI and I open it, I get a blank paged document. When I inspect the reponse content in Fiddler, I can see that the data is corrupted and doesn't match the test PDF file data.
Server side:
[HttpPost]
public HttpResponseMessage GetInstancePdf(InstancePdfModel model) {
var bytes = _digitalFormService.GetInstancePdf(model.ClientGuid, model.InstanceGuid, model.InstanceVersion);
File.WriteAllBytes(# "c:\temp\test.pdf", bytes);
var response = Request.CreateResponse();
response.Content = new ByteArrayContent(bytes);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue(DispositionTypeNames.Inline) {
FileName = "file.pdf"
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return response;
}
Client side:
$scope.downloadPdf = function(instance) {
$scope.isBusy = true;
digitalFormService.getInstancePdf(instance.instanceGuid, instance.instanceVersion).then(function(data) {
if (data.status === 200) {
const file = new Blob([data.data], {
type: data.headers("Content-Type")
});
if (navigator.appVersion.toString().indexOf(".NET") > 0) {
window.navigator.msSaveBlob(file, (`${instance.name} ${(new Date()).toLocaleString()}`).replace(",", ""));
} else {
//trick to download, store a file having its URL
const fileUrl = URL.createObjectURL(file);
const a = document.createElement("a");
a.href = fileUrl;
a.target = "_blank";
a.download = (`${instance.name} ${(new Date()).toLocaleString()}`).replace(",", "");
document.body.appendChild(a);
a.click();
}
} else {
debugger;
}
$scope.isBusy = false;
});
};
function getInstancePdf(instanceGuid, instanceVersion) {
var data = {
clientGuid: digitalFormConfig.clientToken,
instanceGuid: instanceGuid,
instanceVersion: instanceVersion
};
return $http({
url: digitalFormConfig.serverUrl +
"api/DigitalForm/GetInstancePdf",
dataType: "json",
data: data,
method: "POST"
}).then(function(response) {
return response;
},
function() {
return $q.reject("No Data");
});
}
I expect my downloaded PDF to be an informational document, matching the test PDF file saved inside the Web API Controller, but I get a blank document instead (same number of pages as test file, but blank).
I used Fiddler to inspect the response body. When I save the response body from within Fiddler as a pdf - everything is fine. So I am sure my server side code is correct. The problem must be somewhere on the client side.
Any help? Thanks.
I found the mistake. The bug was in the client side service. Code should look as follows:
function getInstancePdf(instanceGuid, instanceVersion) {
var data = {
clientGuid: digitalFormConfig.clientToken,
instanceGuid: instanceGuid,
instanceVersion: instanceVersion
};
return $http({
responseType: "arraybuffer",
url: digitalFormConfig.serverUrl +
"api/DigitalForm/GetInstancePdf",
dataType: "json",
data: data,
method: "POST"
}).then(function (response) {
return response;
},
function () {
return $q.reject("No Data");
});
}
The line responseType: "arraybuffer", was omitted previously.

Facebook Graph API - upload photo using JavaScript

Is it possible to upload a file using the Facebook Graph API using javascript, I feel like I'm close. I'm using the following JavaScript
var params = {};
params['message'] = 'PicRolled';
params['source'] = '#'+path;
params['access_token'] = access_token;
params['upload file'] = true;
function saveImage() {
FB.api('/me/photos', 'post', params, function(response) {
if (!response || response.error) {
alert(response);
} else {
alert('Published to stream - you might want to delete it now!');
}
});
}
Upon running this I receive the following error...
"OAuthException" - "(#324) Requires upload file"
When I try and research this method all I can find out about is a php method that apears to solve this
$facebook->setFileUploadSupport(true);
However, I am using JavaScript, it looks like this method might be to do with Facebook Graph permissions, but I already have set the permissions user_photos and publish_stream, which I believed are the only ones I should need to perform this operation.
I have seen a couple of unanswered questions regarding this on stackoverflow, hopefully I can explained myself enough. Thanks guys.
Yes, this is possible, i find 2 solutions how to do that and they are very similar
to each other, u need just define url parameter to external image url
FIRST one using Javascript SDk:
var imgURL="http://farm4.staticflickr.com/3332/3451193407_b7f047f4b4_o.jpg";//change with your external photo url
FB.api('/album_id/photos', 'post', {
message:'photo description',
url:imgURL
}, function(response){
if (!response || response.error) {
alert('Error occured');
} else {
alert('Post ID: ' + response.id);
}
});
and SECOND one using jQuery Post request and FormData:
var postMSG="Your message";
var url='https://graph.facebook.com/albumID/photos?access_token='+accessToken+"&message="+postMSG;
var imgURL="http://farm4.staticflickr.com/3332/3451193407_b7f047f4b4_o.jpg";//change with your external photo url
var formData = new FormData();
formData.append("url",imgURL);
$.ajax({
url: url,
data: formData,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
alert("POST SUCCESSFUL");
}
});
EDIT: this answer is (now) largely irrelevant. If your image is on the web, just specify the url param as per the API (and see examples in other answers). If you would like to POST the image content to facebook directly, you may want to read this answer to gain understanding. Also see HTML5's Canvas.toDataUrl().
The API says: "To publish a photo, issue a POST request with the photo file attachment as multipart/form-data."
FB is expecting that the bytes of the image to be uploaded are in the body of the HTTP request, but they're not there. Or to look at it another way - where in the FB.api() call are you supplying the actual contents of the image itself?
The FB.api() API is poorly documented, and doesn't supply an example of an HTTP POST which includes a body. One might infer from the absence of such an example that it doesn't support this.
That's probably OK - FB.api() is using something called XmlHttpRequest under the covers which does support including a body ... look it up in your favourite JavaScript reference.
However, you'll still have 2 sub-problems to solve:
how to prepare the image bytes (and rest of the request) as multipart/form-data; and
getting the bytes of the image itself
(incidentally, the need to encode the message body is probably what the PHP setFileUploadSupport(true) method is for - tell the facebook object to encode the message body as multipart/form-data before sending)
But it's a bit meessier than that
Unfortunately, sub-problem '2' may bite you - there is no way (last time I looked) to extract the bytes of an image from the browser-supplied Image object.
If the image to be uploaded is accessible via a URL, you could fetch the bytes with XmlHttpRequest. Not too bad.
If the image is coming from the user's desktop, your probable recourse is to offer the user a:
<form enctype="multipart/form-data">
<input type="filename" name="myfile.jpg" />
<input type="hidden" name="source" value="#myfile.jpg"/>
<input type="hidden" name="message" value="My Message"/>
<input type="hidden" name="access_token" value="..."/>
</form>
(notice that source references the name given to the file-upload widget)
... and hope that FB anticipated receiving the data in this manner (try it with a static HTML form first, before coding it up dynamically in JS). One might infer that in fact it would, since they don't offer another means of doing it.
i used #Владимир Дворник code with some modification, I had the same issue and with this code it worked very well:
var imgURL = //your external photo url
FB.api('/photos', 'post', {
message: 'photo description',
access_token: your accesstoken
url: imgURL
}, function (response) {
if (!response || response.error) {
alert('Error occured:' + response);
} else {
alert('Post ID: ' + response.id);
}
});
Photos can be uploaded to facebook profile using Ajax as follows.
$.ajax({
type: "POST",
url: "https://graph.facebook.com/me/photos",
data: {
message: "Your Msg Goes Here",
url: "http://www.knoje.com/images/photo.jpg[Replace with yours]",
access_token: token,
format: "json"
},
success: function(data){
alert("POST SUCCESSFUL"); }
});
So this is the best way to post photo to a facebook profile with GRAPH API and is the simple one.
In many answer i have seen that image url is shwon by the source,picture or image etc but that doesn't works.
The use of of source,picture or image leads to a (#324) Requires upload file error .
Best way to avoid the 324 error.
Only #Thiago's answer is answering the question of uploading data via javascript. I've found that the Facebook JS API doesn't cover this situation.
I've also brew & tested my personl solution.
Main steps
Get the binary data of the image (I've used a canvas, but using an input box is possible as well)
Form a multipart request with all necesarry data for the graph API call
Include the binary data in the request
Encode everything in a binary array and send it so via XHR
Code
Conversion utilities
var conversions = {
stringToBinaryArray: function(string) {
return Array.prototype.map.call(string, function(c) {
return c.charCodeAt(0) & 0xff;
});
},
base64ToString: function(b64String) {
return atob(b64String);
}
};
Image posting snippet
var DEFAULT_CALL_OPTS = {
url: 'https://graph.facebook.com/me/photos',
type: 'POST',
cache: false,
success: function(response) {
console.log(response);
},
error: function() {
console.error(arguments);
},
// we compose the data manually, thus
processData: false,
/**
* Override the default send method to send the data in binary form
*/
xhr: function() {
var xhr = $.ajaxSettings.xhr();
xhr.send = function(string) {
var bytes = conversions.stringToBinaryArray(string);
XMLHttpRequest.prototype.send.call(this, new Uint8Array(bytes).buffer);
};
return xhr;
}
};
/**
* It composes the multipart POST data, according to HTTP standards
*/
var composeMultipartData = function(fields, boundary) {
var data = '';
$.each(fields, function(key, value) {
data += '--' + boundary + '\r\n';
if (value.dataString) { // file upload
data += 'Content-Disposition: form-data; name=\'' + key + '\'; ' +
'filename=\'' + value.name + '\'\r\n';
data += 'Content-Type: ' + value.type + '\r\n\r\n';
data += value.dataString + '\r\n';
} else {
data += 'Content-Disposition: form-data; name=\'' + key + '\';' +
'\r\n\r\n';
data += value + '\r\n';
}
});
data += '--' + boundary + '--';
return data;
};
/**
* It sets the multipart form data & contentType
*/
var setupData = function(callObj, opts) {
// custom separator for the data
var boundary = 'Awesome field separator ' + Math.random();
// set the data
callObj.data = composeMultipartData(opts.fb, boundary);
// .. and content type
callObj.contentType = 'multipart/form-data; boundary=' + boundary;
};
// the "public" method to be used
var postImage = function(opts) {
// create the callObject by combining the defaults with the received ones
var callObj = $.extend({}, DEFAULT_CALL_OPTS, opts.call);
// append the access token to the url
callObj.url += '?access_token=' + opts.fb.accessToken;
// set the data to be sent in the post (callObj.data = *Magic*)
setupData(callObj, opts);
// POST the whole thing to the defined FB url
$.ajax(callObj);
};
Usage
postImage({
fb: { // data to be sent to FB
caption: caption,
/* place any other API params you wish to send. Ex: place / tags etc.*/
accessToken: 'ACCESS_TOKEN',
file: {
name: 'your-file-name.jpg',
type: 'image/jpeg', // or png
dataString: image // the string containing the binary data
}
},
call: { // options of the $.ajax call
url: 'https://graph.facebook.com/me/photos', // or replace *me* with albumid
success: successCallbackFunction,
error: errorCallbackFunction
}
});
Extra
Extracting the binary string representation of a canvas image
var getImageToBeSentToFacebook = function() {
// get the reference to the canvas
var canvas = $('.some-canvas')[0];
// extract its contents as a jpeg image
var data = canvas.toDataURL('image/jpeg');
// strip the base64 "header"
data = data.replace(/^data:image\/(png|jpe?g);base64,/, '');
// convert the base64 string to string containing the binary data
return conversions.base64ToString(data);
}
Information on how to load the binaryString from an input[type=file]
HTML5 File API read as text and binary
Notes:
There are of course alternative approaches as well
Using an HTML form in an iframe - you cannot get the response from the call
Using a FormData & File approach, but unfortunately in this case there are a lot of incompatilities which make the process harder to use, and you would end up duct-taping around the inconsistencies - thus my choice was manual data assembly since HTTP standards rarely change :)
The solution does not require any special HTML5 features.
The above example uses jQuery.ajax, jQuery.extend, jQuery.each
Yes, you can do this posting data to an iframe like here, or you can use jQuery File Upload .
The problem is you can't get response from iframe, using plugin you can use a page handle.
Example: upload a video using jQuery File Upload
<form id="fileupload" action="https://graph-video.facebook.com/me/photos" method="POST" enctype="multipart/form-data">
<input type="hidden" name="acess_token" value="user_acess_token">
<input type="text" name="title">
<input type="text" name="description">
<input type="file" name="file"> <!-- name must be file -->
</form>
<script type="text/javascript">
$('#fileupload').fileupload({
dataType: 'json',
forceIframeTransport: true, //force use iframe or will no work
autoUpload : true,
//facebook book response will be send as param
//you can use this page to save video (Graph Api) object on database
redirect : 'http://pathToYourServer?%s'
});
</script>
To upload a file from the local computer with just Javascript try HelloJS
<form onsubmit="upload();">
<input type="file" name="file"/>
<button type="submit">Submit</button>
</form>
<script>
function upload(){
hello.api("facebook:/me/photos", 'post', document.getElementById('form'), function(r){
alert(r&&!r.error?'Success':'Failed');
});
}
</script>
There's an upload demo at http://adodson.com/hello.js/demos/upload.html
https://stackoverflow.com/a/16439233/68210 contains a solution that works if you need to upload the photo data itself and don't have a url.
This still works. I am using it as below:
var formdata= new FormData();
if (postAs === 'page'){
postTo = pageId; //post to page using pageID
}
formdata.append("access_token", accessToken); //append page access token if to post as page, uAuth|paAuth
formdata.append("message", photoDescription);
formdata.append("url", 'http://images/image.png');
try {
$.ajax({
url: 'https://graph.facebook.com/'+ postTo +'/photos',
type: "POST",
data: formdata,
processData: false,
contentType: false,
cache: false,
error: function (shr, status, data) {
console.log("error " + data + " Status " + shr.status);
},
complete: function () {
console.log("Successfully uploaded photo to Facebook");
}
});
} catch (e) {
console.log(e);
}
I have to ask though if you people have any idea if this is advisable or has a big security risk compared to using PHP api for Facebook.
This works:
function x(authToken, filename, mimeType, imageData, message) {
// this is the multipart/form-data boundary we'll use
var boundary = '----ThisIsTheBoundary1234567890';
// let's encode our image file, which is contained in the var
var formData = '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="source"; filename="' + filename + '"\r\n';
formData += 'Content-Type: ' + mimeType + '\r\n\r\n';
for (var i = 0; i < imageData.length; ++i) {
formData += String.fromCharCode(imageData[i] & 0xff);
}
formData += '\r\n';
formData += '--' + boundary + '\r\n';
formData += 'Content-Disposition: form-data; name="message"\r\n\r\n';
formData += message + '\r\n';
formData += '--' + boundary + '--\r\n';
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://graph.facebook.com/me/photos?access_token=' + authToken, true);
xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
// Solving problem with sendAsBinary for chrome
try {
if (typeof XMLHttpRequest.prototype.sendAsBinary == 'undefined') {
XMLHttpRequest.prototype.sendAsBinary = function(text) {
var data = new ArrayBuffer(text.length);
var ui8a = new Uint8Array(data, 0);
for (var i = 0; i < text.length; i++) ui8a[i] = (text.charCodeAt(i) & 0xff);
this.send(ui8a);
}
}
} catch (e) {}
xhr.sendAsBinary(formData);
};
I seem to have a similar problem, but solutions above didn't work.
I was using the solution suggested by Arrabi (just use the url property only) to post images without any problem. My images are around 2-3 MB each.
When I migrated my app to another server (changing the absolute url of my images in the post) the method kept giving me 324 errors for images above around 100k size.
I thought it was due to some Apache setting on my end, but when I changed apache for lighttpd the problem was still there.
The connections from Facebook actually show up in my (apache) log:
69.171.234.7 - - [08/Jun/2012:11:35:54 +0200] "GET /images/cards/1337701633_518192458.png HTTP/1.1" 200 2676608 "-" "facebookplatform/1.0 (+http://developers.facebook.com)"
69.171.228.246 - - [08/Jun/2012:11:42:59 +0200] "GET /images/test5.jpg HTTP/1.1" 200 457402 "-" "facebookplatform/1.0 (+http://developers.facebook.com)"
69.171.228.246 - - [08/Jun/2012:11:43:17 +0200] "GET /images/test4.jpg HTTP/1.1" 200 312069 "-" "facebookplatform/1.0 (+http://developers.facebook.com)"
69.171.228.249 - - [08/Jun/2012:11:43:49 +0200] "GET /images/test2.png HTTP/1.1" 200 99538 "-" "facebookplatform/1.0 (+http://developers.facebook.com)"
69.171.228.244 - - [08/Jun/2012:11:42:31 +0200] "GET /images/test6.png HTTP/1.1" 200 727722 "-" "facebookplatform/1.0 (+http://developers.facebook.com)"
Only test2.png succeeded.
I use the following to share a photo (some BitmapData from the Phaser framework). It seems to work...
// Turn data URI to a blob ready for upload.
dataURItoBlob(dataURI:string): Blob {
var byteString = atob(dataURI.split(',')[1]);
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ab], { type: 'image/jpeg' });
}
// Share the given bitmapData as a photo on Facebook
sharePhoto(accessToken: string, photo: BitmapData, message: string): void {
// Create form data, set up access_token, source and message
var fd = new FormData();
fd.append("access_token", accessToken);
fd.append("source", this.dataURItoBlob(photo.canvas.toDataURL("image/jpeg")));
fd.append("message", message);
var request = new XMLHttpRequest();
var thisPtr = this;
request.onreadystatechange = function () {
if (request.readyState == XMLHttpRequest.DONE) {
var json = JSON.parse(request.responseText);
if (json.hasOwnProperty("error")) {
var error = json["error"];
if (error.hasOwnProperty("type")) {
var errorType = error["type"];
if (errorType === "OAuthException") {
console.log("Need to request more permissions!");
}
}
}
} else if (request.readyState == XMLHttpRequest.HEADERS_RECEIVED) {
} else if (request.readyState == XMLHttpRequest.LOADING) {
} else if (request.readyState == XMLHttpRequest.OPENED) {
} else if (request.readyState == XMLHttpRequest.UNSENT) {
}
}
request.open("POST", "https://graph.facebook.com/me/photos", true);
request.send(fd);
}
In case anyone still looking for how to upload directly from canvas to Facebook photos, this works for me:
function postImageToFacebook(token, imageData, message, successCallback, errorCallback) {
var fd = new FormData();
fd.append("access_token", token);
fd.append("source", imageData);
fd.append("caption", message);
$.ajax({
url: "https://graph.facebook.com/me/photos?access_token=" + token,
type: "POST",
data: fd,
processData: false,
contentType: false,
cache: false,
success: function (data) {
successCallback(data);
},
error: function (shr, status, data) {
errorCallback(data);
},
complete: function (data) {
console.log('Completed');
}
});
}
function dataURItoBlob(dataURI) {
var byteString = atob(dataURI.split(',')[1]);
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ab], {type: 'image/jpeg'});
}
To use it
// *IMPORTANT*
var FBLoginScope = 'publish_actions'; // or sth like 'user_photos,publish_actions' if you also use other scopes.
var caption = "Hello Facebook!";
var successCallback = ...;
var errorCallback = ...;
var data = $('#your_canvas_id')[0].toDataURL("image/jpeg");
try {
imageData = dataURItoBlob(data);
} catch (e) {
console.log(e);
}
FB.getLoginStatus(function (response) {
if (response.status === "connected") {
postImageToFacebook(response.authResponse.accessToken, imageData, caption, successCallback, errorCallback);
} else if (response.status === "not_authorized") {
FB.login(function (response) {
postImageToFacebook(response.authResponse.accessToken, imageData, caption, successCallback, errorCallback);
}, {scope: FBLoginScope});
} else {
FB.login(function (response) {
postImageToFacebook(response.authResponse.accessToken, imageData, caption, successCallback, errorCallback);
}, {scope: FBLoginScope});
}
});
Modified from: http://gorigins.com/posting-a-canvas-image-to-facebook-and-twitter/

Categories