how to render two pods content on the same page? - ember.js

I'm new to ember/ember-cli and am slowly getting my head around the immense learning curve... I have come across an issue I was hoping someone could advise me on...
I have an App that displays a contact and then places tabbed content underneath the contact details, one tab contains some notes info the other some site locations info.
I essentially have a Bootstrap "Tabbed" section to my page. With (currently) two Tabs labelled "Sites" and "Notes". The idea being if you click Notes, you see content from the Notes pod and if you click Sites you see content from the Sites Pod.
To do this i am naming my outlets e.g.
{{outlet 'sites-tab'}}
and
{{outlet 'notes-tab'}}
i.e.
{{#em-tabs selected-idx=tab_idx}}
{{#em-tab-list}}
{{#em-tab}}Sites{{/em-tab}}
{{#em-tab}}Notes{{/em-tab}}
{{#em-tab}}...{{/em-tab}}
{{/em-tab-list}}
{{#em-tab-panel}}
{{outlet 'sites-tab'}}
{{/em-tab-panel}}
{{#em-tab-panel}}
{{outlet 'notes-tab'}}
{{/em-tab-panel}}
{{#em-tab-panel}}
<p>Future Use</p>
{{/em-tab-panel}}
{{/em-tabs}}
and using:
renderTemplate: function() {
this.render({
into: 'contacts.show', // the template to render into
outlet: 'notes-tab' // the name of the outlet in that template
});
}
in the two pods routes to place the content in the right place.
if i use the urls manually e.g:
contacts/5961168002383609856/sites
contacts/5961168002383609856/notes
Then the content is rendered into the relevant Tab (and the other is empty).
each pod structure is along the lines of:
app/pods/notes/-form/template.hbs
app/pods/notes/edit/controller.js
app/pods/notes/edit/route.js
app/pods/notes/edit/template.hbs
app/pods/notes/index/controller.js
app/pods/notes/index/route.js
app/pods/notes/index/template.hbs
app/pods/notes/new/controller.js
app/pods/notes/new/route.js
app/pods/notes/new/template.hbs
app/pods/notes/show/controller.js
app/pods/notes/show/route.js
app/pods/notes/show/template.hbs
app/pods/notes/base-controller.js
app/pods/notes/route.js
can you think of what would make ember-cli render both contents into each outlet on the same page?
my app/router.js contains:
Router.map(function() {
this.resource("contacts", function() {
this.route("new");
this.route("edit", {path: ':contact_id/edit'});
this.route("show", {path: ':contact_id'}, function(){
this.resource("notes", function() {
this.route('new');
this.route('edit', {path: ':note_id/edit'});
});
this.resource("sites", function() {
this.route('new');
this.route('edit', {path: ':site_id/edit'});
});
});
});
});
many thanks with any help you can suggest.. thanks.
EDIT:
OK, as per #Sam Selikoff suggestion I tried switching to components, doing:
ember generate component contact-sites
ember generate component contact-notes
created the files:
app/components/contact-notes.js
app/components/contact-sites.js
and
app/templates/components/contact-notes.hbs
app/templates/components/contact-sites.hbs
I then moved my template html from pods/notes/index/template.hbs into app/templates/components/contact-notes.hbs
This (with a few tweaks) seemed to display the content correctly. I then moved on to editing a Note. TO do this I have a button with an action: {{action "editNote" note}} so had to move my actions from pods/notes/index/route.js into app/components/contact-notes.js
for example:
app/components/contact-notes.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
newnote: function(note) {
console.log("NEW NOTE:", note.contact);
this.transitionTo('notes.new');
return false;
},
editNote: function(note) {
console.log("Edit Note:", this);
this._transitionTo('notes.edit', note);
return false;
}
}
});
but I cant seem to get the Edit Note Route to work. I either (using this._transitionTo('notes.edit', note); ) get an error saying:
DEPRECATION: Ember.View#transitionTo has been deprecated, it is for internal use only
or if i use this._transitionTo('notes.edit', note); I get a different error:
TypeError: currentState is undefined
if (currentState.enter) { currentState.enter(this); }
any thoughts on how I can get to a route from within a component? - thanks.

In general you shouldn't need to call render or use named outlets that often. Instead, use components, something like
{{#em-tabs selected-idx=tab_idx}}
{{#em-tab-list}}
{{#em-tab}}Sites{{/em-tab}}
{{#em-tab}}Notes{{/em-tab}}
{{/em-tab-list}}
{{#em-tab-panel}}
{{contact-sites site=contact.sites}}
{{/em-tab-panel}}
{{#em-tab-panel}}
{{contact-notes notes=contact.notes}}
{{/em-tab-panel}}
{{/em-tabs}}
Remember your URL structure is tied to how your interface renders, so if you want two things to show simultaneously, don't tie them to two distinct URLs.

Related

Ember nested route not rendering template

I am using the latests ember-cli version 2.12.1 and ember.
I have configured my routes as this:
Router.map(function() {
this.route('companies', function() {
this.route('companydetail', {
path: '/:company_id'
}, function() {
this.route('employees', function() {
this.route('employeedetail', {
path: '/:employee_id'
});
});
});
});
});
The templates are in
/templates/companies/index.hbs
/templates/companies/companydetail.hbs
/templates/companies/companydetail/employees/employees/employeedetail.hbs
I can link to the route
{{#link-to "companies.companydetail.employees.employeedetail" model employee}}Edit{{/link-to}}
and that works. But the template is not rendered.
Instead the companydetail.hbs is used. I changed the
/routes/companies/companydetail/employees/employeedetail.js to render the correct template:
renderTemplate: function(params) {
this.render('companies/companydetail/employees/employeedetail', {
into: 'application'
});
}
This is working, BUT: the call to the model (request to the server) is not done. I could try and make the call manually, but I start to believe, that I am doing something wrong with the route.
Any advice?
UPDATE:
The url is /companies/1/employees/2. When ember constructs this url when I click on a link, the request to the model is not executed. When I refresh the browser page, the requests are fired. This is a somewhat typical experience since the model-call is not triggered when the url not changed. But the strange thing is, that it changes and still no model-request...
Thanks in advance,
Silas
That the companydetail.hbs is used is correct. The employeedetail.hbs should be rendered into the {{outlet}} inside the companydetail.hbs. Make sure to have an {{outlet}} inside the companydetail.hbs.

link-to works in index.hbs but not in application.hbs

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.

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.

Emberjs Modal as a route

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.

Not rendering/removing sidebar based on route

Something I've been experimenting around with Ember for a couple of hours and can't work out. Hopefully it's just a terminology issue that I'm getting stumped on as I read through the Ember docs.
I have an application, that, for the most part, consists of a sidebar/top bar (called wrapper), and a footer.
My basic application.hbs looks like this (I'm using Ember App Kit to provide structure):
{{partial "wrapper"}}
{{outlet}}
{{partial "footer"}}
If this was the state of my application, it would work pretty well. Page content loads in the {{outlet}} fine.
My main issue is how to break out of this template structure in an "Ember" way (and preferably without going all jQuery and removing DOM elements willy-nilly).
I have a few routes that I don't want the wrapper and the footer to show on (they're full page login/forgot password routes, and a couple of minimal interface/no distractions modes).
I experimented with trying to remove the sidebar and footer by making the default template (application.hbs):
{{#if showWrappers}}
{{partial "wrapper"}}
{{/if}}
{{outlet}}
{{#if showWrappers}}
{{partial "footer"}}
{{/if}}
Where showWrappers is in the ApplicationController:
export default Ember.Controller.extend({
showWrappers: function() {
var routes = ['login'],
currentPath = this.get('currentPath'),
show = true;
routes.forEach(function(item) {
var path = new RegExp('^' + item + '*');
if (!Ember.isEmpty(currentPath.match(path))) {
show = false;
}
});
return show;
}.property('currentPath'),
});
Attemping to transition to /login from / using {{link-to}} returns in an error: Uncaught Error: Cannot perform operations on a Metamorph that is not in the DOM presumably because I'm removing things Ember wanted to keep (I am using {{link-to}} and {{bind-attr}} in the sidebar, so there are bindings there).
Aware that I could use actions and jQuery to hide elements of the page and bring them back for the "distraction free" mode, but I'd prefer to learn how to structure templates and use Routes with the renderTemplate hook potentially using this.render (?) to blow away the current DOM and rebuild from a different base (rather than application.hbs).
Thoughts? More than happy to clarify.
I have discovered disconnectOutlet, and have converted my partials into outlets:
{{outlet wrapper}}
{{outlet}}
{{outlet footer}}
Made my ApplicationRoute render to them by default:
export default Ember.Route.extend({
renderTemplate: function() {
this.render();
this.render('wrapper', {
outlet: 'wrapper',
into: 'application'
});
this.render('footer', {
outlet: 'footer',
into: 'application'
});
}
});
and then on the LoginRoute, I just run this.disconnectOutlet for both wrapper and footer, and seems to work pretty well.