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.
Related
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.
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.
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.
So I am trying to figure out how best to put modals on a route, such that you can navigate to them via the url.
application.hbs
{{outlet}}
{{outlet modal}}
There is a discussion here and emberjs cookbook provides another example but nothing covers how you can have modals on a specific route.
The closest thing I have seen is this Stack Overflow question but it suffers from two problems:
When the modal route is visited, the view in the main outlet gets destroyed. So in your UI, things underneath the modal get wiped out.
history.back() is that you essentially revisit that route causing that view to be redrawn and feels very hackish.
This is where I feel a solution would exist but not sure what exactly:
App.MyModalRoute = Ember.Route.extend({
renderTemplate: function(controller, model) {
/**
* When my modal route is visited, render it in the outlet
* called 'modal' but somehow also persist the default outlet.
**/
this.render({ outlet: 'modal' });
}
});
Here is how we handle it:
In the route you use a render piece similar to what you described:
App.UserDeleteRoute = Ember.Route.extend({
renderTemplate: function() {
this.render({
into: 'application',
outlet: 'modal'
});
},
actions: {
closeModel: function() {
this.transitionTo('users.index');
}
},
deactivate: function() {
this.render('empty', {
into: 'application',
outlet: 'modal'
});
}
}
Clearing out outlet on the "deactivate" hook is an important step.
For the main template all you need is:
{{outlet}}
{{outlet "modal"}}
This does not override the default outlet.
One thing you may want to do for all your modals is implement a layout:
App.UserDeleteView = Ember.View.extend({
layoutName: 'settings/modals/layout',
templateName: 'settings/modals/user_delete'
});
Another gotcha is that there needs to be a parent route rendered into the main outlet. If whatever is in that outlet is not the parent of your modal, then the act of moving away from that route will remove it from the outlet.
working JS bin
You've got several options here:
make modal route child of route you want to persist - than either render modal nested in application template, like in example you mentioned, or put it inside its own template id="myModal"
add modal outlet inside persisted route's outlet and render it in renderTemplate method
renderTemplate: function(controller, model) {
this.render(); //render default template
this.render('myModalTemplate', { //render modal to its own outlet
outlet: 'modal',
into: 'myModal', //parent view name. Probably same as route name
controller : controller
});
}
Besides, you can render template with modal in named outlet any moment(on action f.e.), by just calling render method with proper arguments
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}}