I want to implement simple ember app, where I have a search dialog, a list of results and a detailed view if I click on the results, something like this:
http://jsbin.com/tuyapabuhe/2/edit
The search method of the IndexController is doing an ajax request to populate the model, but I'm not sure if that is the best way to do it. I specially don't like the var self = this; part. Is there an ember way to do that search?
EDIT
I updated the example, now is doing an ajax request and is more realistic:
http://jsbin.com/wimogu/4/edit
The ajax call should be happening inside the model hook for the Index route. Instead of observes you can just use a property as follows:
App.IndexRoute = Ember.Route.extend({
model: function(){
return data; // your ajax call here...
}
});
App.IndexController = Ember.ArrayController.extend({
filtered: function() {
var name = this.get('name') || '';
var people = data.filter(function(el){
if(el.name.toLowerCase().indexOf(name)>-1)
return el;
});
return people;
}.property('name', 'model')
});
Then, in your template you can just do
{{#each user in filtered}}
{{#link-to 'person' user.id}}
<div>{{user.name}}</div>
{{/link-to}}
<hr/>
{{/each}}
Working solution here
Per my comment on another answer, I would suggest the following for AJAX calls based on one or more filters, complete with debouncing to limit the number of requests:
function handleSearch() {
this.set('model', this.store.find('user', this.get('query')));
}
App.IndexController = Ember.Controller.extend({
search: '',
sort: 'first_name',
direction: 'asc',
query: function() {
return {
search: this.get('search'),
sort: this.get('sort'),
direction: this.get('direction')
};
}.property('search'),
queryDidChange: function() {
Ember.run.debounce(this, handleSearch, 200);
}.observes('query').on('init'),
actions: {
clearSearch: function() {
this.set('search', '');
}
}
});
I have this running in the wild right now and it works perfectly.
Related
i am trying to build my first emberjs app and i wonder how i can save the state of a nested route to rebuild that state when the top route is revisted in the current session.
To give an example:
Lets Say a user switches from /overview/item1 to /info and then returns to
/overview/ and want to redirect him to /overview/item1
HTML
<div id="navigation">
{{#link-to 'info' class='link' }}Info{{/link-to}}
{{#link-to 'overview' class='link'}} Overview {{/link-to}}
</div>
JS
App.Router.map(function(){
this.route('info');
this.resource('overview', function () {
this.resource('item', { path : '/:item_id'});
});
});
it would be really nice if somebody could give me a hint to the right approach of this.
There are various ways for achieving your goal. Basically, you need to store state of last visited overview/:item_id route in the parent route or controller. Then, you need to check this state before resolving model of overview route. If state is not null (user was selected some item from overview/:item_id), abort current transition and start the new one (to
overview/:selected_item_id).
Schematic solution in code:
// 1st approach
App.OverviewController = Ember.ObjectController.extend({
selectedItem: null
});
App.OverviewRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (this.get('controller.selectedItem')) {
transition.abort();
this.transitionTo('overview.item', this.get('selectedItem'));
}
}
});
App.OverviewItemRoute = Ember.Route.extend({
afterModel: function(model) {
this.controllerFor('overview').set('selectedItem', model);
}
});
// 2nd approach (less code)
App.OverviewRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (this.get('controller.selectedItem')) {
transition.abort();
this.transitionTo('overview.item', this.get('selectedItem'));
}
},
setupController: function(controller) {
controller.reopen({ selectedItem: null });
}
});
App.OverviewItemRoute = Ember.Route.extend({
afterModel: function(model) {
this.controllerFor('overview').set('selectedItem', model);
}
});
It's important to keep the item itself, not it's id, because it'll way more easier to load overview/:item_id route in the future (passing stored model in this.transitionTo('overview.item', item)).
I'm evaluating Emberjs in the context of whether we could port some of our code to it but maintain the same api so my first day really looking at it.
I'm using the Tom Dale tutorial but WITHOUT ember-data. I think I have kinda figured out how to get data into the app (thx #kingping2k). I just need to get this to save / update.
I have a doneEditing action which gets called when I click on it as seen by console but how do I get a reference to the model. Looking at the controller docs (http://emberjs.com/api/classes/Ember.Controller.html), I don't see a really obvious property like model or something. How would I tell the PostController to save the post that it is getting back in its route? Also, do people normally use jQuery promises to do something else after the save has completed here(I'm assuming yes)?
I've included the relevant code with the doneEditing action at the bottom where I'm looking for help:
thx for any help
Model:
Hex.Post = Ember.Object.extend({
id: null,
body: null,
isEnabled: null,
createdAt: null,
save: function(data){
console.log("you want to save this item");
$.post( "api/post", data, function( data ) {
// something here
});
}
});
View:
<script type="text/x-handlebars" id="post">
{{#if isEditing}}
{{partial 'post/edit'}}
<button {{action 'doneEditing'}}>Done</button>
{{else}}
<button {{action 'edit'}}>Edit</button>
{{/if}}
<h1>{{id}}</h1>
{{outlet}}
</script>
Route:
Hex.PostRoute = Ember.Route.extend({
model: function(params) {
console.log('called with: ' + params.post_id);
return Hex.Post.findById(params.post_id);
}
});
Controller:
Hex.PostController = Ember.ObjectController.extend({
isEditing: false,
actions:{
edit: function() {
this.set('isEditing', true);
},
doneEditing: function() {
this.set('isEditing', false);
console.log("this gets called");
//this.get('content').save();
//this.save();
//console.log("here: " + this.model.id);
//this.model.save(); //doesn't work ???
// this.post.save(); //doesn't work ???
//this.get('store').commit(); // prob no
}
}
});
when you return a model from the model hook it's then passed to the setupController in the route. The default implementation of setupController does this, controller.set('model', model)
setupController:function(controller, model){
controller.set('model', model');
}
so to get the model within the context of the controller just get that property.
var model = this.get('model')
I would return the promise, then you can trigger something on save/failure etc
save: function(){
console.log("you want to save this item");
return Ember.$.post( "api/post", JSON.stringify(this));
}
doneEditing: function() {
this.set('isEditing', false);
var model = this.get('model');
model.save().then(function(){alert('saved');},
function(){alert('failure');});
}
And generally you'll put save in the reopen
Hex.Post.reopen({
save: function(){
console.log("you want to save this item");
return Ember.$.post( "api/post", JSON.stringify(this));
}
});
I have a route like this:
App.PeopleRoute = Ember.Route.extend({
model: function()
{
return App.Persons.find(personId);
}
});
where personId is loaded asynchronically and is a normal JavaScript variable outside Ember. Now when route is displayed it gets the current PersonId and displays proper data. But when i change the value of personId it does not update the view.
So my question is what is a way to refresh this route to find records with new personId?
This is because model hook is executed only when entered via URL for routes with dynamic segments. Read more about it here.
The easiest solution for this would be to use transitionTo.
App.PeopleRoute = Ember.Route.extend({
model: function(params)
{
return App.Persons.find(params.personId);
},
actions: {
personChanged: function(person){
this.transitionTo("people", person);
}
}
});
App.PeopleController = Em.Controller.extend({
observeID: function(){
this.send("personChanged");
}.observes("model.id");
});
I have two controllers which both load to the same outlet, so only one can be active at one time. Both observe a property on a third controller like this:
App.SearchController = Ember.ObjectController.extend({
needs: ['navigation'],
updateResults: function () {
console.log('load search data');
}.observes('controllers.navigation.search')
});
Full sample
http://jsfiddle.net/FMk7R/1/
When the property changes some data is fetched. If I click on both links so that both are loaded, then when the property changes, both controllers receive the observes event and load the data. I'd like to load the data only in the one which is visible.
How can I figure out which controller is currently active and load the data only in the active one?
Ideally your controllers should not know that they are active. One alternative is to invert the relationship, so that NavController is responsible for changing a query property of the "active" controller.
** UPDATE - Adding example based on comment **
App.SearchRoute = Ember.Route.extend({
setupController: function(controller) {
this.controllerFor('navigation').set('active', controller);
}
});
App.ImagesRoute = Ember.Route.extend({
setupController: function(controller) {
this.controllerFor('navigation').set('active', controller);
}
});
App.SearchController = Ember.ObjectController.extend({
loadResults: function (query) {
console.log('loading web search data for: ', query);
}
});
App.ImagesController = Ember.ObjectController.extend({
loadResults: function (query) {
console.log('loading image search data for: ', query);
}
});
App.NavigationController = Ember.ObjectController.extend({
search: '',
active: null,
searchDidChange: function() {
this.get('active').loadResults(this.get('search'));
}.observes('search', 'active')
});
See http://jsfiddle.net/F3uFp/1/
Another alternative is to use computed properties instead. Ember will only refresh computed properties that are actually required to render the active view. For example:
App.SearchController = Ember.ObjectController.extend({
needs: ['navigation'],
results: function () {
console.log('loading web search data');
return("web search results");
}.property('controllers.navigation.search')
});
See updated fiddle here: http://jsfiddle.net/ZTnmp/
http://jsfiddle.net/FMk7R/1/
hi i have the following route:
MB3.PlaylistRoute = Ember.Route.extend({
model: function(params) {
return MB3.Playlist.find(params.playlist_id);
}
});
The playlist has a hasMany realtion with tracks. in the playlist view i want do do some logic with an attribute of the first track of the playlist.
so i added this code:
MB3.PlaylistView = Ember.View.extend({
didInsertElement: function() {
console.log(this.get("controller.tracks").objectAt(0).get("title"));
}
});
The problem is title is undefined (i think because it is not yet loaded. the second thing i tried is waiting for the didLoad event:
MB3.PlaylistView = Ember.View.extend({
didInsertElement: function() {
var self=this;
this.get("controller.tracks").on("didLoad", function() {
console.log(self.get("controller.tracks").objectAt(0).get("title"));
});
}
});
but this logges null as well. How do i accomplish that?
Like Adrien said in the comments, it seems you are running into issue 587. That said, I don't think you actually need the "didLoad" callback in this case. Instead, try using a computed property to get the video_id or track title. For example:
MB3.PlaylistView = Ember.View.extend({
firstTrackTitle: function() {
return this.get('controller.tracks.firstObject.title');
}.property('controller.tracks.firstObject.title')
});
Then in your template, embed the player if this property is defined:
{{#if view.firstTrackTitle}}
embed code here
{{/if}}
FWIW I would put this logic in controller instead of view, but same idea.