ember.js: combine search and filter - ember.js

Having this controller:
App.ProductsController = Ember.ArrayController.extend Ember.PaginationMixin,
sortProperties: ["number"]
I would like to add the "search" functionality, in this (working) way
App.ProductsController = Ember.ArrayController.extend Ember.PaginationMixin,
sortProperties: ["number"]
search: ''
filteredContent: (->
search = #get("search")
#get("content").filter (item)->
name = item.get("name").toLowerCase()
name.match(search.toLowerCase())
).property("submittedSearch")
filterContent: ->
#set "submittedSearch", #get("search")
The problem is that when I apply my search i'm losing my sorting defined before. What could I do?

From the official documentation
Ember.SortableMixin provides a standard interface for
array proxies to specify a sort order and maintain this sorting when
objects are added, removed, or updated without changing the
implicit order of their underlying content array:
Ember.SortableMixin only cares about the content array while we do get, here you're trying to do get on filteredContent which the mixin doesn;t care !
So what I'd suggest you is make the content as computed property which depends on selected search as follows,
App.ProductsController = Ember.ArrayController.extend Ember.PaginationMixin,
sortProperties: ["number"]
search: ''
content: (->
search = #get("search")
#get('products').filter (item) ->
name = item.get("name").toLowerCase()
name.match(search.toLowerCase())
).property('submittedSearch')
Inside the route setupController set the main list to products property of controller instead of content
App.ProductsRoute = Ember.Route.extend
setupControllers: (controller, model) ->
controller.set('products'), App.Products.find()
Now, your content would be filtered courtesy ComputedProperty, sorted courtesy the "mixin"

Related

How is Sorting achieved in an Ember model without using Array Controller?

Every google result is about an ArrayController sorting. Need a sorting mechanism without using ArrayController.
There is a model where there are sort params. Like say 'sortOrder' as one of the properties in the model (which will be from a back end).
Will be rendering this model using #each but this should do the iteration based on the sortOrder property and not the model's ID property.
In Ember 2.0 SortableMixin is deprecated and is on its way out too.
In the Controller (not the ArrayController) you may define a new computed property like SortedUsers1,2,3 below:
export default Ember.Controller.extend({
sortProps: ['lastName'],
sortedUsers1: Ember.computed.sort('model', 'sortProps'),
sortedUsers2: Ember.computed.sort('content', 'sortProps'),
sortedUsers3: Ember.computed('content', function(){
return this.get('content').sortBy('lastName');
})
});
The assumption above is that the model itself is an array of users with lastName as one of user properties. Dependency on 'model' and 'content' look equivalent to me. All three computed properties above produce the same sorted list.
Note that you cannot replace 'sortProps' argument with 'lastName' in sortedUsers1,2 - it won't work.
To change sorting order modify sortProps to
sortProps: ['lastName:desc']
Also if your template is in users/index folder then your controller must be there as well. The controller in users/ would not do, even if the route loading model is in users/.
In the template the usage is as expected:
<ul>
{{#each sortedUsers1 as |user|}}
<li>{{user.lastName}}</li>
{{/each}}
</ul>
Here is how I manually sort (using ember compare)
import Ember from "ember";
import { attr, Model } from "ember-cli-simple-store/model";
var compare = Ember.compare, get = Ember.get;
var Foo = Model.extend({
orderedThings: function() {
var things = this.get("things");
return things.toArray().sort(function(a, b) {
return compare(get(a, "something"), get(b, "something"));
});
}.property("things.#each.something")
});
You just need to include a SortableMixin to either controller or component and then specify the sortAscending and sortProperties property.
Em.Controller.extend(Em.SortableMixin, {
sortAscending: true,
sortProperties: ['val']
});
Here is a working demo.
In situations like that, I use Ember.ArrayProxy with a Ember.SortableMixin directly.
An ArrayProxy wraps any other object that implements Ember.Array
and/or Ember.MutableArray, forwarding all requests. This makes it very
useful for a number of binding use cases or other cases where being
able to swap out the underlying array is useful.
So for example, I may have a controller property as such:
sortedItems: function(){
var items = Ember.ArrayProxy.extend(Ember.SortableMixin).create({content: this.get('someCollection')});
items.set('sortProperties', ['propNameToSortOn']);
return items;
}.property()
Like so: JSBin

Computed property with `#each` won't update

I've created a computed property that relies on all records in the store.
I've tried making the property update on adding/removing records with .property('todos.#each.id'), .property('model.#each.id'), .property('#each.id'), .property('#each') and other combinations, no luck so far. :( When i create new records, existing recrods' property would not update.
Here's a fiddle: http://jsbin.com/UDoPajA/211/edit?output
The property is otherTodos on the Todo controller. This property is used by the <select> dropdown list on the page (via {{view Ember.Select}}).
You're out of scope of the collection. You'll need to get access to the todos controller in order to have a computed property based off of its model. needs will handle this use case. http://emberjs.com/guides/controllers/dependencies-between-controllers/
Additionally to make an easy to access alias to the todos controller's model we use computed.alias. http://emberjs.com/api/#method_computed_alias
Todos.TodoController = Ember.ObjectController.extend({
needs:['todos'],
todos: Ember.computed.alias('controllers.todos.model'),
....
foo: function(){
}.property('todos.#each.id')
});
PS note of caution, in your code you are creating multiple instances of Ember Data filter, filter collections are meant to be live collections that are long living and update as records are added/removed from the store. You might just want to grab the model from todos and filter over it instead of creating a new store filter (which then avoids the async code as well, not that that is an issue).
Here's an implementation that would avoid that (no point in using it as a setter, you are only getting from it):
otherTodos: function() {
var model = this.get('model'),
thisId = model.get('id');
var todos = this.get('todos').filter(function (todo) {
return todo.get('id') !== thisId;
});
var selectContent = todos.map( function(todo){
var selectContent = {
title: todo.get('title'),
id: todo.get('id')
};
return selectContent;
});
return selectContent;
}.property('todos.#each.id'),
Here's an updated jsbin of your code: http://jsbin.com/UDoPajA/216/edit

How to define a Ember.computed helper that acts on the itemController of a Ember.ArrayController?

I recently fell in love with the Ember.computed helpers. Especially the reduce computed ones like computed.mapBy.
They work great when used with the content of an ArrayController like so.
App.People = Ember.ArrayController.extend({
//"content" is an array of App.Person objects.
chosen: Ember.computed.filterBy('content', 'isChosen', true)
})
So lets say the isChosen property doesn't belong on the person model.
I could then define the itemController and create a computed property like so.
App.PeopleController = Ember.ArrayController.extend({
itemController: 'person',
chosen: function() {
return this.filterBy('isChosen', true)
}.property('#each.isChosen')
});
App.PersonController = Ember.ObjectController.extend({
isChosen: false
});
This works, the chosen computed property returns an array based on the personController.isChosen property.
But this is not efficient because it runs the whole filterBy function every time a child changes. Where as the Em.computed.filterBy helper adds and removes individual objects as needed.
Does anyone know how to define a Ember.computed helper that acts on the itemController of a Ember.ArrayController?
You can use #this as the dependentKey to bind to the array of itemControllers.
App.PeopleController = Ember.ArrayController.extend({
itemController: 'person',
chosen: Ember.computed.filterBy('#this', 'isChosen', true)
});
App.PersonController = Ember.ObjectController.extend({
isChosen: false
});
Pretty cool. Thanks to David Hamilton's presentation at the sf ember meetup.

Ember sort data in router/ model

When the route /facades get visited I want to redirect to the first facade. This generally works, but as the list from the server is not sorted, my code doesn't redirect to the "first" facade. I know how to sort in the controller, but how can I sort in the router/ model by any property?
App.FacadesRoute = Ember.Route.extend
model: ->
#get("store").find("facade")
redirect: ->
facade = #modelFor("facades").get("firstObject")
#transitionTo("facades.show", facade)
redirect is deprecated, the recommendation is afterModel, and findProperty is super easy for finding a model in a collection based on some property in the model or if you don't know exactly you can use sortBy.
App.FacadesRoute = Ember.Route.extend
model: ->
#get("store").find("facade")
afterModel: (model, transition) ->
facade = model.findProperty("someproperty", "value on some property");
// or
facade = model.sortBy("someproperty").get('firstObject');
#transitionTo("facades.show", facade)
Ok, I just found a way which works. Not sure if it's the best, but I think this could be useful to others too:
App.FacadesRoute = Ember.Route.extend
model: ->
#get("store").find("facade")
redirect: ->
sortedFacades = Ember.ArrayProxy.createWithMixins Ember.SortableMixin,
sortProperties: ["id"]
content: #modelFor("facades")
facade = sortedFacades.get("firstObject")
#transitionTo("facades.show", facade)

Can't get data from a computed property with ember-model

I am making an app with ember.js and ember-model
I have a model named Plugin defined as follows:
Eme.Plugin = Ember.Model.extend
id: Ember.attr()
name: Ember.attr()
description: Ember.attr()
downloads: Ember.attr()
tags: Ember.attr()
Eme.Plugin.url = "/api/v1/plugins"
Eme.Plugin.adapter = Ember.RESTAdapter.create()
Eme.Plugin.collectionKey = 'plugins'
I want show the most downloaded in index.hbs ( i use ember-rails)
And i fetch data in IndexRoute 's setupController hook:
Eme.IndexRoute = Em.Route.extend
setupController: (controller, model)->
console.log Eme.Plugin.findAll().toArray()
controller.set 'plugins', Eme.Plugin.findAll()
Output :
[nextObject: function, firstObject: undefined, lastObject: undefined, contains: function, getEach: function…]
But in chrome console i execute Eme.Plugin.findAll().toArray(), i got the results as follows:
[{
__ember1377710636537: "ember404"
__ember1377710636537_meta: Meta
_dirtyAttributes: Array[0]
_reference: Object
_super: undefined
get _data: function() {}
isLoaded: true
isNew: false
set _data: function(value) {}
__proto__: Object
}, {
...
}, {
...
}]
In my IndexController have a computed property:
Eme.IndexController = Em.Controller.extend
mostDownloads:(->
# console.log #get('plugins').slice(0, 3)
#get('plugins').slice(0, 3)
).property('plugins')
and i iterate the mostDownloads but there is nothing to show, however when i output {{plugins.length}}, i can't get the count of all my data
Who can give a hand to me?
Plugins looks like an array and would need to use the .#each iterator like so:
Eme.IndexController = Em.Controller.extend({
// Code
}).property('plugins.#each')
Here is documentation on #each http://emberjs.com/guides/object-model/computed-properties-and-aggregate-data/
Regarding your array length, I've never had much luck using .length, for length I usually do
plugins.get('length')
Hope that helps!
I propose two changes to make you app working.
First
I assume since it's called plugins (plural) the call to .findAll() returns an array of plugins for this to work you should change your controller type to be an ArrayController. Then because you are using # aka. this in your computed property you should use the fat arrow => to have the right reference to this, so the resulting IndexController should look like:
Eme.IndexController = Em.ArrayController.extend
mostDownloads:(=>
# console.log #get('content').slice(0, 3)
#get('content').slice(0, 3)
).property('content.[]')
Notice also that we observe content.[] this will trigger whenever the content array changes, items added or removed etc. you could also use content.#each but this is better suited for when you need to observe changes to the properties of a plugin record in the array, e.g. content.#each.name.
Second
Now change also how you set the plugins collection on your controller, you should rather set the controller's content property since this is what it is for:
Eme.IndexRoute = Em.Route.extend
setupController: (controller, model)->
# console.log Eme.Plugin.findAll().toArray()
controller.set 'content', Eme.Plugin.findAll()
This line console.log Eme.Plugin.findAll().toArray() will not work the way you expect because when you call it it will give you a promise back and not the array that is still underway (async...).
And a last change, to print out the plugins length, use the afterModel hook of your IndexRoute, since this is the right time when the model promise has being resolved (the async operation has given back control to your app).
Eme.IndexRoute = Em.Route.extend
...
afterModel: (plugins, transition) ->
console.log plugins.get 'length'
...
Hope it helps.