Ember routes + outlets - ember.js

After diving into Backbone + Marionette for quite a long time, I've been training with Ember for a week or so. I've been able to play with it and build a blog easily. As long as the app relies on routes it's fine. Now I would like to build an app which only has 2 routes, and relies more on states. I want to define the following nested HTML structure :
<div id="topbar"></div>
<div id="content"></div>
<div id="botbar"></div>
I would like the topbar and botbar to remain the same whatever the content is.
Content could be "pos content"
<div id="pos">
<div id="leftsidebar">...</div>
<div id="cart">...</div>
<div id="buttons">...</div>
<div id="rightsidebar">...</div>
<div id="header">...</div>
</div>
or "params content"
<div id="params">...</div>
So far i've managed to build the 1st level of nesting using the following code :
App = Em.Application.create({});
App.Router.map(function() {
this.route('index', { path: '/' });
this.route('params', { path: '/params' });
});
App.IndexRoute = Em.Route.extend({
renderTemplate: function() {
this.render();
this.render('topbar', { outlet: 'topbar' });
this.render('pos', { outlet: 'content' });
this.render('botbar', { outlet: 'botbar' });
}
});
App.ParamsRoute = Em.Route.extend({
renderTemplate: function() {
this.render();
this.render('topbar', { outlet: 'topbar' });
this.render('params', { outlet: 'content' });
this.render('botbar', { outlet: 'botbar' });
}
});
I see in chrome Ember extension that the routes are defined, and my controllers (TopbarController, BotbarController and PosController/ParamsController depending on the route) seems to be instanciated.
Now what I would like is going deeper, and have a controller per sub-outlet (leftsidebar, cart, etc.) but I have no idea how to build the second level of nesting.
The documentation doesn't seem to help me and google is not my friend on this :( Could you guys help me out ? Thank you.

outlets in ember are contextual to the currently active route.
If you have the following application template
<div id="topbar">
{{ outlet 'topbar' }}
</div>
<div id="content">
{{ outlet }}
</div>
<div id="botbar">
{{ outlet 'botbar' }}
</div>
You could clean up your repeatitive code by moving it into the ApplicationRoute
App.ApplicationRoute = Ember.Route.extend({
renderTemplate: function(){
this._super.apply(this, arguments);
this.render('topbar',{outlet: 'topbar', into: 'application'})
this.render('botbar',{outlet: 'botbar', into: 'application'})
}
})
Now you can have the following index template
<div id="index">
<div id="left">
{{ outlet "left" }}
</div>
<div id="content">
{{ outlet }}
</div>
<div id="right">
{{ outlet 'right' }}
</div>
</div>
You need to clarify which context the outlet is located in by rendering "into" it.
App.IndexRoute = Ember.Route.extend({
renderTemplate: function(){
this._super.apply(this, arguments);
this.render('right',{outlet: 'right', into: 'index'})
this.render('left',{outlet: 'left', into: 'index'})
}
})

Related

Ember: {{link-to}} helper error when in {{each}} helper

The link-tohelper return the following error:
Uncaught Error: each doesn't match link-to - 5:10
The template:
<script type="text/x-handlebars" id="actions">
<div class='container-fluid'>
<div class="row"> <!-- -->
<div class="col-md-6 col-lg-4"> <!-- -->
{{#each action in model}}
{{link-to 'action' action}}{{action.id}}{{/link-to}}
{{/each}}
{{outlet}}
</div>
</div>
</div>
</script>
The router:
App.Router.map(function() {
this.resource('application', function() {
this.resource('actions', function() {
this.resource('action', { path: '/:action_id'});
});
});
The route:
App.ActionsRoute = Ember.Route.extend({
model: function() {
return this.store.findAll('action');
//return this.modelFor('user').get('actions');
},
setupController: function (controller, model) {
controller.set('model', model);
},
});
I cannot find what is wrong.
It's a quite small error. When using a block helper such as each or link-to you need to invoke them with a # in front, such as you have done with {{#each}}. Since you lack that on your starting link-to, the parser sees the {{/link-to}} and notices that it currently is working with a each-block and those doesn't match. Just add a # in front of your starting 'link-to' and it should work fine.
{{#each action in model}}
{{#link-to 'action' action}}{{action.id}}{{/link-to}}
{{/each}}

Ember.js: How to filter a model by route

I'm trying to adopt the completed/archived-route-behavior from this TodoMVC-Tutorial.
I'm using Ember 1.7.0, Ember Data 1.0.0-beta.9 and Handlebars 1.3.0.
I want to get all active users on my index route (example.com/organization/users) and all archived users on my archived route (example.com/organization/users/archived).
But the filter doesn't work: archived users are shown on my index route and vice versa, but my console.log output is correct.
Update: Please check this JSBin: http://emberjs.jsbin.com/puxeludeviga/3/
That's how I tried it:
Docket.OrganizationUsersRoute = Docket.AuthenticatedRoute.extend({
model: function() {
return this.store.find('user');
},
renderTemplate: function() {
// render all posts
this.render('organization/users', {
into: 'application'
});
// render toolbar
this.render('organization/toolbar', {
into: 'application',
outlet: 'toolbar'
});
}
});
Docket.OrganizationUsersIndexRoute = Docket.OrganizationUsersRoute.extend({
model: function() {
console.log('index');
return this.store.filter('user', function(user) {
return !user.get('archived');
});
}
});
Docket.OrganizationUsersArchivedRoute = Docket.OrganizationUsersRoute.extend({
model: function() {
console.log('archived');
return this.store.filter('user', function(user) {
return user.get('archived');
});
}
});
And that's my template:
<ul class="entries">
{{#each}}
<li>
<div class="actions">
<button {{action "remove" this}} class="ei-icon-close"></button>
</div>
<div class="link" {{action "edit" this}} data-uk-modal="{target:'#user-modal'}">
<span class="before">{{initial}}</span>{{name}}
</div>
</li>
{{else}}
<li>No users</li>
{{/each}}
</ul>
My (shortened) router.js file:
Docket.Router.map(function() {
// organization route
this.resource('organization', function() {
// user routes
this.resource('organization.users', { path: '/users' }, function() {
this.route('archived');
});
});
});
I think I found your issue. The thing here is that template you're rendering the users into, has this name:
organization/users
and turns out that it matches to you organization.users route, so, it is always rendering the data from that route. You need to change the name of you template, let's say to:
organization/users_list
and then, use that in your routes, it will not have any problems because the name is different to the users route.
something like:
<script type="text/x-handlebars" data-template-name="organization/users_list">
<div class="top">
<h1>Users</h1>
{{#link-to 'organization.users.index' class="button-archive"}}active{{/link-to}} - {{#link-to 'organization.users.archived' class="button-archive"}}archived{{/link-to}}
</div>
<ul class="entries">
{{#each}}
<li>{{name}} - Archived: {{archived}}</li>
{{else}}
<li>No users to show</li>
{{/each}}
</ul>
</script>
and in your routes:
renderTemplate: function() {
// render all posts
this.render('organization/users_list', {
into: 'application'
});
// render toolbar
this.render('organization/toolbar', {
into: 'application',
outlet: 'toolbar'
});
}

Ember.js models not being rendered in nested route

I have an Ember app setup, and when visiting /playlist/1, it renders the templates as expected. However, the model data isn't being displayed. I have a playlist.hbs file with an outlet, and a playlist folder with index.hbs inside of that folder with html and handlebars to display data. I have both App.PlaylistIndexController & App.PlaylistIndexRoute defined.
App.Router.map(function() {
this.resource('account', {path: '/account/:accountId'}, function() {
this.route('login');
});
this.resource('playlist', { path: '/playlist/:playlist_id'}, function() {
this.route('edit');
});
});
FWIW, everything was working properly prior to adding the nested route (with my controller and route defined as App.PlaylistController and App.PlaylistRoute respectively)
"playlist":{"id":1,"name":"playlistname"}
Any ideas how to get the playlist data to display properly?
UPDATE:
App.PlaylistIndexRoute = App.AuthenticatedRoute.extend({
setupController: function(controller, model) {
this._super(controller, model);
var online = this.get('store').find('account');
this.controllerFor('playlistViewers').set('model', online);
},
});
<div id="main">
<div id="primary">
<section id="playlist">
<header class="playlist-header">
<h2>Playlist</h2>
<h1>{{name}}</h1>
</header><!--.playlist-header-->
<div class="playlist-content">
<ul>
{{#each song in songs}}
<li {{action 'play' song}} class="show-for-mobile">
<button {{bind-attr class="song.isPlaying:icon-volume-up:icon-play song.isStreaming:icon-adjust"}} ></button>
<div class="song-meta">
<span class="song-name">{{song.name}}</span>
<span class="song-artist">{{song.artist}}</span>
</div><!--.song-meta-->
</li>
{{/each}}
</ul>
</div><!--.playlist-content-->
</section><!--#playlist-->
</div><!--#primary-->
{{partial "sidebar"}}
That's my current playlist/index.hbs file, but even {{name}} displays nothing
Your PlaylistIndexRoute needs to have a model. This can be the model that was loaded by the playlist resource.
App.PlaylistIndexRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.modelFor('playlist');
}
});

EmberJS: How to update model attributes

I've got a list of messages that are provided by a Rails backend. What I need is when the "toggle_visibility" action button is pressed, it would toggle the "publicly_viewable" property. This means, making a corresponding REST call (to effect the database) and changing the state of the corresponding cached message. Here is where I'm at so far.
Here's what I've got so far, that manages to end up on the debug console:
# app.js
App.Store = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.extend({
url: 'http://localhost:3000'
})
});
App.Message = DS.Model.extend({
body: DS.attr('string'),
mobile_number: DS.attr('string'),
publicly_viewable: DS.attr('boolean'),
created_at: DS.attr('date')
});
App.Router.map(function() {
this.resource('messages');
});
App.MessagesRoute = Ember.Route.extend({
model: function() { return App.Message.find() }
});
App.MessagesController = Ember.ArrayController.extend({
toggle_visibility: function(){
debugger;
}
});
# index.html
{{#each model}}
<button class="close" {{action toggle_visibility this}}><i class="icon-eye-close"></i></button>
<p class="message_body lead">{{body}}</p>
<small class="source_number">from {{mobile_number}}, received {{date created_at}}</small>
{{/each}}
I've been spending the past few hours reading through the Ember Guides and while I've gotten an idea on what the different classes there are, I still can't visualize clearly how to go about it. Particularly, I'm not sure if this should be a route concern or a controller, and I know that if ever it was a controller responsibility, I know that it should be on an ObjectController but I've been having trouble making it work.
You can use ArrayController#itemController and define a controller for the individual record in your ModelArray. Then you have to specify in the Array Controller the Object Controller responsible for a single object, which you have to reference as well in Handlebars. You can do something like this:
JS:
App.MessageController = Ember.ObjectController.extend({
visibilityClass: function() {
var visibility = this.get('model.publiclyViewable');
return 'toggle-visibility mdi-action-visibility%#'.fmt(
visibility ? '':'-off'
);
}.property('model.publiclyViewable'),
actions: {
toggleVisibility: function() {
var model = this.get('model');
model.toggleProperty('publiclyViewable');
model.save();
}
}
});
Handlebars:
<script type="text/x-handlebars" data-template-name="messages">
<!--
At this point the {{each}} helper will know how to lookup for
the controller simply by it's name
-->
{{#each model itemController="message"}}
<div class="panel panel-primary">
<div class="panel-heading">
<div class="pull-left">
<h3 class="panel-title">{{title}}</h3>
</div>
<div class="pull-right">
<a {{action 'toggleVisibility'}}>
<i class={{visibilityClass}} style="color: #FFF"></i>
</a>
</div>
</div>
<div class="panel-body">
{{body}}
</div>
<div class="panel-footer">
<i class="mdi-communication-quick-contacts-dialer"></i> {{mobileNumber}}
<i class="mdi-notification-event-note"></i> {{createdAt}}
</div>
</div>
{{/each}}
</script>
(see fiddle)
Note: Updated to Ember 1.11.x-beta and changed the code a little bit

Applying JQuery effect when ember-data has finished loading and element is rendered

I have a list of items that I want to make draggable. I am using ember-data to get the items from my API and then render them in a view with an ArrayController. I can successfully load the items and render them but I don't know where or when to put the JQuery draggable function.
I have tried using didInsertElement on my view but this is triggered when the view is rendered and not when the items are loaded. I have also tried to put an observer on my ArratController to run the code when the array length changes (i.e when an element is added to the array) None of these things worked.
Any ideas?
My JS code:
var REVISION = 9;
// Application namespace
var App = Ember.Application.create({
ApplicationView: Ember.View.extend({
templateName: 'application',
classNames: ['application-view']
}),
ApplicationController: Ember.Controller.extend(),
RewardsView: Em.View.extend({
templateName: 'rewards',
click: function(event) {
console.log(event);
//window.location.href = event
},
didInsertElement: function() {
this.$(".draggable").draggable();
}
}),
RewardsController: Em.ArrayController.extend({
rewardAdded: function() {
$(".draggable").draggable({
cursor: 'move', // sets the cursor apperance
revert: 'invalid', // makes the item to return if it isn't placed into droppable
revertDuration: 900, // duration while the item returns to its place
});
}.observes('length')
}),
ready: function(){
console.log("Created App namespace");
},
Router: Ember.Router.extend({
goToRewards: Ember.Route.transitionTo('root.rewards'),
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
}),
rewards: Ember.Route.extend({
route: '/rewards',
enter: function ( router ){
console.log("The rewards sub-state was entered.");
},
connectOutlets: function(router, context){
router.get('applicationController').connectOutlet('content','rewards', App.store.findAll(App.Rewards));
}
}),
})
})
});
App.Rewards = DS.Model.extend({
provider: DS.attr('string'),
name: DS.attr('string'),
description: DS.attr('string'),
discount: DS.attr('string'),
img: DS.attr('string'),
video: DS.attr('string'),
price: DS.attr('string'),
available_quantity: DS.attr('string'),
didLoad: function() {
console.log('model loaded', this);
}
});
App.store = DS.Store.create({
revision: REVISION,
adapter: DS.DjangoTastypieAdapter.extend({
serverDomain: "http://example.com",
namespace: "api/v1"
}),
});
// Start!
App.initialize();
My handlebars template:
{% handlebars "rewards" %}
<div class="row-fluid">
<div class="span6">
<div class="box paint color_7">
<div class="title">
<div class="row-fluid">
<h4> Available Rewards </h4>
</div>
</div>
<!-- End .title -->
<div class="content">
<div class="accordion" id="accordion2">
{{#each reward in controller}}
<div class="draggable accordion-group">
{{#with reward}}
{{#if isLoaded}}
<div class="accordion-heading">
<a class="accordion-toggle collapsed" data-toggle="collapse" data-parent="#accordion2" {{bindAttr href="reward.id"}}> {{name}} {{id}} </a>
</div>
<div {{bindAttr id="reward.id"}} class="accordion-body collapse" style="height: 0px; ">
<div class="accordion-inner"> {{description}} </div>
</div>
{{else}}
Loading...
{{/if}}
{{/with}}
</div>
{{/each}}
</div>
</div>
<!-- End .content -->
</div>
<!-- End .box -->
</div>
{% endhandlebars%}
use findQuery instead of findAll
router.get('applicationController').connectOutlet('content','rewards',App.store.findQuery(App.Rewards));
You get the property isLoaded for the content, now you can add observer on isLoaded property to run your required functionality as follows
startDraggableFunctionality: function(){
if(this.get('content.isLoaded'){
/*Your code goes here...*/
}
}.observes('content.isLoaded')
After Rendering + Data loaded
Inside the view add the following method as follows
View
//This method will be executed when the view has finished rendering
afterRender: function(){
this.get('controller').set("viewRendered", true);
}
Controller
viewRendered: false, //set it to false initially
startDraggableFunctionality: function(){
if(this.get('content.isLoaded') && this.get('viewRendered')){
/*Your code goes here...*/
}
}.observes('content.isLoaded', 'viewRendered')
This way if the view renders before loading content, isLoaded make sures that function will be executed only after data has been loaded & vice-versa