19

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);
}
Kode
  • 3,073
  • 18
  • 74
  • 140

1 Answers1

7

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!

arman1991
  • 1,166
  • 1
  • 18
  • 28
  • 1
    No such luck. It is failing with a "Unable to set property "Photo" of undefined or null reference" on this line: $scope.data.Photo = base64Image.replace(/data:image\/jpeg;base64,/g, ''); – Kode Jun 15 '15 at 15:42
  • 1
    In top of your controller, try to initialize _$scope.data_ as object: **$scope.data={}** – arman1991 Jun 15 '15 at 16:47
  • The issue seems to be with my posting of the data. Could you look at my upload function and see why its not passing the correct data/how specifically do I post the base64? – Kode Jun 15 '15 at 17:00
  • Unfortunally, I'm not familiar with sharepoint, but I'm wondering why is _'Content-Type': false_ in your header ? You must set with what kind of data are you handling... – arman1991 Jun 15 '15 at 17:27
  • I was pushed towards that via another posting as that would let SharePoint figure it out. Beyond my headers, does the upload function look correct/do I need to designate the base64 string somewhere? – Kode Jun 15 '15 at 17:34
  • If you have any value in your upload function, than its' OK, if not, than you must share the value of **$scope.data.Photo** from _$scope.filesChanged_ function to _$scope.upload_ via service or factory. The rest of code looks OK, I have no more ideas at the moment... – arman1991 Jun 15 '15 at 18:11
  • Appreciate the assistance. My guess is that is on the SharePoint side of things. Of note, when using the directive in my post, it logs the base64 but the success output is far more complex which leads me to believe it's a SharePoint issue – Kode Jun 15 '15 at 18:19