43

is there a way to return an object from a comprehension in coffeescript? something so that i could express this:

form_values = () ->
  ret = {}
  ret[f.name] = f.value for f in $('input, textarea, select')
  return ret

like this:

form_values = () -> f.name, f.value for f in $('input, textarea, select')

i'd like to construct a single object (not an array of objects). so if the markup looks something like this:

<form name=blah>
  <input type=text name=blah1 value=111 />
  <textarea name=blah2>222</textarea>
  <select name=blah3>
    <option value=333a>
    <option value=333b>
  </select>
</form>

the returned object would be something like this:

{
  blah1: '111',
  blah2: '222',
  blah3: ''
}
Jo Liss
  • 30,333
  • 19
  • 121
  • 170
aaronstacy
  • 6,189
  • 13
  • 59
  • 72
  • There many implementations of `.serializeObject` for jQuery: https://raw.github.com/cowboy/jquery-misc/master/jquery.ba-serializeobject.js – Ricardo Tomasi Nov 01 '11 at 01:14

8 Answers8

26
form_values = new ->
  @[f.name] = f.value for f in $ 'input, textarea, select'
  this

or

form_values = new class then constructor: ->
  @[f.name] = f.value for f in $ 'input, textarea, select'
matyr
  • 5,774
  • 28
  • 22
  • Late +1! This is very clever! – salezica Mar 22 '13 at 18:15
  • 9
    Clever, but completely unintelligible. Is there any way to make this more readable for the next generation? – relet Oct 15 '13 at 11:38
  • It works as a one-liner too: `form_values = new -> @[f.name] = f.value for f in $('input, textarea, select'); this` – Tobia Dec 09 '13 at 16:58
  • That's not a _one-liner_. You just put two statements on the same line using a semi-colon. – Carl Smith Oct 09 '17 at 14:39
  • This does not produce the same results as OP's example did; their `form_values` is a function that returns an object whilst yours _is_ that object. – cueedee Mar 04 '21 at 11:07
23

Nope. Comprehensions only return arrays in CoffeeScript. Search the issue tracker for object comprehensions, and you'll find several proposals, but none were found suitable.

Trevor Burnham
  • 76,828
  • 33
  • 160
  • 196
  • 19
    a pity, object-comprehensions or, at least, some kind of functional object constructor should be part of the core. – tokland Oct 31 '11 at 20:47
  • 4
    I came back to this same post to find that I already voted this up. I can't believe this is being ignored, it's super useful. – David Mar 10 '15 at 19:32
7

Using underscore's object function, you can do this:

form_values = _.object([f.name, f.value] for f in $('input, textarea, select'))
reubano
  • 5,087
  • 1
  • 42
  • 41
  • Either `_.object` is new or I've completely missed it all along. – tokland Mar 10 '15 at 20:23
  • If you are using [lodash](https://lodash.com/), then use the [`fromPairs`](https://lodash.com/docs#fromPairs) function instead. – reubano Aug 22 '16 at 10:47
7

Check the functional library underscore and the extension _.mash from this mixin:

form_values = ->
  _($('input, textarea, select')).mash f -> [f.name, f.value]
tokland
  • 66,169
  • 13
  • 144
  • 170
4

This has already been answered but probably lack of some explanations as this idiom is rather cryptic at first sight:

form_values = (new -> @[f.name] = f.value for f in $ 'input, textarea, select'; @)
//             ^^^  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^  
//           create                      with                                   |
//           a new                       that                                   |
//           empty                     anonymous                                |
//           object                   constructor                               |
//                                                                don't forget -/
//                                                                to return the
//                                                             newly created object

The key idea is to create an empty object (new) with an anonymous constructor (-> ...) that will create the various fields.

Sylvain Leroux
  • 50,096
  • 7
  • 103
  • 125
3

CoffeeScript's creator suggests using a helper function to convert an array of pairs into an object:

form_values = toObject([f.name, f.value] for f in $('input, textarea, select'))

This is arguably the most readable way of doing it, within the current language syntax. It's also very similar to how Python and other languages do it, except for the missing syntactic sugar.

The helper function can be easily written once, using for example the technique from @matyr's and @Sylvain's answers:

// Create a new object from an array of [key, value] pairs.
toObject = (pairs) ->
    new -> @[key] = value for [key, value] in pairs; @
Tobia
  • 17,856
  • 6
  • 74
  • 93
  • This function nets the same result as using lo-dash's `fromPairs`... `pairs = ([k,v*2] for k,v of {one: 1, two: 2 })` `object = _.fromPairs pairs` -> `{ one: 2, two: 4 }` – Alex Gray Jun 27 '18 at 15:40
2

I believe you can do this with no added libraries right in CoffeeScript.

It should be something to the effect of:

$('input, textarea, select').each (item) => @form_values || @form_values = {}; @form_values[$(item).name] = $(item).value

You could simplify the syntax of that by pre-creating the form_values:

form_values = {}
$('input, textarea, select').each (item) -> form_values[$(item).name] = $(item).value

Here is a lengthier response with canned examples:

Take a very simple example where you wanted to map the obj to name value:

items = [ { a: 1 }, { b: 2 }, { c: 3 } ]
items.map((item) -> {name: Object.keys(item)[0], value: item[Object.keys(item)[0]]})

[ { name: 'a', value: 1 }, { name: 'b', value: 2 }, { name: 'c', value: 3 } ]

Note that the above is not really an Object comprehension, just demonstrating an example.

Now let's say there is a bit more structure and you just want to map a known unique key:

items = [{key: "abc", someVar: 1}, {key: "def", someVar: 2}]

In Python you'd do something simple like this: {x['key']:x for x in items}

In CoffeeScript you can get all of this down to one single line though with a caveat:

items.forEach (item) => @x || @x = {}; @x[item['key']] = item

{ abc: { key: 'abc', someVar: 1 }, def: { key: 'def', someVar: 2 } }

In the above code x was not previously defined in the scope, so using => and @ allowed us to bind x with the @x || @x = {} if not previously found, then set the key.

If you don't want to use => and @ you have to define x beforehand:

x = {}
items.forEach (item) => x || x = {}; x[item['key']] = item

{ abc: { key: 'abc', someVar: 1 }, def: { key: 'def', someVar: 2 } }

Samuel
  • 93
  • 1
  • 4
1

Not to beat a dead horse, but I personally thing this is readable and satisfies the 'one line' requirement without needing extra modules:

form_values = {}; form_values[f.name] = f.value for f in $('input, textarea, select')

Don't forget you can still use a semi-colon to combine lines in Coffeescript!

Jay Janssen
  • 131
  • 1
  • 6