Ember.js - Access model attributes from activate function within Route - javascript

I have an Ember VeteranRoute set up as below. My Veteran model has name, date, info and medal attributes. I would like to access the veteran's name so I can add it to the title tag, as you can see in the activate function.
I can see that logging out this to the console in the context of the Route reveals the following which contains all the data I would like access to.
How can I access the name attribute from the data above? I have tried various this.Context._data.name combinations but none seem to work.
app.js VeteranRoute
App.VeteranRoute = Ember.Route.extend({
model: function(params){
return this.store.find("veteran", params.veteran_id);
},
activate: function() {
$(document).attr('title', this.veteran.name);
console.log(this);
}
});

do it in the setupController or afterModel that's when the model has been resolved and is available.
App.VeteranRoute = Ember.Route.extend({
model: function(params){
return this.store.find("veteran", params.veteran_id);
},
setupController: function(controller, model) {
this._super(controller, model);
$(document).attr('title', model.get('name;));
}
});

Related

Ember TransitionTo with queryParams not working

I'm new to ember and I'm trying to use transitionTo with queryParams but I can't get it to work I tried a lot of the solution but I can't find out what is wrong with my code. here the code for the two routes I'm to transition between:
1- index.js:
export default Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller, model);
controller.set("model", model);
},
model() {
return {
searchQuery: ""
};
},
actions: {
search() {
const query = this.controller.get("model.searchQuery");
this.transitionTo("/search-results", {
queryParams: {
q: query
}
});
}
}
});
2-search-results.js:
export default Ember.Route.extend({
model() {
return {
fieldsInput: []
};
},
setupController: function(controller, model) {
this._super(controller, model);
controller.set("model", model);
}
});
I don't know if anything else should be added. I tried setting queryParams on the controller but it didn't work either. also, I tried adding
queryParams: {
q: ""
},
to the search results route but no use.
When you give url for transitionTo method so you need to provide full URL by constructing including queryParams. This will work
this.transitionTo(`/search-results?q=${query}`);
As you mentioned in comment, you were missing to specify queryParams property in route.
In "search-results.js", you need to access(tell your model) about the param you passed into the file from index.js, so in search-results.js, your model should look a little like this:
model(param) {
return {
fieldsInput: []
};
},
If you look where you've defined "fieldsInput" above, you're going to want to do some sort of Ember-Data lookup (if you are not familiar with it, then watch a youtube video or two, it'll help a lot going forward), to make use of the "queryParam" you passed from index.js.
(Hint: "return this.store.findRecord(someObject, param.someProperty)" is probably what you'll want to use)
Also, if you look in the line just under where you've written "this.transitionTo", you've a typo in your spelling of "queryParams".
Hope this helps.

Dynamic segment value

Just getting started with Ember and have a question about the ember way to handle a common pattern.
I have a have the following router.js:
export default Router.map(function() {
this.resource('posts', function(){
this.route('post', { path: "/:title" });
this.route('new');
});
});
I'm wondering how to use the value of the post title as the dynamic segment so that post urls show up as /posts/my-post-title-here
I'm confused as to which model this is being looked up on or if there is an "ember way" to handle this common patter (besides using the posts_id for the dynamic segment).
All my posts are defined in routes/posts.js, so I thought I simply needed to lookup the values in this route inside of my routes/post.js route, like this:
export default Ember.Route.extend({
model: function(params) {
var posts = this.modelFor('posts')
return posts.findBy('title', params.title);
}
});
I'm seeing the /posts/:title route in my Ember inspector, but in the browser, the links are all undefined ( /posts/undefined ).
{{#each model as |post|}}
{{#link-to "posts.post" model }}
<li>{{post.title}}</li>
{{/link-to}}
{{/each}}
I'd love any advice about the proper way to handle this situation or explanations about how Ember looks up values for nested routes.
You need to setup a serializer on your routes/post.js, like this:
// routes/post.js
export default Ember.Route.extend({
model: function(params) {
var posts = this.modelFor('posts')
return posts.findBy('title', params.title);
},
serialize: function(model) {
return { post_slug: model.get('title') };
}
});
See Dynamic Segments

EmberJS: How to load multiple models on the same route?

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: initialize nested model with parent model loaded by ajax

I got the following simple ember.js-setup, which works all great
App.Router.map(function() {
this.resource('tourdates', function() {
this.resource('tourdate', { path: ':tourdate_id' });
});
});
App.TourdatesRoute = Ember.Route.extend({
model: function() {
return $.getJSON('http://someapi.com/?jsoncallback=?').then(function(data) {
return data;
});
}
});
App.TourdateRoute = Ember.Route.extend({
model: function(params) {
return tourdates.findBy('id', params.tourdate_id);
}
});
so, pretty simple, whenever i call index.html#/tourdates, i get the data via api. and when I click on a link in this view and call f.e. index.html#/tourdates/1 it just displays the view for its nested child.
This all breaks, when I directly call index.html#/tourdates/1 with the message
DEPRECATION: Action handlers contained in an `events` object are deprecated in favor of putting them in an `actions` object (error on <Ember.Route:ember174>)
Error while loading route: ReferenceError {}
Uncaught ReferenceError: tourdates is not defined
Although he makes the ajax-call to the api and gets the data, he is not able to initialize the nested model
When your App.TourdatesRoute is loaded, all data from the json, will be rendered. And when you click to edit one of these loaded objects, using a link-to for example, ember is smart enough to get the already referenced object, instead of send a new request. So your url will change to: yourhost.com/tourdate/id.
When you direct call this url, it will call the App.TourdateRoute model method. Because doesn't have any pre loaded data. But in your case you have a:
tourdates.findBy('id', params.tourdate_id);
And I can't see in any place the declaration of tourdates.
I recommed you to change your TourdateRoute to TourdateIndexRoute so when transitioning to tourdates the ajax call is performed once:
App.TourdatesIndexRoute = Ember.Route.extend({
model: function() {
return $.getJSON('http://someapi.com/?jsoncallback=?').then(function(data) {
return data;
});
}
});
The TourdatesRoute is called both for TourdateRoute and TourdatesIndexRoute, because it's the parent route of both. So fetching all data in the TourdatesIndexRoute will ensure this is just called when transitioning to tourdates.
In your TourdateRoute you will load just the record needed. Something like this:
App.TourdateRoute = Ember.Route.extend({
model: function(params) {
// retrieve just one data by id, from your endpoint
return $.getJSON('http://someapi.com/' + params.tourdate_id + '?jsoncallback=?').then(function(data) {
return data;
});
}
});
So a direct call to yourhost.com/tourdate/id will just loaded one record.
About your warning message, it happens because in some route you have:
App.MyRoute = Ember.Route.extend({
events: {
eventA: function() { ...},
eventB: function() { ...},
}
});
The events is deprecated and you need to use actions:
App.MyRoute = Ember.Route.extend({
actions: {
eventA: function() { ...},
eventB: function() { ...},
}
});
I hope it helps

Cannot access Backbone.js collection / models from with in template

I am struggling to pass my collection of models in Backbone.js into the template. Everytime I attempt to access the models (i.e. this.collection.models) I just get an empty array even though I know the collection contains two models of type Contact. I am sure I am missing something basic here. What is the standard way of passing models to Backbone.js templates?
Following is the model, collection and view definitions (the actual view is called from within a Backbone.js router function - source code for router not include here for brevity):
var Contact = Backbone.Model.extend({
urlRoot: '/contacts.json',
idAttribute: '_id',
parse: function(response) {
return response;
}
});
var Contacts = Backbone.Collection.extend({
model: Contact,
url: '/contacts.json',
parse: function(response) {
return response.data;
}
});
var ListContactsView = Backbone.View.extend({
el: $('#content'),
template: _.template($('#list-contacts-tpl').html()),
initialize: function() {
this.collection = new Contacts();
this.collection.fetch();
this.render();
},
render: function() {
console.log(this.collection);
this.$el.html(this.template({ contacts: this.collection.models }));
}
});
The template is defined as follows:
<script id="list-contacts-tpl" type="text/template">
<% console.log(contacts); %>
</script>
If your code is truly as shown in your question, the issue is that you are rendering the view before the fetch has returned, which is why your collection is empty. The fetch takes time to return, but you're calling render immediately after calling fetch.
Either call render in the success handler of the fetch function, or bind render to the reset event of the collection.

Categories