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.
I should start by saying that it's still too difficult to determine best practices when using Ember, and that this will hopefully be resolved with more blog posts and Ember guides. I've been working with Ember for a few months and have some recommendations.
I would start by examining the reason you'd like to avoid bindings in your form. Perhaps you want to only apply changes after a user clicks an
Update
button? One approach is to create a copy of your model which is then bound to your form, and the attributes of that model are then applied to the original model upon success. Here's a simple example using ember-rest. Perhaps better yet, using ember-data, you could wrap changes in a transaction which is only committed when your form is submitted, like in this ember-data example. (Note: both of these examples will be cleaned up with the new conventions being introduced in Ember 1.0 - see below).However, if you really don't want any bindings (and you want to pull values from the form on submit), you could just use an unbound value:
Unbound values won't configure any binding logic and therefore don't require any teardown code. Even if you were to take a bound approach, you still shouldn't need to manually destroy the
input
. It would be better to wrap the logic which determines its presence in your template. For example:I don't think that Ember requires a "full-on set of UIComponents". The Ember core team is specifically trying to avoid rebuilding Sproutcore. UI libs like Flame.js have their place, just as libs like jQuery UI do, but such a lib is in no way required for using Ember. It is one of the specific goals of Ember core to only include HTML4 native controls where bindings or custom event handling are common. The general preference is to defer to standard HTML elements and use helpers such as {{bindAttr}} and {{action}} where appropriate. This is the reason that
Ember.Button
was recently removed from core, now that<button {{action}} />
is effectively the same.For your autocomplete box, don't forget that you could still configure it with jQuery from within Ember. You would need to do so from within your view's
didInsertElement()
event, which indicates that the view has been inserted into the DOM. However, if you want to reuse your logic and generalize your data bindings, creating anAutocompleteView
would be an elegant way to go.Last but not least, I think that the work being done now in Ember regarding view / controller relationships, state managers, and routing is pretty exciting. These are problems that all complex JS MVC apps face, and Ember will try to tackle them together in a cohesive way that provides clear conventions for developers. Because they won't be handled by different libs, I think their inclusion in Ember will actually decrease total js size for most apps. I'm hoping the advantages of Ember will become really clear as 1.0 takes shape and more end-to-end, complex examples and guides are written.