Some random notes on the pangs of ember. Will be expanding as they are uncovered.
Say you have a form that maps to a model, something like:
<form>
<fieldset>
<legend>Who are you?</legend>
<ol>
<li class='field string required validate'>
<label>First Name</label>
<input name='user[first_name]'/>
</li>
<li class='field string required validate'>
<label>Last Name</label>
<input name='user[last_name]'/>
</li>
<li class='field string required validate'>
<label>Email</label>
<input name='user[email]'/>
</li>
</ol>
</fieldset>
</form>
To handle this in jQuery all you have to do is this:
$(function() {
$('#user-form').submit(function() {
var form = $(this);
var data = {};
var id = form.attr('id');
var user = App.User.find(id);
$('.field', this).each(function() {
// really, some `serialize()` function...
var name = $(this).attr('name');
var value = $(this).val();
data[name] = value;
});
user.updateAttributes(data);
if (!user.valid()) {
for (var key in user.errors) {
// firstName-input (pretend at least)
$('#' + key + '-input', form).addClass('invalid')
.append("<output class='error'>" + user.errors[key].join("\n") + "</output>");
}
return false;
}
});
});
... it took me like 5 minutes to write that, and I'm sure I can refactor that to something reusable in any app in an hour or two. Maybe even make it a plugin on Github, another 1 or 2 hours...
Now to make that same thing in Ember...
- Build the HTML form using Handlebars markup.
- Build the views
- Build the controllers
Not only that, think about orders of magnitude more variables.
First, build the HTML, simple enough (and getting excited because of how simple it seems):
{{#with App.newUser}}
<form>
<fieldset>
<legend>Who are you?</legend>
<ol>
<li class='field string required validate'>
<label>First Name</label>
{{view Ember.TextField valueBinding="first_name"}}
</li>
<li class='field string required validate'>
<label>Last Name</label>
{{view Ember.TextField valueBinding="last_name"}}
</li>
<li class='field string required validate'>
<label>Email</label>
{{view Ember.TextField valueBinding="email"}}
</li>
</ol>
</fieldset>
</form>
{{/with}}
Now, I don't want the model to update every time I enter something in the keyboard. After reading through docs and source code for a few minutes, I realize I can just change
{{view Ember.TextField valueBinding="email"}}
to
<input type='text' {{bindAttr value="email"}} />
But now in the first case, I'm using an Ember.View
object, and in the second case, am I using an Ember.View
instance? Not clear, so look it up in the docs. Oh, and if I remove that <input...>
element, what's going to happen do the bindings? Now all of a sudden I can't do the quick-and-easy $('input').remove()
. Instead, I have to do Ember.View.VIEWS[$('input[name="email"]').attr('id')]].destroy()
. That's pretty ugly. There's a way around that though, no worries! All you have to do is make the <form>
an Ember.View
, or wrap it all in an Ember.View
as a template.
But now, I want to start adding something like an autocomplete box, or the stackoverflow-like tag box. In jQuery I can just listen to the keyup
event, run some ajax, and position some divs over the text field:
$('input').keyup(function() {
var input = $(this);
var position = input.offset();
$.ajax({
url: '/autocomplete',
data: {query: $(this).val()},
success: function(words) {
var divs = [];
for (var i = 0; i < words.length; i++) {
divs.push('<div>' + words[i] + '</div>');
};
$('#popup').empty().append(divs.join('\n'));
}
});
});
Again, took 2-3 minutes to whip together that function.
For Ember what do I have to do? I have to create an App.AutocompleteView
, and should that be an Ember.ContainerView
or Ember.CollectionView
? Then I have to think about how and where the event handlers go (for clicking on the tag in the autocomplete box, for example).
Anyway, all of a sudden, with Ember, I have to have a full-on set of UIComponents, with very rich event handling systems, like flamejs:
- alert_panel.js
- button_view.js
- checkbox_view.js
- collection_view.js
- disclosure_view.js
- form_view.js
- horizontal_split_view.js
- image_view.js
- label_view.js
- list_item_view.js
- list_view.js
- list_view_drag_helper.js
- loading_indicator_view.js
- menu_view.js
- panel.js
- popover.js
- progress_view.js
- radio_button_view.js
- root_view.js
- scroll_view.js
- search_text_field_view.js
- select_button_view.js
- stack_item_view.js
- stack_view.js
- tab_view.js
- table_data_view.js
- table_view.js
- text_area_view.js
- text_field_view.js
- tree_item_view.js
- tree_view.js
- vertical_split_view.js
And those aren't easy classes to make, they're pretty involved. And now I'm up to 300KB of JavaScript just to get back to the ability to do basic things I could do in a few lines of jQuery.
Anyway, I'm not going to spend the time describing the details of all this, it would take hours/days. All I'm going to say now is I feel like I have to build basically sproutcore and then some on top of Ember.js in order to get back to what I could do with jQuery 5 minutes with only a few lines of code.
Yeah that seems about right:
But then the project's starting to sound enterprise.
Personally, I won't use Ember.js for any personal projects. I disagree with the slogan on emberjs.com:
The type of productivity they're targeting is, I feel, the same market as the DreamWeaver users, the ones who don't really know how to code but who want to make things quickly without much effort. The thing is, no [good] developers use DreamWeaver, because despite its high intentions, it got in the way more than it helped. It turns out that TextMate and Vim increase productivity - contrary to what you'd expect on the surface.
However, if any of my apps need 100% data-binding, meaning I want every input and list item to reflect a change in a model field, and the model field to reflect the changes in the input or list item, then I will use Ember.js. But the reality is, that's only good for demos. Unless your app is a chat application demoing data-binding, you don't need a built-in data-binding solution. It will just get in your way. Instead of writing new code to build out more of your app (or to have the time to learn something new like Hadoop), you'll be figuring out how to carefully construct your code so it works with data-binding. Hours and days go by like this. And it feels productive. It's insanely seductive. But really, you're just spending your life's energy trying to understand all of the implications of your data-binding code. I will use my energy instead to either learn or create things.
Ember.js seems perfect in principle. I can't argue with that. @d4tocchini and I have talked for years about this (especially about MVC in general). But in practice, at least for me, it only wastes precious time. Sure you can get a demo going in 5 minutes that gets you all excited, where it might take you 2 or 3 days in plain jQuery. But spend a couple weeks on your project and look back: if you went the Ember.js route, most likely you spent the majority of your time debugging and trying to figure out how to fit your code into your app. If you went the plain jQuery route (or Backbone, or Spine, anything without complex Views/Components), you will be done with your app mastering the next thing in your field.
The tendency, or seductive tendency, is to keep digging further and further into data-binding, because it feels right. It is right, in principle. But if you track your results like I do, you'll quickly see you've wasted a lot of valuable time.
I hope this is because I have just not mastered this whole stateful UI thing (like the iPhone Cocoa MVC framework), and in a few months I'll be raving about it's benefits. But, I spent years in Flex, and I always thought what we were doing was amazing, but really, I was spending more and more and more of my time trying to figure out how to build Views/Components and debug performance problems, which is the main reason I stopped using Flex and started using jQuery and plain HTML5.