I'm looking at Ember to see whether it is suitable. One issue that came up is that we have many 'narrow' api calls - these calls return a list with the minimal data to create a list and then the user clicks on a link which goes to the detail view. Due to how link-to helper works, this will bypass the model method in the route. This question has the same issue: Transition from one route to another with a different model in Emberjs But I honestly don't understand the answer he provided. Specifically, he provides this code:
<a {{bindAttr href="somePropertyInYourModel"}}>{{someTextProperty}}</a>
and says:
The property somePropertyInYourModel is a property containing the url to the new page. If the url is in the ember routes it will be as if you where typing that address in the address bar and pressing enter, but without the full reload of the page.
I don't really understand what he's saying (my fault on this). I tried putting in <a {{bindAttr href="{{post}}"}}>{{someTextProperty}}</a> and <a {{bindAttr href="{{post}}"}}>{{someTextProperty}}</a>
but to no avail. Say I have this model:
Hex.Post = Ember.Object.extend({
id: null,
body: null,
isEnabled: null,
createdAt: null
});
How could I get this to work? What is he telling us to do?
thx for help, ember looks really cool but has a lot to know
edit #1
Here's the whole Router list. I want to have a posts view and when the user clicks, it goes to the post view which will be populated to the right. The problem is that the link-to bypasses the model so we really need to reload the model at that point. This would allow us to repurpose much of our existing api. Thx for help
Hex.Router.map(function() {
// put your routes here
this.resource('index', { path: '/' });
this.resource('users', { path: 'users' });
this.resource('loginslogouts', { path: 'loginslogouts' });
this.resource('locations', { path: 'locations' });
this.resource('flaggedcontent', { path: 'flaggedcontent' });
this.resource('posts', function(){
this.resource('post', { path: ':post_id' });
});
this.resource('comments', { path: 'comments' });
});
ahhh, send the id instead of the model, that will retrigger the model hook. Sending a model to the hook makes ember think you have the model, sending an id tells ember to hit the model hook with that id.
{{#link-to 'post' post.id}}{{post.name}}{{/link-to}}
Related
I'm currently working my way through my first Ember JS app, using Balint Erdi's excellent book "Rock and Roll with Ember JS"
I'm stumped and would appreciate some help.
I have the following (fairly simple) routes for my app:
/clients
/clients/:slug
/clients/:slug/requests
So for example, I would want a user to be able to view the requests for a specific client by visiting /clients/abc-company/requests
On the main dashboard, inside clients.hbs, I have the following code to link to each specific client (using dynamic segments):
{{#each model as |item|}}
<tr>
<td>{{item.id}}</td>
<td>{{#link-to "clients.client.requests" client}}
{{item.name}}
{{/link-to}}</td>
<td>{{item.contact}}</td>
<td>{{item.email}}</td>
<td>{{item.phone}}</td>
</tr>
{{/each}}
Now, the issue is that Ember doesn't seem to recognize the second link-to parameter (client).
The console is yelling at me with the following error message:
WARNING: This link-to is in an inactive loading state because at least one
of its parameters presently has a null/undefined value, or the provided
route name is invalid.
but I DID pass the parameters, which is supposed to be client!
Here are the relevant files in my code, I'm hoping someone can point out my obvious mistake or find the semi-colon that I forgot to include, or something.
//app/router.js
(...)
Router.map(function() {
this.route('clients', function() {
this.route('client', { path: ':slug' }, function() {
this.route('requests');
});
});
});
//app/routes/clients.js
(...)
var Client = EmberObject.extend({
id: '',
name: '',
contact: '',
email: '',
phone: '',
// create slug from client name
slug: computed('name', function() {
return this.get('name').dasherize();
})
});
// app/routes/clients/client.js <-- see the route DOES exist
(...)
model: function(params) {
// get the slug from clients, return client matching the slug name in the URL
var clients = this.modelFor('clients');
return clients.findBy('slug', params.slug);
}
});
// app/routes/clients/client/requests.js
(...)
model: function() {
return this.modelFor('clients.client');
}
});
Any help would be appreciated, this is my first Ember.js hurdle and I have a feeling that understanding nested routes properly will be very useful in the future :)
But for now I'm just confused.
Thanks in advance!
EDIT: Added some more code from the template, for clarity.
Annnnd I figured it out.
Lesson 1: When Ember complains that the model you want is invalid and you think you created the route, make sure you actually import the route at the top of the route file (import Route from '#ember/routing/route';)
Mine was missing for some reason and I didn't see it
Lesson 2: In the template file, the parameter needed by #link-to isn't actually the name of the route/model. It's the name of whatever you choose to call your iteration in the each loop.
This is wrong:
{{#each model as |item|}}
<tr>
<td>{{item.id}}</td>
<td>{{#link-to "clients.client.requests" client}}
{{/each}}
This is correct:
{{#each model as |client|}}
<tr>
<td>{{client.id}}</td>
<td>{{#link-to "clients.client.requests" client}}
I have no idea if this information will ever help anyone. In any case, I shall proceed to self high-five myself.
EDIT: Looks like i have to wait 2 days before I can accept my own answer.
Cheers!
When I do a {{#link-to "cars/category" "mini"}} from index.hbs, everything works fine. I transition to cars/category -> templates/cars/category.hbs.
However, when I do the same from application.hbs (for navigation), I transition to an empty page and then automatically transition to the parent route cars.index -> templates/cars/index.hbs.
There is probably some logic to this. How can I transition to this route from a link click in application.hbs?
(a hard link <a href="/cars/mini" works fine, but I'll lose the state of the application.)
routes/cars/category.js model:
model(params) {
return this.store.findRecord('cars/category', params.category_id, {backgroundReload: false});
}
In route.js:
this.route('cars', () => {
this.route('cars/category', { path: '/cars/:category_id' });
});
For Ember 2.11
Have you try to change the route path to dot notation cars.category
A sample nested routes,
Router.map(function() {
this.route('photos', function(){
this.route('photo', { path: '/:photo_id' }, function(){
this.route('comments');
this.route('comment', { path: '/comments/:comment_id' });
});
});
});
A proper link-to helper link with multiple segements,
{{#link-to 'photo.comment' 5 primaryComment}}
Main Comment for the Next Photo
{{/link-to}}
You can read more at,
https://guides.emberjs.com/v2.5.0/templates/links/#toc_example-for-multiple-segments
For the below route.js
this.route('cars', function(){
this.route('category', { path: '/:category_id' });
});
You can do {{#link-to "cars.category" "mini"}}, this will transition to /cars/mini url.
You don't need to have cars/category. because it's already nested inside cars route.
Created Sample Twiddle .
For better understanding routing see AlexSpeller ember-diagonal
Someone suggested that since I transition to the desired page and then immediately transition to its parent, there must have been some other transition queued.
On further inspection, the link-to was indeed inside a drop down menu of a list-item which itself was a link-to too.
The solution was to add bubbles=false to the inner link-to.
The other answers here doubt the used routes. However, they are fine and setup like this for a reason. E.g. multiple routes with a subroute called category cannot all be in the root. However, it was my fault for not disclosing the exact code which put people on the wrong track, for they would probably have noticed the actual problem immediately.
Next time I will be more verbose in my code. I apologize, and thanks for thinking with me.
just started to play with emberjs.dont know if this is stupid question or good one, but I am stuck in this for some days and cant figure out why.
in my router.js
Router.map(function() {
this.resource('posts', {path: '/'});
..........
});
.....
this.resource('post', {path: 'posts/:post_id'});
});
and in the route folder i have posts.js setup as following.it has simple js varible used to hold id , title, and body of articles.
export default Ember.Route.extend({
model: function(){
var posts = [
{
id:'1',
title:'lipsome vesicle',
body:"A liposome is a spherical vesicle"},
{
id:'2',
title:'another lipsome vesicle',
body:"A liposome is a another vesicle"}
]
//console.log(posts)
return posts;
}
});
In posts.hbs, the title of each post is shown as link to the to post. of course by looping through each model as |post| and print link-to post.title.
my post.js file simply get the model for posts and returns it.
export default Ember.Route.extend({
model: function(params) {
return this.modelFor('posts').findBy('id', params.post_id);
}
});
In my template for post.hbs I wanted to simply show the title and body for the post .It would have been more redable if it was like post.title or something like that. but i saw some tutorials that does following.
<h1>{{title}}</h1>
<h1>{{body}}</h1>
the url goes to the localhost:4200/post/1
but I cannot see the title and body
when I checked the value in console for
this.modelFor('posts').findBy('id', params.post_id).title , it prints the title
but view is blank.
I have read somewhere, that the controller is the one that is responsible to get the value from model. in that case too, I dont know how to access the returned model in that controller.
I have watched many tutorials, including the raffler example by railscast, since I have background in rails. but those tuts including lot other seems preety outdated. So for all new learners this is frustating and confusing too. Is there good fresh resources except the emberjs guide?
since the title was being printed in console, after many trials when i tried this trick, i finally managed to get my title in individual post.using Ember inspector plug in when i clicked $E console gave me all the post.so i put that object in array and returned that array.
model: function(params) {
// console.log("hell")
console.log(this.modelFor('posts').findBy('id', params.post_id).title);
var post = [this.modelFor('posts').findBy('id', params.post_id)];
return post;
}
then in my view I looped through the array as:
<ul>
{{#each model as |post|}}
<li>{{post.title}}</li>
{{/each}}
</ul>
but I would like to know better way for this.
I am trying to figure out how to convert a route into a modal, such that you can navigate to it via any other route WHILE preserving underlying(previous) template.
For example:
http://example.com/site/index goes to index.hbs
http://example.com/site/page2 goes to page2.hbs
http://example.com/site/article/1234 goes to article.hbs if user comes from another domain(fresh start)
BUT http://example.com/site/article/1234 opens up article.hbs inside the "article-modal" outlet if user comes any other route.
Here is the router.js
Market.Router.map(function() {
this.route('index', { path: '/' });
this.route('start', { path: 'start' });
this.route('article', { path: 'article/:article_id' });
this.route('404', { path: '*:' });
});
here is application.hbs
<div class="main-container">{{outlet}}</div>
{{outlet "article-modal"}}
and here is article.js route Alternative case #1
Em.Route.extend({
beforeModel: function(transition, queryParams) {
if(!Em.isEmpty(this.controllerFor('application').get('currentRouteName'))) {
this.render('article', {
into: 'application',
outlet: 'article-modal'
});
return Em.RSVP.reject('ARTICLE-MODAL');
}
},
model: function(params) {
return this.store.find('article', params.id);
},
actions: {
error: function(reason) {
if(Em.isEqual(reason, 'ARTICLE-MODAL')) { // ARTICLE-MODAL errors are acceptable/consumed
//
return false;
}
return true;
}
}
});
and here is article.js route Alternative case #2
Em.Route.extend({
renderTemplate: function() {
if(!Em.isEmpty(this.controllerFor('application').get('currentRouteName'))) {
this.render({into: 'index', outlet: 'article-modal'});
} else {
this.render({into: 'application'});
}
},
model: function(params) {
return this.store.find('product', params.id);
},
});
Problem with case #1 is that browser address bar does not reflect current route. If user goes from index route to article route the browser address bar still shows /index.. So if he presses back button app breaks.
Problem with case #2 is that it discards the contents of index.hbs because the article route is not nested.
Is it possible to even have such functionality with Ember?
Thanks :)
This is my second answer to this question. My original answer (https://stackoverflow.com/a/27947475/1010074) didn't directly answer OP's question, however, I outlined three other approaches to handling modals in Ember in that answer and am leaving it there in case it's helpful to anyone else.
Solution: define multiple routes with the same path
While Ember doesn't usually allow you to define two routes that use the same path, you can actually have a nested route and an un-nested route with the same effective path, and Ember works with it just fine. Building off of option 3 from my original answer, I have put together a proof of concept that I think will work for you. Here's a JS Fiddle: http://jsfiddle.net/6Evrq/320/
Essentially, you can have a router that looks something like this:
App.Router.map(function () {
this. resource("index", {path: "/"}, function(){
this.route("articleModal", {path: "/article"});
});
this.route("article", {path: "/article"});
});
And within your templates, link to the index.articleModal route:
{{#link-to "index.articleModal"}}View article!{{/link-to}}
Since articleModal renders inside of index, your index route isn't un-rendered. Since the URL path changes to /article, a reload of the page will route you to your regular Article route.
Disclaimer: I am unsure if this is exploiting a bug in current Ember or not, so mileage here may vary.
Edit: Just re-read OP's question and realized I didn't understand his question, so I created a new answer (https://stackoverflow.com/a/27948611/1010074) outlining another approach that I came up with after experimenting with something.
Option 1: Ember's suggested method for handling Modals
The Ember website has a "cookbook" for how they recommend handling modal dialogs:
http://emberjs.com/guides/cookbook/user_interface_and_interaction/using_modal_dialogs/
Essentially, you would create an action in a route that opens the modal:
App.ApplicationRoute = Ember.Route.extend({
actions: {
openArticleModal: function(article) {
return this.render(article, {
into: 'application',
outlet: 'modal',
controller: this.controllerFor("article")
});
}
}
});
And then call this.send("openArticleModal", article) either from your controller / another route or you could do something like <button {{action "openArticleModal" article}}>View Artice</button> in your template.
Essentially this method takes the modal out of a routed state, which means the modal won't be URL bound, however if you need to be able to open the modal from anywhere in the app and not un-render the current route, then it's one of your few options.
Option 2: If you need URL-bound modals that can be opened from anywhere
For a current project, I have done something that works for this use case by using query params. To me, this feels a little hacky, but it works fairly well in my tests so far (others in the community - if you have opinions on this, please let me know). Essentially, it looks like this:
App.ApplicationController = Ember.Controller.extend({
queryParams: ["articleId"],
articleId: null,
article: function() {
if(!this.get("articleId") return null;
return this.get("store").find("article", this.get("articleId"));
}
});
In application.hbs:
{{#if article.isFulfilled}}
{{render "articleModal" article.content}}
{{/if}}
Then I can use normal {{link-to}} helpers and link to the query param:
{{#link-to (query-params articleId=article.id)}}View Article{{/link-to}}
This works, but I'm not entirely happy with this solution. Something slightly cleaner might be to use an outlet {{outlet "article-modal"}} and have the application route render into it, but it might take more LOC.
Option 3: If the modal is only ever opened from one route
You can make the route that the modal will open into a parent of the modal route. Something like this:
Market.Router.map(function() {
this.resource('articles', { path: '/articles' }, function() {
this.route('modal', { path: '/:article_id' });
});
});
This works well if your modal can only "open" from within a single route. In the example above, the modal will always open on top of the articles route, and if you link-to the modal route from anywhere else in the app, the articles route will render underneath the modal. Just make sure that the "close" action of your modal transitions you out of the modal route, so a user can't close your modal but and still be on the modal route.
UPDATE: This is outdated, my blog is not Ember-based anymore. Basically, my question is simple. I added a record with createRecord(). I can see the didCreate event fired, but I don't know how to make ember load and display what I have just created. This case is about adding a comment to an post -- that is what I want to see instantly.
UPDATE: I have no jsfiddle, but I can show off the live app/site I am talking about is my own blog, here: http://eduardmoldovan.com/
The templates are at the bottom of the page, the javascript is here: http://eduardmoldovan.com/static/eduardmoldovan.com/javascripts/ngin.js
You can manually add your new record to array of records from the didCreate hook:
var newRecord = transaction.createRecord(Ngin.Comment, {
articleUrl: articleUrl,
name: name,
url: url,
email: email,
body: body,
secret: secret
});
newRecord.one('didCreate', this, function () {
this.get('comments').pushObject(newRecord);
});
transaction.commit();
Or, if you want to reload from server, use the reload method:
controller.get("comments").reload();
Edit
After examining sourcecode I found an update method in class RecordArray. It seems to be the right one.
Assuming you have used commit() to save the comment, something like this should work:
{{#each comment in post.comments}}
{{comment.text}}
{{/each}}
The following is neither efficient nor the best method but it will notify the view. And will try to find a better method. Im sure theres an update method and one that notifies the view. Cannot find it immediately.
template
<script type="text/x-handlebars" id="posts">
<button {{action 'newObject'}} ></button>
controller
App.PostsController = Ember.ArrayController.extend
actions: {
newObject: function() {
this.store.createRecord('post', {id: xxxx, title: 'Default title'});
var all = this.store.filter('post', function(post) {
return post; // return each object
}); //This filter will include the new post record
this.set('model', all); // set will notify the view of a mutated array
}
},