Detecting when model update and send Ajax Request in Emberjs - ember.js

In my application i have model and i want when model update then send an ajax Request to server. for example i have a model like this :
App.Post = DS.Model.extend({
title:DS.attr('string'),
comment:DS.attr('string')
});
And controllers like this :
App.PostController = Ember.Controller.extend({
//if post model update send Ajax Request
});
App.CommentController = Ember.Controller.extend({
//if post model update send ajax Request
});
App.OtherController = Ember.Controller.extend({
//if post model update send ajax Request
});

You can define a action in the route
App.ApplicationRoute = Ember.Route.extend({
actions:{
sendRequest:function(){
//send Ajax Request
}
}
});
and in the setupController hook of your post route you subscribe to model events
App.PostRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('model', model);
model.on('didUpdate',controller,function(){this.send('sendRequest')})
model.on('didCreate',controller,function(){this.send('sendRequest')})
model.on('didDelete',controller,function(){this.send('sendRequest')})
}
});
hope this help, it's only an example, you can do it in several ways, the idea is to subscribe to model events that fire when the server respond to POST, PUT or DELETE requests
based on your JsBin....
OlapApp.OlapRoute = Ember.Route.extend({
setupController(controller,model){
var that = this;
this.store.findAll('axisModel').then(function(axisModel){
axisModel.on('didUpdate',controller,function(){this.send('sendRequest')})
axisModel.on('didCreate',controller,function(){this.send('sendRequest')})
axisModel.on('didDelete',controller,function(){this.send('sendRequest')})
that.controllerFor('OlapColumn').set('model', axisModel);
that.controllerFor('OlapRow').set('model', axisModel);
that.controllerFor('OlapFilter').set('model', axisModel);
});
}
});

Related

Ember.js : Modify request URL

I create an Ember app with a Symfony REST api. I also use the REST adapter for my requests.
I have 2 models in my app : users and their related comments.
I already created CRUD operations for my user properties and now I focus on the CRUD operations for comments.
Model user.js
export default DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
comments: DS.hasMany('comment')
});
Model comment.js
export default DS.Model.extend({
title: DS.attr('string'),
message: DS.attr('string'),
user: DS.belongsTo('user')
});
I have a route which shows all comments (and other data) of a given user. The request loads the user object and his relations. On the view I also have a form and an action to create a new comment for this user.
Route users/get.js
import Ember from 'ember';
export default Ember.Route.extend({
id: null,
model(params) {
this.set('id', params.user_id);
return this.get('store').findRecord('user', params.user_id, {include: 'comments'});
},
});
Route users/get/comments.js
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.modelFor('user.get', params.user_id);
},
});
Controller users/get/comments.js
import Ember from 'ember';
export default Ember.Controller.extend({
newComment: null,
user: null,
init: function() {
this._super(...arguments);
let comment = this.store.createRecord('comment');
this.set('newComment', comment);
},
actions: {
saveComment: function() {
let user = this.get('model');
let comment = this.get('newComment');
comment.set('user', user);
comment.save();
}
}
});
Everything works, except for the request sent to the backend. I loaded the comments from the user, so I expect a call to :
POST http://my-app.local/users/comments/
Instead the call is sent to :
POST http://my-app.local/comments/
Do you know why and how could I correct it ?
Second problem, the model is loaded from the 'user.get' route. This works because the user comes from this route to this page, but... It's doesn't work if the user enters directly the URL for comments. That sound logical, but I have no clue how to correct this problem... Can you help me ?
This can be done by rewrite the CommentAdapter.urlForCreateRecord method. Which affect new comment record requests.
adapters/comment.js
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
urlForCreateRecord(modelName, snapshot) {
return '/users/comments'; // the url you want
}
});
There are several urlFor... method you might need to customise your url.
Just check out the document
http://devdocs.io/ember/classes/ds.buildurlmixin/methods#urlForCreateRecord

Ember model hook not re-evaluated when transitioning to current route [duplicate]

While I am not new to web development, I am quite new to to client-side MVC frameworks. I did some research and decided to give it a go with EmberJS. I went through the TodoMVC guide and it made sense to me...
I have setup a very basic app; index route, two models and one template. I have a server-side php script running that returns some db rows.
One thing that is very confusing me is how to load multiple models on the same route. I have read some information about using a setupController but I am still unclear. In my template I have two tables that I am trying to load with unrelated db rows. In a more traditional web app I would have just issued to sql statements and looped over them to fill the rows. I am having difficulty translating this concept to EmberJS.
How do I load multiple models of unrelated data on the same route?
I am using the latest Ember and Ember Data libs.
Update
although the first answer gives a method for handling it, the second answer explains when it's appropriate and the different methods for when it isn't appropriate.
BEWARE:
You want to be careful about whether or not returning multiple models in your model hook is appropriate. Ask yourself this simple question:
Does my route load dynamic data based on the url using a slug :id? i.e.
this.resource('foo', {path: ':id'});
If you answered yes
Do not attempt to load multiple models from the model hook in that route!!! The reason lies in the way Ember handles linking to routes. If you provide a model when linking to that route ({{link-to 'foo' model}}, transitionTo('foo', model)) it will skip the model hook and use the supplied model. This is probably problematic since you expected multiple models, but only one model would be delivered. Here's an alternative:
Do it in setupController/afterModel
App.IndexRoute = Ember.Route.extend({
model: function(params) {
return $.getJSON('/books/' + params.id);
},
setupController: function(controller, model){
this._super(controller,model);
controller.set('model2', {bird:'is the word'});
}
});
Example: http://emberjs.jsbin.com/cibujahuju/1/edit
If you need it to block the transition (like the model hook does) return a promise from the afterModel hook. You will need to manually keep track of the results from that hook and hook them up to your controller.
App.IndexRoute = Ember.Route.extend({
model: function(params) {
return $.getJSON('/books/' + params.id);
},
afterModel: function(){
var self = this;
return $.getJSON('/authors').then(function(result){
self.set('authors', result);
});
},
setupController: function(controller, model){
this._super(controller,model);
controller.set('authors', this.get('authors'));
}
});
Example: http://emberjs.jsbin.com/diqotehomu/1/edit
If you answered no
Go ahead, let's return multiple models from the route's model hook:
App.IndexRoute = Ember.Route.extend({
model: function() {
return {
model1: ['red', 'yellow', 'blue'],
model2: ['green', 'purple', 'white']
};
}
});
Example: http://emberjs.jsbin.com/tuvozuwa/1/edit
If it's something that needs to be waited on (such as a call to the server, some sort of promise)
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
model1: promise1,
model2: promise2
});
}
});
Example: http://emberjs.jsbin.com/xucepamezu/1/edit
In the case of Ember Data
App.IndexRoute = Ember.Route.extend({
var store = this.store;
model: function() {
return Ember.RSVP.hash({
cats: store.find('cat'),
dogs: store.find('dog')
});
}
});
Example: http://emberjs.jsbin.com/pekohijaku/1/edit
If one is a promise, and the other isn't, it's all good, RSVP will gladly just use that value
App.IndexRoute = Ember.Route.extend({
var store = this.store;
model: function() {
return Ember.RSVP.hash({
cats: store.find('cat'),
dogs: ['pluto', 'mickey']
});
}
});
Example: http://emberjs.jsbin.com/coxexubuwi/1/edit
Mix and match and have fun!
App.IndexRoute = Ember.Route.extend({
var store = this.store;
model: function() {
return Ember.RSVP.hash({
cats: store.find('cat'),
dogs: Ember.RSVP.Promise.cast(['pluto', 'mickey']),
weather: $.getJSON('weather')
});
},
setupController: function(controller, model){
this._super(controller, model);
controller.set('favoritePuppy', model.dogs[0]);
}
});
Example: http://emberjs.jsbin.com/joraruxuca/1/edit
NOTE: for Ember 3.16+ apps, here is the same code, but with updated syntax / patterns: https://stackoverflow.com/a/62500918/356849
The below is for Ember < 3.16, even though the code would work as 3.16+ as fully backwards compatible, but it's not always fun to write older code.
You can use the Ember.RSVP.hash to load several models:
app/routes/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
people: this.store.findAll('person'),
companies: this.store.findAll('company')
});
},
setupController(controller, model) {
this._super(...arguments);
Ember.set(controller, 'people', model.people);
Ember.set(controller, 'companies', model.companies);
}
});
And in your template you can refer to people and companies to get the loaded data:
app/templates/index.js
<h2>People:</h2>
<ul>
{{#each people as |person|}}
<li>{{person.name}}</li>
{{/each}}
</ul>
<h2>Companies:</h2>
<ul>
{{#each companies as |company|}}
<li>{{company.name}}</li>
{{/each}}
</ul>
This is a Twiddle with this sample: https://ember-twiddle.com/c88ce3440ab6201b8d58
Taking the accepted answer, and updating it for Ember 3.16+
app/routes/index.js
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
export default class IndexRoute extends Route {
#service store;
async model() {
let [people, companies] = await Promise.all([
this.store.findAll('person'),
this.store.findAll('company'),
]);
return { people, companies };
}
}
Note, it's recommended to not use setupController to setup aliases, as it obfuscates where data is coming from and how it flows from route to template.
So in your template, you can do:
<h2>People:</h2>
<ul>
{{#each #model.people as |person|}}
<li>{{person.name}}</li>
{{/each}}
</ul>
<h2>Companies:</h2>
<ul>
{{#each #model.companies as |company|}}
<li>{{company.name}}</li>
{{/each}}
</ul>
I use something like the answer that Marcio provided but it looks something like this:
var products = Ember.$.ajax({
url: api + 'companies/' + id +'/products',
dataType: 'jsonp',
type: 'POST'
}).then(function(data) {
return data;
});
var clients = Ember.$.ajax({
url: api + 'clients',
dataType: 'jsonp',
type: 'POST'
}).then(function(data) {
return data;
});
var updates = Ember.$.ajax({
url: api + 'companies/' + id + '/updates',
dataType: 'jsonp',
type: 'POST'
}).then(function(data) {
return data;
});
var promises = {
products: products,
clients: clients,
updates: updates
};
return Ember.RSVP.hash(promises).then(function(data) {
return data;
});
If you use Ember Data, it gets even simpler for unrelated models:
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller,model);
var model2 = DS.PromiseArray.create({
promise: this.store.find('model2')
});
model2.then(function() {
controller.set('model2', model2)
});
}
});
If you only want to retrieve an object's property for model2, use DS.PromiseObject instead of DS.PromiseArray:
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller,model);
var model2 = DS.PromiseObject.create({
promise: this.store.find('model2')
});
model2.then(function() {
controller.set('model2', model2.get('value'))
});
}
});
The latest version of JSON-API as implemented in Ember Data v1.13 supports bundling of different resources in the same request very well, if you don't mind modifying your API endpoints.
In my case, I have a session endpoint. The session relates to a user record, and the user record relates to various models that I always want loaded at all times. It's pretty nice for it all to come in with the one request.
One caveat per the spec is that all of the entities you return should be linked somehow to the primary entity being received. I believe that ember-data will only traverse the explicit relationships when normalizing the JSON.
For other cases, I'm now electing to defer loading of additional models until the page is already loaded, i.e. for separate panels of data or whatever, so at least the page is rendered as quickly as possible. Doing this there's some loss/change with the "automatic" error loading state to be considered.

Emberjs - Knowing when model is rendered on template

Is it possible to know (with an event handler that would be the best) when the data from the model is render on the template, also when it's updated ?
I will use this as an example :
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller, model) {
var promise = myNewModel();
this._super(controller, promise);
}
})
I am updating the modal in my setupController because I don't want the user to be stuck on a blank page.
Is there is a way to get that event when all the data are done rendering on the template, in my case when I have updated the model ?
By the time you've reached setupController, the model needs to be a real one, not a promise. Setting the model here to be a promise is not going to work at all. You could try something like this:
setupController: function(controller, model) {
myNewModel().then(function(model) {
controller.set('model', model);
});
}
In other words, it's not an event you're looking for, it's your newModel call fulfilling.
You should probably make use of Loading Substates. Refer docs here
You need to fetch the model from the route#model hook and return a promise. The loading state will be triggered until the promise resolves. You can show a loading template until the promise resolves.
App.ApplicationRoute = Ember.Route.extend({
model: function() {
var promise = myNewModel();
return promise;
}
});
Refer #machty's jsbin and the related PR

emberjs using "needs" but other controller model empty

How do I get the the model to load without going to the route first?
App.UsersRoute = Em.Route.extend({
model: function() {
return ['bob', 'sue', 'tom'];
},
setupController: function(controller, model) {
controller.set('model', model);
}
});
From another controller using
needs: "users"
and
this.get('controllers.users.content');
works fine as long as I visit the UsersRoute first.
Load it in the topmost route that will need it, thusly:
App.SomeOtherRoute = Em.Route.extend({
setupController: function(controller, model) {
controller.set('model', model);
this.controllerFor('user').set('model', ['bob', 'sue', 'tom']);
}
});
Note that if you are using ember-data or epf or ajax, that the model will be a promise. You can't set the model on a controller to be a promise so you would do:
setupController: function(controller, model) {
controller.set('model', model);
return this.get('store').findAll('user').then(function(users) {
this.controllerFor('users').set('model', users);
});
}
Note in the second one I'm using UsersController, not UserController, because you seem to want a collection of users not a single user.
I hit this same problem this past weekend and the following worked for me:
App.SomeOtherController = Ember.Controller.extend({
needs: ['users']
});
App.SomeOtherRoute = Ember.Route.extend({
setupController: function( controller, model ){
this._super( controller, model );
controller.set( 'controllers.users.model', ['bob', 'sue', 'tom'] );
}
});
if it is an ajax call / ember data then you need something like this:
App.SomeOtherController = Ember.Controller.extend({
needs: ['users']
});
App.SomeOtherRoute = Ember.Route.extend({
setupController: function( controller, model ){
this._super( controller, model );
this.get('store').findAll('user').then(function(users) {
controller.set( 'controllers.users.model', users );
});
}
});
However, a colleague pointed out to me during our code review today that if I need to do this I have probably structured my routes / resources / models incorrectly. In other words an outer route should not depend on an inner route's model. So I am now thinking about going back and refactoring this so that the users model is part of the model for the outer route and then I can use that in my inner route controller.

How to enable page refresh when Ember model loaded in parent route has nested models passed to child routes?

A parent route in an Ember app loads a model from the server. The model is an array with a series of fields, including a field that is itself an array containing the models for the child routes. The model array is displayed in the parent route as a list where each item, when clicked, links to the child route while passing the selected model to the child route.
However, since the child route doesn't load its model from the server but receives it from the parent route, refreshing the page (or sharing the URL) when on the child route yields an error. What is a good way of solving that problem?
Here is a JSBin with an example.
You should implement the model hook in your child Routes, and as you are using ember data, use the fixtureAdapter and the store.
An slightly modified JSBin http://jsbin.com/oSUZUvE/2#/documents/1/subdocument/2
App = Ember.Application.create({});
App.ApplicationAdapter = DS.FixtureAdapter;
App.Router.map(function() {
this.resource('documents', function(){
this.resource('document', {path: ':document_id'}, function () {
this.resource('subdocument', {path: 'subdocument/:subdocument_id'});
});
});
});
App.ApplicationRoute = Ember.Route.extend({
redirect: function () {
this.transitionTo('documents');
}
});
App.DocumentsRoute = Ember.Route.extend({
model: function(){
return this.get('store').find('document');
}
});
App.DocumentRoute = Ember.Route.extend({
model: function (params) {
console.log(params);
return this.get('store').find('document',params.document_id);
}
});
App.SubdocumentRoute = Ember.Route.extend({
model: function (params) {
console.log(params);
return this.get('store').find('subdocument',params.subdocument_id);
}
});