No 'addArrayObserver' or 'removeArrayObserver' - ember.js

I am modelling an administrative interface that helps manage database tables. There are two models: [DbxTables, DbxColumns]. I want to allow the user to choose a table using Boostrap's tab-menu and then be able to see the columns in that table. This mostly works. For instance, if I type http://my.domain.com/index.html#/performance where performance is the name of the table I get the following:
If I decide to go to another table by manipulating the URL -- for instance http://my.domain.com/index.html#/food-- then it will successfully switch to the food columns in the right-hand pane. The problems come in when I use my {{linkTo}} links in the left-hand tab menu. One of two things happens:
If the {{linkTo}} looks like {{linkTo 'columns' this}} then it makes the URL parameter something like <App.DbxTable:ember371:performance> rather than just performance
If the {{linkTo}} looks like {{linkTo 'columns' this.id}} then it sets the ULR parameter correctly (or at least so it appears to in the URL window) but the if I click on "meal" in the left-hand tab menu I get the following error: Object meal has no method 'addArrayObserver'. If I click on something else it follows that error message with "Object meal has no method 'removeArrayObserver' ".
In both of the above cases, after receiving an error, the column names on the right hand side do not update. The first style of {{linkTo}} is what the screencast from Tom Dale seemed to suggest was the right syntax. However, seeing that the links were off I came up with the this.id approach. Any help would be greatly appreciated.
For some additional code context (router.js):
App.Router.map(function() {
this.resource('about');
this.resource('dbx', function() {
this.resource('columns', { path: ':dbx_table'});
});
this.resource('oauth');
this.resource('postTypeMappings');
});
App.DbxRoute = Ember.Route.extend({
model: function() {
return App.DbxTable.find();
}
});
App.ColumnsRoute = Ember.Route.extend({
model: function(table) {
return App.DbxColumn.find(table);
}
});
Model: dbx_table.js
App.DbxTable = DS.Model.extend({
name: DS.attr("string"),
desc: DS.attr("string"),
db_column: DS.attr("string"),
columns: DS.attr("raw")
});
Model: dbx_column.js
App.DbxColumn = DS.Model.extend({
name: DS.attr("string"),
dbType: DS.attr("string"),
insight: DS.attr("string"),
enum: DS.attr("string"),
staticUom: DS.attr("string"),
uomContext: DS.attr("string"),
jsonStruct: DS.attr("string"),
desc: DS.attr("string")
});
Model: store.js
App.Store = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.reopen({
namespace: 'api/lifegadget'
})
});
DS.RESTAdapter.registerTransform('raw', {
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
UPDATE (adding handlebars):
In order to provide some more detail. Here are the handlebars templates:
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Ember Starter Kit</title>
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/bootstrap.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<script type="text/x-handlebars">
<div class="navbar">
<div class="navbar-inner">
<a class="brand" href="#">Admin</a>
<ul class="nav">
<li>{{#linkTo "dbx"}}DBX{{/linkTo}}</li>
<li>{{#linkTo "postTypeMappings"}}Post Mappings{{/linkTo}}</li>
<li>{{#linkTo "oauth"}}OAuth{{/linkTo}}</li>
<li>{{#linkTo "about"}}About{{/linkTo}}</li>
</ul>
</div>
</div>
<div id="outlet-target">
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" id="about">
<div class='about'>
Admin Screen
</div>
</script>
<script type="text/x-handlebars" id="dbx">
<div class='dbx'>
<div id="dbx-table-pane" class="tabbable tabs-left">
<ul class="nav nav-tabs">
{{#each model}}
<li>
{{#linkTo "columns" this}}{{db_column}}{{/linkTo}}
</li>
{{/each}}
<li id="add-table">
<em>Add DBX Table</em>
</li>
</ul>
<div id="dbx-main" class="tab-content">
{{outlet}}
</div
</div>
</script>
<script type="text/x-handlebars" id="columns">
The columns are:
<ul>
{{#each model}}
<li>{{name}}</li>
{{/each}}
</ul>
</script>
<script src="js/libs/jquery-1.9.1.js"></script>
<script src="js/libs/handlebars-1.0.0-rc.3.js"></script>
<script src="js/libs/ember-1.0.0-rc.2.js"></script>
<script src="js/libs/ember-data-12.js"></script>
<script src="js/app.js"></script>
<script src="js/router.js"></script>
<script src="js/models/store.js"></script>
<script src="js/models/dbx_table.js"></script>
<script src="js/models/dbx_column.js"></script>
</body>
</html>
UPDATE 2:
I've now added a short video that demonstrates the problem: https://vimeo.com/63388787

This looks similar to this question: How to show the string value of a non id field for a model in an ember.js route?
I think you will need to use the serialize hook in your ColumnsRoute to define how the model is turned into the URL dynamic segment.
App.ColumnsRoute = Ember.Route.extend({
model: function(table) {
return App.DbxColumn.find(table);
},
//dbx_table matches the dynamic route name in your router, and I am assuming
// you want to use the DbxColumn.name as the URL parameter
serialize: function(model) {
return {dbx_table: model.get('name')};
}
});
The model hook handles turning a URL into a model object when the URL is directly navigated to, and the serialize hook is used to construct the URL when a route is transitioned to using an already existing object, such as when transitionTo or a {{#linkTo}} is used.

Related

Ember JS app shows blank page

I'm creating a simple Ember JS app based loosely on the Beginner's Guide on the website. However, I can't seem to get anything to render on the page. When I check the Ember Inspector my routes are all present and I don't see any errors so it's not immediately clear where the problem is.
Here is the html file:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Coin</title>
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/bootstrap.min.css">
</head>
<body>
<script type="text/x-handlebars">
<div>Hello</div>
{{outlet}}
</script>
<script type="text/x-handlebars" id="accounts">
<div>accounts</div>
<div class="col-md-2">
<ul class="nav nav-pills nav-stacked">
{{#each}}
<li>account link {{#link-to 'account' this}}{{name}}{{/link-to}}</li>
{{/each}}
</ul>
</div>
<div class="col-md-10">
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" id="account">
<ul>
{{#each}}
<li>{{comment}}</li>
{{/each}}
</ul>
</script>
<script src="js/libs/jquery-1.10.2.js"></script>
<script src="js/libs/handlebars-1.1.2.js"></script>
<script src="js/libs/ember-1.4.0.js"></script>
<script src="js/app.js"></script>
<script src="dist/js/bs-core.min.js"></script>
<script src="dist/js/bs-nav.min.js"></script>
<!-- to activate the test runner, add the "?test" query string parameter -->
<script src="tests/runner.js"></script>
</body>
</html>
And here is my app.js file:
App = Ember.Application.create();
App.Router.map(function() {
this.resource('accounts');
this.resource('transactions', { path: ':accounts_id' });
})
App.AccountsRoute = Ember.Route.extend({
model: function() {
return accounts;
}
})
App.TransactionsRoute = Ember.Route.extend({
model: function() {
return transactions;
}
})
var accounts = [{
id: 1,
name: 'Account 1',
}, {
id: 2,
name: 'Account 2'
}];
var transactions = [{
id: 1,
id_account: 1,
amount: 12,
comment: 'blah'
}, {
id: 2,
id_account: 1,
amount: 5,
comment: 'blah2'
}, {
id: 3,
id_account: 2,
amount: 98,
comment: 'blah4'
}];
The one thing that renders properly on the page is the "Hello" in the application template. Nothing shows in the {{outlet}} when I navigate to #/accounts or any of the other defined routes. And in fact, when I add #/accounts in the url even my "Hello" disappears.
Is there something obvious that I'm overlooking? I'm new to Ember so I'm sure it's something silly but I can't figure out where the problem is from the documentation.
edit: I'm using Ember v1.4.0 linked directly from the website and I have checked "Allow access to file urls" in Ember Inspector.
I made a working version of what you started in this ember jsbin
What I noticed you were doing wrong was by trying to link to a non-existing route with this {{#link-to 'account' this}}. You didn't define the account route that I notice you want to receive a parameter. However I think you want to send the id of the current item in the iteration. So you can simply do that by changing this into {{#link-to 'account' id}}.
So 2 important modifications to get to something working was to add the route and to fix the param of the link-to helper. Then I just assumed that you want to show transactions as well and fixed that as well.
Hope this helps.

Multiple Adapters Single Store Issue

I am trying to use different Adapters for 2 different Ember Models but this setup seems to be having problems. I'm able to fetch(find) the 2 models using their respective adapters but when i try to save(commit) them, an issue arises and both try to use the first model's adapter only.
<html>
<head>
<script type="text/javascript" src="js/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="js/handlebars.js"></script>
<script type="text/javascript" src="js/ember-latest.js"></script>
<script type="text/javascript" src="js/ember-data-latest.js"></script>
</head>
<body>
<script type="text/x-handlebars" id="application">
<h1>Application</h1>
{{ outlet }}
</script>
<script type="text/x-handlebars" id="index">
<h1>Index</h1>
<b>Create</b>
<a href="#" {{action "createOne"}}>Create One</a>
<a href="#" {{action "createTwo"}}>Create Two</a>
<hr>
<b>Load</b>
<a href="#" {{action "loadOne"}}>Load One</a>
<a href="#" {{action "loadTwo"}}>Load Two</a>
</script>
<script>
App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.extend({
url: "/one"
})
});
App.Store.registerAdapter("App.Two",DS.RESTAdapter.extend({
url: "/two"
}));
App.One = DS.Model.extend({
name: DS.attr("string",{defaultValue:"one"})
});
App.Two = DS.Model.extend({
name: DS.attr("string",{defaultValue:"two"})
});
App.IndexController = Ember.ObjectController.extend({
createOne: function() {
App.One.createRecord({}).save();
},
createTwo: function() {
App.Two.createRecord({}).save();
},
loadOne: function() {
App.One.find();
},
loadTwo: function() {
App.Two.find();
}
});
</script>
</body>
</html>
The Load One and Load Two links work well and hit the url's GET one/ones and GET two/twos respectively. But On using Create One and Create Two links the links hit are PUT one/ones and PUT one/twos. and not one/ones and two/twos. So Ember is trying to hit the second click of creating two with One's adapter and not changing it to Two's Adapter.
Any idea anyone ?

The bound view does not update upon saving when using ember-model RESTAdapter.

I am exploring using ember-model (https://github.com/ebryn/ember-model), as an alternative to ember-data in my ember.js application. I don't have enough rep yet to create a new tag, but if so, I'd create ember-model.js to avoid confusion.
The desired behavior is to display a simple list of records and display a form to create new records via a RESTful rails API, Using GET and POST.
This worked smoothly with the Fixture Adaptor inside ember-model. Upon creation, as expected, the new record is pushed onto the Fixture array and the browser automatically updates to reflect the new record.
Using the ReST Adaptor, I was able to display the list and create a new record. However, the newly created record, which is bound to a template, will not update unless I hit the refresh button on the browser. And now I wonder what I might be doing wrong.
I dug around StackOverflow and elsewhere before I posted. I'm going to dig into the source next, but I thought I'd ask to see if anyone else has any insights.
Thank you, good people of Stack Overflow.
This is my app.js:
window.Dash = Ember.Application.create({
LOG_TRANSITIONS: true
});
Dash.Router.map(function() {
// put your routes here
this.resource('apps', function() {
this.route('add');
this.resource('app', { path: ':app_id' });
});
});
Dash.IndexRoute = Ember.Route.extend({
//temporary redirect to games list
redirect: function () {
this.transitionTo('apps');
}
});
Dash.AppsRoute = Ember.Route.extend({
model: function() {
return Dash.App.find();
}
});
Dash.AppsAddController = Ember.Controller.extend({
save: function() {
var name = this.get('name');
var id = this.get('id');
var account_id = this.get('account_id');
var auth_id = this.get('auth_id');
var state = Dash.AppState.selectedState;
var store_type = Dash.AppPlatform.selectedPlatform;
var store_app_id = this.get('store_app_id');
var download_url = this.get('download_url');
var newApp = Dash.App.create({
name: name,
id: id,
auth_id: auth_id,
state: state,
store_type: store_type,
store_app_id: store_app_id,
download_url: download_url
});
newApp.save();
}
});
Dash.AppPlatform = Ember.ArrayController.create({
selectedPlatform: null,
content: [
Ember.Object.create({id: 'itunes', name: 'iOS'}),
Ember.Object.create({id: 'android', name: 'Google Play'}),
Ember.Object.create({id: 'amazon', name: 'Amazon Appstore'}),
]
});
Dash.AppState = Ember.ArrayController.create({
selectedState: null,
content: [
Ember.Object.create({name: 'test'}),
Ember.Object.create({name: 'production'})
]
})
Dash.App = Ember.Model.extend({
id: Ember.attr(),
name: Ember.attr(),
auth_id: Ember.attr(),
state: Ember.attr(),
store_type: Ember.attr(),
store_app_id: Ember.attr(),
download_url: Ember.attr(),
});
Dash.App.adapter = Ember.RESTAdapter.create();
//hardcoding account id for now
Dash.App.url = 'http://localhost:3000/accounts/980190962/apps';
Dash.App.collectionKey = 'apps';
This is index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dashboard</title>
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<h2>Dashboard</h2>
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="apps">
{{#linkTo 'apps.add' }}Add an App{{/linkTo}}
<h3>Apps List</h3>
{{#if model}}
<ol>
{{#each model}}
<li>{{#linkTo 'app' this}}{{name}}{{/linkTo}}</li>
{{/each}}
</ol>
<div>{{outlet}}</div>
{{else}}
<p>You have no Apps.</p>
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="apps/index">
<p>Click a game to Edit or click Add to enter a new App.</p>
</script>
<script type="text/x-handlebars" data-template-name="app">
<div>
Name: {{name}}<br />
ID: {{id}}<br />
Account ID: {{account_id}}<br />
AuthID : {{auth_id}}<br />
Test Mode?: {{state}}<br />
Store Type: {{store_type}}<br />
Store App ID: {{store_app_id}}<br />
Download URL: {{download_url}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="apps/add">
<p><label for="name">Name:</label><br />{{view Ember.TextField valueBinding='name'}}</p>
<p><label for="id">ID:</label><br />{{view Ember.TextField valueBinding='id'}}</p>
<p><label for="account_id">Account ID:</label><br />{{view Ember.TextField valueBinding='account_id'}}</p>
<p><label for="auth_id">Auth ID:</label><br />{{view Ember.TextField valueBinding='auth_id'}}</p>
<p><label for="state">Test Mode:</label><br />{{view Ember.Select contentBinding='Dash.AppState' valueBinding='Dash.AppState.selectedState' optionValuePath="content.name" optionLabelPath="content.name"}}</p>
<p><label for="store_type">Store Type:</label><br />{{view Ember.Select contentBinding='Dash.AppPlatform' valueBinding='Dash.AppPlatform.selectedPlatform' optionValuePath="content.id" optionLabelPath="content.name"}}</p>
<p><label for="store_app_id">Store App ID:</label><br />{{view Ember.TextField valueBinding='store_app_id'}}</p>
<p><label for="download_url">Download URL:</label><br />{{view Ember.TextField valueBinding='download_url'}}</p>
<p><button {{action 'save'}}>Save</button></p>
</script>
<script src="js/libs/jquery-1.9.1.js"></script>
<script src="js/libs/handlebars-1.0.0-rc.4.js"></script>
<script src="js/libs/ember-1.0.0-rc.6.js"></script>
<script src="js/libs/ember-model.js"></script>
<script src="js/app.js"></script>
</body>
</html>
It won't automatically work this way. Your content array in the AppState controller is just a static array with items in it. You'd have to do something like this
this.controllerFor('Apps').get('content').pushObject(newApp) to see it reflect in the UI.
Your Apps controller will be an ArrayController because the Apps route's model function returned an array.

Emberjs rendering wrong template

I am working with EmberJs and can't figure out why the following code won't render the 'new' template. When I navigate to /shops, I get a list of shops and a create button linking to '/shops/new', but when I click create, the 'new' template isn't rendered and instead stays the same as 'shops'. I can navigate to each individual shop fine, just not new.
App.js
window.App = Ember.Application.create();
App.Router.reopen({
location: 'history'
});
// Router
App.Router.map(function() {
this.resource('shops', function() {
this.route('new');
});
this.resource('shop', { path: '/shops/:shop_id' });
});
App.ShopsRoute = Ember.Route.extend({
model: function() {
return App.Shop.find();
}
});
App.ShopsNewRoute = App.ShopsRoute.extend({
model: function() {
return App.Shop.createRecord();
},
setupController: function( controller, model ) {
return this._super(),
controller.set( 'content', model )
}
});
App.ShopsNewController = Ember.ObjectController.extend();
// Models
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.RESTAdapter
});
App.Shop = DS.Model.extend({
name: DS.attr('string'),
body: DS.attr('string'),
});
Index.html
<!DOCTYPE html>
<html>
<head>
<title>Ember/Rails App</title>
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
<body>
<script type="text/x-handlebars" data-template-name="application">
<div class="row">
<div class="twelve columns">
<h1>Ordr</h1>
<hr>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="shops/new">
<h2>New Shop</h2>
</script>
<script type="text/x-handlebars" data-template-name="shops">
{{#each shop in controller}}
{{#linkTo "shop" shop}} {{ shop.id }}{{/linkTo}}
{{/each}}
{{#linkTo 'shops.new'}}Create{{/linkTo}}
</script>
<script type="text/x-handlebars" data-template-name="shop">
<h2>{{name}}</h2>
<p>{{body}}</p>
</script>
</body>
</html>
The way you have set up your routes and templates, you are telling Ember that you want to navigate to shops.new and keep all the list of shops showing even while you are in shops.new.
If that is in fact the scenario that you want, all you need to do is add an {{outlet}} inside the shops template:
<script type="text/x-handlebars" data-template-name="shops">
{{#each shop in controller}}
{{#linkTo "shop" shop}} {{ shop.id }}{{/linkTo}}
{{/each}}
{{#linkTo 'shops.new'}}Create{{/linkTo}}
{{outlet}}
</script>
However, if that is not what you really intend, and you actually want the list of shops to be hidden when you transition to shops.new, then you will need to change a couple of things.
change your App.ShopsRoute to App.ShopsIndexRoute :
App.ShopsIndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('shop');
}
});
and your shops template to shops/index template:
<script type="text/x-handlebars" data-template-name="shops/index">
{{#each shop in controller}}
{{#linkTo "shop" shop}} {{ shop.id }}{{/linkTo}}
{{/each}}
{{#linkTo 'shops.new'}}Create{{/linkTo}}
</script>
Any of those 2 methods should fix your problem, depending on what your intentions are.

Using multiple named outlets and a wrapper view with no content in Emberjs

I'm trying to use multiple named outlets with Ember.js. Is my approach below correct?
Markup:
<script type="text/x-handlebars" data-template-name="application">
<div id="mainArea">
{{outlet main_area}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="home">
<ul id="sections">
{{outlet sections}}
</ul>
<ul id="categories">
{{outlet categories}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="sections">
{{#each section in controller}}
<li><img {{bindAttr src="section.image"}}></li>
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="categories">
{{#each category in controller}}
<img {{bindAttr src="category.image"}}>
{{/each}}
</script>​
JS Code:
Here I set the content of the various controllers to data grabbed from a server and connect outlets with their corresponding views. Since the HomeController has no content, set its content to an empty object - a hack to get the rid of this error message:
Uncaught Error: assertion failed: Cannot delegate set('categories'
) to the 'content' property of object
proxy : its 'content' is undefined.
App.Router = Ember.Router.extend({
enableLogging: false,
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router){
router.get('sectionsController').set('content',App.Section.find());
router.get('categoriesController').set('content', App.Category.find());
router.get('applicationController').connectOutlet('main_area', 'home');
router.get('homeController').connectOutlet('home', {});
router.get('homeController').connectOutlet('categories', 'categories');
router.get('homeController').connectOutlet('sections', 'sections');
}
})
})
});
​
If it's any help, I got this error because I was connecting to an Ember.ObjectController instead of Ember.Controller.