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

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.

Related

Ember Route Query Params

i would like to open a new perspective with query params.
That means in my index.hbs i click a button get the value from the input field.
After that i will open a new route in my case map-view with path '/map' with query params like
localhost:4200/map/?search=xyz
when i do in my index controller:
queryparams:['location'],
location:null,
and in my route
actions:{
search(location){
this.transitionTo('map-view');
}
}
i get on my index url instantly
/?location=xcyxyx
but i want on my map route
localhost:4200/map/?search=xyz
Define search property in index.js controller, use it for queryParams value transitioning to map-view route.
index.hbs
{{input value=search}}
<br />
<button {{action 'transitionToLocation'}}> Search</button>
controllers/index.js - refer transitionToRoute
import Ember from 'ember';
export default Ember.Controller.extend({
search:'india',
actions:{
transitionToLocation(){
this.transitionToRoute('map-view',{queryParams: {search: this.get('search')}});
}
}
});
routes/map-view.js
Define queryParams search with refreshModel true then this will force to fire beforeModel and model and afterModel hook for search property change.
import Ember from 'ember';
export default Ember.Route.extend({
queryParams:{search: {refreshModel:true}},
});
The queryparam is going to be 'search' since that is what you want showing in your URL.
The syntax can be found on:
https://guides.emberjs.com/v2.10.0/routing/query-params/
Further down on that page, it shows how to specify query parameters when using transitionTo. Hope this helps.

Ember loading template works only on page refresh

I have in my Ember app this routes:
Router.map(function() {
// landing page (login/register)
this.route('home', {path: '/'});
// authenticated pages
this.route('webappFrame', {path: '/app'}, function() {
this.route('feed', {path: 'feed'});
this.route('photoDetail', {path: 'photo/detail/:id'});
});
});
where both "feed" and "photoDetail" have a model hook that return a promise (records coming from the server);
I have a loading.hbs template in the /template folder;
After a page refresh on both "feed" and "photoDetail" routes, the loading template is correctly displayed;
but when navigating between the routes, it is not shown again (the outlet remains white untile the promise is resolved);
I use ember-cli 2.3.0-beta.1 (but also tried 1.13.14 stable) with ember 2.3.0;
In the official ember docs is written that it should always traverse the templates tree up until it finds a loading template;
can someone show me what's wrong here?
UPDATE ----------------------------------------------------
// feed model hook
model: function(params, transition) {
return this.store.query('photo', {type: 'feed'});
}
// photoDetail model hook
model: function(params, transition) {
var self = this;
return Ember.RSVP.hash({
photo: self.store.find('photo', params.id),
comments: self.store.query('comment', {id: params.id})
});
},
and in feed template:
{{#each photo as |item|}}
{{photo-item item=item}}
{{/each}}
and then the photo-item component:
{{#link-to "webappFrame.photoDetail" item.id}}
<img class="photoImage" src="{{imageurl item.hash type='photo'}}">
{{/link-to}}
where {{imageurl}} is just an helper that creates the path for the image;
here i pass "item.id" to the link-to (instad of passing "item" itself) to force the reload of the photo when entering detail (in order to get also comments)
right now, I've added templates/webapp-frame/loading.hbs and it works;
the thing is that the loading template is always the same in the whole app; so I expected to have only one instead of having a copy of it in every /templates subfolder...
Ember handles the loading templates per route, its always "routename-loading.hbs", it seems you have not created the matching templates that´s why you only see a white page (default). If you´re not sure which templates are necessary or how they have to be named, install ember inspector and check the routes tab, there you can see what ember expects and how it should be named.

Can't load form to create new instance of model

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/

Unable to use value from an Ember Data store in template

I'm building a simple application using Ember v1.8.1, Ember CLI 0.1.15 and Ember Data 1.0.0-beta.15 in an attempt to learn the basics of Ember.js. Data is coming from a simple REST API, so I'm using the DS.RESTAdapter and DS.RESTSerializer. The payload is normalized in the serializer, because the API doesn't properly wrap its data in the required hashes.
I'm trying to implement an edit route, but am unable to populate the form fields of that template or even display a simple value. My index route, displaying a list of posts, works as expected, but edit doesn't for some reason.
Using {{title}} in my template doesn't render the value, but outputs a string of unexpected data, something along the lines of: <my-app#model:post::ember413:21>. I'm probably passing something improperly from the route or elsewhere, but I don't know what and where exactly.
Below is some of my relevant code:
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('login');
this.resource('posts', function() {
this.route('add');
this.route('view', { path: ':post_id'});
this.route('edit', { path: ':post_id/edit'});
});
});
export default Router;
app/routes/posts/edit.js
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model: function(params){
return this.store.find('post', params.post_id);
}
});
app/routes/posts/index.js
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model: function(){
return this.store.find('post');
}
});
app/controllers/posts/edit.js
import Ember from 'ember';
export default Ember.ObjectController.extend();
app/serializers/application.js
import Ember from "ember";
import DS from "ember-data";
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
primaryKey: 'ID',
attrs: {
author: { embedded: 'always' }
},
extractSingle: function(store, type, payload, id) {
payload = { post : payload };
return this._super(store, type, payload, id);
},
extractArray: function(store, type, payload) {
payload = { posts : payload };
return this._super(store, type, payload);
},
serializeIntoHash: function(hash, type, record, options) {
Ember.merge(hash, this.serialize(record, options));
}
});
app/templates/edit.hbs
<div class="page">
<div class="page-content">
<h2>{{title}}</h2>
{{{content}}}
</div>
</div>
app/templates/index.hbs
<div class="page">
<div class="page-content">
<ul>
{{#each}}
<li>
<h2>{{title}}</h2>
{{{content}}}
</li>
{{/each}}
</ul>
</div>
</div>
Having looked at some examples the above should work, so probably there's an error in the serializer. In the route I can access values from the store however. The following logs the expected data when used in the model method of my edit.js route:
this.store.find('post', params.post_id).then(function(post) {
console.log(post.get('title'));
});
UPDATE: Here is a JS Bin recreating most of the application without Ember CLI. The behaviour is exactly the same as on my local machine; the edit route doesn't properly show the content value (title works, for some reason). When I swap to fixtures instead of the API output the problem remains the same. Also the model in the bin doesn't map to all the values returned by the API, but that doesn't seem to make a difference.
UPDATE 2: Seems the content entry of the returned payload is causing the problems, when I rename the key it's displayed correctly in the edit template. Didn't realise this before, because I was so focussed on getting the content value to display and forgot to test other values. Not sure why it happens only happens on a nested route though and with that particular keyword.
UPDATE 3: If I understand correctly, this might be related to an outstanding issue for Ember Data, there's a proposal for reporting such collisions as well.

ember.js, ember-cli: Outlets not nesting properly

I'm having an issue where I'm unable to get nested outlets to appear properly in my Ember CLI app. The view tree I want is as follows:
application (list of all resources, of which client_availability is one)
- client_availabilities.index (list of client_availabilities)
-- client_availability (individual client_availability)
This is very similar to the "application > posts.index > post" hierarchy in the Ember Starter Kit. My desired behavior is for a list of client_availabilities to appear in "mainoutlet" when I navigate to client_availabilities.index, then persist when I bring up an individual client_availability in "suboutlet".
Easy, right? This is the default behavior & why we all love Ember. However, I can't seem to get it working. When I explicitly target my named suboutlet in client_availabilities.index and click on an individual client_availability, nothing shows up in either outlet:
Scenario 1: Render suboutlet inside client_availabilities
/app/template/application.hbs:
{{link-to 'Client Availabilities' 'client_availabilities'}}
{{outlet 'mainoutlet'}}
/app/template/client-availabilities/index.hbs:
{{outlet 'suboutlet'}}
/app/routes/client-availabilities/index.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render({
into: "application",
outlet: "mainoutlet"
});
},
model: function() {
return this.store.find('client_availability');
}
});
/app/routes/client-availability.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render('client_availability', {
into: "client_availabilities",
outlet: "suboutlet"
});
},
model: function(params) {
return this.store.find('client_availability', params.client_availability_id);
}
});
Alternately, when I target my mainoutlet in application, client_availability appears in "suboutlet" client_availabilities.index disappears from "mainoutlet":
Scenario 2: Render suboutlet inside application
/app/template/application.hbs:
{{link-to 'Client Availabilities' 'client_availabilities'}}
{{outlet 'mainoutlet'}}
{{outlet 'suboutlet'}}
/app/template/client-availabilities/index.hbs: (empty)
/app/routes/client-availabilities/index.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render({
into: "application",
outlet: "mainoutlet"
});
},
model: function() {
return this.store.find('client_availability');
}
});
/app/routes/client-availability.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render('client_availability', {
into: "application",
outlet: "suboutlet"
});
},
model: function(params) {
return this.store.find('client_availability', params.client_availability_id);
}
});
And here's my router, the same in both cases:
/app/router.js:
import Ember from 'ember';
var Router = Ember.Router.extend({
location: 'auto'
});
Router.map(function() {
this.resource('client_availabilities', function() {
this.resource('client_availability', { path: ':client_availability_id' });
});
});
export default Router;
I'm happy to share more code, but the application is split into several files and unfortunately not something I can post in its entirety. Can anyone see what I'm doing wrong? The rest of the app is working fine, I just can't seem to get this basic behavior to work.
Do you have an /app/templates/client-availibilities.hbs template with only {{outlet}} inside of it? Without this, the app is going to lose its place in the outlet tree. Ember-CLI and the Ember Starter Kit are very, very different from each other in structure, so I can see where the confusion comes from.
How I like to think of Ember's rendering style is that each handlebars file inside the templates folder (i.e. /templates/users.hbs) represents a change the overall state of the application from one subject to another (example: from newsfeed to users).
The corresponding subfolders inside the templates folder change the state of the subject itself.
For example:
Required Templates
Users container OR the only users page you need app-wide is at /templates/users.hbs
Optional Templates
Users Index would be at /templates/users/index.hbs
Users Show would be at /templates/users/show.hbs
Users New would be at /templates/users/new.hbs
You can have [ /templates/users.hbs ] without having [ /templates/users/*.hbs ] and still keep track of your data; however, you cannot have [ templates/users/index.hbs ] without [ /templates/users.hbs ] and still keep track of your data. Why? Imagine if you navigate to somesite.com/users. There is currently no top-level template with an outlet into which Ember can render the [ users/index.hbs ] template. The [ /templates/users.hbs ] template bridges that gap and also serves as a container for all other pages inside the /templates/users folder as well.
For example, in the terms of your app, in order to render [ /app/templates/client-availibilities/index.hbs ] when a user visits http://www.yourwebsite.com/client-availibilities, your app will need these templates defined so that ember can drill down into them.
application.hbs // and in its outlet, it will render...
--client-availibilities.hbs // and in its outlet, it will render by default...
----client-availibilities/index.hbs // then, for the client-availability (singular), you can have ember render it in
----client-availibilities/show.hbs // will render also in the client-availabilites as it is a separate state of the subject. Can also be nested inside the index route within the router so that it renders inside the index template.
As it is, I would structure your app as such...
/app/router.js
... // previous code
Router.map(function() {
this.resource('client_availabilities', function() {
this.route('show', { path: '/:client_availability_id' });
// this.route('new'); ! if needed !
// this.route('edit', { path: '/:client_availability_id/edit' ); ! if needed !
});
});
... // code
/app/templates/application.hbs
{{link-to 'Client Availabilities' 'client_availabilities'}}
{{outlet}}
/app/templates/client-availabilities.hbs
{{outlet}}
/app/templates/client-availabilities/index.hbs
<ul>
{{#each}}
{{#if available}}
<li>
{{#link-to #link-to 'client-availabilities.show' this}}
{{firstName}} {{lastName}}
{{/link-to}}
</li>
{{/if}}
{{else}} <!-- we want this to only render if the each loop returns nothing, which is why it's outside the if statement -->
<li>Nobody is available</li>
{{/each}}
</ul>
<!-- Note: you don't need to put an outlet here because you're at the end of the tree -->
/app/templates/client-availabilities/show.hbs
<!-- Everything you want to show about each availability -->>
<!-- Note: you don't need to put an outlet here because you're at the end of the tree -->
/app/routes/client-availabilities/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('client_availability');
}
});
/app/routes/client-availabilities/show.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
return this.store.find('client-availability', params.client_availability_id);
}
});
/app/models/client-availability.js
import DS from 'ember-data';
var client-availability = DS.Model.extend({
firstName: DS.attr('string'),
lastname: DS.attr('string'),
available: DS.attr('boolean'),
available_on: DS.attr('date')
});
export default client-availability;
However, are you sure you want to structure your app by the availability of each client? Wouldn't it make more sense to structure it by each client and then just filter each client to show if they were available or not? Resources are supposed to be nouns, and routes are supposed to be adjectives. Therefore, it would be best to use a client as your model instead of their availability and have a either an isAvailable property on the model (as used in the example above) or a one-to-many association with an additional availability model if you want to show clients who have several availabilities (as shown below).
For example,
/app/models/client.js
import DS from 'ember-data';
var Client = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
availabilities: DS.hasMany('availability')
});
export default Client;
/app/models/availability.js
import DS from 'ember-data';
var Availability = DS.Model.extend({
date: DS.attr('date'),
client: DS.belongsTo('client')
});
export default Availability;
In the long run, this latter approach would set up your app to show all availabilities at once and allow the user to filter by the client, plus it would allow the user to view a client and see all their availabilities. With the original approach (the isAvailable property on the client model), the user can only get the availabilities from the client model itself, but what if the user wants to see all clients who are available on, say, March 3rd at noon? Well, without an availability model associated with the client model, you are going to have to put a lot of code into your client controller that ember would give you by default if you go down the one-to-many path.
If you need more advice on where to go from here, let me know. I'm more than happy to add more examples of the templates, controllers, and routes that you'll need in order to pull this off.