Router v2.1 transitionTo does not work - ember.js

I'm trying to use the new Router API (at commit 6a165ad), and I have some problems.
Given this router:
Router.map(function(match) {
match("/posts").to("posts", function(match) {
match("/new").to('new', function(match) {
match("/author").to('author');
});
});
});
I'm trying to transition to the new route.
Using new.index: this.transitionTo('new.index')
It works, but as you can see the route name is not really explicit (we don't even know that it's for a new post). It's consequently not a viable solution.
Using posts.new: this.transitionTo('posts.new')
I hoped it works, but an error is thrown:
The route posts.new was not found.
I believed the transition to the index was made automatically, but it seems not.
Using customized route name:
Since the commit specified above, Ember allows custom route naming. As my previous attempt does not work, I tried to force the new route to be posts.new, but it still does not work (idem if it was foo.new).
It looks like its not possible to go to a customized route that has nested routes.
TL;DR
I'd like to transition to the new route (and specifying posts). How should it be done ?
Before the router v2.1, I had routes that has child without to (i.e. match("/posts", function(match) { ... })), is it still working ? If so, what's the name of its children ?

This was actually a bug in Ember. Because index is implicit, you should not need to explicitly provide it.
The bug was fixed on master.
If you want to go to a route that has child routes, you should transitionTo the specified name of the route, and Ember will automatically add the index for you.

Related

Fetch new data from API in jQuery plugin's callback

I am new to ember, so please treat me like a fool. What I'm trying to do first is to understand the concept.
In my application I heavily rely on few jQuery plugins they fetch new portion of data in their callbacks, that's how these plugins are designed, but I am not sure how can I trigger them to fetch a new portion of data from API passing to API updated query parameters after plugin has been rendered.
I have wrapped the plugin in a component, in component's template I send data to it as (I use emblem.js syntax here)
= plotly-chart chartData=model
In model I have
//app/models/data-points.js
import DS from 'ember-data';
export default DS.Model.extend({
// time: DS.attr(),
ch1: DS.attr(),
ch2: DS.attr(),
ch3: DS.attr(),
temperature: DS.attr(),
});
And then in component itself I fetch data
//app/components/plotly-chart.js
dataPoints: Ember.computed.map('chartData', function(item){
return item.getProperties('ch1', 'ch2', 'ch3', 'temperature');
}),
and make some manipulations with data, which isn't so important for the question itself.
Ah, and I have a route graph/ which later calls that component
//app/routes/graph.js
import Ember from 'ember';
export default Ember.Route.extend({
queryParams: {
start_timestamp: {
refreshModel: true
},
end_timestamp: {
refreshModel: true
}
},
model(params) {
return this.get('store').query('data-point', params);
}
});
So as you see I have tried to fetch new properties via query params, finally it works great if I just update the url in browser, but now can I trigger new call to API and fetch new data and get this new data in a component itself?
Also I'm struggling to understand what role controllers play in all of these. It is mentioned that controllers will be deprecated soon, but still used here https://guides.emberjs.com/v2.10.0/routing/query-params/
My code seems to work without controllers, so this is really confusing.
Also I suspect maybe I should use services for what I'm trying to achieve, but not sure how.
Ember experts, could you please point me into a right direction? The most important thing is how to fetch new portion of data from API with updated query parameters (query parameters to API itself, not nessesarely the ember application, but I suspect in ember-data it is the same thing? or not %) %) %)).
UPDATE 1
Really I should use services for that, shouldn't I? Pass arguments into a service and query a store there. Would that be a correct pattern?
Then query parameters in url are not the same as querying the store and is an independent thing. Am I right?
but how can I trigger new call to API and fetch new data and get this new data in a component itself?
If you change your queryParam values in a controller using an action (combined with your current route setup) it will adjust your route and re-call your API, as the values are bound together to make this particular use case simple :-) You're about 98% of the way there ... :-)
Re controllers going away, they won't for a long time as the replacement hasn't been worked out yet. You could do some of this in a service if you want to, but there is no need as you are almost done.
Thanks, that make sense though. I just worried I'm doing it wrong.
By they way finally I found a way to access store from the controller Access store from component but:
1. I was unable to take out the data from that variable, probably it's me being stupid.
2. I double it's the right way to access store directly in a component and better to use services for that or rely on “Data Down Actions Up” (DDAU) paradigm?
Finally I was able to fetch new portion of a data calling a controller's action from within the controller, but then the next problem raised - the data was updated, but the JS code did not know about that because I feed the jQuery plugin with this data and it did not pick up changes automatically. I think I might be doing it a wrong way there %)
But finally I get it working by adding an Ember's observer to that variable and in observer calling a redraw function (for chart in this particular place).
#acorncom Thanks!

ember data model is not reloading even after queryParams is changing

I want my model to reload when the queryParams has changed. queryParams change is reflecting in the URL but model reload is not happening. I followed the ember.js guides (https://guides.emberjs.com/v2.6.0/routing/query-params/) on making full-on transition. I've my queryParams declared in my controller
//controllers/service/service.js
import Ember from 'ember';
export default Ember.Controller.extend({
queryParams:['showFriends']
});
//routes/service/service.js
model(params){
return this.store.query('service', params);
},
actions:{
updatePage(params){
this.transitionTo('service',{queryParams:{showFriends:params}});
},
},
queryParams:{
showFriends:{
refreshModel:true
}
},
I'm calling updatePage from a component which passes params as true or false based on a checkbox selection. The component is called from application.hbs file as below
{{test-component model=model updatePage="updatePage"}}
If I remove refreshModel:true from my route, I see the URL is updating but my model is not reloading (as expected according to the docs).
Since, I'm doing a full-on transition I added the line refreshModel:true which gives me error
ember.debug.js:32096 TypeError: this.refresh is not a function
at Class.queryParamsDidChange (ember.debug.js:26286)
at Object.triggerEvent (ember.debug.js:28580)
at Object.trigger (ember.debug.js:53473)
at fireQueryParamDidChange (ember.debug.js:52255)
at Object.queryParamsTransition (ember.debug.js:51983)
at Object.getTransitionByIntent (ember.debug.js:51913)
at Object.transitionByIntent (ember.debug.js:52018)
at doTransition (ember.debug.js:52590)
at Object.transitionTo (ember.debug.js:52087)
at Class._doTransition (ember.debug.js:28291)
I spent more than 2 days on this issue but of no use. Anybody who has faced this similar issue or has any idea on how to solve this would be a great help to me. Thanks in advance
Put queryParams:{
showFriends:{
refreshModel:true
}
} in corresponding route.
Routes are responsible for fetching models, so it should be there
Answering my own question, thought if somebody is facing the same problem. Solution is simple but you know finding out the right spot is the difficult task.
I've a service injected into all of my routes and the name of the service is "refresh". When I call refreshModel:true in the route as we all know ember in the backend calls this.refresh() method.
But, here in my case, a service with refresh name is already been injected in the application, ember is not calling this.refresh() method of the route instead calling the injected service which is obviously not a method.
So, it is throwing me a error this.refresh is not a function. Ember would've have given the error more specifically but again its hard for anyone to expect this scenario. Anyways, thanks for all who gave the solutions which directed me in the right path.

Lazy loading route definitions in Ember.js

I'm trying to implement lazy loading in an Ember.js application. Ideally, I'd prefer to have all the relevant code for each module, including any controller and route definitions, in a separate .js file that gets lazy loaded.
Right now, the js file gets loaded correctly when I transition to the route, but because Ember implicitly generates a route definition, the implicitly-generated route object is used instead of the route in my lazy-loaded js file.
In my lazy-loaded js file, I've got a route App.UsersManagerRoute that should be linked to the users.manager route. In the Ember Inspector, I can see that an implicitly generated route is being used instead, even after I've loaded the js file.
To try to fix this, I've tried to manually register the route after loading the js file, but it doesn't seem to be working. This is my code that does the lazy loading:
Ember.Router.reopen({
_doTransition: function (_targetRouteName, models, _queryParams) {
var resourceName = _targetRouteName.split('.')[0];
var self = this;
var transition = self._super(_targetRouteName, models, _queryParams);
transition.abort();
var fullRouteName = 'route:' + camelizeRouteName(_targetRouteName);
self.container.unregister(fullRouteName);
App.lazyLoad(resourceName, function() {
self.container.register(fullRouteName, App[sentenceCasedRouteName(_targetRouteName) + 'Route']);
transition.retry();
});
return transition;
}
});
After I unregister the implicitly generated route and register my lazy-loaded route, the route definition seems to be used correctly, but for some reason, I see a blank page instead of the right template. I'm not too sure what I'm missing here, or if what I'm trying to do is the recommended approach.
All the examples of lazy loading in Ember I've seen so far place the Route outside the lazy-loaded file. Is that the only option that would work?
Auto generation of ember routes is caused by link-to component through href computed property. Never fight against it. Ember will not work properly and you will loose. But you should know it deeply in order to understand the mechanism.
href LinkToComponent method ask for a URL. Before answer, Ember looks for the route. If it doesn't exist, Ember creates one from route:basic.
Container and Registry have some useful method: reset and lookup the former, register and unregister the latter.
register and unregister modify the registry.
lookup creates instances if they don't exist, looking for the factory in factoryCache, and storing them in cache. If the factory doesn't exist there, it asks the Registry.
reset clears cache and factoryCache of the specified member.
That said, the right sequence in order to achieve lazy loading should be:
unregister(fullName);
reset(fullName);
register(fullName, factory);
lookup(fullName);
For an initial solution, have a look at https://github.com/ricottatosta/ember-wiz
Auto generation of ember object is caused by link-to component through href computed property. In order to avoid this behavior (it could be responsible of blank pages) I advice to change href to avoid calling function that calculate so called 'intention' that autogenerate missing objects.

In Ember, multiple URLs, same route/controller/view/logic, different filter?

In Ember, I want the following urls to be handled by the same route:
/due: Show all tasks with due dates.
/due-past: Show all tasks with due dates in the past.
/due-today: Show all tasks with due dates today.
/due-tomorrow: Show all tasks with due dates tomorrow.
/due-this-week: Show all tasks with due dates this week.
/due-next-week: Show all tasks with due dates next week.
/due-this-month: Show all tasks with due dates this month.
I was imagining I would catch all urls by the same route and use the "when" part ('past', 'today', 'tomorrow', etc.) to filter in the model on that route. But I'm lost and don't know where to look for answers.
For example:
app/router.js:
Router.map(function() {
this.route('due', { path: '/due-:when' });
});
But this doesn't work.
Maybe I'm confused and don't understand Ember concepts. What is the Ember way?
Update
Now that I understand that the dynamic part must be a segment, I am forced to do something more like this:
Router.map(function () {
this.route('due');
this.route('due', {path: '/due/:when'});
});
But I realized that {{#link-to 'due'}}Due{{/link-to}} was just hitting the /due/undefined url, so /due is never getting hit. I guess I ditch the due route and just have /due/all? Oy. Every minute another compromise.
You could create a due route with nested routes for the period. URLs would then look something like this:
/due
/due/:period
In the due period you would fetch all tasks and in the period-routes you would have access to the period params in the params-parameter in the route's model-hook.
See http://emberjs.com/guides/routing/defining-your-routes/#toc_dynamic-segments for more detailed information.
The Ember way I suppose or the way I would implement something like this is to use the new query-params feature and declare the :period-dynamic segment a query params property on the due-Controller. With this you only end up with one route that is able to filter the due-tasks.
You can also specify if you want to query the server when a period changes or if you only want to filter the already fetched due-tasks considering that you would need to fetch all the tasks for the /due-route anyway.
If there was some kind of pagination in the due-route you would need to query the server for the different periods of course. See Opting into full transitions for details.
I think query params is the way to go here because the period routes are not really nested resources and will likely display the same interface as the due-route. The only thing that differentiates the two 'kind' of routes (due and due-period) is the filtering. Thus I'd opt for the query-params approach here.
If those are the routes you want, then there's no reason why you can't have them that way. This router map should get you that layout:
App.Router.map(function() {
this.resource('due'),
this.resource('past-due'),
this.resource('due-today'),
this.resource('due-tomorrow'),
this.resource('due-this-week'),
this.resource('due-next-week'),
this.resource('due-this-month')
});
Though, I'd probably go for something like this:
App.Router.map(function() {
this.resource('due', function() {
this.resource('today'),
this.resource('tomorrow'),
this.resource('this-week'),
this.resource('next-week'),
this.resource('this-month')
});
this.resource('past-due');
});
Then your routes would be:
#/due
#/due/today
#/due/tomorrow
#/due/this-week
#/due/next-week
#/due/this-month
#/past-due
It doesn't really matter too much if you use route or resource, its suggested that you use resource for top level items and routes for verbs like new or edit.
When it comes time to fetch your models, you'll just have to hardcode the values to retrieve, like this:
App.PastDueRoute = Ember.Route.extend({
model:function() {
this.store.find('todo',{due:'past-due'});
}
});
And you can link to routes by doing:
{{#link-to 'past-due'}}Past Due{{/link-to}}
If you don't have it already, I highly suggest that you install Ember Inspector in Chrome and use it too look at your app, specially your routes. It'll really help you get a sense of what you're defining.

How can you store a path and transitionTo it later in Ember's v2 Router?

i'm trying to migrate to the new Router in Ember. the use case is this: user is not logged in but requests a URL that requires login. he is redirected to a login route, after successful login he is redirected to his original destination.
i achieved this with the prior Router by overriding Router.route(path) and intercepting path requests when the app was in unauthorized state.
the new Router doesn't have a route() function, also, i don't know how to override it now that the Router instance is created automatically by Ember. i probably shouldn't do that anyway.
there is a Route.redirect() hook that looks useful. however, Route no longer extends Path in the v2 Router, so there is no Root.path, and no path information is passed into Route.redirect(), so i don't know how to save the path info for calling transitionTo() later.
i've supplied my general approach below. how can i accomplish this? it seems like a very common use case for many application.
// i imagine something like this should happen
App.AuthRequiredRoute = Ember.Route.extend({
redirect: function() {
if(!App.controllerFor('login').get('isLoggedIn')) {
var pathToSave = ????
App.controllerFor('login').set('pathAfterLogin',pathToSave);
this.transitionTo('login');
}
}
}
// and then after login, the LoginController would call App.router.transitionTo(this.pathAfterLogin)
I have done a lot of research into this myself in the last day or two. I can share with you what I have discovered, and a couple of thoughts.
First of all, you can some information regarding the current path and contexts like so:
this.router.router.currentHandlerInfos
This returns an array. Each object has both a name and a context property. The names correspond to the name of the router you would call in transitionTo.
In my opinion, although you could work with something like this, it would be messy. The API docs don't refer to this and it may not be the intention to use this as a public API.
I see issues with the above solution for deeper nested dynamic segments too. Given how new the router v2 is, I think it will continue to evolve and a better solution is likely to present itself. It's a fairly common thing to want to save the current location and return to it at a later date.
In the meantime, rather than a redirect, perhaps use a conditional block in your template that presents a login rather than an outlet if the authenticated flag is not set on the ApplicationController? I know it's not as "right" but it is "right now".