Has no method 'addArrayObserver' during {{#linkTo}} click - ember.js

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

Related

Ember.js active link class with query params

In an indexview I have links that set the sorting:
# Template
{{#link-to 'products' (query-params sortBy="title")}}Title{{/link-to}}
{{#link-to 'products' (query-params sortBy="price")}}Price{{/link-to}}
# Controller
queryParams: ['sortBy']
sortBy: 'id'
sortProperties: ( ->
[#get("sortBy")]
).property("sortBy")
That generates links that always have the class of 'active', but I want to highlight the currently active sort filter. What is the best way to do it?
I tried binding to a computed property like this:
{{#link-to 'products' (query-params sortBy="price") classNameBindings='sortByPrice'}}Price{{/link-to}}
sortByPrice: -> (
#get('sortBy') == 'price'
).property('sortBy')
That didn't quite work, but even if it did, that's not DRY at all – and eventually I would like to add a lot of different attributes on which to sort.
As I understand, the problem is that ember adds the 'active' class when it's in the context of that controller, which it always is with different query-params.
(Running the latest canary build of Ember as of 14th June)
This has been fixed in Ember Canary, as of https://github.com/emberjs/ember.js/pull/5109
QueryParams should add the "active" class based on whether the declared parameter in the {{#link-to}} helper has the same value as the attribute at that moment, as I can demonstrate in this jsbin.
That said, I'm having the same problem, so I believe there's some fringe case where this doesn't work right, and I'd be happy if you could modify this example to reflect that.
I'm facing the same problem now and I have temporary solution.
<!-- Posts Template -->
<!-- Categories -->
<div class="block step visible-desktop visible-tablet">
<div class="header">
<h3>Category</h3>
</div>
<div class="area categories">
<ul>
{{#each staticCategory in controller.staticCategories}}
{{post-category currentCategory=currentCategory staticCategory=staticCategory}}
{{/each}}
</ul>
</div>
</div>
<!-- Categories end -->
//Posts Controller
staticCategories: ['Front-End', 'JavaScript', 'jQuery', 'null'],
currentCategory: function () {
return this.get('category');
}.property('category'),
queryParams: ['category'],
category: null,
filteredContent: function () {
var category = this.get('category');
var posts = this.get('model');
return category ? posts.filterBy('category', category) : posts;
}.property('category', 'model')
//Post-Category Component template
{{#link-to 'posts' (query-params category=staticCategory)}}
{{staticCategory}}
{{/link-to}}
//Post-Category Component js
Blog.PostCategoryComponent = Ember.Component.extend({
tagName: 'li',
isActive: function () {
return this.get('staticCategory') === this.get('currentCategory')
}.property('currentCategory', 'staticCategory'),
classNameBindings: ['isActive:active']
});
I've found a solution for this. Ember (currently) seems to make a distinction between linking to a resource and linking to a sub-route, e.g doing {{link-to "resource"}} will always set the active class, but doing {{link-to "resource.index"}} will toggle the active state according to their query params.
Here's a jsbin showcasing the difference: http://emberjs.jsbin.com/zawukucisoni/3/edit
I've opened an issue that can be found here: https://github.com/emberjs/ember.js/issues/5359

Ember JS Deep Linking

I have an Ember JS 1.5.1 app with ember-data 1.0.8 beta. There are TWO simple compiled templates the relevant parts are:
index
<div class="container-fluid">
<div class="col-md-2 sidebar">
<ul class="nav nav-sidebar">
{{#each model}}
<li>
{{#link-to 'activities' this}}{{name}}{{/link-to}}
</li>
{{/each}}
</ul>
</div>
<div class="col-md-10 col-md-offset-2">
{{outlet}}
</div>
</div>
activities
<div>
<ul>
{{#each model.activities}}
<div class="row">
<p>activity {{id}} is {{name}}</p>
</div>
{{/each}}
</ul>
</div>
The application is also simple, reduced to a few bits of fixture data and some route functions:
window.App = Ember.Application.create();
App.ApplicationAdapter = DS.FixtureAdapter;
App.Router.map( function(){
this.resource('index', {path: '/'}, function(){
this.resource('activities', { path:':name'}, function(){
this.resource('activity');
});
});
});
App.IndexRoute = Ember.Route.extend({
model: function(){
return this.store.find('role');
}
});
App.ActivitiesRoute = Ember.Route.extend({
model: function(params){
var roles = this.modelFor('index');
return roles.findBy('name', params.name).get('activites');
}
});
App.Role = DS.Model.extend({
name: DS.attr('string'),
activities: DS.hasMany('activity', {async:true} )
});
App.Activity = DS.Model.extend({
name: DS.attr('string')
});
App.Role.FIXTURES = [{
id: 1,
name: 'Management',
activities: [1]
},{
id: 2,
name: 'Analysis',
activities: [1,2]
},{
id: 3,
name: 'Development',
activities: [2]
}]
App.Activity.FIXTURES = [{
id: 1,
name: 'talking'
},{
id: 2,
name: 'doing'
}];
What I get when I navigate to localhost is a simple list of the three roles on the left hand side of the screen and nothing on the right hand side. (as expected)
When I then select a link (such as 'Analysis') the outlet on the right hand side fills with the expected list of two activity names "talking" and "doing".
LHS list RHS pane
========== ========
Management talking
Analysis doing
Development
So far so good.
I noticed that when I hovered over the 'Analysis' link the browser shows the url below as expected
localhost:/#/Analysis
However when I cut and paste this url into the browser address bar directly I only get the left hand side list of links and nothing in the main window. The list of "talking" and "doing" does no appear. There are no errors shown in the browser and ember does not raise and exceptions.
How do you get this simple nested route to refresh all the contents when you directly deep link rather than having to navigate from the root all the time?
When you use link-to and pass it the model, it will skip the model hook supplying the model from the link-to to the route. If you refresh the page, it will hit each route down the tree until it's fetched the models for each resource/route necessary to fulfill the request. So if we look at your routes one at a time it will do this:
Hit the application route, fetch its model if it exists (application route is the root of every Ember app).
Hit your index route, where it will return App.Role.find()
Hit your activites route, where it will return App.Activity.find()
Number 3 is where you real issue lies. Regardless of whether or not that part of the url says Analysis, Management, or Development you will already return App.Activity.find(). You've defined the dynamic slug :name, ember will parse the appropriate part of the url, and pass that part is as an object, in the case of Analysis Ember will pass in { name: 'Analysis' } to your model hook. You will want to take advantage of this, to return the correct model.
App.ActivitiesRoute = Ember.Route.extend({
model: function(params){
var roles = this.modelFor('index');
return roles.findBy('name', params.name);
}
});
Additionally you are using a fairly old version of Ember Data. Here's a small example of how Ember Data should be used with newer versions: http://emberjs.jsbin.com/OxIDiVU/617/edit
As you can see, you no longer declare the store. Additionally you may run into trouble with what would be considered async properties, and might want to read https://github.com/emberjs/data/blob/master/TRANSITION.md

How to populate nested views/controllers?

I'm attempting to create an Ember application with nested views. I (basically) want three vertical columns. The left-most containing a list of Environments. Clicking an environment will populate the second (centre) column with the Job objects associated with the environment, and then when clicking on a Job in second column it populates the third column with various details based on the Job.
The data is coming from a Rails api via ActiveModel serializers, embedding IDs as required.
So far, I have the following routes defined:
Skills.Router.map ()->
this.resource 'environments', ->
this.resource 'environment', {path: ":environment_id"}, ->
this.resource 'jobs'
Skills.IndexRoute = Ember.Route.extend
redirect: ->
this.transitionTo 'environments'
Skills.EnvironmentsRoute = Ember.Route.extend
setupController: (controller) ->
controller.set('model', this.store.find('environment'))
My handlebars application template:
<div class="container">
<div class="row">
{{outlet }}
</div>
</div>
The Environments template
<div id="environments" class="col-sm-4">
{{#each }}
{{view Skills.EnvironmentView }}
{{/each}}
</div>
<div class="col-sm-8">
{{outlet}}
</div>
Environments View:
Skills.EnvironmentView = Ember.View.extend
templateName: 'environment'
click: ->
# should I be trying to update the Jobs controller here?
Environments controller:
Skills.EnvironmentsController = Ember.ArrayController
Models:
Skills.Environment = DS.Model.extend
title: DS.attr('string')
description: DS.attr('string')
jobs: DS.hasMany('job')
Skills.Job = DS.Model.extend
title: DS.attr('string')
environment: DS.belongsTo('environment')
This will lookup and display the Environments in the first column. How do I get the Jobs controller populated with the selected Environments Jobs from the association?
Edit: Binding seems like what I'm after, but I'm unsure as to how to bind the model/content property of one controller to a property from another?
What you should do is populate the list of environments in the environments template
(not the environments.index template). When an environment is selected from this list it should transition to the environment.show route. In the environment resource route it should show the list of jobs for that environment. This environment template should be rendered into the environments template. Then choosing a job will transition to the job.show route underneath the jobs resource.
The key is that the top level resource route will be retained and the subsequent child routes will be rendered into it, preserving the resource lists while rendering the child details.
Skills.Router.map ()->
this.resource 'environments', ->
this.route 'index'
this.resource 'environment', {path: ":environment_id"}, ->
this.route 'index'
this.route 'show'
this.resource 'jobs'
this.route 'index'
this.resource 'job'
this.route 'show'

Ember.js / Ember Data - Render hasMany key/value pair in template

I have a Document model, which has attributes/properties defined to it using a hasMany relationship. The purpose is to freely be able to define content in different areas of the document like header, body, footer while also creating presentational attributes like color or image.
KF.Document = DS.Model.extend
title: DS.attr 'string'
documentAttributes: DS.hasMany 'documentAttribute'
KF.DocumentAttribute = DS.Model.extend
attrKey: DS.attr 'string'
attrValue: DS.attr 'string'
document: DS.belongsTo 'document'
Document.documentAttributes returns a DS.ManyArray so in order to render it I could do the following:
{{#each da in documentAttributes}}
<p>{{da.attrKey}} - {{da.attrValue}}</p> <!-- returns: "header - this is my header" -->
{{/each}}
The problem is that I want to access the keys directly (using a proxy?) so I can bind the data directly like so:
{{textarea value=documentAttributes.header cols="80" rows="6"}}
<img {{ bindAttr src="documentAttributes.imageSrc" }} >
{{textarea value=documentAttributes.footer cols="80" rows="6"}}
How should I approach this?
An approach could be to enhance an em view (for the brave maybe a component as well), or create a proxy, that receives a DocumentAttribute object and defines dynamically a property with name the value of the attrKey and return the value of the attrValue. You could achieve this with the following code ,
http://emberjs.jsbin.com/ehoxUVi/2/edit
js
App = Ember.Application.create();
App.Router.map(function() {
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return createProxy(App.DocumentAttribute.create());
}
});
App.DocumentAttribute = Ember.Object.extend({
attrKey:"theKey",
attrValue:"theValue"
});
function createProxy(documentAttr){
App.DocumentAttributeProxy = Ember.ObjectProxy.extend({
createProp: function() {
_this = this;
var propName = this.get('attrKey');
if (!_this.get(propName)) {
return Ember.defineProperty(_this, propName, Ember.computed(function() {
return _this.get('attrValue');
}).property('attrKey'));
}
}.observes('content')
});
var proxy = App.DocumentAttributeProxy.create();
proxy.set('content',documentAttr);
return proxy;
}
HB
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{attrKey}}
<br/>
{{attrValue}}
<br/>
{{theKey}}
</script>
I couldn't get melc's solution to work with the DS.ManyArray returned by the relationship.
But his examples gave me some ideas and I did the following. Basically mapping the items through a "shortcut key" on the controller.
KF.DocumentsShowRoute = Ember.Route.extend
setupController: (controller, model) ->
controller.set('model', model)
# make an `Object` to store all the keys to avoid conflicts
controller.set('attrs', Ember.Object.create())
# loop through all `DocumentAttributes` in the `DS.ManyArray` returned by the relationship,
# get the `attrKey` from each item and make a shortcut to the item under `attrs` object
model.get('documentAttributes').forEach (item, index, enumerable) ->
key = item.get('attrKey')
controller.get('attrs').set(key, item)
The template, where header is the attrKey
{{input value=attrs.header.attrValue}}

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