Emberjs: Dynamic template nesting and routing - javascript

I've been trying to figure this out for most of today and it's driving me insane, because I think i'm almost there, but just can't figure the last part out...
I have a route, called Map, which renders a sidebar, and within it has a named outlet for sidebar content:
map.hbs:
<div id="map-container">
{{render sidebar}}
<div id="map-canvas">
</div>
</div>
...
sidebar.hbs:
<div id="content-menu">
{{outlet sidebar-content}}
</div>
Each menu item in my sidebar has a custom action called loadModule, which performs a render of a named view into the sidebar-content outlet (using {{action 'loadModule' 'sidebar.module'}}):
var MapRoute = App.AuthenticatedRoute.extend({
actions: {
loadModule: function(module) {
//load a valid view template into the view
this.render(module,
{
into: 'sidebar',
outlet: 'sidebar-content'
});
}
}
});
module.exports = MapRoute;
Any action within the controller for that view works fine, I can trigger them from buttons etc, or by calling them in a didInsertElement in the SidebarModuleViews.
My issue is that I can't define a model for these views, so if I try and get data from my API in any of their Controllers, it won't render that data out to the templates.
I tried to use link-to, but I couldn't make the template append to the current viewport, rather than refreshing the entire page, which defeats the point of having a sidebar (I don't want the route to change)
var SidebarUserController = App.ApplicationController.extend({
actions: {
doSomething: function() {
alert('SOMETHING');
},
fetchUserProfile: function() {
//do something
var mod = this.store.find('profile', App.Session.get('uid'));
}
}
});
I can trigger either of those actions from the rendered template once it's rendered, however, although my store updates with the record, the handlebars helpers in the sidebar/user.hbs do not populate with the model data.
Here is my model:
var Profile = DS.Model.extend({
uid: DS.attr('string'),
firstName: DS.attr('string'),
lastName: DS.attr('string'),
gender: DS.attr('string'),
DOB: DS.attr('date'),
email: DS.attr('string')
});
module.exports = Profile;
and here is my sidebar/user.hbs:
<div class="container">
<button {{action 'doSomething'}}>Do A Thing</button>
<h1>{{firstName}} {{lastName}}</h1>
<h4>{{id}}</h4>
{{#if isAuthenticated}}
<a href="#" {{action 'logout'}}>Logout</a>
{{/if}}
</div>
In that template, the firstName, lastName and id fields do not populate, even though i'm pulling the data from the API and successfully storing it.
Additionally, if it helps, my router.map for sidebar/user looks like this:
this.resource('sidebar', function() {
this.route('user');
});
I believe that the fundamental issue here is that I can't work out how to set the model for the controller without triggering the route. Am I going about this wrong?

Ok so i've worked this out for my particular instance. It may not be the best way of doing it, but it's what I need:
In my MapRoute, I setup the model and controller for my additional sidebar menus in the setupController function. Doing this allows me to load critical data (such as user profile etc), on page load, and I can still retain the render function for each sidebar module in the Route, which will allow the intial data to load, and still allow me to update the model data for each sidebar module controller on subsequent functions:
map_route.js:
actions: {
loadModule: function(module) {
this.render(module, {into: 'sidebar', outlet: 'sidebar-content'});
}
},
setupController: function(controller, profile) {
var model = this.store.find('profile', App.Session.get('uid'));
var controller = this.controllerFor('sidebar.user');
controller.set('content', model);
},
...

Related

Is it possible to change properties in Ember routes?

What is the correct way to set properties on an Ember route? I have a title message that I would like to be displayed on page load and then I would like to change that title as the user interacts with the page.
import Ember from 'ember';
export default Ember.Route.extend({
title: "Welcome!",
model(thing1) {
return Ember.RSVP.hash({
thing1: this.store.findRecord('thing1', thing1.thing_id),
thing2: this.store.findAll('thing2'),
thing3: this.store.findAll('thing3')
});
},
actions: {
changeTitle() {
this.set("title", "I changed!")
}
}
});
In my template I load another component and pass in the value for title
{{title-tile title=title}}
And in my component title, I print out (theoretically) the title:
{{title}}
I have also tried removing the intermediary step and just printing out the title directly but that doesn't work.
In the console I have no errors, and I am otherwise able to print out the model data from the RSVP hash. There is no (obvious) documentation on this. All documentation focuses on printing out model properties.
What am I missing?
Edit
It appears to me that routes are just meant to handle models and that components are supposed to take care of everything else.
I've actually tried explicitly calling the title to be set on route load and it still doesn't work.
...
init() {
title: "Welcome!"
}
...
You could use a computed property in hash passed to template:
export default Ember.Route.extend({
title: 'Welcome!',
model(thing1) {
return Ember.RSVP.hash({
// ... omitted for brevity
title: Ember.computed(() => this.get('title')) // computed property
});
},
actions: {
changeTitle() {
this.set("title", "I changed!")
this.refresh(); // it is required to refresh model
}
}
});
Working demo.
Full code behind demo.

Ember JS - Unable to get action in each to change class of div modal

I'm having some major issues trying to do something that should be simple, which means that I'm obviously missing something simple.
I'm pulling articles via an API, and instead of having someone click the link to an article, I want a modal to popup with the content of the article on click, so that there wouldn't be any need for page changing or having to load the api multiple times. Naturally, the easiest way I thought of solving this was to use an action to set a property from false to true, and use that property on a bind-attr class to show the modal. However, no matter what I seem to do, I can't ever get the property value initially set or changed in the action, and logging the variable to check and see what it returns results in an error saying the variable is not defined. I would really like to see what the problem is here so I can also use this solution on my list/grid class toggling functions, because right now I resorted to using jQuery for that due to having similar problems.
Below is the code used for my articles listing and the action handling. The action does fire, which I confirmed with an alert(), but no luck on the property. Thanks for the help!
HTML
<script type="text/x-handlebars" data-template-name="articles">
<button {{action 'listStyle'}}>List</button>
<button {{action 'gridStyle'}}>Grid</button>
<section id="articles-category" class="grid">
<div class="row">
{{#each}}
<div class="col-xs-6 col-md-3 article-wrapper" {{action "openArticle"}}>
<div class="article">
<img src="http://placehold.it/300x270">
<div class="content">
<h3>{{title}}</h3>
</div>
</div>
</div>
<div {{bind-attr class=":article-modal isArticleActive:enabled" target="controller"}}>
<div class="article-close">X</div>
<div class="article-back">Back to Articles</div>
<div class="article-wrapper">
<div class="container">
<h2 class="article">{{title}}</h2>
{{{unescape html_body}}}
</div>
</div>
</div>
{{/each}}
</div>
</section>
</script>
app.js
// MAIN APP CONFIGURATION
App = Ember.Application.create();
DS.RESTAdapter.reopen({
host: 'http://mihair.herokuapp.com',
namespace: 'api'
});
Ember.Handlebars.helper('unescape', function(value) {
newValue = value.replace(/(?:\r\n|\r|\n)/g, '<br />');
return new Ember.Handlebars.SafeString(value);
});
// ROUTER
App.Router.map(function(){
this.resource('articles', {path: ':id'});
this.resource('article', {path: 'articles/:id_or_slug'});
});
App.ArticlesRoute = Ember.Route.extend({
model: function() {
return this.store.find('articles');
}
});
App.ArticleRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('articles', params.id_or_slug)
}
});
// MODELS
App.Response = DS.Model.extend({
success: DS.attr('boolean', {defaultValue: false}),
message: DS.attr('string')
});
App.Articles = DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
html_body: DS.attr('string')
});
App.Article = DS.Model.extend({
id: DS.attr('number'),
title: DS.attr('string'),
description: DS.attr('string'),
html_body: DS.attr('string')
});
// CONTROLLERS
App.ArticlesController = Ember.ArrayController.extend({
isArticleActive: false,
actions: {
listStyle: function() {
$('#articles-category').removeClass('grid').addClass('list');
},
gridStyle: function(){
$('#articles-category').removeClass('list').addClass('grid');
},
openArticle: function() {
this.set('isArticleActive', true);
}
}
});
isArticleActive lives on the ArticlesController (which is the collection), but when you use {{#each}} you change the context from the controller to an individual article. You'll want to work with an itemController in order to create properties for each item individual model instead of for the collection as a whole.
Ember.js Controller Does not work

Ember model is gone when I use the renderTemplate hook

I have a single template - editPerson.hbs
<form role="form">
FirstName: {{input type="text" value=model.firstName }} <br/>
LastName: {{input type="text" value=model.lastName }}
</form>
I want to render this template when the user wants to edit an existing person or create a new person. So, I set up routes:
App.Router.map(function(){
this.route("createPerson", { path: "/person/new" });
this.route("editPerson", { path: "/person/:id"});
// other routes not show for brevity
});
So, I define two routes - one for create and one for edit:
App.CreatePersonRoute = Ember.Route.extend({
renderTemplate: function(){
this.render("editPerson", { controller: "editPerson" });
},
model: function(){
return {firstName: "John", lastName: "Smith" };
}
});
App.EditPersonRoute = Ember.Route.extend({
model: function(id){
return {firstName: "John Existing", lastName: "Smith Existing" };
}
});
So, I hard-code the models. I'm concerned about the createPerson route. I'm telling it to render the editPersonTemplate and to use the editPerson controller (which I don't show because I don't think it matters - but I made one, though.)
When I use renderTemplate, I lose the model John Smith, which in turn, won't display on the editTemplate on the web page. Why?
I "fixed" this by creating a separate and identical (to editPerson.hbs) createPerson.hbs, and removing the renderTemplate hook in the CreatePerson. It works as expected, but I find it somewhat troubling to have a separate and identical template for the edit and create cases.
I looked everywhere for how to properly do this, and I found no answers.

Ember.js: showing multiple database-backed resources/models in the same page/route?

I'm writing a small test app using Ember, in the form of a budget manager. I have a Budget object, which contains general properties like the monthly limit and a description. I also have a set of Expense objects, which contain a name, the amount spent, etc. Both are retrieved from a server using Ember Data's REST adapter.
HTML:
<body>
<script type="text/x-handlebars" data-template-name="budget">
<h2>{{name}} (€ {{amount}})</h2>
</script>
<script type="text/x-handlebars" data-template-name="expenses">
<ul id="expense-list">
{{#each model}}
{{render "expense" this}}
{{/each}}
</ul>
</script>
<!-- expense template -->
<script type="text/x-handlebars" id="expense">
<li>
<label>{{description}}</label>
<label class="subtle">{{formatDate time}}</label>
<label class="amount">{{amount}}</label>
</li>
</script>
</body>
</html>
JavaScript:
window.App = Ember.Application.create();
App.ApplicationAdapter = DS.RESTAdapter.extend({
host: 'http://localhost:5000',
namespace: 'outthepocket/api'
});
// Model
App.Expense = DS.Model.extend({
amount: DS.attr('number'),
description: DS.attr('string'),
time: DS.attr('date')
});
App.Budget = DS.Model.extend({
name: DS.attr('string'),
amount: DS.attr('number')
});
// Routes
App.Router.map( function() {
this.resource('budget');
this.resource('expenses');
});
App.ExpensesRoute = Ember.Route.extend({
model: function()
{
return this.store.find('expense');
}
});
App.BudgetRoute = Ember.Route.extend({
model: function()
{
return this.store.find('budget', 1);
}
});
Following the architecture I see in all the Ember tutorials, there is an ExpensesRoute with the list of expenses as its model, and a BudgetRoute with the selected budget as its model. This works great as long as I go through the proper URL to see each resource:
myapp.html#budget renders the budget template with data from the server.
myapp.html#expenses renders the expenses template with data from the server.
The problem I'm having now is that I want to display both templates, with their data, on one page (the index page). I've tried two solutions for this:
Solution 1: Have separate routes and templates and call {{render budget}} and {{render expenses}} in the main application template. This renders both templates, but without any data.
Solution 2: Have just an IndexRoute and return both budget and expenses from its model property, rendering them into the index template. This more or less works, but seems counter to Ember's otherwise nice separation of different resources, routes and controllers.
Any thoughts? I've been through five or six tutorials and Ember's official guide, but none of those have made clear how to assemble a one-page web app with multiple templates backed by multiple resources without having to link to different pages/routes.
You can use Ember.RSVP.hash to load more than one model, in a single object:
App.IndexRoute = Ember.Route.extend({
model: function()
{
return Ember.RSVP.hash({
expenses: this.store.find('expense'),
budget: this.store.find('budget', 1)
})
}
});
And in the template you can access each resolved promise by the key:
{{expenses}} will return the result from this.store.find('expense') promise and {{budget}} the result from this.store.find('budget', 1) promise.
So in your index template you will able to do:
<script type="text/x-handlebars" id="index">
{{render "expenses" expenses}}
{{render "budget" budget}}
</script>

Individual view from an item in an ArrayController

I have an ember.js model and controller setup like so:
//model
App.Order = Ember.Object.extend({
content: null,
create: function(data) {
this.set('content', data);
return this._super();
}
});
//orders controller
App.ordersController = Ember.ArrayController.create({
content: [],
init: function() {
var self = this;
var orders = [];
$.getJSON('js/data.json', function(data) {
data.forEach(function(item) {
var order = App.Order.create(item);
orders.push(order);
});
self.set('content', orders);
});
},
selectItem: function(data) {
alert(data);
}
});
With the following view:
{{#each App.ordersController}}
<div {{action selectItem target="App.ordersController"}}>{{order_number}}</div>
{{/each}}
Which prints out a list of orders with a click action that alerts the corresponding item. This works fine.
What I want to do is show a clicked item in a separate view, eventually with the goal of creating a floating dialogue with orders details shown. I'm new to ember and not sure how this should be implemented. I have a function selectItem which alert's a clicked order but I need to link this to a separate view and print the order details.
Should I store the selected item in a separate controller with a corresponding view and update this when selectItem is clicked? Or could I update a sperate view from the ordersController? Any advice is much appreciated.
When you use the router ember does the instantiation of your class for you. By specifing the "orders" route is looks for a template called orders and a controller called OrdersController if it can't find one it'll generate one for you. (I've omitted the controller for clearity). To load your model from a json source you could have a look at ember-data.
here is a jsfiddle so you can fiddle with it a bit.
You should definitely have a look here these are guides for ember that really help you on the way. The documentation is getting better and better. :)
JS:
window.App = App = Em.Application.create();
//model
App.Order = Ember.Object.extend({
order_number: null,
});
//we create 2 routes one for all the order and one per specific order
App.Router.map(function(){
this.resource('orders', { path: "/" });
this.resource("order", { path: "/:order_id" });
});
//the route supplies the model and handles the event to transition to a new route.
App.OrdersRoute = Em.Route.extend({
events: {
selectItem: function (orderId) {
//select the order by the "orderId" you want as a model for your new view.
this.transitionTo("order", order);
}
},
model: function(){
return content; //an array containing the orders;
}
});
//supplies the model for the "order" route by selecting one acording to the params;
App.OrderRoute = Em.Route.extend({
model: function(params){
return order; //select an object from the array according to the params
},
});
HBS:
<script type="text/x-handlebars" data-template-name="orders">
{{#each controller}}
<!-- this calls the event handler "selectItem" on the ordersroute -->
<div {{action "selectItem" order_number}}>{{order_number}}</div>
{{/each}}
<!-- this is handled by "App.OrderRoute" -->
<a href="#/3"/>with a direct link</a>
</script>
<script type="text/x-handlebars" data-template-name="order">
{{content.order_number}}
{{#linkTo "orders"}}Back to orders{{/linkTo}}
</script>

Categories

Resources