How to save changes on associated models in Ember Data - ember.js

I'm running Ember Data 1.0.0 with Ember 1.3.0.
I have this data:
{
"product": {
"id": 185588,
"name": "USB MIDI Adapter",
"images":["imageid1", "imageid2"....]
},
"images": [
{
"id": "imageid1",
"url": "google.com"
},
{
"id": "imageid2",
"url": "google.com2"
}
]
}
And these models:
App.Image = DS.Model.extend({
url: DS.attr('string')
});
App.Product = DS.Model.extend({
name: DS.attr('string'),
images: DS.hasMany('Image')
});
In my template I have an edit form to change the url on the associated images:
<form {{action save on="submit"}}>
{{input type="text" value=name}}
{{#each images}}
* {{input type="text" value=url}}<br>
{{/each}}
<button type="submit">Save</button>
</form>
The save action is:
save: function() {
var product = this.modelFor('productEdit');
product.save();
this.transitionTo('product', product);
}
The problem is that the save action only saves the product (PUT product), but not the changed that's made to the image urls. The model binding works fine, when I change the url on images the gui is updated as it should, but the PUT is not sent as it should.
How do I save the image changes?

You either need to call save on each record, call save on the recordarray(which will iterate and save), or you can override the serializer (serializeIntoHash) to inject the images into the save of the product.
records.forEach(function(record){
if(record.get('isDirty')) record.save();
});

Related

ember - loading model data in route for power sort?

I have a very simple set up right now. I have a book model that has a name and author. I'm trying to create a simple form that will create a new book. For the author I'm using power select to load the authors from the author model. The form set up looks like this:
<form {{action "save" on="submit"}}>
{{input value=model.title placeholder="Title"}}<br>
{{#power-select class="select"
selected=model.author
options=authors
onchange=(action (mut model.author)) as |author|}}
{{author.name}}
{{/power-select}}
<input type="submit" value="Save">
</form>
However I'm having trouble setting up the route to get this working. So far no authors show up in the select, even though there are authors stored in my database. My route looks like this:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.createRecord('book');
},
actions: {
save() {
this.modelFor(this.routeName).save();
}
},
store: Ember.inject.service(),
authors: Ember.computed({
get() {
return this.get('store').findAll('author');
}
}).readOnly()
});
First of all, how should I properly load data from the author model in the route for the books/new route? Secondly, should I be doing this in the route? From what I have read, and what people have told me, loading model data should be done in the route.
Move authors property to corresponding controller.
Also you don't need to add readonly.
So in controller :
authors: Ember.computed(function(){
return this.get('store').findAll('author');
})
And for loading model in route. Yes you should load that model which is to be a resource manipulating, in route. So now you're doing it right.
1) Using Ember.RSVP.hash inside route model hook
your route file-> I assume books/new.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
newBook : this.store.createRecord('book'),
authors : this.store.findAll('author')
});
},
actions: {
save() {
this.modelFor(this.routeName).newBook.save();
}
}
});
and inside template you can access authors by using model.authors. and title by using model.newBook.title
<form {{action "save" on="submit"}}>
{{input value=model.newBook.title placeholder="Title"}}<br>
{{#power-select class="select"
selected=model.newBook.author
options=model.authors
onchange=(action (mut model.newBook.author)) as |author|}}
{{author.name}}
{{/power-select}}
<input type="submit" value="Save">
</form>
2) Like ebrahim suggested, you can have the below code in required controller,
authors: Ember.computed(function(){
return this.store.findAll('author');
})
3)As author model data is going to be shared data model for authors,books,books.new routes. so you can keep it in service and access it from all the required routes.
authors-service.js -> In service
import Ember from 'ember';
export default Ember.Service.extend({
store:Ember.inject.service(),
authors: undefined,
init(){
this._super(...arguments);
this.get('store').findAll('author', { reload: true }).then(function(results){
this.set('authors',results); //As this is going to be Ember.enumerables, you can iterate and get the data.
});
}
});
You can access authors from authors-service.js in any where by injecting it
authorsService:Ember.inject.service(). I guess in your case, you need to create controller books/new.js for books/new.hbs template,
books/new.js -> controller file
import Ember from 'ember';
export default Ember.Controller.extend({
authorsService:Ember.inject.service(),
authors: Ember.computed.alias('authorsService.authors');
});
Inside books/new.hbs template you can access authors property.

ember-data relationships not working

Trying to figure out how to use relationships and it just is not working.
// Data
{
"apps": {
"-AFCH5-Kvkc_nfQxnpZ8": {
"name": "Leap Day",
"playLink": "https://play.google.com/store/apps/details?id=com.nitrome.leapday",
"price": "0.00",
"like": 0,
"show": "-SFCH5-Kvkc_nfQxnpZ8",
"provider": "-PFCH5-Kvkc_nfQxnpZ8",
"imageUrl": "https://www.google.com/url?q=https://lh3.googleusercontent.com/5oR-jbrmKNWqdWQnwrDjPD2PJUJekGK_BUAQOjKD3GuJRTk2MLVzuU2HJ0wyY2BYPsdS%3Dw300-rw&sa=D&ust=1464126095843000&usg=AFQjCNH0RSH6_0bA_EUqnZaldtUxCH1fAw"
},
"-AFCH5-Kvkc_nfQxnpZ9": {
"name": "Gangfort",
"playLink": "https://www.google.com/url?q=https://play.google.com/store/apps/details?id%3Dcom.gangfort.game.android&sa=D&ust=1464126095844000&usg=AFQjCNFSTZ1p_uBvIHYw97c29XwJU3gEjw",
"price": "1.99",
"like": 0,
"show": "-SFCH5-Kvkc_nfQxnpZ8",
"provider": "-PFCH5-Kvkc_nfQxnpZ8",
"imageUrl": "https://www.google.com/url?q=https://lh3.googleusercontent.com/DV5mFhDQ2ADEbiF0S4cxL313JDqRazy9et7Etky5WtH7gxsm9DvbHhb52N0MH1swgfzR%3Dw300-rw&sa=D&ust=1464126095844000&usg=AFQjCNEymzxjrdN8wwL4qN40w5i8i9MlPw"
}
},
"shows": {
"-SFCH5-Kvkc_nfQxnpZ8": {
"number": 432,
"name": "Google I/O Secrets Revealed",
"date": "05/22/2016",
"app": ["-AFCH5-Kvkc_nfQxnpZ8", "-AFCH5-Kvkc_nfQxnpZ9"],
"url": "http://podnutz.com/aaa432/"
}
},
"providers": {
"-PFCH5-Kvkc_nfQxnpZ8": {
"firstName": "Steve",
"lastName": "McLaughlin",
"nick": "D2d",
"app": ["-AFCH5-Kvkc_nfQxnpZ8", "-AFCH5-Kvkc_nfQxnpZ9"]
}
}
}
Here is the app model
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { belongsTo } from 'ember-data/relationships';
export default Model.extend({
name: attr('string'),
playLink: attr('string'),
price: attr('string'),
like: attr('number'),
show: belongsTo('show', {
async: true
}),
provider: belongsTo('provider', {
async: true
}),
imageUrl: attr('string')
});
Here is the show model
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { hasMany } from 'ember-data/relationships';
export default Model.extend({
number: attr('number'),
name: attr('string'),
date: attr('date'),
apps: hasMany('app'),
url: attr('string')
});
Here is my route
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll('app');
}
});
Here is what the ember console looks like
So the problem is when I try to access the show object in my template, its not coming up
{{#each model as |show|}}
{{app.show.name}}
{{/each}}
Update
From the looks of the markup it seems like something is working here
Here is the console when I log app.show
Looks like apps: hasMany('app') reference in your show model is overwriting your relationship defined in the shows model.
you are not iterating through each app model in template.
it should be like that
{{#each model as |app|}}
{{app.show.name}}
{{/each}}
your each loop is like that {{#each model as |show|}}
Try this:
{{#each model as |app|}}
{{#if app.show}}
{{app.show.name}}
{{/if}}
{{else}}
Loading apps...
{{/each}}
Here I would recommend taking advantage of ember-promise helpers since the relation between an app and a show is asynchronous:
{{#each model as |app}}
{{#if (await app.show)}}
{{get (await app.show) 'name'}}
{{/if}}
{{else}}
Loading apps...
{{/each}}

set model attribute from ember uploader S3 url?

I have got ember-uploader to upload files successfully to S3. When the image is done uploading, I would like to set the model property image_url to the returned URL, then preferably submit the form to create the record as well. How would I do that?
app/models/post.js:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
body: DS.attr('string'),
image_url: DS.attr('string')
});
app/templates/posts/new.hbs:
<div class="new-post-form">
{{input type="text" placeholder="Name" value=name}}
{{input type="text" placeholder="Message" value=body}}
{{s3-upload value=image_url}}
<button {{action 'addPost'}} class="submit">Submit</button>
</div>
app/components/s3-upload.js:
import Ember from 'ember';
import EmberUploader from 'ember-uploader';
export default EmberUploader.FileField.extend({
url: 'http://localhost:3000/sign',
filesDidChange: (function() {
var uploadUrl = this.get('url');
var files = this.get('files');
var uploader = EmberUploader.S3Uploader.create({
url: uploadUrl
});
uploader.on('didUpload', function(response) {
// S3 will return XML with url
var uploadedUrl = Ember.$(response).find('Location')[0].textContent;
uploadedUrl = decodeURIComponent(uploadedUrl); // => http://yourbucket.s3.amazonaws.com/file.png
console.log("UPLOADED ! : " + uploadedUrl);
});
if (!Ember.isEmpty(files)) {
uploader.upload(files[0]); // Uploader will send a sign request then upload to S3 }
}).observes('files')
});
As you can see I tried setting the value of my s3-uploader component to image_url, that didn't seem to do anything.

How do I pass the value of a query param into the form?

My URL looks like http://localhost:4099/checkout/schedule/new?addressId=12 I am trying to pass the query param addressId to the form.
I've tried submitting it as a hidden input, but by the time it hits the save action. I check the Network tab of Ember inspector and this is what it is passing:
{"delivery":{"instructions":"foo","deliver_on":"bar","address_id":null}}
address_id is still null. What am I missing?
Full code below:
// app/pods/checkout/schedule/new/route.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('delivery');
// return this.store.createRecord('delivery', { addressId: this.get('addressId')});
},
// Cleanup the controller, when you leave the new route so the stale new record is also
// removed from the store.
// You can also use https://github.com/dockyard/ember-data-route instead
resetController: function (controller, isExiting) {
var model = controller.get('model');
if (!model.get('isDeleted') && isExiting && model.get('isNew')) {
model.deleteRecord();
} else {
model.rollback();
}
}
});
// app/pods/checkout/schedule/new/controller.js
import Ember from 'ember';
export default Ember.Controller.extend({
queryParams: ['addressId'],
addressId: null,
actions: {
save: function() {
var _this = this;
// this.get('model').set('addressId', this.get('addressId'));
this.get('model').save().then(function(){
_this.transitionToRoute('checkout.address.index');
}, function() {
// Need this promise, so we can render errors, if any, in the form
});
return false;
},
cancel: function() {
return true;
}
}
});
// app/pods/checkout/schedule/new/template.hbs
<form {{action "save" on="submit"}}>
{{addressId}}
{{input type="hidden" value=addressId}}
<p>
<label>Instructions:
{{input value=model.instructions}}
</label>
{{#each error in errors.instructions}}
<br />{{error.message}}
{{/each}}
</p>
<p>
<label>Deliver on:
{{input value=model.DeliverOn}}
</label>
{{#each error in errors.DeliverOn}}
<br />{{error.message}}
{{/each}}
</p>
<input type="submit" value="Next"/>
<button {{action "cancel"}}>Cancel</button>
</form>
// app/models/delivery.js
import DS from 'ember-data';
export default DS.Model.extend({
address: DS.belongsTo('address', { async: true }),
items: DS.hasMany('item', { async: true }),
instructions: DS.attr('string'),
deliverOn: DS.attr('string')
});
I believe what's happening is that you are not really submitting your form. Instead, you are calling save() on your model, which submits your model data. Therefore, hidden parameter in the form will not help you here.
Your addressId in the URL is tied to your addressId property in the controller, where as the addressId: null you are seeing being submitted in Chrome is the value of addressId property in the model

Link route to single object, without id

I have prepared this jsfiddle. This is the code:
App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 13,
adapter: 'DS.FixtureAdapter'
});
App.Router.map(function() {
// put your routes here
});
App.User = DS.Model.extend({
name : DS.attr('string'),
email : DS.attr('string'),
});
App.User.FIXTURES = [{ id: 'me', name: 'Max Smith', email: 'max.smith#email.com' }];
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
And this is the template:
<script type="text/x-handlebars" data-template-name="index">
{{#each model}}
<div class="form_labels_wrapper">
<dl class="dl-horizontal">
<dt>Id:</dt> <dd>{{id}}</dd>
<dt>Name:</dt> <dd>{{name}}</dd>
<dt>Email:</dt> <dd>{{email}}</dd>
</dl>
{{/each}}
</div>
</script>
What I want to do is to display the data of a single user. The opbject has no id (it represents the logged in user, which is session related, and has no id visible for ember). Instead, I am forced to create a list of users (with one user), and give it a fake id (me). This makes no sense in my application.
I would like to use this template, but I have no idea how to configure ember for this:
<script type="text/x-handlebars" data-template-name="index">
<div class="form_labels_wrapper">
<dl class="dl-horizontal">
<dt>Name:</dt> <dd>{{name}}</dd>
<dt>Email:</dt> <dd>{{email}}</dd>
</dl>
</div>
</script>
With this fixture:
App.User.FIXTURES = { name: 'Max Smith', email: 'max.smith#email.com' };
Note that this is a single element, and that I am not looping through the models with #each, because this should not be a list: there is only a single element.
Ember refuses to accept this. What can I do to get this to work?
You can returning an array of records from the model hook, as a result ember is generating an ArrayController for it, which expects it's content to be an array.
Change the model hook to return the single record. For instance using me as the id.
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.User.find('me');
}
});
Then your template works. See the updated jsfiddle.