I've created a custom Google Form which allows a logged in Google user to include multiple files with the form.
The files are uploaded into a newly created folder in Google Drive.
The Google Script uses the current user's email address + current date and time as the folder name.
Problem and expected result:
Because of the forEach function, the script uploads each file into a separate folder (with the same name).
I would like to upload each file of one form submission into one folder.
HTML:
<iframe name="hidden_iframe" id="hidden_iframe" style="display:none;" onload="if(submitted) { picUploadJs(myForm); }"></iframe>
<form id="myForm" class="col s12" action="https://docs.google.com/forms/d/e/xxx/formResponse" target="hidden_iframe" onsubmit="submitted=true;">
<input placeholder="1234" name="entry.1234" id="user" type="text">
<label for="user">User:</label>
<input name="picToLoad" type="file" id="sampleFile" />
<div id="status" style="display: none">
Uploading. Please wait...
</div>
<button type="submit" name="action">Send</button> <!-- Modified -->
</form>
Javascript:
function picUploadJs(myForm) {
const f = document.getElementById("files");
[...f.files].forEach((file, i) => {
const fr = new FileReader();
fr.onload = (e) => {
const data = e.target.result.split(",");
const obj = {fileName: file.name, mimeType: data[0].match(/:(\w.+);/)[1], data: data[1]};
document.getElementById('status').style.display = 'inline';
google.script.run.withSuccessHandler(updateOutput).processForm(obj);
}
fr.readAsDataURL(file);
});
}
function updateOutput() {
var outputDiv = document.getElementById('status');
outputDiv.innerHTML = "The File was UPLOADED!";
window.location='https://Thankyou';
}
Google Script:
function doGet(e) {
Logger.log("doGet done");
return HtmlService.createTemplateFromFile('test')
.evaluate() // evaluate MUST come before setting the Sandbox mode
.setTitle('Form')
//.setSandboxMode();//Defaults to IFRAME which is now the only mode available
}
function processForm(theForm) {
var dateTime = Utilities.formatDate(new Date(), "GMT+2", "dd-MM-yy_HH-mm");
var email = Session.getActiveUser().getEmail();
var parentFolder=DriveApp.getFolderById('xxxxx');
var newFolder=parentFolder.createFolder(email + "_" + dateTime);
var newFolderId = DriveApp.getFoldersByName(newFolder).next().getId();
var fileBlob = Utilities.newBlob(Utilities.base64Decode(theForm.data), theForm.mimeType, theForm.fileName);
var fldrSssn = DriveApp.getFolderById(newFolderId);
fldrSssn.createFile(fileBlob);
return true;
}
When the files are submitted, you want to create one new folder every submitting.
You want to upload multiple files to the created folder.
If my understanding is correct, how about this modification? Please think of this as just one of several answers.
Modified scripts:
HTML:
Please modify as follows.
From:
<input name="picToLoad" type="file" id="sampleFile" />
To:
<input name="picToLoad" type="file" id="files" multiple />
Javascript:
Please modify picUploadJs().
function picUploadJs(myForm) {
const f = document.getElementById("files");
google.script.run.withSuccessHandler((folderId) => { // Added
var files = [...f.files];
files.forEach((file, i) => {
const fr = new FileReader();
fr.onload = (e) => {
const data = e.target.result.split(",");
const obj = {fileName: file.name, mimeType: data[0].match(/:(\w.+);/)[1], data: data[1]};
document.getElementById('status').style.display = 'inline';
google.script.run.withSuccessHandler(() => {
if (i == files.length - 1) updateOutput();
}).processForm(obj, folderId); // Modified
}
fr.readAsDataURL(file);
});
}).createFolder();
}
Google Apps Script:
I separated processForm() to processForm() and createFolder().
// Added
function createFolder() {
var dateTime = Utilities.formatDate(new Date(), "GMT+2", "dd-MM-yy_HH-mm");
var email = Session.getActiveUser().getEmail();
var parentFolder = DriveApp.getFolderById('xxxxx');
var newFolder = parentFolder.createFolder(email + "_" + dateTime);
var newFolderId = DriveApp.getFoldersByName(newFolder).next().getId();
return newFolderId;
}
// Modified
function processForm(theForm, newFolderId) {
var fileBlob = Utilities.newBlob(Utilities.base64Decode(theForm.data), theForm.mimeType, theForm.fileName);
var fldrSssn = DriveApp.getFolderById(newFolderId);
fldrSssn.createFile(fileBlob);
return true;
}
Note:
In your HTML, the top line is as follows.
<form id="myForm" target="hidden_iframe" onsubmit="submitted=true;">
From your previous question, if you are using the following lines, please update your question. I supposed that you are using the following script.
<iframe name="hidden_iframe" id="hidden_iframe" style="display:none;" onload="if(submitted) { picUploadJs(myForm); }"></iframe>
<form id="myForm" action="https://docs.google.com/forms/d/e/xxx/formResponse" target="hidden_iframe" onsubmit="submitted=true;">
If this was not the result you want, I apologize.
What I'm trying to achieve is to append a file to a post request which I got from a drag & drop field with javascript.
The problem is, I don't want to read all input fields and post the data by ajax call, I want to use the default submit method from #HTML.BeginForm.
When I do this, the multipart doesn't really contain the file.
(Attention: It works when I just submit the file or when I read all input fields manually and submit with a separate ajax.)
My code:
Drag&Drop js:
var file;
var isDragged = false;
var formData;
function dropHandler(ev) {
isDragged = true;
ev.preventDefault();
// Use DataTransfer interface to access the file(s)
for (var i = 0; i < ev.dataTransfer.files.length; i++) {
file = ev.dataTransfer.files[i];
formData = new FormData($("#form"));
formData.append("File.PayLoad", file);
formData.append("File.FileMetadataId", $('#File_FileMetadataId').val())
formData.append("File.FileObjectId", $('#File_FileObjectId').val())
}
}
HTML:
#using (Html.BeginForm("Edit", "DocumentTemplates", FormMethod.Post, new { role = "form", enctype = "multipart/form-data", id = "form" }))
{
#Html.AntiForgeryToken()
<div class="row">
<div class="col-xs-4">
#Html.LabelFor(model => model.Language)
</div>
<div class="col-xs-8">
#Html.HiddenFor(model => model.Language) #Html.DisplayFor(model => model.Language)
</div>
</div>
<div class="row">
<div class="col-xs-8">
#Html.TextBoxFor(model => model.File.Payload, new { type = "file", #id = "browseFile", ondrop = "dropHandler(event);", ondragover = "dragOverHandler(event);" })
#Html.ValidationMessageFor(model => model.File.Payload, null, new { #class = "text-danger" }) or Drag & Drop a File.
</div>
</div>
}
Request in Fiddler with empty Filename:
-----------------------------7e27b381715d4
Content-Disposition: form-data; name="File.FileMetadataId"
44
-----------------------------7e27b381715d4
Content-Disposition: form-data; name="File.FileObjectId"
44
-----------------------------7e27b381715d4
Content-Disposition: form-data; name="File.Payload"; filename=""
Content-Type: application/octet-stream
-----------------------------7e27b381715d4--
UPDATE:
I found out, you can overwrite the files from a file input, but only in Chrome. Since I need it to work on IE 11, this doesn't help me, but maybe it helps someone else. You don't need to append all the form fields, but just set the input type file to your dropped file and submit…
You have several problems there. One of the problems is code below. You missed one } in your code.
If you put it like below, the last value just store in file that is incorrect.
for (var i = 0; i < ev.dataTransfer.files.length; i++) {
file = ev.dataTransfer.files[i];
} // missing }
If you put it like below, the last value just store in formData that is incorrect.
function dropHandler(ev) {
isDragged = true;
ev.preventDefault();
// Use DataTransfer interface to access the file(s)
for (var i = 0; i < ev.dataTransfer.files.length; i++) {
file = ev.dataTransfer.files[i];
formData = new FormData($("#form"));
formData.append("File.PayLoad", file);
formData.append("File.FileMetadataId", $('#File_FileMetadataId').val());
formData.append("File.FileObjectId", $('#File_FileObjectId').val());
}
} // missing }
Second problem is ev.dataTransfer.files. As you can see in File drag and drop, it's better to check ev.dataTransfer.items and sometimes it has your files and ev.dataTransfer.files is empty.
Finally, you can do it like this:
function dropHandler(ev) {
isDragged = true;
ev.preventDefault();
formData = new FormData($("#form"));
if (ev.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
for (var i = 0; i < ev.dataTransfer.items.length; i++) {
// If dropped items aren't files, reject them
if (ev.dataTransfer.items[i].kind === 'file') {
var file = ev.dataTransfer.items[i].getAsFile();
formData.append("File.PayLoad" + i, file);
}
}
} else {
// Use DataTransfer interface to access the file(s)
for (var i = 0; i < ev.dataTransfer.files.length; i++) {
file = ev.dataTransfer.files[i];
formData.append("File.PayLoad" + i, file);
}
}
}
I have a form and file input. I want to select images from another folder.
echo '<pre>';
var_dump($_FILES);
echo '</pre>';
When I send my form I need to see all selected images but I see only the last selected.
For example, the first time I select 2 files. After selecting 5 files and submitting my form I see only the last selected files, but I need to send all 7 files. Is it possible??
<form action="" method="POST" enctype="multipart/form-data">
<input type="file" id="sell-images" name="images[]" multiple>
<input type="submit" value="submit">
</form>
I have this function for file counter:
$.fn.fileUploader = function (filesToUpload, sectionIdentifier) {
var fileIdCounter = 0;
this.closest("#sell-images").change(function (evt) {
var output = [];
for (var i = 0; i < evt.target.files.length; i++) {
fileIdCounter++;
var file = evt.target.files[i];
var fileId = sectionIdentifier + fileIdCounter;
filesToUpload.push({
id: fileId,
file: file
});
};
for(var z = 0; z < filesToUpload.length; z++)
{
evt.target.files[z] = filesToUpload[z]['file'];
}
console.log(evt.target.files)
});
return this;
};
Here is my onchange function:
$(document).ready(function() {
var filesToUpload = [];
var files1Uploader = $("#sell-images").fileUploader(filesToUpload, "images[]");
$("#sell-images").on('change',function(e) {
e.preventDefault();
Here I see in console all selected files from another folder
console.log($("#sell-images")[0].files);
var formData = new FormData();
for (var i = 0, len = filesToUpload.length; i < len; i++) {
formData.append("files", filesToUpload[i].file);
}
});
});
Here I have all selected files, but when I want to select a new file and send post in my PHP file, the global variable $_FILES shows me only the last selected files.
How can I send my all selected files?
I can do it with another technique every time when selecting a file with JavaScript hide that input and create new but I don't want that.
..., but I need to send all 7 files. Is it possible??
Yes it is possible.
How can I send my all selected files?
Use the array syntax for multiple files. With the current implementation, the last file overwrites all previous entries in the formData object. So update this line:
formData.append("files", filesToUpload[i].file);
To this:
formData.append("files[]", filesToUpload[i].file);
That way all the files in the array filesToUpload will be sent, instead of just the last one.
See this demonstrated in this plunker.
i have the following code, the issue i have is that i am getting a error of e.originalEvent.dataTransfer is undefined.
my code is as follows
HTML
Select images: <input type="file" id='fileupload' name="userfile[]" multiple>
Javascript is as follows
var hot = $('#fileupload');
hot.change(function (e)
{
e.preventDefault();
var files = e.originalEvent.dataTransfer.files;
//send dropped files to Server
handleFileUpload(files,hot);
});
function handleFileUpload(files,obj)
{
for (var i = 0; i < files.length; i++)
{
var fd = new FormData();
var e = document.getElementById("child_id");
fd.append('userfile[]', files[i]);
var filename=files[i].name;
var status = new createStatusbar(obj,files[i]); //Using this we can set progress.
status.setFileNameSize(files[i].name,files[i].size);
sendFileToServer(fd,status,filename);
}
}
The attribute files belongs to the input field. This you'll get by the target attribute.
If I test the setting above, I have success with this descriptor:
e.originalEvent.target.files
Then, files is an array of File objects, containing name, lastModifiedDate, type etc.
With window.open method I open new site with parameters, which I have to pass by post method.I've found solution, but unfortunately it doesn't work. This is my code:
<script type="text/javascript">
function openWindowWithPost(url,name,keys,values)
{
var newWindow = window.open(url, name);
if (!newWindow) return false;
var html = "";
html += "<html><head></head><body><form id='formid' method='post' action='" + url +"'>";
if (keys && values && (keys.length == values.length))
for (var i=0; i < keys.length; i++)
html += "<input type='hidden' name='" + keys[i] + "' value='" + values[i] + "'/>";
html += "</form><script type='text/javascript'>document.getElementById(\"formid\").submit()</sc"+"ript></body></html>";
newWindow.document.write(html);
return newWindow;
}
</script>
Next, I create arrays:
<script type="text/javascript">
var values= new Array("value1", "value2", "value3")
var keys= new Array("a","b","c")
</script>
And call function by:
<input id="Button1" type="button" value="Pass values" onclick="openWindowWithPost('test.asp','',keys,values)" />
But, when I click on this button, the site test.asp is empty (of course I try get pass values - Request.Form("b")).
How could I solve this problem, why I can't get pass values?
Instead of writing a form into the new window (which is tricky to get correct, with encoding of values in the HTML code), just open an empty window and post a form to it.
Example:
<form id="TheForm" method="post" action="test.asp" target="TheWindow">
<input type="hidden" name="something" value="something" />
<input type="hidden" name="more" value="something" />
<input type="hidden" name="other" value="something" />
</form>
<script type="text/javascript">
window.open('', 'TheWindow');
document.getElementById('TheForm').submit();
</script>
Edit:
To set the values in the form dynamically, you can do like this:
function openWindowWithPost(something, additional, misc) {
var f = document.getElementById('TheForm');
f.something.value = something;
f.more.value = additional;
f.other.value = misc;
window.open('', 'TheWindow');
f.submit();
}
To post the form you call the function with the values, like openWindowWithPost('a','b','c');.
Note: I varied the parameter names in relation to the form names to show that they don't have to be the same. Usually you would keep them similar to each other to make it simpler to track the values.
Since you wanted the whole form inside the javascript, instead of writing it in tags, you can do this:
let windowName = 'w_' + Date.now() + Math.floor(Math.random() * 100000).toString();
var form = document.createElement("form");
form.setAttribute("method", "post");
form.setAttribute("action", "openData.do");
form.setAttribute("target", windowName);
var hiddenField = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", "message");
hiddenField.setAttribute("value", "val");
form.appendChild(hiddenField);
document.body.appendChild(form);
window.open('', windowName);
form.submit();
I completely agree with mercenary's answer posted above and created this function for me which works for me. It's not an answer, it's a comment on above post by mercenary
function openWindowWithPostRequest() {
var winName='MyWindow';
var winURL='search.action';
var windowoption='resizable=yes,height=600,width=800,location=0,menubar=0,scrollbars=1';
var params = { 'param1' : '1','param2' :'2'};
var form = document.createElement("form");
form.setAttribute("method", "post");
form.setAttribute("action", winURL);
form.setAttribute("target",winName);
for (var i in params) {
if (params.hasOwnProperty(i)) {
var input = document.createElement('input');
input.type = 'hidden';
input.name = i;
input.value = params[i];
form.appendChild(input);
}
}
document.body.appendChild(form);
window.open('', winName,windowoption);
form.target = winName;
form.submit();
document.body.removeChild(form);
}
Even though I am 3 years late, but to simplify Guffa's example, you don't even need to have the form on the page at all:
$('<form method="post" action="test.asp" target="TheWindow">
<input type="hidden" name="something" value="something">
...
</form>').submit();
Edited:
$('<form method="post" action="test.asp" target="TheWindow">
<input type="hidden" name="something" value="something">
...
</form>').appendTo('body').submit().remove();
Maybe a helpful tip for someone :)
You could simply use target="_blank" on the form.
<form action="action.php" method="post" target="_blank">
<input type="hidden" name="something" value="some value">
</form>
Add hidden inputs in the way you prefer, and then simply submit the form with JS.
I created a function to generate a form, based on url, target and an object as the POST/GET data and submit method. It supports nested and mixed types within that object, so it can fully replicate any structure you feed it: PHP automatically parses it and returns it as a nested array.
However, there is a single restriction: the brackets [ and ] must not be part of any key in the object (like {"this [key] is problematic" : "hello world"}). If someone knows how to escape it properly, please do tell!
Without further ado, here is the source:
function getForm(url, target, values, method) {
function grabValues(x) {
var path = [];
var depth = 0;
var results = [];
function iterate(x) {
switch (typeof x) {
case 'function':
case 'undefined':
case 'null':
break;
case 'object':
if (Array.isArray(x))
for (var i = 0; i < x.length; i++) {
path[depth++] = i;
iterate(x[i]);
}
else
for (var i in x) {
path[depth++] = i;
iterate(x[i]);
}
break;
default:
results.push({
path: path.slice(0),
value: x
})
break;
}
path.splice(--depth);
}
iterate(x);
return results;
}
var form = document.createElement("form");
form.method = method;
form.action = url;
form.target = target;
var values = grabValues(values);
for (var j = 0; j < values.length; j++) {
var input = document.createElement("input");
input.type = "hidden";
input.value = values[j].value;
input.name = values[j].path[0];
for (var k = 1; k < values[j].path.length; k++) {
input.name += "[" + values[j].path[k] + "]";
}
form.appendChild(input);
}
return form;
}
Usage example:
var obj = {
"a": [1, 2, [3, 4]],
"b": "a",
"c": {
"x": [1],
"y": [2, 3],
"z": [{
"a": "Hello",
"b": "World"
}, {
"a": "Hallo",
"b": "Welt"
}]
}
};
var form = getForm("http://example.com", "_blank", obj, "post");
document.body.appendChild(form);
form.submit();
form.parentNode.removeChild(form);
I found a better way to pass parameters to the popup window and even to retrieve parameters from it :
In the main page :
var popupwindow;
var sharedObject = {};
function openPopupWindow()
{
// Define the datas you want to pass
sharedObject.var1 =
sharedObject.var2 =
...
// Open the popup window
window.open(URL_OF_POPUP_WINDOW, NAME_OF_POPUP_WINDOW, POPUP_WINDOW_STYLE_PROPERTIES);
if (window.focus) { popupwindow.focus(); }
}
function closePopupWindow()
{
popupwindow.close();
// Retrieve the datas from the popup window
= sharedObject.var1;
= sharedObject.var2;
...
}
In the popup window :
var sharedObject = window.opener.sharedObject;
// function you have to to call to close the popup window
function myclose()
{
//Define the parameters you want to pass to the main calling window
sharedObject.var1 =
sharedObject.var2 =
...
window.opener.closePopupWindow();
}
That's it !
And this is very convenient because:
You have not to set parameters in the URL of the popup window.
No form to define
You can use illimited parameters even objects.
Bi-directionnal : you can pass parameters AND, if you want you, can retreive new parameters.
Very easy to implement.
Have Fun!
I wanted to do this in React using plain Js and the fetch polyfill.
OP didn't say he specifically wanted to create a form and invoke the submit method on it, so I have done it by posting the form values as json:
examplePostData = {
method: 'POST',
headers: {
'Content-type' : 'application/json',
'Accept' : 'text/html'
},
body: JSON.stringify({
someList: [1,2,3,4],
someProperty: 'something',
someObject: {some: 'object'}
})
}
asyncPostPopup = () => {
//open a new window and set some text until the fetch completes
let win=window.open('about:blank')
writeToWindow(win,'Loading...')
//async load the data into the window
fetch('../postUrl', this.examplePostData)
.then((response) => response.text())
.then((text) => writeToWindow(win,text))
.catch((error) => console.log(error))
}
writeToWindow = (win,text) => {
win.document.open()
win.document.write(text)
win.document.close()
}
The default submit Action is Ext.form.action.Submit, which uses an Ajax request to submit the form's values to a configured URL. To enable normal browser submission of an Ext form, use the standardSubmit config option.
Link: http://docs.sencha.com/extjs/4.2.1/#!/api/Ext.form.Basic-cfg-standardSubmit
solution: put standardSubmit :true in your config. Hope that this will help you :)
I've used this in the past, since we typically use razor syntax for coding
#using (Html.BeginForm("actionName", "controllerName", FormMethod.Post, new { target = "_blank" }))
{
// add hidden and form filed here
}