EmberJS: Object as Query Param to Refresh Model - javascript

I followed the Query Params guide (http://guides.emberjs.com/v1.11.0/routing/query-params/) and it worked great. Specifically, refreshing the model did exactly what I wanted.
I'm moving the filter to the json-api spec and filtering takes place in a filter object. So rather than:
http://localhost:3000/accounts?id=1
The server responds to:
http://localhost:3000/accounts?filter[id]=1
I tried to get the query params to work refreshing the model based on an object, but it doesn't seem to update.
// app/controllers/accounts/index.js
import Ember from 'ember';
export default Ember.Controller.extend({
queryParams: ['filter', 'sort'],
filter: {},
sort: '-id'
});
// app/routes/accounts/index.js
import Ember from 'ember';
export default Ember.Route.extend({
queryParams: {
filter: { refreshModel: true },
sort: { refreshModel: true }
},
model: function(params) {
return this.store.find('account', params);
},
});
// template
<th>{{input type="text" placeholder="ID" value=filter.id}}</th>
Is it possible to have query params work with an object?

This answer is as of Ember version 1.13.0-beta.1+canary.
The short answer: No. Query params will not work with an object.
The long answer:
As of now, a private function named _serializeQueryParams in the Router serializes the queryParams.
_serializeQueryParams(targetRouteName, queryParams) {
var groupedByUrlKey = {};
forEachQueryParam(this, targetRouteName, queryParams, function(key, value, qp) {
var urlKey = qp.urlKey;
if (!groupedByUrlKey[urlKey]) {
groupedByUrlKey[urlKey] = [];
}
groupedByUrlKey[urlKey].push({
qp: qp,
value: value
});
delete queryParams[key];
});
for (var key in groupedByUrlKey) {
var qps = groupedByUrlKey[key];
var qp = qps[0].qp;
queryParams[qp.urlKey] = qp.route.serializeQueryParam(qps[0].value, qp.urlKey, qp.type);
}
},
qp.urlKey would evaluate to 'filter' in your example, and object would be serialized as 'object [Object]'. Even though you could override the serializeQueryParam method in your route, that wouldn't help because the queryParam key would still be 'filter', and you'd need it to be 'filter%5Bid%5D'
Based on this comment in the Ember Discussion Forum, it sounds like object query params are unlikely, and you'd be better off just flattening and unflattening the filtered fields.

I know this is a bit late but you can use this workaround to allow for objects with query params. It's pretty easy to get working and so far I haven't found any issues with it.
I ran into the same problem when building an Ember app on top of my JSON API (http://jsonapi.org/).
The JSON API specification provides recommended syntax for both paging and filtering that requires object based query params.
For paging it suggests syntax like this:
/books?page[size]=100&page[number]=1
and for filtering it suggest syntax like this:
/books?filter[author]=Bob
While Ember.js Query Params (as of Ember v2.1) do not support this out of the box it is fairly simple to get working. In your controller you should map a controller property to the query param "object" as a string.
So for example, in the above "filter" example you would map a controller property called filterByAuthorValue to the query param filter[author].
The code to do this would look like this:
export default Ember.Controller.extend({
queryParams: ['sort',{
filterByAuthorValue: 'filter[author]'
}],
sort: '',
filterByAuthorValue: ''
});
Note in the example above I also have a query param called sort (which also follows JSON API recommendations but doesn't require an object). For more information on mapping a controller property to a query param see this section of the official Ember.js guide:
http://guides.emberjs.com/v2.1.0/routing/query-params/#toc_map-a-controller-s-property-to-a-different-query-param-key
Once you have the query param created you then need to handle the query param in your router. First, the router should force the model to be refreshed when the controller property filterByAuthor changes:
export default Ember.Route.extend({
queryParams: {
sort: {
refreshModel: true
},
filterByAuthor:{
refreshModel: true
}
}
});
Finally, you now need to translate the controller property filterByAuthor into an actual object when you load the model in the router's model method and assign the value from the controller property filterByAuthor. The full router code would then look like:
export default Ember.Route.extend({
queryParams: {
sort: {
refreshModel: true
},
filterByAuthor:{
refreshModel: true
}
},
model: function(params){
// The params value for filtering the entity name
if(params.filterByAuthor){
params.filter = {};
params.filter['author'] = params.filterByAuthor;
delete params.filterByAuthor;
}
return this.store.query('book', params);
},
});
Settings things up like this allows for an object based query param to be used with Ember and thus follow the JSON API recommendations.
The above has been tested with the following versions:
Ember : 2.1.0
Ember Data : 2.1.0
jQuery : 1.11.3

Related

Correct way to make an ajax call from EmberJs component?

I would like to know what is the correct way to make an ajax call from an ember component. for example
I want to create a reusable component that makes an employee search by employee's Id, then when the response comes back from the server I want to update the model with the data from the ajax response.
I donĀ“t know if this is the correct way to do it, I'm really new on emberjs.
export default Ember.Component.extend({
ajax: Ember.inject.service(),
actions: {
doSearch() {
showLoadingData();
var self = this;
this.get('ajax').post('http://server.ip.com/api/v1/getEmployee', { "id": this }).then(function(data) {
self.set('model.name', data.name);
self.set('model.position', data.position);
hideLoadingData();
});
}
}});
EDIT: I misunderstood the question, so here's an updated version of my answer:
First, I think you should switch to using ember-data. Then fetching an employee by id would just resolve to calling this.get("store").find("employee", id).
If you wish to use plain ajax, I suggest that you create a Service that encapsulates specifics (API endpoint URL, data format, and so on) and only exposes simple methods for finding and updating models.
And finally, to comply with the "data down, actions up" pattern, you shouldn't update the model in this component. Instead send an action to the parent controller/component. Like so:
app/components/employee-selector.js (the component you're writing):
export default Ember.Component.extend({
actions: {
updateId(id) {
Ember.$.post("http://server.ip.com/api/v1/getEmployee", { id: params.id }.then((response) => {
this.sendAction("select", response);
});
}
});
app/templates/new/it-request.hbs:
{{employee-selector select=(action "selectEmployee")}}
app/controllers/new/it-request.js:
export default Ember.Controller.extend({
actions: {
selectEmployee(employeeData) {
this.set("model.name", employeeData.name);
this.set("model.position", employeeData.name);
}
}
});
Old answer:
An idiomatic solution would be to do this in a Route.
First you should add a route in app/router.js:
this.route("employees", function() {
this.route("show", { path: ":id" });
}
Than define the route in app/employees/show/route.js:
import Ember from "ember";
export default Ember.Route.extend({
model(params) {
return new Ember.RSVP.Promise((resolve, reject) {
Ember.$.post("http://server.ip.com/api/v1/getEmployee", { id: params.id }.then(
(response) => { resolve(response) },
reject
);
});
}
});
(The only reason I wrapped everything in a new promise is to allow response customization - just replace resolve(response) with a code that transforms the raw response from the server and invoke resolve with this transformed version).
But if you'll have more communication with the API, and I suppose that you will, I suggest that you try using ember-data or any other data layer library for ember (probably Orbit).
Or at least write a service that abstracts away all communication with the API and use it anywhere you'd use raw ajax requests.
I was using Ember class directly in action so it looked like this
actions: {
doSomething() {
Ember.$.post('http://your-api-endpoint', data).then(function(response){ /* your callback code */});
}
}
And other way to communicate with BE is to use Ember Store (as you said) then in route you can get model from BE
example
App.PressRoute = Ember.Route.extend({
route: "press",
controllerName: 'press',
model: function(params) {
var controller = this.controllerFor("Press");
if(controller.get("loaded") == false) {
controller.set("loaded",true);
return this.store.find('Article',{limit: 200});
}
else return this.store.all('Article');
},
renderTemplate: function() {
this.render('press');
}
});
There are several ways you can do this!
Firstly, Ember has a convient alias for jQuery: Ember.$. So, if you're familiar jQuery, this should be easy enough.
You can also use Ember's RSVP package. There's a good example here on how to make a request and do something with the response.
Thirdly, you can the ember-ajax service.
But what you're asking (update the model with the data from the ajax response) is already built into Ember Data. You'll need to map your API into what ember expects with an adapter and/or a serializer. Once your service is transformed into what Ember expects, you can query your server for a single record then save it.

Ember How to retain query parameters while manually refreshing the page?

I am using ember 2.7.0.while manually refreshing the page ember clears the ember-data as well us query parameters, so i am unable to load the page in setupController while refreshing. Is there any possible way to retain both model & query parameters, at least retaining query parameter would be fine to reload my page.
route.js
model(params) {
return this.store.peekRecord("book",params.book_id);
},
setupController(controller,model,params){
if(!model){
//fetch book record again if the model is null
}
controller.set('isdn',params.queryParams.isdn);
controller.set('book',model);
}
Any help should be appreciable.
Edited setupController as per Adam Cooper comment :
setupController(controller,model,params){
var isdn = params.queryParams.msisdn;
controller.set('isdn',isdn);
if(!model){
this.store.findRecord('book', isdn).then((customer) => {
this.set('book',customer);
},(resp,status) => {
this.set('errorMessage', `Book with this ${isdn} does not exist.`);
this.set('book', []);
});
}else{
controller.set('device',model);
}
}
Page gets rendered before "findRecord" returning promise.Is there any way to stop page rendering till find record resolves the promise?
You are setting in route properties instead of controller..
setupController(controller, model, params){
var isdn = params.queryParams.msisdn;
controller.set('isdn', isdn);
if(!model){
this.store.findRecord('book', isdn).then((customer) => {
controller.set('book', customer);
}, (resp, status) => {
controller.set('errorMessage', `Book with this ${isdn} does not exist.`);
controller.set('book', []);
});
}else{
controller.set('device', model);
}
}
Only the controller properties will decorate template.
You can even try the below, why don't you give opportunity to model hook to resolve since that will wait for the Promises to resolve.
model(params) {
var result = this.store.peekRecord("book",params.book_id);
if(result !== null){
result= this.store.findRecord('book', params.book_id)
}
return result;
}
setupController(controller,model){
controller.set('book',model);
}
You will need to generate an actual controller for your route and then define a queryParams property in the controller. It looks like the query param you're trying to hold onto is isdn so your controller should look something like:
export default Ember.Controller.extend({
queryParams: ['isdn']
});
"manually refreshing the page ember clears the ember-data as well us query parameters"
Once you completely refresh the browser, a new ember app instance is created and hence ember-data cannot be retained. Ember-data is just for the app on the UI, once ember is exited it will not be retained.
"as well us query parameters"
your query params are part of your url and it should not get cleared. Make sure the below two are present
Include queryParams in ur controller i.e.
queryParams: ['param1', 'param2']
And in your route make sure you have done
queryParams : {
param1: {
refreshModel: true
},
param2: {
refreshModel: true
}
}
"Page gets rendered before "findRecord" returning promise"
You are not doing something right, is the adapter, model, serializer etc defined correctly(if required) in order to use findRecord? Just to debug return a plain object and make sure ur setupController is called before rendering. i.e.
model() {
return {dummy: 'dummy'};
}

ember.js, filtering PromiseManyArray in the controller

I am trying to define a computed property that consists of a filtered hasMany relationship. When I loop over the items of the PromiseManyArray, I get undefined when trying to access the attribute I want to filter on. On later calls to this computed property, everything works fine.
This is a simplified version of my controller code:
export default Ember.Controller.extend({
availableModules: function () {
let thisModule = this.get('model')
console.log(thisModule.get('library.modules')) // This logs <DS.PromiseManyArray:ember604>
// loop over siblings
return thisModule.get('library.modules').filter(mod => {
// mod.classification is undefined
return mod.get('classification') !== 'basis'
})
}.property('model')
})
For the Module model we can assume that it has a classification attribute, and it belongs to a Library object, and the Library model hasMany modules.
I have tried something like this, and it logs properly the attribute classification, but I don't know how to return anything so that the template can render it.
availableModules: function () {
let thisModule = this.get('model')
thisModule.get('library.modules').then(mods => {
mods.forEach(mod => {
console.log(mod.get('classification'))
})
})
}.property('model')
So the problem seems to be that inside of the PromiseManyArray.filter method, the attributes of the found objects are not yet resolved... How can I create a promise that will return all filtered objects once those have been resolved? I don't know how to get my head around this. Thanks.
Inspired by Bloomfield's comment, and with help of this thread in the ember forum, I have found an acceptable solution. Basically it consists of resolving all the relationships in the route, so that when the controller is called, you don't have to deal with promises.
Solution:
In the model hook of the route, return a hash of promises of all the needed information
Define a custom setupController, and inside of it, store the model and the extra data in the controller
The route code looks like this:
export default Ember.Route.extend({
model(params) {
let module = this.store.findRecord('module', params.mod_id)
return Ember.RSVP.hash({
module: module,
siblingModules: module.then(mod => mod.get('library.modules')), // promise based on previous promise
})
},
setupController(controller, hash) {
controller.set('model', hash.module)
controller.set('siblingModules', hash.siblingModules)
},
})
Note: for the route to still work properly, the {{#link-to 'route' model}} have to explicitly use an attribute, like the id: {{#link-to 'route' model.id}}
Extra info
Bloomfield's approach consisted of using the afterModel hook to load the extra data in an attribute of the Route object, and then in the setupController, set the extra data in the Controller. Something like this:
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('module', params.mod_id)
},
afterModel(model) {
return model.get('library.modules').then(modules => {
this.set('siblingModules', modules)
})
},
siblingModules: null, // provisional store
setupController(controller, model) {
controller.set('model', model)
controller.set('siblingModules', this.get('siblingModules'))
},
})
But this feels like a hack. You have to return a promise in afterModel, but you can't access the result. Instead the result has to be accessed via .thenand then stored in theRoute` object... which is not a nice flow of information. This has however the advantage that you don't have to specify any attribute for the links in the template.
There are more options like using PromiseProxyArray, but that's too complicated for a newcomer like me.
For anyone running into PromiseManyArray issues in modern times, make sure you have async: false explicitly set on any hasMany relationships directly serialized by the API. Modern versions of Ember will behave unexpectedly if you don't, such as computed properties not working when you use pushObject.

Ember route or controller expose data model as json

I am trying to work with Ember.js
Can I expose my data model as JSON through a route or controller?
I have an object like this saved in the store:
this.store.createRecord('Person', {
id: 1,
name: this.get('name'),
email: this.get('email')
});
I want to expose this data from a route or controller as JSON object. I don't want to use any view.
Is it possible to do this?
Thanks for help!
EDIT
My route is:
App.ResultRoute = Ember.Route.extend({
model: function() {
return this.store.find('person', 1);
}
});
There is '1' because I want only this record.
In this way It works and I see in the view the {{name}} and the {{email} of the Person object.
I want to see only the JSON, I tried to do how you suggest me :
App.ResultRoute = Ember.Route.extend({
afterModel: function (model) {
model.get('content').forEach(function (item) {
console.log(item.get('content'));
});
}
});
But I receive this error:
Uncaught Error: Assertion Failed: Error: More context objects were passed than there are dynamic segments for the route: error
What is my error?
The way I would do this would be, I would have an api in my model which would return a plain json object to whoever asked it. So the Person model would have a getPersonDetails method which will hide all the internal details, including the attributes and associations and whatever else, and return the state of the person object it is invoked upon.
So, for example, if you wanted to display a table of persons or something, you would do a createRecord, and just ask the newly created person object for it's details.
Start from the beginning of this guide. http://emberjs.com/guides/routing/specifying-a-routes-model/ It will show you how to specify a model for a route.
Then, read this entire guide on controllers: http://emberjs.com/guides/controllers/
In general, you would access that data from the route's model hook with:
this.store.find('person') // All records
If you wanted to access that first object as JSON, you could do:
var person_JSON = this.store.find('person').then(function (persons) {
//The persons records are now available so you can do whatever you want with them
console.log(persons.objectAt(0).get('content'));
});
You could also iterate over all records and strip out the content to produce raw json without the Ember wrapping... Just depends on what you need to really do.
Really the best place to put this would be the route's afterModel hook, though. You wouldn't be working with a promise, as Ember would have dealt with that for you:
afterModel: function (model) {
model.get('content').forEach(function (item) {
console.log(item.get('content'));
});
}
Hope that helps.
Edit: Since you have one record try this:
afterModel: function (model) {
console.log(model.get('content'));
}

Creating and updating nested objects in Ember

I got nested JSON data from the server like this:
{
name: "Alice",
profile: {
something: "abc"
}
}
and I have the following model:
App.User = Ember.Object.extend({
name: null,
profile: Ember.Object.extend({
something: null
})
})
If I simply do App.User.create(attrs) or user.setProperties(attrs), the profile object gets overwritten by plain JS object, so currently I'm doing this:
var profileAttr = attrs.profile;
delete attrs.profile
user.setProperties(attrs); // or user = App.User.create(attrs);
user.get('profile').setProperties(profileAttrs);
It works, but I've got it in a few places and in the real code I've got more than one nested object, so I was wondering if it's ok to override User#create and User#setProperties methods to do it automatically. Maybe there's some better way?
Based on your comment, you want the automatic merging behaviour you get with models (the sort of thing you get with .extend()). In that case, you could try registering a custom transformer, something like:
App.ObjectTransform = DS.Transform.extend({
deserialize: function(json){
return Ember.Object.create(json);
}
});
App.User = DS.Model.extend({
profile: DS.attr('object')
});
See: https://github.com/emberjs/data/blob/master/TRANSITION.md#json-transforms
If you are doing your server requests without an adapter you can use the model class method load() with either an array of json objects or a single object. This will refresh any known records already cached and stash away the JSON for future primary key based lookups. You can also call load() on a model instance with a JSON hash as well but it will only update that single model instance.
Its unclear why you are not using an adapter, you can extend one of the Ember Model adapters and override the the record loading there, eg. extend from the RESTAdapter and do any required transform on the JSON if required by overriding _loadRecordFromData
You can also override your models load function to transform data received if required as well. The Ember Model source is fairly easy to read so its not hard to extend to your requirements.

Categories