I'm using Livewire and Filepond to allow users to upload images to a gallery.
I need my users to be able to set the order of the images and save that to the database.
Filepond has an option allowReorder: true and a callback that fires when the order has been changed onreorderfiles(files, origin, target)
Here is the basics of my upload component
<div
x-data="{ 'images': null }"
x-init="
FilePond.registerPlugin(FilePondPluginImagePreview);
FilePond.registerPlugin(FilePondPluginImageExifOrientation);
FilePond.registerPlugin(FilePondPluginFileValidateType);
FilePond.registerPlugin(FilePondPluginFileValidateSize);
FilePond.registerPlugin(FilePondPluginImageResize);
FilePond.setOptions({
allowMultiple: true,
allowReorder: true,
server: {
process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
#this.upload('images', file, load, error, progress)
},
revert: (filename, load) => {
#this.removeUpload('images', filename, load)
},
},
onreorderfiles(files, origin, target){
// **** What do I put here to update the order of the images in my livewire component? ******
},
});
const pond = FilePond.create($refs.input, {
acceptedFileTypes: ['image/png', 'image/jpeg'],
maxFileSize: '7MB',
allowImageCrop: true,
allowImageResize: true,
imageResizeTargetWidth: '1000px',
imageResizeTargetHeight: '1000px',
});
pond.on('addfile', (error, file) => {
if (error) {
console.log('Oh no');
return;
}
images = true;
});
"
>
<div wire:ignore wire:key="images">
<div x-show="images == null" class="ex-builder-no-images">
<i class="fas fa-image"></i>
<p>There are no images for this experience.
<br>Upload some below. <br><small>(MAX 10 Images)</small></p>
</div>
<div class="form-group text-center">
<input
id="image-upload"
type="file"
x-ref="input"
multiple
>
#error('images.*')
<p wire:key="error_images" class="mt-2 text-sm text-red-600" id="email-error">{{ $message }}</p>
#enderror
</div>
</div>
</div>
I'm also showing a preview of the first image in the gallery (this is the gallery's featured image) like this <img src="{{ $images ? $images[0]->temporaryUrl() : '/images/placeholder.jpg' }}"> I'm expecting this image to update after the user drags a new image to be the first.
How can I update the image order in the public $images = []; found in my livewire componenet using the onreoderfiles() callback?
Thanks!
Not sure if this is correct, but it seems to work
I added this to FilePond.setOptions()
FilePond.setOptions({
allowMultiple: true,
allowReorder: true,
server: {
process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
#this.upload('images', file, load, error, progress)
},
revert: (filename, load) => {
#this.removeUpload('images', filename, load)
},
},
// Added this ----------------------------------------
onreorderfiles(files, origin, target){
#this.set('images', null);
files.forEach(function(file) {
#this.upload('images', file.file);
});
},
});
This removes the previous uploaded images and reuploads with the new order.
Related
I'm uploading multiple files on submission of a form.
This is the image upload input (html):
<form class="form-inline" id="cost-estimate-form">
<div class="form-field-short col s12 m6">
<i class="material-icons prefix">insert_photo</i>
<label for="input-file">Upload photos</label>
<input id="input-file" type="file" name="images" accept="image/jpeg, image/png, application/pdf" multiple/> <!-- todo: ugly on safari -->
</div>
<!-- rest-->
<button class="btn waves-effect col s6 m3 offset-m6" type="submit" name="action">Submit
<i class="material-icons right">send</i>
</button>
And the .js
'submit #cost-estimate-form': function(event, tmpl){
event.preventDefault();
let files;
if(event.target.images) {
files = event.target.images.files;
}
Meteor.call('travelRequests.insert', tmpl.data, function(err, trId) {
if (err) {
alertError(err.message);
}
else {
if (files) {
var imageDetails = [];
for (var i = 0, j = 0, ln = files.length; i < ln; i++) {
Image.insert(files[i], function (err, fileObj) {
if (err) {
console.log('Error uploading image: ');
console.log(err);
} else {
console.log('[DB] insert image(id=' + fileObj._id);
j++;
let imagePath = '/uploads/images-' + fileObj._id + '-' + fileObj.name();
imageDetails.push({id: fileObj._id, name: fileObj.name(), path: imagePath});
if (j === ln) { // when last file is successful
Meteor.call('travel.addImages', trId, imageDetails,
function (err, _) {
if (err) alertError(err.reason);
});
console.log('travel.addImages');
}
}
});
}
}
}
});
Router.go('travel_requests_list');
Meteor.call('travelRequests.insert'... creates an entity. Then I try to update the images uploaded for that entity after uploading the files with Meteor.call('travel.addImages',....
However, when clicking submit button on the form, the next screen refreshes multiple times and I get error in client:
cfs_power-queue.js:525 Error: "Queue" network [undefined], Error
at cfs_upload-http.js:382
at cfs_upload-http.js:108
at underscore.js:794
at XMLHttpRequest.xhr.onreadystatechange (cfs_upload-http.js:167)
And in the mongodb, some files are completely uploaded and some are not:
Example complete file:
{
"_id" : "MEWTZaXLX9gvx5utc",
"original" : {
"name" : "IMG_3867.JPG",
"updatedAt" : ISODate("2017-07-19T02:57:55Z"),
"size" : 4231984,
"type" : "image/jpeg"
},
"uploadedAt" : ISODate("2017-09-15T02:30:40.204Z"),
"copies" : {
"images" : {
"name" : "IMG_3867.JPG",
"type" : "image/jpeg",
"size" : 4231984,
"key" : "images-MEWTZaXLX9gvx5utc-IMG_3867.JPG",
"updatedAt" : ISODate("2017-09-15T02:30:40Z"),
"createdAt" : ISODate("2017-09-15T02:30:40Z")
}
}
}
Example incomplete file:
{
"_id" : "cgHcSCRPvzgekW6Ai",
"original" : {
"name" : "IMG_3869.JPG",
"updatedAt" : ISODate("2017-07-19T02:58:10Z"),
"size" : 4108047,
"type" : "image/jpeg"
},
"chunkSize" : 2097152,
"chunkCount" : 1,
"chunkSum" : 2
}
Collection definition:
Image = new FS.Collection("images", {
/* the file director: .meteor/local/uploads */
stores:[new FS.Store.FileSystem("images",{path:Meteor.settings.uploadRoot+"/uploads"})],
filter: {
allow: {
contentTypes: ['image/*', 'application/pdf'] //allow only images and pdf in this FS.Collection
}
}
});
if(Meteor.isServer){
Image.allow({
'insert': function () {
return true;
},
'update': function () {
return true;
},
'download':function(){
return true;
}
});
}
Why does this happen? Should I wait for file upload to finish before routing to the next screen? How do I do that if that is the problem?
I'm new to meteor so any help is appreciated.
As you are not using kadira's FlowRouter, I may be not be the right person to answer about the redirection issues as Router mechanism can hold data inside routes as well which is very bad. As per latest Meteor conventions, Subscription must be at template level implementation.
But, I can tell you that you can wait for completion of image upload. When the image is uploaded successfully only then you can perform next operation which can be either redirection to next page or any other operation.
Below is the code to wait for the completion of your image upload. The basic Idea is the url of image is generated only when the Image gets uploaded completely. so we wait for url to be generated, Once we receive url, it means we are done.
var myImage = event.target.images.files[0];
if(myImage){
var myFile = new FS.File(myImage);
Images.insert(myFile, function(err, result){
if(!err){
var liveQuery = Images.find(result._id).observe({
changed: function(newImage, oldImage) {
if (newImage.url() != null) {
liveQuery.stop();
// Here the image is uploaded successfully.
}
}
});
} else {
//console.log(err);
}
});
}
I have a component where I am uploading a video file, everything works fine on my local machine, and it used to work fine on the production server, Namechap is where I host the project, until only recently I have done some work and made changes, that I saw it doesn't work on the production server anymore.
I am using Vue v. 1.0.28, and this is the upload component, where in the fileInputChange() method I am posting form data, to the /upload endpoint, which on the production server for some reason I can't read in the backend:
<template>
<div class="card-content col-md-10 col-md-offset-1">
<div v-if="!uploading">
<div class="col-md-12 Image-input__input-wrapper">
Upload video
<input type="file" name="video" id="video" #change="fileInputChange" class="Image-input__input" accept="video/*">
</div>
</div>
<div class="alert alert-danger video-upload-alert" v-if="failed">Something went wrong. Please check the video format and try again. If you need any help please contact our <a>support service.</a></div>
<div id="video-form">
<div class="alert alert-info" v-if="uploading && !failed && !uploadingComplete">
Please do not navigate away from this page, until the video has finished uploading. Your video will be available at {{ $root.url }}/videos/{{ uid }}, once uploaded.
</div>
<div class="alert alert-success" v-if="uploading && !failed && uploadingComplete">
Upload complete. Video is now processing. Go to your videos.
</div>
<div class="progress" v-if="uploading && !failed && !uploadingComplete">
<div class="progress-bar" v-bind:style="{ width: fileProgress + '%' }"></div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<label for="title" class="control-label">Title</label>
<input type="text" class="form-control" v-model="title">
</div>
<!--
<div class="col-md-12 form-group">
<label for="visibility" class="control-label">Visibility</label>
<select class="form-control" v-model="visibility">
<option value="private">Private</option>
<option value="unlisted">Unlisted</option>
<option value="public">Public</option>
</select>
</div>
-->
</div>
<div class="row">
<div class="col-md-12 form-group">
<label for="description" class="control-label">Description</label>
<textarea class="form-control" v-model="description"></textarea>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<button type="submit" class="btn btn-submit" #click.prevent="update">Save</button>
</div>
</div>
<div class="row">
<div class="col-md-12 form-group">
<span class="help-block pull-right">{{ saveStatus }}</span>
</div>
</div>
</div>
</template>
<script>
function initialState (){
return {
uid: null,
uploading: false,
uploadingComplete: false,
failed: false,
title: null,
link: null,
description: null,
visibility: 'private',
saveStatus: null,
fileProgress: 0
}
}
export default {
data: function (){
return initialState();
},
methods: {
fileInputChange() {
this.uploading = true;
this.failed = false;
this.file = document.getElementById('video').files[0];
var isVideo = this.isVideo(this.file.name.split('.').pop());
if (isVideo) {
this.store().then(() => {
var form = new FormData();
form.append('video', this.file);
form.append('uid', this.uid);
this.$http.post('/upload', form, {
progress: (e) => {
if (e.lengthComputable) {
this.updateProgress(e)
}
}
}).then(() => {
this.uploadingComplete = true
this.uploading = false
}, () => {
this.failed = true
this.uploading = false
});
}, () => {
this.failed = true
this.uploading = false
})
}
else {
this.failed = true
this.uploading = false
}
},
isVideo(extension) {
switch (extension.toLowerCase()) {
case 'm4v':
case 'avi':
case 'mpg':
case 'mp4':
case 'mp3':
case 'mov':
case 'wmv':
case 'flv':
return true;
}
return false;
},
store() {
return this.$http.post('/videos', {
title: this.title,
description: this.description,
visibility: this.visibility,
extension: this.file.name.split('.').pop()
}).then((response) => {
this.uid = response.json().data.uid;
});
},
update() {
this.saveStatus = 'Saving changes.';
return this.$http.put('/videos/' + this.uid, {
link: this.link,
title: this.title,
description: this.description,
visibility: this.visibility
}).then((response) => {
this.saveStatus = 'Changes saved.';
setTimeout(() => {
this.saveStatus = null
}, 3000)
}, () => {
this.saveStatus = 'Failed to save changes.';
});
},
updateProgress(e) {
e.percent = (e.loaded / e.total) * 100;
this.fileProgress = e.percent;
},
}
}
</script>
The problem is that on upload in my controller in the store function the request object is empty on the production server, which I got when I did dd($request->all()). Then it fails to find the video, in the network inspector I get a 404 error, which is returned by firstOrFail() method, because it can't find the Video model, since it is missing $request->uid.
No query results for model [App\Video].
This is the controller:
class VideoUploadController extends Controller
{
public function index()
{
return view('video.upload');
}
public function store(Request $request)
{
$player = $request->user()->player()->first();
$video = $player->videos()->where('uid', $request->uid)->firstOrFail();
$request->file('video')->move(storage_path() . '/uploads', $video->video_filename);
$this->dispatch(new UploadVideo(
$video->video_filename
));
return response()->json(null, 200);
}
}
I am not sure what is going on, since on inspecting the network tab in the console I am sending a request payload that looks like this:
------WebKitFormBoundarywNIkEqplUzfumo0A Content-Disposition: form-data; name="video"; filename="Football Match Play.mp4" Content-Type: video/mp4
------WebKitFormBoundarywNIkEqplUzfumo0A Content-Disposition: form-data; name="uid"
159920a7878cb2
------WebKitFormBoundarywNIkEqplUzfumo0A--
It is really frustrating since I have no idea how to fix this, when everything is fine on my local machine, not sure what is wrong on the production server and how to fix it?
Update
It started working again on its own, since I have created a ticket in the Namecheap support service, all of sudden after one day, it started working again, I haven't done any changes, and on asking the Namecheap support I have got an answer from them that they haven't done any changes either, so I have no idea what went wrong, which is still a bit frustrating since I would like to avoid that in the future, but at least everything is working now again as it should.
Laravel's $request->all() method only pulls from the input, thus in the store() method of VideoUploadController $request->all() return empty object.
In your script, it calls this.store() after checked the isVideo is true while file changes. As a result, there isn't a uid parameter or video parameter.
I use Dropzone image uploader. This plugin fortunately uploaded my images in binary format and I like it. And also move file uploaded in my "/upload" folder perfectly but without extension. I need to show the image uploaded, from upload folder in my HTML page by different extension. This is a screenshot of my upload folder:
How can I read and show the images by their address in tag?
The upload folder is 2 previews directory from my HTML file. How can I address in ?
This is my HTML uploader:
<meta name="csrf-token" content="XYZ123">
<form id="upload-widget" method="post" action="/upload" class="dropzone">
<div class="fallback">
<input name="file" type="file" />
</div>
</form>
This is my Dropzone uploader js code:
Dropzone.options.uploadWidget = {
paramName: 'file',
maxFilesize: 2, // MB
maxFiles: 1,
dictDefaultMessage: 'Drag an image here to upload, or click to select one',
headers: {
'x-csrf-token': document.querySelectorAll('meta[name=csrf-token]')[0].getAttributeNode('content').value,
},
acceptedFiles: 'image/*',
init: function() {
this.on('success', function( file, resp ){
console.log( file );
console.log( resp );
});
this.on('thumbnail', function(file) {
if ( file.width < 20 || file.height < 20 ) {
file.rejectDimensions();
} else {
file.acceptDimensions();
}
});
},
accept: function(file, done) {
file.acceptDimensions = done;
file.rejectDimensions = function() {
done('The image must be at least 20 x 20')
};
}
};
And I didn`t use my controllers for this.
Controller (snippet, as its very large):
function loadImages() {
if (Authentication.loggedIn()) {
Images.get(function(s) {
$scope.images = s.images;
},
function(f) {} );
}
}
$scope.loginOnSubmit = function() {
Authentication.login($scope.model.login).then(
function(okResponse) {
WizardHandler.wizard().next();
loadImages()
},
function(failureResponse) {
// TODO: Alert use with the failure
}
);
};
loadImages();
View:
<wz-step title="{{ strings.images.bullet }}">
<div class="row">
<div class="col-md-10">
<h1 ng-bind="strings.images.title"></h1>
<p ng-bind="strings.images.description"></p>
<div ui-grid="{ data: images }" class="imagesGrid"></div>
</div>
</div>
</wz-step>
using angular-wizard wizard plugin (wz-step directive..).
I've looked over the web, thought its related to the $digest, etc.
I've tried using $timeout(...) to overcomes this, no go.
what happens is, the data doesnt show up in the view, but when I refresh the page, then its there, or when I even re-size the page, it appears.
I just started looking into dropzone.js Is it possible to somehow modify the previewTemplate area to add additional info about the files uploaded and then submit the form to an mvc method?
For simplicity I want to add two fields DocumentTypeID and ExpirationDate for each file that a user wants to upload
#model MyProject.Model.Document
#using (Html.BeginForm("Create", "Document", FormMethod.Post, new { enctype = "multipart/form-data", #class = "dropzone", #id = "my-awesome-dropzone" }))
{
<div class="row-fluid">
<fieldset class="span6">
<div class="editor-label">
#Html.LabelFor(model => model.DocumentTypeID, "DocumentType")
</div>
<div class="editor-field">
#Html.DropDownList("DocumentTypeID", String.Empty)
#Html.ValidationMessageFor(model => model.DocumentTypeID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ExpirationDate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ExpirationDate)
#Html.ValidationMessageFor(model => model.ExpirationDate)
</div>
</fieldset>
<div class="span6"> <div class="dropzone-previews"></div> </div>
</div>
}
Here is the controller method which for now should accept one file at a time
[HttpPost]
public ActionResult Create(Document document, HttpPostedFileBase file)
{
if (ModelState.IsValid && file != null)
{
db.Documents.Add(document);
document.FilePath = ProcessDocumentUpload(Request.Files[0], document.DocumentID);
db.SaveChanges();
return "";//? // not sure what to return yet
}
}
Now the Js function for dropzone
<script type="text/javascript">
$(function () {
// "myAwesomeDropzone" is the camelized version of the HTML element's ID
Dropzone.options.myAwesomeDropzone = {
autoDiscover: false,
paramName: "file", // The name that will be used to transfer the file
maxFilesize: 5, // MB
maxFiles: 1, //for now upload one at a time
//I started looking at the template and added two elements as an experiment.
previewTemplate: "<div class=\"dz-preview dz-file-preview\">\n <div class=\"dz-details\">\n <div class=\"dz-filename\"><span data-dz-name></span></div>\n <div class=\"dz-size\" data-dz-size></div>\n <img data-dz-thumbnail />\n </div>\n <input type=\"text\" data-dz-doc-expiration-date class=\"dz-doc-input\" />\n <select class=\"dz-doc-input\" data-dz-doc-document-type-id ></select>\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress></span></div>\n <div class=\"dz-success-mark\"><span>✔</span></div>\n <div class=\"dz-error-mark\"><span>✘</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
//dictDefaultMessage: "Drop files here to upload or click",
// The configuration that allows the whole form to be submitted on button click
autoProcessQueue: false,
uploadMultiple: false,
parallelUploads: 1,
addRemoveLinks: true,
previewsContainer: ".dropzone-previews", //show a preview in another place
// The setting up of the dropzone
init: function () {
var myDropzone = this;
// First change the button to actually tell Dropzone to process the queue.
$("input[type=submit]").on("click", function (e) {
// Make sure that the form isn't actually being sent.
e.preventDefault();
e.stopPropagation();
myDropzone.processQueue();
});
// Listen to the sendingmultiple event. In this case, it's the sendingmultiple event instead
// of the sending event because uploadMultiple is set to true.
this.on("sendingmultiple", function () {
// Gets triggered when the form is actually being sent.
// Hide the success button or the complete form.
});
this.on("successmultiple", function (files, response) {
// Gets triggered when the files have successfully been sent.
// Redirect user or notify of success.
});
this.on("errormultiple", function (files, response) {
// Gets triggered when there was an error sending the files.
// Maybe show form again, and notify user of error
});
},
accept: function (file, done) {
//maybe do something here for showing a dialog or adding the fields to the preview?
}
};
});
</script>
Thanks for looking!
have you tried to handle the event 'sending'?
$dropzone.on('sending', function (file, xhr, formData) {
formData.append('id', $id);
});
MVC controller
public JsonResult UploadImage(string id)
{
for (int i = 0; i < Request.Files.Count; i++)
{
HttpPostedFileBase file = Request.Files[i];
...
}
return Json(true, JsonRequestBehavior.DenyGet);
}
I've been using also MVC 4 and it has worked well.