So I have been trawling the net for a day now trying to find a complete example of how to upload an image along with a normal POST (create) request from a backbone model. So after some initial digging around I discovered the FileReader api in HTML5 - After some testing I had this working great outside backbone by creating a XMLHttpRequest()
The problem im now trying to solve is how can I make bacbone send the FILES data along with the POST request like you would experience in a normal multipart form work flow. Im pretty new to backbone so please forgive any obvious errors. Heres what I have so far.
model
define(
[
'backbone'
],
function (Backbone) {
var Mock = Backbone.Model.extend({
url: '/api/v1/mocks',
idAttribute: '_id',
readFile: function(file){
var reader = new FileReader();
self = this;
reader.onload = (function(mockFile){
return function(e){
self.set({filename: mockFile.name, data: e.target.result});
};
})(file);
reader.readAsDataURL(file);
}
});
return Mock;
}
);
view
define(
[
'jquery',
'backbone',
'underscore',
'text!../templates/create_mock-template.html',
'models/mock',
'collections/mocks'
],
function ($, Backbone, _, createMockTemplate, Mock, mocksCollection) {
var CreateMockView = Backbone.View.extend({
template: _.template(createMockTemplate),
events: {
'submit form': 'onFormSubmit',
'click #upload-link': 'process',
'change #upload': 'displayUploads'
},
initialize: function () {
this.render();
return this;
},
render: function () {
this.$el.html(this.template);
},
process: function(e){
var input = $('#upload');
if(input){
input.click();
}
e.preventDefault();
},
displayUploads: function(e){
// Once a user selects some files the 'change' event is triggered (and this listener function is executed)
// We can access selected files via 'this.files' property object.
var formdata = new FormData();
var img, reader, file;
var self = this;
for (var i = 0, len = e.target.files.length; i < len; i++) {
file = e.target.files[i];
if (!!file.type.match(/image.*/)) {
if (window.FileReader) {
reader = new FileReader();
reader.onloadend = function (e) {
self.createImage(e.target.result, e);
};
self.file = file;
reader.readAsDataURL(file);
}
}
}
},
createImage: function(source, fileobj) {
var image = '<img src="'+source+'" class="thumbnail" width="200" height="200" />'
this.$el.append(image);
},
onFormSubmit: function (e) {
e.preventDefault();
// loop through form elements
var fields = $(e.target).find('input[type=text], textarea');
var data = {};
// add names and vlaues to data object
$(fields).each(function(i, f) {
data[$(f).attr('name')] = $(f).attr('value');
});
// create new model from data
var mock = new Mock(data);
// this should set the filename and data attributes on the model???
mock.readFile(this.file);
// save to the server
mock.save(null, {
wait: true,
error: function(model, response) {
console.log(model)
},
success: function(model, response) {
mocksCollection.add(model);
console.log(model, response);
}
});
},
});
return CreateMockView;
}
);
I totally appreciate this may all be a bit hand wavey, its more a proof of concept than anything else and a good chance to learn backbone too. So the crux of my question is this
When the request is sent by backbone to the api why arent I seeing the data and filename attrs in the request to the node/express server
Is what im trying to do even possible, I basically thought i'd be able to read the data attr and create and image on the server?
Is there a way to perhaps overide the sync method on the Mock model and create a properly formed request where POST and FILES are correctly set.
Im sure this is possible but I just cant seem to find the bit of info I need to plough on and get this working.!
Hope someone can help!
CHEERS
edit
Just wanted to provide a bit more info as by my understanding and some brief chats on the document cloud irc channel this should work. So when I call
mock.readFile(this.file)
the fileName and data attributes dont seem to get set. In fact a console log here dosent even seem to fire so im guessing this could be the problem. I would be happy with this approach to basically build up the Image on the node end based on the value of data and fileName. So why arent these properties being set and passed along in the post request to the api?
I have resolved this situation splitting the Model creation in two steps:
Basic information
Assets
For example if my Model is a Film, I show a create-form that only include title and synopsis. This information is sent to the server and create the Model. In the next step I can show the update-form and now is very easier to use File Uploads plugins like:
jQuery File Upload Very profesional and stable
BBAssetsUpload Very beta, but based on Backbone, and you can say you know the programmer ;P
You can also check their source code for references to try to achieve a one-step Backbone Model with File creation solution.
jquery-file-upload-middleware for express is probably worth mentioning.
Related
I would like to enable pagination and I'm torn between client side and server side pagination. In the long term (more data) it is probably better to do server side pagination, but I haven't found a good tutorial on it.
I use Angular/Express/Mongo. I have the Boostrap UI in use, and would like to use their pagination directive for pagination. I have read some articels on how to kind of do it, but they are outdated and I cannot get it to work. http://fdietz.github.io/recipes-with-angular-js/common-user-interface-patterns/paginating-through-client-side-data.html
Could anybody help me get that example to work with Bootstrap UI for Angular?
If you have a set number of items per page, you could do it this way :
Define an angular service to query the data on your server.
.factory('YourPaginationService', ['$resource',
function($resource) {
return $resource('baseUrl/page/:pageNo', {
pageNo: '#pageNo'
});
}
]);
Call it via the angular controller. Don't forget to inject your service, either globally or in the controller.
$scope.paginationController = function($scope, YourPaginationService) {
$scope.currentPage = 1;
$scope.setPage = function (pageNo) {
$scope.currentPage = pageNo;
YourPaginationService.query({
pageNo: '$scope.currentPage'
});
};
};
On express 4 (if you have it), set up your route.
app.route('/articles/page/:pageNo')
.get(data.listWithPagination) //random function name
Then you need to wire that function with the desired Mongo request in your Node controller. If you have Mongoose, it works like this :
exports.listWithPagination = function(req, res) {
var pageLimit = x; //Your hardcoded page limit
var skipValue = req.params.pageNo*pageLimit;
YourModel.find() //Your Mongoose model here, if you use Mongoose.
.skip(skipValue)
.limit(pageLimit)
.exec(function(err, data) {
if (err) {
return res.send(400, {
message: getErrorMessage(err)
});
} else {
res.jsonp(data);
}
});
};
That's how I would do it on a typical MEAN stack. If you're working with different libraries/technologies, you might need to adapt a few things.
I have tried to find a solution for the following but it does not seem to be obvious for a beginner. I am integrating a form upload using backbone and express. The form consists of a user information and a file upload. The idea is to upload the file and persist the user model to a database with a field referencing to a file.
Uploading a file using express can be easily implemented sending the form through POST with the proper HTML tags (<form method='post' action='/upload' enctype='multipart/form-data'>) and a simple handler like it is done in http://shivalibari.com/blog/2014/02/file-upload-using-node/.
In my case, however, I use a backbone view to handle the submit event and use save() to persist the model making a POST to express:
submit: function(e){
e.preventDefault();
var formData = {};
console.log("submit form");
// read form...
$('.form-group').children('input').each(function(i,el){
if( $( el ).val() != '' )
{
formData[ el.id ] = $( el ).val();
}
});
this.user = new User();
this.user.save(formData);
}
as simplified version of the POST handler...
var app = express();
app.post( '/api/users', function( request, response) {
var user = new UserModel({
name: request.body.name,
email: request.body.email,
file: request.body.file
});
user.save( function( err ) {
if( !err ) {
return console.log( 'created' );
} else {
return console.log( err );
}
return response.send( user );
});
}
Any ideas of how to handle the file upload maybe before, after or during model persistence?
I'd like to minimize the use of plugins unless strictly necessary using node.js.
I found my own answer in the following blog post: http://estebanpastorino.com/2013/09/27/simple-file-uploads-with-backbone-dot-js/. The solution consists on using the iframe trick to pass the file objects to the server and can be easily implemented using a jquery plugin called jquery.iframe-transport.
This jquery plugin makes the file objects available on the server in the request.files attribute, which can be used to be read/write files as explained in http://shivalibari.com/blog/2014/02/file-upload-using-node/.
In my implementation I end up using two different models for the user profile and the files. I first synced the file model, and then used a success callback to sync the user profile model.
I have the following (simplified for example) angular directive which creates a dropzone
directives.directive('dropzone', ['dropZoneFactory', function(dropZoneFactory){
'use strict';
return {
restrict: 'C',
link : function(scope, element, attrs){
new Dropzone('#'+attrs.id, {url: attrs.url});
var myDropZone = Dropzone.forElement('#'+attrs.id);
myDropZone.on('sending', function(file, xhr, formData){
//this gets triggered
console.log('sending');
formData.userName='bob';
});
}
}
}]);
As you can see the the sending event handler I'm trying to send the username ("bob") along with the uploaded file. However, I can't seem to retrieve it in my route middleware as req.params comes back as an empty array (I've also tried req.body).
My node route
{
path: '/uploads',
httpMethod: 'POST',
middleware: [express.bodyParser({ keepExtensions: true, uploadDir: 'uploads'}),function(request,response){
// comes back as []
console.log(request.params);
//this sees the files fine
console.log(request.files);
response.end("upload complete");
}]
}
Here is what the docs say on the sending event
Called just before each file is sent. Gets the xhr object and the formData objects as second and third parameters, so you can modify them (for example to add a CSRF token) or add additional data.
EDIT
I dropped the programmatic approach for now. I have two forms submitting to the same endpoint, a regular one with just post and a dropzone one. Both work, so I don't think it's an issue with the endpoint rather with how I handle the 'sending' event.
//Receives the POST var just fine
form(action="http://127.0.0.1:3000/uploads", method="post", id="mydz")
input(type="hidden", name="additionaldata", value="1")
input(type="submit")
//With this one I can get the POST var
form(action="http://127.0.0.1:3000/uploads", method="post", id="mydz2", class="dropzone")
input(type="hidden", name="additionaldata", value="1")
OK, I've actually figured it out, thanks to Using Dropzone.js to upload after new user creation, send headers
The sending event:
myDropZone.on('sending', function(file, xhr, formData){
formData.append('userName', 'bob');
});
As opposed to formData.userName = 'bob' which doesn't work for some reason.
I would like to add to NicolasMoise's answer.
As a beginner in webdev I got stuck on how to obtain an instance of Dropzone. I wanted to retrieve an instance of Dropzone that had been generated by the autodiscovery feature. But it turns out that the easiest way to do this is to manually add a Dropzone instance after first telling Dropzone not to auto-discover.
<input id="pathInput"/>
<div id="uploadForm" class="dropzone"/>
<script>
$(document).ready(function(){
Dropzone.autoDiscover = false;
var dZone = new Dropzone("div#uploadForm", {url: "/api/uploads"});
dZone.on("sending", function(file, xhr, data){
data.append("uploadFolder", $("#pathInput")[0].value);
});
});
</script>
Serverside the data will be in request.body.uploadFolder
Nicolas answer is one possible solution to the problem. It is especially useful if you need to alter the file object prior to sending.
An alternative is to use the params option:
var myDropzone = new Dropzone("div#myId",
{ url: "/file/post", params: { 'param_1': 1 }});
cf. the documention
For those that are using thatisuday/ng-dropzone the callback methods are done as such:
<ng-dropzone class="dropzone" options="dzOptions" callbacks="dzCallbacks" methods="dzMethods"></ng-dropzone>
In a controller:
$scope.dzCallbacks = {
sending: function(file, xhr, form) {
console.log('custom sending', arguments);
form.append('a', 'b');
}
};
I am trying to parse for a specific tag inside 2 websites.
I need to do that in an async way but to display the results to the user at the end.
I tried using htmlParser2 but I am not using it correctly.
For example how can I parse all the h2 tags from the html...
Here is a code I was trying before:
var htmlparser = require('htmlparser2');
var parser = new htmlparser.Parser({
onopentag: function(name, attribs){
if(name === "h2"){
//
}
},
ontext: function(text){
//
},
onclosetag: function(tagname){
if(tagname === "h2"){
//
}
}
});
parser.write(html_txt);
parser.end();
I am quite new to node.js so any help will be more than great.
Thanks
I'm trying to break a moustache template up into various components so I can reuse them, and get the assembled text returned through node.js. I cannot find anyone that has done this.
I can return must ache pages imply with:
function index(request, response, next) {
var stream = mu.compileAndRender('index.mu',
{name: "Me"}
);
util.pump(stream, response);
}
I just cannot figure out how to render a template and use it in another template. I've tried rendering it separately like this:
function index(request, response, next) {
var headerStream = mu.compileAndRender('header.mu', {title:'Home page'});
var headerText;
headerStream.on('data', function(data) {
headerText = headerText + data.toString();
});
var stream = mu.compileAndRender('index.mu',
{
heading: 'Home Page',
content: 'hello this is the home page',
header: headerText
});
util.pump(stream, response);
}
But the problem is that the header is not rendered before the page and even if I get that to happen. The header is seen as display text rather than html.
Any help appreciated.
You need to put the lines var stream = ... and util.pump(... in headerStream.on('end', function () { /* here */ });, so that they are ran after the headerStream has sent all the data to your data listener.
To include raw HTML you have to use the triple braces: {{{raw}}} in your template as http://mustache.github.com/mustache.5.html states.
The moustache guys came back with the answer. I can do it by using partials like this:
{{> index.mu}}
Inside the moustache template rather than trying to do it in node.js.