I'm fairly new to Ember and I'm starting to write more complex apps. I've been pulling hair trying to figure out why defining a controller for the index breaks the index route's model population.
I've tried the "setupcontroller" function, but still no luck.
Here's the route code:
App.IndexRoute = Ember.Route.extend({
model: function () {
return Ember.RSVP.hash({ //return promises for both models here
featuredJobs: $.getJSON('http://api.*********/featured/jobs', {'token': guestToken}),
featuredEmployers: $.getJSON('http://api.********/featured/employers', {'token': guestToken})
})
}
});
When I add App.IndexController = Ember.Controller.extend({... it breaks the model's {{#each}} helper. (not the app). I can see the model assigned to the route in the Ember inspector. Here's the template:
<div class="panel-body">
<div class="list-group ft-jobs">
{{#each featuredJobs}}
{{#linkTo 'job' _id class="list-group-item"}}
<h4 class="list-group-item-heading">{{title}}</h4>
<p class="list-group-item-text">{{description}}</p>
{{/linkTo}}
{{else}}
<p class="text-center">Sorry, no featured jobs are available.</p>
{{/each}} //END OF SNIPPET
Your controller needs to extend ObjectController since it's being backed by an object.
http://emberjs.jsbin.com/OxIDiVU/134/edit
Related
I have an Ember route with a model that loads data from a few different places, using Ember.RSVP.hash. Each of these results in a call to a different API route in the backend:
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model() {
return Ember.RSVP.hash({
profile: this.store.queryRecord('profile', {}),
subscriptions: this.store.findAll('subscription'),
packages: this.store.findAll('package'),
});
},
});
The problem I'm having is that when any of the data calls throws an error, the entire template fails to load. What I would like to do instead is display as much data as is available even in case of an error, with the portions that couldn't be loaded displayed as an empty model of the appropriate type (with some additional error information). However, I don't seem to be able to do this. I tried adding an error handler to the route, but from the error handler there doesn't seem to be any way to continue the transition despite the error.
One possibility is to use service for obtaining such information. Model will be completely loaded but data from service will show after they will be loaded.
Take a look at http://emberigniter.com/render-promise-before-it-resolves/
I would suggest passing data down from the route, whether it uses one or more other services to retrieve the data (also from the same author http://emberigniter.com/should-components-load-data/ ). The template can be rendered partially by populating the model provided to the components with separate requests. A good place for this kind of code is in setupController. Usually I only add a call to a service from a component if there is a logical coupling between the two eg a component showing a specific ad.
rough example,
http://emberjs.jsbin.com/netesehasi/1/edit?html,js,output
js
...
App.IndexRoute = Ember.Route.extend({
model:function(){
return {dataForA:null,dataForB:null,dataForC:null};
},
setupController:function(controller,model) {
this._super.apply(arguments);
// simulate fetching data
setTimeout(function(){
Ember.set(model,'dataForA', 'this is data for component a');
controller.set('model', model);
},2000);
setTimeout(function(){
Ember.set(model,'dataForB', 'this is data for component b');
controller.set('model', model);
},1000);
setTimeout(function(){
Ember.set(model,'dataForC', {error:'error for component c'});
controller.set('model', model);
},3000);
}
});
...
hbs
...
<script type="text/x-handlebars" data-template-name="components/comp-a">
{{#if data}}
{{data}}
{{else}}
loading...
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="components/comp-b">
{{#if data}}
{{data}}
{{else}}
loading...
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="components/comp-c">
{{#if data}}
{{#if data.error}}
{{data.error}}
{{else}}
{{data}}
{{/if}}
{{else}}
loading...
{{/if}}
</script>
...
I have this router:
// app/router.js
Router.map(function() {
this.route('battle', function(){
this.route('combats');
})
});
In the combats route I can access to the battle model easily using:
// app/routes/battle/combat.js
this.modelFor('battle');
But if I want to access to this model also in the combats template things start to be complicate:
// app/templates/battle/combats.hbs
<h1>Combats for Battle {{<how to access to the battle>.title}}</h1>
{{#each model as |combat|}}
{{combat.date}}
{{/each}}
I have solved this sending properties to the combats Controller from the combats Route:
// app/routes/battle/combat.js
setupController: function(controller, model) {
controller.set('content', model);
controller.set('battle', this.modelFor('battle'));
}
But I don't know if it is the correct way, it looks too much indirect under my perspective, like that you have to make a long workaround to make this property available in the template.
It depends on how generic you want your code to be. For your special usecase it might be appropriate to use the Ember.RSVP.hash in your model hook in combats.js like this:
model(){
return Ember.RSVP.hash({
combats: //here your combat.js model code,
battle: this.modelFor('battle');
})
}
Then you can remove your setupController function and rewrite your template to:
<h1>Combats for Battle {{model.battle.title}}</h1>
{{#each model.combats as |combat|}}
{{combat.date}}
{{/each}}
What I'm trying to accomplish is to return only one object to the model and loop through it's properties in a handlebars template. Thanx-a-Lot for any help!
My response looks like this:
{"U+554A":{
"id":1,
"unihex":"U+554A",
"num_reference":"呵",
"totalStrokes":10,
"kMandarin":"a\n",
"kDefinition":"exclamatory particle\n"},
"U+611B":{
"id":2,
"unihex":"U+611B",
"num_reference":"愛",
"totalStrokes":13,
"kMandarin":"\u00c3\u00a0i\n",
"kDefinition":"love, be fond of, like\n"}
}
I tried everything. The most obvious being these two, just as a tryout, but the ember inspector shows no model is set:
var sinograms = Ember.$.getJSON(apiurl);
return sinograms['U+554A'];
I also tried:
var sinograms = Ember.$.getJSON(apiurl);
return sinograms[0];
NB:
I can change the response format if necessary.
I know it works when I loose the object-keys (without "U+554A"), but then how do I select the matching character.
#abuani: Upon your request. Thanx btw.
//app.js
App = Ember.Application.create();
App.Router.map(function() {
this.resource('signup');
this.resource('login');
this.resource('profile');
this.resource('overview');
this.resource('practice');
});
App.OverviewRoute = Ember.Route.extend({
model: function() {
var url = 'http://localhost/~hiufung/RoadToChinese/index.php/api/sinograms/random?limit=2';
var sinograms = Ember.$.getJSON(url);
return sinograms;
}
});
//index.html (inline template)
<script type="text/x-handlebars" id="overview">
<header class="bar bar-nav">
<a class="icon icon-left pull-left" href="back"></a>
<h1 class="title">RoadToChinese</h1>
<a id="showRightPush" class="icon icon-gear pull-right" href="overview-settings"></a>
</header>
<div class="content">
<div class="content-padded">
{{#each object in model}}
<p>{{object.num_reference}}<p>
{{/each}}
</div>
</div>
</script>
I can't add a comment to your initial question, but can you please post a few other pieces of code:
The router that's loading this(I imagine this is being done in your model function), the setupController if you have one, and the template that's trying to render the object. Without this information, there's little I can do to help.
EDIT Since there's code:
I should have noticed, your model is return an objects when Ember expects the model to be an array of objects. The first thing you should do is make the return from the server an Array. If you don't have control over this, then you can change your model to return
return Ember.A([sinograms]);
And that should work.
After that though, you can remove the nested object within each object. If you don't remove that, then you also need to make the inner objects an Array so you can iterate over it.
Let me know how this goes.
Here's the JSBIN
Good morning,
I would appreciate any help you can provide. I'm currently pulling my hair out in frustration. I really want to get the hang of Ember but I must be missing something.
All I want to do is the following:
User visits /rooms route, app does a App.Room.find() call
User visits /rooms/1 route, app sets a selectedRoom property on the RoomController.
Use the selectedRoom property in the rooms template to indicate which room is currently selected.
Create an isSelected computed property using the selectedRoom property in each RoomController to adjust the display of that particular room
This should be easy, but it is not working for me.
Routes
App.RoomsRoute = Ember.Route.extend
setupController: ->
#controllerFor('application').set('currentRoute', 'rooms')
model: ->
App.Room.find()
App.RoomRoute = Ember.Route.extend
setupController: (model) ->
#controllerFor('application').set('currentRoute', 'rooms')
#controllerFor('rooms').set('selectedRoom', model)
model: ->
App.Room.find(params.room_id)
Controller
(this should set a default value so that when the user is only at /rooms, there is a string to write to the template)
App.RoomsController = Ember.ArrayController.extend
selectedRoom: 'default'
App.RoomController = Ember.ObjectController.extend
needs: ['rooms']
isSelected: ( ->
selectedRoom = 'controllers.rooms.selectedRoom'
if selectedRoom.id == #get('id')
return true
else
return false
).property('controllers.rooms.selectedRoom')
Template
rooms
<p>My selected room: {{ selectedRoom.room_id }}</p>
{{#each room in controller}}
{{#linkTo 'rooms.room' room}} Room {{ room.room_id }} {{/linkTo}}
{{/each}}
room
# This template is a contrived simplification of what I want to do... but essentially I just want to be able to access the `isSelected` property of the `RoomController`
<div {{ bindAttr class='isSelected' }}>
<p>Room {{ room.room_name }}</p>
</div>
When I ran your code (with some blanks filled in) on an older version of Ember (v1.0.0-pre.4), it seemed to work as you wanted, with a selected room becoming highlighted when you clicked on it.
When I tried it with the latest version (1.0.0-rc.4) it wasn't working, so this tells me it's probably a breaking change in Ember to blame.
According to this answer, the model and setupController hooks are called under different circumstances.
To make your code work, I had to add controller.set("model", model); in setupController:
App.RoomsRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controllerFor('application').set('currentRoute', 'rooms');
// Load the model property here because the model hook isn't
// called in this case
controller.set("model", model);
},
model: function() {
return App.Room.find();
}
});`
Not an Ember expert, and not sure if this is the same problem you were having, but this works for me. The full working code I tried is here: https://gist.github.com/jasonschock/5667656#file-app-js-L15
As you'll be able to tell from my question, I'm slowly learning EmberJS. I've read the great guide on routes and I felt ready to take on the world but then...
In my example, I thought the {{somethingView}} would be rendered and not the controller property {{somethingCtrl}}. Is this the correct behaviour? If so how would you render a property from the Ember.View?
The JS
window.App = Ember.Application.create({
ready: function() {
this.initialize();
}
});
window.App.Router = Ember.Router.extend({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router) {
var controller = router.get('applicationController');
controller.connectOutlet('garments');
}
})
})
})
window.App.ApplicationView = Ember.View.extend({
templateName: 'application',
});
window.App.ApplicationController = Ember.Controller.extend();
window.App.GarmentsController = Ember.Controller.extend({
somethingCtrl: "Something in the controller"
});
window.App.GarmentsView = Ember.View.extend({
templateName: 'garments',
somethingView: "Something in the view"
});
The DOM stuff
<script type="text/x-handlebars" data-template-name="application">
<h1>Hi Ember</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="garments">
<h1>Garments</h1>
{{somethingView}}<br>
{{somethingCtrl}}
</script>
The Fiddle
This behaviour is correct. These are my understandings of these ember core concepts:
Model: These objects represent the date that is handled by your application. These are the business objects that form the domain model of your Application.
Controller: A Controller is responsible for providing access to your models. Controllers have the property content, where models should be injected (a single Object for Ember.Controller and an array of objects for Ember.ArrayController). The Controller passes this content to your View. The Controller is the default context for your view. Therefore the behaviour you describe is expected.
View: The View is just intended for displaying issues. I personally use it mainly to do jQuery animations.
But nonetheless it is possible to access the view instance in the template. You just have to use the variable with the name 'view' in your template. I updated your fiddle with a working example: http://jsfiddle.net/jPK8A/5/
<script type="text/x-handlebars" data-template-name="garments">
<h1>Garments</h1>
{{view.somethingView}}<br>
{{somethingCtrl}}
</script>
But to be clear: The most common case should be to access contents from your controller. It should be not often that you access variables of your view. You want to display date in your App and this date resides in models and should therefore be accessed through controllers. The most likely case might be, that you want to store labels in your view or something like that (labels that have to be computed).