Is it possible to create a nested adapter in EmberJS? - ember.js

Am I able to nest Ember Data adapters?
For example, say if I have a model //models/site.js with a template //templates/site.hbs and I want to make a custom query() request to ${ENV.APP.API_HOST}/customers/${org}/sites - as per the docs I can simply customise the query() function at //adapters/site.js.
But what if I have a second template at //templates/sites/show.hbs and I need to query a second distinctly different endpoint such as ${ENV.APP.API_HOST}/customers/${org}/sites/${id} (or any other deeply nested data endpoint) can I setup an adapter under //adapters/sites/show.js? I can't seem to achieve that with Ember Data currently.

As far as I know, Ember doesn't support nested endpoints at the moment. Related discussions: 1, 2.

So I was able to customise and fix this by using an ember plugin - https://github.com/amiel/ember-data-url-templates/. It has good documentation and allows you to customise URL segments.
My site adapter
// adapters/site.js
export default ApplicationAdapter.extend({
urlTemplate: '{+host}/api/{apiVersion}v1/customers{/org}/sites{/site}',
queryUrlTemplate: '{+host}/api/{apiVersion}v1/customers{/org}/sites'
});
And my service adapter
// adapters/service.js
export default ApplicationAdapter.extend({
urlTemplate: '{+host}/api/{apiVersion}v1/customers{/org}/services{/service}',
});
Then in my routes I loaded params that were picked up by the URL segments in my adapters thanks to ember-data-url-templates. Using seperate queryRecord() calls with Ember store allowed me to specify the correct endpoints as required.
// routes/sites.js
export default Ember.Route.extend({
model: function(params) {
let siteQuery = this.modelFor('sites');
let org = siteQuery.customer_orgCode;
return RSVP.hash({
site: this.get('store').queryRecord('site', { org: org, site: params.site_id })
});
}
});
// routes/sites/show.js
export default Ember.Route.extend({
model: function(params) {
let siteQuery = this.modelFor('sites');
let org = siteQuery.customer_orgCode;
return Ember.RSVP.hash({
service: this.get('store').queryRecord('service', { org: org, service: params.instance_id })
});
}
});
NB // I've use an RSVP hash as there's likely to be multiple calls for the same model, but you can just return the this.get query as necessary directly to model: as well.

Related

Shared data between various controllers

I have a project where I need to build an Ember application. The application will have many routes and some of the routes will have some model.
My problem at the moment is that some information is global, meaning they are present in each page (.hbs) and I need to update it periodically.
I've tried to put information on the application route like the following but it didn't work, the content is not accessible on other routes:
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON('/api/users/current')
}
});
I've also tried to reload the information with a setInterval but this didn't work either.
import Ember from 'ember';
export default Ember.Route.extend({
init: function() {
var thyself = this;
var interval = setInterval(function(){
thyself.my_reload()
}, 1000);
this.set('interval', interval);
this.set('counter', {});
},
my_reload: function() {
var counter = this.get('counter');
if (counter >= 10) {
clearInterval(this.get('interval'));
}
this.set('data', Ember.$.getJSON('/api/users/current'));
}
});
Where can I place this information so it will be available on all routes? And how can I reload the information periodically?
I'm using ember-cli
#NicosKaralis,
you should use service for it.
You can generate it by command: ember generate service name-of-service
And there you should create methods.
When you want to get access from your controller you should inject it in your controller:
nameOfService: Ember.inject.service(), (remember camelCase here)
and if you want some method from your service in your controller you will use it like this (example with computed property, you can also use it without computed property):
someComputedFunctionInController: Ember.computed(function() {
this.get('nameOfService').yourFunctionFromService();
},
nextComputedFunctionInController: Ember.computed(function() {
this.get('nameOfService.getSomethingFromService');
}
for more:
https://guides.emberjs.com/v2.7.0/tutorial/service/
Hope, it will help you.

Ember.js: Load related multiple models

Since the ember-guides explains how to load mutliple models on a route like that
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
songs: this.get('store').findAll('song'),
albums: this.get('store').findAll('album')
});
}
});
Im wondering how to load only the related model-entries from a second one, like loading ALL songs but only the albums which are indexed in the songs if we assume that the song model containing this
...
albums: hasMany('album'),
...
How can I do that?
Assuming your adapter and JSON API backend support it, you can simply say:
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
songs: this.get('store').findAll('song', { include: 'albums' }),
});
}
});
Typically, this will generate a GET to /songs?include=albums, which tells the JSON API backend to include the related album resources, according to http://jsonapi.org/format/#fetching-includes.
On the Ember side of things, this feature is documented at http://emberjs.com/blog/2016/05/03/ember-data-2-5-released.html#toc_code-ds-finder-include-code.
If the above isn't an option, then there's no way to load everything in one request without building a custom endpoint and using store.pushPayload.
Here is one way to do it
export default Ember.Route.extend({
model() {
var promise = new Ember.RSVP.Promise(function(resolve,reject){
this.store.findAll('song').then(function(songs){
var albumPromises = songs.map(fuction(s){return s.get('album')});
Em.RSVP.all(albumPromises).then(function(){
resolve(songs);
});
});
});
return promise;
}
});
So Basically you are waiting till everything is resolved.
Hope it helps!

Ember Data usage without a Backend API and converting Models to JSON (Ember 2.5.0)

I am using Ember v2.5.0 without an external datastore.
After creating a record at the route using the createRecord method it cannot be queried for in other parts of the app (controller or components).
My understanding is that I need to use store.push to save the record locally so that it may be accessed by the controller. However the store.push method requires the arguments to be in json format.
I could just do away with the models however I was wondering if there a quick way to convert the models into json format using Ember version 2.5.0?
I would also like to know if my assumptions on using store.push to persist the data locally is a recommended way to go when using Ember Data without an external backend.
There are other references on "Ember models to json" on stack overflow however they are outdated and I particularly would like to know if my approach/assumptions are correct and if not, what the alternatives are. Im very new to Ember.
Problem
//Route
import Ember from 'ember';
export default Ember.Route.extend({
model() {
let shape, square;
square = this.store.createRecord('square');
shape = this.store.createRecord('shape', {
shared: 'shared-value',
square: square
});
return shape;
}
});
//Controller
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
someActionName() {
console.log(this.store.peekRecord('shape', 1)); //undefined!
}
}
});
//Shape Model
import DS from 'ember-data';
export default DS.Model.extend({
shared: DS.attr('string', { defaultValue: '' }),
square: DS.belongsTo('square')
});
//Square Model
import DS from 'ember-data';
export default DS.Model.extend({
sides: DS.attr('string', { defaultValue: '4' }),
whereIbelong: DS.belongsTo('shape')
});
I don't think using store.push is a good long-term approach, it's better to use the save and destroyRecord methods, as Ember Data expects.
You can create an adapter from the base Adapter using local storage, or maybe just returning the record passed in createRecord/updateRecord works.
Experiment with it and find what works better for your use case, adapters are very flexible.
As a side note, the best way I found to make store.push work as expected is like this:
var obj = {
id: '1',
name: "object name",
// More object properties...
};
store.push(store.normalize('model-name', obj));
createRecord does not add an ID. So this.store.peekRecord('shape', 1) is undefined because your record created does not have an ID. Everything works well if you set an ID for your record.
Ember Twiddle: https://ember-twiddle.com/e2b24b5a2cdab19c7b7401c57aff9959?openFiles=controllers.application.js%2C
If your goal is to persist records on client side have a look at ember-local-storage.

Ember data - change model URL

I am using ember 2.0 and ember-data 2.0, and I have been struggling to find a way to pass custom URL to model.
For example, if my model is named Person and stored in model/person.js file, I would like rest web service url for finding record to be xxx/user/1, or in other words to avoid convention, and pass my URL to rest service - is is possible at all?
You can use Adapter.
If your backend conventions differ from Ember Data convention, it easy to change its functionality by swapping out or extending the default Adapter.
App.ApplicationAdapter = DS.RESTAdapter.extend({
namespace: 'api/v1',
pathForType: function(type) {
return Ember.Inflector.inflector.singularize(type);
}
});
If you want just override a specific model just write new adapter with modelName + Adapter.
When I want to use a custom adapter for a 'note' model I can do something like:
App.Note = DS.Model.extend({
title: DS.attr('string'),
/* others attrs */
});
App.NoteAdapter = DS.RESTAdapter.extend({
namespace: 'other/endpoint',
pathForType: function(type) {
return Ember.Inflector.inflector.pluralize(type);
}
});
Take a look at ember adapter guide, if you use ember-cli use blueprint generator like:
ember generate adapter user

Get current route name in Ember

I need to get the current route name in my ember application; I tried this:
Ember App.Router.router.currentState undefined
but it doesn't work for me (there is probablig something i'm missimg...) I use Ember rc6 and I have a multilingual app; in the applicationRoute I detect the browser's language and I redirect to the correct page with:
this.transitionTo(userLang);
but I would like this to be executed only when user are on the home page, so something like this:
if (currentRoute == 'home'){
this.transitionTo(userLang)
}
NOTE: as of Ember 3.16, the original answer is not only recommended, but observers are strongly discouraged.
To get the current route name, you can utilize the Router Service: https://api.emberjs.com/ember/3.18/classes/RouterService/properties/currentRouteName?anchor=currentRouteName
export default class MyComponent extends Component {
#service router;
get activeRoute() {
return this.router.currentRouteName;
}
}
Original answer below
You could observe the application's currentPath and set it to the current route accordingly when it changes:
App = Ember.Application.create({
currentPath: '',
});
App.ApplicationController = Ember.Controller.extend({
updateCurrentPath: function() {
App.set('currentPath', this.get('currentPath'));
}.observes('currentPath')
}),
This way you have access to the currentPath when ever you want with App.get('currentPath');
E.g.
if (App.get('currentPath') == 'home'){
this.transitionTo(userLang);
}
Hope it helps.
This worked for me on 1.3.0-beta (and a quick glance at the source for 1.1.2 suggests it would work there too):
App.__container__.lookup('router:main').location.lastSetURL
Note that the documentation states:
At present, it relies on a hashchange event existing in the browser.
However, I believe it's strongly suggested that App.__container__ not be used in production code. A more acceptable alternative would be to use App.Router.router.currentHandlerInfos, which provides information on the current Ember route.
Yet another option is currentRouteName on the ApplicationController. You can add needs: ['application'] to your controller, then access the route name with controllers.application.currentRouteName. This will return something like posts.index.
With the shift to components, it is harder to get route name. The best way is to add an initializer such as
ember g initializer router
(from command line), and
export function initialize(application) {
application.inject('route', 'router', 'router:main');
application.inject('component', 'router', 'router:main');
}
export default {
name: 'router',
initialize
};
in a initializers/router.js. You can also inject into controller if you need to. Then just do simply
this.get('router.currentRouteName');
in JS, or
{{router.currentRouteName}}
in template.
This is the only way I have found to get it reliably, and observable in Ember 2.4
If you want to get current route in your component or controller you can inject routing service (routing: Ember.inject.service('-routing'))
(for more) and use:
this.get('routing.currentRouteName') or this.get('routing.currentPath')
Example with component and computed property:
import Ember from 'ember';
export default Ember.Component.extend({
routing: Ember.inject.service('-routing'),
checkMyRouteName: Ember.computed('routing.currentRouteName', function() {
return this.get('routing.currentRouteName');
})
})
Example with controller and computed property:
import Ember from 'ember';
export default Ember.Controller.extend({
routing: Ember.inject.service('-routing'),
checkMyRouteName: Ember.computed('routing.currentRouteName', function() {
return this.get('routing.currentRouteName');
})
})
Current route in your route you just need this.routeName
Example with route:
import Ember from 'ember';
export default Ember.Route.extend({
checkMyRouteName() {
return this.routeName;
}
})
Just as an update, in Ember 1.8.1, we can get the routeName inside an Ember.Route object by doing this.routeName.
Currently as of Ember 1.7.0 you can get the current route from within a route by calling this.routeName.
The Ember namespace API now has a getOwner method, which is very useful for looking up the currentRouteName, or, other route properties.
const owner = Ember.getOwner(this);
const currentRoute = owner.lookup('router:main').currentRouteName;
const routeInfo = owner.lookup(`route:${currentRoute}`).get('info');
// etc.
I've created an Ember Twiddle example to demonstrate. Use the text input above the "Output" pane to hit other routes like /blue, /green, or /red.
Ember has a RouterService since 2.15. It provides the name of the current route as currentRouteName property. A polyfill exists for Ember 2.4 - 2.14 if you are still on such an old version.
import Component from '#ember/component';
export default Component.extend({
router: service(),
isHomeRoute: computed('router.currentRouteName', function() {
return this.router.currentRouteName === 'home';
}),
});
All other solutions mentioned here are relying on private API that might already be deprecated / removed. Using RouterService is working at least up the current version, which is 3.12 at the time of writing this.
Please note that the "home" is not /. The root URL is called "index".
I had the same problem for a while. then i started exploring router. It always have a state object which can be obtained from any route using
var route = this;
var handlerInfos = route.get("router.router.state.handlerInfos");
var currRouteHandlerInfo = handlerInfos[handlerInfos.length-1];
var currRouteName = currRouteHandlerInfo.name; //"home"
that's it. Now you have the current route name!
if you want the current route params,
var routerParams = this.get("router.router.state.params");
var currRouteParams = routerParams[currRouteName]; //{ homeId : "1" }
You can simple parse the current URL. This way you can use your full url for example:
http://127.0.0.1:8000/index.html/#/home
and extract from this string the suffix:
/home
which is the current route name.
A simple JS function (that works regardless to your Ember version) will be:
function getCurrentRoute()
{
var currentRoute;
var currentUrl = window.location.href; // 'http://127.0.0.1:8000/index.html/#/home'
var indexOfHash = currentUrl.indexOf('#');
if ((indexOfHash == -1) ||
(indexOfHash == currentUrl.length - 1))
{
currentRoute = '/';
}
else
{
currentRoute = currentUrl.slice(indexOfHash + 1); // '/home'
}
return currentRoute;
}
Example of use:
if (getCurrentRoute() == '/home')
{
// ...
}