aaronstacy
aaronstacy

Reputation: 6428

Object from comprehension in CoffeeScript [dict/hash comprehensions]

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: ''
}

Upvotes: 43

Views: 7986

Answers (8)

Jay Janssen
Jay Janssen

Reputation: 131

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!

Upvotes: 1

reubano
reubano

Reputation: 5363

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

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

Upvotes: 7

Tobia
Tobia

Reputation: 18811

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; @

Upvotes: 3

Sylvain Leroux
Sylvain Leroux

Reputation: 52030

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.

Upvotes: 4

Samuel
Samuel

Reputation: 93

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 } }

Upvotes: 2

tokland
tokland

Reputation: 67900

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

form_values = ->
  _($('input, textarea, select')).mash f -> [f.name, f.value]

Upvotes: 7

matyr
matyr

Reputation: 5774

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'

Upvotes: 26

Trevor Burnham
Trevor Burnham

Reputation: 77416

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.

Upvotes: 23

Related Questions