ember.js: notify entry creation in nested route - ember.js

I have a problem: I add an object, and i would like it to be appended 'live' to my list of objects, in a context of nested routes. Being in Ember.js context, i don't want to notify this change, it should auto-update.
Is it possible?
given this router:
App.Router.map ->
#resource "raw_materials", ->
#route 'new'
and these templates:
raw_materials.hbl
{{#linkTo raw_materials.new}}CREATE{{/linkTo}}
<ul>
{{#each controller}}
<li>
{{name}} - {{quantity}}
</li>
{{outlet}}
raw_materials/new.hbl
{{view Ember.TextField valueBinding="name"}}
{{view Ember.TextField valueBinding="quantity"}}
<a {{action 'create' this}}>V</a>
controller:
App.RawMaterialsNewController = Ember.ArrayController.extend
create: (params)->
App.RawMaterial.createRecord({
name: params.name,
quantity: params.quantity
})
#get("store").commit()
so that the result is this:
UPDATE:
my current route is:
App.RawMaterialsRoute = Ember.Route.extend
model: -> App.RawMaterial.find({})
if i change it in
App.RawMaterialsRoute = Ember.Route.extend
model: -> App.RawMaterial.find()
the problem is resolved.
The reason why i used .find({}) is that it gives me the isLoaded event, while with .find() the array isLoaded is always true, also when it's not loaded
What can I do?
Thank you

Related

How to access a property in an itemController via the needs API in Ember.js

How can I access the properties of an itemController when I'm accessing the content of a controller via the needs API?
App.PostsIndexController = Ember.ArrayController.extend
itemController: 'post'
someAction: -> console.log("i'm melting!")
App.PostController = Ember.ObjectController.extend
someComputedProperty: (-> true ).property()
App.IndexController = Ember.Controller.extend
needs: ['postsIndex']
<script type="text/x-handlebars" data-template-name="index">
{{#each post in controllers.postsIndex.content}}
{{someComputedProperty}}
<a {{action someAction target="postsIndex"}}>click</a>
{{/each}}
</script>
I tried {{controllers.postsIndex.someComputedProperty}} and {{someComputedProperty}} in the index template.
How can I access the properties of an itemController when I'm accessing the content of a controller via the needs API?
First you should pass the controllers.postsIndex to the {{each}} helper instead of controllers.postsIndex.content. Otherwise the {{each}} helper will be looping over your model objects without going thru the PostsIndexController. So like #MilkyWayJoe suggested, try something like:
<script type="text/x-handlebars" data-template-name="index">
{{#each post in controllers.postsIndex}}
{{someComputedProperty}}
<a {{action someAction target="postsIndex"}}>click</a>
{{/each}}
</script>
I tried that, but it seems that someComputedProperty doesn't update when it changes, although it calculates correctly on page load.
Ember computed properties don't update unless you specify a list of dependencies:
App.PostController = Ember.ObjectController.extend
someComputedProperty: (-> true ).property('title', 'someotherproperty', 'etc')

Trouble rendering view associated with nested route in ember

I am having trouble with ember.js. Using the following routing setup I can not get the entries/new route to work. The index works fine but the entries/new template just refuses to render. I think it is where I am trying to render the view inside renderTemplate but I'm not sure what I'm doing incorrect. Your help would be much appreciated.
Journal.Router.map ->
#resource 'entries', {path: '/' }, ->
#route 'new'
return
return
Journal.EntriesNewRoute = Ember.Route.extend
renderTempalte: ->
#render 'entriesNew', {
into: 'application'
}
setupController: (controller) ->
controller.set 'heading', 'new entry'
return
Journal.EntriesNewView = Ember.View.extend
className: ['entries-new']
templateName: 'entries/new'
Journal.EntriesNewController = Ember.Controller.extend
heading: "New Journal Entry"
createEntry: ->
title = #get 'newTitle'
content = #get 'newContent'
if !title.trim() and !content.trim() then return null
Journal.Entry.createRecord
title: title
content: content
#get('store').commit()
return
And the entries/new template
{{ heading }}
{{view Ember.TextField id="entry-title" placeholder="Enter a title" valueBinding="newTitle"}}
{{view Ember.TextArea id="entry-content" placeholder="What do you have to say?" valueBinding="newContent"}}
<button {{action "createEntry"}} class="save">Save</button>
In your route, the 'into' should target the route that has your {{outlet}}
renderTempalate: ->
#render 'entriesNew', {
into: 'entries'
}
Though the renderTemplate hook's default action is to render into it's resources outlet, so as long as your Entries template has an {{outlet}} in it and your obeying the strict naming conventions, you don't need to define that hook at all.
If that's not the issue maybe post your Entries template

Dynamically setting controllers in {{#each}} with Ember

In Ember you can dynamically lookup controllers with {{#each}} doing something like this according to the guides:
Template:
{{#each controller}}
{{name}}
{{/each}}
Array controller:
App.DocumentListController = Ember.ArrayController.extend
lookupItemController: ((object) ->
if #get('name') == 'something' then 'someController'
else 'someOtherController'
I have a situation where I want to do the same thing but I need to use an ObjectController instead of an ArrayController.
So my template would look more like this:
{{#each controller in controller.documents itemController=lookupItemController}}
{{name}}
{{/each}}
Object controller:
App.DocumentListController = Ember.ObjectController.extend
lookupItemController: (object) ->
if #get('name') == 'something' then 'someController'
else 'someOtherController'
This second example is just made up and doesn't work. Is there a way to dynamically set controllers like this in an {{#each}}? Is there a better way to accomplish this?
What you might do in this instance is something like:
App.ListController = Ember.ObjectController.extend
needs: ['documents']
App.DocumentsController = Ember.ArrayController.extend
needs: ['list']
contentBinding: 'controllers.list.documents'
lookupItemController: (object) ->
if #get('name') == 'something' then 'someController'
else 'someOtherController'
And then in your template (assuming 'ListController' is the controller for the view):
{{#each document in controllers.documents}}
{{document.name}}
{{/each}}

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')

Ember.js: replacing simple linkTo helper with a view

I've got an app with basic functionality built out. I'm not going through and adding additional features. In this case I need to convert a simple button, currently using linkTo, to a View. Problem is that I'm not sure how to convert one to the other and still keep the link intact.
How do I do this conversion? Here's the code I have now:
<script type="text/x-handlebars" data-template-name="accountItem">
{{#each account in controller}}
{{#linkTo "account" account}}
<img {{bindAttr src="account.icon"}} />
{{/linkTo}}
{{/each}}
</script>
and here's the code I'm going to have:
<script type="text/x-handlebars" data-template-name="accountItem">
{{#each account in controller}}
{{#view "Social.AccountButtonView"}}
<img {{bindAttr src="account.icon"}} />
{{/view}}
{{/each}}
</script>
Social.AccountButtonView = Ember.View.extend({
tagName: 'a',
classNames: ['item-account'],
click: function(){
// do something
}
});
I would assume that I'd be building on top of the click handler in the View, but I'm not sure how to pass the reference to item being iterated over, nor how to reference the correct route within the View.
Assistance please?
Update 1
The first version renders an href attribute with a value of #/accounts/4 based on the Router I have set up:
Social.Router.map(function() {
this.resource('accounts', function(){
this.resource('account', { path: ':account_id'});
});
});
When I convert the current code to a view, how do I mimic the functionality that linkTo provides?
You can define a property binding for account in your handlebars template.
This binding works like this:
<script type="text/x-handlebars">
<h1>App</h1>
{{#each item in controller}}
{{#view App.AccountView accountBinding="item"}}
<a {{bindAttr href="view.account.url"}} target="_blank">
{{view.account.name}}
</a>
{{/view}}
{{/each}}
</script>
Note that I added accountBinding, so the general rule is propertyName and Binding as a suffix. And remember that when you add a property to a view, you will not be able to access it directly, instead you will have to access it with view.propertyName as shown above.
Just keep in mind that you must have a View class when using the {{view}} helper:
window.App = Em.Application.create();
App.AccountView = Em.View.extend(); // this must exist
App.ApplicationRoute = Em.Route.extend({
model: function() {
return [
{id: 1, name: 'Ember.js', url: 'http://emberjs.com'},
{id: 2, name: 'Toronto Ember.js', url: 'http://torontoemberjs.com'},
{id: 3, name: 'JS Fiddle', url: 'http://jsfiddle.com'}];
}
})
Working fiddle: http://jsfiddle.net/schawaska/PFxHx/
In Response to Update 1:
I found myself in a similar scenario, and ended up creating a child view to mimic the {{linkTo}} helper. I don't really know/think it's the best implementation tho.
You can see my previous code here: http://jsfiddle.net/schawaska/SqhJB/
At that time I had created a child view within the ApplicationView:
App.ApplicationView = Em.View.extend({
templateName: 'application',
NavbarView: Em.View.extend({
init: function() {
this._super();
this.set('controller', this.get('parentView.controller').controllerFor('navbar'))
},
selectedRouteName: 'home',
gotoRoute: function(e) {
this.set('selectedRouteName', e.routeName);
this.get('controller.target.router').transitionTo(e.routePath);
},
templateName: 'navbar',
MenuItemView: Em.View.extend({
templateName:'menu-item',
tagName: 'li',
classNameBindings: 'IsActive:active'.w(),
IsActive: function() {
return this.get('item.routeName') === this.get('parentView.selectedRouteName');
}.property('item', 'parentView.selectedRouteName')
})
})
});
and my Handlebars looks like this:
<script type="text/x-handlebars" data-template-name="menu-item">
<a {{action gotoRoute item on="click" target="view.parentView"}}>
{{item.displayText}}
</a>
</script>
<script type="text/x-handlebars" data-template-name="navbar">
<ul class="left">
{{#each item in controller}}
{{view view.MenuItemView itemBinding="item"}}
{{/each}}
</ul>
</script>
I'm sorry I can't give you a better answer. This is what I could come up with at the time and haven't touched it ever since. Like I said, I don't think this is the way to handle it. If you are willing to take a look into the {{linkTo}} helper source code, you'll see a modular and elegant implementation that could be the base of your own implementation. I guess the part you're looking for is the href property which is being defined like so:
var LinkView = Em.View.extend({
...
attributeBindings: ['href', 'title'],
...
href: Ember.computed(function() {
var router = this.get('router');
return router.generate.apply(router, args(this, router));
})
...
});
So I guess, from there you can understand how it works and implement something on your own. Let me know if that helps.