What is the alternative to Ember.ViewState that has been deprecated? I tried using Ember.State instead with ContainerView etc., but could not get the view loaded. If someone can help (preferably with any example), that will be great.
(Unable to share entire code as it is Work in Progress)
newState: Ember.State.create({
...
view: Ember.ContainerView.create({
childViews: [ Dashboard.ChartView.create() ]
}),
});
Also how to debug why a view is not rendered, especially if you want to know if your layouts and outlets are the problem? Do outlets work with StateManager? Right now, assume I have only the following in my index.html, is it enough (I am using Ember AnimatedOutlet)?
<script type="text/x-handlebars" data-template-name="application">
{{animatedOutlet name="main"}}
</script>
With the new Ember specifications, how to use outlets with StateManager without using routes? I want a single-page app with only default "/" route?
Thanks,
Paddy
For this sort of problem I typically create an Ember.StateManager from the now separate ember-states project. I then have computed properties on my controller that determine whether various parts of the page should be shown. Then, in my templates, I use {{#if shouldShowPartX}} ... {{\if}} type statements that are only displayed if the StateManager is in the given state. Here's a more complete example:
App.MyController = Ember.Controller.extend({
isOpen: Ember.computed.equal('panelManager.currentState.name', 'open')
init: function() {
this._super();
# Create the state manager instance from the class defined on this controller.
this.set('panelManager', this.PanelManager.create());
},
reset: (function() {
this.get('panelManager').send('reset');
}).on('init'),
PanelManager: Ember.StateManager.extend({
initialState: 'open',
open: Ember.State.create({
# If already open do nothing.
open: function(manager) {},
# Close the panel
shrink: function(manager) {
manager.transitionTo('closed');
},
}),
closed: Ember.State.create({
# If already closed, do nothing
shrink: function() {},
# Open the panel
open: function(manager) {
manager.transitionTo('open');
},
}),
reset: function(manager) {
manager.transitionTo(manager.get('initialState'));
}
})
});
Then In my view I can do something like:
{{#if isOpen}}
<div class="panel panel-open"> ... </div>
{{else}}
<div class="panel panel-closed"> ... </div>
{{/if}}
This is an admittedly simplistic example, and one wouldn't usually use a StateManager for a simple two-state situation, but a more complicated example would probably be confusing.
Would this help? It doesn't require messing directly with a ContainerView and instead relies on an implicit ContainerView that handles the {{#if}} blocks and creates views according to the state of the StateManager.
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 am using ember to built a "wizard accordion".
Basically what I want is:
an accordion which is always shown
the accordion contains all the steps
one step is active but it is also possibly to change the header of previous steps
each step has its own model (e.g. Selecting from countries in first step, selecting from products in second)
it should be possible to jump back and force between steps
out of all the selections a central model is built which is sent to the server after the final step
The number of steps is dynamic and sometimes certain steps are skipped
The architecture i thought about is have 3 outlets in the application template:
{{previousSteps}}
{{outlet}}
{{nextSteps}}
and always rendering 3 templates in each Route. Each step would have it's own route and controller and the create action of each step would save the shared model to application controller and transition to the next step.
But I guess this solution is by far not the best. Does somebody have a better architecture for this?
Especially how to structure the routes, controller and templates
Thank you
Update:
I am doing it the following way now. I appreciate any comments on my solution.
App.Router.map(function() {
this.resource('steps', { path: '/' });
});
App.StepsRoute = Ember.Route.extend({
model: function() {
return [
{ controllerName: 'countries', templateName: 'countries', items: this.store.find('country') },
{ controllerName: 'products', templateName: 'products', items: this.store.find('product') }
]
}
});
App.StepsController = Ember.ArrayController.extend({
currentStep: "countries",
transitionToStep: function(step) {
this.set('currentStep', step);
},
lookupItemController: function(modelObject) {
return modelObject.controllerName;
}
});
App.CountriesController = Ember.ObjectController.extend({
needs: ['steps'],
currentStep: Ember.computed.oneWay('controllers.steps.currentStep'),
isExpanded: function() {
return "countries" === this.get('currentStep');
}.property('currentStep')
});
Steps.handlebars:
{{#each step in controller}}
<div {{bind-attr class=":step controller.isExpanded:expanded"}}>
{{view Ember.View templateName=step.templateName}}
</div>
{{/each}}
I had to use ObjectController for Countries controller since using an ArrayController as itemController did not work
Based on the way you laid out the UI, I think I'd do something like this in the router:
App.Router.map(function() {
this.resource('wizard', function() {
this.route('step1');
this.route('step2');
this.route('step3');
});
});
Then, have a wizard template that looks like this:
{{#link-to 'wizard.step1'}}Step 1{{/link-to}}
<div {{bind-attr class='step1Expanded}}>{{outlet 'step1'}}</div>
{{#link-to 'wizard.step2'}}Step 2{{/link-to}}
<div {{bind-attr class='step2Expanded}}>{{outlet 'step2'}}</div>
{{#link-to 'wizard.step3'}}Step 3{{/link-to}}
<div {{bind-attr class='step3Expanded}}>{{outlet 'step3'}}</div>
Then, inside each of the step routes, you would need to override renderTemplate so that it will render in the appropriate outlet, like this:
App.WizardStep1Route = Ember.Route.extend({
renderTemplate: function() {
this.render({outlet: 'step1'});
}
});
Finally, inside the WizardController, you would need to add computed properties to handle the step1Expanded logic being applied to the classes so you can know which one is displaying at any given time.
All this will allow you to load different models per step and will also allow you to handle model validation logic inside your routes. For example, if you cannot proceed to step3 until step1 and step2 are completed, you can add logic to handle that inside the step1 and step2 routes.
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.
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.