Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get arrays/objects sent via form in sails.js (with enctype multipart/form-data)

I'm nesting information in my forms to match my models, this greatly simplify things on the backend, but I can't find out how to get arrays or objects (or the combination of both) in Sails.js

Assuming I have a form like this

NOTE: Support for "multipart/form-data" is totally required.

<form action="/articles" method="post" enctype="multipart/form-data">
    <input type="file" name="status" value="published">
    <!-- Entry 0 -->
    <input(type="text" name="entries[0][title]" value="Entry 1")
    <input(type="text" name="entries[0][content]" value="Entry 1 Content...")
    <!-- Entry 1 -->
    <input(type="text" name="entries[1][title]" value="Entry 2")
    <input(type="text" name="entries[1][content]" value="Entry 2 Content...")
    <!-- images -->
    <input type="file" name="images[]">
    <input type="file" name="images[]">
</form>

Im expecting to get an object like this in the req.params.all() obj

{
 status: 'published',
 entries: [
   {title: 'Entry 1', content: 'Entry 1 Content...'},
   {title: 'Entry 2', content: 'Entry 2 Content...'}
 ]
}

Now when calling req.params.all()/req.body what I'm getting instead is:

{
 status: 'published',
 'entries[0][title]': 'Entry 1'
 'entries[0][content]': 'Entry 1 Content...'
 'entries[1][title]': 'Entry 2'
 'entries[1][content]': 'Entry 2 Content...'
 'entries[0][title]': 'Entry 1'
}

Calling req.file('images[]') gives the correct behavior. I'm checking the ._files property of what is returned by that fn and shows my 2 images in there. Using the brackets here seems really weird, but that's what it is.

I guess that related to what I'm getting with req.params.all() I can further parse this, but it'll be hacky and brittle if something changes in the future. In any case this is a common pattern in any web app and supported by many languages and frameworks, so to me is really odd that it seems impossible to get what I need with just plain sails.js functionality, so i'm guessing that I'm not doing something as it should or that I'm missing something. Please point me out in the right direction, and if actually Sails does not support this basic nesting behaviour, then how should I proceed?

Sending raw content via Javascript is not an option here (Unless is impossible otherwise) as suggested in the third answer in this question: Is it possible in Sailsjs to build more complex models Doing it this way, at least for the text based fields I get the correct output, not sure with images, since I've tested via postman with rawdata.

Edit: So far i've tried changing the skipper body parser in config/http.js like this:

bodyParser: {
   fn: require('body-parser').urlencoded,
   options: {extended:true}
 }

But that made my server useless, it did start, but it didn't respond to any request, not sure why (Even using their our example with skipper, that you just have to uncomment, does not work).

Since skipper is based on bodyparser, I modified the skipper module index.js just to test what happens.

var URLEncodedBodyParser = connect.urlencoded({extended:true})

But it didn't work, I get the same result as the begining, even installing body-parser and using it instead of the connect.urlencoded body parser had no effect.

Edit 2: As @robertklep stated using form-data without multipart works, but of course I lose the ability to upload files, which is really important and the reason I put it in the example form.

Edit 3: Just to complement the accepted answer in case someone needs it, this is what I did:

In config/http.js

middleware: {  
     order: [
      // some middleware
      'bodyParser',
      'qsBodyParser',
      // more middleware
    ],
    qsBodyParser: require('../api/middleware/qsBodyParser')
}

And in api/middleware/qsBodyParser

Qs = require('qs');

qsBodyParser = function(req, res, next) {
  if req.is('multipart/form-data'){
   req.body = Qs.parse(req.body);
  }
  return next();
};

module.exports = qsBodyParser;
like image 821
Lu Roman Avatar asked Aug 13 '15 03:08

Lu Roman


1 Answers

The currect version of skipper depends on [email protected], which depends on [email protected], which doesn't handle the way you are sending form arrays/objects.

Your form example comes out like this (using extended : true):

{
  "entries" : [{
    "content" : "Entry 1 Content..."
  }, {
    "content" : "Entry 2 Content..."
  }]
}

The latest version ([email protected]) works as expected, so you have to plug that into skipper somehow.

EDIT: this comment seems to suggest that using multipart/form-data will disable array(/object?) parsing altogether.

EDIT #2: you can manually parse req.body using qs, which seems to accept an object as argument:

var qs  = require('qs');

var obj = qs.parse({
 status: 'published',
 'entries[0][title]': 'Entry 1',
 'entries[0][content]': 'Entry 1 Content...',
 'entries[1][title]': 'Entry 2',
 'entries[1][content]': 'Entry 2 Content...',
});

// obj is now:
// { status: 'published',
//   entries:
//    [ { title: 'Entry 1', content: 'Entry 1 Content...' },
//      { title: 'Entry 2', content: 'Entry 2 Content...' } ] }
like image 71
robertklep Avatar answered Oct 13 '22 01:10

robertklep