How to add multiple selection into a many-to-many model in Ember.js? - ember.js

I have this small app where I'm trying to add the fruits selections of a multiple Ember.Select into an attribute of a model, "myfruits" of Person Alice. However, things are broken.
Perhaps my model is set up incorrectly.
This is the Ember.Select handlebars in the html:
{{view Ember.Select
multiple="true"
contentBinding="App.fruits"
valueBinding="pickedFruits"
}}
This is the model:
App.Person = DS.Model.extend({
name: DS.attr('string'),
myfruits: DS.hasMany('App.Fruit')
});
App.Fruit = DS.Model.extend({
kind: DS.attr('string'),
likedBy: DS.hasMany('App.Person')
});
This is the function that tries to save the multiple selection:
pickThem: function(){
var input_fruits = this.get('pickedFruits');
// should I create a Fruit object for each input_fruits?
var aperson = App.Person.createRecord({
name: "Alice",
myfruits: input_fruits
});
aperson.save();
}
I feel like the problem might be I'm not creating the Fruit objects. But I'm not sure how to make it work with the many-to-many relationship between Person and Fruit.

I guess what you need to do is as you already mentioned to create a App.Fruit record for every selected fruit and add it to the newly created App.Person.
Basically the important bit is:
App.PersonController = Ember.ArrayController.extend({
pickThem: function(){
var aperson = App.Person.createRecord({name: "Alice", myfruits: []});
this.get('pickedFruits').forEach(function(item){
aperson.get('myfruits').pushObject(App.Fruit.createRecord({kind:item, likedBy:[aperson.get('id')]}));
});
aperson.save();
}
});
Then provide a model for your person template:
App.PersonRoute = Ember.Route.extend({
model: function() {
return App.Person.find();
}
});
and in your template you can loop over the person records and inside that loop over their respective fruits:
{{#each model}}
{{name}} likes are:
{{#each myfruits}}
{{kind}}
{{/each}}
{{/each}}
Have a look at this updated jsbin.
You should however reset your local store adapter's data to avoid multiple entries after each application initialization. I've done it by creating a pseudo random suffix for the namespace of the LSAdapter, but this could be anything you find more convenient.
App.LSAdapter = DS.LSAdapter.create({
namespace: 'app-emberjs-'+Math.floor(Math.random()*1000)
});
Hope it helps.
Edit
After reading your last comment and just to show how it looks like in the chrome debugger tools that the LSAdapter stores the data. Have a look at the below screenshot. Here I've reloaded 2 times the app, and as expected two namespaces are created. If you have the same namespace every time thing are going to overlap resulting in some unexpected behavior.

Related

How does one access model data in a router/controller?

Bear with me please, I'm new.
Been breaking my head over this problem and sort of here as last resort. It's about how to access a model's data when that route loads. For instance, when /meals/2 loads, I want a function to run that sets the background of the document using that model's background-image string property. Or when /meals loads, the a function that uses a property of the collection's first item.
Any help on 'the ember way' to do this would be much appreciated.
Menu.hbs
{{#each meal in model}}
<span {{action 'mealSelected' meal.image_large}}>
{{#link-to 'menu.meal' meal tagName="li" class="meal-block" href="view.href"}}
[...]
{{/link-to}}
</span>
{{/each}}
<div id="meal-info-wrapper">
{{outlet}}
</div>
Model:
export default DS.Model.extend({
name: DS.attr('string'),
image: DS.attr('string')
});
Router.js
export default Router.map(function() {
this.route('about');
this.route('menu', { path: '/' }, function() {
this.route('meal', { path: '/meal/:id/:slug' });
});
});
routes/menu.js
export default Ember.Route.extend({
model: function() {
return this.store.find('menu');
},
afterModel: function() {
Ember.$(document).anystretch('temp-images/bg-1.png');
}
});
What I want to do in routes/menu.js for instance would be to have that image url be supplied by the model.
afterModel will run only once the model has been resolved, and the model is passed as an argument. So, based on my understanding of your app, you can adjust your routes/menu example to:
export default Ember.Route.extend({
model: function() {
return this.store.find('menu');
},
afterModel: function(model) {
Ember.$(document).anystretch(model.get('firstObject.image'));
}
});
Correct me if I misunderstood something, what you want to do is:
Change the background image of a DOM element based on a property found
in each Model's record.
Model loading is an async operation, you want to do the image swaping once you are sure the data is loaded. You used the afterModel hook to guarantee that, but that is not enough.
You want to modify the DOM inside your template, but you need to make sure that the template has been rendered. So, the DOM manipulation logic, instead of placing it in afterModel, it belongs to the didInsertElement event that Views have.
I suggest you use a component (its a view too), something like:
// your template
{{#each meal in model}}
{{meal-component content=meal}}
{{/each}}
// the meal-component
didInsertElement: function() {
var imgURLProperty = this.get('content.imgURLProperty');
Ember.$(document).anystretch(imgURLProperty);
}
Of course, you can't copy paste any of that. It just shows you the main mechanic of how you can modify a template based on the properties of a model.

How can I know which item in a handlebars each loop triggered a function in my Ember controller?

I am new to Ember, and I am trying to set up a list of folders. When you click on the icon next to a folder, it will load (i.e. find('folder', folder_id) ) the child folders. If the top level folder has 16 sub-folders, I am trying to set a property on those sixteen folders as they are finished loading -- so if the model for one of the sub-folders is finished loading, I want to set a property on it while the other fifteen folders are still being retrieved and serialized.
In my folder model:
import DS from 'ember-data';
export default DS.Model.extend({
files: DS.hasMany('file'),
children: DS.hasMany('folder', { inverse: 'parent', async: true }),
parent: DS.belongsTo('folder', {inverse: 'children'}),
name : DS.attr('string'),
nodeId : DS.attr('string'),
classId : DS.attr('string'),
parentId: DS.attr('string'),
contents: DS.attr(),
isVisible: DS.attr('boolean'),
childName: DS.attr('string')
});
In my template/view:
{{#each child in children}}
{{#if child.isLoading}}
Loading -->
{{else}}
{{setChildProperty}}
{{/if}}
{{/each}}
In my controller:
import Ember from 'ember';
export default Ember.Controller.extend({
children: function() {
var model = this.get('model');
var children = model.get('children');
return children;
}.property(),
setChildProperty: function(){
// how can I know, here in the controller, what the index is for
// the child that triggered this function, so that I can set a
// property on it without getting some type of
// 'didSetProperty / root.state.loading' error.
// The code below will cause errors because not all of the
// children have finished loading:
// var model = this.get('model');
// var self = this;
// var children = model.get('children');
// var contents = model.get('contents');
//
// children.forEach(function(item, index){
// var folderName = contents[index].folder;
// item.set('name',folderName);
// });
}.property('children.#each.isLoading'),
});
My Ember-CLI version is 0.1.15
Any help would be greatly appreciated.
UPDATE
In regards to mpowered's solution, the real problem is the nature of my folder models, in that the folder model does not have a name property, instead it has a list of child names. And since the child relationships are retrieved asynchronously when a user clicks on a sub-folder, I need to get the child folder names from another array, the contents array, which has identical indices. So using mpowered's solution my problem would be like so:
foldr: {{folder.id}}<br>
{{#each child in folder.children}}
{{#view 'toggle-list'}}
<i {{bind-attr id="child.id"}} class="fa fa-caret-right"></i>
{{/view}}
Index: {{_view.contentIndex}}
<!-- I need to be able to echo the above index in the
folder.contents array to get the child name.
-->
<!-- these work when uncommented, but I need a dynamic solution
name: {{folder.contents.[1].folder}}
name: {{folder.contents.1.folder}}
-->
<!-- None of these work:
name:{{!folder.contents.[_view.contentIndex].folder}}
name:{{!folder.contents.index.folder}}
name:{{!folder.contents.[index].folder}}
name:{{!folder.contents.{{!_view.contentIndex}}.folder}}
-->
Child:{{child.id}}..
<br>
<div {{bind-attr id="child.childName"}} class="folder-child hidden">
{{#if child.isVisible}}
isVisible is true<br>
{{folder-tree-component folder=child}}
{{/if}}
</div>
{{/each}}
I should also note that I am using a PODS structure and I have no control over the JSON response I get from the server to populate my models (other than Ember serializers of course).
There are many things that are concerning about this.
First, properties are not actions. You don't EVER want to change the state of an object when you're getting a property unless you have very very good reasons for doing so, or if you're implementing a getter/setter pattern. Delete setChildProperty, because that's all bad. In the template, you should just be displaying the property, not trying to "do" anything with it.
Second, this should probably be created as a component, because it sounds like the recursive structure you have here would lend itself well to reusable components. Something like folder-tree-component.hbs:
{{folder.name}}
{{#each child in folder.children}}
{{folder-tree-component folder=child}}
{{/each}}
And in your main route:
{{folder-tree-component folder=model}}
// Or, alternatively
{{#each child in model.children}}
{{folder-tree-component folder=child}}
{{/each}}
If I understand you correctly, you want a computed property on your model, not to "set" something on the model (or the controller/component) when it's finished loading. When the property is requested, it will compute the value and cache it in case you ask for it again. On your model:
name: function() {
// something with this.get('contents')
}.property('contents', 'otherDependency') // <- These will tell Ember to recompute the property when changed
I would learn more about ember fundamentals before trying to tackle this, there are some very simple, yet crucial things to learn about how Ember ticks, and a file tree isn't the simplest implementation to begin with.

Add and remove items from the customer’s item-list using ember.js relationships

I’m building my first app with ember.js and have this problem:
The customer should be able to add and remove items from the list. The standard way to do this is by creating a new item and add it to the customer.
App.Customer = DS.Model.extend(
{
firstname: DS.attr('string'),
lastname: DS.attr('string'),
items: DS.hasMany('item')
});
App.Item = DS.Model.extend(
{
customer: DS.belongsTo('customer'),
description: DS.attr('string')
});
My problem is that I can’t perform magic and let an item appear, I have to select one from my item pool, assign it to the customer and when removing the item form the customer I have to put it back into my item pool.
How do I remove the relationship between the objects without destroying any of the objects themselves? Meaning I’d like to let the foreign key jump around like I’m able to in a MySQL database.
You can remove an item from a customer like this:
In CustomerEditController:
removeItem: function(item) {
this.get('items').removeObject(item);
}
and then in your customer edit template:
{{#each items}}
{{this.description}}<button {{action removeItem this}}>remove item</button>
{{/each}}
#Steve H. It wasn’t exactly what I meant, but thanks to your help I could figure it out myself.
I had to add a few lines to your removeItem function
removeItem: function (item)
{
this.get('store').find('customer', 0).then(function (myPool)
{
this.get('items').removeObject(item);
myPool.get('items').pushObject(item);
});
}

Nested routes causing data reload/replacement?

Posted this on the emberjs forums, but SO seems more appropriate.
Hi! I have two routes called classyears and classyear. They're nested like so:
this.resource('classyears', function(){
this.resource('classyear', { path: '/classyear/:classyear_id'});
});
Posterkiosk.ClassyearsRoute = Ember.Route.extend({
model: function() {
return Posterkiosk.Classyear.find();
}
});
Posterkiosk.ClassyearRoute = Ember.Route.extend({
model: function(model) {
return Posterkiosk.Classyear.find(model.classyear_id);
}
});
My templates are:
Classyears:
<div class="yearList">
{{#each item in model}}
{{#linkTo 'classyear' item}}{{item.id}}{{/linkTo}}
{{/each}}
</div>
{{outlet}}
Classyear:
<div class="transformContainer">
{{trigger sizeComposites}}
{{name}}
{{#each students}}
{{partial student}}
{{/each}}
</div>
(The "trigger" helper is from another SO post. The issue was happening prior to adding it, though)
I'm using the Ember-model RESTAdapter. When I load /classyear/:classyear_id, it looks like classyear is rendering its data twice. Once with the correctly-loaded data, and once with no data loaded. The order appears to be random. If the no-data option happens last, it wipes out the correctly-loaded data, leaving a blank page. Vice-versa, and the page content displays just fine.
Any thoughts?
/edit 2: More info:
It looks as though the 0-record reply is from classyears loading. So, it's likely that the zero-record reply is actually just zero records in my hasMany field "students".
If I load /classyears (no class year specified), it only loads once, to get the class year options. If I then click on a class year, it doesn't reload classyears unless I refresh the page, at which time, it loads both, and if the classyears load (a findall) finishes second, it displays no data on the page (other than the classyears template, correctly populated, at the top).
So... maybe my classyears model isn't handling the hasMany field correctly?
I feel like I'm getting closer, but still not sure what's up.
First of all you need to specify a model for a Student, like so:
Posterkiosk.Student = Ember.Model.extend({
id: Ember.attr(),
name: Ember.attr(),
imageUrl: Ember.attr(),
gradyear: Ember.attr()
});
Posterkiosk.Student.adapter = fixtureAdapter;
Now, in your example you are setting the key for the has many to students, but students is an array of objects, not id's, so create a property called student_ids, and pass an array of ids, now that is your key.
Posterkiosk.Classyear = Ember.Model.extend({
students: Ember.hasMany('Posterkiosk.Student', {key: 'student_ids'})
});
If you set embedded: true, then your Classyears server response should come back like this:
{
classyears: [
{..},
{..}
],
students: [
{..},
{..}
]
}
Otherwise, EM would make a separate call to the endpoint on the Student model, and get that data based on the student_ids property.
See the working jsbin.
Tip: RC.7+ removed the underscore from partials, plus the partial name should be in quotes..

Ember Object not setting linkTo using ember-model

I have an array of objects from my controller. I can display the properties but the linkTo object is not being set
Im also using ember-model not ember-data
Msmapp.ClassroomsRoute = Ember.Route.extend({
model: function() {
return Msmapp.Classroom.findAll(); //Ember.Model
}
});
Msmapp.Classroom = Msmapp.Model.extend({
classroom_id: Ember.attr('number'),
classroom_name: Ember.attr('string'),
teacher_id: Ember.attr('number'),
grade: Ember.attr('number'),
students: Ember.attr('string'),
assignments: Ember.attr('string'),
classroomStudents: function () {
var studentObjects = []
this.get('students').forEach(function(student) {
studentObjects.push(Msmapp.Student.create(student));
});
return studentObjects;
}.property('students')
});
Msmapp.Classroom.reopenClass({
collectionKey: "classrooms",
url: '/classrooms'
});
{{#each classroom in controller }}
<li class="item">
{{#linkTo 'classroom' classroom }}{{ classroom.classroom_name }}{{/linkTo}}
</li>
{{/each}}
The classroom.classroom_name work just fine. I have even changed it to a computed property.
But the link is not setting the model. Its a nested route.
im getting /classrooms/undefined
The odd thing is when i click the link, it populates the classroom model and view.
My route
this.resource('classrooms', function() {
this.resource('classroom', {path: ':classroom_id'}, function() {
this.route('new_student');
});
this.route('new');
});
note: if i change the :classroom_id to just :id i get back something like this
/classrooms/<Msmapp.Classroom:ember403>
I just switched from ember-data to ember-model so im sure its something trivial im overlooking.
What is the primary key on your Classroom model?
It looks like it is classroom_id instead of id.
Ember.Model assumes a primary key of id, it is currently generating the URL with the id attribute of your classroom model, which is actually undefined because your actual id is classroom_id
You either need to change your data to use id for a primary key (Suggested), or customize Ember-Model to use a different primary key.
<Msmapp.Classroom:ember403> is result of model method toString(). I had the same problem and looks like linkTo not looking on rout parameter name. So my solution is
{{#linkTo 'classroom' classroom.classroom_id }}...