EmberJS: Use AJAX data from parent resource in child route - ember.js

Let's say that I want to create an ember app that displays a matrix and allows
you to click on a cell for more information. My routes look like this:
App.Router.map(function() {
this.resource("data", function() {
this.route("cell", {path: ':row/:col'});
});
});
App.DataRoute = Ember.Route.extend({
model: function(params) {
return App.DataModel.create({
/* information needed for AJAX query */
});
},
});
App.DataCellRoute = Ember.Route.extend({
model: function(params) {
return Ember.Object.create({
row: params.row,
col: params.col
});
},
serialize: function(model) {
return {row: model.row, col: model.col};
},
});
Furthermore, the matrix load is expensive (say, a slow AJAX request), so I
don't want to have to reload the data when I transition to the child route.
Also, assume that the data in the cells can't be serialized, so I can't just
pass the cell data in the URL.
Here's what the controllers looks like:
App.DataController = Ember.ArrayController.extend({
model: null,
content: Ember.A(),
loadData: function() {
self = this;
$.ajax({
/* ... */,
success: function(data) {
App.beginPropertyChanges();
self.clear();
data.forEach(function(row) {
self.pushObject(row);
});
App.endPropertyChanges();
},
}.observes('model'),
});
App.DataCellController = Ember.ObjectController.extend({
needs: 'data',
model: {row: 0, col: 0},
matrixBinding: 'controllers.data.content',
cell: function() {
var xy = this.get('model');
return this.get('matrix')[xy.row][xy.col];
}.property('matrix.#each', 'model'),
});
When you click on a cell, its view tells DataController to send an event that
transitons to data.cell with the appropriate row/column. I expected that
when I transition to the data.cell route, I should have access to
DataController's content. However, all I get is the default empty array.
What am I doing wrong?
EDIT:
To answer the question 'how is DataController.content set', I updated the
question to show a more accurate depicition of DataRoute and
DataController. Basically, DataController has a model that contains
information pertinent to the AJAX request. I have a function, loadData,
which observes the model and loads content.

Related

save store with hasmany and belong to relationship

I have a store with "row" and "column" models (tr and td in the html), buttons add rows and columns into rows.
I want to save the changes to server only when a "save" button is pressed.
At this time have done this code, it nearly work, but I have some problem with the "save" method :
The "id" of row and columns came from server when they are saved, but when a row is saved it don't know allready the columns id, and visversa. So that the save method is buggy and the code not nice at all.
I beggin with Ember, certainly there is a better way to do that ?, thank you if you can give me some help.
May be also something could be better done in my addRow and addColumn methods ?
Ember 1.11 with restAdapter
App.IndexRoute = Ember.Route.extend({
model: function () {
return this.store.find('row');
},
setupController: function(controller, model) {
controller.set('model', model);
}
});
App.Row = DS.Model.extend({
titre: DS.attr('string'),
columns: DS.hasMany('column', {async: true, embedded: 'always'}),
});
App.Column = DS.Model.extend({
titre: DS.attr('string'),
row: DS.belongsTo('row', {async: true}),
});
App.RowSerializer = DS.RESTSerializer.extend({
serializeHasMany: function(record, json, relationship) {
var hasManyRecords, key;
key = relationship.key;
hasManyRecords = Ember.get(record, key);
if (hasManyRecords && relationship.options.embedded === "always") {
json[key] = [];
hasManyRecords.forEach(function(item, index) {
json[key].push(item.get('id'));
});
}else{
this._super(record, json, relationship);
}
}
});
App.IndexController = Ember.Controller.extend({
selectedColumn: null,
actions: {
save: function(){
var row = this.store.all('row');
row.forEach(function(r, index, self){
r.save().then(function(r2){
r2.get('columns').forEach(function(c){
c.set('row',r2);
c.save().then(function(){
r.save();
})
})
})
})
},
clickCol: function(column){
this.set('selectedColumn', column)
},
addRow: function(){
_this = this;
var newRow = _this.store.createRecord('row',{titre:'Titre row'});
var newColumn = _this.store.createRecord('column', {
titre: 'Titre colonne',
})
newRow.get('columns').addObject(newColumn);
},
addColumn: function(){
_this = this;
this.get('selectedColumn').get('row').then(function(r){
var newColumn = _this.store.createRecord('column', {
titre: 'Titre colonne',
row: r
})
})
}
}
})
EDIT
I did find this :
DS.RESTAdapter: Robust Support for Parent->Child Hierarchies
where there is among other writted : "Allow parent and child records to be saved in the same commit", "[RESTAdapter] Allow new parent, child to be saved at once"...
witch seems to be something like I'm looking for, but can't find anywhere how to make it work ?
also this :
Client-Side IDs with Ember Data
witch say "Consider the case where the user creates a post with several attachments. You’ll need to make sure that the post is saved and has its ID resolved before attempting to save the child models in order to preserve this relationship"...

How to avoid too many empty records?

Ember : 1.5.0-beta.2
Ember Data : 1.0.0-beta.7
I have the following router:
App.Router.map(function() {
this.resource('posts', function() {
this.route('new');
});
});
My PostsNewRoute creates a new record in the model hook:
App.PostsNewRoute = Ember.Route.extend({
model: function() {
return this.store.createRecord('post');
}
});
Since I don't want transient record to be visible, I filter them out in my PostsRoute:
App.PostsRoute = Ember.Route.extend({
model: function() {
this.store.find('post');
return this.store.filter('post', function(post) {
return !post.get('isNew');
});
}
});
This works as expected, but every transition to posts.new add a new record to the store, which is something I would like to avoid. So, instead of calling createRecord every time the model hook is called, I filter the store for an empty record and return this if there is one found:
App.PostsNewRoute = Ember.Route.extend({
model: function() {
var route = this;
return this.store.filter('post', function(post) {
return post.get('isNew');
}).then(function(result) {
return result.get('firstObject') || route.store.createRecord('post');
);
});
This gives me at the most one empty record.
My question: is there a better way to avoid my store being populated with (many) empty records ?
UPDATE:
Instead of filtering on the isNew attribute, I can probably use currentModel:
model: function() {
this.get('currentModel') || this.store.createRecord('post');
};
You can use this addon https://github.com/dockyard/ember-data-route to clean up when you leave a /new route. It hooks into the willTransition action hook that gets called on the route whenever a transition occurs.
The source code is a short read: https://github.com/dockyard/ember-data-route/blob/master/addon/mixins/data-route.js.
The alternative would be to not create a new record in the model hook, but according to a comment of yours it doesn't seem to be an option.

Ember.js - Duplicate record in Store that clears after refresh

I have a basic Rest API and when I visit my newPost page, there is a form I can fill in with 'title' and 'text' fields as well as a submit button. After I create the record, I save it with the controller and transitionToRoute('posts').
When I view the 'posts' page, the input I just entered is displayed twice. I checked the Ember inspector and it looks like the record is put into the data store twice, once as I click the create button and one after the page refreshes. When I refresh the browser again, the duplicate is removed.
How do I get rid of the duplicate record in the store? Cheers for any suggestions.
EDIT: I believe I have solved my issue by using a filter to remove posts that hadn't been persisted yet.
Updated Routes:
App.PostsRoute = Ember.Route.extend({
model: function() {
return this.store.find('post');
},
setupController: function() {
var posts = this.store.filter('post', function (post) {
return !post.get('isDirty');
});
this.controllerFor('posts').set('model', posts);
}
});
App.NewPostRoute = Ember.Route.extend({
model: function(){
return this.store.createRecord('post');
}
});
Routes:
/* Routes */
App.Router.map(function() {
this.resource('posts');
this.resource('newPost');
});
App.PostsRoute = Ember.Route.extend({
model: function() {
return this.store.find('post');
},
setupController: function() {
var posts = this.store.filter('post', function (post) {
return !post.get('isDirty');
});
this.controllerFor('posts').set('model', posts);
}
});
App.NewPostRoute = Ember.Route.extend({
model: function(){
return this.store.createRecord('post');
}
});
NewPostController:
actions:
createPost: function() {
...
var post = this.get('store').createRecord('post', {
title: title,
text: text
});
this.set('title', '');
this.set('text', '');
post.save();
this.transitionToRoute('posts');
...
newRecord: function() {
this.set('content', App.Post);
}
Thanks!

Ember: use controller data in route or how to fetch data properly

This question is a follow up on my previous question: Architecture for reusable object in ember
In my app I create multiple charts using an Ember.Component. The daterange for all the charts is controlled by a Daterangepicker which has its own controller etc.. Now the data for each chart is fetched in the IndexRoute (with an ajax call), and the daterange is passed in the query string.
The problem is that I can't seem to figure out how to access the daterange from the IndexRoute. Here's my code:
IndexRoute.js
App.IndexRoute = Ember.Route.extend({
model: function(){
var that = this;
return Ember.Object.extend({
registrationsData: null,
registrations: function() {
var self = this;
$.ajax({
url: Routing.generate('ysu_user_api_statistics_registrations', {startDate: that.dateRange.startDate, endDate: that.dateRange.endDate}),
success: function(data) {
var labels = [];
var values = [];
var chartData = {
labels : data.labels,
datasets : [
{
data : data.values,
}
],
};
self.set('registrationsData', chartData);
}
});
}.property(),
}).create();
},
dateRange: Ember.Object.create({
id: 1,
startDate: '2013-08-01',
endDate: '2013-08-31'
}),
});
Index.hbs
{{ my-chart title="Registrations" dataBinding=model.registrations registrationsDataBinding=model.registrationsData}}
MyChartComponent.js
App.MyChartComponent = Ember.Component.extend({
...
dataBinding: null,
registrationsDataBinding: null,
dateRangeBinding: null,
modelDateRangeBinding: null,
chartContext: null,
myChartController: null,
didInsertElement: function() {
/* Create and set controller */
if (!this.get('myChartController')) {
myChartController = new App.MyChartController()
this.set('myChartController', myChartController);
}
this.set('chartContext', $(this.get('element')).find('canvas')[0].getContext("2d"));
},
drawChart: function() {
if(this.get('chartContext')) {
var ctx = this.get('chartContext');
var options = {
bezierCurve : false,
pointDotRadius : 6,
pointDotStrokeWidth : 4,
datasetStrokeWidth : 4,
}
var myNewChart = new Chart(ctx).Line(this.get('registrationsDataBinding'), options);
}
}.observes('registrationsDataBinding', 'myChartController.dateRange'),
});
MyChartController.js
App.MyChartController = Ember.ArrayController.extend({
container: App.__container__,
needs: ['daterangepicker'],
dateRange: 'controllers.daterangepicker.selectedRange',
dateRangeBinding: 'controllers.daterangepicker.selectedRange',
});
I must admit, this setup feels kinda weird. So ultimately my question is:
What would be the correct way to fetch data for my charts based on startDate and endDate set in my DatePickerController?
I have been struggling with this problem as well.
In some of my apps, I've needed the URL to control the date range (e.g. a particular month). In these cases, I would created a MonthRoute and a MonthModel - think of it as a monthly report. The MonthModel has a hasMany property of the actual data I wanted to chart:
App.Month = DS.Model.extend({
companies: DS.hasMany('App.Company')
});
A datepicker would let the user enter a new route, which would fetch (say) the Jan-2013 month model
{
month: {
id: 'Jan-2013',
companies: [
{name: 'Acme, Inc', revenue: 10425, ...},
...
]
}
}
Then, I would set the embedded companies data on my CompaniesController in the setupController hook:
App.MonthRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('model', model);
this.controllerFor('companies').set('model', model.get('companies'));
}
});
Then, I would do the various array manipulations on my CompaniesController, and make that data available to my charts.
I have some code for this up on github, as well as a demo. I'd be interested to hear your thoughts.

Ember Router transitionTo nested route with params

App.Router.map(function() {
this.resource('documents', { path: '/documents' }, function() {
this.route('edit', { path: ':document_id/edit' });
});
this.resource('documentsFiltered', { path: '/documents/:type_id' }, function() {
this.route('edit', { path: ':document_id/edit' });
this.route('new');
});
});
And this controller with a subview event that basically transitions to a filtered document
App.DocumentsController = Ember.ArrayController.extend({
subview: function(context) {
Ember.run.next(this, function() {
//window.location.hash = '#/documents/'+context.id;
return this.transitionTo('documentsFiltered', context);
});
},
});
My problem is that this code works fine when Hash of page is changed.
But when I run the above code NOT w/ the location.hash bit and w/ the Ember native transitionTo I get a cryptic
Uncaught TypeError: Object [object Object] has no method 'slice'
Any clues?
Thanks
UPDATE:
App.DocumentsFilteredRoute = Ember.Route.extend({
model: function(params) {
return App.Document.find({type_id: params.type_id});
},
});
{{#collection contentBinding="documents" tagName="ul" class="content-nav"}}
<li {{action subview this}}>{{this.nameOfType}}</li>
{{/collection}}
The problem is that your model hook is returning an array, while in your transitionTo you are using a single object. As a rule of thumb your calls to transitionTo should pass the same data structure that is returned by your model hook. Following this rule of thumb i would recommend to do the following:
App.DocumentsController = Ember.ArrayController.extend({
subview: function(document) {
var documents = App.Document.find({type_id: document.get("typeId")});
Ember.run.next(this, function() {
return this.transitionTo('documentsFiltered', documents);
});
}
});
Note: I assume that the type_id is stored in the attribute typeId. Maybe you need to adapt it according to your needs.