Controller.get('view')? managing dom focus from controller events


#1

When building accessible applications its imperative that you return focus to an element when a user interaction has completed.

For example, the user clicks an “add” button, a form is rendered, then you need to focus the form or an element in the form.

The “add” handler is in the controller, with no access to the view. How do you focus an element in the template from the controller?

Then, when the user submits the form, you need to either focus the newly created element, or the button that started the interaction. Same problem, you are in the controller action.

My current hack is to set then unset some silly random property for the view to observe and then do something. Maybe others think this is acceptable but it give me the groans.

// the controller action
someAction: function() {
  this.set('actionComplete', true);
  Ember.run.later(this, 'set', 'actionComplete', false, 0);
}

// and the view

focusAThing: function() {
  if (this.get('controller.actionComplete')) this.get('element').focus();
}.observes('controller.actionComplete')

Accessible applications have to do this with nearly every controller action, ever.

You always have the trifecta with a route or a {{render}}: controller, view, template. Perhaps the controller should have access to the view? Why not? Or is there a better way to handle this scenario, or a better new feature than giving controllers their views?


#2

you may want this observer to contain

focusAThing: function() {
  if (this.get('controller.actionComplete')) this.get('element').focus();
}.observes('controller.actionComplete'),
_focusAThing: function() {
  Ember.run.scheduleOnce('afterRender', this, this.focusAThing);
}.observes('controller.actionComplete')

#3

yeah, its common you have to wait for render to finish to focus something, was just trying to get the use case out :slight_smile:


#4

Although not completely identical to your use case, I have been pondering something similar for a while.

I often find data-binding inadequate for complex components that have a lot of internal states, such as maps, media players, etc.

There are many reasons for this. Sometimes it’s undesirable for the internal states to be directly manipulated by the outside world (e.g. on a map certain zoom levels might be invalid at certain <lat,lng> when in satellite mode). Sometimes the action you perform requires modifying multiple attributes at once (e.g. pan the map to a particular <lat, lng> + zoom level, with easing). Sometimes the action you perform requires knowledge to the internals of the component that you don’t care about (e.g. you want to pan the map by certain screen pixels).

In these cases, passing explicit messages (i.e. calling methods with arguments) seems much more natural and appropriate to me.

I’m also unsure if there’s a good reason that controllers shouldn’t have access to the views. Cocoa has a pretty similar MVC separation to Ember, and it allows the controller to access views that it is interested in by connecting them via IBOutlets, which it could then use to call methods on them (focus, etc). Web components can also expose methods and they can be accessed via getElementById, etc.

So, I began experimenting with this idea to connect views (components) with their controllers in one of our projects. Here is a simplified example of my approach – basically the controller declares some named ‘handles’ (slots/outlets/… not sure what to call them), and you connect those with the appropiate views in the template.

It’s a pretty rough implementation, but it worked pretty well for my needs. I don’t claim that it’s the perfect approach, but I think it’s a pretty good start.


#5

Not sure if this is the best use of it, but I rewrote @ryanflorence’s example using my view handler mixin. Result is here.


#6

You could use Ember.Evented to trigger an event from the controller that the view can listen to. Here is a similar discussion with that example and another idea: Best practices of emitting events bound for a view


#7

While events seem to work okay for notifications, I’m not too sure how it would translate into either @ryanflorence’s scenario or my components scenario without encouraging very tight coupling.

Taking @ryanflorence’s example (assuming I got it right), there seems to be three ways of accomplishing it with events: either the controller can emit a colorAdded event, a viewShouldFocus/textFieldShouldFocus type event or a colorFieldShouldFocus event.

Option 1 is not bad, as it allows other parties to subscribe to the same event for other purposes. However, this would require implementing a very specific View subclass that knows to watch out for this specific event from the controller. It’s unlikely that this view would be useful anywhere else, and it seems like a lot of ceremony just to get something so simple to work.

Option 2 would have all views listen for a viewShouldFocus (or have all input views listen to textFieldShouldFocus) on their controller. The obvious problem is that there could be multiple of these managed by the same controller and it would be ambiguous that which view should handle that.

Option 3 is basically the same as option 1, and would again require implementing a very specific view subclass that can’t be reused.


#8

Assuming that we agree that the use case (@ryanflorence’s or mine) is legit, I think it’s quite obvious that the solution would require one of these conditions to be true:

  1. the controller would need to know about the view
  2. the view would need to know about the controller
  3. the view and controller both needs to know about each other

I would assert that 1 or 2 is obviously better than 3 because of the reduced coupling.

Ryan’s current hack (which is basically the same thing as the option 1 of the events approach) pretty much requires 3.

Based on my observation, the reusability of view-related objects seems to follow this order: components > views > “view controllers” ( > templates). If we agree with that, it seems to follow that having the controller know about its views is better than the other way around.

Based on my limited understanding/experience, this kind of controller -> view communication seems to be pretty standard among desktop MVC frameworks as well. However, whenever I bring up this idea with other Ember users, there seems to always be a sentiment of “You’re doing it wrong™”.

Are there good reasons for Ember to discourage that, or is it just a case of “we don’t yet have a good mechanism to do this currently”? (@machty, @tomdale?)


#9

So this thread has been quiet for a bit. Can one assume that based on the quiet nature here that the ideas presented are indeed the ‘best’ way to do this? I’m having the same issues (needing to manage focus for accessibility). I would much rather have a reference to my view in my controller than the boolean flipping, or event dispatching as they add a decent amount of boiler plate / ceremony to, what feels like, should be a fairly simple operation.


#10

I do one of three things, and all of them make ember core members groan:

  1. just use components for everything, not controller/view splits
  2. Access the view with Ember.View.views[viewId] and call focus or whatever
  3. Just use jQuery in the controller action $(elementSelector).focus()

I actually really like #3 because its simple, is one line of code, and sticks out like a sore thumb.


#11

@ryanflorence: I have the same requirement and have not yet found a good solution and was wondering how in your solution 2. do you know what the viewId is in the controller to enable a lookup via Ember.View.views[viewId]? Thanks


#12

I’m building my first major Ember app. I’ve spent 3+ years with a different framework (not angular) and my mine is blown that I have to use jQuery to get elements not directly related to the event target of the action but still in the same view. I fully expected to find this.get(‘view’) or something similar that I can use inside of the controller’s action handler to directly access the dom related to the controller. For now, I’m going to use jQuery to get the elements from the view which need to be acted upon, but it feels gross. I hope that there is a better solution in the works.


#13

It’s because controllers shouldn’t know or care about how their data is presented, and thus should not be “DOM aware.” If you need DOM transformations, you should probably be handling them on the View object, which can observe and react to changes in the controller, or even receive actions itself.

That said, this is all going away in 2 months in favor of just using components for everything, which have everything from Views and Controllers in one object.