Upload properly encoded (base64?) image to SharePoint with AngularJS - javascript

I am able to upload an image file to SharePoint, but it is not being recognized as an image. I have tried utilizing the following directive based on research that states images need to be base64 encoded when uploaded into SharePoint, but it still uploads a file that appears to be corrupt: https://github.com/adonespitogo/angular-base64-upload
I am happy to use this directive, but am unsure of how to pass what I need into SharePoint's REST API.
The original iteration I had does not use this directive, but is more of a straight upload attempt.
What I need to achieve is as follows:
1) Successfully upload an image without it being "corrupted", and does this require base64 encoding/how do I achieve this?
2) Upload images by their name (not "test.jpg") and have some metadata (ex. upload an image with the title or department name it belongs to)
Iteration 1: No Directive
Here is my HTML (Please note that the controller is tied to the page via ng-route):
<div class="col-md-12">
<form>
<input type="file" onchange="angular.element(this).scope().filesChanged(this)" data-ng-model="files" multiple>
<button data-ng-click="upload()">Submit</button>
<li data-ng-repeat="file in files">{{file.name}}</li>
</form>
</div>
Here is my controller:
$scope.filesChanged = function (elm) {
$scope.files = elm.files
$scope.$apply();
}
$scope.upload = function () {
var fd = new FormData()
angular.forEach($scope.files,function(file){
fd.append('file',file)
})
$http.post("/sites/asite/_api/web/lists/getByTitle('Images')/RootFolder/Files/add(url='test.jpg',overwrite='true')", fd,
{
transformRequest: angular.identity,
headers: {
'Content-Type':undefined, 'X-RequestDigest': $("#__REQUESTDIGEST").val()}
}).success(function (d) {
console.log(d);
});
}
UPDATE: I believe the issue is isolated to my $http post to SharePoint. Using the directive mentioned above, I am able to output the base64, but am unsure how to pass this into my post for upload.
Iteration 2: Using Directive
Here is my current HTML using the https://github.com/adonespitogo/angular-base64-upload directive:
<form>
<input type="file" data-ng-model="files" base-sixty-four-input>
<button data-ng-click="upload()">Submit</button>
</form>
My controller that is posting the corrupted image files to SharePoint:
$scope.upload = function () {
console.log($scope.files); // Output result from upload directive
$http({
method: 'POST',
url: "/sites/ens/_api/web/lists/getByTitle('Report Images')/RootFolder/Files/add(url='" + $scope.files.filename +"',overwrite='true')",
headers: {
'Content-Type': false ,
'X-RequestDigest': $("#__REQUESTDIGEST").val()
},
data: $scope.files,
}).success(function (data) {
console.log(data);
});
}
Update 2: Using SP.RequestExecutor as follows creates the same result. A file upload but not rendering. This happens for images and documents:
Iteration 3: Using Directive and SP.RequestExecutor
$scope.upload = function () {
var dataURL = 'data:' + $scope.files.filetype + ';' + 'base64,' + $scope.files.base64;
var createitem = new SP.RequestExecutor("/sites/asite");
createitem.executeAsync({
url: "/sites/asite/_api/web/lists/getByTitle('Images')/RootFolder/Files/add(url='" + $scope.files.filename + "')",
method: "POST",
binaryStringRequestBody: true,
body: dataURL,
success: fsucc,
error: ferr,
state: "Update"
});
function fsucc(data) {
alert('success');
}
function ferr(data) {
alert('error\n\n' + data.statusText + "\n\n" + data.responseText);
}
}
Update 3: Using .ajax as follows, it will successfully post the image, but when using $http, it corrupts the image.
Iteration 3: Using .Ajax (works)
function uploadFileSync(spWebUrl , library, filename, file)
{
var reader = new FileReader();
reader.onloadend = function(evt)
{
if (evt.target.readyState == FileReader.DONE)
{
var buffer = evt.target.result;
var completeUrl = spWebUrl
+ "/_api/web/lists/getByTitle('"+ library +"')"
+ "/RootFolder/Files/add(url='"+ filename +"',overwrite='true')?"
+ "#TargetLibrary='"+library+"'&#TargetFileName='"+ filename +"'";
$.ajax({
url: completeUrl,
type: "POST",
data: buffer,
async: false,
processData: false,
headers: {
"accept": "application/json;odata=verbose",
"X-RequestDigest": $("#__REQUESTDIGEST").val(),
"content-length": buffer.byteLength
},
complete: function (data) {
//uploaded pic url
console.log(data.responseJSON.d.ServerRelativeUrl);
$route.reload();
},
error: function (err) {
alert('failed');
}
});
}
};
reader.readAsArrayBuffer(file);
}
Iteration 4: Using $http (corrupts image)
function uploadFileSync(spWebUrl , library, filename, file)
{
var reader = new FileReader();
reader.onloadend = function (evt) {
if (evt.target.readyState == FileReader.DONE) {
var buffer = evt.target.result;
var completeUrl = spWebUrl
+ "/_api/web/lists/getByTitle('" + library + "')"
+ "/RootFolder/Files/add(url='" + filename + "',overwrite='true')?"
+ "#TargetLibrary='" + library + "'&#TargetFileName='" + filename + "'";
$http({
url: completeUrl,
method: "POST",
data: buffer,
processData: false,
headers: {
"accept": "application/json;odata=verbose",
"X-RequestDigest": $("#__REQUESTDIGEST").val(),
"content-length": buffer.byteLength
}
}).success(function (data) {
//uploaded pic url
//console.log(data.responseJSON.d.ServerRelativeUrl);
$route.reload();
}).error(function (err) {
alert(err);
});
}
};
reader.readAsArrayBuffer(file);
}

Yes, you must do the base64 encoding.
Following this article, your filesChanged will be function for base64 encoding:
$scope.filesChanged = function (input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
//Sets the Old Image to new New Image
$('#photo-id').attr('src', e.target.result);
//Create a canvas and draw image on Client Side to get the byte[] equivalent
var canvas = document.createElement("canvas");
var imageElement = document.createElement("img");
imageElement.setAttribute('src', e.target.result);
canvas.width = imageElement.width;
canvas.height = imageElement.height;
var context = canvas.getContext("2d");
context.drawImage(imageElement, 0, 0);
var base64Image = canvas.toDataURL("image/jpeg");
//Removes the Data Type Prefix
//And set the view model to the new value
$scope.data.Photo = base64Image.replace(/data:image\/jpeg;base64,/g, '');
}
//Renders Image on Page
reader.readAsDataURL(input.files[0]);
}
};
My advice to you is also to change ng-model from $scope.files to $scope.data.Photo to avoid problems with scope and add an id in your input tag. (in this case id="photo-upload")
So, your HTML for upload will look like:
<input type="file" onchange="angular.element(this).scope().filesChanged(this)" data-ng-model="data.Photo" id="photo-upload" multiple>
And for representing your uploaded pic, in your case, you can use this:
<img ng-src="data:image/jpeg;base64,{{data.Photo}}" id="photo-id"/>
I'm just not sure for multiple upload, but for single upload it works great for me.
Hope this will help you to solve your problem with SharePoint.
Good luck!

Related

upload canvas image to Laravel

I have a canvas image generated by some JS on my page that I'm trying to upload to my server using Laravel, my controller code:
public function imageUploadAPI(Request $request)
{
$imageName = time().'.'.$request->image->getClientOriginalName();
$request->image->move('/home/XXXXX/storage/app/public/images/'.$request->project_id.'/'.$request->report_id.'/', $imageName);
return response()->json(['success'=>['You have successfully upload image.'],'image'=>[$request->project_id.'/'.$request->report_id.'/'.$imageName]]);
}
Im trying to send in as a blob but it did NOT work, any ideas?
my code:
function uploadImage() {
var canvas = document.getElementById('snapshot');
canvas.toBlob((blob) => {
const newImg = document.createElement('img');
const url = URL.createObjectURL(blob);
newImg.onload = () => {
URL.revokeObjectURL(url);
};
var formdata = new FormData($("#addForm")[0]);
formdata.append('image', url);
$.ajax({
url : "/api/image-upload",
type: "POST",
data : formdata,
processData: false,
contentType: false,
success:function(data){
console.log(data.image);
},
error:console.log('Error'),
});
});
}
One thing i know is laravel need file instance to upload image using laravel storage helpers so check if your upload is file instance or not. Here is a solution if your uploaded file is base64 and you want to change it to file instance How to convert base64 image to UploadedFile Laravel

Laravel - Get data from Ajax Post request

I want to upload images with ajax. I convert the image to a base64 string. Posting the data works, but how do I get it on the server (I'm using Laravel 5.4)? I can do $request->all() what gives me an array with all the images base64 strings combined. I cannot do anything with that because whatever I do with that array will result in a 500 error.
This is my script to convert the images and post them.
let queue = [];
function addFile(input) {
let files = input.files,
j = files.length,
file;
for (i = 0; i < j; i += 1) {
let reader = new FileReader();
reader.onloadend = function (e) {
$('#upload-InnerPanel').append(
"<div class='upload-ItemPanel'><img class='upload-ImagePreview' src='" + e.target.result + "' > </div>");
queue.push(reader.result);
};
file = files[i];
reader.readAsDataURL(file);
}
}
$('#upload-ButtonSelect').on("click" , function () {
$('#upload-UploadInput').click();
});
$('#upload-UploadInput').change(function () {
addFile(this);
});
$('#upload-ButtonUpload').click(function () {
$.ajax({
url: "/admin/upload",
type: "POST",
data: queue,
processData: false,
error: function(xhr, status, error) {
let err = xhr.responseText;
//console.log(err);
$('#upload-InnerPanel').append("<iframe width='600' height='500' src='" + err +"'> </iframe>")
},
success: function (xhr) {
console.log(xhr);
},
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
});
This is my controller:
public function upload(Request $request)
{
return var_dump($request->all());
}
That works, sort of, because My response is one long base64 in an array with just 1 item. Even if I add multiple images I just get one item in the array instead of three. It now combines them all in to one. Also, as I said. I cannot do anything with that array what does not result in a 500 error.
So my question is:
How do I get it to work so I can post multiple items instead of one and get the data on the backend?
You could add each file as a new input on your form, so that you have them separately on the back end.
In your addFile Javascript, instead of queue.push(reader.result), append a hidden input with the results:
reader.onloadend = function (e) {
// ... your code
// Update the form selector to suit your form
$('form').append('<input type="hidden" name="files[]" value="' + reader.result + '">');
};
Then in your ajax form submission:
$('#upload-ButtonUpload').click(function () {
// Again update the form selector to suit your form
var data = $('form').serialize();
$.ajax({
url: "/admin/upload",
type: "POST",
data: data,
// ... etc

JS The downloaded archive throws "the archive is either unknown format or damaged"

I keep getting the problem with downloaded zip file. All the time when I click on the archive it throws "Archive is either unknown format or damaged". I think the problem is with the coding (format of the content of the archive). Please help!
$.ajax({
url: '/Unloading/' + $("#radioVal:checked").val(),
type: "POST",
data: { 'dateTimeTo': $("#dateTimeTo").val(), 'dateTimeFrom': $("#dateTimeFrom").val() },
beforeSend: function() {$("#divLoading").show();},
success: function (result) {
$("#divLoading").hide();
if (result.length === 0) {
var message ="Error";
$("#dialog-message").text(message);
$("#dialog-message").dialog({
modal: true,
buttons: {
close: function() {
$(this).dialog("close");
}
}
});
} else {
var xmlstr, filename, bb;
filename = "UnloadedLeases.zip";
bb = new Blob([result], { type: "application/zip" }); // I think somewhere here is a problem with the coding
var pom = document.createElement('a');
pom.setAttribute("target", "_blank");
pom.setAttribute('href', window.URL.createObjectURL(bb));
pom.setAttribute("download", filename);
document.body.appendChild(pom);
pom.click();
document.body.removeChild(pom); //removing the element a from the page
}
},
As far as I know, $.ajax doesn't let you download binary content out of the box (it will try to decode your binary from UTF-8 and corrupt it). Either use a jQuery plugin (like jquery.binarytransport.js) or use a xhr directly (with responseType).
$.ajax({
url: '/Unloading/' + $("#radioVal:checked").val(),
type: "POST",
dataType: 'binary', // using jquery.binarytransport.js
// ...
success: function (result) {
// Default response type is blob
if (result.size === 0) { // instead of length for blobs
// ...
} else {
var bb = result; // already a blob
// ...
}
}
})

How to copy file using Rest API and javascript in Sharepoint 2013 between site and subsite

I need to copy file between document libraries. Library A is located in one site and Library B is located in subsite. I know how to copy file between libraries on the same level but the problem is with copying between different level.
The code I use to copy file between libraries on the same level.
$.ajax({
url : "http://xxx/PWA/_api/web/folders/GetByUrl('/PWA/CopyFromLibrary')/Files/getbyurl('Import.csv')/copyTo(strNewUrl = '/PWA/TargetLibrary/Import.csv',bOverWrite = true)",
method: 'POST',
headers: {
"Accept": "application/json; odata=verbose",
"X-RequestDigest": $("#__REQUESTDIGEST").val()
},
success: function () {
alert("Success! Your file was copied properly");
},
error: function () {
alert("Problem with copying");
}
});
For different level I use just another target URL:
url : "http://xxx/PWA/_api/web/folders/GetByUrl('/PWA/CopyFromLibrary')/Files/getbyurl('Import.csv')/copyTo(strNewUrl = '/PWA/Subsite/TargetLibrary/Import.csv',bOverWrite = true)",
And it doesn't work.
How to work around this problem?
Just figured this one out today for the cross site solution. The trick is-- don't use $.ajax for the download of the document. Use good old XMLHttpRequest. The reason is that JQuery simply doesn't let you get a raw binary data array from SharePoint. But, the XMLHttpRequest does because it allows you to get an arraybuffer as part of its implementation, which SharePoint accepts!
The following is the code with the parts identified for building the full source and target REST urls. Note that you can use $.ajax to upload the file.
sourceSite is a sharepoint site suitable for appending the '_api' rest endpoint
sourceFolderPath is the relative folder path your document is located within
sourceFileName is the filename of the document
targetSite, targetFolderPath and targetFileName are the mirror images or source, only for the destination.
requestDigest is that special value you need for SharePoint to accept updates.
function copyDocument(sourceSite, sourceFolderPath, sourceFileName, targetSite, targetFolderPath, targetFileName, requestDigest) {
var sourceSiteUrl = sourceSite + "_api/web/GetFolderByServerRelativeUrl('" + sourceFolderPath + "')/Files('" + sourceFileName + "')/$value";
var targetSiteUrl = targetSite + "_api/web/GetFolderByServerRelativeUrl('" + targetFolderPath + "')/Files/Add(url='" + targetFileName + "',overwrite=true)";
var xhr = new XMLHttpRequest();
xhr.open('GET', sourceSiteUrl, true);
xhr.setRequestHeader('binaryStringResponseBody', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function (e) {
if (this.status == 200) {
var arrayBuffer = this.response;
$.ajax({
url: targetSiteUrl,
method: 'POST',
data: arrayBuffer,
processData: false,
headers: { 'binaryStringRequestBody': 'true', 'Accept': 'application/json;odata=verbose;charset=utf-8', 'X-RequestDigest': requestDigest }
})
.done(function (postData) {
console.log('we did it!');
})
.fail(function (jqXHR, errorText) {
console.log('dadgummit');
});
}
}
xhr.send();
}
What kind of error are you getting?
One probable cause of your problem is that your RequestDigest does not match the location where you want to POST your file since it is fetched from the page where your code is running. Fetch a matching RequestDigest by calling '_api/contextinfo' on your target location.
See:
http://blogs.breeze.net/mickb/2012/11/20/SP2013GettingAFormDigestForUpdateRESTCalls.aspx
and
http://msdn.microsoft.com/en-us/magazine/dn198245.aspx (writing to Sharepoint section)
Note
File Move operations only work within the scope of a given document library. You cannot copy between document libraries.
http://msdn.microsoft.com/en-us/library/office/dn605900(v=office.15).aspx#Folder6
For POST - operation we need request digest value, which is used by SharePoint to authenticate mainly for Post, Delete, Update not needed for GET operation,
Sample jquery ajax code for post operation-
$.ajax({
url: url + "/_api/web/lists/getbytitle('" + listname + "')/items",
type: "POST",
contentType: "application/json;odata=verbose",
data: JSON.stringify(item),
headers: {
"Accept": "application/json;odata=verbose",
"X-RequestDigest": $("#__REQUESTDIGEST").val()
},
success: function (data) {
success(data); // Returns the newly created list item information
},
error: function (data) {
failure(data);
}
});
You can try the following code for copying file from one location to another within SharePoint.
The following example will be helpful in copying files within SharePoint sandbox.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<div ng-app="myapp" ng-controller="mycont">
<input type="button" ng-click = "myclick()" value="Angular File Copy" />
</div>
<input type=button onclick="x()" value="jQueryFile copy" />
<script>
var dt =new Date();
var val_ue = dt.getDate()+""+dt.getHours()+""+dt.getMinutes()+""+dt.getSeconds() +"1" ;
var url1 = "/_api/web/getfilebyserverrelativeurl('/Lists/Document_Mapping/Attachments/1/9.jpg')";
var url2 = "/Lists/AddressVersioning/Attachments/84/" ;
var combined = "";
var app = angular.module('myapp',[]);
var _headers = {
'X-RequestDigest': document.getElementById("__REQUESTDIGEST").value,
'accept':'application/json;odata=verbose'
};
app.controller('mycont',function($scope,$http){
$scope.myclick = function(){
combined = url1 + "/copyTo('" + url2 + val_ue + ".jpg')";
$http({method:'POST',url:combined,headers:_headers}).then(
function(response){
console.log("hi");
val_ue += 1;
},
function(error){
console.log("Error:");
console.log(error);
},
function(process){
console.log("process:");
console.log(process);
}
);
}
});
var x = function(){
combined = url1 + "/copyTo('" + url2 + val_ue + ".jpg')";
$.ajax({
url : combined,
method: 'POST',
headers: {
"Accept": "application/json; odata=verbose",
"X-RequestDigest": $("#__REQUESTDIGEST").val()
},
success: function () {
alert("Success! Your file was copied properly");
val_ue +=1;
},
error: function () {
alert("Problem with copying");
}
});
}
</script>
Note: the above function will not work if the list item is newly created. But for all other situations it will work (even form one doc library to another doc library or cross site / site collection)

Saving HTML 5 Canvas as Image on the server using ASP.NET

I need some help here..
Im trying to save a canvas image after drawing..
following this example (http://www.dotnetfunda.com/articles/article1662-saving-html-5-canvas-as-image-on-the-server-using-aspnet.aspx)
$("#btnSave").click(function () {
var image = document.getElementById("canvas").toDataURL("image/png");
image = image.replace('data:image/png;base64,', '');
$.ajax({
type: 'POST',
url: "../../Home/UploadImage?imageData=" + image,
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function (msg) {
alert('Image saved successfully !');
}
});
});
the controller:
public void UploadImage(string imageData)
{
string fileNameWitPath = path + DateTime.Now.ToString().Replace("/", "-").Replace(" ", "- ").Replace(":", "") + ".png";
using (FileStream fs = new FileStream(fileNameWitPath, FileMode.Create))
{
using (BinaryWriter bw = new BinaryWriter(fs))
{
byte[] data = Convert.FromBase64String(imageData);
bw.Write(data);
bw.Close();
}
}
}
But when im trying to convert form base64 the string that is passed like parameter in method, throw an error
Invalid length for a character array Base-64.
You can't post data with querystring parameters
Try this;
$("#btnSave").click(function () {
var image = document.getElementById("canvas").toDataURL("image/png");
image = image.replace('data:image/png;base64,', '');
$.ajax({
type: 'POST',
url: "../../Home/UploadImage",
data: '{ "imageData" : "' + image + '" }',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function (msg) {
alert('Image saved successfully !');
}
});
});
In that example, the author has posted the image data using a hidden field, notice below line of code in his article
<input type="hidden" name="imageData" id="imageData" />
http://www.dotnetfunda.com/articles/show/2665/saving-html-5-canvas-as-image-in-aspnet-mvc
And on click of the button, he is submitting the form after getting canvas data to the hidden field so follow the same approach. As written by Mehmet, querystring has limitations and its prone to be modified as it goes via url.
Instead of this
image = image.replace('data:image/png;base64,', '');
use this:
image = image.substr(23, image.length);
remove the leading characters up to the first comma (use any dev tool to see what you posted).

Categories