Is it possible to add actions to HTML elements in js?


#1

Hey guys.

I’ve been reading Ember guides recently and they do a good job of laying out Ember’s basic technical concepts. However, there is not much said about its ideology and goals it pursues. Those things have been mentioned in interviews and articles, but I’d like to ask one specific question here that’s still unclear to me.

To add an action to an element that’s injected into DOM from a template, we need to specify it within the same template using action and that’s only way to do it. Is that right?

This is both technical and ideological question, let me elaborate on that.

I’ve been comparing the source code of different implementations of TodoMVC. I’ve looked at Spine, Backbone, Ember, and Angular; specifically, their index.html file. So far, only Backbone and Spine keep their HTML clean of any application logic. They have a bit of logic in their templates, but it’s not that significant.

Ember, on the other hand, mixes actions and value bindings into its templates. And Angular places all kind of knowledge about application into HTML. I believe these latter examples lead to a more difficult to maintain and keep sane code base.

I’m coming from a non-webdev background, but I’ve been following its evolution and I think one can pick out these stages from the history of using JS in browsers, in chronological order:

  • At first, we had to add event listeners to elements in HTML using on* properties and implement the handling functions in a .js file. This was the way to go because in JS itself you could only easily query for elements with ids.

  • jQuery appears with its powerful selectors allowing for easy element access in JS. No need to hard-code event handlers in HTML any more. This proves to be a very useful thing: a web designer can work with HTML and CSS to provide the visual and structural foundation of the site while a separate JS programmer would code all of the interactive behavior.

  • The rise of templates and MVC frameworks. We go back to step 1 by mixing behavior and logic back into HTML and making it virtually impossible to have a separate person who has no JS or templating knowledge to work side by side with a JS programmer.

This is my impression of the way webdev evolves, correct me if I’m wrong.

I like Ember, it’s very clean and code efficient (as in “energy efficient”) and it sets good conventions of keeping things separate, i.e. there is usually only one good place to put a certain kind of state in: model, controller, route, or application. But the fact that it treats HTML as its building block and at the same time intertwines it with application logic seems like a move in the direction that leads to less flexibility in the long run.


#2

I am a JS newbie, however, I have quite some experience on server-side “enterprise” architecture, and as such I understand your concern about separating things to prevent flexibility on the long run.

I add a look to the Ember JS sample code your pointed out, and I agree with you they embedded some logic into it. I think they could have done it differently, with a better separation. They could use child views and assemble them into index.html. I am sure they could do similar things with routers too. This is actually how I create my own applications with Ember JS.

So, maybe the persons who wrote those examples were more fluent with Angular than with Ember, and I would love to see an Ember expert (1) propose a refactored solution of this TodoMVC sample app.

On a higher level, I have already seen effort to separate things as much as possible leading to lesser understandable code, with stacks of layers of almost empty code, mostly technical. Java culture used to be very fan on this (dont know if this is still the case, as I stopped using it some years ago).

So I thinks this is a matter of balance between those extremes, and I like the way Ember JS allows us to set the cursor ourselves. I am sure newbies will still create crappy apps with logic code dumped everywhere, and on my experience setting strong boundaries does not help them create better code.

What helps them actually is to rise the issue as you did, and I thank you for doing it :slight_smile:


(1) Just seen that @tom contributed to the sample code, so the expert had already a look to it.


#3

I don’t know if this answers your question but it’s possible to externalize all of your templates and load them into the Ember namespace via AJAX. @wycats showed an example of this a while back. The code might be outdated but I suspect it’ll work just fine:


#4

We hear this criticism fairly frequently. If you prefer, you can do all your event handling by creating views and putting events on those. Just for clarity, something like

{{view App.MyView}}
App.MyView = Ember.View.extend({
  click: function(){
    ... do something ...
  }
});

This works fine but you’re app will eventually be filled with View subclasses whose only behavior is responding to a single, simple event (usually click).

In Backbone, as a counter point, events are declared in the view

var DocumentRow = Backbone.View.extend({
  events: {
    "click .icon":          "open",
    "click .button.edit":   "openEditDialog",
    "click .button.delete": "destroy"
  },
});

but the side effect is that view and template are now tightly coupled in the View: the selector is defined in two places and the matching event must exist. It’s easy to inadvertently break events by changing the template without realizing a specific selector is being used by a developer to trigger behavior. I’ve often seen people better signal this with a js- prefix on classes, but I don’t know if I’d describe this approach as “clean of any application logic” and offers the same pitfalls when collaborating with non-developers. Imagine if the designer decided, for styling purposes, to use <a> tags instead of <button>. If he isn’t comfortable looking at code, he will constantly risk breaking existing behavior.

Despite how it might seem, the {{action}} helper differs from on* properties in a important way: You’re not connecting the event handling to a specific block of code. Actions first target the current controller of the template and, if the action name isn’t handled, the current route. If the action is still not handled, the call bubbles up through the parent routes all the way to root, and finally noops itself.

You’re setting up a declarative interface for the template and the code that responds will depend entirely on the current state of the application, including not responding at all. The template has no knowledge of how the application responds but clearly signals that an element potentially has associated behavior. The controller (or route) has a method that can be called and it has no knowledge of where this method was called from.

It’s about as loose a coupling as possible, but a lot of people think it’s pretty tight. I’d love to hear their reasons.


#5

I wasn’t going to criticize Ember’s approach. Instead, I was hoping someone from the dev team would explain the motivation behind it. And I think you did a good job of doing just that. Thanks, trek!


#6

I love this kind of coupling. The fact that your action is not bound to something specific, is just perfect. I wasn’t even aware, that if I don’t handle the {{action}} in the route, it bubbles up which gives me interesting coding structure opportunities.

The bottom line is that for me this is perfectly loose coupling.