How to locally filter a model and automatically respond to changes to model data? - ember.js

I have a model that's fetched with store.find. It's a list of "tasks" and the user can filter these tasks by status and other properties. How do we keep the UI/component updated when the filter is applied and when properties are changed? Eg. Task list is filtered only to show completed tasks and the user changes the status of one the tasks from completed to in-progress. The task has to be automatically removed from the UI/component.

One of the best things about ember data is changes you make to a model anywhere in your app take effect everywhere. So I'd solve this by applying multiple getters in the same component and passing in the full list of tasks from the route. Then when isOpen or the filter criteria or whatever updates the getter will refresh and you'll get new data in the template.
import Component from '#glimmer/component';
import { tracked } from '#glimmer/tracking';
export default class TaskListComponent extends Component {
#tracked filterBySomething;
get openTasks() {
return this.args.tasks.filterBy('isOpen');
}
get filteredTasks() {
return this.openTasks.filter((task) => {
return task.hasSomething === this.filterBySomething;
});
}
}
With a template like
{{#each this.filteredTasks as |task|}}
{{task}}
{{/each}}
Which would be called from a route template as:
<TaskList #tasks={{#model}} />

Related

Ember list of model not reloaded without refreshing page

I'm displaying only published articles by sorting my list with the 'published' attribute.
Now when I edit an article and set it from 'published' to 'draft' and then I return to the list I see the 'draft' article even if I wrote a filter in my controller.
How i'm saving
article.set('isPublished', true);
article.save();
this.transitionToRoute('article.list');
Route :
model() {
return this.get('store').findAll('articles');
}
Controller :
articleSorted: computed.filterBy('model', 'isPublished', true),
Besides before I refresh the page some article are still 'draft' and when I refresh they are 'published'... Just going to another page and return to the list, or doing a browser refresh is enough to list properly only 'published' articles and so solve my problem.
Where am I suppose to look to solve my problem without refreshing ?
Thanks
I'm taking a best guess here based on your question and comments. Have full route and controller code would be helpful, so if this doesn't help I'll need that information.
Based on:
Just going to another page and return to the list, or doing a browser refresh is enough to list properly only 'published' articles and so solve my problem.
I would guess that there is an issue loading the articles or else the computed property is not being re-evaluated when isPublished changes.
I would try to load everything and filter it in a computed property. This might looks like:
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
export default Route.extend({
store: service(),
model() {
return this.store.findAll('article');
}
});
import { computed } from '#ember/object';
import Controller from '#ember/controller';
export default Controller.extend({
articles: computed('model.#each.isPublished', function () {
return this.model.filterBy('isPublished');
}),
});
This will load all the articles in the model hook and then handle the filtering in a computed property. When the isPublished property changes on any one of the articles then the list should updated.
The reason for the delay in updating is probably due to the way you're saving the change. When running .save() it's an asynchronous operation that you need to wait on before transitioning. Try:
actions: {
async publishArticle(article){
article.set('isPublished', true);
await article.save();
this.transitionToRoute('article.list');
}
}
Which will wait for the promise to resolve first.
First try to set variable into the model , then make model.save(). like
article.set('name', 'draft');
article.save().then(transitionToarticle).catch(failure);; // => PATCH to '/article/id'

how to filter model to get data in realtime in emberfire

so i have quota model like this :
export default DS.model.extend({
quota : DS.attr('number'),
sellerId: DS.attr('string'),
buyerId:DS.attr('string') });
and i have assignQuota routes with dynamic segment like this:
this.route('assignQuota', {path:'/assignQuota/:buyer_id'}
and in assignQuota.js :
export default Ember.Route.extend({
model(params) {
const sellerId = this.get("session").get("uid");
return this.store.query('quota',{
orderBy:'buyerId',
equalTo: params.buyer_id
}).then(function(quota){
return quota.filterBy('sellerId',sellerId)
});
}
});
and in my template (simplify) is like this:
{{#each model as |quota|}}
{{quota.quota}}
{{/each}}
it worked but if someone add data or delete data in quota model, the list didn't update automatically in the template.
The template only refresh after i refresh the browser. The funny thing is if I use ember inspector to inspect the data for quota, it shown that the model already changes if someone changes the model but the template didn't reflect the changes.
please help
thanks
The issue lies, how are you doing transitionTo to assignQuota route, If you are passing model to the dynamic segment,then it will skip calling the model hook and it will render same model data.
The reason is that the model does not observe changes.
Create a computed property and make it observer change changes of the model, and then using the computed value to create a list (your each loop).
quotaList: Ember.computed('model.[]', function() {
// Your update logic here
// return the new value
})

How to have two different models within a route and its subroute?

I'm making a simple web chat system with Ember.
I have a route /chatrooms that lists a few chatrooms, and then I also have /chatrooms/:chatroom_id that should show the actual chatroom with messages.
The second route is within the first one, like this:
this.resource('chatrooms', function() {
this.route('show', {
path: ':chatroom_id'
});
});
When I access /chatrooms, a call is made to the server (/api/chatrooms) is a list of rooms is returned and displayed, like expected.
When I click a room, the application transitions to /chatrooms/id, but no call is made to retrieve the messages (available at /api/chatrooms/id), even when I try to define a model.
I have a similar scenario with the users. A list of users is retrieved, then displayed. When a name is clicked, the profile is shown. No second call is made, but that's okay since Ember knows everything about the user already.
In my current case, when a list is first returned, it includes all the information except the messages. I believe that would be too much otherwise (10 chatrooms * 100 last messages = 1000 elements in my JSON for each request). So I want to call the server for messages only when a chatroom is selected.
Do you know how to do it, or maybe there's something wrong I'm doing in the first place?
Updates
Template code from app/templates/chatrooms.hbs
<h1>Chatrooms</h1>
<ul class="sub-menu nobullet flex mas">
{{#each chatroom in model}}
<li class="mrs">{{link-to chatroom.name "chatrooms.show" chatroom class="pat"}}</li>
{{/each}}
</ul>
{{outlet}}
In this case, model is an array of chatrooms.
My routes:
app/routes/chatrooms.js
export default Ember.Route.extend({
model: function() {
return this.store.find('chatroom');
}
});
app/routes/chatrooms/show.js
export default Ember.Route.extend({
model: function(params) {
return this.store.get('chatroom', params.chatroom_id);
},
actions: {
send: function() {
...
}
}
});
As discussed in this thread, when you link-to a route and the model is already loaded, the model hook of the route is not fired because there’s no need to reload the data.
If you transition to a route and all the context objects -the objects which will serve as models to templates- are passed in, the beforeModel and model hooks will not be called.
Later in the thread balint corrects:
In fact, the beforeModel hook still gets called in that case, it is only the model hook that does not.
If you want to force the model to be reloaded, you can change your link to use the ID instead of the model:
{{link-to chatroom.name "chatrooms.show" chatroom.id class="pat"}}
You could also load the data in the beforeModel or afterModel hooks, or setupController.
Also, in the chatrooms/show route, you are getting the already-loaded model from the Ember Data store rather than loading it from the server. Try this:
return this.store.find('chatroom', params.chatroom_id);
I ended up adding a links property to the JSON response for chatrooms. When the content of a chatroom has to be displayed, the link is used and the messages retrieved. It only requires two requests, and there's not need to preload all the messages from all the chatrooms and no need to make a request for each message.

Proper way to set multiple models on route; depending on user authentication?

I'm currently working on an Ember app and it is coming along fine but since I am new to MVC applications in general there are a lot of concepts that don't come naturally to me.
I am currently trying to return two models for my index route. I referred to another SO question (EmberJS: How to load multiple models on the same route?) for the correct method and it has worked great.
My problem is now that I need to only set one of the two models only if the user is authenticated. I am using ember-simple-auth, and currently this is what I've got:
// app/routes/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
if (this.get('session.isAuthenticated')) {
var _this = this;
this.get('store').find('user', this.get('session.uid')).then(function(user) {
_this.set('model.entries', user.get('entries'));
});
}
return Ember.RSVP.hash({
newEntry: this.get('store').createRecord('entry', {
body: 'Write here ...'
})
});
}
});
For some reason, this does not work. After my route is loaded, the model only has the 'newEntry' property and not an 'entries' property, although the promise does get fulfilled (I put console.logs inside to prove it).
What could be happening? And is this the best way to accomplish this?
There is a set of data that you always want to load, for every user. Do that in the model hook, that is actually the data for the route.
There is another piece of info that you want to add only if a condition is met (authentication). Do that in the afterModel hook.
...is provided the route's resolved model...
http://emberjs.com/api/classes/Ember.Route.html#method_afterModel
So, now you can append or remove data from the model. Or take any relevant action depending on the data that you received.

How can I send a message / call a method on all itemController children instances in Ember?

I'm trying to use the "Buffered Proxy" pattern on a collection of items in a form from a hasMany model. When complete, I'm trying to have a "Save" button, which triggers a save action, allow me to save all the as-yet unsaved changes I've made. More info on the BP in Ember:
http://coryforsyth.com/2013/06/27/ember-buffered-proxy-and-method-missing/
I can get all this to work fine with a top level model attribute, but I'm confused as to how to tell all my non-singleton itemControllers that I want them to save their buffers, then be able to call the grandparent to save the whole enchilada. I was hoping I'd be able to do something like this from the parent array controller:
actions: {
saveStuff: function() {
// Something like this possible?
this.get('allTheNonSingletonItemControllerChildren').send('saveThoseBuffers');
}
}
Child controller:
saveThoseBuffers: function() {
var grandParent = this.get('parentController').get('parentController');
this.applyBufferedChanges();
grandParent.saveEntireRecord(); // Not sure how this would work yet - can't use 'needs' because none of these controllers are singletons.
}
Grandparent:
saveEntireRecord: function() {
this.get('model').save().then(function() {
//other stuff;
}
}
View is something like:
{{#each stuff in childitems itemController="childController"}}
{{input type="text" value=stuff.name}}
{{/each}}
<button {{action 'saveStuff'}}>Save</button>
Nothing in the docs or SO has revealed the incantations for this.
UPDATE:
Based on a suggestion, I also tried:
children = this.get('content');
children.forEach(function(child) {
child.send('saveThoseBuffers');
});
but received:
"Uncaught Error: Attempted to handle event saveThoseBuffers on while in state root.loaded.saved."
UPDATE 2:
Versions:
DEBUG: Ember : 1.5.0-beta.2 ember.js:3496
DEBUG: Ember Data : 1.0.0-beta.7+canary.b45e23ba ember.js:3496
DEBUG: Handlebars : 1.3.0 ember.js:3496
DEBUG: jQuery : 1.9.1 ember.js:3496
UPDATE 3:
Tried getting access to subcontrollers using:
var children = this.get('_subControllers');
That always returns an empty array, regardless of where itemController is set (in the ArrayController or in each loops using itemController=)
UPDATE 4:
I've created a JSFiddle that shows what I'm attempting is possible using _subControllers:
http://jsfiddle.net/spA9Q/5/
However, it only works by doing some setup in the route using setupController, which I don't see how I can use in my application (the controller in question cannot be named the same as the model, as it's for one 'mode' of viewing/editing that model using {{render}} and it uses an async
hasMany relationship.)
None of the above methods worked (hopefully Buffered Proxy will be fleshed out and officially support/integrated into Ember someday soon, as not saving nested models until buttons are pushed is a common use case) so I wound up with the following in the parent controller, which does the job:
childModels = this.get('child.content.content');
childModels.forEach(function(child) {
child.rollback(); // or .save()
});