I am creating a form with Remix, and I want upload images to Cloudinary.
I have here a Vanilla JS form example showing how to use the Web Fetch API to send images to Cloudinary: https://codepen.io/team/Cloudinary/pen/QgpyOK <— I would like to "transform" or "adapt" this code to work with Remix. Does anybody could help me, please?
I will paste the code I am working with so far.
On this code I've added already the HTML code from the CodePen link above. I just need to know how to exactly add the JS code, after the handleChange() I believe.
On the form element #fileElem we see there the onChange={handleFiles(this.files)} and this is what triggers the upload. My problem now, and because I am also already creating a post, is that I don't know exactly how/where to add all the handleFiles() code from the codepen link above.
On the code below you'll find comment blocks where I explain what I was did.
// import { isErrorResponse } from "#remix-run/react/data";
import { redirect, Form, useActionData, useTransition } from 'remix';
import { createPost } from '~/post';
export let action = async ({ request }) => {
let formData = await request.formData();
let title = formData.get('title');
let slug = formData.get('slug');
let markdown = formData.get('markdown');
let errors = {};
if (!title) errors.title = true;
if (!slug) errors.slug = true;
if (!markdown) errors.markdown = true;
if (Object.keys(errors).length) {
return errors;
}
await createPost({ title, slug, image, markdown });
return redirect('/admin');
};
export default function NewPost() {
let errors = useActionData();
let transition = useTransition();
let slug = '';
const handleChange = (e, document) => {
let text = e.target.value;
// using regex and replace, let's convert spaces to dashes
slug = text.replace(/\s/g, '-');
// lets set the value of the slug text box to be our new slug in lowercase
document.getElementById('slugInput').value = slug.toLowerCase();
};
// ATTEMPT - added bellow the JS code from codepen !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// ATTEMPT - added bellow the JS code from codepen !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// ATTEMPT - added bellow the JS code from codepen !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
const cloudName = 'demo';
const unsignedUploadPreset = 'doc_codepen_example';
var fileSelect = document.getElementById('fileSelect'),
fileElem = document.getElementById('fileElem'),
urlSelect = document.getElementById('urlSelect');
fileSelect.addEventListener(
'click',
function (e) {
if (fileElem) {
fileElem.click();
}
e.preventDefault(); // prevent navigation to "#"
},
false
);
urlSelect.addEventListener(
'click',
function (e) {
uploadFile('https://res.cloudinary.com/demo/image/upload/sample.jpg');
e.preventDefault(); // prevent navigation to "#"
},
false
);
// ************************ Drag and drop ***************** //
function dragenter(e) {
e.stopPropagation();
e.preventDefault();
}
function dragover(e) {
e.stopPropagation();
e.preventDefault();
}
dropbox = document.getElementById('dropbox');
dropbox.addEventListener('dragenter', dragenter, false);
dropbox.addEventListener('dragover', dragover, false);
dropbox.addEventListener('drop', drop, false);
function drop(e) {
e.stopPropagation();
e.preventDefault();
var dt = e.dataTransfer;
var files = dt.files;
handleFiles(files);
}
// *********** Upload file to Cloudinary ******************** //
function uploadFile(file) {
var url = `https://api.cloudinary.com/v1_1/${cloudName}/upload`;
var xhr = new XMLHttpRequest();
var fd = new FormData();
xhr.open('POST', url, true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
// Reset the upload progress bar
document.getElementById('progress').style.width = 0;
// Update progress (can be used to show progress indicator)
xhr.upload.addEventListener('progress', function (e) {
var progress = Math.round((e.loaded * 100.0) / e.total);
document.getElementById('progress').style.width = progress + '%';
console.log(`fileuploadprogress data.loaded: ${e.loaded}, data.total: ${e.total}`);
});
xhr.onreadystatechange = function (e) {
if (xhr.readyState == 4 && xhr.status == 200) {
// File uploaded successfully
var response = JSON.parse(xhr.responseText);
// https://res.cloudinary.com/cloudName/image/upload/v1483481128/public_id.jpg
var url = response.secure_url;
// Create a thumbnail of the uploaded image, with 150px width
var tokens = url.split('/');
tokens.splice(-2, 0, 'w_150,c_scale');
var img = new Image(); // HTML5 Constructor
img.src = tokens.join('/');
img.alt = response.public_id;
document.getElementById('gallery').appendChild(img);
}
};
fd.append('upload_preset', unsignedUploadPreset);
fd.append('tags', 'browser_upload'); // Optional - add tag for image admin in Cloudinary
fd.append('file', file);
xhr.send(fd);
}
// *********** Handle selected files ******************** //
var handleFiles = function (files) {
for (var i = 0; i < files.length; i++) {
uploadFile(files[i]); // call the function to upload the file
}
};
// ATTEMPT - end of JS code from codepen !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// ATTEMPT - end of JS code from codepen !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// ATTEMPT - end of JS code from codepen !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// ATTEMPT - end of JS code from codepen !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// ATTEMPT - end of JS code from codepen !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
return (
<Form method='post'>
<p>
<label htmlFor=''>
Post Title: {errors?.title && <em>Title is required</em>}{' '}
<input onChange={handleChange} type='text' name='title' />
</label>
</p>
<p>
<label htmlFor=''>
{' '}
Post Slug: {errors?.slug && <em>Slug is required</em>}
<input placeholder={slug} id='slugInput' type='text' name='slug' />
</label>
</p>
<p>
<label htmlFor='markdown'>Markdown:</label>{' '}
{errors?.markdown && <em>Markdown is required</em>}
<br />
<textarea name='markdown' id='' rows={20} cols={30} />
</p>
<p>
<button type='submit'>
{transition.submission ? 'Creating...' : 'Create Post'}
</button>
</p>
{/* // ATTEMPT - added HTML form code from codepen !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */}
{/* // ATTEMPT - added HTML form code from codepen !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */}
{/* // ATTEMPT - added HTML form code from codepen !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */}
<div id='dropbox'>
<h1>Client-Side Upload to Cloudinary with JavaScript</h1> Learn more in
this blog post -{' '}
<a href='https://cloudinary.com/blog/direct_upload_made_easy_from_browser_or_mobile_app_to_the_cloud'>
Direct upload made easy from browser or mobile app to the cloud
</a>
<div className='form_line'>
<h4>
Upload multiple files by clicking the link below or by dragging and
dropping images onto the dashed region
</h4>
<div className='form_controls'>
<div className='upload_button_holder'>
<input
type='file'
id='fileElem'
multiple
accept='image/*'
style={{ display: 'none' }}
onChange={handleFiles(this.files)}
/>
<a href='#' id='fileSelect'>
Select some files
</a>
<a href='#' id='urlSelect'>
URL Upload
</a>
</div>
</div>
</div>
<div className='progress-bar' id='progress-bar'>
<div className='progress' id='progress'></div>
</div>
<div id='gallery' />
</div>
{/* // end of ATTEMPT */}
{/* // end of ATTEMPT */}
{/* // end of ATTEMPT */}
</Form>
);
}
I've also found this post about this topic, but the code seems to be gone forever from the internet: https://twitter.com/remix_run/status/1494096161669464064?lang=en
Thank you!
The remix example was moved here
Related
I am pretty new to javascript. Im trying to upload my image file but somehow I can only browse my file and my image is not uploading. I had a problem with inputField.addEventListener('change', function(e){});
I can see that my addEventListener('click', () => {}) is working fine but I dont understand if I did inputField.click(); it did not get into the inputField.addEventListener('change', function(e){});
const draggerArea = document.getElementById('dragger');
const inputField = document.getElementById('fileInputField');
const dragText = document.getElementById('drag-text');
const fileName = document.getElementById('fileName');
const browseFile = document.getElementById('uploadFile');
browseFile.addEventListener('click', () => {
inputField.value = ""
alert(String("hello"))
inputField.click();
});
inputField.addEventListener('change', function(e) {
file = this.files[0];
alert(String(file))
fileHandler(file);
});
const fileHandler = (file) => {
alert("hello")
const validExt = ["image/jpeg", "image/jpg", "image/png"]
if (validExt.includes(file.type)) {
const fileReader = new FileReader();
fileReader.onload = () => {
const fileURL = fileReader.result;
let imgTag = `<img src=${fileURL} alt=""/>`
draggerArea.innerHTML = imgTag;
let paragraph = `<div class="fileName"><p>${file.name.split('.')[0]}</p><i class="fa-solid fa-trash-can" onclick={deleteHandler()}></i></div>`
fileName.innerHTML = paragraph;
}
fileReader.readAsDataURL(file);
draggerArea.classList.add('active')
} else {
draggerArea.classList.remove('active');
dragText.textContent = "Drag and Drop File"
}
};
<div id="dragger_wrapper">
<div id="dragger">
<div class="icon"><i class="fa-solid fa-images"></i></div>
<h2 id="drag-text">Drag and Drop File</h2>
<h3>Or</h3>
<button class="uploadFile" id="uploadFile">Browse File</button>
<input type="file" hidden id="fileInputField" />
</div>
<div id="fileName"> </div>
</div>
<script src="dragAndDropController.js"></script>
modified code
you have no problem with inputField.addEventListener('change', function(e){});
tutorial on youtube
You want to to click hidden input and it is also working when you click browse button. This is not complete upload process . It is just displaying the file and need to store image in server.
Here is uploading with php
This is a second part to the problem that had been resolved here: Insert Image Link from Google Drive into Google Sheets After Uploading an Image via Web App
I'm developing a web application where a user can upload a picture by clicking on a button. This action will upload pictures into a certain directory in my google drive with a unique folder and name.
Now, I'm trying to copy and paste the google drive link of a picture any time it has been uploaded.
With the help of #Tanaike, I was able to get the link of the url from google drive into the google sheet when I pre-assign a part of the folder name (fn) and the picture title (i) within the getFileUrl(fn,i) function in "Code.gs". But I get this "TypeError: Cannot call method "getFilesByName" of undefined." output whenever I try to pass the user-input "fn" and "i".
page.html
--This is the front end, where a user uploads the picture
<html>
<head>
<body>
<form action="#" method="post" enctype="multipart/form-data">
<div class="row">
<div class="file-field input-field">
<div class="waves-effect waves-light btn-small">
<i class="material-icons right">insert_photo</i>
<span>Import Picture</span>
<input id="files" type="file" name="image">
</div>
<div class="file-path-wrapper">
<input disabled selected type="text" class="file-path
validate" placeholder="Choose an image">
</div>
</div>
</div>
</form>
<?!= include("page-js"); ?>
</div> <!-- CLOSE CONTAINER-->
</body>
</html>
This is part of the javascript to put relevant info in an array, which will later be used to append a row in the google sheet
page-js.html
<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/1.0.0/js/materialize.min.js"></script>
<script src="https://gumroad.com/js/gumroad.js"></script>
document.getElementById("files").addEventListener("loadend",doStuff1);
document.getElementById("addAnother").addEventListener("click",doStuff1);
<script>
function doStuff1(){
num.picNum2=i;
var personName=document.getElementById("fn").value;
google.script.run.withSuccessHandler(doStuff2).getFileUrl("fn","i"); // Modified by Tanaike
var userInfo ={};
userInfo.firstName= document.getElementById("fn").value;
userInfo.number=i;
userInfo.fileUrl=fileId00;
num.picNum=i;
i++;
google.script.run.userClicked(userInfo);
}
// Added by Tanaike
function doStuff2(fileId00) {
var userInfo = {};
userInfo.firstName = document.getElementById("fn").value;
userInfo.number = i;
userInfo.fileUrl = "https://docs.google.com/document/d/"+fileId00 +"/";
i++;
google.script.run.userClicked(userInfo);
}
</script>
This is part of the javascript to upload picture file into the Google drive
(still part of page-js.html)
var file,
reader = new FileReader();
var today = new Date();
var date = today.getFullYear()+'-'+(today.getMonth()+1)+'- '+today.getDate();
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,num.picNum,date,$('input#fn')
.val(),$('input#date').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 image to upload");
return;
}
file = files[0];
if (file.size > 1024 * 1024 * 5) {
showError("The file size should be < 5 MB.");
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>
This part grabs the array "userInfo" and appends the content in a row within a designated google sheet. Any time, I click on the button in the front end, it creates a new row.
This is where if I set fn and i values within the getFileUrl function manually and have a correponding picture and a folder under the designated directory, I get a valid link back in my google sheet. However, if I leave the argument as variables that the user input in the web app, I get the aforementioned error in my link within the sheet.
Code.gs
//google sheet web script
var url="https://docs.google.com/spreadsheets/d/XXXXX";
function getFileUrl(fn,i){
try{
var today0 = new Date();
var date0 = today0.getFullYear()+'-'+(today0.getMonth()+1)+'-'
+today0.getDate();
var dropbox0 = "OE Audit Pictures";
var folder0,folders0 = DriveApp.getFoldersByName(dropbox0);
while (folders0.hasNext())
var folder0=folders0.next();
var dropbox20=[date0,fn].join(" ");
var folder20,folders20=folder0.getFoldersByName(dropbox20);
while (folders20.hasNext())
var folder20=folders20.next();
var file0, files0= folder20.getFilesByName(i);
while (files0.hasNext())
var file0=files0.next();
var fileId0=file0.getUrl();
return fileId0;
} catch(f){
return f.toString();
}
}
function userClicked(userInfo){
var ss= SpreadsheetApp.openByUrl(url);
var ws=ss.getSheetByName("Data");
ws.appendRow([userInfo.number,new Date(),
userInfo.firstName,userInfo.fileUrl]);
}
function include(filename){
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
function uploadFileToGoogleDrive(data, file, fn, date) {
try {
var dropbox = "OE Audit Pictures";
var folder, folders = DriveApp.getFoldersByName(dropbox);
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(dropbox);
}
var contentType = data.substring(5,data.indexOf(';')),
bytes =
Utilities.base64Decode(data.substr(data.indexOf('base64,')+7)),
blob=Utilities.newBlob(bytes, contentType, file)
var dropbox2=[fn,date].join(" ");
var folder2, folders2=folder.getFoldersByName(dropbox2)
if (folders2.hasNext()){
folder2=folders2.next().createFile(blob);
} else {
file = folder.createFolder([fn,date].join(" ")).createFile(blob);
}
return "OK";
} catch (f) {
return f.toString();
}
}
As #Jescanellas and #Tanaike commented, the better approach is to fix your code editing the function doStuff2 in page-js.html as this:
function doStuff2(fileId00) {
var userInfo = {};
userInfo.firstName = document.getElementById("fn").value;
userInfo.number = i;
userInfo.fileUrl = "https://docs.google.com/document/d/"+fileId00 +"/";
i++;
google.script.run.userClicked(userInfo);
}
About the error you're getting, you're not using the brackets in the whiles, this is causing the errors because only takes the first line after the while inside the loop. Code.gs:
while (folders0.hasNext()) {
var folder0=folders0.next();
var dropbox20=[date0,fn].join(" ");
var folder20,folders20=folder0.getFoldersByName(dropbox20);
while (folders20.hasNext()) {
var folder20=folders20.next();
var file0, files0= folder20.getFilesByName(i);
while (files0.hasNext()) {
var file0=files0.next();
var fileId0=file0.getUrl();
return fileId0;
}
}
}
Regarding your question about the file Id, you can get it easily after you create the file, because this will return you a File object from which you can get the ID using the getId method [1]:
file = folder.createFolder([fn,date].join(" ")).createFile(blob);
fileId = file.getId();
[1] https://developers.google.com/apps-script/reference/drive/file
I'm working on an app that allows users to post events. I'm using NodeJS, Express and Mongo.
I created a form that allows users to input event details, and upload an image relating to the event. I also created a form that allows the user to edit event details.
The form looks as follows:
The Problem:
User fills form with event details and attaches a picture.
User submits form
User decides he wants to change the event title, but NOTHING ELSE
User clicks edit event, changes the title, and submits
The problem: Even though the user didn't delete the picture associated with the event, the picture is no longer there.
Here is part of my new.ejs file (for posting new event, just adding this here for reference)
<script type="text/javascript" src="/js/eventform.js"></script>
<form action="/events"
method="POST"
enctype="multipart/form-data"
onSubmit="return(validate(this));" // validating user input
novalidate >
....
....
<input name="image" type="file" id="image" accept="image/*" style="display:none" onchange="handleFiles(this.files)">
<div id="imageBorder" >
<div id="imageContainer">
<div id="dropbox">
<i class="fa fa-picture-o" aria-hidden="true"></i>
<p> Drop image here or click to upload</p>
</div>
<div id="preview" class="hidden">
</div>
<button id="fileSelect" class="...">Upload Image</button>
<button id="fileRemove" class="...">Remove Image</button>
</div>
</div>
....
....
</form>
Notice that I'm using a hidden input field. Also I have two divs, preview (hidden initially) and dropbox. When an image is uploaded, the class 'hidden' is removed from preview and added to dropbox.
Here is part of the js file newevent.js
$(document).ready(function() {
....
eventImageSetup();
....
}
....
function eventImageSetup() {
var dropbox = document.getElementById("dropbox"),
fileElem = document.getElementById("image"),
fileSelect = document.getElementById("fileSelect"),
fileRemove = document.getElementById("fileRemove");
$(dropbox).height($('#imageBorder').height());
fileSelect.addEventListener("click", function(e) {
if (fileElem) {
fileElem.click();
e.preventDefault(); // to prevent submit
}
}, false);
fileRemove.addEventListener("click", function(e) {
e.preventDefault(); // prevent submit
if(!$('#preview').hasClass('hidden')) { // if there is an image
$('#preview').empty();
$('#dropbox').removeClass('hidden');
$('#preview').addClass('hidden');
$('#fileSelect').text('Upload Image');
$('#image').wrap('<form>').closest('form').get(0).reset();
$('#image').unwrap();
}
removeError($('#imageError'), $('#image'));
});
dropbox.addEventListener("dragenter", dragenter, false);
dropbox.addEventListener("dragover", dragover, false);
dropbox.addEventListener("drop", drop, false);
}
function handleFiles(files) {
var file = files[0];
.... // some error checking
var img = document.createElement("img");
img.id = "uploadedImage";
img.file = file;
img.onload = function() {
adjustImageSize(img);
};
$('#dropbox').addClass('hidden');
$('#preview').removeClass('hidden');
$('#preview').empty();
$('#preview').append(img);
$('#fileSelect').text('Replace Image');
var reader = new FileReader();
reader.onload = (function(aImg) {
return function(e) {
aImg.src = e.target.result;
};
})(img);
reader.readAsDataURL(file);
}
Here is part of my edit.ejs file
<form action="/events/<%=event._id%>?_method=PUT"
method="POST"
enctype="multipart/form-data"
onSubmit="return(validate(this));"
novalidate >
<input name="image" type="file" id="image" accept="image/*" style="display:none" onchange="handleFiles(this.files)">
<div id="imageBorder" >
<div id="imageContainer">
<div id="dropbox" class="hidden">
<i class="fa fa-picture-o" aria-hidden="true"></i>
<p> Drop image here or click to upload</p>
</div>
<div id="preview">
<script>
var imageExists = '<%=event.image%>';
if(imageExists) {
var myImg = document.createElement("IMG");
var source = "../../<%= event.image %>";
myImg.src = source;
adjustImageSize(myImg);
$('#preview').append(myImg);
}
</script>
</div>
<button id="fileSelect" class="...">Upload Image</button>
<button id="fileRemove" class="...">Remove Image</button>
</div>
</div> <!-- END OF imageBorder -->
....
</form>
The script above succesfully makes the image appear in the edit page, as follows.
But when you click submit, the picture doesn't show up.
Here is the nodejs route file. You can see the problem here
// UPDATE SPECIFIC EVENT IN DATABASE
router.put("/:id", upload.single('image'), middleware.checkEventOwnership, function(req, res) {
var filepath = undefined;
if(req.file) {
filepath = req.file.path.substr(7); // Substr to remove "/public"
}
req.body.image = filepath;
Event.findByIdAndUpdate(req.params.id, req.body, function(err, foundEvent) {
if(err) {
console.log(err);
req.flash("error", err);
} else {
req.flash("success", "Successfully edited your event");
}
res.redirect("/events/" + req.params.id);
});
});
Basically, if I leave the image untouched in the edit form, req.file doesn't exist. Thus, req.body.image = undefined. And an image is no longer associated with the event.
Common sense would say do this
if(req.file) {
filepath = req.file.path.substr(7);
req.body.image = filepath;
}
But if you do that, you introduce a new problem: If the user edits the event and removes the image (i.e decides he doesn't want an image associated with the event), the image never gets deleted.
Any idea how to solve this problem? I know I have to do something in the edit.ejs script... More specifically, I need to create an image file... But I'm not sure how to approach this
So I got this to work through a hack I REALLY don't like. I'm sure there is a better, cleaner, standard way of dealing with edit.ejs and images. In other words, please help me find a better solution!
Here are the changes in edit.ejs
<form action="/events/<%=event._id%>?_method=PUT"
method="POST"
enctype="multipart/form-data"
onSubmit="return validate(this) & editPageImageProcessing(this);"
novalidate >
....
....
<div id="preview">
<script>
var imageExists = '<%=event.image%>';
if(imageExists) {
var myImg = document.createElement("IMG");
var source = "../../<%= event.image %>";
myImg.src = source;
myImg.name = "previewImage";
myImg.id = "previewImage";
adjustImageSize(myImg);
$('#preview').append(myImg);
}
</script>
</div>
Basically, I added the lines
myImg.name = "previewImage";
myImg.id = "previewImage";
and added a function editPageImageProcessing.
What this function does is: IF the user didn't upload a new image, and did not delete the image, create a hidden input field "hiddenImage", and let its value be the source of the original image. See below:
// This function deals with edit image
function editPageImageProcessing(form) {
// If the user didn't change the image
// preview would be NOT hidden
// there would not be a req.file (document.getElementById('image').val == '')
// we want a hiddenimage input field
var aFile = document.getElementById('image').value;
console.log("File: ", aFile);
var preview = document.getElementById('preview');
console.log("Preview has hidden class: " + $(preview).hasClass('hidden'));
if(aFile == '' && !$(preview).hasClass('hidden')) {
var input = document.createElement('input');
$(input).attr("name", "hiddenImage");
$(input).attr("id", "hiddenImage");
$(input).attr("type", "hidden");
var myImage = document.getElementById('previewImage');
$(input).attr("value", myImage.src);
$('form').append(input);
}
return true;
}
Now, in the edit route, I did this
// UPDATE SPECIFIC EVENT IN DATABASE
router.put("/:id", upload.single('image'), middleware.checkEventOwnership, function(req, res) {
var filepath = undefined;
if(req.file) { // If user uploaded a new image
filepath = req.file.path.substr(7); // Substr to remove "/public"
console.log(filepath);
} else if(req.body.hiddenImage) { // If user kept the same image
var index = req.body.hiddenImage.lastIndexOf("/uploads");
filepath = req.body.hiddenImage.substr(index);
// req.body.hiddenImage WILL ONLY EXIST if user left image unchanged
}
req.body.image = filepath; // If user deleted image, this will be undefined, which is what we want
Event.findByIdAndUpdate(req.params.id, req.body, function(err, foundEvent) {
if(err) {
console.log(err);
req.flash("error", err);
} else {
req.flash("success", "Successfully edited your event");
}
res.redirect("/events/" + req.params.id);
});
});
Okay, so its messy.
Any better solution would be appreciated
This might seem like a duplicate but please read first and let me know if you have questions. I have a single xpage with two data sources, one form for information and the other for image attachments. Now unlike typical applications I do not want to attach the image to the existing main data source (the first one) I want to asynchronously upload images and create separate documents for each image with the main datasource's UNID as the subID of the new image document. This is the just the way of the current data structure and I have little to no say on changing this so here's the challenge is presented.
I have successfully been able to create xhr requests (with the guide of various async uploader previous works of Julian and Sven) and create new documents with images with the custom control I have written but once I embed it into the XPage with two datasources and attempt to upload and create a new document, it creates a duplicate of the main datasource and wipes out all the other field values (expected behavior of a duplicate with empty fields). I suspect it's because I use the $$submitid and all the required form values for my upload request and they're somehow tied or shared with the first datasource but I can't be too sure.
My custom control has it's own datasource and the xpage has a panel with a datasource as part of it. Custom Control below.
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.data>
<xp:dominoDocument var="imgAttach" formName="mImageAttach"
concurrencyMode="force">
</xp:dominoDocument>
</xp:this.data>
<xp:inputText id="inputText2" style="width:657.0px"><xp:this.defaultValue><![CDATA[#{javascript:facesContext.getExternalContext().getRequest().getRequestURI();
context.getUrl()}]]></xp:this.defaultValue></xp:inputText>
<xp:br></xp:br>
<xp:br></xp:br>
<xp:inputText id="parentId" value="#{imgAttach.ParUNID}"
style="width:333.0px">
<xp:this.defaultValue><![CDATA[#{javascript:var url = context.getUrl().toString();
/*
if(url.indexOf("createComponent.xsp") > 0 && context.getUrlParameter("documentId") != ""){
var doc:NotesDocument = newComponent.getDocument();
return doc.getUniversalID();
} else {
return "";
}
*/}]]></xp:this.defaultValue>
<xp:this.rendered><![CDATA[#{javascript:var url = context.getUrl().toString();
if(url.indexOf("createComponent.xsp") > 0){
return true;
} else {
return false;
}}]]></xp:this.rendered>
</xp:inputText>
<xp:br></xp:br>
<xp:br></xp:br>
<xp:inputText id="inputText1"
defaultValue="#{javascript:imgAttach.getDocument().getUniversalID()}"
style="width:333.0px; display:none;">
</xp:inputText>
<xp:br></xp:br>
<xp:br></xp:br>
<xp:inputText id="Created" style="width:297.0px; display:none;"
value="#{imgAttach.Created}"
defaultValue="#{javascript:#Created()}">
<xp:this.converter>
<xp:convertDateTime type="date" dateStyle="short"></xp:convertDateTime>
</xp:this.converter>
</xp:inputText>
<xp:br></xp:br>
<xp:div styleClass="file-input">
<div class="file-preview ">
<div class="close fileinput-remove">×</div>
<div class="">
<div class="file-preview-thumbnails" id="file-thumbs">
</div>
<div class="clearfix"></div>
<div
class="file-preview-status text-center text-success">
</div>
<div class="fileinput-error file-error-message"
style="display: none;">
</div>
</div>
</div>
<div class="input-group-btn">
<button type="button" tabindex="500"
title="Clear selected files"
class="btn btn-default fileinput-remove fileinput-remove-button">
<i class="glyphicon glyphicon-trash"></i>
Remove
</button>
<button tabindex="500" id="upload-files"
title="Upload selected files"
class="btn btn-default fileinput-upload fileinput-upload-button">
<i class="glyphicon glyphicon-upload"></i>
Upload
</button>
<div tabindex="500" class="btn btn-primary btn-file"
id="files-container">
<i class="glyphicon glyphicon-folder-open"></i>
Browse …
<input name="add-files[0]" id="add-files" type="file"
multiple="true" class="file-input">
</input>
<xp:fileUpload id="FileUploadCtrl"
value="#{imgAttach.Body}" useUploadname="true"
style="display:none">
</xp:fileUpload>
<xp:eventHandler event="onsaveDoc" submit="false"
refreshMode="norefresh" immediate="false" save="true" id="saveDoc" />
</div>
</div>
</xp:div>
<xp:br></xp:br>
<xp:br></xp:br>
<xp:scriptBlock id="scriptBlock2" type="text/javascript">
<xp:this.value>
<![CDATA[
$(function() {
$("#files-container").delegate('input', "change", function() {
var files = !!this.files ? this.files : [];
if (!files.length || !window.FileReader){
//console.log("No file selected or no file reader suppport");
return; // no file selected, or no FileReader support
}
for(i=0; i<files.length; i++) {
if (/^image/.test( files[i].type)){ // only image file
var reader = new FileReader(); // instance of the FileReader
reader.readAsDataURL(files[i]); // read the local file
var img = document.createElement("img");
img.file = files[i];
img.name = 'no_'+ i;
img.classList.add("file-preview-image");
reader.onload = (function(aImg) { return function(e) { aImg.src = e.target.result; }; })(img);
$("#file-thumbs").append(img);
}
}
/*
//add new upload button
var currentInput = $('#add-files');
var nextInput = $('#files-container');
var inputsCount = $('.file-input').length;
// now we change the 'id' attribute of said element because id's should be unique
currentInput.attr('id','add-files'+inputsCount);
// and finally we hide the old element
currentInput.hide();
// now, we append a new input element with an incremented array key defined by the length of already existing input elements
nextInput.append('<input type="file" name="add-files['+inputsCount+']" id="add-files" multiple="true" class="file-input" />');
*/
});
$("#upload-files").on("click", function(e) {
e.preventDefault();
e.stopPropagation();
var all_files = [];
try {
var files = document.querySelectorAll(".file-preview-image");
for(var i = 0; i < files.length; i++) {
//
a_uploader(files[i].file, "#{id:saveDoc}", 0, 10);
}
} catch(e) {
console.log(e);
}
});
function a_uploader(file, uploadID, counter, maxSize){
try {
var formData = new FormData();
formData.append("#{id:FileUploadCtrl}", file);
formData.append("#{id:Created}", document.getElementById("#{id:Created}").value);
formData.append("$$viewid", dojo.query("input[name='$$viewid']")[0].value);
formData.append("$$xspsubmitid", uploadID);
formData.append( "view:_id1", "view:_id1");
if(document.getElementById("#{id:parentId}") != undefined ){
formData.append("#{id:parentId}", document.getElementById("#{id:parentId}").value);
}
var xhr = new XMLHttpRequest();
/* event listners */
xhr.upload.addEventListener("progress", function(e) {
if (e.lengthComputable) {
var percentComplete = Math.round(e.loaded * 100 / e.total);
console.log("Uploading... " +percentComplete.toString() + "%)");
}
else {
console.log("Uploading...")
}
}, false);
xhr.addEventListener("error", function(e) {
//insert error between error div
console.log("error: " + e);
}, false);
xhr.addEventListener("abort", function() {
//insert cancelled between div
console.log("Upload cancelled");
}, false);
xhr.open("PATCH", "#{javascript:context.getUrl()}");
xhr.send(formData);
} catch (e) {
console.log("a_uploader: "+e);
}
}
function clear_inputFiles(){
try {
var files = document.querySelectorAll(".file-preview-image");
for(var i = 0; i < files.length; i++) {
}
} catch(e) {
}
}
});]]>
</xp:this.value>
</xp:scriptBlock>
<xp:this.resources>
<xp:script src="/x$.js" clientSide="true"></xp:script>
</xp:this.resources>
</xp:view>
I thought I had set my first datasource to ignore request params as well as the data source inside of the component control. After setting the ignore request params to true on the first data source and finagling the datasource to get the document ID if it was edit mode, I was then able to use the custom control to upload images independently of the first/main datasource.
Now that they are uploading independently there is still a duplicate being created which seems odd to me. If I click the save button the first data source I don't create any image attachments but if I upload images it creates a duplicate of the primary doc.
Edit:
I had to remove the editDocument and documentId from the URL and specify my own docId as a parameter. I guess the HTTP referrer was the culprit in this case for creating the duplicate....or at least the way the page was set up from creating the doc to saving the doc.
I want to upload multiple files and want to show their progress.I am trying the following code.
Here is my html code...
<div id='albumBox'>
<input type='file' multiple name='newsfeedAlbum[]' id='newsfeedAlbum' onchange='uploadNewsfeedImages()' />
<div>
<div id='uploadingImages'>
</div>
<div>
<preogress id='progressBar'> </progress>
</div>
<div>
<input type='button' id='albumButton' value='post' disabled />
</div>
here is my javascript code...
function uploadNewsfeedImages()
{
alert(document.getElementById("newsfeedAlbum").files.length);
var files = document.getElementById("newsfeedAlbum").files;
for(var i = 0;i < files.length;i++)
{
var file = files[i];
//alert("file name is "+files.item(i).name);
var formData = new FormData();
formData.append("image",file);
var xhr = new XMLHttpRequest();
xhr.open("POST","add_newsfeed.php",true);
alert(i);
xhr.upload.onprogress = function()
{
alert("bujji" + i);
}
xhr.send(formData);
}
}
function showUploadProgress(event)
{
var uploaded = event.loaded/event.total;
uploaded = Math.floor(uploaded*100);
document.getElementById("progressBar").value = uploaded;
}
But when I am trying to upload two images and alerting on upload.progress event it is alerting bujji2 and bujji2 instead of bujji0 and bujji1.How to handle individual upload.progress events....
It's a scope issue. The for loop will have completed by the time the onprogress events will trigger, so 'i' will be 2 every time the onprogress triggers because your 'for-loop' has finished running.
You can wrap the onprogress function in a closure to get the desired effect.
xhr.upload.onprogress = (function() {
return function() {
alert('bujji' + i);
};
}());
You need to save your i, because it will be changed before onprogress fired.
xhr.upload.fileNum = i;
xhr.upload.onprogress = function()
{
alert("bujji" + this.fileNum);
}
xhr.send(formData);
And read explanation for previous answer: Creating_closures_in_loops.3A_A_common_mistake