Thursday, February 28, 2013

Would you like some CoffeScript?

I am not sure.

I grew up on white space agnostic languages - C, C#, Java, and, yes, JavaScript among others. Languages attaching significance to tabs and spaces took me some using to, but I learned to appreciate the clarity and conciseness of languages like F# and Ruby.

Now, CoffeScript, well... I would really love to use it instead of the JavaScript. The syntax seems to be friendlier - more concise with much  less noise (brackets, keywords etc.) necessary to express the same concepts.


CoffeScript

JavaScript
# Function:
square = (x) -> x * x
// Function
square = function(x) {
  return x * x;
};
# Object:
kids =
  brother:
    name: "Max"
    age:  11
  sister:
    name: "Ida"
    age:  9
// Object:
kids = {
  brother: {
    name: "Max",
    age: 11
  },
  sister: {
    name: "Ida",
    age: 9
  }
};
# Loop:
eat food for food in ['toast', 'cheese', 'wine']
// Loop:
var food, _i, _len, _ref;

_ref = ['toast', 'cheese', 'wine'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  food = _ref[_i];
  eat(food);
}

The examples above are from the official CoffeeScript web site and they look great - right? Brackets are gone, commas, semicolons... Throw in variable scoping, classes, and a bunch of smaller perks like Ruby style string interpolation, and there you have it - a strong case in favor of using CoffeScript everywhere. This is especially true for coding for Angular or other frameworks with similar style, where, for instance, nested anonymous functions occur on every other line of code.

Unfortunately this is not the end of the story. What gives me pause is that the compiler (the language) seems to be too finicky. Some expressions compile as expected, while others, seemingly very similar do not:

# This one compiles:
foo param,->
// as expected:
foo(param, function() {});
# This one does not:
foo ->,param
// ERROR
# This one is good:
variable1 - variable2 -
        variable3 - variable4
// as expected:
variable1 - variable2 - variable3 - variable4;
# This one not so much:
variable1 - variable2
        - variable3 - variable4
// ERROR

Combine it with the less than perfect diagnostics and you can spend more than a few minutes figuring out why after you copy-pasted an existing function the code would not even compile. And here is another twist. Sometimes a whitespace in a wrong place can produce code which compiles into something completely unexpected. Here is an example:

# This one:
foo(bar) param
// compiles to:
foo(bar)(param);
# with one extra space:
foo (bar) param
// it compiles to:
foo(bar(param));

Of course part of this is just the learning curve and will go away after some time. You can also make an argument that quirks like the one above should be caught by unit testing. All true, but I am still uneasy... Should I keep using JavaScript or should I switch to Coffee?

So... what is your poison? Today's specials are:

Verbosity of the older syntax with JavaScript

or

Hypersensitivity to spaces and Syntax traps with CoffeeScript

Which one would do you feel better about?

Me - I am not sure.

Saturday, February 2, 2013

JQuery: the Angular way - Selectmenu


Yesterday I was presented with a challenge: how to use the selectmenu plugin by Felix Nagel in an angularjs application.
The angular way with jQuery plugins is to create an angular directive wrapping this plugin and then apply this directive to the tag this plugin should be applied to. Something to the effect of:


<select select-menu>
   <option value="...">...</option>
</select>

for the HTML and

.directive(

  "selectMenu"
  [ '$log',
    (console) ->
      {
        link: (scope, element, attrs, ctrl) ->
            $(element).selectmenu()
      }
    ])


for the directive definition in CoffeScript. For plugins relaying just on the HTML tag they are attached to, this approach works just fine. It will work fine for the selectmenu plugin as well as long as the html, including the nested option tags is static.

But as soon as you try to the list of option dynamic, like:

<select select-menu>
   <option ng-repeat="option in options" value="...">...</option>
</select>

it no longer works, the list of options will show up empty. The reason is that the selectmenu method is called too early. The ng-repeat is yet to do its magic and the real list of options object does not exist yet. 

The solution to this problem is simple: invite the Option tag to the party. Create another directive and apply it to every option. When this directive is linked it should modify the parent select to reflect that a new option just has been added: 

.directive(
  "selectOption"
  [ '$log'
    (console) ->
      {
        link: (scope, element, attrs, ctrl) ->
          $(scope.select).selectmenu() 
      }
    ])

The way it works is that right after the new option tag is added to the DOM, the link method is called and the selectmenu plugin documentation recoomends calling selectmenu method again as a way to update the list of options.

This would be the end of the story, but there was one more thing. It turns out that it is still too early. The option tag has been added but the value of the text is not evaluated yet. I had to do it myself using the $interpolate function. This was the last touch. You can see the result here