Ember.js embed one view in another without altering the route - ember.js

I'm trying to create something like a carousel in Ember, where the current item shown is controlled by one of four buttons on the page (for simplicity's sake, I'm only showing two buttons). I'm currently implementing this using outlets and routes, but I'd much prefer to have I have this work without altering the route. Here's how I'm currently going about this:
<!-- index.hbs (snipped, simplified) -->
<div>
{{outlet highlights}}
</div>
<!-- buttons for controlling outlet content (only showing 2 for now) -->
<div>
<a class="button" href="#" {{action "showHighlight" "placeholder1"}}>
Highlight 1
</a>
<a class="figcaption" href="#" {{action "showHighlight" "placeholder2"}}>
Highlight 2
</a>
</div>
My index controller handles the action that's called when the buttons are pressed:
App.IndexController = Ember.ArrayController.extend({
highlights: ['placeholder1', 'placeholder2'],
actions: {
showHighlight: function(highlight) { // render a new view into
this.transitionToRoute('/highlights/' + highlight);
}
}
});
My routes for the highlights:
App.Router.map(function() {
this.resource('highlights', function() {
this.route('placeholder1', { path: '/placeholder1' });
this.route('placeholder2', { path: '/placeholder2' });
this.route('placeholder3', { path: '/placeholder3' });
this.route('placeholder4', { path: '/placeholder4' });
});
});
For each placeholder route:
App.Placeholder1Route = Ember.Route.extend({
renderTemplate: function() {
this.render({
into: 'index',
outlet: 'highlights'
});
}
});
Is there a better way to do this, without having to use routes?

I ended up solving this by handling the action in the route and choosing the proper template to render:
App.IndexRoute = Ember.Route.extend({
actions: {
showHighlight: function(name) {
var highlightTemplate = 'highlight_' + name;
this.render(highlightTemplate, {
into: 'index',
outlet: 'highlights'
}
}
}
});
Using this technique, you don't need to specify any routes for the highlights and you don't need an IndexController (unless you want one for handling other events).

Related

renderTemplate into outlet is not working

I have templates box/inbox.hbs and users/profile.hbs with {{outlet inbox}} in profile.hbs
In my profile.hbs I have {{#link-to 'box.inbox'}}this is a link{{/link-to}} which should render box/inbox.hbs into the outlet {{outlet inbox}} which I'm trying to do in my router.js with:
Router.BoxInboxRoute = Ember.Route.extend({
renderTemplate: function(){
this.render('box.inbox', {into: 'users.profile', outlet: 'inbox'});
}
});
but the link just redirects me to another page /box/inbox. How do I get it so that the inbox.hbs is rendered in the outlet on profile.hbs?
I think the link-to helper is the source of confusion. As far as I know, link-to will always change the URL and route, so you would never be able to achieve rendering the box.inbox route into a specific part of the users.profile template by clicking a link unless you use nested routes.
However, to achieve something like clicking on a link to show the inbox content, you could always load the box.inbox route hidden using the named outlet, then on click of a link, show the div containing the box.inbox contents. Here's an example.
UsersProfileRoute:
Router.UsersProfileRoute = Ember.Route.extend({
renderTemplate: function(){
this.render();
this.render('box.inbox', {into: 'users.profile', outlet: 'inbox'});
}
});
users/profile.hbs:
<a href='#' {{action 'showInboxAction'}}>click to see inbox</a>
<div {{bind-attr class=":inbox-style shouldShowInbox:displayed:hidden"}} >
{{outlet inbox}}
</div>
css:
.inbox-style.displayed {
display: block;
}
.inbox-style.hidden {
display: none;
}
and in the user profile controller have an action:
action:
{
showInboxAction: function()
{
this.set('shouldShowInbox', true);
}
}

How to add search results from an action to the user interface in Emberjs

I have adopted a simple Ember app. Currently, I load a set of locations via the model method on the route like this:
Hex.LocationsbysectionsRoute = Ember.Route.extend({
model: function(params) {
return $.getJSON("/arc/v1/api/all-locations").then( function(response){
return response.map(function (child) {
return Hex.Location.create(child);
});
});
}
});
I would like to add a search button at the bottom to add locations to a specific section. I understand that I could use transitionTo but I'd just like to place this into the DOM somehow - this seems really simple but having a hard time finding a working example online.
Something like:
<script type="text/x-handlebars" id="locationsbysections">
<input id='hex-ember-location-val' /><button {{action 'searchForLocations'}}>search</button>
</script>
But I'm not really sure how to handle the searchForLocations action and get the results into the UI. Would I use the model on the Route? I was thinking something like this but how would I deliver the Promise to the template?
Hex.LocationsbysectionsController = Ember.ArrayController.extend({
actions: {
searchForLocations: function() {
var query=$('#hex-ember-location-val').val();
$.getJSON("/arc/v1/api/locations/query_by_sections/" + query).then( function(response){
var items=[];
$.each(response, function(idx, val){
var location=Hex.Location.create(val);
items.push(location);
console.log(location);
});
});
}
}
});
I'm able to put this into the items array but how would I render that into the original locationsbysections template? It doesn't seem like the model method of the Router is the place to do this but how would I get this to work?
I have tried something like this:
{{#if hasSearchItems}}
<div>there are {{items.length}} search resutls!!!</div>
{{#each items}}
<div>{{name}} <button {{action "addToSection" this}}>add to section</button></div>
{{/each}}
{{else}}
<div>there are no search results</div>
{{/if}}
and then manage the hasSearchItems variable in the Controller, but no luck.
If you don't use real ember-data model you can eventually leave model empty and set your property in setupController:
Hex.LocationsbysectionsRoute = Ember.Route.extend({
model: function(params) {return null},
setupController: function(controller, model) {
$.getJSON("/arc/v1/api/all-locations").then(function(response) {
var locations = response.map(function (child) {
return Hex.Location.create(child);
});
controller.set("locations", locations)
});
}
}
<script type="text/x-handlebars" id="locationsbysections">
{{#each location in locations}}
<div>{{location.name}}</div>
etc...
{{/each}}
</script>
In this manner you can overwrite your locations property without problems.
<script type="text/x-handlebars" id="locationsbysections">
...
{{input type='text' value=searchInput}}
<button {{action 'searchForLocations'}}>search</button>
</script>
Hex.LocationsbysectionsController = Ember.ArrayController.extend({
searchInput: "",
actions: {
searchForLocations: function() {
var that = this;
$.getJSON("/arc/v1/api/locations/query_by_sections/" + that.get("searchInput")).then(function(response) {
var locations = response.map(function (child) {
return Hex.Location.create(child);
});
that.set("locations", locations)
});
});
}
});

Do not understand why EmberJs is not returning the model from the route

I am using EmberJs version 1.4 and I have the following set of templates and routes. The idea is that when the user goes to the "Widgets" route the returned model is only a collection of Widget Ids and Widget names to create the links and then when the user clicks on a link a call to a service will get all of the selected widget data to be displayed on the "Widget" template.
JavaScript code
window.Awesome = Ember.Application.create();
Awesome.Router.map(function() {
this.resource("awesome", {path: "/"}, function(){
this.route('login');
});
this.resource("widgets", function () {
this.resource('widget', { path: '/:widgetId' }, function () {
this.route('general', { path: 'info' });
this.route('configuration');
this.route('operations');
})
});
});
Awesome.WidgetsRoute = Awesome.AuthenticationRoute.extend({
model: function(){
//TODO: Call a service to get the model.
return { widgets: [{ widgetId: 1, widgetName: "Great Widget" }, { widgetId: 2, widgetName: "Fantastic Widget" }, { widgetId: 3, widgetName: "Brutal Widget" }] };
}
});
Awesome.WidgetIndexRoute = Awesome.AuthenticationRoute.extend({
model: function (params) {
var receivedWidgetId = params.widgetId;
return { widgetName: "Hardcoded Widget", widgetId: receivedWidgetId };
}
});
Handlebars templates
<script type="text/x-handlebars" data-template-name="widgets">
<section class="span3 left-section">
<div class="btn-group-vertical btn-group-justified registration-actions-menu">
<button id="createNewWidget" class="btn btn-link">Create New Widget</button>
<button id="joinWidgetTeam" class="btn btn-link">Join Widget Team</button>
</div>
<div class="registered-widgets-menu">
<div class="btn-group-vertical">
{{#each widget in widgets}}
{{#link-to 'widget' widget class="btn btn-link"}}{{widget.widgetName}}{{/link-to}}
{{/each}}
</div>
</div>
</section>
<section class="span8">
{{outlet}}
</section>
</script>
<script type="text/x-handlebars" data-template-name="widget">
<div id="widgetOptions">
<!-- TODO: Change the anchors for handlebars link-to helpers. -->
<h1>{{widgetName}}</h1> <h5>{{widgetId}}</h5>
<ul id="widgetNavigation">
<li>Widget Info</li>
<li>Widget Configuration</li>
<li>Widget Operations</li>
</ul>
</div>
<div id="widgetContent">
<!-- TODO: Design some awesome widget content. -->
Some awesome widget content
</div>
</script>
The thing I do not understand is why when I click on any of the widget links from the "Widgets" template and the "Widget" template is displayed, even though I can see that the model hook on the WidgetIndexRoute gets executed, the displayed widgetName is not the hard coded one but the one that was selected on the list which leads me to believe that even though I would call a service to get additional data, this data would not be available for the template.
The other thing I do not understand is that when I debug the code the params.widgetId is undefined but when I try running the url with an arbitrary value, said value is displayed on the template but the widgetName is empty.
Any help is appreciated.
Out of completeness just in case this might have anything to do with it, both routes are extending this other one to support authentication:
Awesome.AuthenticationRoute = Ember.Route.extend({
beforeModel: function(transition){
if(!Awesome.get('loggedUser')){
this.redirectToLogin(transition);
}
},
redirectToLogin: function(transition) {
var loginController = this.controllerFor('awesome.login');
loginController.set('attemptedTransition', transition);
this.transitionTo('awesome.login');
}
});
Please check out this post at Ember Blog about new version of Ember (1.5):
ROUTES INHERIT MODEL
Ember routes and leaf resources (without nested routes) will inherit the parent route's model.
Take the following example:
App.Router.map(function(){
this.resource('post', function(){
this.route('edit');
});
});
App.PostRoute = Ember.Route.extend({
model: function(){
return {title: 'ZOMG', text: 'AWESOME'};
}
});
App.PostEditRoute = Ember.Route.extend({
model: function(){
return this.modelFor('post');
}
});
Now in 1.5, you do not have to define the model hook for PostEditRoute as the default implementation is to use the parent routes model.

Display crumble path with ember

I want to display a crumble path with Ember. How can I iterate through the current path?
In my opinion there are two approaches:
The ember-way
EDIT: see my answer below
I keep this question up-to-date with the current status of displaying breadcrumbs. You can browse through the revisions of this question to see the history.
There are a couple of goals here:
Listen on route change
Finding current route
displaying list of the current route
display working links to the steps in the route
Controller
App.ApplicationController = Ember.Controller.extend({
needs: ['breadcrumbs'],
currentPathDidChange: function() {
path = this.get('currentPath');
console.log('path changed to: ', path);
this.get('controllers.breadcrumbs').set('content',this.get('target.router.currentHandlerInfos'));
}.observes('currentPath')
});
App.BreadcrumbsController = Em.ArrayController.extend({});
Router
App.ApplicationRoute = Ember.Route.extend({
renderTemplate: function() {
this.render();
this.render('breadcrumbs', {
outlet: 'breadcrumbs',
into: 'application',
controller: this.controllerFor('breadcrumbs')
});
}
});
Template
{{! application template }}
<div class="clearfix" id="content">
{{outlet "breadcrumbs"}}
{{outlet}}
</div>
{{! breadcrumbs template }}
<ul class="breadcrumb">
{{#each link in content}}
<li>
<a {{bindAttr href="link.name"}}>{{link.name}}</a> <span class="divider">/</span>
</li>
{{/each}}
</ul>
The current problems to tackle are:
When I go to the URL: #/websites/8/pages/1 the output for the breadcrumbs is: (I removed all the script-tag placeholders
<ul class="breadcrumb">
<li>
application <span class="divider">/</span></li>
<li>
sites <span class="divider">/</span>
</li>
<li>
site <span class="divider">/</span>
</li>
<li>
pages <span class="divider">/</span>
</li>
<li>
page <span class="divider">/</span>
</li>
<li>
page.index <span class="divider">/</span>
</li>
</ul>
The URL's should be a valid route
The menu is now hardcoded with {{#linkTo}} to the routes, I tried to make that dynamic, like here but a transitionTo doesn't trigger the currentPath-observer
The other way
Most is the same as above, but there are a couple of differences. The breadcrumbs are made by looping over location.hash instead of getting it from the Router.
The ApplicationController becomes:
ApplicationController = Ember.Controller.extend({
needs: ['breadcrumbs'],
hashChangeOccured: function(context) {
var loc = context.split('/');
var path = [];
var prev;
loc.forEach(function(it) {
if (typeof prev === 'undefined') prev = it;
else prev += ('/'+it)
path.push(Em.Object.create({ href: prev, name: it }));
});
this.get('controllers.breadcrumbs').set('content',path)
}
});
ready : function() {
$(window).on('hashchange',function() {
Ember.Instrumentation.instrument("hash.changeOccured", location.hash);
});
$(window).trigger('hashchange');
}
We need to subscribe the custom handler in the ApplicationRoute
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller, model) {
Ember.Instrumentation.subscribe("hash.changeOccured", {
before: function(name, timestamp, payload) {
controller.send('hashChangeOccured', payload);
},
after: function() {}
});
}
});
So far the alternative approach is working best for me, but it's not a good way of doing it because when you configure your Router to use the history instead of location.hash this method won't work anymore.
Based on your current breadcrumb output I guess you have an error in your router.
The following command should return array with current breadcrumb:
App.get('Router.router.currentHandlerInfos');
Your router should be nested:
this.resource('page 1', function () {
this.resource('page 2');
});
You can use #linkTo instead of a tag in your breadcrumb, you will get active class for free.
I came up with a much simpler solution that I posted to the Ember discourse.
I found a (Ember-way) solution to display breadcrumbs. It is based on the router instead of my location.hash.
Infrastructure
First we need to make the infrastructure for the breadcrumbs before we add or remove items from the breadcrumbs array.
Menu
In my app.js I define a NavItem-object. This is a skeleton for all navigatable items. I use it to define my menu-items, but we are also going to use it for the breadcrumbs.
App.NavItem = Em.Object.extend({
displayText: '',
routeName: ''
});
// define toplevel menu-items
App.dashboardMenuItem = App.NavItem.create({
displayText: 'Dashboard',
routePath: 'dashboard',
routeName: 'dashboard'
});
App.sitesMenuItem = App.NavItem.create({
displayText: 'Websites',
routePath: 'sites.index',
routeName: 'sites'
});
Controllers
We need a BreadcrumbsController to keep the breadcrumbs in a central place
App.BreadcrumbsController = Em.ArrayController.extend({
content: []
});
My ApplicationController depends on the BreadcrumbsController
App.ApplicationController = Ember.Controller.extend({
needs: ['breadcrumbs']
});
The BreadcrumbsView is a subview of ApplicationView
Views
App.ApplicationView = Ember.View.extend({
BreadcrumbsView: Ember.View.extend({
templateName: 'breadcrumbs',
init: function() {
this._super();
this.set('controller', this.get('parentView.controller.controllers.breadcrumbs'));
},
gotoRoute: function(e) {
this.get('controller').transitionToRoute(e.routePath);
},
BreadcrumbItemView: Em.View.extend({
templateName:'breadcrumb-item',
tagName: 'li'
})
})
});
Templates
In my application-template I output the breadcrumbsview above the outlet
{{view view.BreadcrumbsView}}
{{outlet}}
I'm using Twitter Bootstrap so my markup for my breadcrumbs-template is
<ul class="breadcrumb">
{{#each item in controller.content}}
{{view view.BreadcrumbItemView itemBinding="item"}}
{{/each}}
</ul>
The breadcrumb-item-template
<a href="#" {{action gotoRoute item on="click" target="view.parentView"}}>
{{item.displayText}}
</a> <span class="divider">/</span>
Routing
We need to respond to the routing in our app to update the breadcrumbs.
When my SitesRoute (or any other toplevel route) is activated, we push the NavItem to the Breadcrumbs, but I also want to do that with the rest of my toplevel routes, so I first create a TopRoute
App.TopRoute = Em.Route.extend({
activate: function() {
this.controllerFor('menu').setActiveModule(this.get('routeName'));
var menuItem = app.menuItems.findProperty('routeName',this.get('routeName'));
this.controllerFor('breadcrumbs').get('content').pushObject(menuItem);
},
deactivate: function() {
var menuItem = app.menuItems.findProperty('routeName',this.get('routeName'));
this.controllerFor('breadcrumbs').get('content').removeObject(menuItem);
}
});
All my toproutes extend from this route, so the breadcrumbs are automatically updatet
App.SitesRoute = App.TopRoute.extend();
For deeper levels it works almost the same, all you have to do is use the activate and deactivate hooks to push/remove objects from the Breadcrumbs
App.SiteRoute = Em.Route.extend({
activate: function() {
var site = this.modelFor('site');
this.controllerFor('breadcrumbs').get('content').pushObject(app.NavItem.create({
displayText: site.get('name'),
routePath: 'site',
routeName: this.get('routeName')
}));
},
deactivate: function() {
var site = this.modelFor('site');
this.controllerFor('breadcrumbs').get('content').removeAt(1);
}
});

Ember.js Redirect to Template

I have a list of matches, and when I click one, I want to display the match. I know that I can do a Master-Detail style page, where when I click one, I can see the outlet somewhere on the same page, but that is not what I want.
I want it so that when I click on a link, it goes to an entirely new page for the match. I'm not really sure how to go about doing that.
Here is my route for #/matches (in coffeescript)
App.MatchesRoute = Ember.Route.extend(
model: ->
App.Match.find()
)
Here is my matches.handlebars
<div id="matches">
{{#each match in controller}}
{{#linkTo "match" match class="panel six columns"}}
Match between {{match.player.name}} and {{match.opponent.name}}
{{/linkTo}}
<br />
{{/each}}
</div>
// I know that if I have this outlet, it will render `match.handlebars`
// right here, but I want it to be it's own page.
// {{outlet}}
I've only been working with Ember for a few days, and all of the examples I've found use Master-Detail views.
Please let me know of any other information I can provide from my code.
<Edit date="March 11th 2013">
I've pushed a this repository in GitHub. This is a conceptual app that uses renderTemplate somewhat the way you're describing.
</Edit>
In your child route, use the renderTemplate hook in order to tell your application to render a specific template in a specific {{outlet}}. Example:
Source Fiddle
App.Router.map(function() {
this.resource('matches', { path: 'matches' }, function() {
this.route('match', { path: 'match/:match_id' });
});
});
App.MatchesRoute = Em.Route.extend({
model: function() {
return App.Match.find();
},
setupController: function(controller, model) {
model = App.Match.find();
controller.set('content', model);
},
renderTemplate: function() {
this.render('matches', {
into: 'application'
})
}
});
App.MatchesMatchRoute = Em.Route.extend({
model: function(params) {
return App.Match.find(params.match_id);
},
setupController: function(controller, model) {
controller.set('content', model);
},
renderTemplate: function() {
this.render('match', {
into: 'application'
})
}
});
This MatchesMatchRoute is setup to render its template (matches/match) into the application template. And since there is only one {{outelet}} this template (see handlebars below), we don't have to specify anything:
<script type="text/x-handlebars">
<h1>App</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="matches">
<h2>Matches</h2>
<ul>
{{#each match in controller}}
<li>
{{#linkTo matches.match match}}
{{match.title}}
{{/linkTo}}
</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="match">
<h3>{{title}}</h3>
<p>{{description}}</p>
</script>
If you have a scenario with multiple outlets, you have to hame them, like in the handlebars below:
<script type="text/x-handlebars">
<h1>App</h1>
{{outlet main}}<br />
{{outlet nested}}
</script>
Then your routes will have to specify the outlet as well. Example:
Source Fiddle
[...route code...]
renderTemplate: function() {
this.render('content', {
into: 'application',
outlet: 'main'
});
this.render('buttons', {
into: 'application',
outlet: 'nested'
});
}
[...route code...]
You can cause a template to render into a different template's outlet by using the renderTemplate hook when defining the route (see the guide: http://emberjs.com/guides/routing/rendering-a-template/)
For your example it might look like this:
App.MatchRoute = Ember.Route.extend({
renderTemplate: function() {
this.render({ into: 'matches' });
}
});