Is it possible to use Inertia form helper for upload files so, that no file input is involved? - file-upload

WHat I did is, that through drag' drop i took the value of dataTransfer files, and made form files field equal to that:
const form = useForm({
files: [],
});
form.files = e.dataTransfer.files;
Is this a valid method, or for some reason it is mandatory to use file input?

Related

Vue2-Dropzone process form when files manually added

Is it possible to manually process the dropzone form (or queue) when the file is manually loaded?
We have the concept of a drivers license. The user uploads a photo and enters other information such as the license number, expiration date, etc.. The user clicks the save button and I call processQueue() which submit the entire form. This all works just fine.
Next, we display this license in a non-form way with an edit button. When they click the "Edit" button, I display the form again and populate the fields with previously entered data including manually adding the previously submitted photo of their license. Basically like this from the documentation:
mounted: () {
var file = { size: 300, name: "Icon", type: "image/png" };
var url = "https://example.com/img/logo_sm.png";
this.$refs.myVueDropzone.manuallyAddFile(file, url);
}
This appears to all work as expected. I see the dropzone with a thumbnail of the previously uploaded file. The input fields are all populated with previously entered data.
The problem occurs when I try to process this form again with:
onSubmit() {
this.$refs.myVueDropzone.processQueue()
}
If they only make changes to the input fields like the license number and do not upload a new file, the onSubmit() or processQueue() does not work. It only works if I remove and re-add a file or add a second file. It's as if it does not recognize a file has been added. Is manuallyAddFile only for displaying and not actually adding the file?
How can I submit this form when the file is manually added?
After a bit of research on Vue2 Dropzone
Manually adding files
Using the manuallyAddFile method allows you to programatically add files to your dropzone area. For example if you already have files on your server that you'd like to pre-populate your dropzone area with then simply use the function when the vdropzone-mounted event is fired.
source
So the solutions is to check and see if anything needs to be processed in your queue. Since you manually added the file you already have it, it does not need to be uploaded again. Only if the user adds a new file does it need to be uploaded.
You could do this a few ways, but I would recommend something like the example below for it's simplicity:
onSubmit() {
if (this.$refs.myVueDropzone.getQueuedFiles().length) {
this.$refs.myVueDropzone.processQueue()
}
}
If you need to wait until the queue has finished processing to send your form you can leverage vdropzone-queue-complete. Below is a quick example:
<template>
<!-- SOME FORM ELEMENTS HERE -->
<vue-dropzone
ref="myVueDropzone"
:options="dropzoneOptions"
#vdropzone-error="errorUploading"
#vdropzone-success="fileUploaded"
#vdropzone-queue-complete="submitForm"
/>
<button #click="saveForm">Save</button>
</template>
<script>
export default {
methods: {
saveForm () {
if (this.$refs.myVueDropzone.getQueuedFiles().length) {
this.$refs.myVueDropzone.processQueue()
} else {
this.submitForm()
}
},
errorUploading (file, message, xhr) {
// do something when a file errors
// perhaps show a user a message and create a data element to stop form from processing below?
},
fileUploaded (file, response) {
// do something with a successful file upload. response is the server response
// perhaps add it to your form data?
},
submitForm () {
// Queue is done processing (or nothing to process), you can now submit your form
// maybe check for errors before doing the below step
// do what ever you need to submit your form this.$axios.post(...) etc.
}
}
}
</script>

createjs loadManifest, it should be loading files in manifest, correct?

If I understand the docs correctly… 
window.queue = new createjs.LoadQueue(true, null, true);
queue.loadManifest({src: manifest, type: "manifest"}, true);
should be loading the files that are located in the json file, correct? Not seeing any requests in inspector, only getting the results array in console. Do I have to loop over results array and do the loadFile manually?
JSON is formatted correctly in a {src:"",id:"",type:"createjs.Types.IMAGE"} structure.
Any help is appreciated.
Adding more code:
function to pass in manifest url
function loadImages(manifest) {
window.queue = new createjs.LoadQueue(true, null, true);
queue.loadManifest({src: manifest, type: "manifest"}, true);
queue.on("fileload", this.handleFileLoaded);
queue.on("progress", function(event) {
console.log("progress " + event.progress);
});
queue.on("fileprogress", function(event) {
console.log("file progress " + event.progress);
});
queue.on("error", function(event) {
console.log("file error");
});
queue.on("complete", function(event) {
console.log("queue complete");
console.log(event);
});
queue.load();
return queue;
}
handleFileLoaded event is just dumping event to console at this point.
Manifest with two examples
{
"path":"https://images.unsplash.com/",
"type":"manifest",
"manifest": [
{
"src":"photo-1542838454-d4dce2a7cfde?fit=crop&w=500&q=60",
"id":"stair_boy",
"type":"createjs.Types.IMAGE"
},
{
"src":"photo-1549948558-1c6406684186?fit=crop&w=500&q=60",
"id":"night_bridge",
"type":"createjs.Types.IMAGE"
}
]}
I get access to the manifest array in the fileload event, I can manually load the images from there, but that seems counterintuitive to the whole point of using the PreloadJS. Seems like on page load, Preload should load the manifest, recognize 'type'… loop through files and in network inspector I should see the web requests for the images.
The types in your manifest are incorrect. You are passing in a string value of "createjs.Types.IMAGE". This is not equal to "image", nor is it the equivalent of the JavaScript createjs.Types.IMAGE, since it is interpretted as a string.
Instead use the string value "image"
{
"path":"https://images.unsplash.com/",
"type":"manifest",
"manifest": [
{
"src":"photo-1542838454-d4dce2a7cfde?fit=crop&w=500&q=60",
"id":"stair_boy",
"type":"image"
},
{
"src":"photo-1549948558-1c6406684186?fit=crop&w=500&q=60",
"id":"night_bridge",
"type":"image"
}
]}
Edit: The type property is only required when there is not a recognizable image extension, such as this case.
From the docs:
The loadManifest call supports four types of manifests:
A string path, which points to a manifest file, which is a JSON file that contains a "manifest" property, which defines the list of files to load, and can optionally contain a "path" property, which will be prepended to each file in the list.
An object which defines a "src", which is a JSON or JSONP file. A "callback" can be defined for JSONP file. The JSON/JSONP file should contain a "manifest" property, which defines the list of files to load, and can optionally contain a "path" property, which will be prepended to each file in the list.
An object which contains a "manifest" property, which defines the list of files to load, and can optionally contain a "path" property, which will be prepended to each file in the list.
An Array of files to load.
Your example uses the first approach. If something is not working, then feel free to post more code.
You could always throw some more events on your queue to see what is happening, such as "fileststart", "fileload", and "error". You should get at least one event when the first manifest starts loading.
Cheers.

How to add an uploaded file to a list of already uploaded files in Vue?

I currently have a file uploader that accepts a single CSV file. Then with axios I POST such file to the server and everything works just fine. What I'm not being able to achieve is being able to upload another CSV that will get added to the list of CSVs uploaded. I'm not talking about uploading various files at once, I'm taking about uploading different files at different points in time.
This is the method that is used to select a CSV file in the .vue file.
staticCampaignCSVSelected: function (file) {
console.log('campaign-detail.vue#staticCampaignCSVSelected', file)
let vc = this
vc.selectedHeuristicId = -1
Campaign.uploadStaticCSV(vc.campaign, file[0])
.then(
function (data) {
alert('CSV cargado con exito')
}
)
.catch(
function (err, data) {
console.log("campaign-detail#staticCampaignCSVSelected - catch", err.response)
alert(err.response.data.error)
}
)
},
This is the function that I have in some other JS file to POST to the API:
function uploadStaticCSV (campaign, csv) {
console.log('Campaign#uploadStaticCSV', campaign, csv)
//long list of assertions
let formData = new FormData()
formData.append('csv', csv)
return axios.post(API.campaignUploadStaticCSV(campaign.id), formData)
}
And this is the function I have in my endpoints.js file:
campaignUploadStaticCSV: function (id) { return this.campaign(id) + '' + '/csv' },
I haven't found a way to properly pass a[file] array as a parameter to the functions, which is what I believe I need to somehow do.
Any help would be appreciated :)
As far as i understood your question you need a way to pass a file from browser interface to your staticCampaignCSVSelected(file) method. If so why not to use an input model or a simple event or a watcher. E.g.
<input type="file" #input="staticCampaignCSVSelected($event.target.files[0])" />
But also i see a mistake in your code. You should append .then().catch() callbacks to axios.post() itself but not to Campaign.uploadStaticCSV() method.
And
return axios.post()
will not return a server response. You have to handle it in
axios.post().then(response => {})
callback

How to create standalone custome page?

I'm looking for a way to create single page model/ standalone single page.
It's like a custom single page for 'About Us', 'Home Page','Our Team',etc.
They are single page with backend options.
Anyone have any idea ?
So you need to create all needed type of files, like route JS file, template file, add info about that file into routes/index.js
example:
create file routes/views/aboutUs.js :
var keystone = require("keystone");
exports = module.exports = function(req, res) {
var view = new keystone.View(req, res);
var locals = res.locals;
// locals.section is used to set the currently selected
// item in the header navigation.
locals.section = "about-us";
locals.title = "About our company";
// Render the view
view.render("aboutUs");
};
create template file templates/aboutUs.pug :
block content
p Our company is super cool. We based it here long time ago
Put all your static content into template with correct syntax and css
Finally make addition to routes/index.js file:
app.get("/aboutUs", routes.views.aboutUs);
if you need to control user access to page also add such string
app.all("/aboutUs*", middleware.requireUser);
And dont forget to restart the app to see changes
That's clearly not what OP is asking for. They're asking if there is a way to create a single ADMIN UI editable page for Home, About Us, and so on. My answer is that I don't believe that is possible with KeystoneJS. Which is annoying, because I have clients that want that and Keystone would be perfect otherwise. Seems the only way to do it is create a list, auto create a record if one doesn't exist, and set "nocreat", and "novelette" on the list.

How do you test uploading a file with Capybara and Dropzone.js?

I've switched to using the Dropzone.js plugin for drag-and-drop file uploads. How can I write a Capybara test to ensure this functionality keeps on working?
Previously I had a template with an input file element:
<input type="file" name="attachments">
And the test was simple:
When(/^I upload "([^"]*)"$/) do |filename|
attach_file("attachments", File.expand_path(filename))
# add assertion here
end
However this no longer works because Dropzone doesn't have a visible file input.
To solve this, simulate a drop event to trigger dropping an attachment onto Dropzone. First add this function to your step definition:
# Upload a file to Dropzone.js
def drop_in_dropzone(file_path)
# Generate a fake input selector
page.execute_script <<-JS
fakeFileInput = window.$('<input/>').attr(
{id: 'fakeFileInput', type:'file'}
).appendTo('body');
JS
# Attach the file to the fake input selector
attach_file("fakeFileInput", file_path)
# Add the file to a fileList array
page.execute_script("var fileList = [fakeFileInput.get(0).files[0]]")
# Trigger the fake drop event
page.execute_script <<-JS
var e = jQuery.Event('drop', { dataTransfer : { files : [fakeFileInput.get(0).files[0]] } });
$('.dropzone')[0].dropzone.listeners[0].events.drop(e);
JS
end
Then test with:
When(/^I upload "([^"]*)"$/) do |filename|
drop_in_dropzone File.expand_path(filename)
# add assertion here
end
NOTE: You need to have jQuery loaded, and the Dropzone element requires the dropzone class.
These days I find this way more graceful
page.attach_file(Rails.root.join('spec/fixtures/files/avatar.png')) do
page.find('#avatar-clickable').click
end
Where is in my case #avatar-clickable is a div which contain Dropzone form tag.
Building off of #deepwell's answer which didn't quite work for me, here is a solution using vanilla JS for the events and event dispatching, and a neutral selector for the dropzone:
def drop_in_dropzone(file_path, zone_selector)
# Generate a fake input selector
page.execute_script <<-JS
fakeFileInput = window.$('<input/>').attr(
{id: 'fakeFileInput', type:'file'}
).appendTo('body');
JS
# Attach the file to the fake input selector
attach_file("fakeFileInput", file_path)
# Add the file to a fileList array
page.execute_script("fileList = [fakeFileInput.get(0).files[0]]")
# Trigger the fake drop event
page.execute_script <<-JS
dataTransfer = new DataTransfer()
dataTransfer.items.add(fakeFileInput.get(0).files[0])
testEvent = new DragEvent('drop', {bubbles:true, dataTransfer: dataTransfer })
$('#{zone_selector}')[0].dispatchEvent(testEvent)
JS
end
uses global vars on purpose, so I could test in js console, but feel free to scope them.
In case anyone is interested, I ported #deepwell's function to javascript, to use it with javascript flavoured selenium:
this.dropInDropzone = function(filePath) {
var script = "fakeFileInput = $('#fakeFileInput'); if (fakeFileInput.length === 0) fakeFileInput = window.$('<input/>').attr({id: 'fakeFileInput', type:'file'}).appendTo('body');";
// Generate a fake input selector
return driver.executeScript(script).then(function() {
// Attach the file to the fake input selector
return driver.findElement(webdriver.By.css('#fakeFileInput')).sendKeys(filePath);
}).then(function() {
// Add the file to a fileList array
return driver.executeScript("var fileList = [fakeFileInput.get(0).files[0]]");
}).then(function() {
// Trigger the fake drop event
script = "var e = jQuery.Event('drop', { dataTransfer : { files : [fakeFileInput.get(0).files[0]] } }); $('.dropzone')[0].dropzone.listeners[0].events.drop(e);"
return driver.executeScript(script);
});
};
Since Capybara 3.21.0, you can drop files on elements like this:
find(".dropzone").drop(Rails.root.join("spec/fixtures/file.txt"))
See the Element#drop source for details.