Adding and retrieving metadata from Ember routes - ember.js

For reporting purposes, I want to associate some metadata with an Ember route, and would prefer to do it as follows:
this.route('list', { path: '/list', description: 'Master List' });
then access this description property from places like the route itself, or from elsewhere, such as the didTransition hook on the application router. I've reviewed the source for Router and Route and cannot say I really understand it, certainly not well enough to understand how to retrieve custom properties specified in this way. I see there is an object called DSL, which is apparently the this of the this.route specified in the map method on Router, but cannot see how to get from here to there. From within a subclass of Ember.Route, I see properties called this.router, and this.router.router, but am unclear on what these point to.
Or, the following would also work if that allowed me to do what I wanted:
this.route('list', { path: '/list' }, function() {
this.description = "Master List";
});
Can I associate custom properties with a route specified in Router#map, and if so how?

There doesn't appear to be an elegant way to set metadata about a route when it is defined in the router, but maybe try this ugly solution within your application controller:
currentPathChange: function () {
switch(this.get('currentPath')){
case 'test.index':
console.log('test.index is the foo!');
break;
case 'test.new':
console.log('test.new is the bar!');
break;
}
}.observes('currentPath')
JSBin DEMO

Without extending Ember's Router, one option is to have a separate object that maintains route metadata. Simple example:
this.route('list', { path: '/list' });
routeMetaData['list'] = 'Master List';
To access the metadata in the didTransition hook:
didTransition: function() {
var metadata = routeMetaData[this.routeName];
}

I ended up solving this along the following lines, writing my own "route" function which records data I need, then passes it along to the DSL:
var myRouteData = {};
function route(dsl, name, options, fn) {
if (typeof options === 'function') fn = options, options = {};
var routeName = dsl.parent ? dsl.parent + '.' + name : name;
myRouteData[routeName] = { options.myRouteOption };
dsl.route(name, options.fn);
}
Usage:
this.resource('foo', function() {
route(this, 'bar', {myRouteOption: true});
});

Related

Ember component - TypeError: store is undefined

I am using ember 2.3. When I tried to access store inside a component, I am getting the following error in console.
This is what I have tried in component.js
export default Ember.Component.extend({
actions: {
saveEmployee : function() {
var store = this.store;
var newEmployee = store.createRecord("employee", {
fname: "Manu",
lname: "Benjamin",
email: "manu.benjamin#gmail.com",
department: "IT Services"
});
newEmployee.save().then(()=> {
console.log("Record successfully saved!!!!");
});
}
}
});
Do I need to include anything to use store in my component?
I was trying to figure out the reason for this type error. At last I found the solution from the following blog by Josh Farrant.
https://blog.farrant.me/how-to-access-the-store-from-within-an-ember-component/
we need to inject the store service in component.
store: Ember.inject.service(),
we can use the store injected in actions using the get function,
let store = this.get('store');
I modified my code using the above solution and it worked for me.
export default Ember.Component.extend({
store: Ember.inject.service(),
actions: {
saveEmployee : function() {
var store = this.get('store');
var newEmployee = store.createRecord("employee", {
fname: "Manu",
lname: "Benjamin",
email: "manu.benjamin#gmail.com",
department: "IT Services"
});
newEmployee.save().then(() => {
console.log("Record successfully saved!!!!");
});
}
}
});
Thanks Josh Farrant for the nice blog.
This topic has been already discussed multiple times and I am sure you can find many posts on the issue. Let me repeat shortly what has been mentioned already: try to avoid using store directly within a component.
In case your design allows you to manipulate Ember store in an associated route or a controller (where the store is btw available by default), try to send an action from a component so that it bubbles to the controller and performs a store manipulation in the controller. May
EmberJS guides - how to send actions from components help you with that.

"undefined" instead of ID in URL on transition in Ember and Ember Data

I have a pretty standard post model with a title and a text field. I have two routes for the model -- new route and show route. I want to create a post from new route and then transition to show route.
This is my router file
this.route('post-new', { path: '/posts/new' });
this.route('post-show', { path: '/posts/:postId' });
and submit action in post-new controller is something like this.
actions: {
submit() {
const { title, text } = this.getProperties('title', 'text');
let post = this.store.createRecord('post', {
title: title,
text: text
});
post.save().then(() => {
//success
this.transitionToRoute('post-show', post);
}, () => {
//error
});
}
}
So I am expecting this to redirect from http://localhost:4200/posts/new to something like http://localhost:4200/posts/23 (assuming 23 is id).
The save() is successful and record is created on the backend (which is rails) and I also see the post record updated in browser (it now has an ID) using Ember Inspector. But the redirection is happening to http://localhost:4200/posts/undefined.
How can I make this to redirect to something like http://localhost:4200/posts/23 after save ?
Btw, The versions are:
ember cli : 2.3.0-beta.1
ember : 2.3.0
ember data : 2.3.3
UPDATE
I was able to make it work by replacing this
this.transitionToRoute('post-show', post);
with this
this.transitionToRoute('/posts/' + post.id);
But I am hoping for a solution using the route name and not actual route path.
Try:
post.save().then(savedPost => {
//success
this.transitionToRoute('post-show', savedPost);
},
You can implement the serialize hook on your route.
serialize(model) {
return { postId: model.get('id') };
}
This will allow you to avoid calling the model hook if you already have the model. So, both of these will work as expected:
this.transitionToRoute('post-show', post); // this will NOT call model() hook
this.transitionToRoute('post-show', post.id); // this will call the model() hook
More information available in the API docs for Route.

Saving a user to a session as a computed property

I've seen other questions about this (like this one), and I believe this should be working
import Ember from 'ember';
import Session from 'simple-auth/session';
export default {
name: 'session-with-me',
before: 'simple-auth',
initialize: function() {
Session.reopen({
me: function() {
if (this.get('isAuthenticated')) {
return this.container.lookup('service:store').find('me', { singleton: true });
}
}.property('isAuthenticated')
});
}
};
the find('me', { singleton: true }) is a working patch of ember-jsonapi-resources. While debugging I can see the request being sent, and the payload comes through. I use the same find call elsewhere in the app, and can confirm a model gets instantiated fine.
On the inspector, under container > simple-auth-session I can see me as a session property, but it shows as { _id: 68, _label: undefined ...}
Has the way to set a session property changed? I may have seen a mention about this somewhere, but I can't find it anymore.
This is in the same domain of another question I asked earlier, but I'm giving up on that approach and trying simply to fetch the user independently of the authentication process.
Set up a custom session like that:
export default Session.extend({
me: function() {
var accessToken = this.get('secure.access_token');
if (!Ember.isEmpty(accessToken)) {
return DS.PromiseObject.create({
promise: this.container.lookup('service:me').find({});
});
}
}.property('secure.access_token')
});
// app/config/environment.js
ENV['simple-auth'] = {
session: 'session:me'
}
DS.PromiseObject is actually part of Ember Data which you're not using - I don't know whether there's an equivalent in the library you chose.
This is most likely an issue with ember-jsonapi-resources, not with Ember Simple Auth.
Instead of reopening the session though you should define your own one that extends the default one that Ember Simple Auth provides - see e.g. this answer: How to store the user in a session
We ended up making it work like this:
// app/sessions/me.js
export default Session.extend({
me: function() {
var accessToken = this.get('secure.access_token');
if (!Ember.isEmpty(accessToken)) {
let self = this;
return this.container.lookup('service:me').find({}).then((me) => {
self.set('me', me);
});
}
}.property('secure.access_token')
});
// app/config/environment.js
ENV['simple-auth'] = {
session: 'session:me'
}
Partly this was due to the way resource services are initialized in EJR (so #marcoow's hunch on this was correct), the other part was just bad coding on my part.
Interestingly we didn't have to explicitly register the session in the container

Ember organize routes & resources?

Hi guys i have bunch of images that i want to sort by 'Recent' or 'Popular' or 'Hot'.
For now i have a route which is defined like this:
App.Router.map(function () {
this.route("browse");
});
I wanted to do something like browse/recent to show the images by recent and browse/popular for the popular but I cant nest routes.
Shall I change my code so instead of the browse route ill have images resource?
And nest into it my filters? so ill have something like images/recent images/popular...
It seems like too many routes, maybe ill have in the future 10 filters does it mean ill have to create 10 different routes & controllers? cant i just use 1 controller and set a logic to filter(with ember-data)?
You should probably use a noun (images) as a resource name. You can then create multiple routes, each applying different filter on your data (different model hook), but each using the same controller / template. A simplified example:
First, create an images resource, with individual routes for your filters:
App.Router.map(function() {
this.resource('images', function () {
this.route('hot');
this.route('new');
});
});
Then, create a shared route, which will use hardcoded template and controller. The part with setupController is needed because the default controller will be (probably auto-generated) controller for ImagesNew or ImagesHot. You must take the given model and use it to set up shared ImagesController.
App.ImagesRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('images', {
controller: 'images'
});
},
setupController: function (_, model) {
this.controllerFor('images').set('content', model);
}
});
App.ImagesController = Ember.Controller.extend({
// your shared logic here
});
Finally, you can create filtering routes. Each should inherit the base ImagesRoute and provide its own filtered data in the model hook.
App.ImagesHotRoute = App.ImagesRoute.extend({
model: function () {
return this.store.getHotImages();
}
});
App.ImagesNewRoute = App.ImagesRoute.extend({
model: function () {
return this.store.getNewImages();
}
});
Working jsbin example here.
It's a best practice to start with a resource and then nest routes within it.
App.Router.map(function() {
this.resource('images', { path: '/' }, function() {
this.route('browse');
this.route('hottest');
this.route('popular');
});
});
As far as creating ten different controllers, that is not necessary. I'd imagine that the route logic will be different (HottestRoute will load the hottest photos, PopularRoute will load the most popular), but the controller logic should be the same. It is probably best to have named controllers, but they can just extend an already defined controlled.
App.ImagesPopularController = ImagesController.extend();

From Ember Pre1 to Pre4: Multiple dynamic segments per route? Update: What is the allowed syntax for dynamic segments?

I am currently trying to migrate my Ember based on pre1 to the current release pre4. In my pre1-code, i defined a route as follows:
formCreated : Ember.Route.extend({
route : '/genre=:genre/sorting=:sorting/location=:location/range=:range/time=:date/:timeFrame',
....
})
This Route worked fine for me, but now i am struggling to mimic this behaviour with pre4. This is my approach:
App.Router.map(function() {
this.route("/");
this.route("formCreated", { path: "/genre=:genre/sorting=:sorting/location=:location/range=:range/time=:date/:timeFrame" });
});
App.FormCreatedRoute = Ember.Route.extend({
serialize: function(context, params){
// here i am returning a hash containing all the dynamic segments
}
});
What is going wrong?
When the App enters the state, the URL does not get updated properly. I am seeing this result:
/genre=:genre/sorting=:sorting/location=:location/range=:range/time=:date/6:00-19:00
So most of my dynamic segments do not get updated. I made sure that my custom serialize method is returning an appropriate hash object, where one property for each dynamic segment is set.
Are multiple dynamic segments per route still possible with pre4 or do i have to switch to some route nesting approach or something like that?
UPDATE: Root cause found:
I just discovered that the error happened because of the syntax i used for the route. I changed it to the following(replaced the "=" with "/"):
this.route("formCreated", { path: "/genre/:genre/sorting/:sorting/location/:location/range/:range/time/:date/:timeFrame" });
Is there any documentation on how the path may be structured? It seems that syntax has changed since ember-pre1. I would like to have a user friendly URL and those numerous Slashes make it difficult to read. Or is the rule, that a segment always has to start with ":/"?
You will need to use resource nesting, like described here and here
App.Router.map(function() {
this.route('/');
this.resource('genre', { path: '/genre/:genre_id' }, function(params) {
this.resource('sorting', { path: '/sorting/:sorting_id' }, function(params) {
...
});
});
});