Sorting ember-data live array in route model hooks - ember.js

in a ember route's model hook the following works fine:
model: function () {
return this.modelFor('foo').get('bar');
}
Backed by ember-data, I can delete some of foo's bars somewhere else and it will be updated automagically (live array).
Now, I want this to be sorted (and user in sub-routes, so I have to do this in the route, not in the controller).
model: function () {
return this.modelFor('foo').get('bar')
.then(function (data) {
return data.sortBy('baz');
});
},
... does the job only the first time around, because I'm losing the updating.
Is there a way to write automatic updating sorting in line? What is the ember way to solve this?

To answer my own question, based on the answer from Gaurav:
model: function () {
return this.modelFor('foo').get('bar')
.then(function (data) {
return Ember.ArrayProxy.extend({
arrangedContent: Ember.computed.sort('content', 'props'),
props: ['baz:asc']
}).create({
content: data});
});
},

You can create an Ember Object for your model that has a computed property that is the sorted data.
model: function () {
return this.modelFor('foo').get('bar')
.then(function (data) {
return Ember.Object.extend({
arrangedData: Ember.computed.sort('model', 'props'),
props: ['baz:asc']
}).create({ model: data });
});
},
The Ember.Object.extend part should probably be extracted somewhere so it can be reused in other routes.

Related

Merge two model arrays in a route

I have a controller handling a list of models. These models are of two different types (e.g. Message and Comment). In order to use an ArrayController I would have to merge both lists into one. Is there a way to do this ?
Class-based polymorphism, as proposed in this thread, would solve my problem, but they are not likely to be implemented soon.
In my current solution, I use an ObjectController reveiving both comments and messages. I then merge them using a computed property:
App.SomeRoute = Ember.Route.extend({
model: function (params) {
return Em.Object.create({
comments: this.store.find('comment'),
messages: this.store.find('message'),
});
},
});
App.SomeIndexController = Ember.ObjectController.extend({
merged: Em.computed.union('messages', 'comments'),
});
It works, but I don't benefit from all the niceties of an ArrayController (like sortProperties for example).
What I would like to do is something like:
App.SomeRoute = Ember.Route.extend({
model: function (params) {
var comments = this.store.find('comment');
var messages = this.store.find('message');
return merge(comments, messages);
},
});
where merge returns something similar to what is returned by this.store.find('model').
I asked a similar question recently, here is how I solved the issue.
App.SomeIndexController = Ember.ObjectController.extend({
sortProperties: ['some field'],
sortAscending: false, // false for descending
merged: function() {
var comments = this.get('comment') || [], // This gets wherever you've stored the comments array
messages = this.get('message') || [];// This gets wherever you've stored the messages array
var stream = Ember.A();
stream.pushObjects(comments.toArray());
stream.pushObjects(messages.toArray());
return Em.ArrayProxy.createWithMixins(Ember.SortableMixin, {
content: stream,
sortProperties: this.sortProperties,
sortAscending: this.sortAscending
});
}.property('messages.#each', 'comments.#each')
});
Hope this works for you as well. Just an FYI, for my example, my controller is actually one that is rendered, so I do not set up the model for it in the route. I simply have properties on my controller, lets say, commments and messages that constantly updated themselves as RecordArrays.
So for your example you may need to observe .property('model.messages.#each', 'model.comments.#each')
Inspired by #bmeyers' answer, and after exploring ember-data's source a little bit, I came up with a solution that is reusable and not too terrible. It is probably not optimal, but it does the work.
App.Store = DS.Store.extend({
findMultiple: function (types) {
var self = this;
var recordsByType = types.map(function (type) {
return self.find(type);
});
return self.mergeArrayPromises(recordsByType);
},
mergeArrayPromises: function (promises) {
var promise = Ember.RSVP.all(promises).then(function(arrays) {
var mergedArray = Ember.A();
arrays.forEach(function (records) {
mergedArray.pushObjects(records.toArray());
});
return mergedArray;
});
return DS.PromiseArray.create({
promise: promise,
});
},
});
App.SomeRoute = Ember.Route.extend({
model: function (params) {
return this.store.findMultiple(['comment', 'message']);
},
});
This might help. I stumbled upon this a while back and your question reminded me
https://gist.github.com/sebastianseilund/6096696

Multiple models to Ember route

I have a list of printers
GradingX.PrintersRoute = Ember.Route.extend({
model: function () {
var printerList = Em.A();
//Ember.
$.getJSON("http://localhost/printers").then(function (data) {
data.forEach(function (item) {
printerList.addObject(item);
}), function () {
alert("$.getJSON failed!");
};
});
return printerList;
},
});
That I'm trying to access from my header
GradingX.HeaderRoute = Ember.Route.extend({
model: function () {
//console.log("test in header model route");
//return Ember.Object.create({
return Ember.RSVP.hash({
printers: What Goes Here?,
otherObjects: More Stuff Here
});
},
});
I'm trying to follow the answer here https://stackoverflow.com/a/20523510/697827, but since I'm not accessing through Ember-Data I don't think this.store.find('printers') is going to get me what I need.
I'm missing something. Please help!
RSVP.hash expects an object with keys and promises as values. So I think that the following could work:
GradingX.HeaderRoute = Ember.Route.extend({
model: function () {
return Ember.RSVP.hash({
printers: $.getJSON("http://localhost/printers"),
otherObjects: $.getJSON("http://localhost/???")
});
},
});
In the referenced answer is used this.store.find, which also returns a promise, but it's resolved with a DS.RecordArray (an array like object provided by ember data). So what matters for RSVP.hash are promises.

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: View is not updating after request, when using RESTAdapter

As starting point we used http://todomvc.com/architecture-examples/emberjs/ .
We changed the FixtureAdapter to a RESTAdapter and performed the following changes:
Todos.TodosActiveRoute = Ember.Route.extend({
model: function(){
// ** OLD CODE **
// return this.store.filter('todo', function (todo) {
// return !todo.get('isCompleted');
// });
// ** NEW CODE **
return this.store.findQuery('todo', {isCompleted: false})
},
renderTemplate: function(controller){
this.render('todos/index', {controller: controller});
}
});
We can load the todo items correctly, but if we want to delete one of them a DELETE request is successfully sent to the backend but the todo-item is not removed from the UI.
EDIT:
The delete action is:
removeTodo: function () {
var todo = this.get('model');
todo.deleteRecord();
todo.save();
}
The problem is with using findQuery here. It results in a non live array. That's why the view is not updated after delete (I think if you add a todo, it should neither work)
You can use store.filter passing it the query and the filter function. I think it should work as you expect.
Todos.TodosActiveRoute = Ember.Route.extend({
model: function(){
return this.store.filter('todo', {isCompleted: false}, function (todo) {
return !todo.get('isCompleted');
});
},
renderTemplate: function(controller){
this.render('todos/index', {controller: controller});
}
});

Example of how to use Zendesk's ember-resource adapter

Is there any working and current example on how to use Zendesk's ember-resource adapter with ember.js? I think I understand how to define the models, but I can't find any hint on how to use it in controllers and routes.
In general, there are two approaches for a given route: (1) go immediately to the page and fill in data as it becomes available (2) wait for the data to be fetched before transitioning.
Case 1 is quite straightforward. You create an instance of the model class, call fetch, and return it.
var FooRoute = Em.Route.extend({
model: function(params) {
var foo = Foo.create({ id: params.id });
foo.fetch();
return foo;
},
setup: function(foo) {
// foo is a Foo, but may not have its data populated
}
});
Case 2 is more complicated because Ember-Resource's fetch method returns a promise that resolves with two arguments -- the underlying JSON data and the model itself. An Ember.Route that returns such a promise will only pass the first to setup, so we have to create our own promise:
var FooRoute = Em.Route.extend({
model: function(params) {
var foo = Foo.create({ id: params.id }),
deferred = $.Deferred();
foo.fetch().then(
function(json, model) { deferred.resolve(model); },
function(error) { deferred.reject(error); }
);
return deferred.promise();
},
setup: function(foo) {
// foo is a Foo with its data populated
}
});