Can't load form to create new instance of model - ember.js

router.js has
Router.map(function() {
this.route('concepts');
this.route('create-concept', { path: '/concepts/new' });
this.route('concept', { path: '/concepts/:id' });
});
controllers/create-concept.js has
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
create: function() {
var concept = this.store.createRecord('concept', {
description: this.get('description').trim()
});
this.set('description', '');
concept.save();
}
}
});
templates/create-concept.js has
{{textarea value="description"}}
{{action 'create'}}
routes/create-concept.js is just the default
import Ember from 'ember';
export default Ember.Route.extend({
});
When I go to the URL /concepts/new in my browser, however, I just get the error
Uncaught TypeError: Cannot read property 'getAttribute' of undefined
in my browser. I have no idea what the cause of this generic error would be, and documentation on this seems to be pretty sparse...
EDIT -- when I am visiting /concepts and click on a link to /concepts/new, it shows
Preparing to transition from 'concepts' to ' create-concept'
Transitioned into 'create-concept'
so it would appear that it does find the correct route
Since it's hard to copy and paste this for some reason, here's a screenshot of part of the stack trace:
EDIT 2 -- Minimal code demonstrating problem

I don't think you really want value="description", you probably mean value=description
One assigns the literal string description to the value property, the other binds the property description to the value property.
Your action needs to actually be attached to something! A div, button, anchor tag.
<div {{action 'create'}}>blah</div>
<button {{action 'create'}}>blah</button>
http://guides.emberjs.com/v1.10.0/templates/actions/

Related

How can I avoid "Assertion Failed: `id` passed to `findRecord()` has to be non-empty string or number" when refreshing an ember page?

There's this really annoying feature about Ember that I'm not sure how to get around. I may have a url that looks like the following
http://{my-blog-name}/posts/view/{some-blogpost-ID}
The way I get to this page is by clicking on a link inside of my {my-blog-name}/posts page. This works and will display the page as expected. However, if I try to refresh the page, or if I just literally type my http://{my-blog-name}/posts/view/{some-blogpost-ID} into my url search box, I will get
Assertion Failed: `id` passed to `findRecord()` has to be non-empty string or number
Here is how I navigate to the posts/view/{some-blog-id} page.
post.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('post');
}
});
posts.hbs
<li class="title-list-item">{{#link-to "posts.view" posts}}{{posts.title}}{{/link-to}}</li>
view.js
import Ember from 'ember';
var siteId;
export default Ember.Route.extend({
model(params) {
siteId = params.site_id;
return this.store.findRecord('post', params.site_id);
}
});
view.hbs
<div id="Links">
<h1 id="blog-header-title">My Blog</h1>
<!--<p>{{!#link-to 'welcome'}} See about me{{!/link-to}}</p>-->
{{outlet}}
</div>
{{outlet}}
router.js
import Ember from 'ember';
import config from './config/environment';
const Router = Ember.Router.extend({
location: config.locationType,
rootURL: config.rootURL
});
Router.map(function() {
this.route('index', { path: '/' }); // This is usually automatic if path undeclared, but declared here to support /index below
this.route('posts', function() {
this.route('view', {path: '/view/:post_id'});
});
this.route('welcome');
}
This is really frustrating because it means I can't make a blog post and share the link with a friend. Why does this happen and is there a good way to get around it?
posts.js route is returning all the posts available, that's the RecordArray.
<li class="title-list-item">{{#link-to "posts.view" posts}}{{posts.title}}{{/link-to}}</li>
so in the above posts - refers to single post model or RecordArray of post model ?. if the above is single model then you will receive params.post_id in model hook of view.js, currently you are taking params.site_id instead of params.post_id.
Reason for not executing the model hook.
https://guides.emberjs.com/v2.13.0/routing/specifying-a-routes-model/#toc_dynamic-models
Note: A route with a dynamic segment will always have its model hook
called when it is entered via the URL. If the route is entered through
a transition (e.g. when using the link-to Handlebars helper), and a
model context is provided (second argument to link-to), then the hook
is not executed. If an identifier (such as an id or slug) is provided
instead then the model hook will be executed.

How can I get the Id from the URL in an Ember Route?

I have a two panel display where I show a list of items on the left, then detail about a selected item on the right (using nested route).
My route looks like this:
Router.map(function() {
this.route('authenticated', {path: '/'}, function() {
this.route('bakery', function() {
this.route('cakes', function() {
this.route('detail', { path: '/:id' });
});
});
});
});
My URL looks like
http://localhost:3333/bakery/cakes/e34b3ce3
When an item is selected, it is set to "active" (temporary property on the model - default is false) and highlighted via an action on the bakery/cakes route. The detail is then shown on the right.
If I refresh the page, the item is no longer highlighted - but the detail is still shown.
Ideally I'd like to use the afterModel() hook in the bakery/cakes route to set that item back to active again, but I've been unable to get the Id to be able to do this.
I've tried the following:
Accepted answer from here
This question doesn't help me as the model will have reloaded and my "active" property will be false so I can't just select where active = true.
I'm using ember 2.5.0. Thanks.
I wonder if it'd be better to architect your structure a bit differently (from what I assume you're doing).
First, load all of the cakes on the authenticated.bakery.cakes route;
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll('cakes');
}
});
Secondly, show your "full width" cakes list on the authenticated.bakery.cakes.index template (the cake models will be inherited);
<div class="full width cake list">
{{#each model as |cake|}}
{{#link-to "authenticated.bakery.cakes.detail" cake.id}}
{{!-- cake photo --}}
{{cake.name}}
{{!-- other cake details... --}}
{{/link-to}}
{{/each}}
</div>
Next, on your authenticated.bakery.cakes.detail route, load the specific cake along with the list of cakes;
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
let cakes= this.modelFor('authenticated.bakery.cakes');
return Ember.RSVP.hash({
cakes: cakes,
cake: cakes.findBy('id', params.id)
});
}
});
Finally on the authenticated.bakery.cakes.detail template, show the condensed/narrow list of cakes along with the specific cake details. And using {{link-to}}, the 'active' class will automatically be applied;
<div class="narrow width cake list">
{{#each model.cakes as |cake|}}
{{#link-to "authenticated.bakery.cakes.detail" cake.id}}
{{cake.name}}
{{/link-to}}
{{/each}}
</div>
<div class="cake details">
{{model.cake.name}}
</div>
As another option, change your model active flag on the proper route hooks should work. (I think anyway, haven't done this myself.) On your authenticated.bakery.cakes.detail route;
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('cakes', params.id);
},
afterModel(cake) {
cake.set('active', true);
},
actions: {
willTransition() {
this.get('controller.model').set('active', false);
}
}
});

Ember.js modal component - where should I write the openModal action?

I am trying to setup a modal window as outlined in this post:
http://blog.atmartin.io/a-dead-simple-ember-cli-modal/
The author indicated that the openModal action is written in the app/controllers/application.js...
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
openModal: function(targetId) {
var modal = Ember.Views.views[targetId];
modal.send('toggleModal');
}
}
});
However, I am using the component from the books list page, using a test button:
<button {{action 'openModal' 'modal-author'}}>Open Author Modal Window</button>
{{#simple-modal enabled=false title="--Simple Modal --" id="modal-author"}}
Modal text. Oh girl!
{{/simple-modal}}
So, I tried (I may be wrong...) to insert the openModal action (when the button is clicked) into the book.js route:
// app/routes/books.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll('book');
},
actions: {
openModal: function(targetId) {
var modal = Ember.View.views[targetId];
modal.send('toggleModal');
},
....
}
});
In this case, I get an error Ember.view seems to be undefined ...
books.js:11 Uncaught TypeError: Cannot read property 'views' of undefined
You are using a tutorial that utilizes a deprecated & removed construct (views) in recent versions of Ember.
I recommend you use the https://github.com/yapplabs/ember-modal-dialog addon for creating modals in recent versions of Ember.

How do these actions resolve and bubble in this Ember.js app?

Here is my basic app in Ember.js:
This is my app/router.js:
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
this.route('todos', { path: '/'}, function() {
this.route('complete');
this.route('incomplete');
});
});
export default Router;
So when I hit the root path or '/', my application.hbs loads first, then my app/templates/todos.hbs, then my app/templates/todos/index.hbs right? The app/templates/todos/index.hbs gets loaded inside the outlet of the todos.hbs right?
This is my app/templates/todos.hbs:
<p>
this is the app/templates/todos.hbs.
</p>
{{todo-input action="createTodo"}}
{{#todo-list todos=model}}
{{outlet}}
{{/todo-list}}
So my todo-input component has an action called 'createTodo'. When does this get called?
This is my todo-input component handlebars template:
<p>
this is the todo-input.hbs component. It gets called inside todos.hbs
</p>
{{input type="text" id="new-todo" placeholder="What needs to be done?"
value=newTitle enter="submitTodo"}}
Questions:
When I hit enter in the input field, it calls submitTodo right? Where does it look first? Does it look in the component's js file which is app/components/todo-input.js right? This is that code:
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
submitTodo(newTitle) {
if (newTitle) {
this.sendAction('action', newTitle);
}
this.set('newTitle','');
}
}
});
What argument gets passed to the submitTodo method?
What is this line:
this.sendAction('action', newTitle);
Where should I define this 'createTodo' action? Should it be put in the routes/todos.js ?
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll('todo');
},
actions: {
createTodo(newTitle) {
this.store.createRecord('todo', {
title: newTitle,
complete: false
}).save();
}
}
});
I am mainly confused as to how the action in this line:
{{todo-input action="createTodo"}}
relates to the enter attribute in the todo-input component template:
{{input type="text" id="new-todo" placeholder="What needs to be done?"
value=newTitle enter="submitTodo"}}
When does the action createTodo even get fired?
When I hit enter in the input field, it calls submitTodo right?
Yes, because you've specified that this action should fire on enter event. More info can be found here.
Where does it look first? Does it look in the component's js file
which is app/components/todo-input.js right?
Yes, it does look in component's javascript code for appropriate action.
What argument gets passed to the submitTodo method?
Input value is passed to to submitTodo action as first and only argument.
What is this line:
this.sendAction('action', newTitle);
It fires an action passed as parameter (in this case createTodo) to component with newTitle as argument.
Where should I define this 'createTodo' action? Should it be put in
the routes/todos.js ?
Controller would be better place for your action, so you should put createTodo action in controllers/todos.js.
When does the action createTodo even get fired?
Check this out:
User presses enter when he has input focused
Input helper fires action attached to enter event - which is submitTodo
submitTodo gets called because it's located in component's actions set
submitTodo calls action passed to todo-input component with this.sendAction() - this action is createTodo

Observe error property on Ember.Model

I'm trying to create a component, which creates an input field for a specific field on a Ember.model.
When calling model.save() I would like to display the possible error messages regarding that field, right beneath the field. But I can't seem to get notified when my Errors property on Ember.Model changes.
Is there a way to observe the model.errors property, in order to display the correct error messages?
I tried:
.property{'model.errors'} and Ember.computed
.observes('model.errors')
.observes('model').on('becameInvalid')
I think i'm pretty close, as my errors with the solution below, are being dipslayed, however when I change my input to something else invalid, and try to save again, my original errors do not get cleared, and the new ones do not get added.
When I put breakpoints, or console.logs, I can see that the code never enters that particular section for displaying errors again, so my guess is that the computed property is not working. And my template is never updated with new errors.
Here's my code at the moment:
My component: components/inputfield-text.js
import Ember from 'ember';
export default Ember.Component.extend({
value: Ember.computed('model', 'field', {
get: function() {
return this.get('model').get(this.get('field'));
},
set: function(key, value) {
this.get('model').set(this.get('field'), value);
return value;
}
}),
errors: function(){
var errors = this.get('model.errors');
console.log(errors)
return errors.errorsFor(this.get('field'));
}.property('model.errors', 'model', 'field')
});
My Component's template: templates/components/inputfield-text.hbs
{{input type='text' value=value class='form-control' placeholder=placeholder}}
{{#each errors as |error|}}
<div class="error">
{{error.message}}
</div>
{{/each}}
And for the sake of completeness, the code I use for embedding the component in a template:
{{inputfield-text model=model field='name'}}
Found it, I had to add [] to my computed property, correct code below, notice difference between.
property('model.errors', 'model', 'field')
And
property('model.errors.[]')
Correct component code: components/inputfield-text.js
import Ember from 'ember';
export default Ember.Component.extend({
value: Ember.computed('model', 'field', {
get: function() {
return this.get('model').get(this.get('field'));
},
set: function(key, value) {
this.get('model').set(this.get('field'), value);
return value;
}
}),
errors: function(){
var errors = this.get('model.errors');
console.log(errors)
return errors.errorsFor(this.get('field'));
}.property('model.errors.[]')
});