Ember.js outlets and routes - javascript

I've been trying to learn Ember.js for the last two weeks and I've really struggled. I'm hoping for an 'a-ha' moment but each new feature I try to implement always results in hours of failed testing. I just don't seem to be grasping the framework. I feel like I'm working against it. I'm hoping someone can explain a path forward through this simple example.
I am creating a web app that allows the user to pick products that they will sell to a client. There is a list of products they can chose from and then a list of products they've selected.
I imagine a left-column with navigation controls and a main column showing either the selected products or new products they can add to the order. Here is the basic template:
<script type="text/x-handlebars" data-template-name="pc">
<div id="nav">{{outlet nav}}</div>
<div id="main">{{outlet main}}</div>
</script>
Here is the left navigation template:
<script type="text/x-handlebars" data-template-name="nav">
<div class="button">{{#linkTo "pc.add"}}Add Products{{/linkTo}}</div>
</script>
Here is the selected products template:
<script type="text/x-handlebars" data-template-name="selectedProducts">
{{#each p in controller}}
<div class="product">
<h4>{{p.name}}</h4>
</div>
{{/each}}
</script>
Here is the available products template:
<script type="text/x-handlebars" data-template-name="addProducts">
<div id="addProducts" class="addProducts">
{{#each p in controller}}
<div class="product">
<h4>{{p.name}}</h4>
</div>
{{/each}}
<button {{action "addSelectedProducts"}}>Add Selected Products</button>
<button {{action "back"}}>Back</button>
</div>
</script>
I can load the 'pc' template with some already selected products. Great. I can also navigate to the 'Add Products' template. Great. But when I click 'Add Selected Products', I can't figure out how to move the selected products into the controller/model behind the 'Selected Products' template and then get that template to re-render in place of the 'Add Products template'. It's really two question. How do I update the model of another controller from within a different controller? And, how do I then transition from an event to another route?
Can someone show me how you would design the Route(s) and Controllers? I know that's asking a lot. I"m mostly interested in seeing how you respond to an event in the AppProductsController, update SelectedProductsController's model, and then transition to SelectedProductsRoute and have it re-render the template.
I want to believe this is an amazing framework but I just keep hitting walls.
Andrew

How do I update the model of another controller from within a different controller?
Connect controllers using the needs property. So something like:
//in AddProductsController
needs: ['selectedProducts']
addSelectedProducts: function() {
// Now selectedProductsController can be accessed via the controllers property
otherController = this.get('controllers.selectedProducts');
// add the selected ones...
}
See http://emberjs.com/guides/controllers/dependencies-between-controllers/
how do I then transition from an event to another route?
//in AddProductsController
this.transitionToRoute('blogPosts');
See http://emberjs.com/api/classes/Ember.Controller.html#method_transitionToRoute

Related

Ember Nested Routes and rendering models

I have an invoice application generator and i want to show the invoices with all his transactions in 2 different ways, at the moment i can only do it in 1 way ( Edit Link)
On Edit link where i can see all my invoices and transactions
together ( it s how it works now)
On View link where i want to see only the specific Invoice information with his own transactions
and not any of the other invoices and informations
I have reproduced my case here
This is the route code
App.Router.map(function(){
this.resource('invoices', function(){
this.resource('invoice', { path:'/:invoice_id' }, function(){
this.route('edit');
});
this.route('create');
});
});
The problem is that as long as i am inside the invoices resources i am sharing the invoices templates where everything is generated, but is there a way where i can see only my single invoice with his own transactions inside the invoices route? Is it achievable with the same Route Code? What's the best way to get this done?
<script type="text/x-handlebars" id="invoices">
<div class="large-12 columns">
{{#link-to "invoices.create"}} Add Invoice {{/link-to}}
</div>
<ul class="fatturas-listing">
{{#each invoice in model}}
<li>
{{#link-to "invoice" invoice}}
Edit {{invoice.title}}
{{/link-to}}
</li>
<li>
{{#link-to "invoice" invoice}}
View {{invoice.title}}
{{/link-to}}
</li>
{{else}}
<li>no fatturas… :(
</li>
{{/each}}
</ul>
{{outlet}}
</script>
I don't get your 'edit' part. The problem you have right now is the outlet defined in the invoices template, all sub routes will be rendered in here, so you cannot show an invoice without its parent (invoices) content.
I think the most common way is to remove the outlet and show all invoices in the InvoicesIndex route. Clicking an invoice will then go to a single invoice (a new page without showing the invoices list from the InvoicesIndex route).

What's the easiest way to implement routes to show certain views?

I've created a site that has multiple panels that slide in from the right side of the screen.
I want to be able to put a link on each panel that will share my webpage, and when the user comes to the site, that specific panel will be open.
For example:
www.something.com/#/panel-1
Will show my page with panel-1 opened, while:
www.something.com/#/panel-2 will show my page with panel-2 opened.
What's the easiest way to do this? Can I use Ember,Angular, or Backbone's router and views with only simple html? Should I just use something like router.js?
Any help, advice, or links would be appreciated.
Of course you can do that. That's the one of the strongest qualities of ember.js. After declaring your routes, framework can generate all the corresponding controllers and views automatically (it's called convention over configuration). See an example
Ember.Application.create({});
Ember.Router.map(function(){
this.route('panel1');
this.route('panel2');
});
<script type="text/x-handlebars">
{{link-to 'index' 'index'}}
{{link-to 'first panel' 'panel1'}}
{{link-to 'second panel' 'panel2'}}
<!--your panels will be inserted automatically in the outlet property-->
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="panel1">
panel 1
</script>
<script type="text/x-handlebars" data-template-name="panel2">
panel 2
</script>
Demo: http://jsbin.com/dekixayuxa/1/

How to make something happen after a template has rendered in emberjs

So the concept is simple: You come to the app, you have a default template, with a default navigation element. You click a link in that navigation element it renders a new template: #/rails.
From here the default navigation needs to be hidden, and your new nav needs to be rendered.
The way i attempted to approach this, seems a bit silly: what I did was,
SG.Router.map(function(){
this.resource('rails');
});
$(document).ready(function() {
if($('#rails-nav').length !== 0){
$('#main-nav').hide();
}
});
Now the issue with this is that if you go from the default application template, to the rails template via the link - you get two navs unless you refresh that page. My friend stated that I should use something like:
{{outlet nav}} and then render a navigation based on template. The issue is I don't know how to set this up and I have been looking all over the ember site.
Could some one help me out?
If i understood correctly, when you say default template you mean the application template, where all other templates are rendered within its {{outlet}} helper.
There are several approaches to achieve what you want, but a simple one i think would be to use the index template as your default template. In this case evertyhting will be much simpler and work as you require, since you can specify whether the navigation element is shown by placing it inside a template or not.
http://emberjs.jsbin.com/sume/1/edit
hbs
<script type="text/x-handlebars">
<h2> Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
This is the default template using <i>index</i>
<br/>
<br/>
<span style="background-color:grey">this is the nav part {{#link-to 'rails'}}go to rails{{/link-to}}</span>
</script>
<script type="text/x-handlebars" data-template-name="rails">
this is the rails app
</script>
js
App = Ember.Application.create();
App.Router.map(function() {
this.resource("rails");
});
Also to make this answer a bit more relevant to the title of the question, in order to do something when a template has rendered one way would be to use the didInsertElement callback of the View class.
App.RailsView = Ember.View.extend({
didInsertElement:function(){
if($('#rails-nav').length !== 0){
$('#main-nav').hide();
}
}
});
http://emberjs.jsbin.com/dido/1/edit

Template renders only after refreshing the page

I want to have the list of universities and after clicking on one of them, next to this list majors' list is supposed to show. After clicking on one of majors, next to those two lists list of subjects shows. However I did something wrong in routing I suppose. After clicking on one of universities URL changes but the list of majors doesn't show up. But after refreshing the page (with this url) it works perfectly fine. Here is the code:
http://jsbin.com/AQAref/1/edit
Like the other guys said, your jsbin doesn't work. It's pretty apparent the real problem is with your link-to. If it works when you refresh, but not when you click on a link then the difference is where the model is coming from. When you refresh, the model comes from the model hook. When you navigate using a link, the model was supplied by the link-to statement.
In your situation, if you refresh the page you get the majors of a specific university from your university model hook. If you click on the university from your page, the university model is sent to the university resource, not the majors of that university.
So either this needs to say majors
{{#each}}
<li {{bindAttr id="id"}}>
<div class="list-element-title">
{{#link-to 'university' this.majors}}{{name}}{{/link-to}}
</div>
</li>
{{/each}}
instead of
{{#each}}
<li {{bindAttr id="id"}}>
<div class="list-element-title">
{{#link-to 'university' this}}{{name}}{{/link-to}}
</div>
</li>
{{/each}}
Or your route needs to return the university, but I'm guessing the route model hook is correct since you said it works on refresh.
App.UniversityRoute = Ember.Route.extend({
model: function(params) {
var majors = universities.findBy('id', params.univ_id).majors;
return majors;
}
});
I didn't look through the rest of your code, but make sure you are sending the same model through the link-to as you would get if you got the model through the model hook.

Ember.js problems binding content of array controller to content of a different array controller

We're having trouble linking the content of one array controller with the content of another array controller in Ember.js. We've scoured Stackoverflow, but no solution seems to work for us.
The scenario is simple. We have a model called "Campaign". The model is pretty basic and looks like this...
Campaign Model:
App.Campaign = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
opts: DS.attr('string'),
starred: DS.attr('boolean'),
keywords: DS.hasMany('App.Keyword')
});
// Create dummy campaign array
App.Campaign.FIXTURES = new Array();
// Populate dummy campaign array
...
The Campaign controller is also basic. It's a simple ArrayController with nothing going on inside it ...
Campaign Index Controller:
App.CampaignsIndexController = Em.ArrayController.extend({
});
For the sake of this question, we've stripped our application template down to the essentials. As you can see below, the application template has a sidebar. In the sidebar are two views. One view is for the site's navigation, the other view is for "Starred Campaigns". This view will contain a subset of the overall collection of campaigns.
Application Template:
<!-- ================================================== -->
<!-- ======================= APP ====================== -->
<!-- ================================================== -->
<div id="app" class="">
<!-- ================================================== -->
<!-- ==================== SIDEBAR ===================== -->
<!-- ================================================== -->
<div id="sidebar">
{{ view App.NavigationView }}
{{ view App.StarredCampaignsView controller="App.StarredCampaignsController" }}
</div>
<!-- ================================================== -->
<!-- ====================== PAGE ====================== -->
<!-- ================================================== -->
<div id="page" class="">
{{ outlet page }}
</div>
</div>
We have a controller for Starred Campaigns. This is where things start to get confusing. In our controller, we bind the content of the ArrayController to the content of the CampaignsIndexController, which holds the array of all Campaign models.
Starred Campaigns Controller:
App.StarredCampaignsController = Em.ArrayController.extend({
content: [],
contentBinding: Ember.Binding.oneWay('App.CampaignsIndexController.content')
});
The view of for Starred Campaigns is also very simple...
Starred Campaigns View:
App.StarredCampaignsView = Em.View.extend({
templateName: 'templates/layout/starred-campaigns',
});
And this is what the template for Starred Campaigns looks like. It is attempting to loop over the items in the controller.content and pass them off to another view.
Starred Campaigns Template:
<h5 class="sidebar-title">Starred Campaigns</h5>
<ul class="starred-campaigns clearfix">
{{# each item in controller.content }}
{{ view App.StarredCampaignItemView itemBinding="item" }}
{{/ each }}
</ul>
However, despite all of this, the content in StarredCampaignsController remains empty, even when we populate the App.Campaign array that is represent by App.CampaignsIndexController. See any problems with our approach? I feel like we're missing something really simple here.
In Ember, for controllers to require other controllers, you need to use the needs property. This is always changing in Ember: in previous versions it was the responsibility of the router with this.controllerFor, and with even older versions, the approach was more akin to the approach you appear to be attempting to take. However, the final approach has long since been deprecated, and instances of controllers/models/views are no longer held on App directly, only their object representations.
To dive straight in with the needs, you need to tell your App.StarredCampaignsController controller that it requires the App.CampaignsIndexController like so:
App.StarredCampaignsController = Em.ArrayController.extend({
content: [],
needs: ['campaigns_index']
});
You now have access to the App.CampaignsIndexController controller in your starred campaigns view: {{controllers.campaigns_index}}.
See simple JSFiddle: http://jsfiddle.net/AGJfB/
The reason why yours isn't working is because you've reference the object of the controller, as opposed to its instance:
Ember.Binding.oneWay('App.CampaignsIndexController.content')
Whilst you have an App.CampaignsIndexController on App, that's its object. Ember will create the instance for you when you access it through any of the Ember ways (this.controllerFor, needs, navigating to it via the URL, etc...).
In days gone by we could use:
Ember.Binding.oneWay('App.campaignsIndexController.content')
However, that's such a bad way to do things, because you're referencing an absolute path to the controller. Ever since Ember pre 1, I've never had to use Ember.Binding.oneWay, but there still will be cases where you need it. When you find yourself using it though, it's probably wise to ask yourself if it's necessary, and whether there's a better way to do it.
I hope I've explained myself well. Please feel free to ask any questions!

Categories

Resources