nervgh / angular-file-upload

[ALMOST NOT MAINTAINED] Angular File Upload is a module for the AngularJS framework
MIT License
3.43k stars 1.13k forks source link

allow upload of blob #208

Closed bettysteger closed 10 years ago

bettysteger commented 10 years ago

hey I just used https://github.com/alexk111/ngImgCrop to crop an image before uploading.. and now I did this to upload the blob with angular-file-upload:

  uploader.onBeforeUploadItem = function(item) {
    var blob = dataURItoBlob($scope.croppedImage);
    item._file = blob;
  };

This works just fine, but the file name is ignored because of this line: (and because of that the server doesn't recognize the file extension)

form.append(item.alias, item._file);
-->
------WebKitFormBoundaryGD5fYyZwXHVXxfZ4
Content-Disposition: form-data; name="avatar"; filename="blob"
Content-Type: image/png

It would be possible to add the name as third parameter, see https://developer.mozilla.org/en-US/docs/Web/API/FormData

what do you think of adding a third parameter or allow to upload a Blob in the first place?

btw: you made a really cool plugin!

nervgh commented 10 years ago

Hi. Thanks.

allow upload of blob

Blob uploading is possible. You can pass any data with file. Probably, in your case it looks like this:

// client
uploader.onAfterAddingFile = function(item) {
    item.formData.push({name: item.file.name});
};
// server
$name = $_POST['name'];

what do you think of adding a third parameter or allow to upload a Blob in the first place?

I can write something like this

form.append(item.alias, item._file, item.file.name);

insted that code. What do you think about it?

bettysteger commented 10 years ago

Yes @nervgh, changing this line of code is what I meant ;) because then I don't need to add additional formData (client) and I don't need to get the name extra on the server, because it would work out of the box (with carrierwave)

bettysteger commented 10 years ago

Blob uploading is possible. You can pass any data with file.

yes, I know that, but I don't want it to pass it with the file, I want to just upload a Blob... that's why I overwrite item._file before the upload ;)

nervgh commented 10 years ago

Thanks =)

EnchanterIO commented 10 years ago

@lpsBetty @nervgh Do any of you have a functional example of using ngImgCrop together with this uploader? I just can't make it work.

bettysteger commented 10 years ago

hey @TrkiSF2, I used onBeforeUploadItem to overwrite the actual '_file' with the blob:

updated solution, if you have a scope inheritance problem: https://gist.github.com/lpsBetty/2057e0bd0b4142d4070db0e1175ca47e

  /**
   * Show preview with cropping
   */
  uploader.onAfterAddingFile = function(item) {
    $scope.croppedImage = '';
    var reader = new FileReader();
    reader.onload = function(event) {
      $scope.$apply(function(){
        $scope.image = event.target.result;
      });
    };
    reader.readAsDataURL(item._file);
  };

  /**
   * Upload Blob (cropped image) instead of file.
   * @see
   *   https://developer.mozilla.org/en-US/docs/Web/API/FormData
   *   https://github.com/nervgh/angular-file-upload/issues/208
   */
  uploader.onBeforeUploadItem = function(item) {
    var blob = dataURItoBlob($scope.croppedImage);
    item._file = blob;
  };

  /**
   * Converts data uri to Blob. Necessary for uploading.
   * @see
   *   http://stackoverflow.com/questions/4998908/convert-data-uri-to-file-then-append-to-formdata
   * @param  {String} dataURI
   * @return {Blob}
   */
  var dataURItoBlob = function(dataURI) {
    var binary = atob(dataURI.split(',')[1]);
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
    var array = [];
    for(var i = 0; i < binary.length; i++) {
      array.push(binary.charCodeAt(i));
    }
    return new Blob([new Uint8Array(array)], {type: mimeString});
  };

and the HTML:

        <!-- crop area if uploaded image-->
        <img-crop ng-show="image" image="image" result-image="croppedImage" area-type="square" result-image-size="250"></img-crop>

I hope I can you help you with that snippets ;)

trivago-llukac commented 10 years ago

IpsBetty thank you for the code! I will try it when I come home but from a quick view I am missing one thing. When do you call something like $scope.uploader.addToQueue(item) and how the item object looks like in that state please?

bettysteger commented 10 years ago

the uploader is bind to the dropzone and to the input field in my HTML. so when a file is chosen uploader.onAfterAddingFile (see above) will be called

EnchanterIO commented 10 years ago

Thank you very much. I made it work finally!!! :) If you would like to stay in touch https://twitter.com/TrkiSF2

nervgh commented 10 years ago

@lpsBetty , thanks =)

brianfeister commented 9 years ago

Sorry to continue this thread that's already long - @TrkiSF2, any chance you could post your working code as a Plunkr or similar?

@lpsBetty - thank you SO much for posting your code. I'm just curious what the HTML context is for the upload directory? I realize that we're saving the base64 encoded image string to $scope.image. But the reference to that via your example (<img-crop image="image">) doesn't seem to be working. If I simply do <img ng-src="{{image}}"> then the data URI correctly renders an image, but passing $scope.image has no effect when used in the context of the <img-crop> directive from your example. Any ideas? This is such a WONDERFUL use case, would be great to include in the docs, since client side crop is an amazing complement to this directive.

Maybe someone can share what wraps the example crop code from here:

        <!-- crop area if uploaded image-->
        <img-crop ng-show="image" image="image" result-image="croppedImage" area-type="square" result-image-size="250"></img-crop>

@nervgh - I spent days recreating this functionality in raw javascript, and got it working. However, this directive is so well written and documented I'm definitely tossing that work in favor of this library. Great documentation, robust eventing, and modular / flexible - Thank you!!!

brianfeister commented 9 years ago

Ugh, sorry for the stupid question (though I think the full HTML template would still be great to post). Turns out I was just missing the CSS which is required for the ngImgCrop directive

.cropArea {
  background: #E4E4E4;
  overflow: hidden;
  width:500px;
  height:350px;
}
brianfeister commented 9 years ago

Thanks again @lpsBetty! I had to make some changes to accomodate multi-file uploads, I think your code was assuming singular. For anyone else that wants to see all the moving parts, I've created a public gist that wires it all together:

https://gist.github.com/brianfeister/56a1c6c77cd5928a1c53

bettysteger commented 9 years ago

@brianfeister yes i just needed single file upload, thanks your gist :+1:

imchintoo commented 9 years ago

Hi @nervgh Is there any way to trigger upload image event? Actually i want to upload the image after inserting/updating the data?

bettysteger commented 9 years ago

Hi @chin2, there is a property called autoUpload, see https://github.com/nervgh/angular-file-upload/wiki/Module-API

just set it to true

imchintoo commented 9 years ago

Thanks @lpsBetty :+1:

imchintoo commented 9 years ago

Hi, @lpsBetty, @nervgh Yes one thing is that i want to load a default image while rendering the FileUpload, is it possible?

bettysteger commented 9 years ago

@chin2 So you want an image already in the queue? why do you want that?

imchintoo commented 9 years ago

@lpsBetty Actually while edit form, the uploaded image should be rendered. Have you got idea what is want exactly?

bettysteger commented 9 years ago

@chin2 so you want a preview? just use the result-image like this:

 <!-- crop area if uploaded image-->
 <img-crop ng-show="image" image="image" result-image="croppedImage" area-type="square" result-image-size="150"></img-crop>
<!-- preview -->
<img alt="avatar_preview" ng-src="{{croppedImage}}" width="150" height="150" />
jiangyanghe commented 9 years ago

angular-file-upload how to add an parameter in url? /* the 'sn' is i want to add it in the url/ var uploader = $scope.uploader = new FileUploader({ url:url+'/ver/1.0/members/~/orgs/~/vas/'+sn+'/photo' , autoUpload: false, headers:{'Authorization':accessKey+':0a632a6beaf24928b1ccfdf7bc680317','Accept':'/*'} }); uploader.onSuccessItem = function(fileItem, response, status, headers) { if(response.retCode == "SUCCESS"){ $scope.vasPhoto = response.data.path; }else{ alert(response.msg); } };

amuste commented 9 years ago

TH's @lpsBetty

hfar commented 9 years ago

Hi, @lpsBetty is it possible to upload both original image and the cropped image?

bettysteger commented 9 years ago

@hfar hmmm maybe with two end points?

kkmoghe commented 9 years ago

Hi, I want to use this uploader for uploading multiple files, I am able to do that, now new thing is when it comes to mobile I should be able to click picture & upload that, I think there is no direct way, so I used following for picture :

                                        </div>

I see the image on click of button & camera, but how to upload it now thro.' this uploader ?

kkmoghe commented 9 years ago

div class="visible-sm visible-xs form-group">input type="file" accept="image/*" id="capture" capture="camera">/div>

Above is the code I used, appreciate your help.

bettysteger commented 9 years ago

@kkmoghe I think it should work automatically? What is your actual HTML ? you can use ```` to write code in github

kkmoghe commented 9 years ago
                                         <div class="visible-sm visible-xs form-group">
                                             <input type="file" accept="image/*" id="captureImage" capture="camera" nv-file-select="" uploader="uploader" onchange="angular.element($('captureImage')).scope().$apply(function($scope){$scope.uploader.changeFile();});">
                                         </div>

Above is the code.

bettysteger commented 9 years ago

@kkmoghe ok this is definitely the wrong thread for your question! why do you use the onchange attribute.. there is no need for that!

First: what is your controller code? Hopefully you define $scope.uploader ? Second: what do you want to do? When you select an image immediately upload? Then here is the solution in your controller, just add:

$scope.uploader.autoUpload = true;
kkmoghe commented 9 years ago

Hi Bettina, Sorry to post it in wrong thread, but your help is much appreciated.Sorry to post it in wrong thread, but your help is much appreciated.

yes I have $scope.uploader My requirement is not to immediately upload, I should be able to upload multi-files in desktop as well as mobile, so for desktop it works : I am pasting my uploader declaration & my tpl code to use that. So desktop it works, but for photo, I used that capture tag, but some how it is ignoring it, in mobile I am displaying two buttons one for capture=camera and other like normal desktop, camera is clicking & I see the file name after that, but its not getting uploaded, so I think I am missing some thing, where it is going wrong ?

uploader is defined in controller as :

var uploader = $scope.uploader = new FileUploader({
    sendAllAtOnceItemsMultipleFileInputs: false,
    url: '',
    dataType: "json",
    cache: false,
    contentType: "application/json",
    aliases: 'file1,file2,file3,file4,file5,file6'
});

                                <div class="pr">
                                    <div class="form-group">
                                         <div class="visible-sm visible-xs form-group">
                                             <input type="file" accept="image/*" id="captureImage" capture="camera" nv-file-select="" uploader="uploader" onchange="angular.element($('captureImage')).scope().$apply(function($scope){$scope.uploader.changeFile();});">
                                         </div>
                                        <div id="ChooseBtnLabel" class="uploadfix" ng-show="checkNoOfAttachedFiles()">Select a File
                                            <input type="file" nv-file-select="" class="multi" 
                                            uploader="uploader"
                                            onchange="angular.element($('#ChooseBtnLabel')).scope().$apply(function($scope){$scope.uploader.changeFile();});"
                                            multiple />
                                        </div>

                                        <p ng-show="invalidAddFileAttemptMessage!==''" class="red">{{invalidAddFileAttemptMessage}}</p>

                                        <div class="MultiFile-list" id="files_wrap_list"
                                             ng-repeat="item in uploader.queue">
                                            <div ng-if="uploader.isUploading">Uploading...
                                            </div>
                                            <div ng-hide="'NO_FILES_CHOSEN' === item.file.name">
                                                <a class="MultiFile-remove" ng-click="removeItemFromQueue(item);">x</a>
                                                <span class="MultiFile-label MultiFile-title" ng-bind="item.file.name"></span>
                                            </div>
                                        </div>

                                        </div>

                                    </div>

                                </div>
bettysteger commented 9 years ago

sorry I don't know why!

bettysteger commented 9 years ago

@kkmoghe I don't use capture.. but maybe you should use another file uploader.. I saw that https://github.com/danialfarid/ng-file-upload supports capture!

kkmoghe commented 9 years ago

ok, thx. I got this uploader in my search, but nervgh uploader we are using in our existing other modules also, so my design team is reluctant to add this another uploader just for mobile photo capture, they suggest me to use nervgh or write your own plugin.

ganu commented 9 years ago

@lpsBetty, folowed your code but I'm getting null value in myCroppedImage on uploader.onBeforeUploadItem function. Coudn't convert to blod value. Please Check my following code

$scope.myImage=''; $scope.myCroppedImage='';

uploader.onAfterAddingFile = function(item) {
    var reader = new FileReader();
    reader.onload = function(event) {
      $scope.$apply(function(){
        $scope.myImage = event.target.result;            
      });
    };
    reader.readAsDataURL(item._file);
};

uploader.onBeforeUploadItem = function(item) {
  var blob = dataURItoBlob($scope.myCroppedImage);
  console.log(blob);
  item._file = blob;
};

var dataURItoBlob = function(dataURI) {
  var binary = atob(dataURI.split(',')[1]);
  var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
  var array = [];
  for(var i = 0; i < binary.length; i++) {
    array.push(binary.charCodeAt(i));
  }
  return new Blob([new Uint8Array(array)], {type: mimeString});
};

Please help me on this

hartjo commented 9 years ago

$scope.croppedImage is Undefined cant get blob data

what am i missing? need help please

var dataURItoBlob = function(dataURI) { var binary = atob(dataURI.split(',')[1]); var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; var array = []; for(var i = 0; i < binary.length; i++) { array.push(binary.charCodeAt(i)); } return new Blob([new Uint8Array(array)], {type: mimeString}); };

    $scope.handleFileSelect = function (evt) {
        var file = evt.currentTarget.files[0];
        $scope.$apply(function ($scope) {
            $scope.myImage = URL.createObjectURL(file);
        });
    };

    var handleFileSelect=function(evt) {
        $scope.croppedImage = '';
        $scope.myImage = '';
        var file=evt.currentTarget.files[0];
        var reader = new FileReader();
        reader.onload = function (evt) {
            $scope.$apply(function($scope){
                $scope.myImage=evt.target.result;
            });
        };
        reader.readAsDataURL(file);
        console.log($scope.croppedImage);

    };
    angular.element(document.querySelector('#fileInput')).on('change',handleFileSelect);

    var uploader = $scope.uploader = new FileUploader({
        url: '/upload.php'
    });

    // FILTERS

    uploader.filters.push({
        name: 'customFilter',
        fn: function(item /*{File|FileLikeObject}*/, options) {
            return this.queue.length < 10;
        }
    });

    // CALLBACKS

    uploader.onWhenAddingFileFailed = function(item /*{File|FileLikeObject}*/, filter, options) {
        console.info('onWhenAddingFileFailed', item, filter, options);
    };
    uploader.onAfterAddingFile = function(fileItem) {

    };
    uploader.onAfterAddingAll = function(addedFileItems) {
        console.info('onAfterAddingAll', addedFileItems);
    };
    uploader.onBeforeUploadItem = function(item) {
        console.log('=================');
        console.log($scope.croppedImage);
        var blob = dataURItoBlob($scope.croppedImage);
        item._file = blob;
    };
    uploader.onProgressItem = function(fileItem, progress) {
        console.info('onProgressItem', fileItem, progress);
    };
    uploader.onProgressAll = function(progress) {
        console.info('onProgressAll', progress);
    };
    uploader.onSuccessItem = function(fileItem, response, status, headers) {
        console.info('onSuccessItem', fileItem, response, status, headers);
    };
    uploader.onErrorItem = function(fileItem, response, status, headers) {
        console.info('onErrorItem', fileItem, response, status, headers);
    };
    uploader.onCancelItem = function(fileItem, response, status, headers) {
        console.info('onCancelItem', fileItem, response, status, headers);
    };
    uploader.onCompleteItem = function(fileItem, response, status, headers) {
        console.info('onCompleteItem', fileItem, response, status, headers);
    };
    uploader.onCompleteAll = function() {
        console.info('onCompleteAll');
    };

    console.info('uploader', uploader);
bettysteger commented 9 years ago

hmmm I really don't know what's the problem, but it could be a 'scope' inheritance problem, because of this I changed my cropped image model to an object to make sure that it set by the ngImgCrop!

from croppedImage to cropped = {image: ''}

Here is the full code:

/**
 * Show preview of cropped image
 */
uploader.onAfterAddingFile = function(item) {
  $scope.cropped = {image: ''};
  var reader = new FileReader();
  reader.onload = function(event) {
    $scope.$apply(function(){
      $scope.image = event.target.result;
    });
  };
  reader.readAsDataURL(item._file);
};

/**
 * Upload Blob (cropped image) instead of file.
 * @see
 *   https://developer.mozilla.org/en-US/docs/Web/API/FormData
 *   https://github.com/nervgh/angular-file-upload/issues/208
 */
uploader.onBeforeUploadItem = function(item) {
  var blob = dataURItoBlob($scope.cropped.image);
  item._file = blob;
};

/**
 * Converts data uri to Blob. Necessary for uploading.
 * @see
 *   http://stackoverflow.com/questions/4998908/convert-data-uri-to-file-then-append-to-formdata
 * @param  {String} dataURI
 * @return {Blob}
 */
var dataURItoBlob = function(dataURI) {
  // convert base64/URLEncoded data component to raw binary data held in a string
  var byteString;
  if (dataURI.split(',')[0].indexOf('base64') >= 0) {
    byteString = atob(dataURI.split(',')[1]);
  } else {
    byteString = decodeURI(dataURI.split(',')[1]);
  }
  var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
  var array = [];
  for(var i = 0; i < byteString.length; i++) {
    array.push(byteString.charCodeAt(i));
  }
  return new Blob([new Uint8Array(array)], {type: mimeString});
};
mpmadhuranga commented 9 years ago

@lpsBetty Could you please tell me how to trigger the upload onAfterAddingFile function. Currebtly im using this, $(document).on('change', '#fileInput', function(event) { uploader.onAfterAddingFile(event); });

But it is not working as it is.

bettysteger commented 9 years ago

@madhurangaonbit you don't need this JS, you can just add this line and the uploader will automatically upload the image:

uploader.autoUpload = true;

see https://github.com/nervgh/angular-file-upload/wiki/Module-API

the callback function of onAfterAddingFile will be called everytime a file is added in the browser..

hartjo commented 9 years ago

im sorry for no response i have something to do on my work so.. btw thank you so much i got it work using your approach thank you so much :)

gregui commented 9 years ago

madhurangaonbit

    FileUploader.FileSelect.prototype.isEmptyAfterSelection = function() {
        return false;
    };
LaneTwo commented 8 years ago

@lpsBetty , thank you very much for your solution, finally solved my issue, I have struggled on that for several days.

raysefo commented 8 years ago

Hi @lpsBetty ,

On my IOS mobile application I am taking picture and then select a picture from gallery. After selecting this picture I am using ng-img-crop.js to crop this image and without saving this cropped image, trying to upload it to a server. (In this partivular example I am trying to upload local IIS)
I am using local IIS 7.5 and installed asp, isapi etc. Now I am getting 0 from the response. Image is not uploaded to the server and there is no error log on IIS. Would you please help me?

Best Regards.

Here is my sample js: `angular.module('starter', ['ionic', 'ngCordova', 'ngImgCrop', 'ngFileUpload'])

.controller("ExampleController", ['$scope', '$cordovaCamera', 'Upload', '$timeout', '$cordovaFileTransfer', function ($scope, $cordovaCamera, Upload, $timeout, $cordovaFileTransfer) {

$scope.takePhoto = function () {
    var options = {
        quality: 100,
        destinationType: Camera.DestinationType.DATA_URL,
        sourceType: Camera.PictureSourceType.CAMERA,
        allowEdit: true,
        encodingType: Camera.EncodingType.JPEG,
        targetWidth: 300,
        targetHeight: 300,
        popoverOptions: CameraPopoverOptions,
        saveToPhotoAlbum: true
    };

    $cordovaCamera.getPicture(options).then(function (imageData) {
        $scope.imgURI = "data:image/jpeg;base64," + imageData;
    }, function (err) {
        console.log(err);
        alert(err);
    });
}

$scope.choosePhoto = function () {
    var options = {
        quality: 100,
        destinationType: Camera.DestinationType.DATA_URL,
        sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
        allowEdit: true,
        encodingType: Camera.EncodingType.JPEG,
        targetWidth: 300,
        targetHeight: 300,
        popoverOptions: CameraPopoverOptions,
        saveToPhotoAlbum: false
    };

    $cordovaCamera.getPicture(options).then(function (imageData) {
        $scope.imgURI = "data:image/jpeg;base64," + imageData;
        $scope.myImage = $scope.imgURI;

    }, function (err) {
        console.log(err);
        alert(err);
    });
}

$scope.myCroppedImage = '';

$scope.upload = function (dataUrl) {

Upload.upload({

    url: 'http://192.168.1.20/wcf/upload',
    data: {
        file: Upload.dataUrltoBlob(dataUrl)
    },

}).then(function (response) {
    $timeout(function () {
        $scope.result = response.data;
        console.log(response.data);
        alert(response.data);
    });
    console.log(response.data);
}, function (response) {
    if (response.status > 0) $scope.errorMsg = response.status
        + ': ' + response.data;
    alert(response.status);
});

}

}]);`

And here is the related markup: `

            <img-crop image="myImage" result-image="myCroppedImage" chargement="'Loading'"
                      area-type="rectangle"
                      area-min-size="50"
                      result-image-format="image/jpeg"
                      result-image-quality="1"
                      result-image-size="{w:300, h:50}"></img-crop>

        </div>
        <div ng-show="myImage !== undefined">Gönderilecek Resim:</div>
        <div class="croppedArea"><img ng-src="{{myCroppedImage}}" ng-show="myImage !== undefined" id="image" /></div>

    </center>
    <button class="button button-full button-balanced icon-right ion-images" ng-click="upload(myCroppedImage);" ng-if="myImage !== undefined">
        Resim Yükle
    </button>`
bettysteger commented 8 years ago

@raysefo what is IIS ? ;)

raysefo commented 8 years ago

@lpsBetty Internet Information Services (IIS) for Windows for my local PC :) Should I use file transfer plugin (https://www.npmjs.com/package/cordova-plugin-file-transfer) instead?

raysefo commented 8 years ago

any idea @lpsBetty ?

bettysteger commented 8 years ago

hmm @raysefo no sorry, I have no experience with IIS..

raysefo commented 8 years ago

Lets forget about IIS, as a result it is a server. What is the difference between file upload and file transfer? @lpsBetty

MrSpark2591 commented 8 years ago

@lpsBetty @nervgh can we have

                            formData = new FormData();
            formData.append("categoryCode", $scope.category.name);
            formData.append("request",item._file);
                            $http.post(URL,formData);

and this works great for me when i pass it to java spring backend but the problem is it won't update item.progress property . any solution?

bettysteger commented 8 years ago

@MrSpark2591 yeah that is because you are not using the fileuploader for uploading the actual image, you are just making your own POST request - if you use fileuploader with the correct functions (uploadAll or uploadItem) it works!