Saving parent_id relationship with ember data - ember.js

Okay. I've had a real good look through SO and other sources returned by Google, but am yet to find an answer to my problem.
I have two models:
App.Kid = Ember.Model.extend
title: DS.attr "string"
parent: DS.belongsTo "App.Parent"
App.Parent = Ember.Model.extend
title: DS.attr "string"
kids: DS.hasMany "App.Kid"
Most questions on here discuss retrieving ember data relationships from sideloaded JSON data, which I'm capable of doing. What I need to know is how do I save the parent_id when creating a new kid?
At the moment I'm saving a new kid in the App.KidController like so:
App.ParentController = Ember.ObjectController.extend
needs: [ "widgets" ]
App.KidCreateController = Ember.ObjectController.extend
needs: [ "dashboard" ]
saveChild: ->
#content.get( "store" ).commit()
And when saving there is no parent_id to be seen, however I do get a new parent object (assigned to a parent key) with the id of 0.
Can anybody explain what I'm doing wrong here?
Update 1
So using my actual situation I'll explain what I'm doing.
A user has several dashboards which have multiple widgets attached to it. My model structure looks like this:
App.Dashboard = DS.Model.extend
title: DS.attr "string"
widgets: DS.hasMany "App.Widget"
App.Widget = DS.Model.extend
title: DS.attr "string"
type: DS.attr "string"
...
dashboard: DS.belongsTo "App.Dashboard"

One note at first, you have labelled your question with ember-data but your are defining you models using Ember.Model.extend which is used when using ember-model as your persistence library, therefore you should change it to DS.Model.extend or label your question differently, I'm assuming ember-data in my answer.
If your App.Kid model belongs to a App.Parent which has a kids array than your should use a ParentController to save the new Childs belonging to that parent this way you will have access to the parent id and it's kids array which you need to push the new Childs into.
Example:
Given a simple template like this:
{{#each parent in model itemController="parent"}}
<strong>(id: {{parent.id}}) {{parent.title}}</strong>
<div class="well">
<strong>Kids</strong>
<ul>
{{#each kid in parent.kids}}
<li>(id: {{kid.id}}) {{kid.title}}</li>
{{/each}}
</ul>
Kid name: {{input valueBinding="newKidName"}} <button class="btn btn-info" {{action saveChild newKidName}}>Add kid</button>
</div>
{{/each}}
You should then define a ParentController for each parent item and create there a saveChild function:
App.ParentController = Ember.ObjectController.extend({
needs: ['dashboard'],
saveChild: function(newKidName) {
// get the dashboard id you want with
// this.get('controllers.dashboard').get('id');
// this assumes you have one dashboard?
// create the new child
var newKid = App.Kid.createRecord({title: newKidName});
// push the newly created child into the parent's kids array
this.get('content').get('kids').pushObject(newKid);
// commit changes to the parent
this.get('content').get('store').commit();
}
});
See here for a simple demo of the example: http://jsbin.com/odosoy/115/edit
Hope it helps.

Well, it turns out I was being a idiot. I'm using Laravel as my backend and when defining your models you need to define which attributes are fillable, however I forgot to add dashboard_id to the list. Everything works fine now.

Related

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.

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

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);
});
}

How to add multiple selection into a many-to-many model in 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.

Has no method 'addArrayObserver' during {{#linkTo}} click

Hey not sure if anyone can help me, but I have been struggling with this error for a long time:
"Uncaught TypeError: Object <App.AssetType:ember408:2> has no method 'addArrayObserver'"
Here is the template with the {{#linkTo}}'s that produce this error when clicked
<div class="row">
<div class="twelve columns">
<h2>{{title}} - Assets</h2>
</div>
</div>
<div class="row">
<div class="three columns">
<ul>
{{#each assetTypes}}
{{#linkTo 'product.filter' this}}{{title}}{{/linkTo}}
{{/each}}
</ul>
</div>
<div class="nine columns">
{{outlet}}
</div>
</div>
and the Application code
window.App = Ember.Application.create
rootElement: '.solution_products_documents'
App.ApplicationRoute = Ember.Route.extend
model: ->
App.Product.find()
App.ApplicationController = Ember.ArrayController.extend
sortProperties: ['title']
App.ProductRoute = Ember.Route.extend
model: (params) ->
App.Product.find params.product_id
setupController: (controller, model) ->
controller.set 'documents', model.get 'document_ids'
App.ProductController = Ember.ObjectController.extend
assetTypes: (->
docs = #get('documents')
docs.getEach 'asset_type_id'
).property('documents')
App.ProductFilterRoute = Ember.Route.extend
model: (params) ->
type = App.AssetType.find params.asset_type_id
product = this.modelFor 'product'
docs = product.get 'document_ids'
model = docs.filterProperty 'asset_type_id', type
App.ProductFilterController = Ember.ArrayController.extend()
App.Router.map ->
#route 'index', { path: '/' }
#resource 'product', { path: '/products/:product_id' }, ->
#route 'filter', { path: '/filter-by/:asset_type_id' }
##
# MODELS / EMBER-DATA
##
serializer = DS.JSONSerializer.create()
serializer.configure 'App.Document',
sideloadAs: 'documents'
serializer.configure 'App.AssetType',
sideloadAs: 'asset_types'
serializer.configure 'App.Product',
sideloadAs: 'products'
App.RestAdaptor = DS.RESTAdapter.extend
serializer: serializer
namespace: URL.slice 1
DS.Store.extend(
adapter: App.RestAdaptor
revision: 11
).create()
App.Product = DS.Model.extend
title: DS.attr 'string'
document_ids: DS.hasMany 'App.Document'
App.Document = DS.Model.extend
title: DS.attr 'string'
product_id: DS.belongsTo 'App.Product'
asset_type_id: DS.belongsTo 'App.AssetType'
App.AssetType = DS.Model.extend
title: DS.attr 'string'
document_ids: DS.hasMany 'App.Document'
######### /> END MODELS #################
Everything works as planned if I put the URL #/products/4/filter-by/2 into the address bar. It's only when I click the {{#linkTo}}'s that I get this error and the content is not displayed. The error is thrown before it get's to the App.ProductFilterRoute because the debugger statement in the route is not executed, but it is on page refresh.
Any help or direction is greatly appreciated, as I don't really know where to look.
UPDATE:
If I do not use the {{#linkTo}} helper and instead manually construct the url
{{title}}
everything works fine. What is different between the linkTo and manual href?
The error basically says that Ember expects an Array, when you navigate to the ProductFilterRoute.
Why does Ember expect an Array here?
The Controller for your Route (ProductFilterController) is of type ArrayController.
I am not very familiar with coffeescript, but your model hook seems to return an array too. Important Notice: The model hook is just executed when entering your App via Url. (This is why your manual navigation by Url and the href both work. Cite from EmberDoc: "A hook you can implement to convert the URL into the model for this route.")
Why is the error thrown?
So your route revolves around an array. You are passing just a plain object. So the golden rule is: Pass the same data structure (an array in this case) to your {{linkTo}} helper, which is returned by your model hook implementation.
A possible solution:
Use an action instead of {{linkTo}}
Implement an action in your route that finds all document with the given asset_type and pass it to your route.
Modifiations to template:
<a {{action 'filterProductByAsset' this}}> {{title}} </a>
Extensions to ProductFilterRoute:
events:{
filterProductByAsset : function(assetTypeId){
type = App.AssetType.find(asset_type_id);
product = this.modelFor('product');
docs = product.get('document_ids');
models = docs.filterProperty('asset_type_id', type);
this.transitionTo("product.filter", models)
}
}
I suspect this is the problem:
assetTypes: (->
docs = #get('documents')
docs.getEach 'asset_type_id'
).property('documents')
this looks like it will produce an array like this:
[1,2,3,4,5]
When really you need an array of objects that respond to id, e.g.
object1 = Em.Object.create id: 1
object2 = Em.Object.create id: 2
[object1, object2] #etc
If you want to change this behaviour, you will need to look into the serialize hook of the route you are linking to, in this case product.filter
With regard to {{linkTo}} vs. manually created links, the linkTo helper is js enabled, you should always use it instead of a manual link. This becomes more of a real problem when using the HistoryLocation / pushState support as it forces a full page reload.
There is another problem here: you can't observe documents like this. You must use something like property('documents.#each')