Metamorph error when trying to save record - ember.js

I'm getting a strange error when I'm trying to save some data.
Uncaught Error: Cannot perform operations on a Metamorph that is not in the DOM.
I've noticed something weird in the actual DOM
<h1>Posts page</h1>
<script id="metamorph-2-start" type="text/x-placeholder"></script>
<script id="metamorph-2-start" type="text/x-placeholder"></script>
<p>...</p>
...
<button data-ember-action="5" class="btn btn-warning">Cancel</button>
<script id="metamorph-2-end" type="text/x-placeholder"></script>
<table class="table table-striped table-hover>
There are two of the same metamorph tags and only one end tag. I've also tried not using the partial (ie having the code sit in the dom). While the duplicate metamorph start tag disappears, when trying to save, the metamorph tag its trying to reference doesn't exist.
Here is a JSBin of my code. the JSBin works, which is promising. The only difference between the jsbin and my code is that I'm using Ember App Kit. My guess is I'm doing something wrong with the ES6 setup. I've posted my controller code here
var IndexController = Ember.ArrayController.extend({
addingNew: false,
actions: {
createAccount: function() {
var account = this.store.createRecord('account', {
name: 'howdy',
});
account.save();
},
showNew: function() {
this.set('addingNew', true);
},
cancelNew: function() {
this.set('name', '');
this.set('addingNew', false);
}
}
});
export default IndexController;
What am I doing wrong to get this error?

I've run into this before when I have html comments enclosing ember tags. Something like:
<!-- {{#if something}}X{{else}}Y{{/if}} -->

Related

Getting a weird persistance issue with ember data fragments and localstorage

Apologies if this isn't quite the right place (as opposed to either libraries own github issue page, but as I've not been able to determine exactly which library is not quite working correctly hard to log it specifically).
I'm using ember data fragments on my model (an array), and localstorage to save down my model. When calling rollback upon the saved model, it seems to reset the fragments back to their original state (i.e. no values), but it still maintains the fragment itself on the array, rather than dropping the item out of the array.
I've got a fiddle setup, click 'add' to add a model, click to view it's details, then click 'add' in there, followed by 'cancel'. You can see that the type + desc values drop out, but the element is still there.
If I switch out to using the Fixture adapter then it all works as expected, just not sure where to start even attempting to debug, I've stepped through many lines of _super calls, and what not trying to figure it out, but just get lost.
Note
This is a pseudo version of my actual app, and curiously enough when you navigate to the home page and then back to the details page, it seems to resolve the type/desc correctly, which it is not doing on my actual app, it still maintains the default values. However refreshing the page makes it work perfectly from then onwards.
Any help greatly appreciated!
<!DOCTYPE html>
<html>
<head>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="//builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>
<script src="//builds.emberjs.com/tags/v1.7.0/ember.js"></script>
<script src="//builds.emberjs.com/canary/ember-data.js"></script>
<script src="//raw.githubusercontent.com/lytics/ember-data.model-fragments/master/dist/ember-data.model-fragments.js"></script>
<script src="//raw.githubusercontent.com/kurko/ember-localstorage-adapter/master/localstorage_adapter.js"></script>
<script>
window.App = Ember.Application.create();
App.ApplicationStore = DS.Store.extend();
App.ApplicationSerializer = DS.LSSerializer.extend();
App.ApplicationAdapter = DS.LSAdapter.extend({
namespace: 'cars'
});
App.Car = DS.Model.extend({
make: DS.attr(),
model: DS.attr(),
features: DS.hasManyFragments('feature')
});
App.Feature = DS.ModelFragment.extend({
type: DS.attr(),
description: DS.attr()
});
App.Router.map(function () {
this.route('index', { path: '/' });
this.route('car', { path: '/car/:car_id'});
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('car');
},
actions : {
add: function(model) {
var car = this.store.createRecord('car', {
make: 'Dodge',
model: 'Viper',
features: []
});
car.save();
}
}
});
App.CarRoute = Ember.Route.extend({
actions: {
add: function(model) {
model.get('features').createFragment({
type: 'Something',
description: 'Some desc'
});
model.save(); //*/
},
cancel: function(model) {
model.rollback();
}
}
});
</script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<script type="text/x-handlebars" data-template-name="index">
{{#link-to 'index'}}Home{{/link-to}}
<ol>{{#each}}
<li>{{#link-to 'car' this}}{{name}} {{model}}{{/link-to}}</li>
{{else}}
<button {{action 'add' model}}>Add</button>
{{/each}}</ol>
</script>
<script type="text/x-handlebars" data-template-name="car">
{{#link-to 'index'}}Home{{/link-to}}
<dl>
<dt>Make</dt>
<dd>{{make}}
<dt>Model</dt>
<dd>{{model.model}}</dd>{{#each features}}
<dt>{{_view.contentIndex}}. {{type}}</dt>
<dd>{{description}}</dd>
{{/each}}
</dl>
<button {{action 'add' model}}>Add</button>
<button {{action 'cancel' model}}>Cancel</button>
</script>
</body>
</html>
I havent worked with data fragments but fragment is a model itself so the element/fragment is still there because you have created a record for it.
This record is stored in the ember store until you do something with it.
Rollback, via emberjs.com,does this - "If the model isDirty this function will discard any unsaved changes".
The model in this case seems to be the fragment. Rollback gets rid of the changes, which is what it is doing in your case, removing the type and desc values, but the record itself is still in the store.
If you want to get rid of the fragment altogether you would have to delete it. http://emberjs.com/guides/models/creating-and-deleting-records/

Ember.js: ArrayController undefined in template

Problem:
I am kind of struggling with the organization of my first ember app. The current issue is that the my Items ArrayController is not defined in my dashboard template:
<script type="text/x-handlebars" data-template-name="dashboard">
{{#if controllers.items}}
<p class="alert alert-error">Dashboard can access item's info - Nice!</p>
{{else}}
<p class="alert alert-error">Dashboard cannot access items... :-/</p>
{{/if}}
</script>
Likely cause: *
**EDIT: after talking with #conrad below, I'm kind of questioning this:*
I had a similar issue in an earlier post and kingpin2k suggested the cause was that I:
"never created anything that uses the options controller".
This is probably the case here as well. This quick screencast shows that a breakpoint on my ArrayController is not hit on page load - but it is hit when I inspect the Items controller in the Ember inspector tool (eg, Ember creates the ArrayController object right then for the first time).
Apparent non-solutions:
My Dashboard controller says it needs the Items controller. I guess that isn't enough to instantiate the ArrayController?
App.ItemsController = Ember.ArrayController.extend({
len: function(){
return this.get('length');
}.property('length'),
totalCost: function() {
return this.reduce( function(prevCost, item){
return parseInt(item.get('values').findBy('type', 'cost').price, 10) + prevCost;
}, 0);
}.property('#each.values')
[more computed properties...]
});
App.DashboardController = Em.Controller.extend({
needs: ['items'],
itemsLength: Ember.computed.alias('controllers.items.len'),
itemsTotalCost: Ember.computed.alias('controllers.items.totalCost'),
[more computed properties...]
});
Furthermore, each item in Items is being rendered in my items template. I guess that does not create the missing controllers.items either...
<script type="text/x-handlebars" data-template-name="items">
{{#each}}
[these render fine]
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="display">
<!-- DISPLAY TEMPLATE -->
{{!- DASHBOARD -}}
{{render dashboard}}
{{!- ITEMS -}}
{{render 'items' items}}
</script>
So then.. what?
I can imagine many possible avenues, but haven't gotten any of them to work yet:
Specify the Items ArrayController in {{render dashboard}}?
Some configuration in a Route?
Maybe my templates/routes are not correctly arranged?
You could make sure that the ItemController is instantiated in the dashboard template by calling it in the DashboardController's init function:
App.DashboardController = Em.Controller.extend({
needs: ['items'],
init: function() {
this._super();
this.get('controllers.items.length');
}
});
/edit:
removed the part that was not helpful

Ember removes element from DOM after creating it, and jQuery can not find it

I have this wrapper around Ember.Select, to activate Select2 features:
App.Select2SelectView = Ember.Select.extend({
prompt: 'Please select...',
classNames: ['input-xlarge'],
didInsertElement: function() {
Ember.run.scheduleOnce('afterRender', this, 'processChildElements');
},
processChildElements: function() {
this.$().select2({
// do here any configuration of the
// select2 component
escapeMarkup: function (m) { return m; } // we do not want to escape markup since we are displaying html in results
});
},
willDestroyElement: function () {
this.$().select2('destroy');
}
});
Sometimes I need to make a drop-down invisible, and I do it like this:
{{#if cityVisible}}
<div class="control-group">
<label class="control-label">City</label>
<div class="controls">
{{view SettingsApp.Select2SelectView
id="city-id"
contentBinding="currentCities"
optionValuePath="content.city"
optionLabelPath="content.city"
selectionBinding="controller.selectedCity"
prompt="Select a city"}}
<i class="help-block">Select the city for your geographical number</i>
</div>
</div>
{{/if}}
But whenever the drop-down is invisible, I get the following error:
Uncaught TypeError: Cannot call method 'select2' of undefined
I guess the element is inserted, but then removed by Ember from the DOM (bound property cityVisible), so that jQuery is not able to find it?
What can I do to avoid that error message? I do not want to make the view visible/invisible, I want to keep the whole control-group under the cityVisible control.
This is normal behaviuor that ember removes the view, as a workaround you could do the following:
HTML
<div {{bindAttr class="view.cityVisible::hideCities"}}>
<div class="control-group">
...
</div>
</div>
CSS
.hideCities {
display: none;
}
Remove the {{#if}} around the html block, and wrap it with a div instead on which you set a css class which contains display: none; you could use the cityVisible or a different property in your view or controller and set it to true/false to toggle it's visibility. This mecanisnm should leave your html markup in the DOM an thus available for jQuery. Note that if your citiesVisible property lives in your controller then remove the view. prefix from view.citiesVisible to be only citiesVisible, this depends on your setup.
See demo here.
Hope it helps.

Ember cannot find my view

I have defined a View subclass MenuitemView to which I'm referring from a template (see below). But Ember doesn't find it. I keep getting this error:
Error: assertion failed: Unable to find view at path 'App.MenuitemView'. What am I missing?
Initialization code:
window.require.register("initialize", function(exports, require, module) {
var App;
App = require('app');
App.Router.map(function(){});
require('routes/IndexRoute');
require('templates/menuitem');
require('templates/application');
require('templates/index');
App.initData();
});
View definition:
window.require.register("views/MenuItemView", function(exports, require, module) {
var App;
App = require('app');
module.exports = App.MenuitemView = Em.View.extend({
templateName: 'menuitem',
visibility: function(){
if (App.selectedDishType && !App.selectedDishType === this.get('dish').get('type')) {
return 'invisible';
} else {
return '';
}
}
});
});
Template referring to view (templates/index.hbs):
{{#each item in content}}
{{view App.MenuitemView itemBinding=item}}
{{/each}}
View template (templates/menuitem.hbs)
<div class="dishitem">
<div class="dishimage">
{{thumbnail item.dish.identifier}}
</div>
<div class="dishdetails">
<p class="dishname">{{uppercase item.dish.name}}</p>
<p class="dishdescription">{{item.dish.description}}</p>
<ul class="packages">
{{#each package in item.packages}}
<li>
<span class="packageprice">€ {{package.price}}</span>
<span class="packagespec">
{{#if package.description}}({{package.description}}){{/if}}
</span>
</li>
{{/each}}
</ul>
</div>
</div>
The problem was caused by the fact that I was using Brunch for building the application and I have all the javascript components and templates in separate files. Brunch then compiles the templates to separate common.js javascript modules. The code in the template's compiled module does not have access to the view defined in the view module. The way to normally handle such dependencies is to add "require('my-other-module')" to the dependent module's javascript. But, as my template source is not javascript but handlebars, I cannot add this to the source.
The solution is to ensure your application namespace is globally available. You do this by not putting your application initialization code in a module, e.g. directly in your html, inside a script tag:
<script>
App = require('app');
</script>
What I do notice is that the 'item' part of menuitemview is sometimes upper case (views/MenuItemView), sometimes lower case. Could that be the source of the error message?
I encountered a similar problem with ember-tools, and the accepted answer set me on the right path. I'll leave my solution here for posterity.
I had my app set up like this:
var FooRoute = Ember.Route.extend({
...
renderTemplate: function() {
this.render();
this.render('Bar', {into: 'foo', outlet: 'bar', controller: 'foo' });
},
...
});
and in the template foo.hbs:
{{outlet bar}}
This was causing problems for the same sorts of reasons #RudiAngela mentions in the accepted answer. Instead of including a <script> tag, though, I just changed the {{outlet}} to a {{view}},
{{view App.Bar}}
and this solved the problem for me.

using emberjs new-router-V3 and #with helper to access events defined in a different route

I have this working jsfiddle. The EmBlog.PostsEditRoute has a destroyPost event which I want to call with an action helper in the 'post/show.hbs' which is the template for the EmBlog.PostsShowRoute.
I am using #with helper to change scope in the template as suggested here. It doesn't destroy the object and throws no error.
<script type="text/x-handlebars" data-template-name="posts/show">
{{#with EmBlog.PostsEditController}}
<a href='#' {{action destroyPost this}}> Destroy</a>
{{/with}}
</script>
EmBlog.PostsShowRoute = Ember.Route.extend({
});
EmBlog.PostsEditRoute = Ember.Route.extend({
events: {
destroyPost: function(context) {
var post = context.get('content');
post.deleteRecord();
post.get('store').commit();
this.transitionTo('posts');
}
}
});
I think this is basically because you have to define your event handler in the EmBlog.PostsShowRoute or in the PostsRoute if you want it to be accessible in an other PostsXXX view. see http://emberjs.com/guides/templates/actions/ for details.
(the use of the #with helper here seems wrong here BTW, as your reference is about something quite old). I would simply do
<script type="text/x-handlebars" data-template-name="posts/show">
<a {{action destroyPost content}}> Destroy</a>
</script>
Here is the modified fiddle: http://jsfiddle.net/Qn3ry/4/
Note that when you try to destroy a post which is in the fixtures, it reappears when you transition to posts/index. This is simply because the post is not destroyed in the fixtures, and when entering to the PostIndexRoute, App.Post.find() will reload it again.