Emberjs: transitionToRoute in the same route with different model value - ember.js

I have an Ember application composed from 3 routes:
router.route('territory', { path: 'localhost/app/territory/:tid' });
router.route('aggregator', { path: localhost/app/territory/:tid:/aggregator/:aid' });
router.route(territory, { path: 'localhost/app/territory/:tid/aggregator/:aid/item/:iid' });
the possibles transition are from territory to aggregator, from aggregator to item, and from item to a sub item.
The sub item use the same route (the 3rd), just changing the iID value in the model of route.
I had created an action that allows the user to move into a particular route with some logic and at the end run the command:
model={
tid: "ttt"
aid: "aaa"
iid: "iii"
}
destination = 'item'; //the name of item route
controller.transitionToRoute(destination, model);
If I'm in the item route and I want to move to an other item, the URL will update, but not the content of the page. Obviously if I refresh the page with the generate URL the content will update.
Where is the problem? in the transition method that is Deprecated, or i have to use something different?
IMPORTANT: I'm using EmberJS - V1.0.0-RC.1

Is not a bug is just a normal situation in emberjs because every route have model and setupController.
The model function is used to retrive asynchronously from WS or Data module the necessary information (is a RSVP.Promise). When is complete the information will be passed to setupController function, where will be possible set properties of the controller connected with the view of current route.
Every time that i change the value of path but not the route, only setupController will be called.
To conclude, in my case, the problem was just an organisation problem of code.

Related

Tracking the current URL in a template

I have a "copy link" input in my template that should display the currently loaded URL (with query params) to the user for them to copy & paste.
The input with the URL is part of the template belonging to a parent route that has a number of children:
- Parent route <-- Copy URL link in this template
- Child route
- Child route
- Child route
This means that as you navigate the child routes, the URL should update in the parent route. This is turning out to be very difficult to implement.
AFAIK there is no native event for URL change so I can't create a component and simply track any browser event. I tried hashchange on the window but this obviously only tracks the #and not URL or query params.
I can't use window.location.href when the page loads as any subsequent transitions to the child routes will not be reflected in the input.
I can't get the URL with window.location.href in the didTransition action (or any other route hooks) on the parent route because at that point the URL hasn't updated yet (i.e. I get the previous URL)
EDIT:
At the moment, this is the only approach that seems to work:
// In the parent route:
actions: {
didTransition() {
this._super(...arguments);
Ember.run.schedule('afterRender', () => {
this.set('controller.currentURL', window.location.href);
});
}
}
but seems pretty hacky
Since Ember 2.15 you can use the route service for this.
Example:
import { inject as service } from '#ember/service';
import { alias } from '#ember/object/computed';
export default Component.extend({
router: service(),
currentRoute: alias('router.currentURL')
});
I think you can benefit from router's location's path property. What I mean is, you can inject router to the controller you like with an instance-initializer and define a computed property to watch the current path. Please check out the following twiddle. I wrote an instance initializer named instance-initializers\routerInjector to inject application's router to every controller. I defined a computed property named location within application.js controller as follows:
location: Ember.computed.oneWay('router.location.path')
I added this to application.hbs. If I got what you want correctly; this is what you want.

Use query params of parent route to refresh the model in subroute

I have a route main which has a query param search.
I have subroutes main.sub1, main.sub2
Going to /main/sub1?search=hello or /main/sub2?search=hello sets the query param of the main route to hello. This part works fine.
Now I would like to be able to refresh the model of main.sub1 when the query param search in route main changes, so I here is the code of the route main.sub1:
export default Ember.Route.extend({
queryParams: {
search: {
refreshModel: true
}
},
model(params) {
// here I can use params.search to fetch the model for this route
}
}
I thought that since there is no search query param in the controller of main.sub1 Ember would be smart enough to guess it has to use the one from main. But now, when I go to /main/sub1 I have this error message:
Assertion Failed: You're not allowed to have more than one controller property map to the same query param key, but both main:search and main.sub1:search map to search. You can fix this by mapping one of the controller properties to a different query param key via the as config option, e.g. search: { as: 'other-search' }
My guess is that Ember automatically creates a query param search in main.sub1 which conflicts with the one from main, and does not even try to use the one from main.
How could I overcome this problem?
Simply put: how can I use an attribute of a parent route as a query param of a subroute?
Thanks!
I had the same issue.
It is completely enough to use the queryParams of the "main" route.
You don't need to add any queryParams to the sub-routes.
The refreshModel setting belongs to the main-route and all child routes will refresh as well.
Also you can use for the sub-routes model:
this.paramsFor(‘main’)
See: https://discuss.emberjs.com/t/refresh-model-when-the-queryparam-of-a-parent-route-changes/14903/2

How to implement a search feature?

I have a search feature that allows the user to filter out by products. Typically, I would have the following:
// Routes
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
return Ember.RSVP.hash({
keyword: params.keyword,
products: this.store.find('product', { name: params.keyword, status: 'available' })
});
},
setupController: function(controller, model) {
controller.set('content', model);
}
});
In the template:
// Template
{{#if products.length}}
<ul>
{{#each products}}
<li>
Name: {{name}}
</li>
{{/each}}
</ul>
{{else}}
<p>No products</p>
{{/if}}
Which works. However, what if I wanted a search API endpoint to handle filtering of products...
How will the model hook in the routes look like? Should I create an Ember search model? Is that the right approach?
If not, should I be using Ember.$.ajax instead? Other approach?
Ember.js provides facilities to deal with _query_ing data at resource level. Here is a suggested solution followed by some other options that are closer to your expectations.
Expected Use Case
In Ember, you don't have to map your "representation" of data to your server endpoint. You don't have to be RESTful with your Ember routes (as in URLs).
In Ember, one "representation" of data (say a Controller and View set) can consult multiple data sources.
Use "q" parameter for full text search on your resource.
The point behind all of this is, to decrease complexity of your backend API. If you build a search and query system for one resource, you can reuse it for all other resources of your backend.
Create a search URL
// in your router
this.route('search')
Create a search Route to fetch required data
Ember.RSVP.hash({
stores: this.store.find('store', {
rate_minimum: 4.0,
near: 'lat:long',
q: 'local store name'
}),
products: this.store.find('product', {
price_maximum: '$50',
near: 'lat:long',
q: 'famous shoe brand'
})
});
Fast Response Way
Use Store#pushPayload
Download data with Ember.$.ajax from your search endpoint. Keep in mind as long as your data has a name, you can use Store#pushPayload to load multiple unrelated data types into Ember Data's Store.
In your Route's model hook, use this.store.all('product') to return all products in memory without sending a request. At some point in future Ember will update this array. You can use Ember.RSVP to "represent" more than one resource.
In your Controller, use array operations like filterBy to provide real time update to users' queries beside issuing a new request to API for more relevant data.
This results in immediate response while waiting for backend to reply and immediately updating "representation" of data with backend response.
Ember Data-less Way
Create a Search Route
Use Ember.$.ajax to get data
Pass it as your Route's model hook
Send another ajax request whenever user changes search criteria
I don't think you would do anything different. When you execute a store.find with query parameters, in your case { name: params.keyword, status: 'available' }, the result that is return by the query is what will show in the controller; assuming you are using an ArrayController. Your model and controller should not need to change, you just control what you want based on how you structure your query.
// display all models that are in the store
store.find('someModel')
// display a single model
store.find('someModel', 123) // will display a single model
// display only models that match the query
store.find('someModel', { name: params.keyword, status: 'available' }
Now if you are dealing with endpoints that don't conform to the norm, ie. your search endpoints are not just at /products?name=some-name&status=available, then you may have to modify your adapter slightly to deal with the differences. Doing this in the adapter means you still have a simple controller/route/model and you are hiding the complexity which is very specific to the endpoint in the adapter.

How to have two different models within a route and its subroute?

I'm making a simple web chat system with Ember.
I have a route /chatrooms that lists a few chatrooms, and then I also have /chatrooms/:chatroom_id that should show the actual chatroom with messages.
The second route is within the first one, like this:
this.resource('chatrooms', function() {
this.route('show', {
path: ':chatroom_id'
});
});
When I access /chatrooms, a call is made to the server (/api/chatrooms) is a list of rooms is returned and displayed, like expected.
When I click a room, the application transitions to /chatrooms/id, but no call is made to retrieve the messages (available at /api/chatrooms/id), even when I try to define a model.
I have a similar scenario with the users. A list of users is retrieved, then displayed. When a name is clicked, the profile is shown. No second call is made, but that's okay since Ember knows everything about the user already.
In my current case, when a list is first returned, it includes all the information except the messages. I believe that would be too much otherwise (10 chatrooms * 100 last messages = 1000 elements in my JSON for each request). So I want to call the server for messages only when a chatroom is selected.
Do you know how to do it, or maybe there's something wrong I'm doing in the first place?
Updates
Template code from app/templates/chatrooms.hbs
<h1>Chatrooms</h1>
<ul class="sub-menu nobullet flex mas">
{{#each chatroom in model}}
<li class="mrs">{{link-to chatroom.name "chatrooms.show" chatroom class="pat"}}</li>
{{/each}}
</ul>
{{outlet}}
In this case, model is an array of chatrooms.
My routes:
app/routes/chatrooms.js
export default Ember.Route.extend({
model: function() {
return this.store.find('chatroom');
}
});
app/routes/chatrooms/show.js
export default Ember.Route.extend({
model: function(params) {
return this.store.get('chatroom', params.chatroom_id);
},
actions: {
send: function() {
...
}
}
});
As discussed in this thread, when you link-to a route and the model is already loaded, the model hook of the route is not fired because there’s no need to reload the data.
If you transition to a route and all the context objects -the objects which will serve as models to templates- are passed in, the beforeModel and model hooks will not be called.
Later in the thread balint corrects:
In fact, the beforeModel hook still gets called in that case, it is only the model hook that does not.
If you want to force the model to be reloaded, you can change your link to use the ID instead of the model:
{{link-to chatroom.name "chatrooms.show" chatroom.id class="pat"}}
You could also load the data in the beforeModel or afterModel hooks, or setupController.
Also, in the chatrooms/show route, you are getting the already-loaded model from the Ember Data store rather than loading it from the server. Try this:
return this.store.find('chatroom', params.chatroom_id);
I ended up adding a links property to the JSON response for chatrooms. When the content of a chatroom has to be displayed, the link is used and the messages retrieved. It only requires two requests, and there's not need to preload all the messages from all the chatrooms and no need to make a request for each message.

Fire method each time route slug changes

I have a Router set up which looks like this:
Social.Router.map(function() {
this.resource('accounts', function(){
this.resource('account', { path: ':account_id'});
});
});
When the account route is entered I'm using the activate method to do some DOM manipulation.
Social.AccountRoute = Ember.Route.extend({
activate: function(){
console.log('entered the account route')
}
});
This works great, the first time the account route is entered. Problem is that I have the ability to change from account to account without leaving the account route. Meaning that I can go from:
account/1
to
account/2
but the activate method only fires one time, the first time I enter the account route. Is there a method that will fire every time the account_id slug changes?
Not a very intuitive solution, but there are 3 methods that are executed every time the model changes:
serialize
setupController
renderTemplate
activate and deactivate are only triggered when entering a new route. However, the methods model, setupController, and serialize will be called with each change of /account/:account_id. (Edit: as pointed out by #mavilein in the comments, model will only be called when triggered by a URL change, and I left off renderTemplate).
Regardless, your routes and controllers shouldn't be concerned with DOM manipulation. Leave that to your views and templates by binding to significant values or observing changes in your controller(s).