I am trying to code my own simple AJAX image upload script via jQuery. I have found some plugins but they are way too customized for what's needed, and I cannot get any of them working properly.
I just want to somehow detect when the user drags and drops an image onto the page. From there I'm sure it's not hard to upload that data and move into a /cache/ directory and allow for further options..
but right now I'm totally stuck with the drag/drop functionality. Literally no idea how I should approach this. What kind of event handler is needed? Will I need to custom code my own event handler? Any advice would be more than appreciated
What kind of event handler is needed?
Drag'n'drop requires a HTML5 browser - but that's pretty much all of them now.
I'd recommend not starting from scratch as there's quite a bit of code needed - I quite like this wrapper that implements it as a jQuery plugin.
http://www.github.com/weixiyen/jquery-filedrop
After defining an element in the document with class div, you can initialise it to accept dropped files with:
function fileSetUploadPercent(percent, divID){
var uploadString = "Uploaded " + percent + " %";
$('#'.divID).text(uploadString);
}
function fileUploadStarted(index, file, files_count){
var divID = getDivID(index, file);
createFileUploadDiv(divID); //create the div that will hold the upload status
fileSetUploadPercent(0, divID); //set the upload status to be 0
}
function fileUploadUpdate(index, file, currentProgress){
//Logger.log("fileUploadUpdate(index, file, currentProgress)");
var string = "index = " + index + " Uploading file " + file.fileName + " size is " + file.fileSize + " Progress = " + currentProgress;
$('#status').text(string);
var divID = getDivID(index, file);
fileSetUploadPercent(currentProgress, divID);
}
function fileUploadFinished(index, file, json, timeDiff){
var divID = getDivID(index, file);
fileSetUploadPercent(100, divID);
if(json.status == "OK"){
createThumbnailDiv(index, file, json.url, json.thumbnailURL);
}
}
function fileDocOver(event){
$('#fileDropTarget').css('border', '2px dashed #000000').text("Drop files here");
}
$(".fileDrop").filedrop({
fallback_id: 'fallbackFileDrop',
url: '/api/upload.php',
// refresh: 1000,
paramname: 'fileUpload',
// maxfiles: 25, // Ignored if queuefiles is set > 0
maxfilesize: 4, // MB file size limit
// queuefiles: 0, // Max files before queueing (for large volume uploads)
// queuewait: 200, // Queue wait time if full
// data: {},
// headers: {},
// drop: empty,
// dragEnter: empty,
// dragOver: empty,
// dragLeave: empty,
// docEnter: empty,
docOver: fileDocOver,
// docLeave: fileDocLeave,
// beforeEach: empty,
// afterAll: empty,
// rename: empty,
// error: function(err, file, i) {
// alert(err);
// },
uploadStarted: fileUploadStarted,
uploadFinished: fileUploadFinished,
progressUpdated: fileUploadUpdate,
// speedUpdated
});
The bit of the web page that accepts uploads has this HTML.
<div class='fileDrop'>
Upload a file by dragging it.
<span id='fileDropTarget'/>
</div>
The file drop works on the outer <div> but it's nice to make a nice big target that says 'DROP HERE' so that users aren't confused about where they need to drop the file.
Probably too late. But you should checkout http://www.dropzonejs.com/
Related
I want to rollback the original dropzone with its message "drop files here" after the success event of dropzone or after the complete event of dropzone.
I don't want to see the preview after success or complete.
This is my dropzone script:
Dropzone.options.myAwesomeDropzone = {
paramName: "file", // The name that will be used to transfer the file
maxFilesize: 2, // MB
parallelUploads: 1,
success: function(file, response) {
var imageSrc = response;
$(".img-responsive").attr('src', imageSrc);
if (imageSrc == '/assets/images/offerfeatimg.jpg') {
$(".removebutton").hide();
} else {
$(".removebutton").show();
}
}
};
Leveraging #kkthxby3 's idea, the innerHTML for the thumbnail can be cleared in the success method using the following code:
success: function (file, response) {
file.previewElement.innerHTML = "";
}
The beauty of this approach is that it clears the thumbnail without firing the removedFile event.
This leaves the following html in the dom where the thumbnail was:
<div class="dz-preview dz-processing dz-image-preview dz-complete"></div>
but as you can see, the div above which is responsible for displaying the thumbnail is now empty.
Another approach is to remove even the enclosing div that wraps the thumbnail along with it's contents. This approach can be accomplished with the following code in the success method and leaves no trace of the thumbnail in the dom:
success: function (file, response) {
file.previewElement.parentNode.removeChild(file.previewElement);
}
Enjoy.
only need call method removeFile in success function
success: function (file, response) {
this.removeFile(file);
}
check doc dropzone
For me the easiest way to make the file preview not appear is with css.
dz-preview and dz-file-preview are a couple classes in the outer div of the preview html generated by the default template.
.dz-preview, .dz-file-preview {
display: none;
}
I also told it to not create thumbnails in the Dropzone.options.
Dropzone.options.myDropzone = {
paramName: "file",
maxFilesize: 2, // MB
url: 'post_image',
createImageThumbnails: false, // NO THUMBS!
init: function () {
this.on('sending', dz_sending),
this.on('success', dz_success),
this.on('error', dz_error),
this.on('complete', dz_complete) // Once it's done...
}
The template still generates all the preview html though. So in my 'complete' function dz_complete I delete it all.
function dz_complete(file) {
$('.dz-preview').remove(); // ...delete the template gen'd html.
}
Just an fyi...
The method 'removeAllFiles' is not necessarily the prime choice. Which is the same as 'removeFile(file)'.
I have an event handler for dropZone's 'removedfile' event... I'm using it to send a server message to delete the respective file from the server (should a user delete the thumbnail after it's been uploaded). Using the method 'removeAllFiles' (as well as the individualized 'removeFile(file)') fires the event 'removedfile' which deletes the uploaded images in addition to clearing the thumbnails.
So one could add some finessing around this but in the reality of it the method is not correct.
Looking through the api for Dropzone I am not seeing an API call to simply reset or clear the thumbnails... The method 'disable()' will clear the stored file names and what not but does not clear the thumbnails... Seems dropzoneJS is actually missing a critical API call to be honest.
My work around is to manually reset the containing div for dropzone:
document.getElementById("divNameWhereDropzoneClassIs").innerHTML = ""
This clears the thumbnails without firing off the event 'removedfile' which is supposed to be used for deleting an image from the server...
The easiest thing is to call the dropzone removeFile() method, using an event listener for the success event.
Dropzone.options.myAwesomeDropzone = {
paramName: "file",
maxFilesize: 2,
parallelUploads: 1,
init: function() {
this.on("success", function(file, response) {
var imageSrc = response;
$(".img-responsive").attr('src', imageSrc);
if(imageSrc == '/assets/images/offerfeatimg.jpg') {
$(".removebutton").hide();
} else {
$(".removebutton").show();
}
this.removeFile(file); // This line removes the preview
})
}
};
I was using file.previewElement.remove(), works fine in Chrome but does not work in IE.
Then I tried this.removeFile(file), but it didn't work for me.
After that i tried file.previewElement.innerHTML = "" which works in both Chrome and IE but it leaves an extra div where the preview elements were.
So this one works better for me...
success: function (file, response) {
file.previewElement.outerHTML = "";
}
If you want to remove an added file from the dropzone, you can call .removeFile(file). This method also triggers the removedfile event.
Here’s an example that would automatically remove a file when it’s finished uploading:
myDropzone.on("complete", function(file) {
myDropzone.removeFile(file);
});
If you want to remove all files, simply use .removeAllFiles(). Files that are in the process of being uploaded won’t be removed. If you want files that are currently uploading to be canceled, call .removeAllFiles(true) which will cancel the uploads.
100% Tested and Working:
$('#preview_image_container .dz-preview .dz-remove').attr('id','removeFile');
document.getElementById("removeFile").click();
I have drag and drop image uploads working using CKEditor 4.5.1. Very nice feature! On the server side I am resizing large images. My JSON response returns the resized image url (set by 'url' in the response) and that is the image that is shown in the CKEditor window after the successful file upload. But the img tag inserted has the height and width attributes set with the values from the original image, not my resized image. Is there a way to return the new height and width values? Or does anyone have an idea of how to hack around this?
And more generally, is there any resource which describes all possible values in the JSON response? I saw it mentioned somewhere that it wasn't documented yet but hopefully someone might know and take the time to share.
When uploading an image finishes, CKEditor replaces an upload widget (which contains an image with Base64 data in the source) with the final HTML. The uploaded image has the same dimensions as the one being uploaded to prevent from blinking during this replacement. Here are the lines that do this replacement.
If blinking when an image is uploaded is not a problem for you, then you can simple overwrite this method:
editor.on( 'instanceReady', function() {
editor.widgets.registered.uploadimage.onUploaded = function ( upload ) {
this.replaceWith( '<img src="' + upload.url + '">' );
}
} );
Now, where can we get the image dimensions from?
One option is to load the image (upload.url) and read its dimensions in the browser. However, this is an asynchronous operation, so it may affect the undo manager and I would not recommend it.
Therefore, if you know the new dimensions you can send then in the server response. If you put them in your JSON response like this:
{
"uploaded": 1,
"fileName": "foo.jpg",
"url": "/files/foo.jpg",
"width:" 300,
"height:" 200
}
You need to handle them in the response (we'll most likely simplify this bit soon):
editor.on( 'fileUploadResponse', function( evt ) {
var fileLoader = evt.data.fileLoader,
xhr = fileLoader.xhr,
data = evt.data;
try {
var response = JSON.parse( xhr.responseText );
// Error message does not need to mean that upload finished unsuccessfully.
// It could mean that ex. file name was changes during upload due to naming collision.
if ( response.error && response.error.message ) {
data.message = response.error.message;
}
// But !uploaded means error.
if ( !response.uploaded ) {
evt.cancel();
} else {
data.fileName = response.fileName;
data.url = response.url;
data.width = response.width;
data.height = response.height;
// Do not call the default listener.
evt.stop();
}
} catch ( err ) {
// Response parsing error.
data.message = fileLoader.lang.filetools.responseError;
window.console && window.console.log( xhr.responseText );
evt.cancel();
}
} );
To learn more check the editor#fileUploadResponse event and the Uploading Dropped or Pasted Files guide.
Then you can use them in the upload widget:
editor.on( 'instanceReady', function() {
editor.widgets.registered.uploadimage.onUploaded = function( upload ) {
this.replaceWith( '<img src="' + upload.url + '" ' +
'width="' + upload.width + '" ' +
'height="' + upload.height + '">' );
}
} );
PS. We were considering including such a feature in the core, but because the release was huge we had to limit it at some point to bring it finally to life. There is a great chance that such a feature will be included in the core soon, and only configuration will be needed.
#Michael , thanks for answer. I yet tested and can say that fileUploadResponse is not required.
Responce data can be reach from instanceReady like this (if present in responce from server ofcource)
__ckeditor.on( 'instanceReady', function() {
__ckeditor.widgets.registered.uploadimage.onUploaded = function( upload ) {
console.log(upload);
this.replaceWith( '<img src="' + upload.url + '" ' +
'width="' + upload.responseData.width + '" ' +
'height="' + upload.responseData.height + '">' );
}
} );
I would like to validate by file dimensions (resolution).
on the documentation page there is only information regarding file name and size, nothing at all in the docs about dimensions, and I also had no luck on Google.
The purpose of this is that I don't want users to upload low-res photos to my server. Thanks.
As Ray Nicholus had suggested, using the getFile method to get the File object and then use that with the internal instance object qq.ImageValidation to run fineuploader's validation on the file. A promise must be return because this proccess is async.
function onSubmit(e, id, filename){
var promise = validateByDimensions(id, [1024, 600]);
return promise;
}
function validateByDimensions(id, dimensionsArr){
var deferred = new $.Deferred(),
file = uploaderElm.fineUploader('getFile', id),
imageValidator = new qq.ImageValidation(file, function(){}),
result = imageValidator.validate({
minWidth : dimensionsArr[0],
minHeight : dimensionsArr[1]
});
result.done(function(status){
if( status )
deferred.reject();
else
deferred.resolve();
});
return deferred.promise();
}
Remained question:
Now I wonder how to show the thumbnail of the image that was rejected, while not uploading it to the server, the UI could mark in a different color as an "invalid image", yet the user could see which images we valid and which weren't...
- Update - (regarding the question above)
While I do not see how I could have the default behavior of a thumbnail added to the uploader, but not being uploaded, but there is a way to generate thumbnail manually, like so:
var img = new Image();
uploaderElm.fineUploader("drawThumbnail", id, img, 200, false);
but then I'll to create an item to be inserted to qq-upload-list myself, and handle it all myself..but still it's not so hard.
Update (get even more control over dimensions validation)
You will have to edit (currently) the qq.ImageValidation function to expose outside the private function getWidthHeight. just change that function deceleration to:
this.getWidthHeight = function(){
Also, it would be even better to change the this.validate function to:
this.validate = function(limits) {
var validationEffort = new qq.Promise();
log("Attempting to validate image.");
if (hasNonZeroLimits(limits)) {
this.getWidthHeight().done(function(dimensions){
var failingLimit = getFailingLimit(limits, dimensions);
if (failingLimit) {
validationEffort.failure({ fail:failingLimit, dimensions:dimensions });
}
else {
validationEffort.success({ dimensions:dimensions });
}
}, validationEffort.success);
}
else {
validationEffort.success();
}
return validationEffort;
};
So you would get the fail reason, as well as the dimensions. always nice to have more control.
Now, we could write the custom validation like this:
function validateFileDimensions(dimensionsLimits){
var deferred = new $.Deferred(),
file = this.holderElm.fineUploader('getFile', id),
imageValidator = new qq.ImageValidation(file, function(){});
imageValidator.getWidthHeight().done(function(dimensions){
var minWidth = dimensions.width > dimensionsLimits.width,
minHeight = dimensions.height > dimensionsLimits.height;
// if min-width or min-height satisfied the limits, then approve the image
if( minWidth || minHeight )
deferred.resolve();
else
deferred.reject();
});
return deferred.promise();
}
This approach gives much more flexibility. For example, you would want to have different validation for portrait images than landscape ones, you could easily identify the image orientation and run your own custom code to do whatever.
EDIT - URL to see the issue http://syndex.me
I am dynamically resizing images bigger than the browser to equal the size of the browser.
This was no easy feat as we had to wait for the images to load first in order to check first if the image was bigger than the window.
We got to this stage (which works):
var maxxxHeight = $(window).height();
$(".theImage").children('img').each(function() {
$(this).load( function() { // only if images can be loaded dynamically
handleImageLoad(this);
});
handleImageLoad(this);
});
function handleImageLoad(img)
{
var $img = $(img), // declare local and cache jQuery for the argument
myHeight = $img.height();
if ( myHeight > maxxxHeight ){
$img.height(maxxxHeight);
$img.next().text("Browser " + maxxxHeight + " image height " + myHeight);
};
}
The thing is, the page is an infinite scroll (I'm using this)
I know that you are not able to attach 'live' to 'each' as 'live' deals with events, and 'each' is not an event.
I've looked at things like the livequery plugin and using the ajaxComplete function.
With livequery i changed
$(".theImage").children('img').each(function() {
to
$(".theImage").children('img').livequery(function(){
But that didnt work.
ajaxComplete seemed to do nothing so i'm guessing the inifinte scroll i'm using is not ajax based. (surely it is though?)
Thanks
Use delegate:
$(".theImage").delegate('img', function() {
$(this).load( function() { // only if images can be loaded dynamically
handleImageLoad(this);
});
handleImageLoad(this);
});
The problem is that your infinite scroll plugin does not provide the callback functionality. Once your pictures are loaded there is no way to affect them.
I have tried to modify your plugin, so that it will serve your needs, please see http://jsfiddle.net/R8yLZ/
Scroll down the JS section till you see a bunch of comments.
This looks really complicated, and I probably don't get it at all, but I'll try anyway :-)
$("img", ".theImage").bind("load", function() {
var winH = $(window).height();
var imgH = $(this).height();
if (winH < imgH) {
$(this).height(winH);
$(this).next().text("Browser " + winH + " image height " + imgH);
}
});
I am building this slideshow, hereby a temp URL:
http://ferdy.dnsalias.com/apps/jungledragon/html/tag/96/homepage/slideshow/mostcomments
There are multiple ways to navigate, clicking the big image goes to the next image, clicking the arrows go to the next or previous image, and you can use your keyboard arrows as well. All of these events call a method loadImage (in slideshow.js).
The image loading is fine, however at the end of that routine I'm making a remote Ajax call using $.get. The purpose of this call is to count the view of that image. Here is the pseudo, snipped:
function loadImage(id,url) {
// general image loading routine
// enable loader indicator
$("#loading").show();
var imagePreloader = new Image();
imagePreloader.src = url;
loading = true;
$(imagePreloader).imagesLoaded(function() {
// load completed, hide the loading indicator
$("#loading").hide();
// set the image src, this effectively shows the image
var img = $("#bigimage img");
img.attr({ src: url, id: id });
imageStartTime = new Date().getTime();
// reset the image dimensions based upon its orientation
var wide = imagePreloader.width >= imagePreloader.height;
if (wide) {
img.addClass('wide');
img.removeClass('high');
img.removeAttr('height');
} else {
img.addClass('high');
img.removeClass('wide');
img.removeAttr('width');
}
// update thumb status
$(".photos li.active").removeClass('active');
$("#li-" + id).addClass('active');
// get the title and other attributes from the active thumb and set it on the big image
var imgTitle = $("#li-" + id + " a").attr('title');
var userID = $("#li-" + id + " a").attr('data-user_id');
var userName = $("#li-" + id + " a").attr('data-user_name');
$(".caption").fadeOut(400,function(){
$(".caption h1").html('' + imgTitle + '');
$(".caption small").html('Uploaded by ' + userName + '');
$(".caption").fadeIn();
});
// update counter
$(".counter").fadeOut(400,function() { $(".counter").text(parseInt($('.photos li.active .photo').attr('rel'))+1).fadeIn(); });
// call image view recording function
$.get(basepath + "image/" + id + "/record/human");
// loading routine completed
loading = false;
}
There is a lot of stuff in there that is not relevant. At the end you can see I am doing the $.get call. The problem is that it is triggered in very strange ways. The first time I navigate to a tumb, it is called once. The next time it is triggered twice. After that, it is triggered 2 or 3 times per navigation action, usually 3.
I figured it must be that my events return multiple elements and therefore call the loadimage routine multiple times. So I placed log statements in both the events and the loadimage routine. It turns out loadimage is called correctly, only once per click.
This means that it seems that the $.get is doing this within the context of a single call. I'm stunned.
Your problem may be:.imagesLoaded is a jQuery plug in that runs through all images on the page. If you want to attach a load event to the imagePreloader only, use
$(imagePreloader).load(function() {
...
}
Otherwise, please provide the code where you call the loadImage() function.
Update:
when clicking on a thumb That is the problem. $(".photos li a").live('click',... should only be called once on page load. Adding a click handler every time a thumb is clicked will not remove the previous handlers.
Another option is to change the code to $(".photos li a").unbind('click').live('click', ... which will remove the previously registered click handlers.