I'm using Ember 1.2, Handlebar 1.12, and EmberModel (not EmberData).
In my application, I have the common parent/child relationship in my route map, with a simple model similar to that shown below.
App.Router.map(function () {
this.resource('strats', {path: "/"}, function() {
this.route('strat', {path: "/strat/:strat_id"});
});
});
App.Item = Ember.Model.extend({
id : Ember.attr(),
itemName : Ember.attr()
});
App.Strat = Ember.Model.extend ({
id : Ember.attr(),
stratName : Ember.attr(),
stratDetail : Ember.attr(),
items : Ember.hasMany ('App.Item', {key: 'items', embedded: true})
});
As in many of the examples, I display strat.id on the left side (using the Handlebar #each helper), and details (i.e., strat.id, strat.stratName and strat.stratDetail) for the selected 'strat' on the right side. To add a new 'strat', I use a button connected to the action function "newStrat," which is shown below.
When adding a new child, everything displays correctly when there's already another child present (e.g., everything works fine when adding the 2nd and subsequent child). But if I'm adding the first child, strat.Id doesn't get displayed on the left side, but strat.id, strat.stratName and strat.stratDetail do get displayed on the right side. If I then call this.get('model').save() and hit the browser's refresh button, display is as expected on both left and right side (as a result of a request for data sent to the server, and the server replying with all saved data).
What's causing this behaviour? Is there anyway to fix it?
When there's no data, in reply to findAll(), my server returns {"strats":" "}. Does what I return for the no-data scenario have anything to do the problem?
Controller
App.StratsController = Ember.ArrayController.extend({
actions: {
newStrat: function() {
var nwStrat =
{
id: "newId",
stratName: "untitled",
stratDetail: "someDetail"
};
var newStrat = App.Strat.create (nwStrat);
this.get('model').pushObject(newStrat);
this.transitionToRoute ('strats.strat', newStrat);
}
}});
Code Snippet from template that displays left side
<div class=\"list-group\" style=\"margin-top: 10px\">
{{#each controller}}
{{#linkTo \"strats.strat\" this class=\"list-group-item\"}}
{{id}}
{{/linkTo}}
{{/each}}
</div>
Related
I'm trying to create a "note" record with createRecord. When I pass it into my action, it properly creates the record, but only creates the "body" attribute I pass in, and not the id or timestamps. It does, however, create these attributes after I refresh. My problem is that I want it to create these as soon as I click my "create" button, so I can sort in descending order, and be able to delete the note without having to refresh each time.
My controller:
import Ember from "ember";
export default Ember.ArrayController.extend({
actions: {
newNote: function() {
var body = this.get('noteCopy');
var note = this.store.createRecord('note', { body: body });
this.set('noteCopy', '');
note.save();
},
deleteNote: function(id) {
this.store.find('note', id).then(function(note) {
note.deleteRecord();
note.save();
});
}
}
});
My template:
{{textarea placeholder="Add a note!" value=noteCopy class='newNoteArea'
autofocus=true}}<br>
<button class='createNoteButton'{{action 'newNote'}} style='font-size:2em'>Save Note</button><br><br>
<br>
{{#each note in model}}
<div class="noteShow">
{{note.body}}<br>
<img src="assets/erase.gif" alt="" class='deleteNoteButton'{{action 'deleteNote' note.id}} style='width:4em'/>
</div>
{{/each}}
{{outlet}}
My server does the sorting properly once the note creates the timestamps attributes... But since I get
id: null, body: "some body", created_at: undefined, updated_at: undefined
every time I create a new note, it doesn't do anything it's supposed to, until I refresh. It occurred to me that this may be a problem with promises, but after trying to implement some .success() and .then() lines, I have a feeling this isn't the case.
Forgive me if this is a newbie question, but I'm still quite new to Ember. Any input is appreciated. Thanks!
The id is given to you by the API server you POST to. You can retrieve the id after the creation was successful.
var newJob = jobController.store.createRecord('job', {
status: 'requested',
message: '',
});
console.log(newJob);
newJob.save().then(() => {
console.log('Job ID ', newJob.id, ' created.');
}.bind(jobController), (err) => {
console.log(err.message);
}.bind(jobController)
);
ive searched for this and have not found an answer. I have 2 routes: "Index", which creates/updates an expense list, and "Charts", which charts the values of the expenses.
In order to handle the expense charts, I have the following function:
getData: function() {
var expenses = this.store.all('expense');
expenses.update();
var retarr = Ember.A();
expenses.forEach(function(expense) {
retarr.pushObject({
label: expense.get('name'),
value: expense.get('amount'),
group: 'expense'
});
});
return retarr;
}.property()
This is then passed to the ember-charts component in the Charts route.
<script type="text/x-handlebars" id='charts'>
<div class="chart-container">
{{horizontal-bar-chart data=getData}}
</div>
However, if I create/delete an expense in the "Index" route and hten transition to the "Charts" route, the DS.RecordArray doesn't update despite calling the "update()" function. As such, the chart does not reflect the created/deleted changes until the page is refreshed.
How do I fix this so the RecordArray auto updates along with the chart? I've broken my head for over two days trying different things. Thanks!
Your property getData should be bound to anything, if this something is an array you should use #each. For example as you can see here:
remaining: function() {
var todos = this.get('todos');
return todos.filterBy('isDone', false).get('length');
}.property('todos.#each.isDone')
I suggest you another approch, let's modify your model:
App.Chart = DS.Model.extend({
// fieds here...
label: function() {
return this.get("name");
}.property("name"),
value: function() {
return this.get("amount");
}.property("amount"),
group: function() {
return "expense";
}.property(),
)};
In your route set myCharts property:
App.ChartRoute = Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller, model);
var charts = this.store.find("chart");
controller.set("myCharts", charts);
}
});
Then you could use your horizontal chart:
<div class="chart-container">
{{horizontal-bar-chart data=myCharts}}
</div>
Note: I didn't tested this code but it should work
I am currently learning Ember and I am making a simple app, but I have encountered a strange problem. I have a route setup and it only pulls the data when I reload the page. Here is my code:
// Start Ember application
App = Ember.Application.create({
LOG_TRANSITIONS: true
});
// Router paths
App.Router.map(function () {
// Homepage
this.resource('index', { path: '/' });
// Book view
this.resource('book', { path: '/book/:id/:version' });
});
// Homepage route
App.IndexRoute = Ember.Route.extend({
model: function () {
// Get all the books
return Ember.$.getJSON('/books');
}
});
// Single book view
App.BookRoute = Ember.Route.extend({
model: function (params) {
// Get a single book
return Ember.$.getJSON('/book?id=' + params.id + '&version=' + params.version);
}
});
When I go to /#/book/1/1 by clicking from the homepage, the page is blank. When I just refresh the page when I'm on there it loads the data and everything works.
Why is this? What can I do to make it work when the user clicks from the homepage?
Thank you everyone for your suggestions. I figured it out with this code here:
App.BookRoute = Ember.Route.extend({
setupController: function (controller,model) {
// Get a single book
Ember.$.getJSON('/book?id=' + model.id + '&version=' + model.version,
function (data) {
controller.set('model',data);
});
}
});
I used setupController instead of model.
you should pass a model in link-to. paste your template code to check how you are creating links. check this for more info http://emberjs.com/guides/templates/links/
If you use link-to helper you must not set context model in this one... you simply need to set an id,
{{#each book in books}}
{{#link-to "book" book.id}}{{book.name}}{{/link-to}}
{{/each}}
and the model of the next route will be request.
The Ember link-to helper doesn't execute the model callback. I've dealt with a situation very much like this recently, and here's how I solved it:
// In the index template
{{#each book in model}}
{{#link-to 'book' book}}{{book.name}}{{/link-to}}
{{/each}}
When you click on the link, you'll get a blank page like you are now, because the BookRoute model callback isn't fired. However, the BookController#init function will be fired. You can use this to go and grab the rest of the details about the book there.
App.BookController = Ember.Controller.extend({
init: function() {
this._super();
// Replace doesNotHaveAllInfo with your check
if (doesNotHaveAllInfo) {
this.set('model', Ember.$.getJSON('/book?id=' + this.get('model.id') + '&version=' + this.get('model.version')));
}
},
});
This way, if the book is loaded through link-to, the new details will be fetched. Note that this will require that the books loaded on the index route contain at least id and version.
Alternatively, if you refresh the page, the model callback will occur, and your doesNotHaveAllInfo check will return false. Whether you click on a link-to or refresh the page, you should see your data.
I'd also recommend abstracting the Ember.$.getJSON code into a reusable method, perhaps on the model, like this:
App.Book.reopenClass({
find: function(id, version) {
return Ember.$.getJSON('/book?id=' + id + '&version=' + version);
}
});
You can then use App.Book.find() to return your model in both your route and your BookController.
I currently have my routes defined like this:
App.Router.map(function() {
this.resource('players', { path: ':page_id' }, function() {
this.resource('player', { path: ':player_id' });
});
});
The idea is that I have a list of player names on the left. The player names displayed depend on the page_id. On the right, I display a single player and all its information based on the player_id. The thing is, both are independent, meaning that I could be on the third player page, while displaying the first player in the list, or no player at all.
What I keep trying to do is something like this, which is a method in the PlayersController that gets called when I click to go to the next player page:
doTransition: function() {
var players = App.Player.findAllForPage(this.playersPerPage, this.currentOffset);
players.reopen({
id: this.currentOffset
});
var playerController = this.get('controllers.player');
var currentPlayer = playerController.getWithDefault('content');
if (currentPlayer) {
this.transitionToRoute('player', players, currentPlayer);
} else {
this.transitionToRoute('players', players);
}
}
What I'm trying to do: When I click to go to the next player page, transition to the PlayersRoute if there is no player currently being displayed, otherwise transition to the PlayerRoute so that the player is still displayed when the transitioning is done.
The problem: sometimes the currentPlayer variable is not always null, even if no player is currently being displayed. Is there a way to get around this, perhaps by getting the current route from somewhere?
Given that you say the two sections (list of players based on page_id, and player information based on player_id) are completely independent, it seems to me like you wouldn't nest the routes, and instead, have two named outlets (call them left and right, or page and player, etc) that you selectively render into.
Router:
App.Router.map(function() {
this.resource('page', { path: ':page_id' });
this.resource('player', { path: ':player_id' });
});
application.hbs:
{{outlet page}}
{{outlet player}}
And then you can override your renderTemplate for your page and player routes to render into the appropriate template. To clarify, page route would display what you currently have as the players route (it's dependant on the page_id, but the page has many players, so the route displays the players based on the page), and player route would display the player information based on the player_id.
(As a side note, I don't think you can nest resources the way you do right now with putting resource player under resource players -- I think only routes can be nested.)
EDIT: Using single route with multiple dynamic segments
I think your suggestion could work. From the linked example it seems like you need to create the "parent" resources (not nested routes, but having more general paths, like /page/ and /page/:page_id/player/:player_id) anyway. You can then set up your models individually via the model in the appropriate route, and just provide a serialize hook for the double dynamic segment route:
serialize: function(model) {
return {
page_id : this.modelFor('page').get('id')
player_id : this.modelFor('player').get('id')
};
}
Note we're not relying on the model object passed in because you've said that the page and player panels can be completely independent, so we use modelFor instead.
I think you can also handle your logic about default page to render / default player to render if none are suggested here via the redirect hook.
Finally, you would override renderTemplate in your PagePlayer route to actually do the rendering:
renderTemplate: function(model, controller) {
this.render("page", { into: "page" });
this.render("player", { into: "player"});
}
I think you have to be careful to NOT render the templates in the more general routes because if you if you move from /page/1/player/2 to /page/1/player/3, the page route is NOT re-entered.
While Sherwin's answer gave me a good idea of where I was going, I just wanted to put a complete example and give a general idea of what I ended up implementing. This could be of help for future reference.
I'm going to make it simple by having the models be a simple int, that way we have a direct translation from url to model and vice versa.
Templates:
<script type="text/x-handlebars">
{{outlet a}}
{{outlet b}}
</script>
<script type="text/x-handlebars" id="a">
{{model}}
</script>
<script type="text/x-handlebars" id="b">
{{model}}
</script>
Application:
App = Ember.Application.create();
App.Router.map(function() {
// This route has 2 dynamic segments
this.resource("ab", { path: "/:a/:b" });
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
// At the entry point, encapsulate the 2 models in the context object,
// and transition to the route with dual dynamic segments
this.transitionTo('ab', {a: 3, b:4});
}
});
App.AbRoute = Ember.Route.extend({
model: function(params) {
// The model is {a, b} directly
return params;
},
renderTemplate: function(){
// Render in the named outlet using the right controller
this.render('a', {into: 'application', outlet: 'a', controller: 'a'});
this.render('b', {into: 'application', outlet: 'b', controller: 'b'});
},
serialize: function(model) {
return {
a: model.a,
b: model.b
};
},
setupController: function(controller, model) {
// Setup each controller with its own model
this.controllerFor('a').set('model', model.a);
this.controllerFor('b').set('model', model.b);
}
});
Additional note:
It would've been possible to have a single 'ab' template rendering {{model.a}} and {{model.b}} from the AbController, but I thought having separate controllers and templates was cleaner, and that it enabled reusability. Additionally, one of the controllers could've been an ArrayController and it would've work perfectly fine.
JS Bin of the example
I'm working with
Ember RC3
Ember Data Revision 12
Handlebars RC3
I have Ember Data sideloading relationships on many of my models, so that I can template the sideloaded relationships like so:
// Models
App.Client = DS.Model.extend({
company: DS.attr('string'),
accountNumber: DS.attr('string'),
startDate: DS.attr('mysqlDate'),
// Relationships
campaigns: DS.hasMany('App.Campaign'),
users: DS.hasMany('App.User'),
phones: DS.hasMany('App.Phone'),
addresses: DS.hasMany('App.Address')
});
App.User = DS.Model.extend({
email: DS.attr('string'),
password: DS.attr('string'),
// Relationships
userType: DS.belongsTo('App.UserType'),
role: DS.belongsTo('App.Role'),
clients: DS.hasMany('App.Client'),
phones: DS.hasMany('App.Phone'),
addresses: DS.hasMany('App.Address')
});
<!-- template -->
<script type="text/x-handlebars" data-template-name="user/index">
<h2>{{email}}</h2>
<h5>Clients</h5>
<ul>
{{#each client in model.clients}}
<li>{{client.company}}</li>
{{/each}}
</ul>
</script>
This works wonderfully...except for every 1 in 10 reloads or so. Every once in a while the sideloaded relationship (in this case the hasMany relationship model.clients) DOES NOT render to the template while all other model properties (not relationships) DO render to the template. What's weird is that it only does this every once in a while.
I'm not quite sure yet how I can set up a js fiddle for this problem, so I wanted to ask:
Where in the call stack could I set a break point to see what properties will actually get rendered?
I'm using {{debugger}} in the template in question, I'm just not sure where the best place would be to inspect the application state in the call stack.
So, my problem was two-fold. First Problem: Here's my router map and routes:
App.Router.map(function() {
this.resource('users', function() {
this.route('create');
this.resource('user', { path: ':user_id' }, function() {
this.route('edit');
this.route('delete');
});
});
});
App.UsersRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
// Default for this route
App.UserRoute = Ember.Route.extend({
model: function(params) {
return App.User.find(params.user_id);
}
});
Therefore, when navigating to the route 'clients/3' the DS.JSONSerializer would do an extract() for the UserRoute and an extractMany() for the UsersRoute. However, interestingly enough, most of the time extractMany() (for getting a JSON return of all of the users) would occur before extract() for the single user and its sideloaded properties. When this happened the sideloaded properties would indeed render to the template. However, every once in a while extract() would come before extractMany() (it asynchronosly "beat" the extract many), the sideloaded properties would not render. I think this is because if the extract() occured first that model would then be reset when the extractMany() then occurred for all of the models, which when extracting many do not have sideloaded properties.
I fixed this first problem by doing the following:
App.Router.map(function() {
this.resource('users', function() {
this.route('create');
});
this.resource('user', { path: 'user/:user_id' }, function() {
this.route('edit');
this.route('delete');
});
});
This prevented both models from being extracted in the same route, but the following might have solved both problems.
Second Problem: When navigating away from client/3 to clients and then back to client/3 again, the model would get reset just like the first problem—-sideloaded properties would get dropped.
The way to fix this was to use the UserRoute's activate hook to reload the model.
App.UserRoute = Ember.Route.extend({
activate: function() {
this.modelFor('user').reload();
}
});
This will force the model to be reloaded with the sideloaded properties every time this route 'activates', which is needed of this particular app we're building anyway.
Hope this helps somebody!
You may want to have a custom property that observes your association and print its content in the console.
printRelationship: function() {
console.log(model.clients.get('length'), model.clients);
}.computed(model.clients.#each);