Store promise resolved in template but not in controller - ember.js

I have a select box with a list of users that I get from the store. I have a method on the controller where I try to get an initial user for the selectbox. No matter what I do I can't seem to get any user record to resolve on the method but the users show up fine in the selectbox. Can someone explain what I'm missing? Thank you in advance for the help.
Controller:
App.TodoModuleController = Ember.ObjectController.extend
users: Em.computed -> #store.findAll 'user'
selectedUser: Em.computed 'users', ->
#get('users').objectAt(1)
# There are 4 users in the db. Just using 1 for a test.
# I also tried
# #get('users').then (users)->
# users.objectAt(1)
selectedUserDidChange: Em.observer 'selectedUser', ->
if #get 'selectedUser' then #assignUser()
assignUser: ->
model = #get 'model'
model.set 'assigneeId', #get('selectedUser').get('id')
unless model.save()
console.log 'Error: TodoModuleController.assignUser'
Template:
{{view Em.Select content=users selection=selectedUser optionValuePath='content.id' optionLabelPath='content.firstName'}}
SOLUTION (thanks to Hrishi):
Todo Model:
App.Todo = DS.Model.extend
text: DS.attr 'string'
assignee: DS.belongsTo 'user', inverse: null
Todo Controller:
App.TodoModuleController = Ember.ObjectController.extend
users: Em.computed -> #store.findAll 'user'
assignedUserDidChange: Em.observer 'assignee', ->
if #get 'assignee' then #assignNewUser()
assignNewUser: ->
unless #get('model').save()
console.log 'Error: TodoModuleController.assignUser'
Select Box:
{{view Em.Select content=users selection=assignee optionValuePath='content.id' optionLabelPath='content.firstName'}}

Computed properties are not computed until someone (in your case, the template) tries to access them, in other words, they are computed lazily. Since the template tries to access them, the users array will get populated and you can see it on the page when you click on your select box. The way I would do this would be to put an attribute on the model called assignee and bind the view's selection directly to that.

Related

Ember Routing with renderTemplate and setupController

I am building an Ember app to show a simple Twitter-like tagging system. When a user visits /items, he or she will see a list of all items. When the user visits /tags, the user will see a list of tags as links. When the user clicks one of these links, the user should be directed to /tags/:id and will see all items tagged with that specific tag. Then the user will be able to search/sort/manipulate the items as he/she would be able to from the ItemsRoute.
How can I make TagRoute use ItemsController and render the items template, using the tag's associated items as the model?
I have tried different combinations of the hooks in TagRoute, and I'm not able to find a recipe that works. There seems to be a fundamental misunderstanding on my part.
Here is my relevant code:
router.js.coffee
App.Router.map ()->
#resource 'items'
#resource 'tags', ->
#resource 'tag', path: ':tag_id'
routes/tag.js.coffee
App.TagRoute = Ember.Route.extend
model: (params)->
#get('store').find 'tag', params.tag_id
controllerName: 'items'
setupController: (controller, model)->
#controllerFor('items').set('model', model.items)
renderTemplate: ->
#render 'items', ->
into: 'tags'
controller: 'items'
templates/tags.hbs
<ul class="tag-list">
{{#each tag in model}}
<li>
{{#link-to 'tag' tag}}
{{tag.name}}
{{/link-to}}
</li>
{{/each}}
</ul>
{{outlet}}
models/items.js.coffee
App.Item = DS.Model.extend(
body: DS.attr('string')
checked: DS.attr('boolean')
tags: DS.hasMany('tag')
)
models/tags.js.coffee
App.Tag = DS.Model.extend(
name: DS.attr('string')
taggings_count: DS.attr('number')
items: DS.hasMany('item')
)
Currently, this give me an error:
Error while processing route: tag Cannot assign to read only property 'name' of function () {
return {
into: 'tags',
controller: 'items'
};
} TypeError: Cannot assign to read only property 'name' of function () {
return {
into: 'tags',
controller: 'items'
};
}
Looking at the Ember Routes Inspector in Chrome, the controllerName property is the only one which overrides Ember's defaults, and Ember still tries to render a generated tag template.
As ahmed.hoban suggested, I have solved this using query params. This helps me avoid duplicating routes and having a tangled router. It hits the database, which is not preferable, but I'm not sure at this point if I'll make that a requirement. I have control over the full-stack, so I was able to make adjustments on the back-end to support the request.
router.js.coffee
App.Router.map ()->
#resource 'tags', path: '/', ->
#resource 'items'
routes/tag.js.coffee - deleted
templates/tags.hbs
<ul class="tag-list">
{{#each tag in model}}
<li>
{{#link-to 'items' (query-params tag=tag.id)}}
{{tag.name}}
{{/link-to}}
</li>
{{/each}}
</ul>
{{outlet}}
controllers/items.js.coffee
App.ItemsController = Ember.ArrayController.extend(
needs: 'tags'
queryParams: ['tag']
tag: null
items: (->
tag = #get 'tag'
if tag
#store.find 'item', tag: tag
else
#get 'model'
).property('tag', 'model')
)

Default ember template with specific model record

I've scouring for a bit, but can't find an answer to this. I've got an Ember route on a Rails app, and I would like one of the model records to be displayed by default when visiting the URL (right now, a user needs to click on a specific link to retrieve the data).
For example, when visiting /albums, I would like /albums/38 to be active and visible without forcing a redirect. Is that possible?
Thanks
App.Router.map ->
#resource 'albums', path: '/', ->
#resource 'album', path: '/:id'
App.AlbumsRoute = Ember.Route.extend
model: -> #store.find 'album'
Add an 'index' route for the 'albums' resource and redirect to the 'album' route with the first model. The index route will only be hit when you hit just the albums resource, and not when you hit deeper (aka not when you hit /albums/1).
App.AlbumsRoute = Ember.Route.extend
model: -> #store.find 'album'
App.AlbumsIndexRoute = Ember.Route.extend
model: ->
album = this.modelFor('albums').get 'firstObject' # This can be whatever...
this.transitionTo('album', album) if Ember.isEmpty(album) is no

Access child records in non-nested routes

If I have an "organization" that has many "clinics" and in my app I don't want the routes nested, but I want to access the clinics on an organization page and the organization on a clinic page, is there something special I need to do? I am using rails as a backend and if I switch from the RestAdapter to the ActiveModelAdapter and embed the clinic ids it works, but I would like to know how to do it with the standard RestAdapter.
Router:
App.Router.map ->
#resource 'organizations', ->
#resource 'organization', path: 'organization/:organization_id', ->
#resource 'clinics', ->
Organization template:
<h1>{{name}}</h1>
<h2>Clinics</h2>
{{#link-to 'clinics.new'}}New Clinic{{/link-to}}
<ul id="org-clinics">
{{#each clinics}}
<li>{{#link-to 'clinic' this}}{{name}}{{/link-to}}</li>
{{else}}
<strong>No clinics yet...</strong>
{{/each}}
</ul>
Organization model:
App.Organization = DS.Model.extend
name: DS.attr 'string'
clinics: DS.hasMany 'clinic', async: true
Clinic template:
<h1>{{name}}</h1>
<strong>Organization: </strong>{{organization.name}}
Clinic model:
App.Clinic = DS.Model.extend
name: DS.attr 'string'
organization: DS.belongsTo 'organization'
Depends on how you want to go about it, but you can bind controller content super easily. For example:
App.OrganizationsController = Ember.ArrayController.extend({
needs: ['clinics'],
clinics: null,
clinicsBinding, 'controllers.clinics.content',
clinicsUpdated: function () {
// Do something here because some record updated
}.observes('clinics.#each.content') // Bind properties
});
App.ClinicsController = Ember.ArrayController.extend({
needs: ['organizations'],
organizations: null,
organizationsBinding, 'controllers.organizations.content'
});
You can acess them in templates, too. For example, {{#each clinics}}
Hope that helps!

Not loading view on page load

The Ember.js application
# routes/application.js.coffee
########################################
App.ApplicationRoute = Ember.Route.extend
model: ->
#store.findAll('category').then (records) ->
{
categories: records
}
# routes/categories.js.coffee
########################################
App.CategoriesRoute = Ember.Route.extend
model: ->
#store.find 'category'
# routes/category.js.coffee
########################################
App.CategoryRoute = Ember.Route.extend
model: (params) ->
serialize: (model, params) ->
id: model.get('slug')
# templates/application.hbs
########################################
{{ partial 'categories' }}
<p>---</p>
{{ outlet }}
# templates/categories.hbs
########################################
<h1>Categories...</h1>
{{#each category in categories}}
{{#link-to 'category' category}}
{{category.title}}
{{/link-to}}
{{/each}}
# templates/category.hbs
########################################
<h2>{{title}}</h2>
<h3>{{description}}</h3>
# router.js.coffee
########################################
App.Router.map ->
#resource 'categories', path: 'categories'
#resource 'category', path: 'category/:slug'
# store.js.coffee
########################################
App.ApplicationAdapter = DS.ActiveModelAdapter.extend
namespace: 'api/v1'
What I am trying to accomplish
I am loading Category on the Application route so the data can be used to render the site-wide navigation via the categories partial.
Model is acquired successfully and stored, according to the Ember console. If it makes any difference, one-to-many data is being sideloaded, called Sections, which is also stored successfully.
The category partial is rendered as expected, with links to each section. Clicking on them yields the rendering of the title and description.
What is wrong
Refreshing the application on the route of say, /#/category/title-1, doesn't render the category view (both title and description). However, clicking on the links generated in the categories partial will render the view.
What I expect on page load is the rendering of the category view, without the need of clicking on the categories links. What am I doing wrong?
After playing around with the Application for too long, I figured out a solution to my problem.
When using the Ember console, I saw that the model for the Category view was referenced to [Object object] and not App.Category. I had to specify the store to search by the slug. Therefore, the code looks like this
# routes/category.js.coffee
########################################
App.CategoryRoute = Ember.Route.extend
model: (params) ->
serialize: (model, params) ->
id: model.get('slug')
#store.find('category', params.slug)
The view is now rendered on page load.

Ember duplicate loading for models

I want to use the information already received within a linked template. Nesting routes has created a whole heap of errors, and I'm a little lost on how to pass this data, but it seems like it must be possible?
This works, but strikes me as a poor hack. Ideally I'd only want one API round-trip...
App.Router.map ->
#resource 'photos'
#route 'photo', path: 'photo/:photo_id'
App.PhotosRoute = Ember.Route.extend
model: ->
Ember.$.getJSON('http://api.flickr.com/services/feeds/photos_public.gne?tags=cake&tagmode=all&format=json&jsoncallback=?')
setupController: (controller,model) ->
controller.set 'photos', model.items.map (i) ->
i.id = model.items.indexOf(i)
App.PhotoRoute = Ember.Route.extend
model: (params) ->
jQuery.getJSON('http://api.flickr.com/services/feeds/photos_public.gne?tags=cake&tagmode=all&format=json&jsoncallback=?').then (api) ->
api.items[params.photo_id]
App.PhotosController = Ember.ObjectController.extend
itemController: 'photo'
{{#linkTo 'photo' this }}<img {{bindAttr src="media.m"}} />{{/linkTo}}
You can use the modelFor(routeName) to get the model from some route. In your case probally you will fetch all data in PhotosRoute, and want to reuse it. You can use this in your PhotoRoute:
App.PhotoRoute = Ember.Route.extend
model: (params) ->
#modelFor('photos').items[params.photo_id]
So just one api call will be done.