Skip to content

Instantly share code, notes, and snippets.

@pritamsharma45
Last active August 29, 2025 14:49
Show Gist options
  • Select an option

  • Save pritamsharma45/3aa38433ab043dad3c31f8be5b9c767b to your computer and use it in GitHub Desktop.

Select an option

Save pritamsharma45/3aa38433ab043dad3c31f8be5b9c767b to your computer and use it in GitHub Desktop.
Data entry with multiple files upload
const DATA_ENTRY_SHEET_NAME = "Sheet1";
const TIME_STAMP_COLUMN_NAME = "Timestamp";
const FOLDER_ID = "";
// == IMAGE UPLOAD CONFIGURATION ==
// Add or edit image column names here - this is the only place you need to modify
const IMAGE_COLUMNS = [
{ name: "before", label: "Before Image" },
{ name: "on process", label: "On Process Image" },
{ name: "after", label: "After Image" },
];
// == CONFIGURATION END ==
function doPost(e) {
try {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(DATA_ENTRY_SHEET_NAME);
if (!sheet) {
throw new Error(`Sheet '${DATA_ENTRY_SHEET_NAME}' not found`);
}
const formData = e.postData.contents ? JSON.parse(e.postData.contents) : {};
// Handle multiple image uploads if present
let imageInfo = {};
if (formData.imageData) {
// Process each image type based on IMAGE_COLUMNS configuration
IMAGE_COLUMNS.forEach((column) => {
if (formData.imageData[column.name]) {
imageInfo[column.name] = saveFile(formData.imageData[column.name]);
}
});
delete formData.imageData; // Remove image data from form data
}
// Prepare data for sheet
const rowData = {
...formData,
[TIME_STAMP_COLUMN_NAME]: new Date().toISOString(),
};
// Add image information to row data using HYPERLINK formula
IMAGE_COLUMNS.forEach((column) => {
if (imageInfo[column.name]) {
// Use HYPERLINK formula for clickable links in Google Sheets
// Use the actual filename as the display text instead of the label
rowData[column.name] = `=HYPERLINK("${imageInfo[column.name].url}","${imageInfo[column.name].name}")`;
}
});
appendToGoogleSheet(rowData, sheet);
return ContentService.createTextOutput(
JSON.stringify({
status: "success",
message: "Data submitted successfully",
})
).setMimeType(ContentService.MimeType.JSON);
} catch (error) {
console.error(error);
return ContentService.createTextOutput(
JSON.stringify({
status: "error",
message: error.toString(),
})
).setMimeType(ContentService.MimeType.JSON);
}
}
/**
* Saves a file to Google Drive
*/
function saveFile(fileData) {
try {
const blob = Utilities.newBlob(Utilities.base64Decode(fileData.data), fileData.mimeType, fileData.fileName);
const folder = DriveApp.getFolderById(FOLDER_ID);
const file = folder.createFile(blob);
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
return {
url: `https://drive.google.com/uc?export=view&id=${file.getId()}`,
name: fileData.fileName,
};
} catch (error) {
console.error("File upload error:", error);
throw new Error("Failed to upload file: " + error.toString());
}
}
/**
* Appends data to the Google Sheet
*/
function appendToGoogleSheet(data, sheet) {
let headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
// If sheet is empty, create headers
if (headers.length === 0 || headers[0] === "") {
const newHeaders = Object.keys(data);
sheet.getRange(1, 1, 1, newHeaders.length).setValues([newHeaders]);
headers = newHeaders;
} else {
// Check if we need to add new columns for the new image fields
const existingHeaders = new Set(headers);
const newColumns = [];
// Add new image columns if they don't exist
IMAGE_COLUMNS.forEach((column) => {
if (!existingHeaders.has(column.name)) {
newColumns.push(column.name);
}
});
// Add new columns to the sheet if needed
if (newColumns.length > 0) {
const lastCol = sheet.getLastColumn();
sheet.getRange(1, lastCol + 1, 1, newColumns.length).setValues([newColumns]);
headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
}
}
// Map data to header columns
const rowData = headers.map((header) => data[header] || "");
sheet.appendRow(rowData);
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css" />
<title>Data Entry Form with File Upload</title>
</head>
<body>
<section class="hero is-primary is-bold">
<div class="hero-body">
<div class="container">
<h1 class="title">Data Entry Form</h1>
</div>
</div>
</section>
<form id="form" class="container m-4 pl-4">
<div class="field">
<label class="label">Client Name</label>
<div class="control">
<input class="input" type="text" placeholder="Your Client Name" name="Client Name" />
</div>
</div>
<div class="field">
<label class="label">Email</label>
<div class="control">
<input class="input" type="email" placeholder="Your Email" name="Email" />
</div>
</div>
<div class="field">
<label class="label">Password</label>
<div class="control">
<input class="input" type="password" placeholder="Your Password" name="Password" />
</div>
</div>
<div class="field">
<label class="label">Date of Birth</label>
<div class="control">
<input class="input" type="date" placeholder="Your Date of Birth" name="DOB" />
</div>
</div>
<div class="field">
<label class="label">Gender</label>
<div class="control">
<label class="radio"> <input type="radio" name="Gender" value="male" /> Male </label>
<label class="radio"> <input type="radio" name="Gender" value="female" /> Female </label>
</div>
</div>
<div class="field">
<label class="label">Image Uploads</label>
<div class="control" id="imageUploadsContainer">
<!-- Image upload fields will be dynamically created here -->
</div>
</div>
<div class="field">
<label class="label">Agree to Terms</label>
<div class="control">
<label class="checkbox"> <input type="checkbox" name="Agree To Terms" value="yes" /> I agree to the terms and conditions </label>
</div>
</div>
<div class="field">
<label class="label">Additional Information</label>
<div class="control">
<textarea class="textarea" placeholder="Any additional information" name="Notes"></textarea>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-primary" type="submit" id="submit-button">Sign Up</button>
</div>
<div class="control">
<button class="button is-danger" type="button" onclick="document.getElementById('form').reset(); setupImageInputListeners();">Cancel</button>
</div>
</div>
</form>
<div id="message" style="display: none; margin: 20px; padding: 10px; border-radius: 4px; font-weight: bold"></div>
<script>
// == IMAGE UPLOAD CONFIGURATION ==
// Add or edit image upload fields here - this should match the IMAGE_COLUMNS in code.js
const IMAGE_UPLOAD_CONFIG = [
{ name: "before", label: "Before Image", placeholder: "Choose before image…" },
{ name: "on process", label: "On Process Image", placeholder: "Choose on process image…" },
{ name: "after", label: "After Image", placeholder: "Choose after image…" },
];
// == CONFIGURATION END ==
const form = document.getElementById("form");
const submitButton = document.getElementById("submit-button");
const messageDiv = document.getElementById("message");
// Dynamically create image upload fields based on configuration
function createImageUploadFields() {
const imageFieldsContainer = document.getElementById("imageUploadsContainer");
// Clear existing content
imageFieldsContainer.innerHTML = "";
// Create new image upload fields
IMAGE_UPLOAD_CONFIG.forEach((config) => {
const fieldDiv = document.createElement("div");
fieldDiv.className = "field";
fieldDiv.innerHTML = `
<label class="label">${config.label}</label>
<div class="control">
<div class="file has-name is-fullwidth">
<label class="file-label">
<input class="file-input" type="file" name="${config.name}" id="${config.name}Input" accept="image/*" />
<span class="file-cta">
<span class="file-icon">
<i class="fas fa-upload"></i>
</span>
<span class="file-label"> ${config.placeholder} </span>
</span>
<span class="file-name" id="${config.name}Display"> No file selected </span>
</label>
</div>
</div>
`;
imageFieldsContainer.appendChild(fieldDiv);
});
}
// Initialize image upload fields
createImageUploadFields();
// Get references to dynamically created elements
function getImageInputs() {
const inputs = {};
IMAGE_UPLOAD_CONFIG.forEach((config) => {
inputs[config.name] = {
input: document.getElementById(`${config.name}Input`),
display: document.getElementById(`${config.name}Display`),
};
});
return inputs;
}
// Add event listeners to image inputs
function setupImageInputListeners() {
const imageInputs = getImageInputs();
IMAGE_UPLOAD_CONFIG.forEach((config) => {
const { input, display } = imageInputs[config.name];
input.addEventListener("change", function () {
if (this.files && this.files.length > 0) {
display.textContent = this.files[0].name;
} else {
display.textContent = "No file selected";
}
});
});
}
// Setup listeners after fields are created
setupImageInputListeners();
// Function to handle file upload
async function uploadFile(file) {
return new Promise((resolve, reject) => {
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],
};
resolve(obj);
};
fr.onerror = reject;
fr.readAsDataURL(file);
});
}
form.addEventListener("submit", async function (e) {
e.preventDefault();
messageDiv.textContent = "Submitting...";
messageDiv.style.display = "block";
messageDiv.style.backgroundColor = "beige";
messageDiv.style.color = "black";
submitButton.disabled = true;
submitButton.classList.add("is-loading");
try {
const formData = new FormData(this);
const formDataObj = {};
// Convert FormData to object
for (let [key, value] of formData.entries()) {
formDataObj[key] = value;
}
// Handle image uploads if files are selected
const imageData = {};
const imageInputs = getImageInputs();
for (const config of IMAGE_UPLOAD_CONFIG) {
const input = imageInputs[config.name].input;
if (input.files && input.files.length > 0) {
const imageObj = await uploadFile(input.files[0]);
imageData[config.name] = imageObj;
}
}
if (Object.keys(imageData).length > 0) {
formDataObj.imageData = imageData;
}
const scriptURL = "https://script.google.com/macros/s/AKfycbw0l6Dq_FSPD2rPp8QFOYT8QQw3NoD4LoZg7WBzy9MwsC6aSklQqMQD6dOIHlKt9IZm4Q/exec";
const response = await fetch(scriptURL, {
redirect: "follow",
method: "POST",
body: JSON.stringify(formDataObj),
headers: {
"Content-Type": "text/plain;charset=utf-8",
},
});
const data = await response.json();
if (data.status === "success") {
messageDiv.textContent = data.message || "Data submitted successfully!";
messageDiv.style.backgroundColor = "#48c78e";
messageDiv.style.color = "white";
form.reset();
setupImageInputListeners();
} else {
throw new Error(data.message || "Submission failed");
}
} catch (error) {
console.error("Error:", error);
messageDiv.textContent = "Error: " + error.message;
messageDiv.style.backgroundColor = "#f14668";
messageDiv.style.color = "white";
} finally {
submitButton.disabled = false;
submitButton.classList.remove("is-loading");
setTimeout(() => {
messageDiv.textContent = "";
messageDiv.style.display = "none";
}, 4000);
}
});
const cancelButton = form.querySelector("button.is-danger");
cancelButton.addEventListener("click", function () {
form.reset();
setupImageInputListeners();
messageDiv.style.display = "none";
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment