ember.js and nested templates/views - templates

I'm still trying to learn ember.js so please bear with me.
Objective
I'm currently creating a one page web application. When the application, the application will do an ajax call which will return a list of numbers lets. The application will process these numbers and create a div for each of the numbers and store it into a div container.
A click event will be associated with each div, so when the user clicks on the link a pop up dialoge will come up.
Code
Index.html
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="payloads">
<div class="page">
<div id="desktopWrap">
<div id="theaterDialog" title="Theater View" class="bibWindow1">
{{view.name}}
{{#each item in model}}
<div {{bindAttr id="item"}} {{action click item}}>
<div class="thumb1" ></div>
<div class="userDetails1">Payload {{item}}</div>
<div class="online1" ></div>
</div>
<div class="spacer10"></div>
{{/each}}
</div>
</div>
</div>
</script>
My app.js file is here:
App = Ember.Application.create();
App.Router.map(function() {
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return ['Payload_1', 'Payload_2', 'Payload_3'];
}
});
App.PayloadsRoute = Ember.Route.extend({
model: function() {
return ['Payload_1', 'Payload_2', 'Payload_3'];
}
})
App.IndexController = Ember.ObjectController.extend(
{
click: function(e)
{
alert("clicked:" + e);
}
})
General Idea
The current code above will create the "theaterDialogue" div box with 3 divs. A onclick action is associated with it through the Controller for each of these divs. When a user clicks on the first div "payload 1" will be printed in an alert box, second div "payload 2" etc. Instead of the print out, I want to be able to render a new dialogue box (jquery dialogue box) where the contents will be rendered from a template. Its not clear to me how this is done.....I understand that views are used to store data for the templates...but not how you would nest a template within one that is generated by an action?
If you could point me anyone, that would be awesome!
Any advice appreciated,
D

Basic approach for nesting is,
Define the nested routes (Main step, if you get this right, you are almost there)
Add {{outlet}} in the templates if you think that this view will have something appended to it later on
For example we have 3 views A, B, C and the nesting is as follows
A
|_B
|_C
Then the templates A & B should have the {{outlet}}, while C is the last one it shouldnt have {{outlet}}
A good example

Related

Different layouts depending on sub resources

Sorry if this is a really obvious questions but I have the following routes:
Web.Router.map(function () {
this.resource('orders', function(){
this.resource("order", {path:":order_id"});
});
});
And for my orders template I have something like:
<div class="someclass">
{{outlet}}
</div>
And what I want todo is:
{{#if onOrderRoute}}
<div class="someclass">
{{outlet}}
{{else}}
<div class="someotherclass">
{{/if}}
</div>
I was wondering what the best way of doing this is, or am I mising something?
There are multiple ways to accomplish this. The view has a layoutName property you can use to specify your layout. Another option is to specify a property on your child view, and then your template can bind to that by using the view property.
For example:
Web.OrderView = Ember.View.extend({
childView: true
);
Then, in your template you bind to view.childView
{{#if view.childView}}
<!-- code goes here -->
{{/if}}
Further, you can even create a mixin and then just inject that mixin into every view.
Web.ChildViewMixin = Ember.Mixin.create({
childView: true
});
Web.ChildView = Ember.View.extend(ChildViewMixin, {
});

Applying background color to component in ember js

I have a toolbar component which has a list of items. When I click on an element I need to add background color to the clicked element. Also need to deselect the previously selected item.
I tried using classNameBinding but it applies to all the elements in the list.
How can I apply Background Color to the elements which are clicked inside the Component
In Template:
<script type="text/x-handlebars" data-template-name="components/test-toolbar">
<ul>
<li {{bindAttr class="bgColor"}} {{action 'redClick'}}>
Red
</li>
<li {{bindAttr class="bgColor"}}>
Yellow
</li>
<li {{bindAttr class="bgColor"}}>
Blue
</li>
</ul>
</script>
<script type="text/x-handlebars" data-template-name="index">
<div>
{{test-toolbar}}
</div>
</script>
In app.js:
App.TestToolbarComponent = Ember.Component.extend({
classNameBindings: ['bgColor'],
bgColor: false,
actions: {
redClick: function () {
this.set('bgColor', true);
}
}
});
Here is the working DEMO: JSBIN LINK
Using actions you can't get the target element to manipulate it. Also using 3 different actions for same is something i don't suggest. You can use click hook to handle this in simple jquery way. here is code and jsbin link
App.TestToolbarComponent = Ember.Component.extend({
click: function(event){
var elem = Ember.$(event.target);
if(elem.is('li')) {
this.$('li').removeClass('active');
elem.addClass('active');
}
}
});
http://emberjs.jsbin.com/qiriwi/2/edit
The answers given will definitely work. But I think there is a very simple way to get this done. You will have a .css (most probably style.css) file associated with your ember app. In that file add the following snippet:
.active{
/*your code goes here
eg. background-color: 'red';*/
}
This will simply allow you to style the component whichever is active at that moment of time.

getting back reference to a specific model using Ember's Array Controller

I'm new to Ember and am finding some of their concepts a bit opaque. I have a app that manages inventory for a company. There is a screen that lists the entirety of their inventory and allows them to edit each inventory item. The text fields are disabled by default and I want to have an 'edit item' button that will set disabled / true to disabled / false. I have created the following which renders out correctly:
Inv.InventoryitemsRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON("/arc/v1/api/inventory_items/" + params.location_id);
}
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled="true"}}</p>
<p>{{input type="text" value=detail disabled="true"}}</p>
<button {{action "editInventoryItem" data-id=id}}>edit item</button>
<button {{action "saveInventoryItem" data-id=id}}>save item</button>
</div>
{{/each}}
</script>
So this renders in the UI fine but I am not sure how to access the specific model to change the text input from disabled/true to disabled/false. If I were just doing this as normal jQuery, I would add the id value of that specific model and place an id in the text input so that I could set the textfield. Based upon reading through docs, it seems like I would want a controller - would I want an ArrayController for this model instance or could Ember figure that out on its own?
I'm thinking I want to do something like the following but alerting the id give me undefined:
Inv.InventoryitemsController=Ember.ArrayController.extend({
isEditing: false,
actions: {
editInventoryItem: function(){
var model = this.get('model');
/*
^^^^
should this be a reference to that specific instance of a single model or the list of models provided by the InventoryitemsRoute
*/
alert('you want to edit this:' + model.id); // <-undefined
}
}
});
In the Ember docs, they use a playlist example (here: http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/) like this:
App.SongsRoute = Ember.Route.extend({
setupController: function(controller, playlist) {
controller.set('model', playlist.get('songs'));
}
});
But this example is a bit confusing (for a couple of reasons) but in this particular case - how would I map their concept of playlist to me trying to edit a single inventory item?
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled="true"}}</p>
<p>{{input type="text" value=detail disabled="true"}}</p>
<button {{action "editInventoryItem" this}}>edit item</button>
<button {{action "saveInventoryItem" this}}>save item</button>
</div>
{{/each}}
</script>
and
actions: {
editInventoryItem: function(object){
alert('you want to edit this:' + object.id);
}
}
Is what you need. But let me explain in a bit more detail:
First of all, terminology: Your "model" is the entire object tied to your controller. When you call this.get('model') on an action within an array controller, you will receive the entire model, in this case an array of inventory items.
The {{#each}} handlebars tag iterates through a selected array (by default it uses your entire model as the selected array). While within the {{#each}} block helper, you can reference the specific object you are currently on by saying this. You could also name the iteration object instead of relying on a this declaration by typing {{#each thing in model}}, within which each object would be referenced as thing.
Lastly, your actions are capable of taking inputs. You can declare these inputs simply by giving the variable name after the action name. Above, I demonstrated this with {{action "saveInventoryItem" this}} which will pass this to the action saveInventoryItem. You also need to add an input parameter to that action in order for it to be accepted.
Ok, that's because as you said, you're just starting with Ember. I would probably do this:
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled=headerEnabled}}</p>
<p>{{input type="text" value=detail disabled=detailEnabled}}</p>
<button {{action "editInventoryItem"}}>edit item</button>
<button {{action "saveInventoryItem"}}>save item</button>
</div>
{{/each}}
</script>
with this, you need to define a headerEnabled property in the InventoryitemController(Note that it is singular, not the one that contains all the items), and the same for detailEnabled, and the actions, you can define them also either in the same controller or in the route:
App.InventoryitemController = Ember.ObjectController.extend({
headerEnabled: false,
detailEnabled: false,
actions: {
editInventoryItem: function() {
this.set('headerEnabled', true);
this.set('detailEnabled', true);
}
}
});
that's just an example how you can access the data, in case the same property will enable both text fields, then you only need one, instead of the two that I put . In case the 'each' loop doesn't pick up the right controller, just specify itemController.

Ember.js: ArrayController undefined in template

Problem:
I am kind of struggling with the organization of my first ember app. The current issue is that the my Items ArrayController is not defined in my dashboard template:
<script type="text/x-handlebars" data-template-name="dashboard">
{{#if controllers.items}}
<p class="alert alert-error">Dashboard can access item's info - Nice!</p>
{{else}}
<p class="alert alert-error">Dashboard cannot access items... :-/</p>
{{/if}}
</script>
Likely cause: *
**EDIT: after talking with #conrad below, I'm kind of questioning this:*
I had a similar issue in an earlier post and kingpin2k suggested the cause was that I:
"never created anything that uses the options controller".
This is probably the case here as well. This quick screencast shows that a breakpoint on my ArrayController is not hit on page load - but it is hit when I inspect the Items controller in the Ember inspector tool (eg, Ember creates the ArrayController object right then for the first time).
Apparent non-solutions:
My Dashboard controller says it needs the Items controller. I guess that isn't enough to instantiate the ArrayController?
App.ItemsController = Ember.ArrayController.extend({
len: function(){
return this.get('length');
}.property('length'),
totalCost: function() {
return this.reduce( function(prevCost, item){
return parseInt(item.get('values').findBy('type', 'cost').price, 10) + prevCost;
}, 0);
}.property('#each.values')
[more computed properties...]
});
App.DashboardController = Em.Controller.extend({
needs: ['items'],
itemsLength: Ember.computed.alias('controllers.items.len'),
itemsTotalCost: Ember.computed.alias('controllers.items.totalCost'),
[more computed properties...]
});
Furthermore, each item in Items is being rendered in my items template. I guess that does not create the missing controllers.items either...
<script type="text/x-handlebars" data-template-name="items">
{{#each}}
[these render fine]
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="display">
<!-- DISPLAY TEMPLATE -->
{{!- DASHBOARD -}}
{{render dashboard}}
{{!- ITEMS -}}
{{render 'items' items}}
</script>
So then.. what?
I can imagine many possible avenues, but haven't gotten any of them to work yet:
Specify the Items ArrayController in {{render dashboard}}?
Some configuration in a Route?
Maybe my templates/routes are not correctly arranged?
You could make sure that the ItemController is instantiated in the dashboard template by calling it in the DashboardController's init function:
App.DashboardController = Em.Controller.extend({
needs: ['items'],
init: function() {
this._super();
this.get('controllers.items.length');
}
});
/edit:
removed the part that was not helpful

EmberJS - sharing a controller / template for different routes

I have a very simple CRUD application that allows for creating new objects as well as editing them.
The template used for adding a record and editing a record are almost identical.
They use the exact same form elements.
The only difference is the title and the button below the form (that should either update or create a record)
In my implementation, I have
2 route definitions
2 route objects
2 controller objects
2 templates
I was wondering if
I can't promote re-use here
if all of these objects are required.
What bothers me :
I have 2 separate templates for create and edit (while they are almost identical)
I have 2 separate controllers that do exactly the same thing.
I was hoping to solve this on the controller level.
As a controller decorates a model, in my case 1 single controller object could wrap either a new record or an existing record.
It could then expose a property (isNewObject) so that the template can decide if we are in the "new" or "edit" flow. The controller could have a single createOrUpdate method that works both in the new and in the update scenario.
Routes
The current implementation is using a new and an edit route for my resource.
this.resource("locations", function(){
this.route("new", {path:"/new"});
this.route("edit", {path: "/:location_id" });
});
The new route
The new route is responsible for creating a new record and is called when the user navigates to the new record screen.
App.LocationsNewRoute = Ember.Route.extend({
model: function() {
return App.Location.createRecord();
}
});
The edit route
The edit route is responsible for editing an existing object when the user clicks the edit button in the overview screen.
I haven't extended the default edit route but instead I'm using the auto generated one.
Controllers
The new and edit controllers are responsible for handling the action that occurs in the template (either saving or updating a record)
The only thing both controllers do is commit the transaction.
Note: I guess this is a candidate for re-use, but how can I use a single controller for driving 2 different routes / templates ?
App.LocationsNewController = Ember.ObjectController.extend({
addItem: function(location) {
location.transaction.commit();
this.get("target").transitionTo("locations");
}
});
App.LocationsEditController = Ember.ObjectController.extend({
updateItem: function(location) {
location.transaction.commit();
this.get("target").transitionTo("locations");
}
});
Templates :
As you can see, the only code-reuse I have here is the partial (the model field binding).
I still have 2 controllers (new and edit) and 2 templates.
The new templates sets the correct title / button and re-uses the form partial.
<script type="text/x-handlebars" data-template-name="locations/new" >
<h1>New location</h1>
{{partial "locationForm"}}
<p><button {{action addItem this}}>Add record</button></p>
</script>
The edit templates sets the correct title / button and re-uses the form partial.
<script type="text/x-handlebars" data-template-name="locations/edit" >
<h1>Edit location</h1>
{{partial "locationForm"}}
<p><button {{action updateItem this}}>Update record</button></p>
</script>
The partial
<script type="text/x-handlebars" data-template-name="_locationForm" >
<form class="form-horizontal">
<div class="control-group">
<label class="control-label" for="latitude">Latitude</label>
<div class="controls">
{{view Ember.TextField valueBinding="latitude"}}
</div>
</div>
<div class="control-group">
<label class="control-label" for="latitude">Longitude</label>
<div class="controls">
{{view Ember.TextField valueBinding="longitude"}}
</div>
</div>
<div class="control-group">
<label class="control-label" for="accuracy">Accuracy</label>
<div class="controls">
{{view Ember.TextField valueBinding="accuracy"}}
</div>
</div>
</form>
</script>
Note: I would expect that I can do something efficient/smarter here.
I would want my template to look this this : (getting the title from the controller, and have a single action that handles both the update and the create)
<script type="text/x-handlebars" data-template-name="locations" >
<h1>{{title}}</h1>
{{partial "locationForm"}}
<p><button {{action createOrUpdateItem this}}>Add record</button></p>
</script>
Question
Can I re-work this code to have more code-reuse, or is it a bad idea to attempt to do this with a single template and a single controller for both the "edit record" and "new record" flows.
If so, how can this be done ? I'm missing the part where my 2 routes (create and edit) would re-use the same controller / template.
You were correct throughout.
And you can use the new controller and template in edit route also.
You have to do only two things.
First give the template name as new in the renderTemplate hook of edit route.
App.LocationsEditRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controllerFor('locations.new').setProperties({isNew:false,content:model});
},
renderTemplate: function() {
this.render('locations/new')
}
});
As the new template is rendered the controller also will be newController, and you can have the action to point to an event in the newController.
If you want to change the title and button text, you can have them as computed properties observing isNew property.
Hope this helps.
P.S: Don't forget to set the isNew property to true in the setupController of new route
Use this:
App.YourNewRoute = Em.Route.extend ({
controllerName: 'controllerName',
templateName: 'templateName'
});
Only use initial name like for homeController user "home" thats it.
Example:
App.YourNewRoute = Em.Route.extend ({
controllerName: 'home',
templateName: 'home'
});
Now you can use template and controller methods of "home".