jQuery file upload control submits large files erroneously - javascript

I have a custom file upload implementation for the blueimp/jQuery file upload control. It works great except when I choose multiple files and one of them exceeds the maximum length, it still attempts to upload the out of bounds file (even though I don't call data.submit() for that particular file).
I have verified the progress event doesn't fire for the large (and unsubmitted) file, but the server ultimately reports a 404.13 (The request filtering module is configured to deny a request that exceeds the request content length), and none of the other files that were actually submitted get uploaded.
How can I change the way I use the jQuery file upload control to upload the valid files, but ignore the large file?
Here is my code...it manipulates a model that is leveraged by knockout to display UI elements, so you won't see any UI manipulation in the code below.
$('#fileupload').fileupload({
dataType: 'json',
url: '/Handlers/UploadHandler.ashx',
progress: function (e, data) {
console.log(data.context);
var progress = parseInt(data.loaded / data.total * 100, 10);
var file = fileUploadDataModel.files()[data.context];
file.percentComplete(progress);
},
add: function (e, data) {
$.each(data.files, function (index, file) {
var fileModel = new FileModel(file.name, file.size);
fileUploadDataModel.files.push(fileModel);
data.context = fileUploadDataModel.files().length - 1;
if (file.size > opts.maxFileSize) {
fileModel.errorMessage('{0} ({1}) exceeds maximum size -- rejected'.format(file.name, friendlyBytes(file.size)));
fileModel.percentComplete(0);
}
else data.submit();
});
},
done: function (e, data) {
var file = fileUploadDataModel.files()[data.context];
file.percentComplete(100);
},
singleFileUploads: false,
});

I was able to get this to work by marking the files that were invalid, removing them from the data.files array and then submitting the rest. Here is the fixed code...
$('#fileupload').fileupload({
dataType: 'json',
url: '/Handlers/UploadHandler.ashx',
progress: function (e, data) {
console.log(data.context);
var progress = parseInt(data.loaded / data.total * 100, 10);
var file = fileUploadDataModel.files()[data.context];
file.percentComplete(progress);
},
add: function (e, data) {
var itemsToRemove = [];
$.each(data.files, function (index, file) {
var fileModel = new FileModel(file.name, file.size);
fileUploadDataModel.files.push(fileModel);
fileModel.sourceIndex = fileUploadDataModel.files().length - 1;
file.fileModel = fileModel;
if (file.size > opts.maxFileSize) {
fileModel.errorMessage('{0} ({1}) exceeds maximum size -- rejected'.format(file.name, friendlyBytes(file.size)));
fileModel.percentComplete(0);
itemsToRemove.push(index);
}
});
itemsToRemove = itemsToRemove.reverse();
for (index in itemsToRemove)
data.files.splice(itemsToRemove[index], 1);
$.each(data.files, function (index, file) {
data.context = file.fileModel.sourceIndex;
data.submit();
});
},
done: function (e, data) {
var file = fileUploadDataModel.files()[data.context];
file.percentComplete(100);
},
singleFileUploads: false,
});

Related

Dropzone.js Sortable files from server loaded files

Using dropzone js when I try to sort server loaded files I receive the following error
Uncaught TypeError: Failed to execute 'readAsDataURL' on 'FileReader':
parameter 1 is not of type 'Blob'. at Dropzone.createThumbnail ... at Dropzone._processThumbnailQueue
I believe the error is something to do with pushing the incorrect mockFile variable into the dropzone files? Being an object rather than a File object which mockflow requires?
Below is the code I am currently using
function getFiles() {
$.getJSON('/listing/attachments/'+$('input[name="listing"]').val(),
function(data) {
if ( data ) {
$.each(data, function(i, item) {
var mockFile = {
name: item.name,
size: 23233,
status: 'success',
type: 'image/jpeg'
};
dropzone.emit("addedfile", mockFile);
dropzone.emit("thumbnail", mockFile, item.file);
dropzone.emit("complete", mockFile);
dropzone.files.push(mockFile);
});
}
});
}
var dropzone = new Dropzone(".dropzone", {
uploadMultiple: false,
parallelUploads: 100,
maxFilesize: 8,
maxFiles: 20,
addRemoveLinks: true,
acceptedFiles: ".png,.jpg,.gif,.bmp,.jpeg",
init:function() {
var self = this;
this.on("removedfile", function(file) {
$.ajax({
type: 'POST',
url: '/upload/delete',
data: {id: file.name, listing: $('input[name="listing"]').val(), _token: $('input[name="_token"]').val()},
dataType: 'html',
success: function(data){
var rep = JSON.parse(data);
if(rep.code == 200) {
}
}
});
} );
if ( $('input[name="listing"]').val() ) {
getFiles();
}
},
});
$(function(){
$(".dropzone").sortable({
items:'.dz-preview',
cursor: 'move',
opacity: 0.5,
containment: '.dropzone',
distance: 20,
tolerance: 'pointer',
update: function(e, ui){
var files = dropzone.files;
files.sort(function(a, b){
return ($(a.previewElement).index() > $(b.previewElement).index()) ? 1 : -1;
})
dropzone.removeAllFiles();
dropzone.handleFiles(files);
dropzone.processQueue();
}
});
});
Many thanks in advance :)
After having put a lot of hours into this I finally have a solution to make jquery sortable work with dropzone.js. I'll put the script of interest first and the full dropzone js script second. The commentary should explain what is happening.
init: function() {
// very important to make the sortable work
var myDropzone = this;
// In your drop zone you have your click handler event
document.getElementById("submit").addEventListener("click", function(e) {
// Make sure that the form isn't actually being sent.
e.preventDefault();
// the new array where we will put in the new files
var current_queue = [];
// the array we want to upgrade
var oldArray = myDropzone.files;
// on the webpage search for all the images that have been uploaded
var imageTags = $('#myDropzone').find('div.dz-image img');
// iterate through all the images that have been uploaded by the user
imageTags.each(function( index, imageTag ) {
// get the image name from the images
imageName = imageTag.alt;
// now we will iterate through the old array
var i;
for (i = 0; i < oldArray.length; i++) {
/** if the name of the image on the website is the same as the image from the old array
* we will add it to the new array. You can see this as sorting the array.
*/
if(imageName === oldArray[i].name){
current_queue.push(oldArray[i]);
}
}
});
/** after everything is done we will update the old array with the
* new array so it knows that the files have been sorted.
*/
myDropzone.files = current_queue;
// dropzone will now submit the request
e.stopPropagation();
myDropzone.processQueue();
});
if you are interested in the full dropzone js script:
$("#myDropzone").sortable({
opacity: 0.7,
});
Dropzone.options.myDropzone = {
// Configuration
url: '../somewhere',
method: 'post',
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: 100,
maxFiles: 100,
addRemoveLinks: true,
// The setting up of the dropzone
init: function() {
// very important to make the sortable work
var myDropzone = this;
// In your drop zone you have your click handler event
document.getElementById("submit").addEventListener("click", function(e) {
// Make sure that the form isn't actually being sent.
e.preventDefault();
// the new array where we will put in the new files
var current_queue = [];
// the array we want to upgrade
var oldArray = myDropzone.files;
// on the webpage search for all the images that have been uploaded
var imageTags = $('#myDropzone').find('div.dz-image img');
// iterate through all the images that have been uploaded by the user
imageTags.each(function( index, imageTag ) {
// get the image name from the images
imageName = imageTag.alt;
// now we will iterate through the old array
var i;
for (i = 0; i < oldArray.length; i++) {
/** if the name of the image on the website is the same as the image from the old array
* we will add it to the new array. You can see this as sorting the array.
*/
if(imageName === oldArray[i].name){
current_queue.push(oldArray[i]);
}
}
});
/** after everything is done we will update the old array with the
* new array so it knows that the files have been sorted.
*/
myDropzone.files = current_queue;
// dropzone will now submit the request
e.stopPropagation();
myDropzone.processQueue();
});
this.on('completemultiple', function(file, json) {
});
// sendingmultiple event
// of the sending event because uploadMultiple is set to true.
this.on("sendingmultiple", function(data, xhr, formData) {
formData.append("name", jQuery("#name").val());
formData.append("sample1", jQuery("#sample1").val());
});
this.on("successmultiple", function(files, response) {
// redirecting user on success. No message atm.
var url = document.location.origin + "/somewhere_to_redirect";
window.location.replace(url);
});
this.on("errormultiple", function(files, response) {
// Gets triggered when there was an error sending the files.
// Maybe show form again, and notify user of error
});
}
}

Jquery File Upload, launch the upload in two steps

I'm struggling with the plugin JQuery-File-upload.
I would like to divide the upload process in two steps.
Step1 - when a file is selected in the input[type=file], I would
like to test its type and its size. The only accepted files should be
image files with a size < 4MB.
If the file does not match with these constraints a popup is displayed with an error message.
If the file is OK I display the name of the file in the input[type=text]
Step2 - when the user click on the button "OK", the upload of the
file start
I have the following code
$('#file-img').fileupload({
dataType: 'json',
autoUpload: false,
formData: {type: 'businessPicture'},
add: function (e, data) {
var uploadErrors = [];
var acceptFileTypes = /^image\/(gif|jpe?g|png)$/i;
if(data.originalFiles[0]['type'].length && !acceptFileTypes.test(data.originalFiles[0]['type'])) {
uploadErrors.push('Not an accepted file type');
}
if(data.originalFiles[0]['size'].length && data.originalFiles[0]['size'] > 4000000) {
uploadErrors.push('File size is too big');
}
if(uploadErrors.length > 0) {
alert(uploadErrors.join("\n"));
} else {
$.each(data.files, function (index, file) {
$("#txt-file-img").val(file.name);
});
//I COMMENT THIS LINE because if I do not, the upload start here.
//data.submit();
}
},
done: function (e, data) {
$("#output").html('<p class="valid">SUCCESS!</p>');
$.each(data.result.files, function (index, file) {
$("#preview-picture").css("max-width","160px");
$("#preview-picture").css("max-height","150px");
$("#preview-picture").attr("src",file.url+'#'+ new Date().getTime());
});
},
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#upload-progress .bar').css(
'width',
progress + '%'
).text(
progress + '%'
);
},
fail: function (e, data) {
$("#output").html('<p class="error">FAIL!</p>');
}
});
});
I don't understand most of the example provided on the Plugin website.
With the code above, the control (size & type) are OK but I don't know how to start the upload only after clicking on the button.
According to you what is the best way to manage this behaviour?
Thanks a lot
OK it's pretty easy. I just have to bind the click event on the button with the action data.submit directly under the event add of the instance $('#file-img').fileupload().
$('#file-img').fileupload({
dataType: 'json',
autoUpload: false,
formData: {type: 'businessPicture'},
add: function (e, data) {
var uploadErrors = [];
var acceptFileTypes = /^image\/(gif|jpe?g|png)$/i;
if(data.originalFiles[0]['type'].length && !acceptFileTypes.test(data.originalFiles[0]['type'])) {
uploadErrors.push('Not an accepted file type');
}
if(data.originalFiles[0]['size'].length && data.originalFiles[0]['size'] > 4000000) {
uploadErrors.push('File size is too big');
}
if(uploadErrors.length > 0) {
alert(uploadErrors.join("\n"));
} else {
$.each(data.files, function (index, file) {
$("#txt-file-img").val(file.name);
});
$("#btn_add_valid").on('click',function () {
data.submit();
});
}
},
//The rest of the function....

Php page isnt sending GET request on load

I'm trying to use the jQuery File Uploader, but I'm having trouble. I've downloaded the plugin from here:
https://github.com/blueimp/jQuery-File-Upload
and been through the documentation. I've got the plugin working on my site, within its own sub directory, but now I want to use the functionality on my own page and I'm having a problem.
When i navigate to the plugin pages in the subdirectory everthing works normally, but when I copy elements of the code and deploy it to my own page something is going wrong. Specifically, on the plugin page the thumbnails all show OK, even on my own server - however, when I use it on my page the thumbs arent uploading properly.
I've noticed that when the page from the plugin loads, there is GET call to the /server/php folder, which contains the file that holds the classes for the plugin, but on my page the same call doesnt run.
I have the pages online in case anyone can help:
The one that is working is here:
http://54.187.7.151/jQfileUpload/index.html
and the one that isnt is here:
http://54.187.7.151/div.php
In terms of file structure, I'm calling the handler script within the plugin from jQuery (/jQfileUpload/server/php) from 'div.php' and just (/server/php) from /jQfileUpload/index.html, since it sits in the same folder.
This has been killing me all afternoon!! Can anyone help?
JQuery for http://54.187.7.151/jQfileUpload/index.html (the only difference between this and div.php is the path to the handler script :
$(function () {
'use strict';
// Change this to the location of your server-side upload handler:
var url = window.location.hostname === 'blueimp.github.io' ?
'//jquery-file-upload.appspot.com/' : 'server/php/',
uploadButton = $('<button/>')
.addClass('btn btn-primary')
.prop('disabled', true)
.text('Processing...')
.on('click', function () {
var $this = $(this),
data = $this.data();
$this
.off('click')
.text('Abort')
.on('click', function () {
$this.remove();
data.abort();
});
data.submit().always(function () {
$this.remove();
});
});
$('#fileupload').fileupload({
url: url,
dataType: 'json',
autoUpload: false,
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
maxFileSize: 5000000, // 5 MB
// Enable image resizing, except for Android and Opera,
// which actually support image resizing, but fail to
// send Blob objects via XHR requests:
disableImageResize: /Android(?!.*Chrome)|Opera/
.test(window.navigator.userAgent),
previewMaxWidth: 100,
previewMaxHeight: 100,
previewCrop: true
}).on('fileuploadadd', function (e, data) {
data.context = $('<div/>').appendTo('#files');
$.each(data.files, function (index, file) {
var node = $('<p/>')
.append($('<span/>').text(file.name));
if (!index) {
node
.append('<br>')
.append(uploadButton.clone(true).data(data));
}
node.appendTo(data.context);
});
}).on('fileuploadprocessalways', function (e, data) {
var index = data.index,
file = data.files[index],
node = $(data.context.children()[index]);
if (file.preview) {
node
.prepend('<br>')
.prepend(file.preview);
}
if (file.error) {
node
.append('<br>')
.append($('<span class="text-danger"/>').text(file.error));
}
if (index + 1 === data.files.length) {
data.context.find('button')
.text('Upload')
.prop('disabled', !!data.files.error);
}
}).on('fileuploadprogressall', function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress .progress-bar').css(
'width',
progress + '%'
);
}).on('fileuploaddone', function (e, data) {
$.each(data.result.files, function (index, file) {
if (file.url) {
var link = $('<a>')
.attr('target', '_blank')
.prop('href', file.url);
$(data.context.children()[index])
.wrap(link);
} else if (file.error) {
var error = $('<span class="text-danger"/>').text(file.error);
$(data.context.children()[index])
.append('<br>')
.append(error);
}
});
}).on('fileuploadfail', function (e, data) {
$.each(data.files, function (index) {
var error = $('<span class="text-danger"/>').text('File upload failed.');
$(data.context.children()[index])
.append('<br>')
.append(error);
});
}).prop('disabled', !$.support.fileInput)
.parent().addClass($.support.fileInput ? undefined : 'disabled');
});

Blueimp File Upload Plugin uploads only once

I have this strange issue and I've tried several solutions (even implementing the same as the Basic Plus demo on their website). I can upload files just fine, single or multiple. They upload on a click of the individual item, or a "Upload All" button. The problem is trying to add additional files before or after uploading. The file upload plugin will not even detect that these files are changing in the file input, so it never fires the "fileuploadadd" event, and requires me to refresh the page in order to upload more files. I'm wondering whether the fileupload change event is being lost somewhere, but I cannot for the life of me figure out where.
Also, does the blueimp file upload plugin require a specific return format of JSON? At the minute, I'm just returning "{\"status\":\"success\"} if the uploads are a success, and a similar error message. EDIT: Changing the response format to examples shown by blueimp had no effect.
Here's some code for the uploader I'm using. Note that I'm currently using ASP.NET and jQuery 2.0.3, and jQuery UI 1.9.2.
function initFileUploader() {
//initProgressBars();
$(upload_progressbar_title).css('display', 'none');
$(upload_progressbar).css('display', 'none');
$(upload_upload).on('click', function () {
$(upload_progressbar).css('display', 'block');
$(upload_progressbar_title).css('display', 'block');
$('.uploadbtn').click();
});
$(upload_browse).on('click', function () {
$(upload_file).click();
return false;
});
$.guid = 0;
console.log('initialising file upload');
var uploadButton = $('<input type="button" id="button" />')
.addClass('button tiny').addClass('uploadbtn')
.prop('disabled', true)
.val('Processing...');
var uploadCon = $('<div class="small-4 medium-6 large-6 columns progresscontainer" />')
.append('<div class="progressbar" />')
.append('<label class="progressbarlabel">Not uploading</label>');
uploadCon.append($(uploadButton).on('click', function () {
var $this = $(this),
data = $this.parent().data();
$this
.off('click')
.val('Abort')
.on('click', function () {
$this.remove();
data.abort();
});
data.submit().always(function () {
$this.remove();
}).success(function (result, textStatus, jqXHR) { console.log("Result: " + result + " - TextStatus " + textStatus); })
.error(function (jqXHR, textStatus, errorThrown) { console.log("Error: " + errorThrown + " - TextStatus " + textStatus); })
.complete(function (result, textStatus, jqXHR) { console.log("Result: " + result + " - TextStatus " + textStatus); });
}));
$(upload_file).fileupload({
dataType: 'json',
autoUpload: false,
acceptFileTypes: /(\.|\/)(pdf|jpe?g|png|doc|docx)$/i,
maxFileSize: 5000000, // 5 MB
}).on('fileuploadadd', function (e, data) {
var uniqueId = $.guid++;
data.context = $('<div id="div_upload_dcon_' + uniqueId +'" class="row"/>').appendTo(upload_filescon);
$.each(data.files, function (index, file) {
file.uniqueId = uniqueId;
var node = $('<div id="div_fname" class="small-6 medium-4 large-4 columns"/>')
.append($('<span/>').text(file.name));
if (!index) {
data.url = baseUrl + 'PostUploadFile?fileName=' + data.files[index].name + '&refId=' + ClientRefId + '&upbyid=' + ClientUserId + '&ticketId=' + globalTicketId;
var contentNode = (uploadCon.clone(true).data(data));
}
node.appendTo(data.context);
$(contentNode).appendTo(data.context);
$(upload_file).on('change', function () {
alert('changing fileinput');
});
});
}).on('fileuploadstart', function (e, data) {
initProgressBars();
}).on('fileuploadchange', function (e, data) {
alert('changing');
}).on('fileuploadprocessalways', function (e, data) {
var index = data.index,
file = data.files[index],
node = $(data.context.children()[index]);
if (file.error) {
console.log(file.error));
}
if (index + 1 === data.files.length) {
$('.uploadbtn').val('Upload').prop('disabled', !!data.files.error);
}
}).on('fileuploadprogress', function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#div_upload_dcon_' + data.files[0].uniqueId).progressbar('value', progress);
}).on('fileuploadprogressall', function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$(upload_progressbar).progressbar('value', progress);
}).on('fileuploaddone', function (e, data) {
getTicketContent(globalTicketId);
}).on('fileuploadstop', function (e, data) {
$(upload_file).val('');
}).on('fileuploadfail', function (e, data) {
$.each(data.files, function (index, file) {
var error = $('<span class="text-danger"/>').text('File upload failed.');
$(data.context.children()[index])
.append('<br>')
.append(error);
});
});
}
Well, after a night's sleep and more thinking about it, I specified this option
replaceFileInput: false,
during the file upload initialisation. And guess what, it works as intended now. I'm guessing that the file input was being lost because the fileupload clones the control by default after an upload or change.
Thanks for any consideration anyone may have given this, I hope it comes in handy for someone else in the future.
It's been two years since the original answer, but I just figured this out for my own case (:
If you use replaceFileInput: false, the code will not work in IE9, which does not support the newer file upload APIs. According to the documentation the fallback support for this browser depends on an "iframe transport" that requires the file input element be replaced each time. Reading that was the big clue for me.
What's really killing you is this:
$(upload_browse).on('click', function () {
$(upload_file).click();
return false;
});
You are assuming that upload_file is still the same element, but it's been replaced with a clone. You're firing a click event on the old file input element. It exists, so you get the system browse dialog, but it's not hooked up to any plumbing.
So the correct solution with full IE9 support is to use "find" to locate upload_file anew each time this click handler is fired. You didn't include your code for setting upload_file, so I don't know what the correct selector would be in your case, but it would look something like this:
$(upload_browse).on('click', function () {
// You should use a more specific selector, better yet use
// find() to locate it inside some div you know is "in scope" so
// you don't fire every file input on the page. Just an example
$('input[type=file]').click();
return false;
});

JavaScript unexpected asynchronization

I'm trying to make a file uploader for my blog system which would just let users drop files in it and it would automatically upload them to server. Strangely (for me), console.log outputs dataArray before it gets filled, while calling it after a timeout outputs it correctly.
For example, if I drop 4 files on my drop area, I would get this:
[]
[file1, file2, file3, file4]
Then I drop 4 more files without uploading/refreshing and I get:
[file1, file2, file3, file4]
[file1, file2, file3, file4, file5, file6, file7, file8]
So my script is working asynchronously for some reason? Can somebody tell me what I'm doing wrong here?
var dataArray = [];
$('.dropArea').bind(
{
drop: function(e)
{
e.stopPropagation();
e.preventDefault();
var files = e.dataTransfer.files;
$.each(files, function(index, file)
{
var fileReader = new FileReader();
fileReader.onload = (function(file)
{
return function(e)
{
var image = this.result;
dataArray.push({
name : file.name,
value: image
});
}
})(files[index]);
fileReader.readAsDataURL(file);
});
console.log(dataArray);
setTimeout(function() { console.log(dataArray) }, 1000);
},
});
you should console.log() in the callback.
fileReader.onload = (function(file)
{
return function(e)
{
var image = this.result;
dataArray.push({
name : file.name,
value: image
});
console.log(dataArray);
}
})(files[index]);
if you call it outside of the callback, it will run immediately after the image is starting to load instead of when the images are finished loading.
I've quickly drawn an image for clarification:
You can solve this by comparing the amount of images that is dropped and the amount of images that finished loading, like so:
var dataArray = [];
var count = 0; // amount of files dropped
var ready = 0; // amount of files finished loading
$('.dropArea').bind(
{
drop: function(e)
{
...
$.each(files, function(index, file)
{
count++; // we start handling a file here so we increment
...
fileReader.onload = (function(file)
{
return function(e)
{
...
ready++; // this image has finished loading so we increment
}
})(files[index]);
});
setTimeout(function() {
if(ready === count) {
// all images have been loaded
console.log(dataArray);
}
}, 1000);
},
});
Just as #Tim S. answered, onload event is fired when file starts loading, and it can take some time depending on file size. Here is how I solved discovering if all files are loaded.
drop: function(e)
{
e.stopPropagation();
e.preventDefault();
var files = e.dataTransfer.files;
console.log(files.length + " queued for upload");
var counter = 0;
var loaded = 0;
$.each(files, function(index, file)
{
counter++;
console.log("File #"+counter+" loading started.")
if (!files[index].type.match('image.*'))
{
$('.dropArea').html(message.error.onlyImage);
errorMessage++;
}
var fileReader = new FileReader();
fileReader.onload = (function(file, count)
{
return function(e)
{
var image = this.result;
dataArray.push({
name : file.name,
value: image
});
loaded++;
console.log("File #"+count+" loaded.");
if (loaded == files.length) {
console.log("Loading finished!");
}
}
})(files[index], counter);
fileReader.readAsDataURL(file);
});
}
And really, console log output looks like this:
4 queued for upload 9a840a0_part_2_dragndrop_2.js:25
File #1 loading started. 9a840a0_part_2_dragndrop_2.js:32
File #2 loading started. 9a840a0_part_2_dragndrop_2.js:32
File #3 loading started. 9a840a0_part_2_dragndrop_2.js:32
File #4 loading started. 9a840a0_part_2_dragndrop_2.js:32
File #2 loaded. 9a840a0_part_2_dragndrop_2.js:52
File #4 loaded. 9a840a0_part_2_dragndrop_2.js:52
File #3 loaded. 9a840a0_part_2_dragndrop_2.js:52
File #1 loaded. 9a840a0_part_2_dragndrop_2.js:52
Loading finished!
As can be seen, file #2 was loaded first because it is the smallest one, while #1 loaded last while it is the largest.

Categories