Backstory
I'm building an online marketplace for used cars where owners can list their cars for sale and buyers can find affordable used cars more easily.
I'm using Sails.js v1.0 as my app's Node.js framework.
Challenge
I want users to be able to add images to each car they've listed, Craigslist-style.
Current Implementation
Here's what I have thus far...
In images.ejs view file I have an ajax-form element:
<ajax-form class="" action="images" :syncing.sync="syncing" :cloud-error.sync="cloudError" #submitted="submittedForm()" :handle-parsing="handleParsingForm" enctype="multipart/form-data">
<div class="form-outer flex justify-center items-center h-100 bt bw2">
<div class="form-inner bg-yellow pa3 w-100">
<div class="mb3 w-100">
<label class="db tl mv2 f4" for="upload">Upload</label>
<input class="pv3" id="upload" type="file" :class="[formErrors.upload ? 'is-invalid' : '']" v-on:change>
<div class="bg-light-red light-gray pa2 br2 mt1" v-if="formErrors.upload">Please select at least one image.</div>
</div>
<p class="bg-light-red light-gray pa2 br2 mt1" v-if="cloudError"><small>An error occured while processing your request. Please check your information and try again, or contact support if the error persists.</small></p>
<div class="">
<ajax-button :syncing="syncing" class="bg-green pa2 ba b--dark-gray br2 bw1 b circular-bold">Add images.</ajax-button>
</div>
</div>
</div>
</ajax-form>
This form submits to the controller at POST: cars/images.js:
module.exports = {
friendlyName: 'Images',
description: 'Allow users to upload images of car.',
inputs: {
},
exits: {
},
fn: async function (req, res) {
req.file('upload').upload({
// don't allow the total upload size to exceed ~10MB
maxBytes: 10000000
},function whenDone(err, uploadedFiles) {
if (err) {
return res.serverError(err);
}
// If no files were uploaded, respond with an error.
if (uploadedFiles.length === 0){
return res.badRequest('No file was uploaded');
}
// Get the base URL for our deployed application from our custom config
// (e.g. this might be "http://foobar.example.com:1339" or "https://example.com")
var baseUrl = sails.config.custom.baseUrl;
// Save the "fd" and the url where the upload for a user can be accessed
User.update(req.session.userId, {
// Generate a unique URL where the upload can be downloaded.
uploadUrl: require('util').format('%s/user/upload/%s', baseUrl, req.session.userId),
// Grab the first file and use it's `fd` (file descriptor)
uploadFd: uploadedFiles[0].fd
})
.exec(function (err){
if (err) return res.serverError(err);
return res.ok();
});
});
}
};
This controller/sails action is using the standard req/res objects within the Sails.js actions2 format.
All of the Sails documentation on file uploads points to the Sails body-parser implementation called Skipper, but I feel the documentation is lacking on the new actions2 syntax.
Can anyone point me to a concrete example of implementing this file upload feature, especially for uploading multiple image files?
Related
I'm try to create an app that take a image using drag and drop method, and immediately do the action specified in the form that containing it.
index.ejs
<form class="form"
id="form"
method="POST"
action="/images/upload" <-- Llamar a esta acción
enctype="multipart/form-data"
#dragover.prevent
v-cloak #drop.prevent="addFile"
>
</form>
I tried this way, the result is capture the object but I don't know how to send the specified action.
index.ejs
var app = new Vue({
el: '#form',
methods:{
addFile(e) {
file = e.dataTransfer.files[0]
console.log(file)
/// Llamar a action
},
})
Finally, it is the rout that is managed the action form.
router.post('/images/upload', (req, res) => {
uploadImage(req, res, (err) => {
if (err) {
err.message = 'The file is so heavy for my service';
return res.send(err);
}
console.log(req.file);
res.send('uploaded');
});
});
Thanks for your assist.
You can use #submit.prevent for calling a method for your action.
Now for your question part:
As soon as you successfully drag and drop the image, either you can click the submit button with the help of jQuery or refs dynamically and call the action method or you can direct call the method of your action.
<template>
<div>
<form class="form" id="form" method="POST" #submit.prevent="YOUR_METHOD_GOES_HERE" enctype="multipart/form-data" #dragover.prevent v-cloak #drop.prevent="addFile">
</form>
</div>
</template>
<<script>
export default {
methods: {
YOUR_METHOD_GOES_HERE(){
// place your action logic here
}
},
}
</script>
I got stuck while uploading updating user avatar on my website. I'm using angular and need to upload image using form and get base64 encoding of this image to pass as a parameter into my backend.
onUploadFinished(event){
const avatar ={
avatar:event.file
}
console.log(avatar)
//validate avatar
if(!this.validateService.validateAvatarUpdate(avatar.avatar)){
this.flashMessage.show('Please, select a new avatar', {cssClass: 'alert-danger', timeout:3000});
return false;
}
this.updateService.updateAvatar(avatar).subscribe(data=>{
console.log()
if(data/*.success*/){ //commented cause response doesnt have property success but still registering user without displaying the flash message
this.flashMessage.show('You successfuly changed your avatar', {cssClass: 'alert-success', timeout:3000});
this.router.navigate(['/profile'])
this.authService.getProfile().subscribe(profile=>{
this.user = profile.user;
},
err=>{
console.log(err);
return false;
});
}else{
this.flashMessage.show('Something went wrong', {cssClass: 'alert-danger', timeout:3000});
this.router.navigate(['/profile'])
}
});
}
I was trying to use ng2-image-upload here is html
<h6>Upload new photo...</h6>
<!-- <form (submit)="onUserAvatarSubmit()" enctype="multipart/form-data">
<input type="file" name="avatar" class="text-center center-block well well-sm">
<p></p>
<input class="btn btn-primary" value="Upload" type="submit">
</form> -->
<image-upload name="avatar" [class]="'customClass'"
[headers]="{Authorization: this.authToken}"
[buttonCaption]="'Choose an Image'"
[dropBoxMessage]="'or just drop them here'"
(uploadFinished)="onUploadFinished($event)"
[url]="''">
</image-upload>
Maybe anyone had such experience with uploading images, so give me some tips how to deal with it. Thanks here is a screenshot So you see when displaying this image we can see its base64 but I cant take it.
I have a basic Videogular video player setup to play videos from Firebase Storage. In the HTML view this works:
<div ng-controller="MyController as controller" class="videogular-container">
<videogular vg-theme="controller.config.theme.url">
<vg-media vg-src="controller.config.sources" vg-native-controls="true"></vg-media>
</videogular>
</div>
In the controller this works:
var ref = firebase.database().ref(); // Create Firebase reference
var obj = $firebaseObject(ref.child($routeParams.id)); // get the record with the key passed in from the URL
var controller = this; // controller refers to the controller object
obj.$loaded( // wait until the async data loads from the remote Firebase
function(data) {
// video player
controller.config = { // provides an object to the controller
preload: "auto",
sources: [
// My Firebase video
{src: $sce.trustAsResourceUrl($scope.wordObject.videos[0].videoURL), type: "video/" + $scope.wordObject.videos[0].videoMediaFormat},
// The Videogular test videos
{src: $sce.trustAsResourceUrl("http://static.videogular.com/assets/videos/videogular.mp4"), type: "video/mp4"},
{src: $sce.trustAsResourceUrl("http://static.videogular.com/assets/videos/videogular.webm"), type: "video/webm"},
{src: $sce.trustAsResourceUrl("http://static.videogular.com/assets/videos/videogular.ogg"), type: "video/ogg"}
],
theme: {
url: "http://www.videogular.com/styles/themes/default/latest/videogular.css"
}
};
},
function(error) {
console.log("Error: ", error)
});
Everything works, to play one video. Now I want to dynamically access arrays of videos by theme. E.g., the user clicks to see all my cat videos or clicks another button to see all my dog videos. I have the Firebase Storage URLs on the $scope and ng-repeat prints out the URLs in the view:
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12 text-center">
<h3>{{currentTheme}}</h3>
<div>
<div ng-repeat="video in currentVideos">
{{video.videoURL}}
</div>
</div>
</div>
</div>
That works great too. So to spin out a series of video players with all my cat videos I just have to make an ng-repeat with a new video player for each video, with the vg-src coming from the $scope:
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12 text-center">
<h3>{{currentTheme}}</h3>
<div>
<div ng-repeat="video in currentVideos">
<div ng-controller="MyController as controller" class="videogular-container">
<videogular vg-theme="controller.config.theme.url">
<vg-media vg-src="{{video.videoURL}}" vg-native-controls="true"></vg-media>
</videogular>
</div>
</div>
</div>
</div>
</div>
That doesn't work. The error is Error: [$parse:syntax], meaning there's an Angular syntax error. The syntax error goes away when I change the vg-src back to vg-src="controller.config.sources":
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12 text-center">
<h3>{{currentWord}}</h3>
<div>
<div ng-repeat="video in currentVideos">
<div ng-controller="EnglishController as controller" class="videogular-container">
<videogular vg-theme="controller.config.theme.url">
<vg-media vg-src="controller.config.sources" vg-native-controls="true"></vg-media>
</videogular>
</div>
</div>
</div>
</div>
</div>
That works. The problem is that vg-src="controller.config.sources" works but vg-src="{{video.videoURL}}" doesn't work. Why can't Videogular source videos from the $scope?
I tried to put my video sources from the $scope onto controller.config in the controller but this never worked. Should I try to do this again tomorrow? (It's late and I'm getting confused trying to figure out why I can't put my video sources from the $scope onto controller.config in the controller.)
I wrote the question before I went to bed and woke up with (what I hope is) the answer. {{video.videoURL}} inserts the URLs of the videos. controller.config.sources inserts an object with a lot of stuff. I'll try making an array of configured objects and see what happens!
...
Yep, that worked! I wrote a tutorial for a Videogular minimum install, using the $scope instead of controller.config. I don't understand why the official How To Start tutorial uses controller.config instead of the $scope.
...
I can get the one video to play from my array of cat videos when the user clicks "Cat Videos" but I can't get ng-repeat to spin out all the videos in the array.
In the controller when the user clicks the "Cat Videos" button the handler accesses the array of cat videos on Firebase Storage, iterates through the array with forEach, for each video in the array it creates a variable for the videoSource and another variable for the video file format (videoSourceType), then makes a videoObject with an array of sources and a theme, then pushes the videoObject into the array $scope.videoObjects.
$scope.videoObjects = [];
$scope.showVideosOfTheme = function() {
theme.videos.forEach(function(video) { // iterate through the array of videos
var i = 0;
var videoSource = $scope.currentVideos[i].videoURL; // set the video source
var videoSourceType = $scope.currentVideos[i].videoMediaFormat; // set the video format
var videoObject = { // make a video object
preload: "auto",
sources: [
{src: $sce.trustAsResourceUrl(videoSource), type: "video/" + videoSourceType},
],
theme: {
url: "http://www.videogular.com/styles/themes/default/latest/videogular.css"
}
};
$scope.videoObjects.push(videoObject);
i++;
});
};
In the HTML view ng-repeat iterates through the array $scope.videoObjects and fdor each video object spins out a new Videogular video player using the theme and the sources. This doesn't work and the error message is Error: [$parse:syntax], in other words, an Angular syntax error.
<div ng-repeat="video in videoObjects" class="videogular-container">
<videogular vg-theme="{{video.theme.url}}">
<vg-media vg-src="{{video.sources}}" vg-native-controls="true"></vg-media>
</videogular>
</div>
I'll keep working on it!
I have a job application form as part of a site built on ServiceStack. I am attempting to use the included ss-utils.js to leverage built-in Fluent Validation, but my form doesn't post the user's file upload. Here's the relevant snippet of the form:
<form id="form-careerapplication" action="#(new CreateCareerApplication().ToPostUrl())" method="post">
<div class="error-summary"></div>
<div class="form-group">
<label>
Upload Resume
</label>
<input type="file" id="Resume" name="Resume" />
<span class="help-block"></span>
</div>
<input type="hidden" name="Id" value="#Model.Id" />
<input type="submit" value="Submit Application" />
</form>
<div id="success" class="hidden">
Thank you for your application.
</div>
$("#form-careerapplication").bindForm({
success: function (careerApplicationResponse) {
$("#form-careerapplication").addClass("hidden");
$("#success").removeClass("hidden");
},
...
Is there something I'm missing in ss-utils.js? Or is there a way of overriding / supplementing the submit behavior to use FormData?
Uploading files via a HTML FORM requires a enctype="multipart/form-data", e.g:
<form id="form-careerapplication" action="#(new CreateCareerApplication().ToPostUrl())"
method="post" enctype="multipart/form-data">
...
</form>
If you want to change support multiple file uploads or change the appearance of the UI Form I recommend the Fine Uploader, there's an example showing how to use Fine Uploader on the HTTP Benchmarks Example.
Whilst Imgur has a simple client HTML and Server example.
Turned out I can use the beforeSend option as part of the configuration passed into bindForm to override the data being sent. Its a bit of a hack, but it worked and I keep the original ss-utils.js fluent validation!
$("#form-careerapplication").bindForm({
success: function (careerApplicationResponse) {
....
},
error: function (error) {
....
},
contentType: false,
processData: false,
beforeSend: function (x, settings) {
var fd = new FormData();
// Tweaked library from https://github.com/kflorence/jquery-deserialize/blob/master/src/jquery.deserialize.js
// Used to translate the serialized form data back into
// key-value pairs acceptable by `FormData`
var data = $.fn.deserialize(settings.data);
$.each(data, function (i, item) {
fd.append(item.name, item.value);
});
var files = $('#form-careerapplication').find("input:file");
$.each(files, function (i, file) {
fd.append('file', file.files[0], file.files[0].name);
});
settings.data = fd;
}
});
None of the answers I have found anywhere have worked. I am trying to extend the example in "Developing Backbone.js Applications" to upload files. Although the form has enctype="multipart/form-data," request.files is always undefined.
The form HTML is:
<form id="addBook" action="..." enctype="multipart/form-data">
<div>
<label for="coverImage">CoverImage: </label><input id="coverImage" name="coverImage" type="file" />
<label for="title">Title: </label><input id="title" type="text" />
<label for="author">Author: </label><input id="author" type="text" />
<label for="releaseDate">Release date: </label><input id="releaseDate" type="text" />
<label for="keywords">Keywords: </label><input id="keywords" type="text" />
<button id="add">Add</button>
</div>
</form>
The backbone that saves the new record is
addBook: function( e ) {
e.preventDefault();
var formData = {};
var reader = new FileReader();
$( '#addBook div' ).children( 'input' ).each( function( i, el ) {
if( $( el ).val() != '' )
{
if( el.id === 'keywords' ) {
formData[ el.id ] = [];
_.each( $( el ).val().split( ' ' ), function( keyword ) {
formData[ el.id ].push({ 'keyword': keyword });
});
} else if( el.id === 'releaseDate' ) {
formData[ el.id ] = $( '#releaseDate' ).datepicker( 'getDate' ).getTime();
} else {
formData[ el.id ] = $( el ).val();
}
}
});
console.log(formData);
this.collection.create( formData );
}
The Node being called.
//Insert a new book
app.post( '/api/books', function( request, response ) {
console.log(request.body);
console.log(request.files);
});
The value of coverimage send to node is correct, I just never get anything in request.files. I have a cool drag and drop I would like to use instead, but until I get this working I am stuck.
I tried the JQuery-file-upload, that got me nowhere.
If I had hair, I would be pulling it out right now.
I wouldn't be submitting the file as part of the model.save/collection.create(model).
What I've used is Plupload for a file upload manager, submitting a file to an upload handler. This upload handler either returns the path to the uploaded file, or fileId if a reference is stored in a database table.
From there I populate a property in my backbone model, then persist the model. You can have your model listenTo plupload, for an upload completed event or similar.
I'm also following the sample of the book "Developing Backbone.js Applications", I extended the functionality to upload images to a folder in the server and save the path in my model to show the correct images. It is working fine. I tried to use Plupload and other jquery plugins but I didn't like them. My sample is using ajax to upload images to the server and then using them. I read many posts referencing the use of iframes to have ajax functionality. The best approach for this I found is using the jquery.form.js to avoid postbacks and load the images in a nice way.
The running sample working fine with nodeJS:
https://github.com/albertomontellano/bookLibrarySampleNodeJS
I based my solution in the post of Mark Dawson:
http://markdawson.tumblr.com/post/18359176420/asynchronous-file-uploading-using-express-and-node-js
However, I had to correct a method of this post to make it work correctly:
app.post('/api/photos', function(req, res) {
var responseServerPath = 'images/' + req.files.userPhoto.name;
var serverPath = 'site/images/' + req.files.userPhoto.name;
console.log(req.files.userPhoto.path);
require('fs').rename(
req.files.userPhoto.path,
serverPath,
function(error) {
if(error) {
console.log(error);
res.send({
error: 'Ah crap! Something bad happened'
});
return;
}
res.send({
path: responseServerPath
});
}
);
});
I hope it helps.
Turned out I had to end up hiring someone to do it, because I can't find any examples online of anybody uploading a file through backbone, even without updating any database interaction. Everybody has the same basic advice about what tools to use to upload the files, but I can't for the life of me find ONE example of someone implementing it.
I am planning on making the code available to everybody, so they can see how it works.