I'm trying to use one file input element to upload multiple files to Drive using html form. This seems to work only for one file, although the file picker allows selecting multiple files. Back in the script log viewer, I only see one file captured of the two I uploaded. Is this unsupported, or am I going the wrong way about it?
Code.gs:
function logForm(form) {
Logger.log(JSON.stringify(form));
return true;
}
index.html:
<html>
<form id="uploadTest" enctype="multipart/form-data">
<input type="file" multiple="multiple" name="fileUpload">
<input type="button" id="upload" value="upload"
onclick="google.script.run.logForm(document.getElementById('uploadTest'));">
</form>
</html>
Log view:
{"fileUpload":{"contents":"GIF87a\u0001\u0000\u0001\u0000�
\u0000\u0000��̖��,\u0000\u0000\u0000\u0000\u0001\u0000
\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;",
"type":"image/gif","name":"1x1.gif","length":35}}
The multiple file select in the dialog when you click on the browse button of the file field happens only for the new browsers supporting HTML5. It wont allow multiple select for old browsers. For older browsers the only good solutions are flash or javascript plugins. Here is a good resource for jquery uploaders ( some support multiple files ): http://creativefan.com/10-ajax-jquery-file-uploaders/. Hence my suggestion is use some plugin so that its supported on old as well as the new browsers.
I'm using possibility to send array of files. Just add [] to name atrribute:
<form action="/" enctype="multipart/form-data" method="post">
<input type="file" name="files[]" />
<input type="file" name="files[]" />
// etc.
<input type="submit">
</form>
You will have array of arrays in $_FILES
Array
(
[files] => Array
(
[name] => Array
(
[0] => 1.png
[1] => 2.png
)
[type] => Array
(
[0] => image/png
[1] => image/png
)
[tmp_name] => Array
(
[0] => /tmp/phpDQOZWD
[1] => /tmp/phpCELeSw
)
[error] => Array
(
[0] => 0
[1] => 0
)
[size] => Array
(
[0] => 32209
[1] => 64109
)
)
)
Of course, you you'll have to upload them one by one. Not convenient to a large number of files, but works in all browsers. For example,using jQuery you can add one more input each time last files[] input was changed.
function addOneMoreInput() {
$('input[type=file]').last().change(function() {
$(this).after('<input type="file" name="files[]" />');
$(this).off('change');
addOneMoreInput();
});
}
addOneMoreInput();
How about this sample script?
In this sample, the following flow is run.
Select files at browser.
Upload the files every file.
Save each file in Google Drive.
In this sample script, the file IDs of created files are returned to the console.
When you use this, please copy and paste the Google Apps Script and HTML to the script editor, and run the HTML using the dialog, sidebar and Web Apps.
Code.gs: Google Apps Script
function saveFile(obj) {
var blob = Utilities.newBlob(Utilities.base64Decode(obj.data), obj.mimeType, obj.fileName);
return DriveApp.createFile(blob).getId();
}
index.html: HTML and Javascript
<input name="file" id="files" type="file" multiple>
<input type='button' value='Upload' onclick='getFiles()'>
<script>
function getFiles() {
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: f.files[i].name, mimeType: data[0].match(/:(\w.+);/)[1], data: data[1]};
google.script.run.withSuccessHandler((id) => {
console.log(id);
}).saveFile(obj);
}
fr.readAsDataURL(file);
});
}
</script>
Note:
index.html can be run as Web Apps and the side bar and dialog of Google Docs.
In this sample script, the input tag uses the multiple attribute.
Related
so basically the task is quite simple, but I didn't find any workable solution for my problem. I have a huge upload script on my website (at the moment localhost), but lets reduce all the complexity to the only neccessary.
So I just want to upload a single file to Google Drive with Google App Script and receive the URL of it to save it in a var, to work with that information on a later point in my function.
Now the problem is I already have the form on my website, I dont want the form inside script.google.com as extra html, I want to transfer my user input to Google App Script, then upload it to google drive and return the url back to my website where I can save it into a var.
My problem now is, I cant put all the stuff together.
This is the form on my website (simplified):
<form name="myForm" method="post">
<!-- <form name="first-form"> -->
<input type="text" placeholder="Name" id="myName">
<input type="file" name="myFile" id="myFile">
<button onclick="UploadFile()" type="submit">submit</button>
</form>
So how can I upload my informations inside google drive and get back a result? How can I push the data in Google App Script without using iFrame or anything else?
THANK YOU!
**** Working example if html is in scripts.google.com ****
gs
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('forms.html').setTitle("Google File Upload by CTRLQ.org");
}
function uploadFileToGoogleDrive(data, file, name, email) {
try {
var dropbox = "Received Files";
var folder, folders = DriveApp.getFoldersByName(dropbox);
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(dropbox);
}
/* Credit: www.labnol.org/awesome */
var contentType = data.substring(5,data.indexOf(';')),
bytes = Utilities.base64Decode(data.substr(data.indexOf('base64,')+7)),
blob = Utilities.newBlob(bytes, contentType, file),
file = folder.createFolder([name, email].join(" ")).createFile(blob);
return "OK";
} catch (f) {
return f.toString();
}
}
html in apps.googlescript
<!DOCTYPE html>
<html>
<head>
<base target="_blank">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Google File Upload by CTRLQ.org</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/css/materialize.min.css">
<style>
.disclaimer{width: 480px; color:#646464;margin:20px auto;padding:0 16px;text-align:center;font:400 12px Roboto,Helvetica,Arial,sans-serif}.disclaimer a{color:#009688}#credit{display:none}
</style>
</head>
<body>
<!-- Written by Amit Agarwal amit#labnol.org -->
<form class="main" id="form" novalidate="novalidate" style="max-width: 480px;margin: 40px auto;">
<div id="forminner">
<div class="row">
<div class="col s12">
<h5 class="center-align teal-text">Upload Files to my Google Drive</h5>
<p class="disclaimer">This File Upload Form (tutorial) is powered by Google Scripts</p>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input id="name" type="text" name="Name" class="validate" required="" aria-required="true">
<label for="name">Name</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input id="email" type="email" name="Email" class="validate" required="" aria-required="true">
<label for="email">Email Address</label>
</div>
</div>
<div class="row">
<div class="file-field input-field col s12">
<div class="btn">
<span>File</span>
<input id="files" type="file">
</div>
<div class="file-path-wrapper">
<input class="file-path validate" type="text" placeholder="Select a file on your computer">
</div>
</div>
</div>
<div class="row">
<div class="input-field col s6">
<button class="waves-effect waves-light btn submit-btn" type="submit" onclick="submitForm(); return false;">Submit</button>
</div>
</div>
<div class="row">
<div class="input-field col s12" id = "progress">
</div>
</div>
</div>
<div id="success" style="display:none">
<h5 class="left-align teal-text">File Uploaded</h5>
<p>Your file has been successfully uploaded.</p>
<p>The pro version (see demo form) includes a visual drag-n-drop form builder, CAPTCHAs, the form responses are saved in a Google Spreadsheet and respondents can upload multiple files of any size.</p>
<p class="center-align"><a class="btn btn-large" href="https://gum.co/GA14?wanted=true" target="_blank">Upgrade to Pro</a></p>
</div>
</form>
<div class="fixed-action-btn horizontal" style="bottom: 45px; right: 24px;">
<a class="btn-floating btn-large red">
<i class="large material-icons">menu</i>
</a>
<ul>
<li><a class="btn-floating red" href="https://gum.co/GA14" target="_blank" title="Buy License - File Upload Form"><i class="material-icons">monetization_on</i></a></li>
<li><a class="btn-floating blue" href="https://youtu.be/C_YBBupebvE" target="_blank" title="Video Tutorial"><i class="material-icons">video_library</i></a></li>
<li><a class="btn-floating green" href="http://www.labnol.org/internet/file-upload-google-forms/29170/" target="_blank" title="How to Create File Upload Forms"><i class="material-icons">help</i></a></li>
</ul>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/js/materialize.min.js"></script>
<script src="https://gumroad.com/js/gumroad.js"></script>
<script>
var file,
reader = new FileReader();
reader.onloadend = function(e) {
if (e.target.error != null) {
showError("File " + file.name + " could not be read.");
return;
} else {
google.script.run
.withSuccessHandler(showSuccess)
.uploadFileToGoogleDrive(e.target.result, file.name, $('input#name').val(), $('input#email').val());
}
};
function showSuccess(e) {
if (e === "OK") {
$('#forminner').hide();
$('#success').show();
} else {
showError(e);
}
}
function submitForm() {
var files = $('#files')[0].files;
if (files.length === 0) {
showError("Please select a file to upload");
return;
}
file = files[0];
if (file.size > 1024 * 1024 * 5) {
showError("The file size should be < 5 MB. Please <a href='http://www.labnol.org/internet/file-upload-google-forms/29170/' target='_blank'>upgrade to premium</a> for receiving larger files in Google Drive");
return;
}
showMessage("Uploading file..");
reader.readAsDataURL(file);
}
function showError(e) {
$('#progress').addClass('red-text').html(e);
}
function showMessage(e) {
$('#progress').removeClass('red-text').html(e);
}
</script>
</body>
</html>
As reccomended Im going to describe the process here.
So we are on the website: www.example.com , there is a form with text input field and file field. Lets say we put in an image and call it example. Now if we press submit, I want to upload the image to google drive without any oAuth (Thats why we need to use google app script here) and name it to what we typed in the textfield. When the upload is done, I want the url of the image of google drive to be returned back to the website, so the form can continue working with the information. I want to save the returned url in a var then, to later on save it in a database. Thats why I need the result back to my website.
So scheme looks like the following:
Enter informations to form on website -> Redirected to google app script: take informations of website form field and upload file to google drive and name it like text input entry -> taking url of google drive as final result -> redirecting final url result back to website -> saving url result in var and continuing doing stuff from function on website -> at the end saving the informations from var to a database -> finish
------------------------------------------------ EDIT: ------------------
Thanks to #Tanaike Im a lot closer to the goal of my challenge here, so in order to see where I got stuck, I'm replicating my issue now:
I took the form with the script from your example:
<form id="form">
<input name="file" id="uploadfile" type="file">
<input name="filename" id="filename" type="text">
<input id="submit" type="submit">
</form>
<script>
const form = document.getElementById('form');
form.addEventListener('submit', e => {
e.preventDefault();
const file = form.file.files[0];
const fr = new FileReader();
fr.readAsArrayBuffer(file);
fr.onload = f => {
const url = "https://script.google.com/macros/s/###/exec"; // <--- Please set the URL of Web Apps.
const qs = new URLSearchParams({filename: form.filename.value || file.name, mimeType: file.type});
fetch(`${url}?${qs}`, {method: "POST", body: JSON.stringify([...new Int8Array(f.target.result)])})
.then(res => res.json())
.then(e => console.log(e)) // <--- You can retrieve the returned value here.
.catch(err => console.log(err));
}
});
</script>
and for google script:
function doPost(e) {
// const folderId = "###"; // Folder ID which is used for putting the file, if you need.
const blob = Utilities.newBlob(JSON.parse(e.postData.contents), e.parameter.mimeType, e.parameter.filename);
const file = DriveApp.getFolderById(folderId || "root").createFile(blob);
const responseObj = {filename: file.getName(), fileId: file.getId(), fileUrl: file.getUrl()};
return ContentService.createTextOutput(JSON.stringify(responseObj)).setMimeType(ContentService.MimeType.JSON);
}
Now when i tried to upload something I had following error: CORS Policy not able to fetch. So I changed this part to the following and added mode no cors:
const qs = new URLSearchParams({filename: form.filename.value || file.name, mimeType: file.type});
fetch(`${url}?${qs}`, {method: "POST", mode: "no-cors", body: JSON.stringify([...new Int8Array(f.target.result)])})
This worked. Second try uploading the file caused in the following error:
it says: syntax error: unexpected end of input
So I changed this line and removed the brackets from res.json
JSON.stringify([...new Int8Array(f.target.result)])})
.then(res => res.json)
third try to upload the file actually worked with the following console result:
ƒ json() { [native code] }
But there is no file uploaded in google drive. Im missing something somewhere. Maybe we should create a folder and place the files in there.
Oh and another information: when i run doPost function in google app sript it says:
TypeError: Cannot read property 'postData' of undefined (line 13
EDIT2 -----------------------------------------
I added https://drive.google.com/uc?export=download&id=###fileId### to your code and everything works fine. The file is getting uploaded.
Lets say we upload the file test.mp3 and we call it testdata.
This is what we recieve:
{
"filename": "testdata",
"fileId": "###some id##",
"fileUrl": "https://drive.google.com/uc?export=download&id=###fileId###"
}
Now when I open the file url, the browser downloads file but its called: testdata, not testdata.mp3. The filetyp ending is missing.
Second task: If you click the link, I want to open the file in browser, when its mp3 file for example i want that you can play the sound in webview, like it is here: https://files.freemusicarchive.org/storage-freemusicarchive-org/music/Creative_Commons/Dead_Combo/CC_Affiliates_Mixtape_1/Dead_Combo_-_01_-_Povo_Que_Cas_Descalo.mp3
I hope you can guide me!
I believe your goal as follows.
Your web site is not related to Google account. It's independent.
Your web site has a form for uploading a file.
When users submit the form, you want to upload the file to your Google Drive without the authorization, and want to return the URL of the uploaded file on Google Drive.
About "Database", this is your database. You will put the retrieved URL of the file to "Database" at the client side.
In this case, I think that your goal can be achieved using the Web Apps created by Google Apps Script.
Usage:
Please do the following flow.
1. Create new project of Google Apps Script.
Sample script of Web Apps is a Google Apps Script. So please create a project of Google Apps Script.
If you want to directly create it, please access to https://script.new/. In this case, if you are not logged in Google, the log in screen is opened. So please log in to Google. By this, the script editor of Google Apps Script is opened.
2. Prepare script.
Please copy and paste the following script (Google Apps Script) to the script editor. This script is for the Web Apps.
Server side: Google Apps Script
Please set the folder ID that you want to put the file.
function doPost(e) {
const folderId = "root"; // Or Folder ID which is used for putting the file instead of "root", if you need.
const blob = Utilities.newBlob(JSON.parse(e.postData.contents), e.parameter.mimeType, e.parameter.filename);
const file = DriveApp.getFolderById(folderId).createFile(blob);
const responseObj = {filename: file.getName(), fileId: file.getId(), fileUrl: file.getUrl()};
return ContentService.createTextOutput(JSON.stringify(responseObj)).setMimeType(ContentService.MimeType.JSON);
}
3. Deploy Web Apps.
On the script editor, Open a dialog box by "Publish" -> "Deploy as web app".
Select "Me" for "Execute the app as:".
By this, the script is run as the owner.
Select "Anyone, even anonymous" for "Who has access to the app:".
Click "Deploy" button as new "Project version".
Automatically open a dialog box of "Authorization required".
Click "Review Permissions".
Select own account.
Click "Advanced" at "This app isn't verified".
Click "Go to ### project name ###(unsafe)"
Click "Allow" button.
Click "OK".
Copy the URL of Web Apps. It's like https://script.google.com/macros/s/###/exec.
When you modified the Google Apps Script, please redeploy as new version. By this, the modified script is reflected to Web Apps. Please be careful this.
4. Upload a file from client side to server side.
Client side: HTML & Javascript
Please set the URL of your Web Apps to the following script.
<form id="form">
<input name="file" id="uploadfile" type="file">
<input name="filename" id="filename" type="text">
<input id="submit" type="submit">
</form>
<script>
const form = document.getElementById('form');
form.addEventListener('submit', e => {
e.preventDefault();
const file = form.file.files[0];
const fr = new FileReader();
fr.readAsArrayBuffer(file);
fr.onload = f => {
const url = "https://script.google.com/macros/s/###/exec"; // <--- Please set the URL of Web Apps.
const qs = new URLSearchParams({filename: form.filename.value || file.name, mimeType: file.type});
fetch(`${url}?${qs}`, {method: "POST", body: JSON.stringify([...new Int8Array(f.target.result)])})
.then(res => res.json())
.then(e => console.log(e)) // <--- You can retrieve the returned value here.
.catch(err => console.log(err));
}
});
</script>
At the client side, when you selected a file from your local PC and push the button, the file is uploaded to your Google Drive by retrieving the data at the Web Apps (server side).
Result:
When above script is run, the following value is returned. From this, you can retrieve the URL of the file.
{
"filename": "### inputted filename ###",
"fileId": "###",
"fileUrl": "https://drive.google.com/file/d/###/view?usp=drivesdk"
}
Note:
When you modified the script of Web Apps, please redeploy the Web Apps as new version. By this, the latest script is reflected to Web Apps. Please be careful this.
In above script, the maximum file size is 50 MB. Because in the current stage, the maximum blob size is 50 MB at Google Apps Script.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
Lots of useful tips in https://stackoverflow.com/a/63391363/1585523 answer! Thank you for sharing. Instead of POSTing the file, we could also use
On the client: index.html
google.script.run.withSuccessHandler(fileName => {}).withFailureHandler(error => {}).saveInDrive(fileAsByteArray);
On the server: Code.gs
function saveInDrive(f) {
const blob = Utilities.newBlob(f, "image/jpeg", "some-name");
const file = DriveApp.getFolderById("root").createFile(blob);
return file.getName()
}
You can even exchange complicated types like JS objects with binary info as values as well in this approach.
I do not think that adding
mode: 'no-cors'
is right way because then this mode will block app from reading response(cors policies), which by the way comes not from response to initial request but from second redirected GET request
I am trying to attach some files (zero/single/multiple) and send them as attachments to an email using ANGULARJS and spring.
One thing noticed is when selecting the files from multiple directories only the recently selected file is shown and previous selected file is not shown. How can I show all the files selected by the user from different directories too and give the ability to delete the file (all files or one file) before submitting the form.
Demo:http://plnkr.co/edit/M3f0TxHNozRxFEnrqyiF?p=preview
html:
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
TO: <input type="text" name="to" id="to" ng-model="to" required ></input><br>
Subject : <input type="text" name="subject" id="subject" ng-model="subject"></input>
<br>Attachment: <input type="file" ng-file-model="files" multiple /> <br>
<p ng-repeat="file in files">
{{file.name}}
</p>
<textarea rows="20" maxlength=35000 name="message" ng-model="message" ></textarea>
<button type="button" ng-click="upload()">Send</button>
</body>
js:
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.files = [];
$scope.upload=function(){
alert($scope.files.length+" files selected ... Write your Code to send the mail");
};
});
app.directive('ngFileModel', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var model = $parse(attrs.ngFileModel);
var isMultiple = attrs.multiple;
var modelSetter = model.assign;
element.bind('change', function () {
var values = [];
angular.forEach(element[0].files, function (item) {
var value = {
// File Name
name: item.name,
//File Size
size: item.size,
//File URL to view
url: URL.createObjectURL(item),
// File Input Value
_file: item
};
values.push(value);
});
scope.$apply(function () {
if (isMultiple) {
modelSetter(scope, values);
} else {
modelSetter(scope, values[0]);
}
});
});
}
};
}]);
The default browser behavior is showing currently selected files, to cahnge that you've to customize that filed. And also, I saw your custom directive code, it doesn't allow to select multiple files from different directories.
So, what you can do is, create another scope variable & every time user selects file/files you push those files to this array. In this way you've have set of all selected files from same/different directories and then you can have delete functionality over each file which's ultimately going to be updated.
Updated html view part:
Attachment: <input type="file" ng-file-model="files" multiple /><br>
<p ng-repeat="file in filesToUpload track by $index">
{{file.name}} <span class="delete-file" ng-click="deleteFile($index)">X</span>
</p>
And for this new array update directive scope.$apply part as:
scope.$apply(function () {
if (isMultiple) {
modelSetter(scope, values);
} else {
modelSetter(scope, values[0]);
}
if(values){
scope.filesToUpload = scope.filesToUpload.concat(values);
}
});
In controller have deleteFile function as:
$scope.deleteFile = function(index){
$scope.filesToUpload.splice(index, 1);
};
Working Demo Example
Now user'll be able to delete files anytime. But the input field will still show the last selected file/files and after deleting particular file also it'll not change its status so for that you can just hide field by opacity: 0; css & then create customized Upload button & from that trigger click on actual hidden file input element.
Update: Check this update of same code with custom upload button:
Plunker Example
I'm trying to use one file input element to upload multiple files to Drive using html form. This seems to work only for one file, although the file picker allows selecting multiple files. Back in the script log viewer, I only see one file captured of the two I uploaded. Is this unsupported, or am I going the wrong way about it?
Code.gs:
function logForm(form) {
Logger.log(JSON.stringify(form));
return true;
}
index.html:
<html>
<form id="uploadTest" enctype="multipart/form-data">
<input type="file" multiple="multiple" name="fileUpload">
<input type="button" id="upload" value="upload"
onclick="google.script.run.logForm(document.getElementById('uploadTest'));">
</form>
</html>
Log view:
{"fileUpload":{"contents":"GIF87a\u0001\u0000\u0001\u0000�
\u0000\u0000��̖��,\u0000\u0000\u0000\u0000\u0001\u0000
\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;",
"type":"image/gif","name":"1x1.gif","length":35}}
The multiple file select in the dialog when you click on the browse button of the file field happens only for the new browsers supporting HTML5. It wont allow multiple select for old browsers. For older browsers the only good solutions are flash or javascript plugins. Here is a good resource for jquery uploaders ( some support multiple files ): http://creativefan.com/10-ajax-jquery-file-uploaders/. Hence my suggestion is use some plugin so that its supported on old as well as the new browsers.
I'm using possibility to send array of files. Just add [] to name atrribute:
<form action="/" enctype="multipart/form-data" method="post">
<input type="file" name="files[]" />
<input type="file" name="files[]" />
// etc.
<input type="submit">
</form>
You will have array of arrays in $_FILES
Array
(
[files] => Array
(
[name] => Array
(
[0] => 1.png
[1] => 2.png
)
[type] => Array
(
[0] => image/png
[1] => image/png
)
[tmp_name] => Array
(
[0] => /tmp/phpDQOZWD
[1] => /tmp/phpCELeSw
)
[error] => Array
(
[0] => 0
[1] => 0
)
[size] => Array
(
[0] => 32209
[1] => 64109
)
)
)
Of course, you you'll have to upload them one by one. Not convenient to a large number of files, but works in all browsers. For example,using jQuery you can add one more input each time last files[] input was changed.
function addOneMoreInput() {
$('input[type=file]').last().change(function() {
$(this).after('<input type="file" name="files[]" />');
$(this).off('change');
addOneMoreInput();
});
}
addOneMoreInput();
How about this sample script?
In this sample, the following flow is run.
Select files at browser.
Upload the files every file.
Save each file in Google Drive.
In this sample script, the file IDs of created files are returned to the console.
When you use this, please copy and paste the Google Apps Script and HTML to the script editor, and run the HTML using the dialog, sidebar and Web Apps.
Code.gs: Google Apps Script
function saveFile(obj) {
var blob = Utilities.newBlob(Utilities.base64Decode(obj.data), obj.mimeType, obj.fileName);
return DriveApp.createFile(blob).getId();
}
index.html: HTML and Javascript
<input name="file" id="files" type="file" multiple>
<input type='button' value='Upload' onclick='getFiles()'>
<script>
function getFiles() {
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: f.files[i].name, mimeType: data[0].match(/:(\w.+);/)[1], data: data[1]};
google.script.run.withSuccessHandler((id) => {
console.log(id);
}).saveFile(obj);
}
fr.readAsDataURL(file);
});
}
</script>
Note:
index.html can be run as Web Apps and the side bar and dialog of Google Docs.
In this sample script, the input tag uses the multiple attribute.
Can I use a PUT method in an HTML form to send data from the form to a server?
According to the HTML standard, you can not. The only valid values for the method attribute are get and post, corresponding to the GET and POST HTTP methods. <form method="put"> is invalid HTML and will be treated like <form>, i.e. send a GET request.
Instead, many frameworks simply use a POST parameter to tunnel the HTTP method:
<form method="post" ...>
<input type="hidden" name="_method" value="put" />
...
Of course, this requires server-side unwrapping.
XHTML 1.x forms only support GET and POST. GET and POST are the only allowed values for
the "method" attribute.
Can I use "Put" method in html form to send data from HTML Form to server?
Yes you can, but keep in mind that it will not result in a PUT but a GET request. If you use an invalid value for the method attribute of the <form> tag, the browser will use the default value get.
HTML forms (up to HTML version 4 (, 5 Draft) and XHTML 1) only support GET and POST as HTTP request methods. A workaround for this is to tunnel other methods through POST by using a hidden form field which is read by the server and the request dispatched accordingly. XHTML 2.0 once planned to support GET, POST, PUT and DELETE for forms, but it's going into XHTML5 of HTML5, which does not plan to support PUT. [update to]
You can alternatively offer a form, but instead of submitting it, create and fire a XMLHttpRequest using the PUT method with JavaScript.
_method hidden field workaround
The following simple technique is used by a few web frameworks:
add a hidden _method parameter to any form that is not GET or POST:
<input type="hidden" name="_method" value="PUT">
This can be done automatically in frameworks through the HTML creation helper method.
fix the actual form method to POST (<form method="post")
processes _method on the server and do exactly as if that method had been sent instead of the actual POST
You can achieve this in:
Rails: form_tag
Laravel: #method("PATCH")
Rationale / history of why it is not possible in pure HTML: https://softwareengineering.stackexchange.com/questions/114156/why-there-are-no-put-and-delete-methods-in-html-forms
for people using laravel
<form method="post" ...>
#csrf
#method('put')
...
</form>
Unfortunately, modern browsers do not provide native support for HTTP PUT requests. To work around this limitation, ensure your HTML form’s method attribute is “post”, then add a method override parameter to your HTML form like this:
<input type="hidden" name="_METHOD" value="PUT"/>
To test your requests you can use "Postman" a google chrome extension
To set methods PUT and DELETE I perform as following:
<form
method="PUT"
action="domain/route/param?query=value"
>
<input type="hidden" name="delete_id" value="1" />
<input type="hidden" name="put_id" value="1" />
<input type="text" name="put_name" value="content_or_not" />
<div>
<button name="update_data">Save changes</button>
<button name="remove_data">Remove</button>
</div>
</form>
<hr>
<form
method="DELETE"
action="domain/route/param?query=value"
>
<input type="hidden" name="delete_id" value="1" />
<input type="text" name="delete_name" value="content_or_not" />
<button name="delete_data">Remove item</button>
</form>
Then JS acts to perform the desired methods:
<script>
var putMethod = ( event ) => {
// Prevent redirection of Form Click
event.preventDefault();
var target = event.target;
while ( target.tagName != "FORM" ) {
target = target.parentElement;
} // While the target is not te FORM tag, it looks for the parent element
// The action attribute provides the request URL
var url = target.getAttribute( "action" );
// Collect Form Data by prefix "put_" on name attribute
var bodyForm = target.querySelectorAll( "[name^=put_]");
var body = {};
bodyForm.forEach( element => {
// I used split to separate prefix from worth name attribute
var nameArray = element.getAttribute( "name" ).split( "_" );
var name = nameArray[ nameArray.length - 1 ];
if ( element.tagName != "TEXTAREA" ) {
var value = element.getAttribute( "value" );
} else {
// if element is textarea, value attribute may return null or undefined
var value = element.innerHTML;
}
// all elements with name="put_*" has value registered in body object
body[ name ] = value;
} );
var xhr = new XMLHttpRequest();
xhr.open( "PUT", url );
xhr.setRequestHeader( "Content-Type", "application/json" );
xhr.onload = () => {
if ( xhr.status === 200 ) {
// reload() uses cache, reload( true ) force no-cache. I reload the page to make "redirects normal effect" of HTML form when submit. You can manipulate DOM instead.
location.reload( true );
} else {
console.log( xhr.status, xhr.responseText );
}
}
xhr.send( body );
}
var deleteMethod = ( event ) => {
event.preventDefault();
var confirm = window.confirm( "Certeza em deletar este conteúdo?" );
if ( confirm ) {
var target = event.target;
while ( target.tagName != "FORM" ) {
target = target.parentElement;
}
var url = target.getAttribute( "action" );
var xhr = new XMLHttpRequest();
xhr.open( "DELETE", url );
xhr.setRequestHeader( "Content-Type", "application/json" );
xhr.onload = () => {
if ( xhr.status === 200 ) {
location.reload( true );
console.log( xhr.responseText );
} else {
console.log( xhr.status, xhr.responseText );
}
}
xhr.send();
}
}
</script>
With these functions defined, I add a event listener to the buttons which make the form method request:
<script>
document.querySelectorAll( "[name=update_data], [name=delete_data]" ).forEach( element => {
var button = element;
var form = element;
while ( form.tagName != "FORM" ) {
form = form.parentElement;
}
var method = form.getAttribute( "method" );
if ( method == "PUT" ) {
button.addEventListener( "click", putMethod );
}
if ( method == "DELETE" ) {
button.addEventListener( "click", deleteMethod );
}
} );
</script>
And for the remove button on the PUT form:
<script>
document.querySelectorAll( "[name=remove_data]" ).forEach( element => {
var button = element;
button.addEventListener( "click", deleteMethod );
</script>
_ - - - - - - - - - - -
This article https://blog.garstasio.com/you-dont-need-jquery/ajax/ helps me a lot!
Beyond this, you can set postMethod function and getMethod to handle POST and GET submit methods as you like instead browser default behavior. You can do whatever you want instead use location.reload(), like show message of successful changes or successful deletion.
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
JSFiddle: https://jsfiddle.net/enriquerene/d6jvw52t/53/
If you are using nodejs, you can install the package method-override that lets you do this using a middleware.
Link to documentation: http://expressjs.com/en/resources/middleware/method-override.html
After installing this, all I had to do was the following:
var methodOverride = require('method-override')
app.use(methodOverride('_method'))
I wrote an npm package called 'html-form-enhancer'. By dropping it into your HTML source, it takes over submission of forms with methods aside from GET and POST, and also adds application/json serialization.
<script type=module" src="html-form-enhancer.js"></script>
<form method="PUT">
...
</form>
In simple words - No.
I have tried to fire a put request in the HTML form, but it sends the POST request to the server. To add the PUT request -
We can do it by listening to the submit action in the script, then fire the put request to a particular endpoint.
Screenshot from the http-server env. test
I'm new to MailChimp and need some help.
With their basic newsletter signup form... you simply embed some prepackaged HTML into your page. However the problem with this is that clicking on submit redirects to a MailChimp page. (I don't want to redirect to MailChimp, I want the user to stay on own website after hitting submit.)
They provide an API and plenty of documentation but just about zero useful examples. The API is supposed to allow me to do a full integration with my site or application. It seems that when I read something in their docs that applies to me, I click the link to get more information and I end up going around in circles. They tell you how to do it but they fail to "show" you how to it.
I can get an API Key, they have tons of documentation, and a whole bunch of wrappers & plugins... PHP, Drupal, Wordpress, etc...
The confusion here regarding their pre-packaged solutions is that I just have a regular static HTML page, it's not Wordpress, PHP, or Drupal... so I just don't know where to start ... I don't even know if I'm supposed to use POST or GET.
I'm not a newbie to API's... I do very well with getting the Google Maps API to do whatever I want. However, Google provides real-world working examples in addition to their detailed documentation which is how I learned it. I just want to see it in action before I can grasp the finer points of the API.
Without any solid examples or tutorials in their online documentation, I'm asking how to create the most basic HTML signup form using their API.
EDITED:
Since posting this answer MailChimp has released version 2 & 3 of their API. Version 3 will be the only supported version starting in 2017. As soon as I have a chance to test it, I will update this answer for API version 3.
MailChimp API v3.0
As per notification at the top of this page, all prior versions of the API will not be supported after 2016.
My solution uses PHP in the background for handling the API, and jQuery to facilitate the Ajax.
1) Download a PHP wrapper that supports API v3.0. As of this writing, there is nothing official listed in the latest MailChimp docs that supports v3.0, but several are listed on GitHub, so I selected this one.
2) Create the following PHP file, store-address.php, using your own API key and list ID, and then place it in the same directory as the wrapper from step one. Remember to follow the documentation for your wrapper, but they all seem fairly similar to this.
<?php // for MailChimp API v3.0
include('MailChimp.php'); // path to API wrapper downloaded from GitHub
use \DrewM\MailChimp\MailChimp;
function storeAddress() {
$key = "xxxxxxxxxxxxxxx-us1";
$list_id = "xxxxxx";
$merge_vars = array(
'FNAME' => $_POST['fname'],
'LNAME' => $_POST['lname']
);
$mc = new MailChimp($key);
// add the email to your list
$result = $mc->post('/lists/'.$list_id.'/members', array(
'email_address' => $_POST['email'],
'merge_fields' => $merge_vars,
'status' => 'pending' // double opt-in
// 'status' => 'subscribed' // single opt-in
)
);
return json_encode($result);
}
// If being called via ajax, run the function, else fail
if ($_POST['ajax']) {
echo storeAddress(); // send the response back through Ajax
} else {
echo 'Method not allowed - please ensure JavaScript is enabled in this browser';
}
3) Create your HTML/CSS/JavaScript(jQuery) form (It is not required to be on a PHP page, and the visitor will never see that PHP is being used in the background.)
The response is in JSON so you'll have to handle it correctly.
Here is what my index.html file looks like:
<form id="signup" action="index.html" method="get">
First Name: <input type="text" name="fname" id="fname" />
Last Name: <input type="text" name="lname" id="lname" />
email Address (required): <input type="email" name="email" id="email" />
<input type="submit" id="SendButton" name="submit" value="Submit" />
</form>
<div id="message"></div>
<script src="jquery.min.js"></script>
<script>
$(document).ready(function() {
$('#signup').submit(function() {
$("#message").html("Adding your email address...");
$.ajax({
url: 'inc/store-address.php', // proper url to your "store-address.php" file
type: 'POST', // <- IMPORTANT
data: $('#signup').serialize() + '&ajax=true',
success: function(msg) {
var message = $.parseJSON(msg),
result = '';
if (message.status === 'pending') { // success
result = 'Success! Please click the confirmation link that will be emailed to you shortly.';
} else { // error
result = 'Error: ' + message.detail;
}
$('#message').html(result); // display the message
}
});
return false;
});
});
</script>
MailChimp API version 1:
(original answer)
After fumbling around for a while, I found a site using the PHP example with jQuery. From that I was able to create a simple HTML page with jQuery containing the basic sign-up form. The PHP files are "hidden" in the background where the user never sees them yet the jQuery can still access & use.
1) Download the PHP 5 jQuery example here... (EDIT: links are dead. However, the only important part is the official API wrapper for PHP which is available HERE.)
http://apidocs.mailchimp.com/downloads/mcapi-simple-subscribe-jquery.zip
If you only have PHP 4, simply download version 1.2 of the MCAPI and replace the corresponding MCAPI.class.php file above.
http://apidocs.mailchimp.com/downloads/mailchimp-api-class-1-2.zip
2) Follow the directions in the Readme file by adding your API key and List ID to the store-address.php file at the proper locations.
3) You may also want to gather your users' name and/or other information. You have to add an array to the store-address.php file using the corresponding Merge Variables.
Here is what my store-address.php file looks like where I also gather the first name, last name, and email type:
<?php
function storeAddress() {
require_once('MCAPI.class.php'); // same directory as store-address.php
// grab an API Key from http://admin.mailchimp.com/account/api/
$api = new MCAPI('123456789-us2');
$merge_vars = Array(
'EMAIL' => $_GET['email'],
'FNAME' => $_GET['fname'],
'LNAME' => $_GET['lname']
);
// grab your List's Unique Id by going to http://admin.mailchimp.com/lists/
// Click the "settings" link for the list - the Unique Id is at the bottom of that page.
$list_id = "123456a";
if ($api->listSubscribe($list_id, $_GET['email'], $merge_vars , $_GET['emailtype'])) {
// It worked!
return 'Success! Check your inbox or spam folder for a message containing a confirmation link.';
} else {
// An error ocurred, return error message
return '<b>Error:</b> ' . $api->errorMessage;
}
}
// If being called via ajax, autorun the function
if($_GET['ajax']) {
echo storeAddress();
}
4) Create your HTML/CSS/jQuery form. It is not required to be on a PHP page.
Here is what my index.html file looks like:
<form id="signup" action="index.html" method="get">
First Name: <input type="text" name="fname" id="fname" />
Last Name: <input type="text" name="lname" id="lname" />
email Address (required): <input type="email" name="email" id="email" />
HTML: <input type="radio" name="emailtype" value="html" checked="checked" />
Text: <input type="radio" name="emailtype" value="text" />
<input type="submit" id="SendButton" name="submit" value="Submit" />
</form>
<div id="message"></div>
<script src="jquery.min.js"></script>
<script>
$(document).ready(function() {
$('#signup').submit(function() {
$("#message").html("Adding your email address...");
$.ajax({
url: 'inc/store-address.php', // proper url to your "store-address.php" file
data: $('#signup').serialize() + '&ajax=true',
success: function(msg) {
$('#message').html(msg);
}
});
return false;
});
});
</script>
Required pieces...
index.html constructed as above or similar. With jQuery, the appearance and options are endless.
store-address.php file downloaded as part of PHP examples on Mailchimp site and modified with your API KEY and LIST ID. You need to add your other optional fields to the array.
MCAPI.class.php file downloaded from Mailchimp site (version 1.3 for PHP 5 or version 1.2 for PHP 4). Place it in the same directory as your store-address.php or you must update the url path within store-address.php so it can find it.
Here is an example using version 2.0 of Mailchimp API together with mailchimp-api (a minimal php abstraction class for dealing with the Mailchimp API).
<?php
include('MailChimp.php');
$MailChimp = new MailChimp('API_KEY');
$result = $MailChimp->call('lists/subscribe', array(
'id' => 'LIST_ID',
'email' => array( 'email' => $_POST['email'] ),
'merge_vars' => array(
'MERGE2' => $_POST['name'] // MERGE name from list settings
// there MERGE fields must be set if required in list settings
),
'double_optin' => false,
'update_existing' => true,
'replace_interests' => false
));
if( $result === false ) {
// response wasn't even json
}
else if( isset($result->status) && $result->status == 'error' ) {
// Error info: $result->status, $result->code, $result->name, $result->error
}
?>
Read more about what you can send with the API call at the MailChimp API Documentation.
Here's another example of using version 2.0 of the Mailchimp API using the Official PHP Wrapper.
The difference between my example and others posted here is that I'm using the subscribe method of the Mailchimp_Lists class, accessible through instantiation of the Mailchimp class (->lists), rather than the generic call method.
$api_key = "MAILCHIMP_API_KEY";
$list_id = "MAILCHIMP_LIST_ID";
require('Mailchimp.php');
$Mailchimp = new Mailchimp($api_key);
$subscriber = $Mailchimp->lists->subscribe($list_id, array('email' => $_POST['email']));
if ( ! empty($subscriber['leid'])) {
// Success
}