Emberjs view not updating on new record creation? - ember.js

I have route which uses ember hash to load multiple models like
import Route from '#ember/routing/route';
export default Route.extend({
model(params) {
return Ember.RSVP.hash({
contest: this.store.query('contest',{match: params.matchId}),
match: this.store.findRecord('match', params.matchId)
})
}
});
and i create a new instance of contest in controller using the following code
newContest() {
this.store
.createRecord('contest', {
name: this.get('contestName'),
fee: this.get('contestFee'),
winning: this.get('contestWinning'),
capacity: this.get('contestCapacity'),
match: this.get('model').match.get('id')
})
.save();
}
The problem being that it is not reflected in template which i am rendering as follows
{{#each model.contest as |contest| }}
<tr>
<td>{{contest.name}}</td>
</tr>
{{/each}}
where am i going wrong with this ?

That's because you use this.store.query. This method just makes a query via adapter, without any sort of caching.
Easiest way to update a model is by calling refresh method of route. To do it from controller you can define refresh action in your route, then from controller do this.send('refresh'); after saving new contest.

Related

Ember model and route setup

Trying to learn Ember with a simple app, which is just a questionnaire. On the first page of the questionnaire (localhost:4200/animal) they choose their favourite animal from a select box (which is a component I made, that I would like to re-use on other questions). The select box is populated via a RESTful API.
//routes/animal.js
import Ember from 'ember';
export default Ember.Route.extend({
model(){
return this.get('store').findAll('animal');
}
});
//models/animal.js
export default Model.extend({
name: attr()
});
//templates/animal.hbs
Choose your favourite animal:
{{select-box
items=model
selectedValue=???
}}
{{#link-to "xyz"}}Go to next question{{/link-to}}
//components/select-box/template.hbs
<select {{action 'actionOnChange' on 'change'}} class='{{class}}'>
{{#each items as |item|}}
<option value='{{item.id}}' selected={{eq item.id selectedValue}}>{{item.name}}</option>
{{/each}}
</select>
//components/select-box/component.js
import Ember from 'ember';
export default Ember.Component.extend({
selectedValue: '',
//when load the page, prepopulate the value
init(){
this._super(...arguments);
//retrieve it from somewhere?
//this.get('selectedValue') is blank if come back to the page
},
actions: {
actionOnChange(){
//save it somewhere? where?
this.set('selectedValue', this.$('select').val());
}
}
});
If I choose an animal, then go onto the next page of the questionnaire, and then come back to the /animal page again, their selection is lost. How do I retain the value that they chose?
All the examples I have looked at show the value being saved in the animal model, but in Ember Inspector on the 'Data' tab for 'animal', it shows all 5 different animals retrieved from the API call. Should I have a second model?
Any help is appreciated.

Ember Data and Firebase Where is my Id

I'm having trouble understanding how to update a record in an Ember.js that uses EmberFire and Firebase.
I have a small test application built to try to understand all of the CRUD functions. I can return a list of tasks that have been created, create a new task, and delete a task. But I cannot correctly update a record in a list.
When I look in the inspector, on my index page, I see that in the Ember debugger, under Data, it shows my model, and there is an Id field that contains the value that Firebase generated when a record was created on the server.
But when I console log the object that is getting passed to my Update Action in the route, there is no Id attribute. I think that is why I get an error of:
Error: no record was found at https://taskline.firebaseio.com/tasks/id
When hitting this piece of code:
export default Ember.Route.extend({
model: function () {
return this.store.findAll('task');
},
actions: {
updateTask: function (model) {
console.log(JSON.parse(JSON.stringify(model)));
this.store.findRecord('task', 'id').then(function(task){
task.set( 'taskname', model.taskname);
task.set( 'startdate', model.startdate);
task.set( 'enddate', model.enddate);
task.set( 'banding', model.banding);
return task.save();
});
},
Ember Fire is supposed to handle the Id isn't it? But if it's not in my model object, how am I supposed to pass it to the find query?
When you would like to update a model, one of the best option to pass the record back from the template/controller to the route action. In this case the record will be ready in your function param, so your updateTask method would look like this (only):
updateTask(record) {
record.save();
}
The whole process:
Firstly, in the Route handler download all records, as you did in your example, and implement the action:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
this.store.findAll('task');
},
actions: {
updateTask(task) {
task.save();
}
},
});
Secondly, in your template list each task with input box. Maybe it could be a table. (I just have one field here taskname, but you could have more, of course.)
<table>
<thead>
<th>ID</th>
<th>Taskname</th>
<th>Action</th>
</thead>
<tbody>
{{#each model as |task|}}
<tr>
<td>{{task.id}}</td>
<td>{{input value=task.taskname}}</td>
<td><button {{action 'updateTask' task}}>Update</button>
</tr>
{{/each}}
</tbody>
</table>
As you see, you are sending back the task record to the updateTask action in your route, so we are able to save it immediately. You can learn all these tricks in this free tutorial, which focuses on the Ember Way and uses Firebase: http://yoember.com

setupController no longer working for Ember route

I can not longer setup a route that has a hash of multiple models. What I use to do is this:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
products: this.store.findAll('product'),
suppliers: this.store.findAll('supplier')
});
},
setupController(controller, model) {
controller.set('products', model.products);
controller.set('suppliers', model.suppliers);
}
});
When I do this I can't loop through each {{#each products}} but I still can with {{#each model.products}}
Object proxying is deprecated, so you should refer to your models with a model. prefix.
The correct way to do this would be to leave out your setupController and use the model as an object with .products and .suppliers keys on it.
You'll still have to {{#each model.products as |product|}} unless you want to alias them in your controller like this:
products: Ember.computed.alias("model.products")
I'd recommend sticking with model.products in your template though to alleviate any confusion as to where that particular data came from. Removing it would imply it's controller-related vs. model data fetched in the route's model hook.
EDIT: The above solution was done in Ember 1.13.5 and should also work in any 2.x version.

Creating a new record not pulling data from template fields

I am attempting to create a new record, however none of the data from the fields is being passed automatically, as I expected Ember to (from what I've read).
My template:
<form {{action save content on="submit"}}>
{{input value=name}}
<button type="submit"}}>Next</a>
From what I've read content is an alias for model and interchanging these makes no difference.
My route:
App.CampaignsNewRoute = Ember.Route.extend({
actions: {
save: function(campaign) {
console.log(campaign.name);
}
},
model: function(controller) {
return this.store.createRecord('campaign');
}
});
And my controller:
App.CampaignsNewController = Ember.ObjectController.extend({
pageTitle: 'New Campaign Setup'
});
When I hit 'Next' it logs undefined. Logging just the campaign shows it's an Ember model, but without the name attribute. name is defined on the campaign model. Setting the input to {{input value=content.name}} places the name attribute within the model returned, but it's still undefined. Am I missing anything in this process? The EmberJS site doesn't show how to do this, from what I can find.
--
As a side note: I was originally using App.CampaignsNewController = Ember.Controller.extend as my model was returning a hash of promises, one of which is an array and Ember didn't like me using either array or object controller. I simplified it to the above to verify it wasn't that which was causing the issue. So any solution taking this into account would be wonderful.
Edit: I can access the template fields by doing this.get('controller').get('name') but surely that is not necessary? Changing my controller to a Ember.Controller.extend also stops that from working, would love to know why. Clarification on best practice here would still be wonderful!
Edit2: this.get('controller.content').get('name') works if the controller is simply an Ember.Controller as opposed to Ember.ObjectController and the template has {{input value=content.name}}. I'll work with but hopefully someone can clarify this is the correct way.
ObjectController is the way to go here. You would have it backed by one particular model, your new model, and you would add additional properties to the controller for use in the template.
Code
App.IndexRoute = Ember.Route.extend({
actions: {
save: function(campaign) {
console.log(campaign.get('color'));
}
},
model: function() {
return Ember.RSVP.hash({
record: this.store.createRecord('color'),
all: this.store.find('color')
});
},
setupController: function(controller, model){
this._super(controller, model.record);
controller.set('allColors', model.all);
}
});
App.IndexController = Em.ObjectController.extend({
});
Template
In the template any time you want to access anything on the model backing the template, you can just access it as if the model is the current scope.
{{name}}
if you want to access any of the properties that exist on the controller you would use the property name that it is on the controller.
{{allColors.length}}
Here's an example:
<form {{action save model on="submit"}}>
Color:{{input value=color}}<br/>
<button type="submit">Next</button>
</form>
<ul>
{{#each item in allColors}}
{{#unless item.isNew}}
<li>{{item.color}}</li>
{{/unless}}
{{/each}}
</ul>
One last tip, always use getters and setters ;)
Ember Data hides the properties, they don't live right on the object, so campaign.name will return undefined forever and ever. If you do campaign.get('name') you'll get a real response.
With the example: http://emberjs.jsbin.com/OxIDiVU/792/edit

Ember, Ember-data, and jquery-ui.dialog, "Oh my!"

The task:
Open a form in a lightbox to create a new "event"; the opened form should be bookmarkable.
The road blocks:
There are examples of opening a lightbox using {{action}} tags, but could not find one that opened in its own route.
There are many examples using older versions of ember.js.
There is not a lot of documentation related to ember-data and REST (I know, I know...it isn't "production ready").
The problem:
The fields in the form were not being tied to a backing model so "null" was being posted to my servlet (a Spring controller).
My very first iteration was not too far off from the final outcome (jsfiddle). The thing that finally made it works swapping this:
EP.EventsNewRoute = Ember.Route.extend({
...
setupController : function(controller, model) {
controller.set("model", model);
},
...
});
...for this:
EP.EventsNewRoute = Ember.Route.extend({
...
setupController : function(controller, model) {
this.controllerFor("events-new").set("model", model);
},
...
});
The question:
Why does the setupController function need to call controllerFor in order to properly set up the model?
And finally, since I struggled to find a fully-functional example, I wanted to make this accessible (and hopefully discover improvements).
Here's the fiddle: http://jsfiddle.net/6thJ4/1/
Here are a few snippets.
HTML:
<script type="text/x-handlebars">
<div>
<ul>
{{#linkTo "events.new" tagName="li"}}
Add Event
{{/linkTo}}
</ul>
</div>
{{outlet events-new}}
</script>
<script type="text/x-handlebars" data-template-name="events-new">
<form>
<div>
<label>Event Name:</label>
{{view Ember.TextField valueBinding="name"}}
</div>
<div>
<label>Host Name:</label>
{{view Ember.TextField valueBinding="hostName"}}
</div>
</form>
</script>
JavaScript:
...
EP.Router.map(function() {
this.resource("events", function() {
this.route("new");
});
});
EP.EventsNewRoute = Ember.Route.extend({
model : function() {
return EP.Event.createRecord();
},
setupController : function(controller, model) {
//controller.set("model", model); // Doesn't work? Why not?
this.controllerFor("events-new").set("model", model); // What does this do differently?
},
...
});
EP.EventsNewController = Ember.ObjectController.extend({
save : function() {
this.get("content.transaction").commit(); // "content.store" would commit _everything modified_, we only have one element changed, so only "content.transaction" is necessary.
}
});
EP.EventsNewView = Ember.View.extend({
...
});
EP.Event = DS.Model.extend({
name : DS.attr("string"),
hostName : DS.attr("string")
});
Resources:
http://emberjs.com/guides/routing/setting-up-a-controller/
http://emberjs.com/guides/getting-started/toggle-all-todos/ (trying to mimic what I learned, but morph the add-new to a new route)
Writing a LightboxView causes problems / Integrating DOM Manipulating jQuery-Plugins makes actions unusable (lightbox "example")
Dependable views in Ember (another lightbox "example" but doesn't have routes for the lightbox opening)
Why does the setupController function need to call controllerFor in order to properly set up the model?
Ember makes URLs a very integral part of its conventions. This means that the state of your application is represented by the route it is on. You've grokked most of this correctly. But there are couple of subtle nuances, that I will clarify below.
First consider an app with the following URLs,
/posts - shows a list of blog posts.
/posts/1 - shows a single blog post.
And say clicking on a post in the list at /posts takes you to /posts/1.
Given this scenario, there 2 ways a user will get to see the post at /posts/1.
By going to /posts and clicking on the 1st post.
By typing in /posts/1, via bookmarks etc.
In both these cases, the PostRoute for /posts/1 will need the model corresponding to Post id 1.
Consider the direct typing scenario first. To provide a way to lookup the id=1 post model, you would use,
model: function(params) {
return App.Post.find(params.post_id);
}
Your template for post will get the model and it can render using it's properties.
Now consider the second scenario. Clicking on post with id=1 takes you to /posts/1. To do this your template would use linkTo like this.
{{#linkTo 'post' post}} {{post.title}} {{/linkTo}}
Here you are passing in the post model to the linkTo helper. It then serializes the data for the post into a URL, ie:- '/posts/1'. When you click on this link Ember realizes that it needs to render the PostRoute but it already has the post model. So it skips the model hook and directly calls setupController.
The default setupController is setup to simply assign the model on the controller. It's implemented to do something like,
setupController: function(controller, model) {
controller.set('model', model);
}
If you do not need to set custom properties on your controller, you don't need to override it. Note: if you are augmenting it with additional properties you still need to call _super to ensure that the default setupController behaviour executes.
setupController: function(controller, model) {
this._super.apply(this, arguments);
controller.set('customProp', 'foo');
}
One final caveat, If you are using linkTo and the route does not have dynamic segments, then the model hook is still called. This exception makes sense if you consider that you were linking to the /posts route. Then the model hook has to fire else Ember has no data to display the route.
Which brings us to the crux of your question. Nearly there, I promise!
In your example you are using linkTo to get to the EventsNewRoute. Further your EventsNewRoute does not have dynamic segments so Ember does call the model hook. And controller.set("model", model); does work in so much as setting the model on the controller.
The issue is to do with your use of renderTemplate. When you use render or {{render}} helper inside a template, you are effectively getting a different controller to the one you are using. This controller is different from the one you set the model on, hence the bug.
A workaround is to pass the controller in the options, which is why renderTemplate gets this controller as an argument.
renderTemplate: function(controller) {
this.render("events-new", {
outlet : "events-new", controller: controller
});
}
Here's an updated jsfiddle.
Final Note: Unrelated to this question, you are getting the warning,
WARNING: The immediate parent route ('application') did not render into the main outlet and the default 'into' option ('events') may not be expected
For that you need to read this answer. Warning, it's another wall of text! :)