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.
Related
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.
I have an application route that is rendering a template into an outlet named 'sidebar', this should be viewable across the whole of the app. I have set up a quick example here.
When I go into one of the routes (in my example, the color route) this outlet will render a different template and when you navigate to another route in the app it should show the sidebar that was there originally.
This doesn't happen automatically and I understand it is because once the ApplciationRoute has been entered which is when the app is first loaded the renderTemplate is called and not called again until page refresh. This makes sense to me, but I'm unsure how to get around this.
I have tried re-calling the Route#render method again under the willTransition action of the ColorRoute but it doesn't work.
...
actions: {
willTransition: function() {
this.render('color.sidebar', {
into: 'application',
outlet: 'sidebar'
});
}
}
...
I just came up with another "workaround" for this using a component instead of a named outlet.
Instead of {{ outlet "sidebar" }} in your application template just use {{ x-sidebar }}
Then, define the x-sidebar component template as follows:
<script type="text/x-handlebars" id="components/x-sidebar">
{{partial sidebar }}
</script>
So, now your newly created component is expecting a sidebar property to tell it which template to display.
You can pass that property when you use the component like so:
{{ x-sidebar sidebar=sidebar }}
Then, you can use activate/deactivate hooks in your routes to set the sidebar property on the application controller, for example:
App.ColorRoute = Ember.Route.extend({
model: function(params) {
return params.color;
},
activate: function(){
this.controllerFor('application').set('sidebar', 'color/sidebar');
},
deactivate: function(){
this.controllerFor('application').set('sidebar', 'sidebar');
}
});
Working solution here
Someone apparently wrote an ember-cli addon to address this
See the following SO answer Ember sidebar - returning from "admin" sidebar to "normal"
I tried to implement user name displaying after log in. It displays in top menu. But top menu is getting displayed before log in, so it user name is getting cached.
I tried many approaches, and using volatile() is seems the best option, but it doesn't work. In this simple example currentTime calculates only once:
<script type="text/x-handlebars" data-template-name="application">
{{currentTime}}
</script>
App.ApplicationController = Ember.Controller.extend({
currentTime: function() {
console.log('computing value');
var time = new Date();
return time;
}.property().volatile()
});
Ember version 1.3
P.S. I prepared the gist to illustrate this issue: http://jsbin.com/OPUSoTaF/1
Actually, I can't find ANY way do display dynamic value in Ember's application template. Tried to display value from another controller using {{render}} helper, value still gets cached.
It seems that I just need to update value on ApplicationController from some other controller, and to do it in a proper way. Like this:
App.LoginController = Ember.Controller.extend({
needs: 'application',
setTime: function() {
this.get('controllers.application').set('currentTime', new Date());
}
});
The application to illustrate: http://jsbin.com/OPUSoTaF/4/edit
You can change ember properties and thus views using Handlebars {{action 'actionName'}} helper. You can add action helper to almost any UI element in your handlebars template an it is usually triggered on click. When triggered it calls actionName method on the controller.
Example:
Handlebars template:
<script type="text/x-handlebars" data-template-name="application">
<button {{action 'login'}}>Login</button>
{{loginTime}}
</script>
Controller:
App.ApplicationController = Ember.Controller.extend({
loginTime: 'User not logged in yet',
actions: {
login: function() {
// ... Do some login stuff ...
this.set('loginTime', new Date());
}
}
});
Working jsbin example is here: http://jsbin.com/udUyOXaL/1/edit
I have a view like this:
App.AbilityFilter = Ember.TextField.extend({
classNames: ['span3'],
keyUp: function(evt) {
this.get('controller').send('filterAbilities','text');
},
placeholder:'Search abilities'
});
It's part of a render like this:
<script type="text/x-handlebars" data-template-name="abilities">
{{view App.AbilityFilter}}
<div class="accordion" id="abilities">
{{#each ability in model}}
<div class="accordion-group">
{{ability.name}}
</div>
{{/each}}
</div>
</script>
Which is being rendered in my application like this:
{{render 'abilities'}}
The problem I'm having is with the event or, rather, the action. The keyUp event fires perfectly well, but for some reason it won't go to a controller.
I've tried adding the filterAbilities to the actions hash on both the App.AbilitiesController and the App.IndexRoute according to this. According to this, the view should be part of the abilities controller since that's the context of it's parent, but it's not working.
I've done some testing and it almost seems like this.get('controller') isn't fetching a controller at all. I'm a bit lost as to what's causing the problem. This code worked a few RCs ago, but as soon as I upgraded to 1.0 it broke.
What I'm trying to do here is filter the list of abilities. If this isn't the way to this anymore, please let me know! Any help would be appreciated. Thanks!!
Ember.TextField and Ember.TextArea are no longer simple views but rather subclasses of Ember.Component which means that this.get('controller') does not refer anymore to the views controller.
But there is a different variable which indeed holds a reference to the surrounding controller and this is this.get('targetObject'). Therefore you should send your action to the targetObject:
App.AbilityFilter = Ember.TextField.extend({
classNames: ['span3'],
keyUp: function(evt) {
this.get('targetObject').send('filterAbilities','text');
},
placeholder:'Search abilities'
});
Hope it helps.
I'm going round in circles here, trying to pull all the components together to produce the desired view. I feel as if I just need to just tweak the dial to bring it all into focus but at the moment it aludes me.
I have two models - Person and Address - which I have created two templates for; I then want to render these two templates in another 'main' template. At the moment I am not linking them in anyway (eventually 1 person will have many nested addresses) because I want to understand the general principes first.
The two templates work individually using App.Router.map
this.resource('listOfPeopleTemplate', { path: '/' });
or
this.resource('listOfAddressesTemplate', { path: '/' });
but not together or when I add the mainViewTemplate and try to add both into that:
App.Router.map(function () {
//this.resource('listOfAddressesTemplate', { path: '/' });
//this.resource('listOfPeopleTemplate', { path: '/' });
this.resource('mainViewTemplate', { path: '/' });
});
The problem seems centered around:
App.MainViewTemplateRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('listOfPeopleTemplate', {into: 'mainViewTemplate', outlet: 'peops'});
this.render('listOfAddressesTemplate', {into: 'mainViewTemplate', outlet: 'address'});
}
});
Errors returned are "outlet (people) was specified but not found"; and "The value that #each loops over must be an Array..". I can see that I may need to do something about the controller for both the Addresses and People but I don't know what. Fact is, i've got myself into such a muddle I now can't even get the originally successfull version working (with either the address or people displaying in their own template).
I have made the following fiddle http://jsfiddle.net/4gQYs/4/. Please, bring me into focus!
I hope I understood your problem!
I have two routes people and places.
App.Router.map(function(){
this.resource('people');
this.resource('places');
});
I am loading the model for both the controller in model hook of people route.
App.PeopleRoute=Ember.Route.extend({
model:function(){
var places=Em.A();
$.getJSON("js/places.js").then(function(json){places.setObjects(json)});
var placesController=this.generateController('places',places);
placesController.set('content',places);
var people=Em.A();
$.getJSON("js/people.js").then(function(json){people.setObjects(json)});
return people;
},
renderTemplate:function(){
this.render('people',{into:"application",outlet:"people"});
this.render('places',{into:"application",outlet:"places"});
}
});
The following is not needed.May be useful in displaying some related data.
App.PeopleController=Ember.ArrayController.extend({
needs:'places'
});
Now I am rendering the two templates in main application template.
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{outlet people}}
{{outlet places}}
</script>
<script type="text/x-handlebars" data-template-name="people">
{{#each controller}}
<p>{{name}}</p>
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="places">
{{#each controller}}
<p>{{name}}</p>
{{/each}}
</script>