Can `insertNewline` invoke a transitionTo? - ember.js

Sample code for my question is here.
It's a simple Ember app that displays the SearchView containing a TextField by default.
When the user enters some text and hits Enter, I want to transition to another state (displayUserProfile) passing the value entered in the textbox.
At first, in the Textbox's insertNewline callback, I called the transitionTo method of the application's router, passing the value as part of the parameter object:
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
App.router.transitionTo('displayUserProfile', {
username: this.get('value')
});
}
});
That works fine, but then I noticed that pangratz's answer on a question about infinite scrolling, uses a different approach. Instead he invokes a method on the view's controller, which in turn calls a method on the controller's target (which is the router).
This changes my code to:
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
Em.tryInvoke(this.get('controller'), 'displayUserProfile', this.get('value').w());
}
});
App.SearchController = Em.Object.extend({
displayUserProfile: function(username) {
this.get('target').transitionTo('displayUserProfile', {
username: username
});
}
});
My question is: which approach is better?
Calling transitionTo directly from the view or delegating it to the view's controller?

I would recommend a different approach. insertNewLine should trigger an action that is handled by the router, which will then transition its state.
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
this.get('controller.target').send('showUser', {username: this.get('value')});
}
});
App.Router = Ember.Router.extend({
...
foo: Ember.Router.extend({
showUser: function(router, evt) {
router.transitionTo('displayUserProfile', evt);
});
}
});
You should put the showUser handler at the top-most route where it is valid in your app.
This approach follows the general pattern of events in Ember apps that views handle DOM-level events and where appropriate, turn them into semantic actions that are handled by the router.

Personally I think the second approach is better.
The first thing is that it's a bad idea to access the router statically. Then for me, you have to keep the views logic-less, so delegating to controller seems a good choice.
In your case this is only a call to the router, but you can imagine processing some algorithms on the textfield value. If you do this proccessing in you view, this will lead to a view, mixing UI code, and logic code. View should handle only UI code.

Related

Call custom function when view is rendered, from one place and not repeating code for every view

in ember application i want to call my custom function which does some modifications of dom elements, the only solution i found is to repeat below code as many times as many views/routes i have
for example
rendering indexView
indexView = Ember.View.Extend({
didInsertElement:function(){
//my custom function call goes here.. myFunction();
}
});
rendering OtherView
OtherView = Ember.View.Extend({
didInsertElement:function(){
//my custom function call goes here.. myFunction();
}
});
rendering MoreView
MoreView = Ember.View.Extend({
didInsertElement:function(){
//my custom function call goes here.. myFunction();
}
});
Is there a way of calling myfunction globaly whenever any view is rendered? I really do not want to repeat code for every single view i render.
thanks!
You can create a Mixin:
App.SomeMixin = Ember.Mixin.create({
didInsertElement: function() {
this._super();
//do your common stuff here
}
});
And use it in your views:
App.SomeView = Ember.View.Extend(App.SomeMixin, {
didInsertElement: function() {
this._super();
//do your custom stuff here
}
});
I would use a mixin to do this. If, however, you find that you are using this mixin into every single view that you create, it might be better to reopen the Ember.View class and add this functionality there. Also,if you reopened the class, what you could do is, depending upon the use case, create a static function inside Ember.View.reopenClass which you would pass to a
Ember.run.scheduleOnce()
utility that ember provides so that, if the function that you need doesn't need any state (for example, just does something to the page after the content has loaded or something.), it will just run the function once after all the views are rendered in the page.
There are two ways to handle this. Create a base class, or reopen the global View class and insert your code. I prefer the first, since it's easier to maintain and track down issues.
Reopen the View class and tack on an additional method that triggers on didInsertElement
Ember.View.reopen({
doStuff:function(){
//myFunction();
}.on('didInsertElement')
});
In the case of debouncing
Ember.View.reopen({
doStuff:function(){
Ember.run.debounce(this.doRealStuff, 200);
}.on('didInsertElement'),
doRealStuff: function(){
console.log('foo');
}
});
http://emberjs.jsbin.com/gecoziwe/1/edit
Create a base class and extend from it (My preferred method, since it's easier to track down issues, can't say this enough)
App.BaseView = Ember.View.extend({
doStuff:function(){
//myFunction();
}.on('didInsertElement')
});
App.FooView = App.BaseView.extend({
});
App.BarView = App.BaseView.extend({
});

How to get reference to current route from controller?

I have a controller (KbRelatedGroupController) that is loaded via a {{render}} helper in a template.
In the controller's action, if I do this.get('target'), it returns a reference to the parent controller for the page (KbShowController).
If I call .target on that, I get a reference to Discourse.Router, which is no good to me.
What I want is a reference to a KbShowRoute, and that is what I expected since .target is supposed to produce the Route when called from a controller is it not?
Really confused here. Why is it so hard to get a reference to the current route from a controller?
The way I see it, you're not supposed to. You can let the action bubble up to the route:
App.KbShowRoute = Ember.Route.extend({
...
actions: {
something: function() {
console.log('called second');
}
}
});
App.KbShowController = Ember.Controller.extend({
...
actions: {
something: function() {
console.log('called first');
}
}
});
see docs
You could:
Handle one part of the action in the controller, and let it bubble to the route by not returning anything in the controller's action handler
Let the route handle the action (by not adding the action to your controller's action hash) and from the route use this.controllerFor(this.routeName).sendAction('..', ...) to call a different action (or part of the action) in the controller.
I hope this helps you!

How do I make my items in my ArrayController listen to an event from a third party API?

I want my items in my ArrayController to listen to
FB.Event.subscribe('edge.create', function(response){
Ember.Instrumentation.instrument("facebook.like", response);
})
I'm making use of the a seperate itemController.
Like2win.ContestsController = Ember.ArrayController.extend({
itemController: "contest",
});
Like2win.ContestController = Ember.ObjectController.extend({
init: function() {
this._super();
instance = this;
Ember.subscribe("facebook.like", {
before: function(name, timestamp, payload) {
instance.send('onLike', payload);
},
after: function(name, timestamp, payload) {
//
}
})
},
For some reason only the last item in my array ends up listening to the event. I'm just starting out with Emberjs so I expect the answer to be simple.
Ember.Instrumentation is a simple software instrumentation api. It's purpose is performance profiling, tracing, not application level event dispatching.
You can see this api in action by setting Ember.STRUCTURED_PROFILE to true. This will start logging the render times for all templates rendered to the DOM by ember.
The specific issue you are having deals with how the Ember runloop works. The after hooks are only fired once with the last context given. This is done to ensure that multiple property changes of the same property do not result in re-rendering the DOM that many times. So the last property change on the runloop wins and the DOM updates with that property value.
What you really need to do is just translate the FB.Event of type edge.create into an application event that your app understands, something like `facebookLike', similar to what you have done above.
I would do this in the enter hook of your ContestRoute. Further exiting from the ContestRoute should probably unsubscribe from this event. So you probably need an unsubscribe in the exit hook.
enter: function() {
var self = this;
FB.Event.subscribe('edge.create', function(response) {
self.get('controller').send('facebookLike', response);
});
}
exit: function() {
// unsubscribe from edge.create events here
}
Then you can handle this event in your ContestController like so,
facebookLike: function(response) {
}

What's the right way to enter and exit modal states with Ember router v2?

I can't figure out the correct way to handle modal states/views with the new Ember router. More generally, how do you handle states that you can enter and exit without affecting the "main" state (the URL)?
For example, a "New Message" button that is always available regardless of the current leaf state. Clicking "New Message" should open the new message modal over the current view, without affecting the URL.
Currently, I'm using an approach like this:
Routes:
App.Router.map(function() {
this.route('inbox');
this.route('archive');
});
App.IndexRoute = Em.Route.extend({
...
events: {
newMessage: function() {
this.render('new_message', { into: 'application', outlet: 'modalView' });
},
// Clicking 'Save' or 'Cancel' in the new message modal triggers this event to remove the view:
hideModal: function() {
// BAD - using private API
this.router._lookupActiveView('application').disconnectOutlet('modalView');
}
}
});
App.InboxRoute = Em.Route.extend({
...
renderTemplate: function(controller, model) {
// BAD - need to specify the application template, instead of using default implementation
this.render('inbox', { into: 'application' });
}
});
App.ArchiveRoute = ... // basically the same as InboxRoute
application.handlebars:
<button {{action newMessage}}>New Message</button>
{{outlet}}
{{outlet modalView}}
I've obviously left out some code for brevity.
This approach 'works' but has the two problems identified above:
I'm using a private API to remove the modal view in the hideModal event handler.
I need to specify the application template in all of my subroutes, because if I don't, the default implementation of renderTemplate will attempt to render into the modal's template instead of into application if you open the modal, close it, and then navigate between the inbox and archive states (because the modal's template has become the lastRenderedTemplate for the IndexRoute).
Obviously, neither of these problems are dealbreakers but it would be nice to know if there is a better approach that I'm missing or if this is just a gap in the current router API.
We do kind of the same thing but without accessing the private API.
I don't know if our solution is a best practice, but it works.
In the events of our RootRoute I have an event (same as your newMessage), where we create the view we need to render, and then append it.
events: {
showNewSomething: function(){
var newSomethingView = app.NewSomethingView.create({
controller: this.controllerFor('newSomething')
});
newSomethingView.append();
}
}
This appends the modal view into our app.
On cancel or save in the newSomethingView we call this.remove() to destroy the view and removing it from the app again.
Again, this doesn't feel like a best practice, but it works. Feel free to comment on this if someone have a better solution.
Don't know if you are using the Bootstrap Modal script or which one, but if you are, this question has a proposed solution. Haven't figured out all the pieces myself yet, but is looking for a similar type of solution myself to be able to use Colorbox in an "Ember best practices"-compliant way.

Emberjs: Conditional redirect in router

Is there a way to have a conditional redirect in the Ember.js Router, without breaking internal consistency of the router?
What you could do (as of today), is something like that:
root: Ember.Route.extend({
index: Ember.Route.extend({
enter: function(router) {
var logged = /* get from appropriated source... */;
Ember.run.next(function() {
if (logged) {
router.transitionTo('loggedIn');
} else {
router.transitionTo('loggedOut');
}
});
}
}),
loggedIn: Ember.Route.extend({
// ...
}),
loggedOut: Ember.Route.extend({
// ...
})
})
Do not miss the Ember.run.next as while you are in enter, the state transition is always pending, so you have to transition after that.
We use it as shown for authent, but you could imagine using it for whatever condition you have to...
The new router now includes a
beforeModel
hook which you could over-ride to include conditional logic while transitioning to a route.
The beforeModel hook will be called before the
model
hook is called and it gets passed a
transition
object. You can decide if you want to redirect to another route using
transitionToRoute()
or you could abort the transition if you don't want to redirect by calling
transition.abort()
Depending on what you're trying to do, you may be looking for conditional transitions. This is covered in another stackoverflow question, the TLDR of which is to check this fiddle.