Reputation: 3443
I'm trying to filter data based on a search string using a dynamic route. When using the transitionToRoute
function from a controller the model data from the route is returned to the viewproperly, however when navigating directly to the url or refreshing the page all the the forEach call is not being executed because the length of the data in the model is 0.
I have a feeling this is because the data is being loaded asynchronously, but I'm not sure how to delay the forEach loop and the rendering of the view until the find
's promise is resolved and the forEach loop has completed.
Here is the model
function of my router:
model : function( params ){
var lists = App.List.find( ), //gets all the lists
query = params.query, //query string from url
re = new RegExp( query, 'i' );
this.set( 'query', query );
return lists.forEach( function( list ){
var cards = list.get( 'cards' ).forEach( function( card ){
//the view has a class bound to the hide property of each card
card.set( 'hide',
( query.length ) ? !( re.test( card.get( 'description' ) ) ) : false
);
} );
return list;
});
}
When a user hits the application with a url with a query string of #/search/red
I want only the cards that have "red" in them returned.
Upvotes: 4
Views: 2364
Reputation: 11668
I just rediscovered this question and this is my try to give an answer. As i already mentioned in the comment, this are my basic ideas:
with Computed Properties:
with Observers: (closer to your original code)
The best approach would be to use computed properties, but i was not sure how to go about it (the Ember Team states that computed properties often lead to better code). Therefore here is a rough sketch of the code with the Observer approach. This should work out ok as a start:
Route:
model : function( params ){
this.set( 'query', params.query );
return App.List.find(); //gets all the lists
},
setupController : function(controller, model) {
this._super(controller, model);
// setupController is a good location to setup your controller
controller.set("query", this.get("query"));
}
Controller:
App.SearchController = Ember.ArrayController.extend({
query : '',
// this observer will fire:
// 1.: every time a list object is added or removed from the underlying array
// 2.: the query changes
modelAndQueryObserver : function(){
re = new RegExp( this.get("query"), 'i' );
return this.get("model").forEach( function( list ){
var cards = list.get( 'cards' ).forEach( function( card ){
//the view has a class bound to the hide property of each card
card.set( 'hide',
( query.length ) ? !( re.test( card.get( 'description' ) ) ) : false
);
} );
return list;
});
}.observes("model.@each", "query")
});
Upvotes: 7
Reputation: 8574
You're right about the problem being due to the asynchronous call. When you enter the route directly the object that is returned from your find call is a promise, and not a live list. You need to wait for the promise to resolve before dealing with it.
Something like this should work:
model : function( params ){
var lists = App.List.find( ), //gets all the lists
query = params.query, //query string from url
re = new RegExp( query, 'i' );
this.set( 'query', query );
// 'lists' is a promise, once it has resolved we can deal with it
lists.then(function(realLists){ // realLists is a real collection of models
realLists.forEach(function(list){
list.get('cards').forEach( function( card ){
//the view has a class bound to the hide property of each card
card.set( 'hide',
( query.length ) ? !( re.test( card.get( 'description' ) ) ) : false
);
});
});
});
// return the 'lists 'promise immediately
// maybe before the 'lists.then' function above has run
// Ember will resolve the promise for you and set up the controller correctly
return lists;
}
Note that depending on your loading method (side loading vs additional http call) when you call list.get('cards')
above, you may actually get a promise instead of a collection of cards. If that's the case you can use the same technique.
cards = list.get('cards');
cards.then(function(realCards){
realCards.forEach(...);
});
Also worth noting is that in the latest versions of Ember Data, the method for finding has changed. Instead of App.List.find( )
you'd do this.store.find('list')
. (The transition guide has more info about the breaking changes. https://github.com/emberjs/data/blob/master/TRANSITION.md)
Upvotes: 0
Reputation: 186
Here is one implementation where model and content properties of a controller is clearly separated at setupController hook of a corresponding route.
I have list of balls with different colors in a separate file.
balls.js
[
{"id":1,"color":"darkred"},
{"id":2,"color":"lightred"},
{"id":3,"color":"darkgreen"},
{"id":4,"color":"lightgreen"},
{"id":5,"color":"darkblue"},
{"id":6,"color":"lightblue"}
]
App.js
App = Ember.Application.create();
App.Router.map(function() {
this.resource('balls',{path:"/balls/:color"});
});
App.BallsRoute = Ember.Route.extend({
model: function(params) {
return params;
},
serialize:function(model){return {color:model.color}},
setupController:function(cont,model){
var balls=Em.A();
if(!App.Balls)
App.Balls=$.getJSON("/start/js/balls.js");
App.Balls.then(function(json){
var re=new RegExp(model.color)
balls.setObjects(json);
var filtered =balls.filter(function(o,i){return re.test(o.color);});
cont.set('content',filtered);
});
}
});
App.ApplicationController=Em.Controller.extend({
searches:[{color:"red"},{color:"blue"},{color:"green"}]
});
App.BallsController=Em.ArrayController.extend({
});
HTML
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
<nav>
{{#each item in searches}}
{{#link-to "balls" item}} {{item.color}} {{/link-to}}
{{/each}}
</nav>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="balls">
<ul>
{{#each controller}}
<li>{{color}}</li>
{{/each}}
</ul>
</script>
I am not using Ember data, as I am not comfortable with it.
Kindly check this Bin
Upvotes: 0