I use jQuery Fileupload plugin for uploading files (.pdf,.zip,.dll and .png/.jpg) to server from local. All of file types above can be uploaded successfully on major browsers Chrome/Firefox/IE, except .png/.jpg.
When trying to upload the .png/.jpg on Chrome, the upload plugin will freeze this browser for 10-15 minutes until the ajax of sending data of XHR object is done, even the size of image is quite small (~90Kb). On contrast, the same process just takes 1-2 seconds to be done on FF/IE.
I am using v5.9 of this plugin with these overwrite options:
var maxFileSize = 500000000000000;
var resizeMaxWidth = 1920;
var resizeMaxHeight = 1200;
var maxChunkSize = 1073741824;
var maxNumberOfFiles = 1;
$('#fileupload').fileupload('option', {
forceIframeTransport: true,
maxFileSize: maxFileSize,
resizeMaxWidth: resizeMaxWidth,
resizeMaxHeight: resizeMaxHeight,
maxChunkSize: maxChunkSize,
xhrFields: {
withCredentials: true
},
acceptFileTypes: regularExpression,
autoUpload: autoUpload
});
I tried to set async: true; to force browser not to freeze the page, but failed.
and I got freezing page on Chrome when the scripts run to _onSend() event and can not return "success" callback.
_onSend: function (e, data) {
var that = this,
jqXHR,
slot,
pipe,
options = that._getAJAXSettings(data),
send = function (resolve, args) {
that._sending += 1;
jqXHR = jqXHR || (
(resolve !== false &&
that._trigger('send', e, options) !== false &&
(that._chunkedUpload(options) || $.ajax(options))) ||
that._getXHRPromise(false, options.context, args)
).success(function (result, textStatus, jqXHR) {
that._onDone(result, textStatus, jqXHR, options);
}).fail(function (jqXHR, textStatus, errorThrown) {
that._onFail(jqXHR, textStatus, errorThrown, options);
}).always(function (jqXHRorResult, textStatus, jqXHRorError) {
that._sending -= 1;
that._onAlways(
jqXHRorResult,
textStatus,
jqXHRorError,
options
);
if (options.limitConcurrentUploads &&
options.limitConcurrentUploads > that._sending) {
// Start the next queued upload,
// that has not been aborted:
var nextSlot = that._slots.shift();
while (nextSlot) {
if (!nextSlot.isRejected()) {
nextSlot.resolve();
break;
}
nextSlot = that._slots.shift();
}
}
});
return jqXHR;
};
this._beforeSend(e, options);
if (this.options.sequentialUploads ||
(this.options.limitConcurrentUploads &&
this.options.limitConcurrentUploads <= this._sending)) {
if (this.options.limitConcurrentUploads > 1) {
slot = $.Deferred();
this._slots.push(slot);
pipe = slot.pipe(send);
} else {
pipe = (this._sequence = this._sequence.pipe(send, send));
}
// Return the piped Promise object, enhanced with an abort method,
// which is delegated to the jqXHR object of the current upload,
// and jqXHR callbacks mapped to the equivalent Promise methods:
pipe.abort = function () {
var args = [undefined, 'abort', 'abort'];
if (!jqXHR) {
if (slot) {
slot.rejectWith(args);
}
return send(false, args);
}
return jqXHR.abort();
};
return this._enhancePromise(pipe);
}
return send();
},
Do you know how to force this plugin's Ajax to run on Chrome as it does on Firefox/IE ? or any hints to fix this issue ?
Thank you !
Cheers,
Related
I am using the Jquery file upload basic-plus.html. I am not sure How to use the Resumable uploading functionality in the below code.
I see something like this according to the documentation but not sure How to use that with the basic-plus.html
$('#fileupload').fileupload({
maxChunkSize: 10000000, // 10 MB
add: function (e, data) {
var that = this;
$.getJSON('server/php/', {file: data.files[0].name}, function (result) {
var file = result.file;
data.uploadedBytes = file && file.size;
$.blueimp.fileupload.prototype
.options.add.call(that, e, data);
});
}
});
I tried something like below. But didn't work.
basic-plus.html
$(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();
$.getJSON('server/php/', {file: data.files[0].name}, function (result) {
console.log('come here');
console.log(result);
var file = result.file;
data.uploadedBytes = file && file.size;
//$.blueimp.fileupload.prototype.options.add.call($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,
//multipart: false,
acceptFileTypes: /(\.|\/)(avi|flv|wmv|mpg|mp4|mov|3gp|m4v)$/i,
maxFileSize: 969932800, // 900 MB
maxChunkSize: 2000000, // 2 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));
console.log(node);
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');
});
I start by presenting my client side the service
addImage (url: string, params: string[], files: File[]): Observable {
return Observable.create(observer => {
let formData: FormData = new FormData(),
xhr: XMLHttpRequest = new XMLHttpRequest();
for (let i = 0; i < files.length; i++) {
formData.append("uploads[]", files[i], file
s[i].name);
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
observer.next(JSON.parse(xhr.response));
observer.complete();
} else {
observer.error(xhr.response);
}
}
};
xhr.upload.onprogress = (event) => {
this.progress = Math.round(event.loaded / event.total * 100);
this.progressObserver.next(this.progress);
};
xhr.open('POST', url, true);
xhr.send(formData);
});
}
then this is my html code
<input type="file" (change)="uploadImage($event)"/>
where I call this method from my component
uploadImage(event) {
var files = event.srcElement.files;
console.log(files);
this._serviceSection.addImage('http://localhost:8080/template/img', [], files).subscribe(() => {
console.log('sent');
});
}
and in my service side
this is a method from my controller
#RequestMapping(value = "/img", method = RequestMethod.POST)
public void getFileContents(#RequestParam MultipartFile file) {
System.out.println("++++++++++++++++++++++++++++++++++++++++ " +file.getOriginalFilename());
}
in a first time a try just to show a fileName but I get this error
It seems that your progressObserver isn't set. The reason for this could be that you didn't subscribe to its associated observable. Don't forget that observables are lazy and if you don't subscribe to them, their initialization callback isn't called.
To prevent from having the error, you could check if it's null or not:
xhr.upload.onprogress = (event) => {
if (this.progressObserver) {
this.progress = Math.round(event.loaded / event.total * 100);
this.progressObserver.next(this.progress);
}
};
Otherwise, you can notice that from RC2, Angular2 accepts FormData objects as parameters of HTTP methods...
I could not found any examples with this scenario so here we go:
I want the user choose a directory, load all files inside it, change them, and save this file overriding it or saving a new file in that same directory without asking where he want to save.
I don't know how to list the files of the directory
I don't know how to save a file in a directory without prompting the filechooser window
I believe it is possible because I see something similar here (last paragraph):
http://www.developer.com/lang/using-the-file-api-outside-the-sandbox-in-chrome-packaged-apps.html
Any answer will be appreciated, Thank you
EDIT: Thanks to Chris Johnsen for giving me this great answer:
var fileHandler = function() {
var _entry = null;
this.open = function(cb) {
chrome.fileSystem.chooseEntry({
type: 'openDirectory'
}, function(dirEntry) {
if (!dirEntry || !dirEntry.isDirectory) {
cb && cb(null);
return;
}
_entry = dirEntry;
listDir(_entry, cb);
});
};
this.save = function(filename, source) {
chrome.fileSystem.getWritableEntry(_entry, function(entry) {
entry.getFile(filename, {
create: true
}, function(entry) {
entry.createWriter(function(writer) {
writer.onwrite = function() {
writer.onwrite = null;
writer.truncate(writer.position);
};
writer.write(new Blob([source], {
type: 'text/javascript'
}));
});
});
});
};
this.saveAs = function(filename, source) {
chrome.fileSystem.chooseEntry({
type: 'openDirectory'
}, function(entry) {
chrome.fileSystem.getWritableEntry(entry, function(entry) {
entry.getFile(filename, {
create: true
}, function(entry) {
entry.createWriter(function(writer) {
writer.onwrite = function() {
writer.onwrite = null;
writer.truncate(writer.position);
};
writer.write(new Blob([source], {
type: 'text/javascript'
}));
});
});
});
});
};
var listDir = function(dirent, cb, listing) {
if (listing === undefined) {
listing = [];
}
var reader = dirent.createReader();
var read_some = reader.readEntries.bind(reader, function(ents) {
if (ents.length === 0) {
return cb && cb(listing);
}
var process_some = function(ents, i) {
for (; i < ents.length; i++) {
listing.push(ents[i]);
if (ents[i].isDirectory) {
return listDir(ents[i], process_some.bind(null, ents, i + 1), listing);
}
}
read_some();
};
process_some(ents, 0);
}, function() {
console.error('error reading directory');
});
read_some();
};
};
Your save method should work fine (mostly, see below) for your second requirement (write to a code-chosen filename without another user prompt), but there are a couple of bugs in open (at least as presented in the question):
Inside the chooseEntry callback, this !== fileHandler because the callback is invoked with a different this (probably the background page’s window object).
You can work around this in several ways:
Use fileHandler instead of this (if you are not using it as any kind of prototype).
Use .bind(this) to bind each of your callback functions to the same context.
Use var self = this; at the top of open and use self.entry (et cetera) in the callbacks.
You may want to call cb for the success case. Maybe you have another way of postponing calls to (e.g.) fileHandler.save (clicking on some element to trigger the save?), but adding something like
⋮
cb && cb(self.entry);
⋮
after self.entry = dirEntry makes it easy to (e.g.) chain open and save:
fileHandler.open(function(ent) {
fileHandler.save('newfile','This is the text\nto save in the (possibly) new file.');
});
There is a latent bug in save: if you ever overwrite an existing file, then you will want to call writer.truncate() (unless you always write more bytes than the file originally held).
⋮
writer.onwrite = function() {
writer.onwrite = null;
writer.truncate(writer.position);
};
writer.write(…);
⋮
It looks like you have a good start on the file listing part. If you want to reference the list of files later, then you might want to save them in your object instead of just logging them; this can get a bit hairy if you want to recurse into subdirectories (and also not assume that readEntries returns everything for its first call).
function list_dir(dirent, cb, listing) {
if (listing === undefined) listing = [];
var reader = dirent.createReader();
var read_some = reader.readEntries.bind(reader, function(ents) {
if (ents.length === 0)
return cb && cb(listing);
process_some(ents, 0);
function process_some(ents, i) {
for(; i < ents.length; i++) {
listing.push(ents[i]);
if (ents[i].isDirectory)
return list_dir(ents[i], process_some.bind(null, ents, i + 1), listing);
}
read_some();
}
}, function() {
console.error('error reading directory');
});
read_some();
}
You could use it in the open callback (assuming you add its success callback) like this:
fileHandler.open(function(ent) {
ent && list_dir(ent, function(listing) {
fileHandler.listing = listing;
console.log('listing', fileHandler.listing.map(function(ent){return ent.fullPath}).join('\n'));
fileHandler.save('a_dir/somefile','This is some data.');
});
});
I wanna save some images from a site. At the moment I can get the paths to the images but I have no clue how to get and save the images with phantomJs.
findRotationTeaserImages = ->
paths = page.evaluate ->
jQuery('.rotate img').map(-> return this.src).get()
for path, i in paths
console.log(path);
//save the image
I know this is an old question, but you do this pretty simply by storing the dimensions and location of each image on the in an object, then altering the phantomjs page.clipRect so that the page.render() method renders only the area where the image is. Here is an example, scraping multiple images from http://dribbble.com/ :
var page = require('webpage').create();
page.open('http://dribbble.com/', function() {
page.includeJs('//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js',function() {
var images = page.evaluate(function() {
var images = [];
function getImgDimensions($i) {
return {
top : $i.offset().top,
left : $i.offset().left,
width : $i.width(),
height : $i.height()
}
}
$('.dribbble-img img').each(function() {
var img = getImgDimensions($(this));
images.push(img);
});
return images;
});
images.forEach(function(imageObj, index, array){
page.clipRect = imageObj;
page.render('images/'+index+'.png')
});
phantom.exit();
});
});
There is now another way to do this.
var fs = require("fs");
var imageBase64 = page.evaluate(function(){
var canvas = document.createElement("canvas");
canvas.width =img.width;
canvas.height =img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
return canvas.toDataURL ("image/png").split(",")[1];
})
fs.write("file.png",atob(imageBase64),'wb');
Solve this by starting a child process running a node script that download the images:
phantomJs script:
findRotationTeaserImages = ->
paths = page.evaluate ->
jQuery('.rotate img').map(-> return this.src).get()
args = ('loadRotationTeaser.js ' + paths.join(' ')).split(' ')
child_process.execFile("node", args, null, (err, stdout, stderr) ->
phantom.exit()
)
nodeJs script
http = require('http-get');
args = process.argv.splice(2)
for path, i in args
http.get path, 'public/images/rotationTeaser/img' + i + '.jpeg', (error, result) ->
In case image dimensions are known:
var webPage = require('webpage');
/**
* Download image with known dimension.
* #param src Image source
* #param dest Destination full path
* #param width Image width
* #param height Image height
* #param timeout Operation timeout
* #param cbk Callback (optional)
* #param cbkParam Parameter to pass back to the callback (optional)
*/
function downloadImg(src, dest, width, height, timeout, cbk, cbkParam) {
var page = webPage.create();
page.settings.resourceTimeout = timeout; //resources loading timeout(ms)
page.settings.webSecurityEnabled = false; //Disable web security
page.settings.XSSAuditingEnabled = false; //Disable web security
page.open(src, function(status) {
// missing images sometime receive text from server
var success = status == 'success' && !page.plainText;
if (success) {
page.clipRect = {
top: 0,
left: 0,
width: width,
height: height
};
page.render(dest);
}
cbk && cbk(success, cbkParam);
page.close();
});
};
I've experienced really a lot troubles when using the render method. Luckily I finally come up with two better solution. Here is the code I used in my project. First solution has some trouble to update the cookie, so it cannot work well when fetching captcha image. Both method will cause a new http request. But with a few modifications, the second one can ommit such kind of request.
The first one fetches the cookie from phantomJs and makes a new http request using request. The second one uses base64 to pass the image.
async download(download_url, stream) {
logger.profile(`download(download_url='${download_url}')`);
let orig_url = await this.page.property('url');
download_url = url.resolve(orig_url, download_url);
let cookies = await this.page.property('cookies');
let jar = request.jar();
for (let cookie of cookies) {
if (cookie.name !== undefined) {
cookie.key = cookie.name;
delete cookie.name;
}
if (cookie.httponly !== undefined) {
cookie.httpOnly = cookie.httponly;
delete cookie.httponly;
}
if (cookie.expires !== undefined)
cookie.expires = new Date(cookie.expires);
jar.setCookie(new Cookie(cookie), download_url, {ignoreError: true});
}
let req = request({
url: download_url,
jar: jar,
headers: {
'User-Agent': this.user_agent,
'Referer': orig_url
}
});
await new Promise((resolve, reject) => {
req.pipe(stream)
.on('close', resolve)
.on('error', reject);
});
// Due to this issue https://github.com/ariya/phantomjs/issues/13409, we cannot set cookies back
// to browser. It is said to be redesigned, but till now (Mar 31 2017), no change has been made.
/*await Promise.all([
new Promise((resolve, reject) => {
req.on('response', () => {
jar._jar.store.getAllCookies((err, cookies) => {
if (err) {
reject(err);
return;
}
cookies = cookies.map(x => x.toJSON());
for (let cookie of cookies) {
if (cookie.key !== undefined) {
cookie.name = cookie.key;
delete cookie.key;
}
if (cookie.httpOnly !== undefined) {
cookie.httponly = cookie.httpOnly;
delete cookie.httpOnly;
}
if (cookie.expires instanceof Date) {
cookie.expires = cookie.expires.toGMTString();
cookie.expiry = cookie.expires.toTime();
}
else if (cookie.expires == Infinity)
delete cookie.expires;
delete cookie.lastAccessed;
delete cookie.creation;
delete cookie.hostOnly;
}
this.page.property('cookies', cookies).then(resolve).catch(reject);
});
}).on('error', reject);
}),
new Promise((resolve, reject) => {
req.pipe(fs.createWriteStream(save_path))
.on('close', resolve)
.on('error', reject);
})
]);*/
logger.profile(`download(download_url='${download_url}')`);
}
async download_image(download_url, stream) {
logger.profile(`download_image(download_url='${download_url}')`);
await Promise.all([
new Promise((resolve, reject) => {
this.client.once('donwload image', data => {
if (data.err)
reject(err);
else
stream.write(Buffer.from(data.data, 'base64'), resolve);
});
}),
this.page.evaluate(function (url) {
var img = new Image(), callback = function (err, data) {
callPhantom({
event: 'donwload image',
data: {
err: err && err.message,
data: data
}
});
};
img.onload = function () {
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext("2d").drawImage(img, 0, 0);
callback(null, canvas.toDataURL("image/png").replace(/^data:image\/(png|jpg);base64,/, ""));
};
img.onerror = function () {
callback(new Error('Failed to fetch image.'));
};
img.src = url;
}, download_url)
]);
logger.profile(`download_image(download_url='${download_url}')`);
}
I have the following JS method to bind the jQuery UI autocomplete widget to a search text box. Everything works fine, including caching, except that I make unnecessary server calls when appending my search term because I don't reuse the just-retrieved results.
For example, searching for "ab" fetches some results from the server. Typing "c" after "ab" in the search box fetches "abc" results from the server, instead of reusing the cached "ab" results and omitting ones that don't match "abc".
I went down the path of manually looking up the "ab" search results, filtering them using a regex to select the "abc" subset, but this totally seems like I'm reinventing the wheel. What is the proper, canonical way to tell the widget to use the "ab" results, but filter them for the "abc" term and redisplay the shortened dropdown?
function bindSearchForm() {
"use strict";
var cache = new Object();
$('#search_text_field').autocomplete({
minLength: 2,
source: function (request, response) {
var term = request.term;
if (term in cache) {
response(cache[term]);
return;
}
$.ajax({type: 'POST',
dataType: 'json',
url: '/get_search_data',
data: {q: term},
success: function (data) {
cache[term] = data;
response(data);
}
});
});
}
Here's my "brute-force, reinventing the wheel" method, which is, for now, looking like the right solution.
function bindSearchForm() {
"use strict";
var cache = new Object();
var terms = new Array();
function cacheNewTerm(newTerm, results) {
// maintain a 10-term cache
if (terms.push(newTerm) > 10) {
delete cache[terms.shift()];
}
cache[newTerm] = results;
};
$('#search_text_field').autocomplete({
minLength: 2,
source: function (request, response) {
var term = request.term.toLowerCase();
if (term in cache) {
response(cache[term]);
return;
} else if (terms.length) {
var lastTerm = terms[terms.length - 1];
if (term.substring(0, lastTerm.length) === lastTerm) {
var results = new Array();
for (var i = 0; i < cache[lastTerm].length; i++) {
if (cache[lastTerm][i].label.toLowerCase().indexOf(term) !== -1) {
results.push(cache[lastTerm][i]);
}
}
response(results);
return;
}
}
$.ajax({type: 'POST',
dataType: 'json',
url: '/get_search_data',
data: {q: term},
success: function (data) {
cacheNewTerm(term, data);
response(data);
return;
}
});
});
}
If anyone wants a version that supports multiple entries in the text box then please see below:
$(function () {
function split(val) {
return val.split(/,\s*/);
}
function extractLast(term) {
return split(term).pop();
}
var cache = new Object();
var terms = new Array();
function cacheNewTerm(newTerm, results) {
// keep cache of 10 terms
if (terms.push(newTerm) > 10) {
delete cache[terms.shift()];
}
cache[newTerm] = results;
}
$("#searchTextField")
.on("keydown",
function (event) {
if (event.keyCode === $.ui.keyCode.TAB &&
$(this).autocomplete("instance").menu.active) {
event.preventDefault();
}
})
.autocomplete({
minLength: 2,
source: function (request, response) {
var term = extractLast(request.term.toLowerCase());
if (term in cache) {
response(cache[term]);
return;
} else if (terms.length) {
var lastTerm = terms[terms.length - 1];
console.log('LAst Term: ' + lastTerm);
if (term.substring(0, lastTerm.length) === lastTerm) {
var results = new Array();
for (var i = 0; i < cache[lastTerm].length; i++) {
console.log('Total cache[lastTerm[.length] = ' +
cache[lastTerm].length +
'....' +
i +
'-' +
lastTerm[i]);
console.log('Label-' + cache[lastTerm][i]);
var cachedItem = cache[lastTerm][i];
if (cachedItem != null) {
if (cachedItem.toLowerCase().indexOf(term) !== -1) {
results.push(cache[lastTerm][i]);
}
}
}
response(results);
return;
}
}
$.ajax({
url: '#Url.Action("GetSearchData", "Home")',
dataType: "json",
contentType: 'application/json, charset=utf-8',
data: {
term: extractLast(request.term)
},
success: function (data) {
cacheNewTerm(term, data);
response($.map(data,
function (item) {
return {
label: item
};
}));
},
error: function (xhr, status, error) {
alert(error);
}
});
},
search: function () {
var term = extractLast(this.value);
if (term.length < 2) {
return false;
}
},
focus: function () {
return false;
},
select: function (event, ui) {
var terms = split(this.value);
terms.pop();
terms.push(ui.item.value);
terms.push("");
this.value = terms.join(", ");
return false;
}
});