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.
Related
I'm attempting to set the belongsTo relationship using a dropdown.
So I have my Books model:
import DS from 'ember-data';
export default DS.Model.extend({
// Relationships
author: DS.belongsTo('author'),
name: DS.attr()
});
And my Author model:
import DS from 'ember-data';
export default DS.Model.extend({
// Relationships
author: DS.hasMany('books'),
name: DS.attr()
});
My Books/new route:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
book: this.store.createRecord('book'),
authors: this.store.findAll('author')
})
},
actions: {
saveBook(newBook) {
newBook.book.save().then(() => this.transitionTo('book'));
},
willTransition() {
this.controller.get('book').rollbackAttributes();
}
}
});
And my Books/new template:
<label >Book Name</label>
{{input type="text" value=model.name placeholder="Book Name"}}
<label>Author</label>
<select>
{{#each model.authors as |author|}}
<option value="{{author.id}}">
{{author.name}}
</option>
{{/each}}
</select>
<button type="submit"{{action 'saveBook' model}}>Add Book</button>
If I remove the select element and just save the name of the book it works fine, but with it I get this: (where id is an auto-generated ID)
Error: Some errors were encountered while saving app#model:book id
at reportError (firebase.js:425)
at firebase.js:445
at tryCatch (ember.debug.js:58165)
at invokeCallback (ember.debug.js:58177)
at publish (ember.debug.js:58148)
at publishRejection (ember.debug.js:58091)
at ember.debug.js:37633
at invoke (ember.debug.js:339)
at Queue.flush (ember.debug.js:407)
at DeferredActionQueues.flush (ember.debug.js:531)
I think I need to do something like getting the author object and setting book.author to that, but I can't find a clear explanation of how. Especially as I can't even work out how to get the data from that select menu in the route!
I feel like I'm missing something pretty simple here, anyone have any insight?
I would suggest moving this functionality to your controller.js where it belongs. Why is your relation to books in AuthorModel called author instead of books?
I would suggest rewriting your action (in the controller) to something like this:
saveBook(newBook) {
newBook.set('author', this.get('selectedAuthor') // or just the call below if you go with the alternative below
newBook.save().then(() => this.transitionTo('book'));
},
Now the problem persists, that you don't have a binding to your selected author. I would suggest using something like ember-power-select to bind your selected author to a controller property.
Then you would do this in your template:
{{#power-select
placeholder="Please select Author"
onchange=(action "authorSelectionChanged")
options=model.authors
as |author|}}
{{author.name}}
{{/power-select}}
And in your actions within your controller:
authorSelectionChanged(author) {
this.get('model.book').set('author', author);
// or the following if you go with the alternative above
this.set('selectedAuthor', author);
}
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.
Using Meteor and Iron Router, I've created dynamic page paths that use multiple parameters. However, if I attempt to access nested/child properties in my path, the route breaks. These posts were helpful but did not address child-properties:
Iron-router nested routes with multiple parameters
meteor iron-router nested routes
Iron Router
this.route('location',{
path: '/properties/:foo/:_id',
waitOn: function(){
return Meteor.subscribe('properties', this.params._id);
},
action: function(){
this.render('propertyPage', {
data: function(){
return Properties.findOne(this.params._id);
}
});
}
});
Markup (Works)
Click Me
When attempting to reference a nested property in the markup, it breaks:
Markup (NOT working)
Click Me
I also tried it inside of the javascript, with no luck:
path: '/properties/:foo.nestedChild/:_id',
Is there a way to reference a nested property without breaking Iron Router?
- - - Edit - - -
For a more practical example:
// data context from route action (Properties.findOne(this.params._id))
property = {
_id: "3cu7B8b6K3EzCgYnQ"
address: {
city: 'Houston',
state: 'TX',
zip: 77006,
lat: null,
lng: null
},
images: ['img1.png', 'img2.png', 'img3.png'],
schools: [
{ grade:'elementary', name:'Haude', rating:4 },
{ grade:'middle', name:'Strauke', rating:5 },
{ grade:'high', name:'Klein', rating:3 },
]
}
I'm trying to build out a url schema like this:
path: '/properties/:address.city/:address.state/:address.zip/:_id'
or in the example's case:
"/properties/Houston/TX/77006/3cu7B8b6K3EzCgYnQ"
In your route, you need to fetch :foo from the params object if you want to use it:
var foo = this.params.foo;
It's little too late, but somebody might benefit anyway. I solved it the following way:
Defining a nested path (BTW Defining paths this way is better for SEO)
Content
Router
this.route('playlistItem', {
path: '/user/:owner/playlist/:playlist',
onBeforeAction: function() {
// You can get your params
var ownerId = this.params.owner
var playlistId = this.params.playlist
// execute some code
},
});
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);
},
...
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>